Compare commits

...
74 Commits
Author SHA1 Message Date
Gani Georgiev 37ff943f67 bumped GitHub action min Go version 2025-01-18 12:48:28 +02:00
Gani Georgiev c7322eec66 updated old v0.22.x changelog 2025-01-18 12:17:07 +02:00
Gani Georgiev 7964669a20 updated changelog 2025-01-18 12:13:55 +02:00
Gani Georgiev b8ea953059 [#6309] fixed fields extraction for view query with multi-level comments 2025-01-18 12:11:36 +02:00
Gani Georgiev 2317695011 bumped app version 2025-01-12 11:40:03 +02:00
Gani Georgiev 8b89bce2a8 [#6281] fixed unique validator error 2025-01-12 11:37:24 +02:00
Gani Georgiev 7175992ce4 updated go deps and jsvm types 2025-01-11 12:33:46 +02:00
Gani Georgiev 63fb2b6506 updated README 2025-01-11 12:28:17 +02:00
Gani Georgiev 1e9847e924 reload created or edited records data in the RecordsPicker 2025-01-10 09:41:46 +02:00
Gani Georgiev c1c499fc1f bumped package version 2025-01-10 08:53:42 +02:00
Gani Georgiev cad16fac6b [#6229] fixed display fields extraction 2025-01-05 14:33:16 +02:00
Gani Georgiev 9414986ca0 updated changelog 2025-01-04 09:24:57 +02:00
Gani Georgiev 6628cdf893 updated tygoja and regenerated jsvm types 2025-01-03 22:11:25 +02:00
Gani Georgiev e521e5343a fixed JSVM types for structs and functions with multiple generic parameters 2025-01-03 21:44:07 +02:00
Gani Georgiev 412341fd78 added missing time macros in the UI autocomplete 2025-01-03 21:38:13 +02:00
Gani Georgiev a8b9aee24e updated otp docs 2025-01-02 08:37:37 +02:00
Gani Georgiev 36df16aaa8 reorganized changelog 2025-01-01 17:49:30 +02:00
Gani Georgiev 44a3e8478d updated go deps and jsvm types 2025-01-01 17:47:17 +02:00
Gani Georgiev 73f1b223ff directly resolve to null for auth check with missing RequestInfo.Auth field 2025-01-01 17:19:30 +02:00
Gani Georgiev c8b29edf9d bumped app version 2025-01-01 16:49:26 +02:00
Gani Georgiev a43f4bf155 reuse the random identifier 2025-01-01 16:41:47 +02:00
Gani Georgiev 3074ed3c5e fixed comment typos 2024-12-30 21:58:29 +02:00
Gani Georgiev 26cb1cef37 added ServeEvent.InstallerFunc field 2024-12-30 20:30:07 +02:00
Gani Georgiev 0155e9333f removed unnecessary request params 2024-12-29 22:29:53 +02:00
Gani Georgiev 118399cc12 fetch colletions list and scaffolds concurrently 2024-12-29 22:26:56 +02:00
Gani Georgiev b062bc6d16 updated jsvm types 2024-12-29 17:46:37 +02:00
Gani Georgiev a8952cfca2 [#6201] expanded the hidden fields check and allow targetting hidden fields in the List API rule 2024-12-29 17:31:58 +02:00
Gani Georgiev 2af9b554ad updated ui/dist 2024-12-28 10:44:51 +02:00
Gani Georgiev 2ef5459698 removed the trailing slash from the env urls 2024-12-28 10:32:14 +02:00
Gani Georgiev 074e977e90 use typed int64 const 2024-12-28 10:23:40 +02:00
Gani Georgiev 07fb052da1 added extra validators for the collection int64 field options 2024-12-28 10:13:18 +02:00
Gani Georgiev 6c53352643 [#6184] fixed unique identity fields input reactivity 2024-12-26 21:37:06 +02:00
Gani Georgiev 00372711fd updated changelog 2024-12-26 13:25:02 +02:00
Gani Georgiev d34c8ec048 added record.SetRandomPassword() helper and updated oauth2 autogenerated password handling 2024-12-26 13:24:03 +02:00
Gani Georgiev d8c0b11271 updated npm deps 2024-12-25 22:37:00 +02:00
Gani Georgiev 56f951e5a2 added crons web apis and ui listing 2024-12-25 22:24:24 +02:00
Gani Georgiev ed1917b307 [#6166] added auth collection select for the settings Send test email popup 2024-12-24 18:10:39 +02:00
Gani Georgiev 47bd4ca11e eagerly interrupt waiting for the email alert send in case it takes longer than 15s 2024-12-24 12:13:33 +02:00
Gani Georgiev 4824701b6c cache jsvm reflect created types 2024-12-24 11:07:26 +02:00
Gani Georgiev 39df26ee21 changed store.Store to accept generic key type 2024-12-23 15:44:00 +02:00
Gani Georgiev e18116d859 synced with master 2024-12-22 16:27:44 +02:00
Gani Georgiev bae5421d62 added Cron.Jobs() method 2024-12-22 16:05:38 +02:00
Gani Georgiev f27d9f1dc9 synced with master 2024-12-22 10:24:44 +02:00
Gani Georgiev 884a3dec4a update js sdk 2024-12-19 22:39:22 +02:00
Gani Georgiev cb0335c2b6 normalized negative multipart/form-data numeric inferred value check 2024-12-19 17:23:03 +02:00
Gani Georgiev 274d499279 synced with master 2024-12-19 11:46:35 +02:00
Gani Georgiev a3377c992b fixed code comment typos 2024-12-19 11:41:34 +02:00
Gani Georgiev 76b9051011 [#5964] refresh the token key on email change 2024-12-17 11:44:27 +02:00
Gani Georgiev 0d720c3c9d print inline attachments in the dev mail send 2024-12-17 09:28:26 +02:00
Gani Georgiev 3e5e02a32c synced with master 2024-12-16 16:03:51 +02:00
Gani Georgiev fd9ae0fd1c updated js sdk 2024-12-16 10:38:20 +02:00
Gani Georgiev 7ddb0db9a4 added extra html escaping for the RECORD:* placeholders as extra measure in case the email are stored as plain html 2024-12-16 10:36:25 +02:00
Gani Georgiev 011f323bcc updated restore backup log error from debug to warn 2024-12-14 10:40:55 +02:00
Gani Georgiev d5d764f83e avoid unnecessary copying the backup archive on restore when the local file system is used 2024-12-14 10:16:36 +02:00
Gani Georgiev 239daf2023 added mailer.Message.InlineAttachments field 2024-12-13 20:37:24 +02:00
Gani Georgiev 2b2dafaf88 synced with master 2024-12-13 20:26:05 +02:00
Gani Georgiev 8f32825cff updated jsvm types 2024-12-12 23:02:42 +02:00
Gani Georgiev 20d378cd76 updated collection db methods godoc 2024-12-12 23:01:01 +02:00
Gani Georgiev 8e63e81561 speedup records cascade delete 2024-12-12 22:55:55 +02:00
Gani Georgiev efe4ef500b removed the create api rule tooltip 2024-12-12 20:25:44 +02:00
Gani Georgiev 09a24e1de6 synced with master 2024-12-12 12:17:02 +02:00
Gani Georgiev 9490a220bc moved unnecessary reassignment 2024-12-12 12:11:47 +02:00
Gani Georgiev e51456bce2 [#6073] added poc implementation for the dry submit removal 2024-12-11 18:43:48 +02:00
Gani Georgiev 35196674e6 skip unnecessary validator in case the default pattern is used 2024-12-11 18:27:21 +02:00
Gani Georgiev 7481c3f7f4 replaced LOWER with COLLATE NOCASE 2024-12-11 11:08:29 +02:00
Gani Georgiev 3ec10a9c7d fixed log message typo 2024-12-11 11:07:42 +02:00
Gani Georgiev 3634fd9c26 synced with master 2024-12-09 08:25:44 +02:00
Gani Georgiev f7c85940c4 synced with master 2024-12-08 14:43:07 +02:00
Gani Georgiev 6edb344ab3 synced with upstream s3 driver 2024-12-07 12:15:03 +02:00
Gani Georgiev c91d889da3 udpated :lower modifier to apply after all other normalizations 2024-12-06 22:09:29 +02:00
Gani Georgiev 6a4e04533c added tests.NewTestAppWithConfig helper 2024-12-06 21:38:57 +02:00
Gani Georgiev e8f49c31e4 added :lower modifier 2024-12-06 16:10:11 +02:00
Gani Georgiev 1abd6ca5c0 Merge branch 'master' into develop 2024-12-06 14:21:37 +02:00
Gani Georgiev 7c01441392 added yesterday and tomorrow date filter macros 2024-12-05 13:34:11 +02:00
136 changed files with 6606 additions and 5345 deletions

No files matched your search

+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '>=1.23.4'
go-version: '>=1.23.5'
# This step usually is not needed because the /ui/dist is pregenerated locally
# but its here to ensure that each release embeds the latest admin ui artifacts.
+75 -3
View File
@@ -1,8 +1,80 @@
## v0.24.4
- Fixed fields extraction for view query with nested comments ([#6309](https://github.com/pocketbase/pocketbase/discussions/6309)).
- Bumped GitHub action min Go version to 1.23.5 as it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.23.5).
## v0.24.3
- Fixed incorrectly reported unique validator error for fields starting with name of another field ([#6281](https://github.com/pocketbase/pocketbase/pull/6281); thanks @svobol13).
- Reload the created/edited records data in the RecordsPicker UI.
- Updated Go dependencies.
## v0.24.2
- Fixed display fields extraction when there are multiple "Presentable" `relation` fields in a single related collection ([#6229](https://github.com/pocketbase/pocketbase/issues/6229)).
## v0.24.1
- Added missing time macros in the UI autocomplete.
- Fixed JSVM types for structs and functions with multiple generic parameters.
## v0.24.0
- ⚠️ Removed the "dry submit" when executing the collections Create API rule
(you can find more details why this change was introduced and how it could affect your app in https://github.com/pocketbase/pocketbase/discussions/6073).
For most users it should be non-breaking change, BUT if you have Create API rules that uses self-references or view counters you may have to adjust them manually.
With this change the "multi-match" operators are also normalized in case the targeted collection doesn't have any records
(_or in other words, `@collection.example.someField != "test"` will result to `true` if `example` collection has no records because it satisfies the condition that all available "example" records mustn't have `someField` equal to "test"_).
As a side-effect of all of the above minor changes, the record create API performance has been also improved ~4x times in high concurrent scenarios (500 concurrent clients inserting total of 50k records - [old (58.409064001s)](https://github.com/pocketbase/benchmarks/blob/54140be5fb0102f90034e1370c7f168fbcf0ddf0/results/hetzner_cax41_cgo.md#creating-50000-posts100k-reqs50000-conc500-rulerequestauthid----requestdatapublicisset--true) vs [new (13.580098262s)](https://github.com/pocketbase/benchmarks/blob/7df0466ac9bd62fe0a1056270d20ef82012f0234/results/hetzner_cax41_cgo.md#creating-50000-posts100k-reqs50000-conc500-rulerequestauthid----requestbodypublicisset--true)).
- ⚠️ Changed the type definition of `store.Store[T any]` to `store.Store[K comparable, T any]` to allow support for custom store key types.
For most users it should be non-breaking change, BUT if you are calling `store.New[any](nil)` instances you'll have to specify the store key type, aka. `store.New[string, any](nil)`.
- Added `@yesterday` and `@tomorrow` datetime filter macros.
- Added `:lower` filter modifier (e.g. `title:lower = "lorem"`).
- Added `mailer.Message.InlineAttachments` field for attaching inline files to an email (_aka. `cid` links_).
- Added cache for the JSVM `arrayOf(m)`, `DynamicModel`, etc. dynamic `reflect` created types.
- Added auth collection select for the settings "Send test email" popup ([#6166](https://github.com/pocketbase/pocketbase/issues/6166)).
- Added `record.SetRandomPassword()` to simplify random password generation usually used in the OAuth2 or OTP record creation flows.
_The generated ~30 chars random password is assigned directly as bcrypt hash and ignores the `password` field plain value validators like min/max length or regex pattern._
- Added option to list and trigger the registered app level cron jobs via the Web API and UI.
- Added extra validators for the collection field `int64` options (e.g. `FileField.MaxSize`) restricting them to the max safe JSON number (2^53-1).
- Added option to unset/overwrite the default PocketBase superuser installer using `ServeEvent.InstallerFunc`.
- Added `app.FindCachedCollectionReferences(collection, excludeIds)` to speedup records cascade delete almost twice for projects with many collections.
- Added `tests.NewTestAppWithConfig(config)` helper if you need more control over the test configurations like `IsDev`, the number of allowed connections, etc.
- Invalidate all record tokens when the auth record email is changed programmatically or by a superuser ([#5964](https://github.com/pocketbase/pocketbase/issues/5964)).
- Eagerly interrupt waiting for the email alert send in case it takes longer than 15s.
- Normalized the hidden fields filter checks and allow targetting hidden fields in the List API rule.
- Fixed "Unique identify fields" input not refreshing on unique indexes change ([#6184](https://github.com/pocketbase/pocketbase/issues/6184)).
## v0.23.12
- Added warning logs in case of mismatched `modernc.org/sqlite` and `modernc.org/libs` versions ([#6136](https://github.com/pocketbase/pocketbase/issues/6136#issuecomment-2556336962)).
- Added warning logs in case of mismatched `modernc.org/sqlite` and `modernc.org/libc` versions ([#6136](https://github.com/pocketbase/pocketbase/issues/6136#issuecomment-2556336962)).
- Skipped the default body size limit middleware for the backup upload endpooint ([#6152](https://github.com/pocketbase/pocketbase/issues/6152)).
- Skipped the default body size limit middleware for the backup upload endpoint ([#6152](https://github.com/pocketbase/pocketbase/issues/6152)).
## v0.23.11
@@ -55,7 +127,7 @@
- Other minor fixes (comment typos, JSVM types update).
- Updated Go deps and the min Go releleaser GitHub action version to 1.23.4.
- Updated Go deps and the min Go release GitHub action version to 1.23.4.
## v0.23.4
+7
View File
@@ -2,6 +2,13 @@
> For the most recent versions, please refer to [CHANGELOG.md](./CHANGELOG.md)
---
## v0.22.30
- (_Backported from v0.24.4_) Fixed fields extraction for view queries with nested comments ([#6309](https://github.com/pocketbase/pocketbase/discussions/6309)).
- Bumped GitHub action min Go version to 1.23.5 as it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.23.5).
## v0.22.29
- (_Backported from v0.23.11_) Upgraded `golang.org/x/net` to 0.33.0 to fix [CVE-2024-45338](https://www.cve.org/CVERecord?id=CVE-2024-45338).
+6 -3
View File
@@ -25,10 +25,13 @@
## API SDK clients
The easiest way to interact with the API is to use one of the official SDK clients:
The easiest way to interact with the PocketBase Web APIs is to use one of the official SDK clients:
- **JavaScript - [pocketbase/js-sdk](https://github.com/pocketbase/js-sdk)** (_Browser, Node.js, React Native_)
- **Dart - [pocketbase/dart-sdk](https://github.com/pocketbase/dart-sdk)** (_Web, Mobile, Desktop, CLI_)
You could also check the recommendations in https://pocketbase.io/docs/how-to-use/.
- **JavaScript - [pocketbase/js-sdk](https://github.com/pocketbase/js-sdk)** (_browser and node_)
- **Dart - [pocketbase/dart-sdk](https://github.com/pocketbase/dart-sdk)** (_web, mobile, desktop_)
## Overview
+1
View File
@@ -41,6 +41,7 @@ func NewRouter(app core.App) (*router.Router[*core.RequestEvent], error) {
bindRecordAuthApi(app, apiGroup)
bindLogsApi(app, apiGroup)
bindBackupApi(app, apiGroup)
bindCronApi(app, apiGroup)
bindFileApi(app, apiGroup)
bindBatchApi(app, apiGroup)
bindRealtimeApi(app, apiGroup)
+59
View File
@@ -0,0 +1,59 @@
package apis
import (
"net/http"
"slices"
"strings"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/cron"
"github.com/pocketbase/pocketbase/tools/router"
"github.com/pocketbase/pocketbase/tools/routine"
)
// bindCronApi registers the crons api endpoint.
func bindCronApi(app core.App, rg *router.RouterGroup[*core.RequestEvent]) {
subGroup := rg.Group("/crons").Bind(RequireSuperuserAuth())
subGroup.GET("", cronsList)
subGroup.POST("/{id}", cronRun)
}
func cronsList(e *core.RequestEvent) error {
jobs := e.App.Cron().Jobs()
slices.SortStableFunc(jobs, func(a, b *cron.Job) int {
if strings.HasPrefix(a.Id(), "__pb") {
return 1
}
if strings.HasPrefix(b.Id(), "__pb") {
return -1
}
return strings.Compare(a.Id(), b.Id())
})
return e.JSON(http.StatusOK, jobs)
}
func cronRun(e *core.RequestEvent) error {
cronId := e.Request.PathValue("id")
var foundJob *cron.Job
jobs := e.App.Cron().Jobs()
for _, j := range jobs {
if j.Id() == cronId {
foundJob = j
break
}
}
if foundJob == nil {
return e.NotFoundError("Missing or invalid cron job", nil)
}
routine.FireAndForget(func() {
foundJob.Run()
})
return e.NoContent(http.StatusNoContent)
}
+149
View File
@@ -0,0 +1,149 @@
package apis_test
import (
"net/http"
"testing"
"time"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tests"
"github.com/spf13/cast"
)
func TestCronsList(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
Method: http.MethodGet,
URL: "/api/crons",
ExpectedStatus: 401,
ExpectedContent: []string{`"data":{}`},
ExpectedEvents: map[string]int{"*": 0},
},
{
Name: "authorized as regular user",
Method: http.MethodGet,
URL: "/api/crons",
Headers: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
},
ExpectedStatus: 403,
ExpectedContent: []string{`"data":{}`},
ExpectedEvents: map[string]int{"*": 0},
},
{
Name: "authorized as superuser (empty list)",
Method: http.MethodGet,
URL: "/api/crons",
Headers: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
},
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
app.Cron().RemoveAll()
},
ExpectedStatus: 200,
ExpectedContent: []string{`[]`},
ExpectedEvents: map[string]int{"*": 0},
},
{
Name: "authorized as superuser",
Method: http.MethodGet,
URL: "/api/crons",
Headers: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
},
ExpectedStatus: 200,
ExpectedContent: []string{
`{"id":"__pbLogsCleanup__","expression":"0 */6 * * *"}`,
`{"id":"__pbDBOptimize__","expression":"0 0 * * *"}`,
`{"id":"__pbMFACleanup__","expression":"0 * * * *"}`,
`{"id":"__pbOTPCleanup__","expression":"0 * * * *"}`,
},
ExpectedEvents: map[string]int{"*": 0},
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}
func TestCronsRun(t *testing.T) {
t.Parallel()
beforeTestFunc := func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
app.Cron().Add("test", "* * * * *", func() {
app.Store().Set("testJobCalls", cast.ToInt(app.Store().Get("testJobCalls"))+1)
})
}
expectedCalls := func(expected int) func(t testing.TB, app *tests.TestApp, res *http.Response) {
return func(t testing.TB, app *tests.TestApp, res *http.Response) {
total := cast.ToInt(app.Store().Get("testJobCalls"))
if total != expected {
t.Fatalf("Expected total testJobCalls %d, got %d", expected, total)
}
}
}
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
Method: http.MethodPost,
URL: "/api/crons/test",
Delay: 50 * time.Millisecond,
BeforeTestFunc: beforeTestFunc,
AfterTestFunc: expectedCalls(0),
ExpectedStatus: 401,
ExpectedContent: []string{`"data":{}`},
ExpectedEvents: map[string]int{"*": 0},
},
{
Name: "authorized as regular user",
Method: http.MethodPost,
URL: "/api/crons/test",
Headers: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
},
Delay: 50 * time.Millisecond,
BeforeTestFunc: beforeTestFunc,
AfterTestFunc: expectedCalls(0),
ExpectedStatus: 403,
ExpectedContent: []string{`"data":{}`},
ExpectedEvents: map[string]int{"*": 0},
},
{
Name: "authorized as superuser (missing job)",
Method: http.MethodPost,
URL: "/api/crons/missing",
Headers: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
},
Delay: 50 * time.Millisecond,
BeforeTestFunc: beforeTestFunc,
AfterTestFunc: expectedCalls(0),
ExpectedStatus: 404,
ExpectedContent: []string{`"data":{}`},
ExpectedEvents: map[string]int{"*": 0},
},
{
Name: "authorized as superuser (existing job)",
Method: http.MethodPost,
URL: "/api/crons/test",
Headers: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
},
Delay: 50 * time.Millisecond,
BeforeTestFunc: beforeTestFunc,
AfterTestFunc: expectedCalls(1),
ExpectedStatus: 204,
ExpectedEvents: map[string]int{"*": 0},
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}
+30 -36
View File
@@ -5,37 +5,31 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"time"
"github.com/fatih/color"
"github.com/go-ozzo/ozzo-validation/v4/is"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/security"
"github.com/pocketbase/pocketbase/tools/osutils"
)
// @todo consider combining with the installer specific hooks after refactoring cmd
func loadInstaller(app core.App, dashboardURL string) error {
if !needInstallerSuperuser(app) {
return nil
}
installerRecord, err := findOrCreateInstallerSuperuser(app)
if err != nil {
return err
}
token, err := installerRecord.NewStaticAuthToken(30 * time.Minute)
// DefaultInstallerFunc is the default PocketBase installer function.
//
// It will attempt to open a link in the browser (with a short-lived auth
// token for the systemSuperuser) to the installer UI so that users can
// create their own custom superuser record.
//
// See https://github.com/pocketbase/pocketbase/discussions/5814.
func DefaultInstallerFunc(app core.App, systemSuperuser *core.Record, baseURL string) error {
token, err := systemSuperuser.NewStaticAuthToken(30 * time.Minute)
if err != nil {
return err
}
// launch url (ignore errors and always print a help text as fallback)
url := fmt.Sprintf("%s/#/pbinstal/%s", strings.TrimRight(dashboardURL, "/"), token)
_ = launchURL(url)
url := fmt.Sprintf("%s/_/#/pbinstal/%s", strings.TrimRight(baseURL, "/"), token)
_ = osutils.LaunchURL(url)
color.Magenta("\n(!) Launch the URL below in the browser if it hasn't been open already to create your first superuser account:")
color.New(color.Bold).Add(color.FgCyan).Println(url)
color.New(color.FgHiBlack, color.Italic).Printf("(you can also create your first superuser by running: %s superuser upsert EMAIL PASS)\n\n", os.Args[0])
@@ -43,6 +37,23 @@ func loadInstaller(app core.App, dashboardURL string) error {
return nil
}
func loadInstaller(
app core.App,
baseURL string,
installerFunc func(app core.App, systemSuperuser *core.Record, baseURL string) error,
) error {
if installerFunc == nil || !needInstallerSuperuser(app) {
return nil
}
superuser, err := findOrCreateInstallerSuperuser(app)
if err != nil {
return err
}
return installerFunc(app, superuser, baseURL)
}
func needInstallerSuperuser(app core.App) bool {
total, err := app.CountRecords(core.CollectionNameSuperusers, dbx.Not(dbx.HashExp{
"email": core.DefaultInstallerEmail,
@@ -65,7 +76,7 @@ func findOrCreateInstallerSuperuser(app core.App) (*core.Record, error) {
record = core.NewRecord(col)
record.SetEmail(core.DefaultInstallerEmail)
record.SetPassword(security.RandomString(30))
record.SetRandomPassword()
err = app.Save(record)
if err != nil {
@@ -75,20 +86,3 @@ func findOrCreateInstallerSuperuser(app core.App) (*core.Record, error) {
return record, nil
}
func launchURL(url string) error {
if err := is.URL.Validate(url); err != nil {
return err
}
switch runtime.GOOS {
case "darwin":
return exec.Command("open", url).Start()
case "windows":
// not sure if this is the best command but seems to be the most reliable based on the comments in
// https://stackoverflow.com/questions/3739327/launching-a-website-via-the-windows-commandline#answer-49115945
return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
default: // linux, freebsd, etc.
return exec.Command("xdg-open", url).Start()
}
}
+5 -5
View File
@@ -111,7 +111,7 @@ func checkCollectionRateLimit(e *core.RequestEvent, collection *core.Collection,
//
//nolint:unused
func isClientRateLimited(e *core.RequestEvent, rtId string) bool {
rateLimiters, ok := e.App.Store().Get(rateLimitersStoreKey).(*store.Store[*rateLimiter])
rateLimiters, ok := e.App.Store().Get(rateLimitersStoreKey).(*store.Store[string, *rateLimiter])
if !ok || rateLimiters == nil {
return false
}
@@ -146,7 +146,7 @@ func checkRateLimit(e *core.RequestEvent, rtId string, rule core.RateLimitRule)
rateLimiters := e.App.Store().GetOrSet(rateLimitersStoreKey, func() any {
return initRateLimitersStore(e.App)
}).(*store.Store[*rateLimiter])
}).(*store.Store[string, *rateLimiter])
if rateLimiters == nil {
e.App.Logger().Warn("Failed to retrieve app rate limiters store")
return nil
@@ -198,9 +198,9 @@ func destroyRateLimitersStore(app core.App) {
app.Store().Remove(rateLimitersStoreKey)
}
func initRateLimitersStore(app core.App) *store.Store[*rateLimiter] {
func initRateLimitersStore(app core.App) *store.Store[string, *rateLimiter] {
app.Cron().Add(rateLimitersCronKey, "2 * * * *", func() { // offset a little since too many cleanup tasks execute at 00
limitersStore, ok := app.Store().Get(rateLimitersStoreKey).(*store.Store[*rateLimiter])
limitersStore, ok := app.Store().Get(rateLimitersStoreKey).(*store.Store[string, *rateLimiter])
if !ok {
return
}
@@ -225,7 +225,7 @@ func initRateLimitersStore(app core.App) *store.Store[*rateLimiter] {
},
})
return store.New[*rateLimiter](nil)
return store.New[string, *rateLimiter](nil)
}
func newRateLimiter(maxAllowed int, intervalInSec int64, minDeleteIntervalInSec int64) *rateLimiter {
+2 -3
View File
@@ -38,9 +38,8 @@ func recordConfirmEmailChange(e *core.RequestEvent) error {
event.NewEmail = newEmail
return e.App.OnRecordConfirmEmailChangeRequest().Trigger(event, func(e *core.RecordConfirmEmailChangeRequestEvent) error {
authRecord.Set(core.FieldNameEmail, e.NewEmail)
authRecord.Set(core.FieldNameVerified, true)
authRecord.RefreshTokenKey() // invalidate old tokens
e.Record.SetEmail(e.NewEmail)
e.Record.SetVerified(true)
if err := e.App.Save(e.Record); err != nil {
return firstApiError(err, e.BadRequestError("Failed to confirm email change.", err))
+2 -2
View File
@@ -47,12 +47,12 @@ func recordConfirmPasswordReset(e *core.RequestEvent) error {
}
}
err = form.app.Save(authRecord)
err = e.App.Save(authRecord)
if err != nil {
return firstApiError(err, e.BadRequestError("Failed to set new password.", err))
}
form.app.Store().Remove(getPasswordResetResendKey(authRecord))
e.App.Store().Remove(getPasswordResetResendKey(authRecord))
return e.NoContent(http.StatusNoContent)
})
@@ -186,11 +186,20 @@ func TestRecordConfirmPasswordReset(t *testing.T) {
t.Fatal("Expected the user to be unverified")
}
oldTokenKey := user.TokenKey()
// manually change the email to check whether the verified state will be updated
user.SetEmail("test_update@example.com")
if err := app.Save(user); err != nil {
if err = app.Save(user); err != nil {
t.Fatalf("Failed to update user test email: %v", err)
}
// resave with the old token key since the email change above
// would change it and will make the password token invalid
user.SetTokenKey(oldTokenKey)
if err = app.Save(user); err != nil {
t.Fatalf("Failed to restore original user tokenKey: %v", err)
}
},
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
_, err := app.FindAuthRecordByToken(
+1 -8
View File
@@ -16,7 +16,6 @@ import (
"github.com/pocketbase/pocketbase/tools/auth"
"github.com/pocketbase/pocketbase/tools/dbutils"
"github.com/pocketbase/pocketbase/tools/filesystem"
"github.com/pocketbase/pocketbase/tools/security"
"golang.org/x/oauth2"
)
@@ -224,12 +223,6 @@ func oauth2Submit(e *core.RecordAuthWithOAuth2RequestEvent, optExternalAuth *cor
payload[core.FieldNameEmail] = e.OAuth2User.Email
// set a random password if none is set
if v, _ := payload[core.FieldNamePassword].(string); v == "" {
payload[core.FieldNamePassword] = security.RandomString(30)
payload[core.FieldNamePassword+"Confirm"] = payload[core.FieldNamePassword]
}
// map known fields (unless the field was explicitly submitted as part of CreateData)
if _, ok := payload[e.Collection.OAuth2.MappedFields.Id]; !ok && e.Collection.OAuth2.MappedFields.Id != "" {
payload[e.Collection.OAuth2.MappedFields.Id] = e.OAuth2User.Id
@@ -292,7 +285,7 @@ func oauth2Submit(e *core.RecordAuthWithOAuth2RequestEvent, optExternalAuth *cor
// set random password for users with unverified email
// (this is in case a malicious actor has registered previously with the user email)
if !isLoggedAuthRecord && e.Record.Email() != "" && !e.Record.Verified() {
e.Record.SetPassword(security.RandomString(30))
e.Record.SetRandomPassword()
needUpdate = true
}
+22 -1
View File
@@ -558,12 +558,21 @@ func TestRecordAuthWithOAuth2(t *testing.T) {
t.Fatalf("Expected password %q to be valid", "1234567890")
}
oldTokenKey := user.TokenKey()
// manually unset the user email
user.SetEmail("")
if err := app.Save(user); err != nil {
if err = app.Save(user); err != nil {
t.Fatal(err)
}
// resave with the old token key since the email change above
// would change it and will make the password token invalid
user.SetTokenKey(oldTokenKey)
if err = app.Save(user); err != nil {
t.Fatalf("Failed to restore original user tokenKey: %v", err)
}
// register the test provider
auth.Providers["test"] = func() auth.Provider {
return &oauth2MockProvider{
@@ -949,6 +958,8 @@ func TestRecordAuthWithOAuth2(t *testing.T) {
"createData": {
"email": "invalid",
"emailVisibility": true,
"password": "1234567890",
"passwordConfirm": "1234567890",
"name": "test_name",
"username": "test_username",
"rel": "0yxhwia2amd8gec"
@@ -1018,6 +1029,16 @@ func TestRecordAuthWithOAuth2(t *testing.T) {
"OnModelValidate": 4,
"OnRecordValidate": 4,
},
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
user, err := app.FindFirstRecordByData("users", "username", "test_username")
if err != nil {
t.Fatal(err)
}
if !user.ValidatePassword("1234567890") {
t.Fatalf("Expected password %q to be valid", "1234567890")
}
},
},
{
Name: "creating user (with mapped OAuth2 fields and avatarURL->file field)",
+1 -6
View File
@@ -85,12 +85,7 @@ func recordAuthWithOTP(e *core.RequestEvent) error {
e.App.Logger().Error("Failed to delete used OTP", "error", err, "otpId", e.OTP.Id)
}
err = RecordAuthResponse(e.RequestEvent, e.Record, core.MFAMethodOTP, nil)
if err != nil {
return err
}
return nil
return RecordAuthResponse(e.RequestEvent, e.Record, core.MFAMethodOTP, nil)
})
}
+134 -53
View File
@@ -13,9 +13,11 @@ import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/forms"
"github.com/pocketbase/pocketbase/tools/filesystem"
"github.com/pocketbase/pocketbase/tools/inflector"
"github.com/pocketbase/pocketbase/tools/list"
"github.com/pocketbase/pocketbase/tools/router"
"github.com/pocketbase/pocketbase/tools/search"
"github.com/pocketbase/pocketbase/tools/security"
)
// bindRecordCrudApi registers the record crud api endpoints and
@@ -57,23 +59,27 @@ func recordsList(e *core.RequestEvent) error {
return err
}
fieldsResolver := core.NewRecordFieldResolver(
e.App,
collection,
requestInfo,
// hidden fields are searchable only by superusers
requestInfo.HasSuperuserAuth(),
)
query := e.App.RecordQuery(collection)
searchProvider := search.NewProvider(fieldsResolver).
Query(e.App.RecordQuery(collection))
fieldsResolver := core.NewRecordFieldResolver(e.App, collection, requestInfo, true)
if !requestInfo.HasSuperuserAuth() && collection.ListRule != nil {
searchProvider.AddFilter(search.FilterData(*collection.ListRule))
if !requestInfo.HasSuperuserAuth() && collection.ListRule != nil && *collection.ListRule != "" {
expr, err := search.FilterData(*collection.ListRule).BuildExpr(fieldsResolver)
if err != nil {
return err
}
query.AndWhere(expr)
// will be applied by the search provider right before executing the query
// fieldsResolver.UpdateQuery(query)
}
records := []*core.Record{}
// hidden fields are searchable only by superusers
fieldsResolver.SetAllowHiddenFields(requestInfo.HasSuperuserAuth())
searchProvider := search.NewProvider(fieldsResolver).Query(query)
records := []*core.Record{}
result, err := searchProvider.ParseAndExec(e.Request.URL.Query().Encode(), &records)
if err != nil {
return firstApiError(err, e.BadRequestError("", err))
@@ -92,9 +98,9 @@ func recordsList(e *core.RequestEvent) error {
// Add a randomized throttle in case of too many empty search filter attempts.
//
// This is just for extra precaution since security researches raised concern regarding the possibity of eventual
// This is just for extra precaution since security researches raised concern regarding the possibility of eventual
// timing attacks because the List API rule acts also as filter and executes in a single run with the client-side filters.
// This is by design and it is an accepted tradeoff between performance, usability and correctness.
// This is by design and it is an accepted trade off between performance, usability and correctness.
//
// While technically the below doesn't fully guarantee protection against filter timing attacks, in practice combined with the network latency it makes them even less feasible.
// A properly configured rate limiter or individual fields Hidden checks are better suited if you are really concerned about eventual information disclosure by side-channel attacks.
@@ -107,7 +113,7 @@ func recordsList(e *core.RequestEvent) error {
len(e.Records) == 0 &&
checkRateLimit(e.RequestEvent, "@pb_list_timing_check_"+collection.Id, listTimingRateLimitRule) != nil {
e.App.Logger().Debug("Randomized throttle because of too many failed searches", "collectionId", collection.Id)
randomizedThrottle(100)
randomizedThrottle(150)
}
return e.JSON(http.StatusOK, e.Result)
@@ -218,6 +224,16 @@ func recordCreate(optFinalizer func(data any) error) func(e *core.RequestEvent)
return firstApiError(err, e.BadRequestError("Failed to read the submitted data.", err))
}
// set a random password for the OAuth2 ignoring its plain password validators
var skipPlainPasswordRecordValidators bool
if requestInfo.Context == core.RequestInfoContextOAuth2 {
if _, ok := data[core.FieldNamePassword]; !ok {
data[core.FieldNamePassword] = security.RandomString(30)
data[core.FieldNamePassword+"Confirm"] = data[core.FieldNamePassword]
skipPlainPasswordRecordValidators = true
}
}
// replace modifiers fields so that the resolved value is always
// available when accessing requestInfo.Body
requestInfo.Body = data
@@ -228,6 +244,13 @@ func recordCreate(optFinalizer func(data any) error) func(e *core.RequestEvent)
}
form.Load(data)
if skipPlainPasswordRecordValidators {
// unset the plain value to skip the plain password field validators
if raw, ok := record.GetRaw(core.FieldNamePassword).(*core.PasswordFieldValue); ok {
raw.Plain = ""
}
}
var isOptFinalizerCalled bool
event := new(core.RecordRequestEvent)
@@ -241,57 +264,74 @@ func recordCreate(optFinalizer func(data any) error) func(e *core.RequestEvent)
// temporary save the record and check it against the create and manage rules
if !hasSuperuserAuth && e.Collection.CreateRule != nil {
// temporary grant manager access level
form.GrantManagerAccess()
dummyRecord := e.Record.Clone()
// manually unset the verified field to prevent manage API rule misuse in case the rule relies on it
initialVerified := e.Record.Verified()
if initialVerified {
e.Record.SetVerified(false)
dummyRandomPart := "__pb_create__" + security.PseudorandomString(6)
// set an id if it doesn't have already
// (the value doesn't matter; it is used only to minimize the breaking changes with earlier versions)
if dummyRecord.Id == "" {
dummyRecord.Id = "__temp_id__" + dummyRandomPart
}
createRuleFunc := func(q *dbx.SelectQuery) error {
if *e.Collection.CreateRule == "" {
return nil // no create rule to resolve
}
// unset the verified field to prevent manage API rule misuse in case the rule relies on it
dummyRecord.SetVerified(false)
resolver := core.NewRecordFieldResolver(e.App, e.Collection, requestInfo, true)
expr, err := search.FilterData(*e.Collection.CreateRule).BuildExpr(resolver)
// export the dummy record data into db params
dummyExport, err := dummyRecord.DBExport(e.App)
if err != nil {
return e.BadRequestError("Failed to create record", fmt.Errorf("dummy DBExport error: %w", err))
}
dummyParams := make(dbx.Params, len(dummyExport))
selects := make([]string, 0, len(dummyExport))
var param string
for k, v := range dummyExport {
k = inflector.Columnify(k) // columnify is just as extra measure in case of custom fields
param = "__pb_create__" + k
dummyParams[param] = v
selects = append(selects, "{:"+param+"} AS [["+k+"]]")
}
// shallow clone the current collection
dummyCollection := *e.Collection
dummyCollection.Id += dummyRandomPart
dummyCollection.Name += inflector.Columnify(dummyRandomPart)
withFrom := fmt.Sprintf("WITH {{%s}} as (SELECT %s)", dummyCollection.Name, strings.Join(selects, ","))
// check non-empty create rule
if *dummyCollection.CreateRule != "" {
ruleQuery := e.App.DB().Select("(1)").PreFragment(withFrom).From(dummyCollection.Name).AndBind(dummyParams)
resolver := core.NewRecordFieldResolver(e.App, &dummyCollection, requestInfo, true)
expr, err := search.FilterData(*dummyCollection.CreateRule).BuildExpr(resolver)
if err != nil {
return err
return e.BadRequestError("Failed to create record", fmt.Errorf("create rule build expression failure: %w", err))
}
resolver.UpdateQuery(q)
q.AndWhere(expr)
ruleQuery.AndWhere(expr)
return nil
resolver.UpdateQuery(ruleQuery)
var exists bool
err = ruleQuery.Limit(1).Row(&exists)
if err != nil || !exists {
return e.BadRequestError("Failed to create record", fmt.Errorf("create rule failure: %w", err))
}
}
testErr := form.DrySubmit(func(txApp core.App, drySavedRecord *core.Record) error {
foundRecord, err := txApp.FindRecordById(drySavedRecord.Collection(), drySavedRecord.Id, createRuleFunc)
if err != nil {
return fmt.Errorf("DrySubmit create rule failure: %w", err)
}
// reset the form access level in case it satisfies the Manage API rule
if !hasAuthManageAccess(txApp, requestInfo, foundRecord) {
form.ResetAccess()
}
return nil
})
if testErr != nil {
return e.BadRequestError("Failed to create record.", testErr)
}
// restore initial verified state (it will be further validated on submit)
if initialVerified != e.Record.Verified() {
e.Record.SetVerified(initialVerified)
// check for manage rule access
manageRuleQuery := e.App.DB().Select("(1)").PreFragment(withFrom).From(dummyCollection.Name).AndBind(dummyParams)
if !form.HasManageAccess() &&
hasAuthManageAccess(e.App, requestInfo, &dummyCollection, manageRuleQuery) {
form.GrantManagerAccess()
}
}
err := form.Submit()
if err != nil {
return firstApiError(err, e.BadRequestError("Failed to create record.", err))
return firstApiError(err, e.BadRequestError("Failed to create record", err))
}
err = EnrichRecord(e.RequestEvent, e.Record)
@@ -411,7 +451,12 @@ func recordUpdate(optFinalizer func(data any) error) func(e *core.RequestEvent)
hookErr := e.App.OnRecordUpdateRequest().Trigger(event, func(e *core.RecordRequestEvent) error {
form.SetApp(e.App)
form.SetRecord(e.Record)
if !form.HasManageAccess() && hasAuthManageAccess(e.App, requestInfo, e.Record) {
manageRuleQuery := e.App.DB().Select("(1)").From(e.Collection.Name).AndWhere(dbx.HashExp{
e.Collection.Name + ".id": e.Record.Id,
})
if !form.HasManageAccess() &&
hasAuthManageAccess(e.App, requestInfo, e.Collection, manageRuleQuery) {
form.GrantManagerAccess()
}
@@ -644,3 +689,39 @@ func extractUploadedFiles(re *core.RequestEvent, collection *core.Collection, pr
return result, nil
}
// hasAuthManageAccess checks whether the client is allowed to have
// [forms.RecordUpsert] auth management permissions
// (e.g. allowing to change system auth fields without oldPassword).
func hasAuthManageAccess(app core.App, requestInfo *core.RequestInfo, collection *core.Collection, query *dbx.SelectQuery) bool {
if !collection.IsAuth() {
return false
}
manageRule := collection.ManageRule
if manageRule == nil || *manageRule == "" {
return false // only for superusers (manageRule can't be empty)
}
if requestInfo == nil || requestInfo.Auth == nil {
return false // no auth record
}
resolver := core.NewRecordFieldResolver(app, collection, requestInfo, true)
expr, err := search.FilterData(*manageRule).BuildExpr(resolver)
if err != nil {
app.Logger().Error("Manage rule build expression error", "error", err, "collectionId", collection.Id)
return false
}
query.AndWhere(expr)
resolver.UpdateQuery(query)
var exists bool
err = query.Limit(1).Row(&exists)
return err == nil && exists
}
+51 -5
View File
@@ -259,6 +259,47 @@ func TestRecordCrudList(t *testing.T) {
"OnRecordEnrich": 4,
},
},
{
Name: "authenticated regular record that matches the collection list rule with hidden field",
Method: http.MethodGet,
URL: "/api/collections/demo3/records",
Headers: map[string]string{
// clients, test@example.com
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6ImdrMzkwcWVnczR5NDd3biIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoidjg1MXE0cjc5MHJoa25sIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.0ONnm_BsvPRZyDNT31GN1CKUB6uQRxvVvQ-Wc9AZfG0",
},
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
col, err := app.FindCollectionByNameOrId("demo3")
if err != nil {
t.Fatal(err)
}
// mock hidden field
col.Fields.GetByName("title").SetHidden(true)
col.ListRule = types.Pointer("title ~ 'test'")
if err = app.Save(col); err != nil {
t.Fatal(err)
}
},
ExpectedStatus: 200,
ExpectedContent: []string{
`"page":1`,
`"perPage":30`,
`"totalPages":1`,
`"totalItems":4`,
`"items":[{`,
`"id":"1tmknxy2868d869"`,
`"id":"lcl9d87w22ml6jy"`,
`"id":"7nwo8tuiatetxdm"`,
`"id":"mk5fmymtx4wsprk"`,
},
ExpectedEvents: map[string]int{
"*": 0,
"OnRecordsListRequest": 1,
"OnRecordEnrich": 4,
},
},
{
Name: "authenticated regular record filtering with a hidden field",
Method: http.MethodGet,
@@ -1747,11 +1788,16 @@ func TestRecordCrudCreate(t *testing.T) {
`"code":"validation_not_unique"`,
},
ExpectedEvents: map[string]int{
"*": 0,
"OnRecordCreateRequest": 1,
// validate events are not fired because the unique check will fail during dry submit
// "OnModelValidate": 1,
// "OnRecordValidate": 1,
"*": 0,
"OnRecordCreateRequest": 1,
"OnModelCreate": 1,
"OnModelCreateExecute": 1,
"OnModelAfterCreateError": 1,
"OnModelValidate": 1,
"OnRecordCreate": 1,
"OnRecordCreateExecute": 1,
"OnRecordAfterCreateError": 1,
"OnRecordValidate": 1,
},
},
{
+23 -35
View File
@@ -6,11 +6,13 @@ import (
"fmt"
"net/http"
"strings"
"time"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/mails"
"github.com/pocketbase/pocketbase/tools/router"
"github.com/pocketbase/pocketbase/tools/routine"
"github.com/pocketbase/pocketbase/tools/search"
"github.com/pocketbase/pocketbase/tools/security"
)
@@ -479,40 +481,6 @@ func autoResolveRecordsFlags(app core.App, records []*core.Record, requestInfo *
return nil
}
// hasAuthManageAccess checks whether the client is allowed to have
// [forms.RecordUpsert] auth management permissions
// (e.g. allowing to change system auth fields without oldPassword).
func hasAuthManageAccess(app core.App, requestInfo *core.RequestInfo, record *core.Record) bool {
if !record.Collection().IsAuth() {
return false
}
manageRule := record.Collection().ManageRule
if manageRule == nil || *manageRule == "" {
return false // only for superusers (manageRule can't be empty)
}
if requestInfo == nil || requestInfo.Auth == nil {
return false // no auth record
}
ruleFunc := func(q *dbx.SelectQuery) error {
resolver := core.NewRecordFieldResolver(app, record.Collection(), requestInfo, true)
expr, err := search.FilterData(*manageRule).BuildExpr(resolver)
if err != nil {
return err
}
resolver.UpdateQuery(q)
q.AndWhere(expr)
return nil
}
_, findErr := app.FindRecordById(record.Collection().Id, record.Id, ruleFunc)
return findErr == nil
}
var ruleQueryParams = []string{search.FilterQueryParam, search.SortQueryParam}
var superuserOnlyRuleFields = []string{"@collection.", "@request."}
@@ -603,8 +571,28 @@ func authAlert(e *core.RequestEvent, authRecord *core.Record) error {
}
// send email alert for the new origin auth (skip first login)
//
// Note: The "fake" timeout is a temp solution to avoid blocking
// for too long when the SMTP server is not accessible, due
// to the lack of context cancellation support in the underlying
// mailer and net/smtp package.
// The goroutine technically "leaks" but we assume that the OS will
// terminate the connection after some time (usually after 3-4 mins).
if !isFirstLogin && currentOrigin.IsNew() && authRecord.Email() != "" {
if err := mails.SendRecordAuthAlert(e.App, authRecord); err != nil {
mailSent := make(chan error, 1)
timer := time.AfterFunc(15*time.Second, func() {
mailSent <- errors.New("auth alert mail send wait timeout reached")
})
routine.FireAndForget(func() {
err := mails.SendRecordAuthAlert(e.App, authRecord)
timer.Stop()
mailSent <- err
})
err = <-mailSent
if err != nil {
return err
}
}
+41 -27
View File
@@ -4,7 +4,6 @@ import (
"context"
"crypto/tls"
"errors"
"fmt"
"log"
"net"
"net/http"
@@ -17,6 +16,7 @@ import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/hook"
"github.com/pocketbase/pocketbase/tools/list"
"github.com/pocketbase/pocketbase/tools/routine"
"github.com/pocketbase/pocketbase/ui"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
@@ -158,6 +158,7 @@ func Serve(app core.App, config ServeConfig) error {
serveEvent.Router = pbRouter
serveEvent.Server = server
serveEvent.CertManager = certManager
serveEvent.InstallerFunc = DefaultInstallerFunc
var listener net.Listener
@@ -204,6 +205,8 @@ func Serve(app core.App, config ServeConfig) error {
}()
// ---------------------------------------------------------------
var baseURL string
// trigger the OnServe hook and start the tcp listener
serveHookErr := app.OnServe().Trigger(serveEvent, func(e *core.ServeEvent) error {
handler, err := e.Router.BuildMux()
@@ -213,10 +216,20 @@ func Serve(app core.App, config ServeConfig) error {
e.Server.Handler = handler
addr := e.Server.Addr
if config.HttpsAddr == "" {
baseURL = "http://" + serverAddrToHost(serveEvent.Server.Addr)
} else {
baseURL = "https://"
if len(config.CertificateDomains) > 0 {
baseURL += config.CertificateDomains[0]
} else {
baseURL += serverAddrToHost(serveEvent.Server.Addr)
}
}
// fallback similar to the std Server.ListenAndServe/ListenAndServeTLS
addr := e.Server.Addr
if addr == "" {
// fallback similar to the std Server.ListenAndServe/ListenAndServeTLS
if config.HttpsAddr != "" {
addr = ":https"
} else {
@@ -224,11 +237,22 @@ func Serve(app core.App, config ServeConfig) error {
}
}
var lnErr error
listener, err = net.Listen("tcp", addr)
if err != nil {
return err
}
listener, lnErr = net.Listen("tcp", addr)
if e.InstallerFunc != nil {
app := e.App
installerFunc := e.InstallerFunc
routine.FireAndForget(func() {
if err := loadInstaller(app, baseURL, installerFunc); err != nil {
app.Logger().Warn("Failed to initialize installer", "error", err)
}
})
}
return lnErr
return nil
})
if serveHookErr != nil {
return serveHookErr
@@ -238,17 +262,6 @@ func Serve(app core.App, config ServeConfig) error {
return errors.New("The OnServe finalizer wasn't invoked. Did you forget to call the ServeEvent.Next() method?")
}
schema := "http"
addr := server.Addr
if config.HttpsAddr != "" {
schema = "https"
if len(config.CertificateDomains) > 0 {
addr = config.CertificateDomains[0]
}
}
baseURL := fmt.Sprintf("%s://%s", schema, addr)
dashboardURL := fmt.Sprintf("%s/_", baseURL)
if config.ShowStartBanner {
date := new(strings.Builder)
log.New(date, "", log.LstdFlags).Print()
@@ -262,16 +275,9 @@ func Serve(app core.App, config ServeConfig) error {
regular := color.New()
regular.Printf("├─ REST API: %s\n", color.CyanString("%s/api/", baseURL))
regular.Printf("└─ Dashboard: %s\n", color.CyanString("%s/", dashboardURL))
regular.Printf("└─ Dashboard: %s\n", color.CyanString("%s/_/", baseURL))
}
go func() {
installerErr := loadInstaller(app, dashboardURL)
if installerErr != nil {
app.Logger().Warn("Failed to initialize installer", "error", installerErr)
}
}()
var serveErr error
if config.HttpsAddr != "" {
if config.HttpAddr != "" {
@@ -280,10 +286,10 @@ func Serve(app core.App, config ServeConfig) error {
}
// start HTTPS server
serveErr = server.ServeTLS(listener, "", "")
serveErr = serveEvent.Server.ServeTLS(listener, "", "")
} else {
// OR start HTTP server
serveErr = server.Serve(listener)
serveErr = serveEvent.Server.Serve(listener)
}
if serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) {
return serveErr
@@ -292,6 +298,14 @@ func Serve(app core.App, config ServeConfig) error {
return nil
}
// serverAddrToHost loosely converts http.Server.Addr string into a host to print.
func serverAddrToHost(addr string) string {
if addr == "" || strings.HasSuffix(addr, ":http") || strings.HasSuffix(addr, ":https") {
return "127.0.0.1"
}
return addr
}
type serverErrorLogWriter struct {
app core.App
}
+28 -9
View File
@@ -71,7 +71,7 @@ type App interface {
Settings() *Settings
// Store returns the app runtime store.
Store() *store.Store[any]
Store() *store.Store[string, any]
// Cron returns the app cron instance.
Cron() *cron.Cron
@@ -369,16 +369,9 @@ type App interface {
// - If you are updating a Collection in a transaction and then call this method before commit,
// it'll return the cached Collection state and not the one from the uncommitted transaction.
// - The cache is automatically updated on collections db change (create/update/delete).
// To manually reload the cache you can call [App.ReloadCachedCollections()]
// To manually reload the cache you can call [App.ReloadCachedCollections]
FindCachedCollectionByNameOrId(nameOrId string) (*Collection, error)
// IsCollectionNameUnique checks that there is no existing collection
// with the provided name (case insensitive!).
//
// Note: case insensitive check because the name is used also as
// table name for the records.
IsCollectionNameUnique(name string, excludeIds ...string) bool
// FindCollectionReferences returns information for all relation
// fields referencing the provided collection.
//
@@ -387,6 +380,32 @@ type App interface {
// as the excludeIds argument.
FindCollectionReferences(collection *Collection, excludeIds ...string) (map[*Collection][]Field, error)
// FindCachedCollectionReferences is similar to [App.FindCollectionReferences]
// but retrieves the Collection from the app cache instead of making a db call.
//
// NB! This method is suitable for read-only Collection operations.
//
// If you plan making changes to the returned Collection model,
// use [App.FindCollectionReferences] instead.
//
// Caveats:
//
// - The returned Collection should be used only for read-only operations.
// Avoid directly modifying the returned cached Collection as it will affect
// the global cached value even if you don't persist the changes in the database!
// - If you are updating a Collection in a transaction and then call this method before commit,
// it'll return the cached Collection state and not the one from the uncommitted transaction.
// - The cache is automatically updated on collections db change (create/update/delete).
// To manually reload the cache you can call [App.ReloadCachedCollections].
FindCachedCollectionReferences(collection *Collection, excludeIds ...string) (map[*Collection][]Field, error)
// IsCollectionNameUnique checks that there is no existing collection
// with the provided name (case insensitive!).
//
// Note: case insensitive check because the name is used also as
// table name for the records.
IsCollectionNameUnique(name string, excludeIds ...string) bool
// TruncateCollection deletes all records associated with the provided collection.
//
// The truncate operation is executed in a single transaction,
+11 -3
View File
@@ -70,7 +70,7 @@ var _ App = (*BaseApp)(nil)
type BaseApp struct {
config *BaseAppConfig
txInfo *txAppInfo
store *store.Store[any]
store *store.Store[string, any]
cron *cron.Cron
settings *Settings
subscriptionsBroker *subscriptions.Broker
@@ -194,7 +194,7 @@ type BaseApp struct {
func NewBaseApp(config BaseAppConfig) *BaseApp {
app := &BaseApp{
settings: newDefaultSettings(),
store: store.New[any](nil),
store: store.New[string, any](nil),
cron: cron.New(),
subscriptionsBroker: subscriptions.NewBroker(),
config: &config,
@@ -532,7 +532,7 @@ func (app *BaseApp) Settings() *Settings {
}
// Store returns the app runtime store.
func (app *BaseApp) Store() *store.Store[any] {
func (app *BaseApp) Store() *store.Store[string, any] {
return app.store
}
@@ -601,6 +601,14 @@ func (app *BaseApp) NewMailClient() mailer.Mailer {
fmt.Fprintf(mailLog, "├─ Attachments: %v\n", attachmentKeys)
}
if len(ae.Message.InlineAttachments) > 0 {
attachmentKeys := make([]string, 0, len(ae.Message.InlineAttachments))
for k := range ae.Message.InlineAttachments {
attachmentKeys = append(attachmentKeys, k)
}
fmt.Fprintf(mailLog, "├─ InlineAttachments: %v\n", attachmentKeys)
}
const indentation = " "
if ae.Message.Text != "" {
textParts := strings.Split(strings.TrimSpace(ae.Message.Text), "\n")
+64 -39
View File
@@ -142,6 +142,10 @@ func (app *BaseApp) CreateBackup(ctx context.Context, name string) error {
//
// If a failure occure during the restore process the dir changes are reverted.
// If for whatever reason the revert is not possible, it panics.
//
// Note that if your pb_data has custom network mounts as subdirectories, then
// it is possible the restore to fail during the `os.Rename` operations
// (see https://github.com/pocketbase/pocketbase/issues/4647).
func (app *BaseApp) RestoreBackup(ctx context.Context, name string) error {
if app.Store().Has(StoreKeyActiveBackup) {
return errors.New("try again later - another backup/restore operation has already been started")
@@ -162,6 +166,13 @@ func (app *BaseApp) RestoreBackup(ctx context.Context, name string) error {
return errors.New("restore is not supported on Windows")
}
// make sure that the special temp directory exists
// note: it needs to be inside the current pb_data to avoid "cross-device link" errors
localTempDir := filepath.Join(e.App.DataDir(), LocalTempDirName)
if err := os.MkdirAll(localTempDir, os.ModePerm); err != nil {
return fmt.Errorf("failed to create a temp dir: %w", err)
}
fsys, err := e.App.NewBackupsFilesystem()
if err != nil {
return err
@@ -170,57 +181,71 @@ func (app *BaseApp) RestoreBackup(ctx context.Context, name string) error {
fsys.SetContext(e.Context)
// fetch the backup file in a temp location
br, err := fsys.GetFile(name)
if err != nil {
return err
}
defer br.Close()
// make sure that the special temp directory exists
// note: it needs to be inside the current pb_data to avoid "cross-device link" errors
localTempDir := filepath.Join(e.App.DataDir(), LocalTempDirName)
if err := os.MkdirAll(localTempDir, os.ModePerm); err != nil {
return fmt.Errorf("failed to create a temp dir: %w", err)
if ok, _ := fsys.Exists(name); !ok {
return fmt.Errorf("missing or invalid backup file %q to restore", name)
}
// create a temp zip file from the blob.Reader and try to extract it
tempZip, err := os.CreateTemp(localTempDir, "pb_restore_zip")
if err != nil {
return err
}
defer os.Remove(tempZip.Name())
if _, err := io.Copy(tempZip, br); err != nil {
return err
}
extractedDataDir := filepath.Join(localTempDir, "pb_restore_"+security.PseudorandomString(4))
extractedDataDir := filepath.Join(localTempDir, "pb_restore_"+security.PseudorandomString(8))
defer os.RemoveAll(extractedDataDir)
if err := archive.Extract(tempZip.Name(), extractedDataDir); err != nil {
return err
// extract the zip
if e.App.Settings().Backups.S3.Enabled {
br, err := fsys.GetFile(name)
if err != nil {
return err
}
defer br.Close()
// create a temp zip file from the blob.Reader and try to extract it
tempZip, err := os.CreateTemp(localTempDir, "pb_restore_zip")
if err != nil {
return err
}
defer os.Remove(tempZip.Name())
defer tempZip.Close() // note: this technically shouldn't be necessary but it is here to workaround platforms discrepancies
_, err = io.Copy(tempZip, br)
if err != nil {
return err
}
err = archive.Extract(tempZip.Name(), extractedDataDir)
if err != nil {
return err
}
// remove the temp zip file since we no longer need it
// (this is in case the app restarts and the defer calls are not called)
_ = tempZip.Close()
err = os.Remove(tempZip.Name())
if err != nil {
e.App.Logger().Warn(
"[RestoreBackup] Failed to remove the temp zip backup file",
slog.String("file", tempZip.Name()),
slog.String("error", err.Error()),
)
}
} else {
// manually construct the local path to avoid creating a copy of the zip file
// since the blob reader currently doesn't implement ReaderAt
zipPath := filepath.Join(app.DataDir(), LocalBackupsDirName, filepath.Base(name))
err = archive.Extract(zipPath, extractedDataDir)
if err != nil {
return err
}
}
// ensure that a database file exists
// ensure that at least a database file exists
extractedDB := filepath.Join(extractedDataDir, "data.db")
if _, err := os.Stat(extractedDB); err != nil {
return fmt.Errorf("data.db file is missing or invalid: %w", err)
}
// remove the extracted zip file since we no longer need it
// (this is in case the app restarts and the defer calls are not called)
if err := os.Remove(tempZip.Name()); err != nil {
e.App.Logger().Debug(
"[RestoreBackup] Failed to remove the temp zip backup file",
slog.String("file", tempZip.Name()),
slog.String("error", err.Error()),
)
}
// move the current pb_data content to a special temp location
// that will hold the old data between dirs replace
// (the temp dir will be automatically removed on the next app start)
oldTempDataDir := filepath.Join(localTempDir, "old_pb_data_"+security.PseudorandomString(4))
oldTempDataDir := filepath.Join(localTempDir, "old_pb_data_"+security.PseudorandomString(8))
if err := osutils.MoveDirContent(e.App.DataDir(), oldTempDataDir, e.Exclude...); err != nil {
return fmt.Errorf("failed to move the current pb_data content to a temp location: %w", err)
}
@@ -257,7 +282,7 @@ func (app *BaseApp) RestoreBackup(ctx context.Context, name string) error {
// registerAutobackupHooks registers the autobackup app serve hooks.
func (app *BaseApp) registerAutobackupHooks() {
const jobId = "__auto_pb_backup__"
const jobId = "__pbAutoBackup__"
loadJob := func() {
rawSchedule := app.Settings().Backups.Cron
+68 -25
View File
@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"slices"
"strings"
"github.com/pocketbase/dbx"
@@ -94,7 +95,7 @@ func (app *BaseApp) FindCollectionByNameOrId(nameOrId string) (*Collection, erro
// - If you are updating a Collection in a transaction and then call this method before commit,
// it'll return the cached Collection state and not the one from the uncommitted transaction.
// - The cache is automatically updated on collections db change (create/update/delete).
// To manually reload the cache you can call [BaseApp.ReloadCachedCollections]
// To manually reload the cache you can call [BaseApp.ReloadCachedCollections].
func (app *BaseApp) FindCachedCollectionByNameOrId(nameOrId string) (*Collection, error) {
collections, _ := app.Store().Get(StoreKeyCachedCollections).([]*Collection)
if collections == nil {
@@ -111,30 +112,6 @@ func (app *BaseApp) FindCachedCollectionByNameOrId(nameOrId string) (*Collection
return nil, sql.ErrNoRows
}
// IsCollectionNameUnique checks that there is no existing collection
// with the provided name (case insensitive!).
//
// Note: case insensitive check because the name is used also as
// table name for the records.
func (app *BaseApp) IsCollectionNameUnique(name string, excludeIds ...string) bool {
if name == "" {
return false
}
query := app.CollectionQuery().
Select("count(*)").
AndWhere(dbx.NewExp("LOWER([[name]])={:name}", dbx.Params{"name": strings.ToLower(name)})).
Limit(1)
if uniqueExcludeIds := list.NonzeroUniques(excludeIds); len(uniqueExcludeIds) > 0 {
query.AndWhere(dbx.NotIn("id", list.ToInterfaceSlice(uniqueExcludeIds)...))
}
var exists bool
return query.Row(&exists) == nil && !exists
}
// FindCollectionReferences returns information for all relation fields
// referencing the provided collection.
//
@@ -168,6 +145,72 @@ func (app *BaseApp) FindCollectionReferences(collection *Collection, excludeIds
return result, nil
}
// FindCachedCollectionReferences is similar to [BaseApp.FindCollectionReferences]
// but retrieves the Collection from the app cache instead of making a db call.
//
// NB! This method is suitable for read-only Collection operations.
//
// If you plan making changes to the returned Collection model,
// use [BaseApp.FindCollectionReferences] instead.
//
// Caveats:
//
// - The returned Collection should be used only for read-only operations.
// Avoid directly modifying the returned cached Collection as it will affect
// the global cached value even if you don't persist the changes in the database!
// - If you are updating a Collection in a transaction and then call this method before commit,
// it'll return the cached Collection state and not the one from the uncommitted transaction.
// - The cache is automatically updated on collections db change (create/update/delete).
// To manually reload the cache you can call [BaseApp.ReloadCachedCollections].
func (app *BaseApp) FindCachedCollectionReferences(collection *Collection, excludeIds ...string) (map[*Collection][]Field, error) {
collections, _ := app.Store().Get(StoreKeyCachedCollections).([]*Collection)
if collections == nil {
// cache is not initialized yet (eg. run in a system migration)
return app.FindCollectionReferences(collection, excludeIds...)
}
result := map[*Collection][]Field{}
for _, c := range collections {
if slices.Contains(excludeIds, c.Id) {
continue
}
for _, rawField := range c.Fields {
f, ok := rawField.(*RelationField)
if ok && f.CollectionId == collection.Id {
result[c] = append(result[c], f)
}
}
}
return result, nil
}
// IsCollectionNameUnique checks that there is no existing collection
// with the provided name (case insensitive!).
//
// Note: case insensitive check because the name is used also as
// table name for the records.
func (app *BaseApp) IsCollectionNameUnique(name string, excludeIds ...string) bool {
if name == "" {
return false
}
query := app.CollectionQuery().
Select("count(*)").
AndWhere(dbx.NewExp("LOWER([[name]])={:name}", dbx.Params{"name": strings.ToLower(name)})).
Limit(1)
if uniqueExcludeIds := list.NonzeroUniques(excludeIds); len(uniqueExcludeIds) > 0 {
query.AndWhere(dbx.NotIn("id", list.ToInterfaceSlice(uniqueExcludeIds)...))
}
var exists bool
return query.Row(&exists) == nil && !exists
}
// TruncateCollection deletes all records associated with the provided collection.
//
// The truncate operation is executed in a single transaction,
+109 -28
View File
@@ -208,34 +208,6 @@ func TestFindCachedCollectionByNameOrId(t *testing.T) {
run(false)
}
func TestIsCollectionNameUnique(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
scenarios := []struct {
name string
excludeId string
expected bool
}{
{"", "", false},
{"demo1", "", false},
{"Demo1", "", false},
{"new", "", true},
{"demo1", "wsmn24bux7wo113", true},
}
for i, s := range scenarios {
t.Run(fmt.Sprintf("%d_%s", i, s.name), func(t *testing.T) {
result := app.IsCollectionNameUnique(s.name, s.excludeId)
if result != s.expected {
t.Errorf("Expected %v, got %v", s.expected, result)
}
})
}
}
func TestFindCollectionReferences(t *testing.T) {
t.Parallel()
@@ -288,6 +260,115 @@ func TestFindCollectionReferences(t *testing.T) {
}
}
func TestFindCachedCollectionReferences(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
collection, err := app.FindCollectionByNameOrId("demo3")
if err != nil {
t.Fatal(err)
}
totalQueries := 0
app.DB().(*dbx.DB).QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {
totalQueries++
}
run := func(withCache bool) {
var expectedTotalQueries int
if withCache {
err := app.ReloadCachedCollections()
if err != nil {
t.Fatal(err)
}
} else {
app.Store().Reset(nil)
expectedTotalQueries = 1
}
totalQueries = 0
result, err := app.FindCachedCollectionReferences(
collection,
collection.Id,
// test whether "nonempty" exclude ids condition will be skipped
"",
"",
)
if err != nil {
t.Fatal(err)
}
if len(result) != 1 {
t.Fatalf("Expected 1 collection, got %d: %v", len(result), result)
}
expectedFields := []string{
"rel_one_no_cascade",
"rel_one_no_cascade_required",
"rel_one_cascade",
"rel_one_unique",
"rel_many_no_cascade",
"rel_many_no_cascade_required",
"rel_many_cascade",
"rel_many_unique",
}
for col, fields := range result {
if col.Name != "demo4" {
t.Fatalf("Expected collection demo4, got %s", col.Name)
}
if len(fields) != len(expectedFields) {
t.Fatalf("Expected fields %v, got %v", expectedFields, fields)
}
for i, f := range fields {
if !slices.Contains(expectedFields, f.GetName()) {
t.Fatalf("[%d] Didn't expect field %v", i, f)
}
}
}
if totalQueries != expectedTotalQueries {
t.Fatalf("Expected %d totalQueries, got %d", expectedTotalQueries, totalQueries)
}
}
run(true)
run(false)
}
func TestIsCollectionNameUnique(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
scenarios := []struct {
name string
excludeId string
expected bool
}{
{"", "", false},
{"demo1", "", false},
{"Demo1", "", false},
{"new", "", true},
{"demo1", "wsmn24bux7wo113", true},
}
for i, s := range scenarios {
t.Run(fmt.Sprintf("%d_%s", i, s.name), func(t *testing.T) {
result := app.IsCollectionNameUnique(s.name, s.excludeId)
if result != s.expected {
t.Errorf("Expected %v, got %v", s.expected, result)
}
})
}
}
func TestFindCollectionTruncate(t *testing.T) {
t.Parallel()
+1 -1
View File
@@ -82,7 +82,7 @@ func (e *RequestEvent) HasSuperuserAuth() bool {
// RequestInfo parses the current request into RequestInfo instance.
//
// Note that the returned result is cached to avoid copying the request data multiple times
// but the auth state and other common store items are always refreshed in case they were changed my another handler.
// but the auth state and other common store items are always refreshed in case they were changed by another handler.
func (e *RequestEvent) RequestInfo() (*RequestInfo, error) {
e.mu.Lock()
defer e.mu.Unlock()
+16
View File
@@ -103,6 +103,22 @@ type ServeEvent struct {
Router *router.Router[*RequestEvent]
Server *http.Server
CertManager *autocert.Manager
// InstallerFunc is the "installer" function that is called after
// successful server tcp bind but only if there is no explicit
// superuser record created yet.
//
// It runs in a separate goroutine and its default value is [apis.DefaultInstallerFunc].
//
// It receives a system superuser record as argument that you can use to generate
// a short-lived auth token (e.g. systemSuperuser.NewStaticAuthToken(30 * time.Minute))
// and concatenate it as query param for your installer page
// (if you are using the client-side SDKs, you can then load the
// token with pb.authStore.save(token) and perform any Web API request
// e.g. creating a new superuser).
//
// Set it to nil if you want to skip the installer.
InstallerFunc func(app App, systemSuperuser *Record, baseURL string) error
}
// -------------------------------------------------------------------
+2
View File
@@ -13,6 +13,8 @@ import (
var fieldNameRegex = regexp.MustCompile(`^\w+$`)
const maxSafeJSONInt int64 = 1<<53 - 1
// Commonly used field names.
const (
FieldNameId = "id"
+2 -2
View File
@@ -47,7 +47,7 @@ type EditorField struct {
// ---
// MaxSize specifies the maximum size of the allowed field value (in bytes).
// MaxSize specifies the maximum size of the allowed field value (in bytes and up to 2^53-1).
//
// If zero, a default limit of ~5MB is applied.
MaxSize int64 `form:"maxSize" json:"maxSize"`
@@ -148,7 +148,7 @@ func (f *EditorField) ValidateSettings(ctx context.Context, app App, collection
return validation.ValidateStruct(f,
validation.Field(&f.Id, validation.By(DefaultFieldIdValidationRule)),
validation.Field(&f.Name, validation.By(DefaultFieldNameValidationRule)),
validation.Field(&f.MaxSize, validation.Min(0)),
validation.Field(&f.MaxSize, validation.Min(0), validation.Max(maxSafeJSONInt)),
)
}
+11
View File
@@ -206,6 +206,17 @@ func TestEditorFieldValidateSettings(t *testing.T) {
},
[]string{},
},
{
"MaxSize > safe json int",
func() *core.EditorField {
return &core.EditorField{
Id: "test",
Name: "test",
MaxSize: 1 << 53,
}
},
[]string{"maxSize"},
},
}
for _, s := range scenarios {
+3 -3
View File
@@ -94,7 +94,7 @@ type FileField struct {
// ---
// MaxSize specifies the maximum size of a single uploaded file (in bytes).
// MaxSize specifies the maximum size of a single uploaded file (in bytes and up to 2^53-1).
//
// If zero, a default limit of 5MB is applied.
MaxSize int64 `form:"maxSize" json:"maxSize"`
@@ -223,8 +223,8 @@ func (f *FileField) ValidateSettings(ctx context.Context, app App, collection *C
return validation.ValidateStruct(f,
validation.Field(&f.Id, validation.By(DefaultFieldIdValidationRule)),
validation.Field(&f.Name, validation.By(DefaultFieldNameValidationRule)),
validation.Field(&f.MaxSelect, validation.Min(0)),
validation.Field(&f.MaxSize, validation.Min(0)),
validation.Field(&f.MaxSelect, validation.Min(0), validation.Max(maxSafeJSONInt)),
validation.Field(&f.MaxSize, validation.Min(0), validation.Max(maxSafeJSONInt)),
validation.Field(&f.Thumbs, validation.Each(
validation.NotIn("0x0", "0x0t", "0x0b", "0x0f"),
validation.Match(filesystem.ThumbSizeRegex),
+44
View File
@@ -539,6 +539,50 @@ func TestFileFieldValidateSettings(t *testing.T) {
},
[]string{},
},
{
"MaxSize > safe json int",
func() *core.FileField {
return &core.FileField{
Id: "test",
Name: "test",
MaxSize: 1 << 53,
}
},
[]string{"maxSize"},
},
{
"MaxSize < 0",
func() *core.FileField {
return &core.FileField{
Id: "test",
Name: "test",
MaxSize: -1,
}
},
[]string{"maxSize"},
},
{
"MaxSelect > safe json int",
func() *core.FileField {
return &core.FileField{
Id: "test",
Name: "test",
MaxSelect: 1 << 53,
}
},
[]string{"maxSelect"},
},
{
"MaxSelect < 0",
func() *core.FileField {
return &core.FileField{
Id: "test",
Name: "test",
MaxSelect: -1,
}
},
[]string{"maxSelect"},
},
}
for _, s := range scenarios {
+2 -2
View File
@@ -51,7 +51,7 @@ type JSONField struct {
// ---
// MaxSize specifies the maximum size of the allowed field value (in bytes).
// MaxSize specifies the maximum size of the allowed field value (in bytes and up to 2^53-1).
//
// If zero, a default limit of 5MB is applied.
MaxSize int64 `form:"maxSize" json:"maxSize"`
@@ -181,7 +181,7 @@ func (f *JSONField) ValidateSettings(ctx context.Context, app App, collection *C
return validation.ValidateStruct(f,
validation.Field(&f.Id, validation.By(DefaultFieldIdValidationRule)),
validation.Field(&f.Name, validation.By(DefaultFieldNameValidationRule)),
validation.Field(&f.MaxSize, validation.Min(0)),
validation.Field(&f.MaxSize, validation.Min(0), validation.Max(maxSafeJSONInt)),
)
}
+14 -3
View File
@@ -200,7 +200,7 @@ func TestJSONFieldValidateSettings(t *testing.T) {
expectErrors []string
}{
{
"< 0 MaxSize",
"MaxSize < 0",
func() *core.JSONField {
return &core.JSONField{
Id: "test",
@@ -211,7 +211,7 @@ func TestJSONFieldValidateSettings(t *testing.T) {
[]string{"maxSize"},
},
{
"= 0 MaxSize",
"MaxSize = 0",
func() *core.JSONField {
return &core.JSONField{
Id: "test",
@@ -221,7 +221,7 @@ func TestJSONFieldValidateSettings(t *testing.T) {
[]string{},
},
{
"> 0 MaxSize",
"MaxSize > 0",
func() *core.JSONField {
return &core.JSONField{
Id: "test",
@@ -231,6 +231,17 @@ func TestJSONFieldValidateSettings(t *testing.T) {
},
[]string{},
},
{
"MaxSize > safe json int",
func() *core.JSONField {
return &core.JSONField{
Id: "test",
Name: "test",
MaxSize: 1 << 53,
}
},
[]string{"maxSize"},
},
}
for _, s := range scenarios {
+2 -2
View File
@@ -259,8 +259,8 @@ func (f *TextField) ValidateSettings(ctx context.Context, app App, collection *C
validation.When(f.PrimaryKey, validation.In(idColumn).Error(`The primary key must be named "id".`)),
),
validation.Field(&f.PrimaryKey, validation.By(f.checkOtherFieldsForPK(collection))),
validation.Field(&f.Min, validation.Min(0)),
validation.Field(&f.Max, validation.Min(f.Min)),
validation.Field(&f.Min, validation.Min(0), validation.Max(maxSafeJSONInt)),
validation.Field(&f.Max, validation.Min(f.Min), validation.Max(maxSafeJSONInt)),
validation.Field(&f.Pattern, validation.When(f.PrimaryKey, validation.Required), validation.By(validators.IsRegex)),
validation.Field(&f.Hidden, validation.When(f.PrimaryKey, validation.Empty)),
validation.Field(&f.Required, validation.When(f.PrimaryKey, validation.Required)),
+44
View File
@@ -430,6 +430,50 @@ func TestTextFieldValidateSettings(t *testing.T) {
},
[]string{"autogeneratePattern"},
},
{
"Max > safe json int",
func() *core.TextField {
return &core.TextField{
Id: "test",
Name: "test",
Max: 1 << 53,
}
},
[]string{"max"},
},
{
"Max < 0",
func() *core.TextField {
return &core.TextField{
Id: "test",
Name: "test",
Max: -1,
}
},
[]string{"max"},
},
{
"Min > safe json int",
func() *core.TextField {
return &core.TextField{
Id: "test",
Name: "test",
Min: 1 << 53,
}
},
[]string{"min"},
},
{
"Min < 0",
func() *core.TextField {
return &core.TextField{
Id: "test",
Name: "test",
Min: -1,
}
},
[]string{"min"},
},
}
for _, s := range scenarios {
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"github.com/spf13/cast"
)
var cachedColors = store.New[*color.Color](nil)
var cachedColors = store.New[string, *color.Color](nil)
// getColor returns [color.Color] object and cache it (if not already).
func getColor(attrs ...color.Attribute) (c *color.Color) {
+1 -1
View File
@@ -122,7 +122,7 @@ func (app *BaseApp) registerMFAHooks() {
recordRefHooks[*MFA](app, CollectionNameMFAs, CollectionTypeAuth)
// run on every hour to cleanup expired mfa sessions
app.Cron().Add("__mfasCleanup__", "0 * * * *", func() {
app.Cron().Add("__pbMFACleanup__", "0 * * * *", func() {
if err := app.DeleteExpiredMFAs(); err != nil {
app.Logger().Warn("Failed to delete expired MFA sessions", "error", err)
}
+1 -1
View File
@@ -119,7 +119,7 @@ func (app *BaseApp) registerOTPHooks() {
recordRefHooks[*OTP](app, CollectionNameOTPs, CollectionTypeAuth)
// run on every hour to cleanup expired otp sessions
app.Cron().Add("__otpsCleanup__", "0 * * * *", func() {
app.Cron().Add("__pbOTPCleanup__", "0 * * * *", func() {
if err := app.DeleteExpiredOTPs(); err != nil {
app.Logger().Warn("Failed to delete expired OTP sessions", "error", err)
}
+32 -2
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"slices"
"strconv"
"strings"
@@ -18,6 +19,7 @@ const (
eachModifier string = "each"
issetModifier string = "isset"
lengthModifier string = "length"
lowerModifier string = "lower"
)
// ensure that `search.FieldResolver` interface is implemented
@@ -47,6 +49,26 @@ type RecordFieldResolver struct {
allowHiddenFields bool
}
// AllowedFields returns a copy of the resolver's allowed fields.
func (r *RecordFieldResolver) AllowedFields() []string {
return slices.Clone(r.allowedFields)
}
// SetAllowedFields replaces the resolver's allowed fields with the new ones.
func (r *RecordFieldResolver) SetAllowedFields(newAllowedFields []string) {
r.allowedFields = slices.Clone(newAllowedFields)
}
// AllowHiddenFields returns whether the current resolver allows filtering hidden fields.
func (r *RecordFieldResolver) AllowHiddenFields() bool {
return r.allowHiddenFields
}
// SetAllowHiddenFields enables or disables hidden fields filtering.
func (r *RecordFieldResolver) SetAllowHiddenFields(allowHiddenFields bool) {
r.allowHiddenFields = allowHiddenFields
}
// NewRecordFieldResolver creates and initializes a new `RecordFieldResolver`.
func NewRecordFieldResolver(
app App,
@@ -193,7 +215,14 @@ func (r *RecordFieldResolver) resolveStaticRequestField(path ...string) (*search
resultVal = val
}
placeholder := "f" + security.PseudorandomString(6)
placeholder := "f" + security.PseudorandomString(8)
if modifier == lowerModifier {
return &search.ResolverResult{
Identifier: "LOWER({:" + placeholder + "})",
Params: dbx.Params{placeholder: resultVal},
}, nil
}
return &search.ResolverResult{
Identifier: "{:" + placeholder + "}",
@@ -355,7 +384,8 @@ func splitModifier(combined string) (string, string, error) {
switch parts[1] {
case issetModifier,
eachModifier,
lengthModifier:
lengthModifier,
lowerModifier:
return parts[0], parts[1], nil
}
+41 -6
View File
@@ -15,6 +15,7 @@ import (
"github.com/pocketbase/pocketbase/tools/list"
"github.com/pocketbase/pocketbase/tools/search"
"github.com/pocketbase/pocketbase/tools/security"
"github.com/spf13/cast"
)
// maxNestedRels defines the max allowed nested relations depth.
@@ -113,6 +114,11 @@ func (r *runner) run() (*search.ResolverResult, error) {
if modifier == lengthModifier && len(r.activeProps) == 3 {
return r.processRequestInfoLengthModifier(bodyField)
}
// check for body arrayble fields ":lower" modifier
if modifier == lowerModifier && len(r.activeProps) == 3 {
return r.processRequestInfoLowerModifier(bodyField)
}
}
// some other @request.* static field
@@ -191,6 +197,10 @@ func (r *runner) processCollectionField() (*search.ResolverResult, error) {
}
func (r *runner) processRequestAuthField() (*search.ResolverResult, error) {
if r.resolver.requestInfo == nil || r.resolver.requestInfo.Auth == nil || r.resolver.requestInfo.Auth.Collection() == nil {
return &search.ResolverResult{Identifier: "NULL"}, nil
}
// plain auth field
// ---
if _, ok := plainRequestAuthFields[r.fieldName]; ok {
@@ -199,10 +209,6 @@ func (r *runner) processRequestAuthField() (*search.ResolverResult, error) {
// resolve the auth collection field
// ---
if r.resolver.requestInfo == nil || r.resolver.requestInfo.Auth == nil || r.resolver.requestInfo.Auth.Collection() == nil {
return &search.ResolverResult{Identifier: "NULL"}, nil
}
collection := r.resolver.requestInfo.Auth.Collection()
r.activeCollectionName = collection.Name
@@ -262,6 +268,19 @@ func toSlice(value any) []any {
return result
}
func (r *runner) processRequestInfoLowerModifier(bodyField Field) (*search.ResolverResult, error) {
rawValue := cast.ToString(r.resolver.requestInfo.Body[bodyField.GetName()])
placeholder := "infoLower" + bodyField.GetName() + security.PseudorandomString(6)
result := &search.ResolverResult{
Identifier: "LOWER({:" + placeholder + "})",
Params: dbx.Params{placeholder: rawValue},
}
return result, nil
}
func (r *runner) processRequestInfoLengthModifier(bodyField Field) (*search.ResolverResult, error) {
if _, ok := bodyField.(MultiValuer); !ok {
return nil, fmt.Errorf("field %q doesn't support multivalue operations", bodyField.GetName())
@@ -396,6 +415,10 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
field := collection.Fields.GetByName(prop)
if field != nil && field.GetHidden() && !r.allowHiddenFields {
return nil, fmt.Errorf("non-filterable field %q", prop)
}
// json field -> treat the rest of the props as json path
if field != nil && field.Type() == FieldTypeJSON {
var jsonPath strings.Builder
@@ -460,6 +483,10 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
return nil, fmt.Errorf("invalid back relation field %q", parts[2])
}
if backField.GetHidden() && !r.allowHiddenFields {
return nil, fmt.Errorf("non-filterable back relation field %q", backField.GetName())
}
backRelField, ok := backField.(*RelationField)
if !ok {
return nil, fmt.Errorf("failed to initialize back relation field %q", backField.GetName())
@@ -713,11 +740,11 @@ func (r *runner) processLastProp(collection *Collection, prop string) (*search.R
// default
// -------------------------------------------------------
result := &search.ResolverResult{
Identifier: fmt.Sprintf("[[%s.%s]]", r.activeTableAlias, cleanFieldName),
Identifier: "[[" + r.activeTableAlias + "." + cleanFieldName + "]]",
}
if r.withMultiMatch {
r.multiMatch.valueIdentifier = fmt.Sprintf("[[%s.%s]]", r.multiMatchActiveTableAlias, cleanFieldName)
r.multiMatch.valueIdentifier = "[[" + r.multiMatchActiveTableAlias + "." + cleanFieldName + "]]"
result.MultiMatchSubQuery = r.multiMatch
}
@@ -743,5 +770,13 @@ func (r *runner) processLastProp(collection *Collection, prop string) (*search.R
}
}
// account for the ":lower" modifier
if modifier == lowerModifier {
result.Identifier = "LOWER(" + result.Identifier + ")"
if r.withMultiMatch {
r.multiMatch.valueIdentifier = "LOWER(" + r.multiMatch.valueIdentifier + ")"
}
}
return result, nil
}
+120 -13
View File
@@ -3,6 +3,7 @@ package core_test
import (
"encoding/json"
"regexp"
"slices"
"strings"
"testing"
@@ -12,7 +13,75 @@ import (
"github.com/pocketbase/pocketbase/tools/search"
)
func TestRecordFieldResolverAllowedFields(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
collection, err := app.FindCollectionByNameOrId("demo1")
if err != nil {
t.Fatal(err)
}
r := core.NewRecordFieldResolver(app, collection, nil, false)
fields := r.AllowedFields()
if len(fields) != 8 {
t.Fatalf("Expected %d original allowed fields, got %d", 8, len(fields))
}
// change the allowed fields
newFields := []string{"a", "b", "c"}
expected := slices.Clone(newFields)
r.SetAllowedFields(newFields)
// change the new fields to ensure that the slice was cloned
newFields[2] = "d"
fields = r.AllowedFields()
if len(fields) != len(expected) {
t.Fatalf("Expected %d changed allowed fields, got %d", len(expected), len(fields))
}
for i, v := range expected {
if fields[i] != v {
t.Errorf("[%d] Expected field %q", i, v)
}
}
}
func TestRecordFieldResolverAllowHiddenFields(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
collection, err := app.FindCollectionByNameOrId("demo1")
if err != nil {
t.Fatal(err)
}
r := core.NewRecordFieldResolver(app, collection, nil, false)
allowHiddenFields := r.AllowHiddenFields()
if allowHiddenFields {
t.Fatalf("Expected original allowHiddenFields %v, got %v", allowHiddenFields, !allowHiddenFields)
}
// change the flag
expected := !allowHiddenFields
r.SetAllowHiddenFields(expected)
allowHiddenFields = r.AllowHiddenFields()
if allowHiddenFields != expected {
t.Fatalf("Expected changed allowHiddenFields %v, got %v", expected, allowHiddenFields)
}
}
func TestRecordFieldResolverUpdateQuery(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -138,7 +207,7 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
"demo4",
"self_rel_many.self_rel_one > true",
false,
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE json_array([[demo4.self_rel_many]]) END) `demo4_self_rel_many_je` LEFT JOIN `demo4` `demo4_self_rel_many` ON [[demo4_self_rel_many.id]] = [[demo4_self_rel_many_je.value]] WHERE ((([[demo4_self_rel_many.self_rel_one]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo4_self_rel_many.self_rel_one]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo4.self_rel_many]]) THEN [[__mm_demo4.self_rel_many]] ELSE json_array([[__mm_demo4.self_rel_many]]) END) `__mm_demo4_self_rel_many_je` LEFT JOIN `demo4` `__mm_demo4_self_rel_many` ON [[__mm_demo4_self_rel_many.id]] = [[__mm_demo4_self_rel_many_je.value]] WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{TEST}} WHERE ((NOT ([[TEST.multiMatchValue]] > 1)) OR ([[TEST.multiMatchValue]] IS NULL))))))",
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE json_array([[demo4.self_rel_many]]) END) `demo4_self_rel_many_je` LEFT JOIN `demo4` `demo4_self_rel_many` ON [[demo4_self_rel_many.id]] = [[demo4_self_rel_many_je.value]] WHERE ((([[demo4_self_rel_many.self_rel_one]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo4_self_rel_many.self_rel_one]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo4.self_rel_many]]) THEN [[__mm_demo4.self_rel_many]] ELSE json_array([[__mm_demo4.self_rel_many]]) END) `__mm_demo4_self_rel_many_je` LEFT JOIN `demo4` `__mm_demo4_self_rel_many` ON [[__mm_demo4_self_rel_many.id]] = [[__mm_demo4_self_rel_many_je.value]] WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > 1)))))",
},
{
"nested complete relations (opt/any operator)",
@@ -152,7 +221,7 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
"demo4",
"self_rel_many.self_rel_one.title > true",
false,
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE json_array([[demo4.self_rel_many]]) END) `demo4_self_rel_many_je` LEFT JOIN `demo4` `demo4_self_rel_many` ON [[demo4_self_rel_many.id]] = [[demo4_self_rel_many_je.value]] LEFT JOIN `demo4` `demo4_self_rel_many_self_rel_one` ON [[demo4_self_rel_many_self_rel_one.id]] = [[demo4_self_rel_many.self_rel_one]] WHERE ((([[demo4_self_rel_many_self_rel_one.title]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo4_self_rel_many_self_rel_one.title]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo4.self_rel_many]]) THEN [[__mm_demo4.self_rel_many]] ELSE json_array([[__mm_demo4.self_rel_many]]) END) `__mm_demo4_self_rel_many_je` LEFT JOIN `demo4` `__mm_demo4_self_rel_many` ON [[__mm_demo4_self_rel_many.id]] = [[__mm_demo4_self_rel_many_je.value]] LEFT JOIN `demo4` `__mm_demo4_self_rel_many_self_rel_one` ON [[__mm_demo4_self_rel_many_self_rel_one.id]] = [[__mm_demo4_self_rel_many.self_rel_one]] WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] > 1)) OR ([[__smTEST.multiMatchValue]] IS NULL))))))",
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE json_array([[demo4.self_rel_many]]) END) `demo4_self_rel_many_je` LEFT JOIN `demo4` `demo4_self_rel_many` ON [[demo4_self_rel_many.id]] = [[demo4_self_rel_many_je.value]] LEFT JOIN `demo4` `demo4_self_rel_many_self_rel_one` ON [[demo4_self_rel_many_self_rel_one.id]] = [[demo4_self_rel_many.self_rel_one]] WHERE ((([[demo4_self_rel_many_self_rel_one.title]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo4_self_rel_many_self_rel_one.title]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo4.self_rel_many]]) THEN [[__mm_demo4.self_rel_many]] ELSE json_array([[__mm_demo4.self_rel_many]]) END) `__mm_demo4_self_rel_many_je` LEFT JOIN `demo4` `__mm_demo4_self_rel_many` ON [[__mm_demo4_self_rel_many.id]] = [[__mm_demo4_self_rel_many_je.value]] LEFT JOIN `demo4` `__mm_demo4_self_rel_many_self_rel_one` ON [[__mm_demo4_self_rel_many_self_rel_one.id]] = [[__mm_demo4_self_rel_many.self_rel_one]] WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > 1)))))",
},
{
"repeated nested relations (opt/any operator)",
@@ -166,7 +235,7 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
"demo4",
"self_rel_many.self_rel_one.self_rel_many.self_rel_one.title > true",
false,
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE json_array([[demo4.self_rel_many]]) END) `demo4_self_rel_many_je` LEFT JOIN `demo4` `demo4_self_rel_many` ON [[demo4_self_rel_many.id]] = [[demo4_self_rel_many_je.value]] LEFT JOIN `demo4` `demo4_self_rel_many_self_rel_one` ON [[demo4_self_rel_many_self_rel_one.id]] = [[demo4_self_rel_many.self_rel_one]] LEFT JOIN json_each(CASE WHEN json_valid([[demo4_self_rel_many_self_rel_one.self_rel_many]]) THEN [[demo4_self_rel_many_self_rel_one.self_rel_many]] ELSE json_array([[demo4_self_rel_many_self_rel_one.self_rel_many]]) END) `demo4_self_rel_many_self_rel_one_self_rel_many_je` LEFT JOIN `demo4` `demo4_self_rel_many_self_rel_one_self_rel_many` ON [[demo4_self_rel_many_self_rel_one_self_rel_many.id]] = [[demo4_self_rel_many_self_rel_one_self_rel_many_je.value]] LEFT JOIN `demo4` `demo4_self_rel_many_self_rel_one_self_rel_many_self_rel_one` ON [[demo4_self_rel_many_self_rel_one_self_rel_many_self_rel_one.id]] = [[demo4_self_rel_many_self_rel_one_self_rel_many.self_rel_one]] WHERE ((([[demo4_self_rel_many_self_rel_one_self_rel_many_self_rel_one.title]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo4_self_rel_many_self_rel_one_self_rel_many_self_rel_one.title]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo4.self_rel_many]]) THEN [[__mm_demo4.self_rel_many]] ELSE json_array([[__mm_demo4.self_rel_many]]) END) `__mm_demo4_self_rel_many_je` LEFT JOIN `demo4` `__mm_demo4_self_rel_many` ON [[__mm_demo4_self_rel_many.id]] = [[__mm_demo4_self_rel_many_je.value]] LEFT JOIN `demo4` `__mm_demo4_self_rel_many_self_rel_one` ON [[__mm_demo4_self_rel_many_self_rel_one.id]] = [[__mm_demo4_self_rel_many.self_rel_one]] LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo4_self_rel_many_self_rel_one.self_rel_many]]) THEN [[__mm_demo4_self_rel_many_self_rel_one.self_rel_many]] ELSE json_array([[__mm_demo4_self_rel_many_self_rel_one.self_rel_many]]) END) `__mm_demo4_self_rel_many_self_rel_one_self_rel_many_je` LEFT JOIN `demo4` `__mm_demo4_self_rel_many_self_rel_one_self_rel_many` ON [[__mm_demo4_self_rel_many_self_rel_one_self_rel_many.id]] = [[__mm_demo4_self_rel_many_self_rel_one_self_rel_many_je.value]] LEFT JOIN `demo4` `__mm_demo4_self_rel_many_self_rel_one_self_rel_many_self_rel_one` ON [[__mm_demo4_self_rel_many_self_rel_one_self_rel_many_self_rel_one.id]] = [[__mm_demo4_self_rel_many_self_rel_one_self_rel_many.self_rel_one]] WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] > 1)) OR ([[__smTEST.multiMatchValue]] IS NULL))))))",
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE json_array([[demo4.self_rel_many]]) END) `demo4_self_rel_many_je` LEFT JOIN `demo4` `demo4_self_rel_many` ON [[demo4_self_rel_many.id]] = [[demo4_self_rel_many_je.value]] LEFT JOIN `demo4` `demo4_self_rel_many_self_rel_one` ON [[demo4_self_rel_many_self_rel_one.id]] = [[demo4_self_rel_many.self_rel_one]] LEFT JOIN json_each(CASE WHEN json_valid([[demo4_self_rel_many_self_rel_one.self_rel_many]]) THEN [[demo4_self_rel_many_self_rel_one.self_rel_many]] ELSE json_array([[demo4_self_rel_many_self_rel_one.self_rel_many]]) END) `demo4_self_rel_many_self_rel_one_self_rel_many_je` LEFT JOIN `demo4` `demo4_self_rel_many_self_rel_one_self_rel_many` ON [[demo4_self_rel_many_self_rel_one_self_rel_many.id]] = [[demo4_self_rel_many_self_rel_one_self_rel_many_je.value]] LEFT JOIN `demo4` `demo4_self_rel_many_self_rel_one_self_rel_many_self_rel_one` ON [[demo4_self_rel_many_self_rel_one_self_rel_many_self_rel_one.id]] = [[demo4_self_rel_many_self_rel_one_self_rel_many.self_rel_one]] WHERE ((([[demo4_self_rel_many_self_rel_one_self_rel_many_self_rel_one.title]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo4_self_rel_many_self_rel_one_self_rel_many_self_rel_one.title]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo4.self_rel_many]]) THEN [[__mm_demo4.self_rel_many]] ELSE json_array([[__mm_demo4.self_rel_many]]) END) `__mm_demo4_self_rel_many_je` LEFT JOIN `demo4` `__mm_demo4_self_rel_many` ON [[__mm_demo4_self_rel_many.id]] = [[__mm_demo4_self_rel_many_je.value]] LEFT JOIN `demo4` `__mm_demo4_self_rel_many_self_rel_one` ON [[__mm_demo4_self_rel_many_self_rel_one.id]] = [[__mm_demo4_self_rel_many.self_rel_one]] LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo4_self_rel_many_self_rel_one.self_rel_many]]) THEN [[__mm_demo4_self_rel_many_self_rel_one.self_rel_many]] ELSE json_array([[__mm_demo4_self_rel_many_self_rel_one.self_rel_many]]) END) `__mm_demo4_self_rel_many_self_rel_one_self_rel_many_je` LEFT JOIN `demo4` `__mm_demo4_self_rel_many_self_rel_one_self_rel_many` ON [[__mm_demo4_self_rel_many_self_rel_one_self_rel_many.id]] = [[__mm_demo4_self_rel_many_self_rel_one_self_rel_many_je.value]] LEFT JOIN `demo4` `__mm_demo4_self_rel_many_self_rel_one_self_rel_many_self_rel_one` ON [[__mm_demo4_self_rel_many_self_rel_one_self_rel_many_self_rel_one.id]] = [[__mm_demo4_self_rel_many_self_rel_one_self_rel_many.self_rel_one]] WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > 1)))))",
},
{
"multiple relations (opt/any operators)",
@@ -236,7 +305,7 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
"demo4",
"@collection.demo1.text > true || @collection.demo2.active > true || @collection.demo1.file_one > true",
false,
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `demo1` `__collection_demo1` LEFT JOIN `demo2` `__collection_demo2` WHERE ((([[__collection_demo1.text]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__collection_demo1.text]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `demo1` `__mm__collection_demo1` WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{TEST}} WHERE ((NOT ([[TEST.multiMatchValue]] > 1)) OR ([[TEST.multiMatchValue]] IS NULL))))) OR (([[__collection_demo2.active]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__collection_demo2.active]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `demo2` `__mm__collection_demo2` WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{TEST}} WHERE ((NOT ([[TEST.multiMatchValue]] > 1)) OR ([[TEST.multiMatchValue]] IS NULL))))) OR (([[__collection_demo1.file_one]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__collection_demo1.file_one]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `demo1` `__mm__collection_demo1` WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{TEST}} WHERE ((NOT ([[TEST.multiMatchValue]] > 1)) OR ([[TEST.multiMatchValue]] IS NULL))))))",
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `demo1` `__collection_demo1` LEFT JOIN `demo2` `__collection_demo2` WHERE ((([[__collection_demo1.text]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__collection_demo1.text]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `demo1` `__mm__collection_demo1` WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > 1)))) OR (([[__collection_demo2.active]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__collection_demo2.active]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `demo2` `__mm__collection_demo2` WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > 1)))) OR (([[__collection_demo1.file_one]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__collection_demo1.file_one]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `demo1` `__mm__collection_demo1` WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > 1)))))",
},
{
"@request.auth fields",
@@ -257,14 +326,14 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
"demo4",
"@collection.users.email > true || @request.auth.email > true",
false,
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `users` `__collection_users` WHERE ((([[__collection_users.email]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__collection_users.email]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `users` `__mm__collection_users` WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] > 1)) OR ([[__smTEST.multiMatchValue]] IS NULL))))) OR {:TEST} > 1)",
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `users` `__collection_users` WHERE ((([[__collection_users.email]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__collection_users.email]] as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `users` `__mm__collection_users` WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > 1)))) OR {:TEST} > 1)",
},
{
"hidden field (add emailVisibility)",
"users",
"id > true || email > true",
"id > true || email > true || email:lower > false",
false,
"SELECT `users`.* FROM `users` WHERE ([[users.id]] > 1 OR (([[users.email]] > 1) AND ([[users.emailVisibility]] = TRUE)))",
"SELECT `users`.* FROM `users` WHERE ([[users.id]] > 1 OR (([[users.email]] > 1) AND ([[users.emailVisibility]] = TRUE)) OR ((LOWER([[users.email]]) > 0) AND ([[users.emailVisibility]] = TRUE)))",
},
{
"hidden field (force ignore emailVisibility)",
@@ -274,7 +343,45 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
"SELECT `users`.* FROM `users` WHERE [[users.email]] > 1",
},
{
"isset key",
"static @request fields with :lower modifier",
"demo1",
"@request.body.a:lower > true ||" +
"@request.body.b:lower > true ||" +
"@request.body.c:lower > true ||" +
"@request.query.a:lower > true ||" +
"@request.query.b:lower > true ||" +
"@request.query.c:lower > true ||" +
"@request.headers.a:lower > true ||" +
"@request.headers.c:lower > true",
false,
"SELECT `demo1`.* FROM `demo1` WHERE (NULL > 1 OR LOWER({:TEST}) > 1 OR NULL > 1 OR LOWER({:TEST}) > 1 OR LOWER({:TEST}) > 1 OR NULL > 1 OR LOWER({:TEST}) > 1 OR NULL > 1)",
},
{
"collection fields with :lower modifier",
"demo1",
"@request.body.rel_one:lower > true ||" +
"@request.body.rel_many:lower > true ||" +
"@request.body.rel_many.email:lower > true ||" +
"text:lower > true ||" +
"bool:lower > true ||" +
"url:lower > true ||" +
"select_one:lower > true ||" +
"select_many:lower > true ||" +
"file_one:lower > true ||" +
"file_many:lower > true ||" +
"number:lower > true ||" +
"email:lower > true ||" +
"datetime:lower > true ||" +
"json:lower > true ||" +
"rel_one:lower > true ||" +
"rel_many:lower > true ||" +
"rel_many.name:lower > true ||" +
"created:lower > true",
false,
"SELECT DISTINCT `demo1`.* FROM `demo1` LEFT JOIN `users` `__data_users_rel_many` ON [[__data_users_rel_many.id]] IN ({:p0}, {:p1}) LEFT JOIN json_each(CASE WHEN json_valid([[demo1.rel_many]]) THEN [[demo1.rel_many]] ELSE json_array([[demo1.rel_many]]) END) `demo1_rel_many_je` LEFT JOIN `users` `demo1_rel_many` ON [[demo1_rel_many.id]] = [[demo1_rel_many_je.value]] WHERE (LOWER({:infoLowerrel_oneTEST}) > 1 OR LOWER({:infoLowerrel_manyTEST}) > 1 OR ((LOWER([[__data_users_rel_many.email]]) > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT LOWER([[__data_mm_users_rel_many.email]]) as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN `users` `__data_mm_users_rel_many` ON [[__data_mm_users_rel_many.id]] IN ({:p4}, {:p5}) WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > 1)))) OR LOWER([[demo1.text]]) > 1 OR LOWER([[demo1.bool]]) > 1 OR LOWER([[demo1.url]]) > 1 OR LOWER([[demo1.select_one]]) > 1 OR LOWER([[demo1.select_many]]) > 1 OR LOWER([[demo1.file_one]]) > 1 OR LOWER([[demo1.file_many]]) > 1 OR LOWER([[demo1.number]]) > 1 OR LOWER([[demo1.email]]) > 1 OR LOWER([[demo1.datetime]]) > 1 OR LOWER((CASE WHEN json_valid([[demo1.json]]) THEN JSON_EXTRACT([[demo1.json]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo1.json]]), '$.pb') END)) > 1 OR LOWER([[demo1.rel_one]]) > 1 OR LOWER([[demo1.rel_many]]) > 1 OR ((LOWER([[demo1_rel_many.name]]) > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT LOWER([[__mm_demo1_rel_many.name]]) as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.rel_many]]) THEN [[__mm_demo1.rel_many]] ELSE json_array([[__mm_demo1.rel_many]]) END) `__mm_demo1_rel_many_je` LEFT JOIN `users` `__mm_demo1_rel_many` ON [[__mm_demo1_rel_many.id]] = [[__mm_demo1_rel_many_je.value]] WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > 1)))) OR LOWER([[demo1.created]]) > 1)",
},
{
"isset modifier",
"demo1",
"@request.body.a:isset > true ||" +
"@request.body.b:isset > true ||" +
@@ -314,7 +421,7 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
"@request.body.rel_many:each > true &&" +
"@request.body.rel_many:each ?< true",
false,
"SELECT DISTINCT `demo1`.* FROM `demo1` LEFT JOIN json_each({:dataEachTEST}) `__dataEach_select_one_je` LEFT JOIN json_each({:dataEachTEST}) `__dataEach_select_many_je` LEFT JOIN json_each({:dataEachTEST}) `__dataEach_file_one_je` LEFT JOIN json_each({:dataEachTEST}) `__dataEach_file_many_je` LEFT JOIN json_each({:dataEachTEST}) `__dataEach_rel_one_je` LEFT JOIN json_each({:dataEachTEST}) `__dataEach_rel_many_je` WHERE ([[__dataEach_select_one_je.value]] > 1 AND [[__dataEach_select_one_je.value]] < 1 AND (([[__dataEach_select_many_je.value]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__dataEach_select_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each({:mmdataEachTEST}) `__mm__dataEach_select_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] > 1)) OR ([[__smTEST.multiMatchValue]] IS NULL))))) AND [[__dataEach_select_many_je.value]] < 1 AND [[__dataEach_file_one_je.value]] > 1 AND [[__dataEach_file_one_je.value]] < 1 AND (([[__dataEach_file_many_je.value]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__dataEach_file_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each({:mmdataEachTEST}) `__mm__dataEach_file_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] > 1)) OR ([[__smTEST.multiMatchValue]] IS NULL))))) AND [[__dataEach_file_many_je.value]] < 1 AND [[__dataEach_rel_one_je.value]] > 1 AND [[__dataEach_rel_one_je.value]] < 1 AND (([[__dataEach_rel_many_je.value]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__dataEach_rel_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each({:mmdataEachTEST}) `__mm__dataEach_rel_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] > 1)) OR ([[__smTEST.multiMatchValue]] IS NULL))))) AND [[__dataEach_rel_many_je.value]] < 1)",
"SELECT DISTINCT `demo1`.* FROM `demo1` LEFT JOIN json_each({:dataEachTEST}) `__dataEach_select_one_je` LEFT JOIN json_each({:dataEachTEST}) `__dataEach_select_many_je` LEFT JOIN json_each({:dataEachTEST}) `__dataEach_file_one_je` LEFT JOIN json_each({:dataEachTEST}) `__dataEach_file_many_je` LEFT JOIN json_each({:dataEachTEST}) `__dataEach_rel_one_je` LEFT JOIN json_each({:dataEachTEST}) `__dataEach_rel_many_je` WHERE ([[__dataEach_select_one_je.value]] > 1 AND [[__dataEach_select_one_je.value]] < 1 AND (([[__dataEach_select_many_je.value]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__dataEach_select_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each({:mmdataEachTEST}) `__mm__dataEach_select_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > 1)))) AND [[__dataEach_select_many_je.value]] < 1 AND [[__dataEach_file_one_je.value]] > 1 AND [[__dataEach_file_one_je.value]] < 1 AND (([[__dataEach_file_many_je.value]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__dataEach_file_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each({:mmdataEachTEST}) `__mm__dataEach_file_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > 1)))) AND [[__dataEach_file_many_je.value]] < 1 AND [[__dataEach_rel_one_je.value]] > 1 AND [[__dataEach_rel_one_je.value]] < 1 AND (([[__dataEach_rel_many_je.value]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__dataEach_rel_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each({:mmdataEachTEST}) `__mm__dataEach_rel_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > 1)))) AND [[__dataEach_rel_many_je.value]] < 1)",
},
{
"regular arrayble:each fields",
@@ -332,7 +439,7 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
"rel_many:each > true &&" +
"rel_many:each ?< true",
false,
"SELECT DISTINCT `demo1`.* FROM `demo1` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.select_one]]) THEN [[demo1.select_one]] ELSE json_array([[demo1.select_one]]) END) `demo1_select_one_je` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.select_many]]) THEN [[demo1.select_many]] ELSE json_array([[demo1.select_many]]) END) `demo1_select_many_je` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.file_one]]) THEN [[demo1.file_one]] ELSE json_array([[demo1.file_one]]) END) `demo1_file_one_je` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.file_many]]) THEN [[demo1.file_many]] ELSE json_array([[demo1.file_many]]) END) `demo1_file_many_je` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.rel_one]]) THEN [[demo1.rel_one]] ELSE json_array([[demo1.rel_one]]) END) `demo1_rel_one_je` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.rel_many]]) THEN [[demo1.rel_many]] ELSE json_array([[demo1.rel_many]]) END) `demo1_rel_many_je` WHERE ([[demo1_select_one_je.value]] > 1 AND [[demo1_select_one_je.value]] < 1 AND (([[demo1_select_many_je.value]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_select_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.select_many]]) THEN [[__mm_demo1.select_many]] ELSE json_array([[__mm_demo1.select_many]]) END) `__mm_demo1_select_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] > 1)) OR ([[__smTEST.multiMatchValue]] IS NULL))))) AND [[demo1_select_many_je.value]] < 1 AND [[demo1_file_one_je.value]] > 1 AND [[demo1_file_one_je.value]] < 1 AND (([[demo1_file_many_je.value]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_file_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.file_many]]) THEN [[__mm_demo1.file_many]] ELSE json_array([[__mm_demo1.file_many]]) END) `__mm_demo1_file_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] > 1)) OR ([[__smTEST.multiMatchValue]] IS NULL))))) AND [[demo1_file_many_je.value]] < 1 AND [[demo1_rel_one_je.value]] > 1 AND [[demo1_rel_one_je.value]] < 1 AND (([[demo1_rel_many_je.value]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_rel_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.rel_many]]) THEN [[__mm_demo1.rel_many]] ELSE json_array([[__mm_demo1.rel_many]]) END) `__mm_demo1_rel_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] > 1)) OR ([[__smTEST.multiMatchValue]] IS NULL))))) AND [[demo1_rel_many_je.value]] < 1)",
"SELECT DISTINCT `demo1`.* FROM `demo1` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.select_one]]) THEN [[demo1.select_one]] ELSE json_array([[demo1.select_one]]) END) `demo1_select_one_je` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.select_many]]) THEN [[demo1.select_many]] ELSE json_array([[demo1.select_many]]) END) `demo1_select_many_je` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.file_one]]) THEN [[demo1.file_one]] ELSE json_array([[demo1.file_one]]) END) `demo1_file_one_je` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.file_many]]) THEN [[demo1.file_many]] ELSE json_array([[demo1.file_many]]) END) `demo1_file_many_je` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.rel_one]]) THEN [[demo1.rel_one]] ELSE json_array([[demo1.rel_one]]) END) `demo1_rel_one_je` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.rel_many]]) THEN [[demo1.rel_many]] ELSE json_array([[demo1.rel_many]]) END) `demo1_rel_many_je` WHERE ([[demo1_select_one_je.value]] > 1 AND [[demo1_select_one_je.value]] < 1 AND (([[demo1_select_many_je.value]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_select_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.select_many]]) THEN [[__mm_demo1.select_many]] ELSE json_array([[__mm_demo1.select_many]]) END) `__mm_demo1_select_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > 1)))) AND [[demo1_select_many_je.value]] < 1 AND [[demo1_file_one_je.value]] > 1 AND [[demo1_file_one_je.value]] < 1 AND (([[demo1_file_many_je.value]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_file_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.file_many]]) THEN [[__mm_demo1.file_many]] ELSE json_array([[__mm_demo1.file_many]]) END) `__mm_demo1_file_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > 1)))) AND [[demo1_file_many_je.value]] < 1 AND [[demo1_rel_one_je.value]] > 1 AND [[demo1_rel_one_je.value]] < 1 AND (([[demo1_rel_many_je.value]] > 1) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_rel_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.rel_many]]) THEN [[__mm_demo1.rel_many]] ELSE json_array([[__mm_demo1.rel_many]]) END) `__mm_demo1_rel_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > 1)))) AND [[demo1_rel_many_je.value]] < 1)",
},
{
"arrayble:each vs arrayble:each",
@@ -342,7 +449,7 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
"select_many:each ?< select_one:each &&" +
"select_many:each = @request.body.select_many:each",
false,
"SELECT DISTINCT `demo1`.* FROM `demo1` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.select_one]]) THEN [[demo1.select_one]] ELSE json_array([[demo1.select_one]]) END) `demo1_select_one_je` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.select_many]]) THEN [[demo1.select_many]] ELSE json_array([[demo1.select_many]]) END) `demo1_select_many_je` LEFT JOIN json_each({:dataEachTEST}) `__dataEach_select_many_je` WHERE (((COALESCE([[demo1_select_one_je.value]], '') IS NOT COALESCE([[demo1_select_many_je.value]], '')) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_select_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.select_many]]) THEN [[__mm_demo1.select_many]] ELSE json_array([[__mm_demo1.select_many]]) END) `__mm_demo1_select_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE ((NOT (COALESCE([[demo1_select_one_je.value]], '') IS NOT COALESCE([[__smTEST.multiMatchValue]], ''))) OR ([[__smTEST.multiMatchValue]] IS NULL))))) AND (([[demo1_select_many_je.value]] > [[demo1_select_one_je.value]]) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_select_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.select_many]]) THEN [[__mm_demo1.select_many]] ELSE json_array([[__mm_demo1.select_many]]) END) `__mm_demo1_select_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] > [[demo1_select_one_je.value]])) OR ([[__smTEST.multiMatchValue]] IS NULL))))) AND [[demo1_select_many_je.value]] < [[demo1_select_one_je.value]] AND (([[demo1_select_many_je.value]] = [[__dataEach_select_many_je.value]]) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_select_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.select_many]]) THEN [[__mm_demo1.select_many]] ELSE json_array([[__mm_demo1.select_many]]) END) `__mm_demo1_select_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mlTEST}} LEFT JOIN (SELECT [[__mm__dataEach_select_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each({:mmdataEachTEST}) `__mm__dataEach_select_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mrTEST}} WHERE NOT (COALESCE([[__mlTEST.multiMatchValue]], '') = COALESCE([[__mrTEST.multiMatchValue]], ''))))))",
"SELECT DISTINCT `demo1`.* FROM `demo1` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.select_one]]) THEN [[demo1.select_one]] ELSE json_array([[demo1.select_one]]) END) `demo1_select_one_je` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.select_many]]) THEN [[demo1.select_many]] ELSE json_array([[demo1.select_many]]) END) `demo1_select_many_je` LEFT JOIN json_each({:dataEachTEST}) `__dataEach_select_many_je` WHERE (((COALESCE([[demo1_select_one_je.value]], '') IS NOT COALESCE([[demo1_select_many_je.value]], '')) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_select_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.select_many]]) THEN [[__mm_demo1.select_many]] ELSE json_array([[__mm_demo1.select_many]]) END) `__mm_demo1_select_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE NOT (COALESCE([[demo1_select_one_je.value]], '') IS NOT COALESCE([[__smTEST.multiMatchValue]], ''))))) AND (([[demo1_select_many_je.value]] > [[demo1_select_one_je.value]]) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_select_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.select_many]]) THEN [[__mm_demo1.select_many]] ELSE json_array([[__mm_demo1.select_many]]) END) `__mm_demo1_select_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] > [[demo1_select_one_je.value]])))) AND [[demo1_select_many_je.value]] < [[demo1_select_one_je.value]] AND (([[demo1_select_many_je.value]] = [[__dataEach_select_many_je.value]]) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_select_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.select_many]]) THEN [[__mm_demo1.select_many]] ELSE json_array([[__mm_demo1.select_many]]) END) `__mm_demo1_select_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mlTEST}} LEFT JOIN (SELECT [[__mm__dataEach_select_many_je.value]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each({:mmdataEachTEST}) `__mm__dataEach_select_many_je` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mrTEST}} WHERE NOT (COALESCE([[__mlTEST.multiMatchValue]], '') = COALESCE([[__mrTEST.multiMatchValue]], ''))))))",
},
{
"mixed multi-match vs multi-match",
@@ -354,7 +461,7 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
"@collection.demo2.active ?= rel_many.rel.active &&" +
"rel_many.email > @request.body.rel_many.email",
false,
"SELECT DISTINCT `demo1`.* FROM `demo1` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.rel_many]]) THEN [[demo1.rel_many]] ELSE json_array([[demo1.rel_many]]) END) `demo1_rel_many_je` LEFT JOIN `users` `demo1_rel_many` ON [[demo1_rel_many.id]] = [[demo1_rel_many_je.value]] LEFT JOIN `demo2` `demo1_rel_many_rel` ON [[demo1_rel_many_rel.id]] = [[demo1_rel_many.rel]] LEFT JOIN `demo1` `demo1_rel_one` ON [[demo1_rel_one.id]] = [[demo1.rel_one]] LEFT JOIN `demo2` `__collection_demo2` LEFT JOIN `users` `__data_users_rel_many` ON [[__data_users_rel_many.id]] IN ({:p0}, {:p1}) WHERE (((COALESCE([[demo1_rel_many_rel.active]], '') IS NOT COALESCE([[demo1_rel_many.name]], '')) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_rel_many_rel.active]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.rel_many]]) THEN [[__mm_demo1.rel_many]] ELSE json_array([[__mm_demo1.rel_many]]) END) `__mm_demo1_rel_many_je` LEFT JOIN `users` `__mm_demo1_rel_many` ON [[__mm_demo1_rel_many.id]] = [[__mm_demo1_rel_many_je.value]] LEFT JOIN `demo2` `__mm_demo1_rel_many_rel` ON [[__mm_demo1_rel_many_rel.id]] = [[__mm_demo1_rel_many.rel]] WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mlTEST}} LEFT JOIN (SELECT [[__mm_demo1_rel_many.name]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.rel_many]]) THEN [[__mm_demo1.rel_many]] ELSE json_array([[__mm_demo1.rel_many]]) END) `__mm_demo1_rel_many_je` LEFT JOIN `users` `__mm_demo1_rel_many` ON [[__mm_demo1_rel_many.id]] = [[__mm_demo1_rel_many_je.value]] WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mrTEST}} WHERE ((NOT (COALESCE([[__mlTEST.multiMatchValue]], '') IS NOT COALESCE([[__mrTEST.multiMatchValue]], ''))) OR ([[__mlTEST.multiMatchValue]] IS NULL) OR ([[__mrTEST.multiMatchValue]] IS NULL))))) AND COALESCE([[demo1_rel_many_rel.active]], '') = COALESCE([[demo1_rel_many.name]], '') AND (([[demo1_rel_many_rel.title]] LIKE ('%' || [[demo1_rel_one.email]] || '%') ESCAPE '\\') AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_rel_many_rel.title]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.rel_many]]) THEN [[__mm_demo1.rel_many]] ELSE json_array([[__mm_demo1.rel_many]]) END) `__mm_demo1_rel_many_je` LEFT JOIN `users` `__mm_demo1_rel_many` ON [[__mm_demo1_rel_many.id]] = [[__mm_demo1_rel_many_je.value]] LEFT JOIN `demo2` `__mm_demo1_rel_many_rel` ON [[__mm_demo1_rel_many_rel.id]] = [[__mm_demo1_rel_many.rel]] WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] LIKE ('%' || [[demo1_rel_one.email]] || '%') ESCAPE '\\')) OR ([[__smTEST.multiMatchValue]] IS NULL))))) AND ((COALESCE([[__collection_demo2.active]], '') = COALESCE([[demo1_rel_many_rel.active]], '')) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__collection_demo2.active]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN `demo2` `__mm__collection_demo2` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mlTEST}} LEFT JOIN (SELECT [[__mm_demo1_rel_many_rel.active]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.rel_many]]) THEN [[__mm_demo1.rel_many]] ELSE json_array([[__mm_demo1.rel_many]]) END) `__mm_demo1_rel_many_je` LEFT JOIN `users` `__mm_demo1_rel_many` ON [[__mm_demo1_rel_many.id]] = [[__mm_demo1_rel_many_je.value]] LEFT JOIN `demo2` `__mm_demo1_rel_many_rel` ON [[__mm_demo1_rel_many_rel.id]] = [[__mm_demo1_rel_many.rel]] WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mrTEST}} WHERE NOT (COALESCE([[__mlTEST.multiMatchValue]], '') = COALESCE([[__mrTEST.multiMatchValue]], ''))))) AND COALESCE([[__collection_demo2.active]], '') = COALESCE([[demo1_rel_many_rel.active]], '') AND (((([[demo1_rel_many.email]] > [[__data_users_rel_many.email]]) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_rel_many.email]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.rel_many]]) THEN [[__mm_demo1.rel_many]] ELSE json_array([[__mm_demo1.rel_many]]) END) `__mm_demo1_rel_many_je` LEFT JOIN `users` `__mm_demo1_rel_many` ON [[__mm_demo1_rel_many.id]] = [[__mm_demo1_rel_many_je.value]] WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mlTEST}} LEFT JOIN (SELECT [[__data_mm_users_rel_many.email]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN `users` `__data_mm_users_rel_many` ON [[__data_mm_users_rel_many.id]] IN ({:p2}, {:p3}) WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mrTEST}} WHERE ((NOT ([[__mlTEST.multiMatchValue]] > [[__mrTEST.multiMatchValue]])) OR ([[__mlTEST.multiMatchValue]] IS NULL) OR ([[__mrTEST.multiMatchValue]] IS NULL)))))) AND ([[demo1_rel_many.emailVisibility]] = TRUE)))",
"SELECT DISTINCT `demo1`.* FROM `demo1` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.rel_many]]) THEN [[demo1.rel_many]] ELSE json_array([[demo1.rel_many]]) END) `demo1_rel_many_je` LEFT JOIN `users` `demo1_rel_many` ON [[demo1_rel_many.id]] = [[demo1_rel_many_je.value]] LEFT JOIN `demo2` `demo1_rel_many_rel` ON [[demo1_rel_many_rel.id]] = [[demo1_rel_many.rel]] LEFT JOIN `demo1` `demo1_rel_one` ON [[demo1_rel_one.id]] = [[demo1.rel_one]] LEFT JOIN `demo2` `__collection_demo2` LEFT JOIN `users` `__data_users_rel_many` ON [[__data_users_rel_many.id]] IN ({:p0}, {:p1}) WHERE (((COALESCE([[demo1_rel_many_rel.active]], '') IS NOT COALESCE([[demo1_rel_many.name]], '')) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_rel_many_rel.active]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.rel_many]]) THEN [[__mm_demo1.rel_many]] ELSE json_array([[__mm_demo1.rel_many]]) END) `__mm_demo1_rel_many_je` LEFT JOIN `users` `__mm_demo1_rel_many` ON [[__mm_demo1_rel_many.id]] = [[__mm_demo1_rel_many_je.value]] LEFT JOIN `demo2` `__mm_demo1_rel_many_rel` ON [[__mm_demo1_rel_many_rel.id]] = [[__mm_demo1_rel_many.rel]] WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mlTEST}} LEFT JOIN (SELECT [[__mm_demo1_rel_many.name]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.rel_many]]) THEN [[__mm_demo1.rel_many]] ELSE json_array([[__mm_demo1.rel_many]]) END) `__mm_demo1_rel_many_je` LEFT JOIN `users` `__mm_demo1_rel_many` ON [[__mm_demo1_rel_many.id]] = [[__mm_demo1_rel_many_je.value]] WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mrTEST}} WHERE NOT (COALESCE([[__mlTEST.multiMatchValue]], '') IS NOT COALESCE([[__mrTEST.multiMatchValue]], ''))))) AND COALESCE([[demo1_rel_many_rel.active]], '') = COALESCE([[demo1_rel_many.name]], '') AND (([[demo1_rel_many_rel.title]] LIKE ('%' || [[demo1_rel_one.email]] || '%') ESCAPE '\\') AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_rel_many_rel.title]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.rel_many]]) THEN [[__mm_demo1.rel_many]] ELSE json_array([[__mm_demo1.rel_many]]) END) `__mm_demo1_rel_many_je` LEFT JOIN `users` `__mm_demo1_rel_many` ON [[__mm_demo1_rel_many.id]] = [[__mm_demo1_rel_many_je.value]] LEFT JOIN `demo2` `__mm_demo1_rel_many_rel` ON [[__mm_demo1_rel_many_rel.id]] = [[__mm_demo1_rel_many.rel]] WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] LIKE ('%' || [[demo1_rel_one.email]] || '%') ESCAPE '\\')))) AND ((COALESCE([[__collection_demo2.active]], '') = COALESCE([[demo1_rel_many_rel.active]], '')) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm__collection_demo2.active]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN `demo2` `__mm__collection_demo2` WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mlTEST}} LEFT JOIN (SELECT [[__mm_demo1_rel_many_rel.active]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.rel_many]]) THEN [[__mm_demo1.rel_many]] ELSE json_array([[__mm_demo1.rel_many]]) END) `__mm_demo1_rel_many_je` LEFT JOIN `users` `__mm_demo1_rel_many` ON [[__mm_demo1_rel_many.id]] = [[__mm_demo1_rel_many_je.value]] LEFT JOIN `demo2` `__mm_demo1_rel_many_rel` ON [[__mm_demo1_rel_many_rel.id]] = [[__mm_demo1_rel_many.rel]] WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mrTEST}} WHERE NOT (COALESCE([[__mlTEST.multiMatchValue]], '') = COALESCE([[__mrTEST.multiMatchValue]], ''))))) AND COALESCE([[__collection_demo2.active]], '') = COALESCE([[demo1_rel_many_rel.active]], '') AND (((([[demo1_rel_many.email]] > [[__data_users_rel_many.email]]) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo1_rel_many.email]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo1.rel_many]]) THEN [[__mm_demo1.rel_many]] ELSE json_array([[__mm_demo1.rel_many]]) END) `__mm_demo1_rel_many_je` LEFT JOIN `users` `__mm_demo1_rel_many` ON [[__mm_demo1_rel_many.id]] = [[__mm_demo1_rel_many_je.value]] WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mlTEST}} LEFT JOIN (SELECT [[__data_mm_users_rel_many.email]] as [[multiMatchValue]] FROM `demo1` `__mm_demo1` LEFT JOIN `users` `__data_mm_users_rel_many` ON [[__data_mm_users_rel_many.id]] IN ({:p2}, {:p3}) WHERE `__mm_demo1`.`id` = `demo1`.`id`) {{__mrTEST}} WHERE NOT ([[__mlTEST.multiMatchValue]] > [[__mrTEST.multiMatchValue]]))))) AND ([[demo1_rel_many.emailVisibility]] = TRUE)))",
},
{
"@request.body.arrayable:length fields",
@@ -387,7 +494,7 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
"self_rel_one.rel_many_cascade.files:length != 7 &&" +
"self_rel_one.rel_many_cascade.files:length ?!= 8",
false,
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `demo4` `__data_demo4_self_rel_one` ON [[__data_demo4_self_rel_one.id]]={:p0} LEFT JOIN `demo3` `__data_demo3_rel_many_cascade` ON [[__data_demo3_rel_many_cascade.id]] IN ({:p1}, {:p2}) LEFT JOIN `demo3` `__data_demo3_rel_one_cascade` ON [[__data_demo3_rel_one_cascade.id]]={:p3} LEFT JOIN `demo4` `demo4_self_rel_one` ON [[demo4_self_rel_one.id]] = [[demo4.self_rel_one]] LEFT JOIN json_each(CASE WHEN json_valid([[demo4_self_rel_one.rel_many_cascade]]) THEN [[demo4_self_rel_one.rel_many_cascade]] ELSE json_array([[demo4_self_rel_one.rel_many_cascade]]) END) `demo4_self_rel_one_rel_many_cascade_je` LEFT JOIN `demo3` `demo4_self_rel_one_rel_many_cascade` ON [[demo4_self_rel_one_rel_many_cascade.id]] = [[demo4_self_rel_one_rel_many_cascade_je.value]] WHERE (json_array_length(CASE WHEN json_valid([[__data_demo4_self_rel_one.self_rel_many]]) THEN [[__data_demo4_self_rel_one.self_rel_many]] ELSE (CASE WHEN [[__data_demo4_self_rel_one.self_rel_many]] = '' OR [[__data_demo4_self_rel_one.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[__data_demo4_self_rel_one.self_rel_many]]) END) END) > {:TEST} AND json_array_length(CASE WHEN json_valid([[__data_demo4_self_rel_one.self_rel_many]]) THEN [[__data_demo4_self_rel_one.self_rel_many]] ELSE (CASE WHEN [[__data_demo4_self_rel_one.self_rel_many]] = '' OR [[__data_demo4_self_rel_one.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[__data_demo4_self_rel_one.self_rel_many]]) END) END) > {:TEST} AND json_array_length(CASE WHEN json_valid([[__data_demo3_rel_many_cascade.files]]) THEN [[__data_demo3_rel_many_cascade.files]] ELSE (CASE WHEN [[__data_demo3_rel_many_cascade.files]] = '' OR [[__data_demo3_rel_many_cascade.files]] IS NULL THEN json_array() ELSE json_array([[__data_demo3_rel_many_cascade.files]]) END) END) < {:TEST} AND ((json_array_length(CASE WHEN json_valid([[__data_demo3_rel_many_cascade.files]]) THEN [[__data_demo3_rel_many_cascade.files]] ELSE (CASE WHEN [[__data_demo3_rel_many_cascade.files]] = '' OR [[__data_demo3_rel_many_cascade.files]] IS NULL THEN json_array() ELSE json_array([[__data_demo3_rel_many_cascade.files]]) END) END) < {:TEST}) AND (NOT EXISTS (SELECT 1 FROM (SELECT json_array_length(CASE WHEN json_valid([[__data_mm_demo3_rel_many_cascade.files]]) THEN [[__data_mm_demo3_rel_many_cascade.files]] ELSE (CASE WHEN [[__data_mm_demo3_rel_many_cascade.files]] = '' OR [[__data_mm_demo3_rel_many_cascade.files]] IS NULL THEN json_array() ELSE json_array([[__data_mm_demo3_rel_many_cascade.files]]) END) END) as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `demo3` `__data_mm_demo3_rel_many_cascade` ON [[__data_mm_demo3_rel_many_cascade.id]] IN ({:p8}, {:p9}) WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] < {:TEST})) OR ([[__smTEST.multiMatchValue]] IS NULL))))) AND json_array_length(CASE WHEN json_valid([[__data_demo3_rel_one_cascade.files]]) THEN [[__data_demo3_rel_one_cascade.files]] ELSE (CASE WHEN [[__data_demo3_rel_one_cascade.files]] = '' OR [[__data_demo3_rel_one_cascade.files]] IS NULL THEN json_array() ELSE json_array([[__data_demo3_rel_one_cascade.files]]) END) END) < {:TEST} AND json_array_length(CASE WHEN json_valid([[demo4_self_rel_one.self_rel_many]]) THEN [[demo4_self_rel_one.self_rel_many]] ELSE (CASE WHEN [[demo4_self_rel_one.self_rel_many]] = '' OR [[demo4_self_rel_one.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4_self_rel_one.self_rel_many]]) END) END) = {:TEST} AND json_array_length(CASE WHEN json_valid([[demo4_self_rel_one.self_rel_many]]) THEN [[demo4_self_rel_one.self_rel_many]] ELSE (CASE WHEN [[demo4_self_rel_one.self_rel_many]] = '' OR [[demo4_self_rel_one.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4_self_rel_one.self_rel_many]]) END) END) = {:TEST} AND ((json_array_length(CASE WHEN json_valid([[demo4_self_rel_one_rel_many_cascade.files]]) THEN [[demo4_self_rel_one_rel_many_cascade.files]] ELSE (CASE WHEN [[demo4_self_rel_one_rel_many_cascade.files]] = '' OR [[demo4_self_rel_one_rel_many_cascade.files]] IS NULL THEN json_array() ELSE json_array([[demo4_self_rel_one_rel_many_cascade.files]]) END) END) IS NOT {:TEST}) AND (NOT EXISTS (SELECT 1 FROM (SELECT json_array_length(CASE WHEN json_valid([[__mm_demo4_self_rel_one_rel_many_cascade.files]]) THEN [[__mm_demo4_self_rel_one_rel_many_cascade.files]] ELSE (CASE WHEN [[__mm_demo4_self_rel_one_rel_many_cascade.files]] = '' OR [[__mm_demo4_self_rel_one_rel_many_cascade.files]] IS NULL THEN json_array() ELSE json_array([[__mm_demo4_self_rel_one_rel_many_cascade.files]]) END) END) as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `demo4` `__mm_demo4_self_rel_one` ON [[__mm_demo4_self_rel_one.id]] = [[__mm_demo4.self_rel_one]] LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo4_self_rel_one.rel_many_cascade]]) THEN [[__mm_demo4_self_rel_one.rel_many_cascade]] ELSE json_array([[__mm_demo4_self_rel_one.rel_many_Line truncated
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `demo4` `__data_demo4_self_rel_one` ON [[__data_demo4_self_rel_one.id]]={:p0} LEFT JOIN `demo3` `__data_demo3_rel_many_cascade` ON [[__data_demo3_rel_many_cascade.id]] IN ({:p1}, {:p2}) LEFT JOIN `demo3` `__data_demo3_rel_one_cascade` ON [[__data_demo3_rel_one_cascade.id]]={:p3} LEFT JOIN `demo4` `demo4_self_rel_one` ON [[demo4_self_rel_one.id]] = [[demo4.self_rel_one]] LEFT JOIN json_each(CASE WHEN json_valid([[demo4_self_rel_one.rel_many_cascade]]) THEN [[demo4_self_rel_one.rel_many_cascade]] ELSE json_array([[demo4_self_rel_one.rel_many_cascade]]) END) `demo4_self_rel_one_rel_many_cascade_je` LEFT JOIN `demo3` `demo4_self_rel_one_rel_many_cascade` ON [[demo4_self_rel_one_rel_many_cascade.id]] = [[demo4_self_rel_one_rel_many_cascade_je.value]] WHERE (json_array_length(CASE WHEN json_valid([[__data_demo4_self_rel_one.self_rel_many]]) THEN [[__data_demo4_self_rel_one.self_rel_many]] ELSE (CASE WHEN [[__data_demo4_self_rel_one.self_rel_many]] = '' OR [[__data_demo4_self_rel_one.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[__data_demo4_self_rel_one.self_rel_many]]) END) END) > {:TEST} AND json_array_length(CASE WHEN json_valid([[__data_demo4_self_rel_one.self_rel_many]]) THEN [[__data_demo4_self_rel_one.self_rel_many]] ELSE (CASE WHEN [[__data_demo4_self_rel_one.self_rel_many]] = '' OR [[__data_demo4_self_rel_one.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[__data_demo4_self_rel_one.self_rel_many]]) END) END) > {:TEST} AND json_array_length(CASE WHEN json_valid([[__data_demo3_rel_many_cascade.files]]) THEN [[__data_demo3_rel_many_cascade.files]] ELSE (CASE WHEN [[__data_demo3_rel_many_cascade.files]] = '' OR [[__data_demo3_rel_many_cascade.files]] IS NULL THEN json_array() ELSE json_array([[__data_demo3_rel_many_cascade.files]]) END) END) < {:TEST} AND ((json_array_length(CASE WHEN json_valid([[__data_demo3_rel_many_cascade.files]]) THEN [[__data_demo3_rel_many_cascade.files]] ELSE (CASE WHEN [[__data_demo3_rel_many_cascade.files]] = '' OR [[__data_demo3_rel_many_cascade.files]] IS NULL THEN json_array() ELSE json_array([[__data_demo3_rel_many_cascade.files]]) END) END) < {:TEST}) AND (NOT EXISTS (SELECT 1 FROM (SELECT json_array_length(CASE WHEN json_valid([[__data_mm_demo3_rel_many_cascade.files]]) THEN [[__data_mm_demo3_rel_many_cascade.files]] ELSE (CASE WHEN [[__data_mm_demo3_rel_many_cascade.files]] = '' OR [[__data_mm_demo3_rel_many_cascade.files]] IS NULL THEN json_array() ELSE json_array([[__data_mm_demo3_rel_many_cascade.files]]) END) END) as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `demo3` `__data_mm_demo3_rel_many_cascade` ON [[__data_mm_demo3_rel_many_cascade.id]] IN ({:p8}, {:p9}) WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] < {:TEST})))) AND json_array_length(CASE WHEN json_valid([[__data_demo3_rel_one_cascade.files]]) THEN [[__data_demo3_rel_one_cascade.files]] ELSE (CASE WHEN [[__data_demo3_rel_one_cascade.files]] = '' OR [[__data_demo3_rel_one_cascade.files]] IS NULL THEN json_array() ELSE json_array([[__data_demo3_rel_one_cascade.files]]) END) END) < {:TEST} AND json_array_length(CASE WHEN json_valid([[demo4_self_rel_one.self_rel_many]]) THEN [[demo4_self_rel_one.self_rel_many]] ELSE (CASE WHEN [[demo4_self_rel_one.self_rel_many]] = '' OR [[demo4_self_rel_one.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4_self_rel_one.self_rel_many]]) END) END) = {:TEST} AND json_array_length(CASE WHEN json_valid([[demo4_self_rel_one.self_rel_many]]) THEN [[demo4_self_rel_one.self_rel_many]] ELSE (CASE WHEN [[demo4_self_rel_one.self_rel_many]] = '' OR [[demo4_self_rel_one.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4_self_rel_one.self_rel_many]]) END) END) = {:TEST} AND ((json_array_length(CASE WHEN json_valid([[demo4_self_rel_one_rel_many_cascade.files]]) THEN [[demo4_self_rel_one_rel_many_cascade.files]] ELSE (CASE WHEN [[demo4_self_rel_one_rel_many_cascade.files]] = '' OR [[demo4_self_rel_one_rel_many_cascade.files]] IS NULL THEN json_array() ELSE json_array([[demo4_self_rel_one_rel_many_cascade.files]]) END) END) IS NOT {:TEST}) AND (NOT EXISTS (SELECT 1 FROM (SELECT json_array_length(CASE WHEN json_valid([[__mm_demo4_self_rel_one_rel_many_cascade.files]]) THEN [[__mm_demo4_self_rel_one_rel_many_cascade.files]] ELSE (CASE WHEN [[__mm_demo4_self_rel_one_rel_many_cascade.files]] = '' OR [[__mm_demo4_self_rel_one_rel_many_cascade.files]] IS NULL THEN json_array() ELSE json_array([[__mm_demo4_self_rel_one_rel_many_cascade.files]]) END) END) as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `demo4` `__mm_demo4_self_rel_one` ON [[__mm_demo4_self_rel_one.id]] = [[__mm_demo4.self_rel_one]] LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo4_self_rel_one.rel_many_cascade]]) THEN [[__mm_demo4_self_rel_one.rel_many_cascade]] ELSE json_array([[__mm_demo4_self_rel_one.rel_many_cascade]]) END) `__mm_demo4_self_rel_one_rel_mLine truncated
},
{
"json_extract and json_array_length COALESCE equal normalizations",
+26 -15
View File
@@ -37,9 +37,9 @@ var (
type Record struct {
collection *Collection
originalData map[string]any
customVisibility *store.Store[bool]
data *store.Store[any]
expand *store.Store[any]
customVisibility *store.Store[string, bool]
data *store.Store[string, any]
expand *store.Store[string, any]
BaseModel
@@ -537,8 +537,8 @@ func newRecordsFromNullStringMaps(collection *Collection, rows []dbx.NullStringM
func NewRecord(collection *Collection) *Record {
record := &Record{
collection: collection,
data: store.New[any](nil),
customVisibility: store.New[bool](nil),
data: store.New[string, any](nil),
customVisibility: store.New[string, bool](nil),
originalData: make(map[string]any, len(collection.Fields)),
}
@@ -681,7 +681,7 @@ func (m *Record) Expand() map[string]any {
// SetExpand replaces the current Record's expand with the provided expand arg data (shallow copied).
func (m *Record) SetExpand(expand map[string]any) {
if m.expand == nil {
m.expand = store.New[any](nil)
m.expand = store.New[string, any](nil)
}
m.expand.Reset(expand)
@@ -833,6 +833,10 @@ func (m *Record) IgnoreEmailVisibility(state bool) *Record {
//
// This could be used if you want to save only the record fields that you've changed
// without overwrite other untouched fields in case of concurrent update.
//
// Note that the fields change comparison is based on the current fields against m.Original()
// (aka. if you have performed save on the same Record instance multiple times you may have to refetch it,
// so that m.Original() could reflect the last saved change).
func (m *Record) IgnoreUnchangedFields(state bool) *Record {
m.ignoreUnchangedFields = state
return m
@@ -1409,12 +1413,18 @@ func onRecordValidate(e *RecordEvent) error {
func onRecordSaveExecute(e *RecordEvent) error {
if e.Record.Collection().IsAuth() {
// ensure that the token key is different on password change
old := e.Record.Original()
if !e.Record.IsNew() &&
old.TokenKey() == e.Record.TokenKey() &&
old.Get(FieldNamePassword) != e.Record.Get(FieldNamePassword) {
e.Record.RefreshTokenKey()
// ensure that the token key is regenerated on password change or email change
if !e.Record.IsNew() {
lastSavedRecord, err := e.App.FindRecordById(e.Record.Collection(), e.Record.Id)
if err != nil {
return err
}
if lastSavedRecord.TokenKey() == e.Record.TokenKey() &&
(lastSavedRecord.Get(FieldNamePassword) != e.Record.Get(FieldNamePassword) ||
lastSavedRecord.Email() != e.Record.Email()) {
e.Record.RefreshTokenKey()
}
}
// cross-check that the auth record id is unique across all auth collections.
@@ -1452,7 +1462,7 @@ func onRecordDeleteExecute(e *RecordEvent) error {
//
// note: the select is outside of the transaction to minimize
// SQLITE_BUSY errors when mixing read&write in a single transaction
refs, err := e.App.FindCollectionReferences(e.Record.Collection())
refs, err := e.App.FindCachedCollectionReferences(e.Record.Collection())
if err != nil {
return err
}
@@ -1491,12 +1501,13 @@ func cascadeRecordDelete(app App, mainRecord *Record, refs map[*Collection][]Fie
for _, refCollection := range sortedRefKeys {
fields, ok := refs[refCollection]
if refCollection.IsView() || !ok {
if !ok || refCollection.IsView() {
continue // skip missing or view collections
}
recordTableName := inflector.Columnify(refCollection.Name)
for _, field := range fields {
recordTableName := inflector.Columnify(refCollection.Name)
prefixedFieldName := recordTableName + "." + inflector.Columnify(field.GetName())
query := app.RecordQuery(refCollection)
+21
View File
@@ -1,5 +1,7 @@
package core
import "github.com/pocketbase/pocketbase/tools/security"
// Email returns the "email" record field value (usually available with Auth collections).
func (m *Record) Email() string {
return m.GetString(FieldNameEmail)
@@ -51,6 +53,25 @@ func (m *Record) SetPassword(password string) {
m.Set(FieldNamePassword, password)
}
// SetRandomPassword sets the "password" auth record field to a random autogenerated value.
//
// The autogenerated password is ~30 characters and it is set directly as hash,
// aka. the field plain password value validators (length, pattern, etc.) are ignored
// (this is usually used as part of the auto created OTP or OAuth2 user flows).
func (m *Record) SetRandomPassword() string {
pass := security.RandomString(30)
m.Set(FieldNamePassword, pass)
m.RefreshTokenKey() // manually refresh the token key because the plain password is resetted
// unset the plain value to skip the field validators
if raw, ok := m.GetRaw(FieldNamePassword).(*PasswordFieldValue); ok {
raw.Plain = ""
}
return pass
}
// ValidatePassword validates a plain password against the "password" record field.
//
// Returns false if the password is incorrect.
+41
View File
@@ -1,9 +1,11 @@
package core_test
import (
"context"
"testing"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tests"
"github.com/pocketbase/pocketbase/tools/security"
)
@@ -117,3 +119,42 @@ func TestRecordPassword(t *testing.T) {
})
}
}
func TestRecordSetRandomPassword(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
oldTokenKey := "old_tokenKey"
record := core.NewRecord(core.NewAuthCollection("test"))
record.SetTokenKey(oldTokenKey)
pass := record.SetRandomPassword()
if pass == "" {
t.Fatal("Expected non-empty generated random password")
}
if !record.ValidatePassword(pass) {
t.Fatal("Expected the generated random password to be valid")
}
if record.TokenKey() == oldTokenKey {
t.Fatal("Expected token key to change")
}
f, ok := record.Collection().Fields.GetByName(core.FieldNamePassword).(*core.PasswordField)
if !ok {
t.Fatal("Expected *core.PasswordField")
}
// ensure that the field validators will be ignored
f.Min = 1
f.Max = 2
f.Pattern = `\d+`
if err := f.ValidateValue(context.Background(), app, record); err != nil {
t.Fatalf("Expected password field plain value validators to be ignored, got %v", err)
}
}
+40 -25
View File
@@ -1822,44 +1822,59 @@ func TestRecordSaveIdUpdateNoValidation(t *testing.T) {
}
}
func TestRecordSaveWithChangedPassword(t *testing.T) {
func TestRecordSaveWithAutoTokenKeyRefresh(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
record, err := app.FindAuthRecordByEmail("nologin", "test@example.com")
if err != nil {
t.Fatal(err)
scenarios := []struct {
name string
payload map[string]any
expectedChange bool
}{
{
"no email or password change",
map[string]any{"name": "example"},
false,
},
{
"password change",
map[string]any{"password": "1234567890"},
true,
},
{
"email change",
map[string]any{"email": "test_update@example.com"},
true,
},
}
originalTokenKey := record.TokenKey()
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
record, err := app.FindFirstRecordByFilter("nologin", "1=1")
if err != nil {
t.Fatal(err)
}
t.Run("no password change shouldn't change the tokenKey", func(t *testing.T) {
record.Set("name", "example")
originalTokenKey := record.TokenKey()
if err := app.Save(record); err != nil {
t.Fatal(err)
}
record.Load(s.payload)
tokenKey := record.TokenKey()
if tokenKey == "" || originalTokenKey != tokenKey {
t.Fatalf("Expected tokenKey to not change, got %q VS %q", originalTokenKey, tokenKey)
}
})
err = app.Save(record)
if err != nil {
t.Fatal(err)
}
t.Run("password change should change the tokenKey", func(t *testing.T) {
record.Set("password", "1234567890")
newTokenKey := record.TokenKey()
if err := app.Save(record); err != nil {
t.Fatal(err)
}
hasChange := originalTokenKey != newTokenKey
tokenKey := record.TokenKey()
if tokenKey == "" || originalTokenKey == tokenKey {
t.Fatalf("Expected tokenKey to change, got %q VS %q", originalTokenKey, tokenKey)
}
})
if hasChange != s.expectedChange {
t.Fatalf("Expected hasChange %v, got %v", s.expectedChange, hasChange)
}
})
}
}
func TestRecordDelete(t *testing.T) {
+5 -4
View File
@@ -50,7 +50,6 @@ func NormalizeUniqueIndexError(err error, tableOrAlias string, fieldNames []stri
return err
}
//
if _, ok := err.(validation.Errors); ok {
return err
}
@@ -59,12 +58,14 @@ func NormalizeUniqueIndexError(err error, tableOrAlias string, fieldNames []stri
// check for unique constraint failure
if strings.Contains(msg, "unique constraint failed") {
// note: extra space to unify multi-columns lookup
msg = strings.ReplaceAll(strings.TrimSpace(msg), ",", " ") + " "
normalizedErrs := validation.Errors{}
msg = strings.ReplaceAll(strings.TrimSpace(msg), ",", " ")
for _, name := range fieldNames {
// blank space to unify multi-columns lookup
if strings.Contains(msg+" ", strings.ToLower(tableOrAlias+"."+name)) {
// note: extra space to exclude other fields starting with the current field name
if strings.Contains(msg, strings.ToLower(tableOrAlias+"."+name+" ")) {
normalizedErrs[name] = validation.NewError("validation_not_unique", "Value must be unique")
}
}
+7
View File
@@ -92,6 +92,13 @@ func TestNormalizeUniqueIndexError(t *testing.T) {
[]string{"a", "b", "c"},
[]string{"a", "b"},
},
{
"unique index error with matching table name and field starting with the name of another non-unique field",
errors.New("UNIQUE constraint failed for fields test.a_2,test.c"),
"test",
[]string{"a", "a_2", "c"},
[]string{"a_2", "c"},
},
}
for _, s := range scenarios {
+9 -7
View File
@@ -437,9 +437,11 @@ func getQueryTableInfo(app App, selectQuery string) ([]*TableInfoRow, error) {
// Raw query identifiers parser
// -------------------------------------------------------------------
var joinReplaceRegex = regexp.MustCompile(`(?im)\s+(full\s+outer\s+join|left\s+outer\s+join|right\s+outer\s+join|full\s+join|cross\s+join|inner\s+join|outer\s+join|left\s+join|right\s+join|join)\s+?`)
var discardReplaceRegex = regexp.MustCompile(`(?im)\s+(where|group\s+by|having|order|limit|with)\s+?`)
var commentsReplaceRegex = regexp.MustCompile(`(?m)(\/\*[\s\S]+\*\/)|(--.+$)`)
var (
joinReplaceRegex = regexp.MustCompile(`(?im)\s+(full\s+outer\s+join|left\s+outer\s+join|right\s+outer\s+join|full\s+join|cross\s+join|inner\s+join|outer\s+join|left\s+join|right\s+join|join)\s+?`)
discardReplaceRegex = regexp.MustCompile(`(?im)\s+(where|group\s+by|having|order|limit|with)\s+?`)
commentsReplaceRegex = regexp.MustCompile(`(?m)(\/\*[\s\S]+\*\/)|(--.+$)`)
)
type identifier struct {
original string
@@ -453,9 +455,9 @@ type identifiersParser struct {
func (p *identifiersParser) parse(selectQuery string) error {
str := strings.Trim(strings.TrimSpace(selectQuery), ";")
str = joinReplaceRegex.ReplaceAllString(str, " _join_ ")
str = discardReplaceRegex.ReplaceAllString(str, " _discard_ ")
str = commentsReplaceRegex.ReplaceAllString(str, "")
str = joinReplaceRegex.ReplaceAllString(str, " __pb_join__ ")
str = discardReplaceRegex.ReplaceAllString(str, " __pb_discard__ ")
tk := tokenizer.NewFromString(str)
tk.Separators(',', ' ', '\n', '\t')
@@ -490,7 +492,7 @@ func (p *identifiersParser) parse(selectQuery string) error {
skip = false
partType = "from"
activeBuilder = &fromParts
case "_join_":
case "__pb_join__":
skip = false
// the previous part was also a join
@@ -500,7 +502,7 @@ func (p *identifiersParser) parse(selectQuery string) error {
partType = "join"
activeBuilder = &joinParts
case "_discard_":
case "__pb_discard__":
// skip following tokens
skip = true
default:
+13 -3
View File
@@ -246,11 +246,21 @@ func TestCreateViewFields(t *testing.T) {
`
select
-- test single line
id,
text,
demo1.id,
demo1.text,
/* multi
line comment */
url, created, updated from demo1
demo1.url, demo1.created, demo2.updated from demo1
-- comment before join
join demo2 ON (
-- comment inside join
demo2.id = demo1.id
)
-- comment before where
where (
-- comment inside where
demo2.id = demo1.id
)
`,
false,
map[string]string{
+14 -14
View File
@@ -27,9 +27,9 @@ type RecordUpsert struct {
accessLevel int
// extra password fields
Password string `form:"password" json:"password"`
PasswordConfirm string `form:"passwordConfirm" json:"passwordConfirm"`
OldPassword string `form:"oldPassword" json:"oldPassword"`
password string
passwordConfirm string
oldPassword string
}
// NewRecordUpsert creates a new [RecordUpsert] form from the provided [core.App] and [core.Record] instances
@@ -93,13 +93,13 @@ func (form *RecordUpsert) Load(data map[string]any) {
// load the special auth form fields
if isAuth {
if v, ok := data["password"]; ok {
form.Password = cast.ToString(v)
form.password = cast.ToString(v)
}
if v, ok := data["passwordConfirm"]; ok {
form.PasswordConfirm = cast.ToString(v)
form.passwordConfirm = cast.ToString(v)
}
if v, ok := data["oldPassword"]; ok {
form.OldPassword = cast.ToString(v)
form.oldPassword = cast.ToString(v)
}
excludeFields = append(excludeFields, "passwordConfirm", "oldPassword") // skip non-schema password fields
@@ -137,9 +137,9 @@ func (form *RecordUpsert) validateFormFields() error {
validateData := map[string]any{
"email": form.record.Email(),
"verified": form.record.Verified(),
"password": form.Password,
"passwordConfirm": form.PasswordConfirm,
"oldPassword": form.OldPassword,
"password": form.password,
"passwordConfirm": form.passwordConfirm,
"oldPassword": form.oldPassword,
}
return validation.Validate(validateData,
@@ -165,17 +165,17 @@ func (form *RecordUpsert) validateFormFields() error {
validation.Key(
"password",
validation.When(
(isNew || form.PasswordConfirm != "" || form.OldPassword != ""),
(isNew || form.passwordConfirm != "" || form.oldPassword != ""),
validation.Required,
),
),
validation.Key(
"passwordConfirm",
validation.When(
(isNew || form.Password != "" || form.OldPassword != ""),
(isNew || form.password != "" || form.oldPassword != ""),
validation.Required,
),
validation.By(validators.Equal(form.Password)),
validation.By(validators.Equal(form.password)),
),
validation.Key(
"oldPassword",
@@ -183,7 +183,7 @@ func (form *RecordUpsert) validateFormFields() error {
// - form.HasManageAccess() is not satisfied
// - changing the existing password
validation.When(
!isNew && !form.HasManageAccess() && (form.Password != "" || form.PasswordConfirm != ""),
!isNew && !form.HasManageAccess() && (form.password != "" || form.passwordConfirm != ""),
validation.Required,
validation.By(form.checkOldPassword),
),
@@ -205,7 +205,7 @@ func (form *RecordUpsert) checkOldPassword(value any) error {
return nil
}
// @todo consider removing and executing the Create API rule without dummy insert.
// Deprecated: It was previously used as part of the record create action but it is not needed anymore and will be removed in the future.
//
// DrySubmit performs a temp form submit within a transaction and reverts it at the end.
// For actual record persistence, check the [RecordUpsert.Submit()] method.
+16 -14
View File
@@ -64,15 +64,15 @@ func TestRecordUpsertLoad(t *testing.T) {
`"text":"test_text"`,
`"number":456`,
`"select_many":["optionB","optionC"]`,
`"password":""`,
`"oldPassword":""`,
`"passwordConfirm":""`,
`"created":""`,
`"updated":""`,
`"json":null`,
},
notExpected: []string{
`"custom"`,
`"password"`,
`"oldPassword"`,
`"passwordConfirm"`,
`"select_many-"`,
`"select_many+"`,
},
@@ -89,9 +89,11 @@ func TestRecordUpsertLoad(t *testing.T) {
record: core.NewRecord(usersCol),
expected: []string{
`"email":"test@example.com"`,
`"oldPassword":"123"`,
`"password":"456"`,
`"passwordConfirm":"789"`,
},
notExpected: []string{
`"oldPassword"`,
`"passwordConfirm"`,
},
},
{
@@ -110,8 +112,10 @@ func TestRecordUpsertLoad(t *testing.T) {
`"email":"test@example.com"`,
`"tokenKey":""`,
`"password":"456"`,
`"oldPassword":"123"`,
`"passwordConfirm":"789"`,
},
notExpected: []string{
`"oldPassword"`,
`"passwordConfirm"`,
},
},
{
@@ -130,8 +134,10 @@ func TestRecordUpsertLoad(t *testing.T) {
`"email":"test@example.com"`,
`"tokenKey":"abc"`,
`"password":"456"`,
`"oldPassword":"123"`,
`"passwordConfirm":"789"`,
},
notExpected: []string{
`"oldPassword"`,
`"passwordConfirm"`,
},
},
{
@@ -168,11 +174,7 @@ func TestRecordUpsertLoad(t *testing.T) {
form.Load(s.data)
loaded := map[string]any{
"oldPassword": form.OldPassword,
"password": form.Password,
"passwordConfirm": form.PasswordConfirm,
}
loaded := map[string]any{}
maps.Copy(loaded, s.record.FieldsData())
maps.Copy(loaded, s.record.CustomData())
+31 -31
View File
@@ -4,11 +4,11 @@ go 1.23
require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/aws/aws-sdk-go-v2 v1.32.7
github.com/aws/aws-sdk-go-v2/config v1.28.7
github.com/aws/aws-sdk-go-v2/credentials v1.17.48
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.44
github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1
github.com/aws/aws-sdk-go-v2 v1.32.8
github.com/aws/aws-sdk-go-v2/config v1.28.10
github.com/aws/aws-sdk-go-v2/credentials v1.17.51
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.48
github.com/aws/aws-sdk-go-v2/service/s3 v1.72.2
github.com/aws/smithy-go v1.22.1
github.com/disintegration/imaging v1.6.2
github.com/domodwyer/mailyak/v3 v3.6.2
@@ -16,18 +16,18 @@ require (
github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc
github.com/fatih/color v1.18.0
github.com/fsnotify/fsnotify v1.7.0
github.com/gabriel-vasile/mimetype v1.4.7
github.com/gabriel-vasile/mimetype v1.4.8
github.com/ganigeorgiev/fexpr v0.4.1
github.com/go-ozzo/ozzo-validation/v4 v4.3.0
github.com/golang-jwt/jwt/v4 v4.5.1
github.com/pocketbase/dbx v1.11.0
github.com/pocketbase/tygoja v0.0.0-20241015175937-d6ff411a0f75
github.com/pocketbase/tygoja v0.0.0-20250103200817-ca580d8c5119
github.com/spf13/cast v1.7.1
github.com/spf13/cobra v1.8.1
gocloud.dev v0.40.0
golang.org/x/crypto v0.31.0
golang.org/x/net v0.33.0
golang.org/x/oauth2 v0.24.0
golang.org/x/crypto v0.32.0
golang.org/x/net v0.34.0
golang.org/x/oauth2 v0.25.0
golang.org/x/sync v0.10.0
modernc.org/sqlite v1.34.4
)
@@ -35,18 +35,18 @@ require (
require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.27 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.8 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.8 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.8 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.9 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.8 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.6 // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/dop251/base64dec v0.0.0-20231022112746-c6c9f9a96217 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
@@ -58,7 +58,7 @@ require (
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
@@ -68,19 +68,19 @@ require (
go.opencensus.io v0.24.0 // indirect
golang.org/x/image v0.23.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.28.0 // indirect
golang.org/x/tools v0.29.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/api v0.214.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb // indirect
google.golang.org/api v0.216.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 // indirect
google.golang.org/grpc v1.69.2 // indirect
google.golang.org/protobuf v1.36.0 // indirect
modernc.org/gc/v3 v3.0.0-20241213165251-3bc300f6d0c9 // indirect
google.golang.org/protobuf v1.36.2 // indirect
modernc.org/gc/v3 v3.0.0-20250105121824-520be1a3aee6 // indirect
modernc.org/libc v1.55.3 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.1 // indirect
modernc.org/strutil v1.2.1 // indirect
modernc.org/token v1.1.0 // indirect
)
+64 -66
View File
@@ -25,42 +25,42 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw=
github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
github.com/aws/aws-sdk-go-v2 v1.32.8 h1:cZV+NUS/eGxKXMtmyhtYPJ7Z4YLoI/V8bkTdRZfYhGo=
github.com/aws/aws-sdk-go-v2 v1.32.8/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc=
github.com/aws/aws-sdk-go-v2/config v1.28.7 h1:GduUnoTXlhkgnxTD93g1nv4tVPILbdNQOzav+Wpg7AE=
github.com/aws/aws-sdk-go-v2/config v1.28.7/go.mod h1:vZGX6GVkIE8uECSUHB6MWAUsd4ZcG2Yq/dMa4refR3M=
github.com/aws/aws-sdk-go-v2/credentials v1.17.48 h1:IYdLD1qTJ0zanRavulofmqut4afs45mOWEI+MzZtTfQ=
github.com/aws/aws-sdk-go-v2/credentials v1.17.48/go.mod h1:tOscxHN3CGmuX9idQ3+qbkzrjVIx32lqDSU1/0d/qXs=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 h1:kqOrpojG71DxJm/KDPO+Z/y1phm1JlC8/iT+5XRmAn8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22/go.mod h1:NtSFajXVVL8TA2QNngagVZmUtXciyrHOt7xgz4faS/M=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.44 h1:2zxMLXLedpB4K1ilbJFxtMKsVKaexOqDttOhc0QGm3Q=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.44/go.mod h1:VuLHdqwjSvgftNC7yqPWyGVhEwPmJpeRi07gOgOfHF8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8=
github.com/aws/aws-sdk-go-v2/config v1.28.10 h1:fKODZHfqQu06pCzR69KJ3GuttraRJkhlC8g80RZ0Dfg=
github.com/aws/aws-sdk-go-v2/config v1.28.10/go.mod h1:PvdxRYZ5Um9QMq9PQ0zHHNdtKK+he2NHtFCUFMXWXeg=
github.com/aws/aws-sdk-go-v2/credentials v1.17.51 h1:F/9Sm6Y6k4LqDesZDPJCLxQGXNNHd/ZtJiWd0lCZKRk=
github.com/aws/aws-sdk-go-v2/credentials v1.17.51/go.mod h1:TKbzCHm43AoPyA+iLGGcruXd4AFhF8tOmLex2R9jWNQ=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.23 h1:IBAoD/1d8A8/1aA8g4MBVtTRHhXRiNAgwdbo/xRM2DI=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.23/go.mod h1:vfENuCM7dofkgKpYzuzf1VT1UKkA/YL3qanfBn7HCaA=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.48 h1:XnXVe2zRyPf0+fAW5L05esmngvBpC6DQZK7oZB/z/Co=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.48/go.mod h1:S3wey90OrS4f7kYxH6PT175YyEcHTORY07++HurMaRM=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27 h1:jSJjSBzw8VDIbWv+mmvBSP8ezsztMYJGH+eKqi9AmNs=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27/go.mod h1:/DAhLbFRgwhmvJdOfSm+WwikZrCuUJiA4WgJG0fTNSw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27 h1:l+X4K77Dui85pIj5foXDhPlnqcNRG2QUyvca300lXh8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27/go.mod h1:KvZXSFEXm6x84yE8qffKvT3x8J5clWnVFXphpohhzJ8=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 h1:GeNJsIFHB+WW5ap2Tec4K6dzcVTsRbsT1Lra46Hv9ME=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26/go.mod h1:zfgMpwHDXX2WGoG84xG2H+ZlPTkJUU4YUvx2svLQYWo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.27 h1:AmB5QxnD+fBFrg9LcqzkgF/CaYvMyU/BTlejG4t1S7Q=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.27/go.mod h1:Sai7P3xTiyv9ZUYO3IFxMnmiIP759/67iQbU4kdmkyU=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7 h1:tB4tNw83KcajNAzaIMhkhVI2Nt8fAZd5A5ro113FEMY=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7/go.mod h1:lvpyBGkZ3tZ9iSsUIcC2EWp+0ywa7aK3BLT+FwZi+mQ=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 h1:8eUsivBQzZHqe/3FE+cqwfH+0p5Jo8PFM/QYQSmeZ+M=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7/go.mod h1:kLPQvGUmxn/fqiCrDeohwG33bq2pQpGeY62yRO6Nrh0=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7 h1:Hi0KGbrnr57bEHWM0bJ1QcBzxLrL/k2DHvGYhb8+W1w=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7/go.mod h1:wKNgWgExdjjrm4qvfbTorkvocEstaoDl4WCvGfeCy9c=
github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1 h1:aOVVZJgWbaH+EJYPvEgkNhCEbXXvH7+oML36oaPK3zE=
github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1/go.mod h1:r+xl5yzMk9083rMR+sJ5TYj9Tihvf/l1oxzZXDgGj2Q=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 h1:CvuUmnXI7ebaUAhbJcDy9YQx8wHR69eZ9I7q5hszt/g=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.8/go.mod h1:XDeGv1opzwm8ubxddF0cgqkZWsyOtw4lr6dxwmb6YQg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 h1:F2rBfNAL5UyswqoeWv9zs74N/NanhK16ydHW1pahX6E=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7/go.mod h1:JfyQ0g2JG8+Krq0EuZNnRwX0mU0HrwY/tG6JNfcqh4k=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 h1:Xgv/hyNgvLda/M9l9qxXc4UFSgppnRczLxlMs5Ae/QY=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.3/go.mod h1:5Gn+d+VaaRgsjewpMvGazt0WfcFO+Md4wLOuBfGR9Bc=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.8 h1:iwYS40JnrBeA9e9aI5S6KKN4EB2zR4iUVYN0nwVivz4=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.8/go.mod h1:Fm9Mi+ApqmFiknZtGpohVcBGvpTu542VC4XO9YudRi0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.8 h1:cWno7lefSH6Pp+mSznagKCgfDGeZRin66UvYUqAkyeA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.8/go.mod h1:tPD+VjU3ABTBoEJ3nctu5Nyg4P4yjqSH5bJGGkY4+XE=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.8 h1:/Mn7gTedG86nbpjT4QEKsN1D/fThiYe1qvq7WsBGNHg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.8/go.mod h1:Ae3va9LPmvjj231ukHB6UeT8nS7wTPfC3tMZSZMwNYg=
github.com/aws/aws-sdk-go-v2/service/s3 v1.72.2 h1:a7aQ3RW+ug4IbhoQp29NZdc7vqrzKZZfWZSaQAXOZvQ=
github.com/aws/aws-sdk-go-v2/service/s3 v1.72.2/go.mod h1:xMekrnhmJ5aqmyxtmALs7mlvXw5xRh+eYjOjvrIIFJ4=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.9 h1:YqtxripbjWb2QLyzRK9pByfEDvgg95gpC2AyDq4hFE8=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.9/go.mod h1:lV8iQpg6OLOfBnqbGMBKYjilBlf633qwHnBEiMSPoHY=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.8 h1:6dBT1Lz8fK11m22R+AqfRsFn8320K0T5DTGxxOQBSMw=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.8/go.mod h1:/kiBvRQXBc6xeJTYzhSdGvJ5vm1tjaDEjH+MSeRJnlY=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.6 h1:VwhTrsTuVn52an4mXx29PqRzs2Dvu921NpGk7y43tAM=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.6/go.mod h1:+8h7PZb3yY5ftmVLD7ocEoE98hdc8PoKS0H3wfx1dlc=
github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -98,8 +98,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/ganigeorgiev/fexpr v0.4.1 h1:hpUgbUEEWIZhSDBtf4M9aUNfQQ0BZkGRaMePy7Gcx5k=
github.com/ganigeorgiev/fexpr v0.4.1/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
@@ -168,10 +168,9 @@ github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3x
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
@@ -183,8 +182,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU=
github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
github.com/pocketbase/tygoja v0.0.0-20241015175937-d6ff411a0f75 h1:XSbmekxgmbI2uPrre/nkCz7y8VsV652TPb3hAYzPb74=
github.com/pocketbase/tygoja v0.0.0-20241015175937-d6ff411a0f75/go.mod h1:hKJWPGFqavk3cdTa47Qvs8g37lnfI57OYdVVbIqW5aE=
github.com/pocketbase/tygoja v0.0.0-20250103200817-ca580d8c5119 h1:TjQtEReJDTpvlNFTRjuHvPQpJHAeJdcQF130eCAAT/o=
github.com/pocketbase/tygoja v0.0.0-20250103200817-ca580d8c5119/go.mod h1:hKJWPGFqavk3cdTa47Qvs8g37lnfI57OYdVVbIqW5aE=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
@@ -229,8 +228,8 @@ gocloud.dev v0.40.0/go.mod h1:drz+VyYNBvrMTW0KZiBAYEdl8lbNZx+OQ7oQvdrFmSQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
@@ -251,11 +250,11 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -271,14 +270,13 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -286,8 +284,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -295,14 +293,14 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA=
google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE=
google.golang.org/api v0.216.0 h1:xnEHy+xWFrtYInWPy8OdGFsyIfWJjtVnO39g7pz2BFY=
google.golang.org/api v0.216.0/go.mod h1:K9wzQMvWi47Z9IU7OgdOofvZuw75Ge3PPITImZR/UyI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
@@ -313,8 +311,8 @@ google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phr
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc=
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb h1:3oy2tynMOP1QbTC0MsNNAV+Se8M2Bd0A5+x1QHyw+pI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
@@ -331,8 +329,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
@@ -350,21 +348,21 @@ modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20241213165251-3bc300f6d0c9 h1:ovz6yUKX71igz2yvk4NpiCL5fvdjZAI+DhuDEGx1xyU=
modernc.org/gc/v3 v3.0.0-20241213165251-3bc300f6d0c9/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/gc/v3 v3.0.0-20250105121824-520be1a3aee6 h1:JoKwHjIFumiKrjMbp1cNbC5E9UyCgA/ZcID0xOWQ2N8=
modernc.org/gc/v3 v3.0.0-20250105121824-520be1a3aee6/go.mod h1:LG5UO1Ran4OO0JRKz2oNiXhR5nNrgz0PzH7UKhz0aMU=
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.8.1 h1:HS1HRg1jEohnuONobEq2WrLEhLyw8+J42yLFTnllm2A=
modernc.org/memory v1.8.1/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.34.4 h1:sjdARozcL5KJBvYQvLlZEmctRgW9xqIZc2ncN7PU0P8=
modernc.org/sqlite v1.34.4/go.mod h1:3QQFCG2SEMtc2nv+Wq4cQCH7Hjcg+p/RMlS1XK+zwbk=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+1 -1
View File
@@ -1,4 +1,4 @@
// Package mails implements various helper methods for sending user and admin
// Package mails implements various helper methods for sending common
// emails like forgotten password, verification, etc.
package mails
+77 -39
View File
@@ -30,6 +30,7 @@ import (
"github.com/pocketbase/pocketbase/tools/mailer"
"github.com/pocketbase/pocketbase/tools/router"
"github.com/pocketbase/pocketbase/tools/security"
"github.com/pocketbase/pocketbase/tools/store"
"github.com/pocketbase/pocketbase/tools/subscriptions"
"github.com/pocketbase/pocketbase/tools/types"
"github.com/spf13/cast"
@@ -295,6 +296,8 @@ func wrapMiddlewares(executors *vmsPool, rawMiddlewares ...goja.Value) ([]*hook.
return wrappedMiddlewares, nil
}
var cachedArrayOfTypes = store.New[reflect.Type, reflect.Type](nil)
func baseBinds(vm *goja.Runtime) {
vm.SetFieldNameMapper(FieldMapper{})
@@ -348,10 +351,11 @@ func baseBinds(vm *goja.Runtime) {
vm.Set("arrayOf", func(model any) any {
mt := reflect.TypeOf(model)
st := reflect.SliceOf(mt)
elem := reflect.New(st).Elem()
st := cachedArrayOfTypes.GetOrSet(mt, func() reflect.Type {
return reflect.SliceOf(mt)
})
return elem.Addr().Interface()
return reflect.New(st).Elem().Addr().Interface()
})
vm.Set("unmarshal", func(data, dst any) error {
@@ -899,12 +903,42 @@ func httpClientBinds(vm *goja.Runtime) {
// -------------------------------------------------------------------
// normalizeException checks if the provided error is a goja.Exception
// and attempts to return its underlying Go error.
//
// note: using just goja.Exception.Unwrap() is insufficient and may falsely result in nil.
func normalizeException(err error) error {
if err == nil {
return nil
}
jsException, ok := err.(*goja.Exception)
if !ok {
return err // no exception
}
switch v := jsException.Value().Export().(type) {
case error:
err = v
case map[string]any: // goja.GoError
if vErr, ok := v["value"].(error); ok {
err = vErr
}
}
return err
}
var cachedFactoryFuncTypes = store.New[string, reflect.Type](nil)
// registerFactoryAsConstructor registers the factory function as native JS constructor.
//
// If there is missing or nil arguments, their type zero value is used.
func registerFactoryAsConstructor(vm *goja.Runtime, constructorName string, factoryFunc any) {
rv := reflect.ValueOf(factoryFunc)
rt := reflect.TypeOf(factoryFunc)
rt := cachedFactoryFuncTypes.GetOrSet(constructorName, func() reflect.Type {
return reflect.TypeOf(factoryFunc)
})
totalArgs := rt.NumIn()
vm.Set(constructorName, func(call goja.ConstructorCall) *goja.Object {
@@ -970,6 +1004,8 @@ func structConstructorUnmarshal(vm *goja.Runtime, call goja.ConstructorCall, ins
return instanceValue
}
var cachedDynamicModels = store.New[string, *dynamicModelType](nil)
// newDynamicModel creates a new dynamic struct with fields based
// on the specified "shape".
//
@@ -980,7 +1016,40 @@ func structConstructorUnmarshal(vm *goja.Runtime, call goja.ConstructorCall, ins
// "total": 0,
// })
func newDynamicModel(shape map[string]any) any {
shapeValues := make([]reflect.Value, 0, len(shape))
var modelType *dynamicModelType
shapeRaw, err := json.Marshal(shape)
if err != nil {
modelType = getDynamicModelStruct(shape)
} else {
modelType = cachedDynamicModels.GetOrSet(string(shapeRaw), func() *dynamicModelType {
return getDynamicModelStruct(shape)
})
}
rvShapeValues := make([]reflect.Value, len(modelType.shapeValues))
for i, v := range modelType.shapeValues {
rvShapeValues[i] = reflect.ValueOf(v)
}
elem := reflect.New(modelType.structType).Elem()
for i, v := range rvShapeValues {
elem.Field(i).Set(v)
}
return elem.Addr().Interface()
}
type dynamicModelType struct {
structType reflect.Type
shapeValues []any
}
func getDynamicModelStruct(shape map[string]any) *dynamicModelType {
result := new(dynamicModelType)
result.shapeValues = make([]any, 0, len(shape))
structFields := make([]reflect.StructField, 0, len(shape))
for k, v := range shape {
@@ -1001,7 +1070,7 @@ func newDynamicModel(shape map[string]any) any {
vt = reflect.TypeOf(newV)
}
shapeValues = append(shapeValues, reflect.ValueOf(v))
result.shapeValues = append(result.shapeValues, v)
structFields = append(structFields, reflect.StructField{
Name: inflector.UcFirst(k), // ensures that the field is exportable
@@ -1010,38 +1079,7 @@ func newDynamicModel(shape map[string]any) any {
})
}
st := reflect.StructOf(structFields)
elem := reflect.New(st).Elem()
result.structType = reflect.StructOf(structFields)
for i, v := range shapeValues {
elem.Field(i).Set(v)
}
return elem.Addr().Interface()
}
// normalizeException checks if the provided error is a goja.Exception
// and attempts to return its underlying Go error.
//
// note: using just goja.Exception.Unwrap() is insufficient and may falsely result in nil.
func normalizeException(err error) error {
if err == nil {
return nil
}
jsException, ok := err.(*goja.Exception)
if !ok {
return err // no exception
}
switch v := jsException.Value().Export().(type) {
case error:
err = v
case map[string]any: // goja.GoError
if vErr, ok := v["value"].(error); ok {
err = vErr
}
}
return err
return result
}
+1 -1
View File
@@ -464,7 +464,7 @@ func TestBaseBindsMailerMessage(t *testing.T) {
t.Fatal(err)
}
expected := `{"from":{"Name":"test_from","Address":"test_from@example.com"},"to":[{"Name":"test_to1","Address":"test_to1@example.com"},{"Name":"test_to2","Address":"test_to2@example.com"}],"bcc":[{"Name":"test_bcc1","Address":"test_bcc1@example.com"},{"Name":"test_bcc2","Address":"test_bcc2@example.com"}],"cc":[{"Name":"test_cc1","Address":"test_cc1@example.com"},{"Name":"test_cc2","Address":"test_cc2@example.com"}],"subject":"test_subject","html":"test_html","text":"test_text","headers":{"header1":"a","header2":"b"},"attachments":null}`
expected := `{"from":{"Name":"test_from","Address":"test_from@example.com"},"to":[{"Name":"test_to1","Address":"test_to1@example.com"},{"Name":"test_to2","Address":"test_to2@example.com"}],"bcc":[{"Name":"test_bcc1","Address":"test_bcc1@example.com"},{"Name":"test_bcc2","Address":"test_bcc2@example.com"}],"cc":[{"Name":"test_cc1","Address":"test_cc1@example.com"},{"Name":"test_cc2","Address":"test_cc2@example.com"}],"subject":"test_subject","html":"test_html","text":"test_text","headers":{"header1":"a","header2":"b"},"attachments":null,"inlineAttachments":null}`
if string(raw) != expected {
t.Fatalf("Expected \n%s, \ngot \n%s", expected, raw)
File diff suppressed because it is too large. Load diff
+28 -12
View File
@@ -70,27 +70,43 @@ func (t *TestApp) registerEventCall(name string) {
// NewTestApp creates and initializes a test application instance.
//
// It is the caller's responsibility to call `app.Cleanup()`
// when the app is no longer needed.
// It is the caller's responsibility to call app.Cleanup() when the app is no longer needed.
func NewTestApp(optTestDataDir ...string) (*TestApp, error) {
var testDataDir string
if len(optTestDataDir) == 0 || optTestDataDir[0] == "" {
// fallback to the default test data directory
_, currentFile, _, _ := runtime.Caller(0)
testDataDir = filepath.Join(path.Dir(currentFile), "data")
} else {
if len(optTestDataDir) > 0 {
testDataDir = optTestDataDir[0]
}
tempDir, err := TempDirClone(testDataDir)
return NewTestAppWithConfig(core.BaseAppConfig{
DataDir: testDataDir,
EncryptionEnv: "pb_test_env",
})
}
// NewTestAppWithConfig creates and initializes a test application instance
// from the provided config.
//
// If config.DataDir is not set it fallbacks to the default internal test data directory.
//
// config.DataDir is cloned for each new test application instance.
//
// It is the caller's responsibility to call app.Cleanup() when the app is no longer needed.
func NewTestAppWithConfig(config core.BaseAppConfig) (*TestApp, error) {
if config.DataDir == "" {
// fallback to the default test data directory
_, currentFile, _, _ := runtime.Caller(0)
config.DataDir = filepath.Join(path.Dir(currentFile), "data")
}
tempDir, err := TempDirClone(config.DataDir)
if err != nil {
return nil, err
}
app := core.NewBaseApp(core.BaseAppConfig{
DataDir: tempDir,
EncryptionEnv: "pb_test_env",
})
// replace with the clone
config.DataDir = tempDir
app := core.NewBaseApp(config)
// load data dir and db connections
if err := app.Bootstrap(); err != nil {
+41 -19
View File
@@ -11,22 +11,18 @@ package cron
import (
"errors"
"fmt"
"slices"
"sync"
"time"
)
type job struct {
schedule *Schedule
run func()
}
// Cron is a crontab-like struct for tasks/jobs scheduling.
type Cron struct {
timezone *time.Location
ticker *time.Ticker
startTimer *time.Timer
jobs map[string]*job
tickerDone chan bool
jobs []*Job
interval time.Duration
mux sync.RWMutex
}
@@ -40,7 +36,7 @@ func New() *Cron {
return &Cron{
interval: 1 * time.Minute,
timezone: time.UTC,
jobs: map[string]*job{},
jobs: []*Job{},
tickerDone: make(chan bool),
}
}
@@ -82,23 +78,30 @@ func (c *Cron) MustAdd(jobId string, cronExpr string, run func()) {
//
// cronExpr is a regular cron expression, eg. "0 */3 * * *" (aka. at minute 0 past every 3rd hour).
// Check cron.NewSchedule() for the supported tokens.
func (c *Cron) Add(jobId string, cronExpr string, run func()) error {
if run == nil {
return errors.New("failed to add new cron job: run must be non-nil function")
func (c *Cron) Add(jobId string, cronExpr string, fn func()) error {
if fn == nil {
return errors.New("failed to add new cron job: fn must be non-nil function")
}
c.mux.Lock()
defer c.mux.Unlock()
schedule, err := NewSchedule(cronExpr)
if err != nil {
return fmt.Errorf("failed to add new cron job: %w", err)
}
c.jobs[jobId] = &job{
c.mux.Lock()
defer c.mux.Unlock()
// remove previous (if any)
c.jobs = slices.DeleteFunc(c.jobs, func(j *Job) bool {
return j.Id() == jobId
})
// add new
c.jobs = append(c.jobs, &Job{
id: jobId,
fn: fn,
schedule: schedule,
run: run,
}
})
return nil
}
@@ -108,7 +111,13 @@ func (c *Cron) Remove(jobId string) {
c.mux.Lock()
defer c.mux.Unlock()
delete(c.jobs, jobId)
if c.jobs == nil {
return // nothing to remove
}
c.jobs = slices.DeleteFunc(c.jobs, func(j *Job) bool {
return j.Id() == jobId
})
}
// RemoveAll removes all registered cron jobs.
@@ -116,7 +125,7 @@ func (c *Cron) RemoveAll() {
c.mux.Lock()
defer c.mux.Unlock()
c.jobs = map[string]*job{}
c.jobs = []*Job{}
}
// Total returns the current total number of registered cron jobs.
@@ -127,6 +136,19 @@ func (c *Cron) Total() int {
return len(c.jobs)
}
// Jobs returns a shallow copy of the currently registered cron jobs.
func (c *Cron) Jobs() []*Job {
c.mux.RLock()
defer c.mux.RUnlock()
copy := make([]*Job, len(c.jobs))
for i, j := range c.jobs {
copy[i] = j
}
return copy
}
// Stop stops the current cron ticker (if not already).
//
// You can resume the ticker by calling Start().
@@ -200,7 +222,7 @@ func (c *Cron) runDue(t time.Time) {
for _, j := range c.jobs {
if j.schedule.IsDue(moment) {
go j.run()
go j.Run()
}
}
}
+45 -3
View File
@@ -2,6 +2,7 @@ package cron
import (
"encoding/json"
"slices"
"testing"
"time"
)
@@ -98,6 +99,11 @@ func TestCronAddAndRemove(t *testing.T) {
// try to remove non-existing (should be no-op)
c.Remove("missing")
indexedJobs := make(map[string]*Job, len(c.jobs))
for _, j := range c.jobs {
indexedJobs[j.Id()] = j
}
// check job keys
{
expectedKeys := []string{"test3", "test2", "test5"}
@@ -107,7 +113,7 @@ func TestCronAddAndRemove(t *testing.T) {
}
for _, k := range expectedKeys {
if c.jobs[k] == nil {
if indexedJobs[k] == nil {
t.Fatalf("Expected job with key %s, got nil", k)
}
}
@@ -121,7 +127,7 @@ func TestCronAddAndRemove(t *testing.T) {
"test5": `{"minutes":{"1":{}},"hours":{"2":{}},"days":{"3":{}},"months":{"4":{}},"daysOfWeek":{"5":{}}}`,
}
for k, v := range expectedSchedules {
raw, err := json.Marshal(c.jobs[k].schedule)
raw, err := json.Marshal(indexedJobs[k].schedule)
if err != nil {
t.Fatal(err)
}
@@ -148,7 +154,7 @@ func TestCronMustAdd(t *testing.T) {
c.MustAdd("test2", "* * * * *", func() {})
if _, ok := c.jobs["test2"]; !ok {
if !slices.ContainsFunc(c.jobs, func(j *Job) bool { return j.Id() == "test2" }) {
t.Fatal("Couldn't find job test2")
}
}
@@ -208,6 +214,42 @@ func TestCronTotal(t *testing.T) {
}
}
func TestCronJobs(t *testing.T) {
t.Parallel()
c := New()
calls := ""
if err := c.Add("a", "1 * * * *", func() { calls += "a" }); err != nil {
t.Fatal(err)
}
if err := c.Add("b", "2 * * * *", func() { calls += "b" }); err != nil {
t.Fatal(err)
}
// overwrite
if err := c.Add("b", "3 * * * *", func() { calls += "b" }); err != nil {
t.Fatal(err)
}
jobs := c.Jobs()
if len(jobs) != 2 {
t.Fatalf("Expected 2 jobs, got %v", len(jobs))
}
for _, j := range jobs {
j.Run()
}
expectedCalls := "ab"
if calls != expectedCalls {
t.Fatalf("Expected %q calls, got %q", expectedCalls, calls)
}
}
func TestCronStartStop(t *testing.T) {
t.Parallel()
+41
View File
@@ -0,0 +1,41 @@
package cron
import "encoding/json"
// Job defines a single registered cron job.
type Job struct {
fn func()
schedule *Schedule
id string
}
// Id returns the cron job id.
func (j *Job) Id() string {
return j.id
}
// Expression returns the plain cron job schedule expression.
func (j *Job) Expression() string {
return j.schedule.rawExpr
}
// Run runs the cron job function.
func (j *Job) Run() {
if j.fn != nil {
j.fn()
}
}
// MarshalJSON implements [json.Marshaler] and export the current
// jobs data into valid JSON.
func (j Job) MarshalJSON() ([]byte, error) {
plain := struct {
Id string `json:"id"`
Expression string `json:"expression"`
}{
Id: j.Id(),
Expression: j.Expression(),
}
return json.Marshal(plain)
}
+71
View File
@@ -0,0 +1,71 @@
package cron
import (
"encoding/json"
"testing"
)
func TestJobId(t *testing.T) {
expected := "test"
j := Job{id: expected}
if j.Id() != expected {
t.Fatalf("Expected job with id %q, got %q", expected, j.Id())
}
}
func TestJobExpr(t *testing.T) {
expected := "1 2 3 4 5"
s, err := NewSchedule(expected)
if err != nil {
t.Fatal(err)
}
j := Job{schedule: s}
if j.Expression() != expected {
t.Fatalf("Expected job with cron expression %q, got %q", expected, j.Expression())
}
}
func TestJobRun(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Shouldn't panic: %v", r)
}
}()
calls := ""
j1 := Job{}
j2 := Job{fn: func() { calls += "2" }}
j1.Run()
j2.Run()
expected := "2"
if calls != expected {
t.Fatalf("Expected calls %q, got %q", expected, calls)
}
}
func TestJobMarshalJSON(t *testing.T) {
s, err := NewSchedule("1 2 3 4 5")
if err != nil {
t.Fatal(err)
}
j := Job{id: "test_id", schedule: s}
raw, err := json.Marshal(j)
if err != nil {
t.Fatal(err)
}
expected := `{"id":"test_id","expression":"1 2 3 4 5"}`
if str := string(raw); str != expected {
t.Fatalf("Expected\n%s\ngot\n%s", expected, str)
}
}
+3
View File
@@ -35,6 +35,8 @@ type Schedule struct {
Days map[int]struct{} `json:"days"`
Months map[int]struct{} `json:"months"`
DaysOfWeek map[int]struct{} `json:"daysOfWeek"`
rawExpr string
}
// IsDue checks whether the provided Moment satisfies the current Schedule.
@@ -130,6 +132,7 @@ func NewSchedule(cronExpr string) (*Schedule, error) {
Days: days,
Months: months,
DaysOfWeek: daysOfWeek,
rawExpr: cronExpr,
}, nil
}
@@ -375,7 +375,6 @@ func (w *writer) open(r io.Reader, closePipeOnError bool) {
if err != nil {
if closePipeOnError {
w.pr.CloseWithError(err)
w.pr = nil
}
w.err = err
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"github.com/spf13/cast"
)
var cachedPatterns = store.New[*regexp.Regexp](nil)
var cachedPatterns = store.New[string, *regexp.Regexp](nil)
// SubtractSlice returns a new slice with only the "base" elements
// that don't exist in "subtract".
+10 -9
View File
@@ -9,15 +9,16 @@ import (
// Message defines a generic email message struct.
type Message struct {
From mail.Address `json:"from"`
To []mail.Address `json:"to"`
Bcc []mail.Address `json:"bcc"`
Cc []mail.Address `json:"cc"`
Subject string `json:"subject"`
HTML string `json:"html"`
Text string `json:"text"`
Headers map[string]string `json:"headers"`
Attachments map[string]io.Reader `json:"attachments"`
From mail.Address `json:"from"`
To []mail.Address `json:"to"`
Bcc []mail.Address `json:"bcc"`
Cc []mail.Address `json:"cc"`
Subject string `json:"subject"`
HTML string `json:"html"`
Text string `json:"text"`
Headers map[string]string `json:"headers"`
Attachments map[string]io.Reader `json:"attachments"`
InlineAttachments map[string]io.Reader `json:"inlineAttachments"`
}
// Mailer defines a base mail client interface.
+6 -1
View File
@@ -114,11 +114,16 @@ func (c *SMTPClient) send(m *Message) error {
yak.Cc(addressesToStrings(m.Cc, true)...)
}
// add attachements (if any)
// add regular attachements (if any)
for name, data := range m.Attachments {
yak.Attach(name, data)
}
// add inline attachments (if any)
for name, data := range m.InlineAttachments {
yak.AttachInline(name, data)
}
// add custom headers (if any)
var hasMessageId bool
for k, v := range m.Headers {
+31
View File
@@ -0,0 +1,31 @@
package osutils
import (
"os/exec"
"runtime"
"github.com/go-ozzo/ozzo-validation/v4/is"
)
// LaunchURL attempts to open the provided url in the user's default browser.
//
// It is platform dependent and it uses:
// - "open" on macOS
// - "rundll32" on Windows
// - "xdg-open" on everything else (Linux, FreeBSD, etc.)
func LaunchURL(url string) error {
if err := is.URL.Validate(url); err != nil {
return err
}
switch runtime.GOOS {
case "darwin":
return exec.Command("open", url).Start()
case "windows":
// not sure if this is the best command but seems to be the most reliable based on the comments in
// https://stackoverflow.com/questions/3739327/launching-a-website-via-the-windows-commandline#answer-49115945
return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
default:
return exec.Command("xdg-open", url).Start()
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"github.com/pocketbase/pocketbase/tools/list"
)
// MoveDirContent moves the src dir content, that is not listed in the exclide list,
// MoveDirContent moves the src dir content, that is not listed in the exclude list,
// to dest dir (it will be created if missing).
//
// The rootExclude argument is used to specify a list of src root entries to exclude.
+1 -1
View File
@@ -34,7 +34,7 @@ type Event struct {
hook.Event
data store.Store[any]
data store.Store[string, any]
}
// RWUnwrapper specifies that an http.ResponseWriter could be "unwrapped"
+2 -1
View File
@@ -325,7 +325,8 @@ func inferValue(raw string) any {
// try to convert to number
//
// note: expects the provided raw string to match exactly with the minimal string representation of the parsed float
if raw[0] == '-' || (raw[0] >= '0' && raw[0] <= '9') && inferNumberCharsRegex.Match([]byte(raw)) {
if (raw[0] == '-' || (raw[0] >= '0' && raw[0] <= '9')) &&
inferNumberCharsRegex.Match([]byte(raw)) {
v, err := strconv.ParseFloat(raw, 64)
if err == nil && strconv.FormatFloat(v, 'f', -1, 64) == raw {
return v
+2 -1
View File
@@ -31,6 +31,7 @@ func TestUnmarshalRequestData(t *testing.T) {
"000001",
"-000001",
"1.6E-35",
"-1.6E-35",
"10e100",
"1_000_000",
"1.000.000",
@@ -328,7 +329,7 @@ func TestUnmarshalRequestData(t *testing.T) {
name: "*map[string]any",
data: mapData,
dst: pointer(map[string]any{}),
result: `{"bool1":true,"bool2":[true,false],"json_a":null,"json_b":123,"json_c":[1,2,3],"mixed":[true,123,"test"],"number1":1,"number2":[2,3],"number3":[2.1,-3.4],"number4":[0,-0,0.0001],"string0":"","string1":"a","string2":["b","c"],"string3":["0.0","-0.0","000.1","000001","-000001","1.6E-35","10e100","1_000_000","1.000.000"," 123 ","0b1","0xFF","1234A","Infinity","-Infinity","undefined","null"]}`,
result: `{"bool1":true,"bool2":[true,false],"json_a":null,"json_b":123,"json_c":[1,2,3],"mixed":[true,123,"test"],"number1":1,"number2":[2,3],"number3":[2.1,-3.4],"number4":[0,-0,0.0001],"string0":"","string1":"a","string2":["b","c"],"string3":["0.0","-0.0","000.1","000001","-000001","1.6E-35","-1.6E-35","10e100","1_000_000","1.000.000"," 123 ","0b1","0xFF","1234A","Infinity","-Infinity","undefined","null"]}`,
},
{
name: "valid pointer struct (all fields)",
+3 -31
View File
@@ -628,7 +628,7 @@ func (e *manyVsManyExpr) Build(db *dbx.DB, params dbx.Params) string {
Identifier: "[[" + rAlias + ".multiMatchValue]]",
// note: the AfterBuild needs to be handled only once and it
// doesn't matter whether it is applied on the left or right subquery operand
AfterBuild: multiMatchAfterBuildFunc(e.op, lAlias, rAlias),
AfterBuild: dbx.Not, // inverse for the not-exist expression
},
)
@@ -650,7 +650,7 @@ func (e *manyVsManyExpr) Build(db *dbx.DB, params dbx.Params) string {
var _ dbx.Expression = (*manyVsOneExpr)(nil)
// manyVsManyExpr constructs a multi-match many<->one db where expression.
// manyVsOneExpr constructs a multi-match many<->one db where expression.
//
// Expects subQuery to return a subquery with a single "multiMatchValue" column.
//
@@ -676,7 +676,7 @@ func (e *manyVsOneExpr) Build(db *dbx.DB, params dbx.Params) string {
r1 := &ResolverResult{
NoCoalesce: e.noCoalesce,
Identifier: "[[" + alias + ".multiMatchValue]]",
AfterBuild: multiMatchAfterBuildFunc(e.op, alias),
AfterBuild: dbx.Not, // inverse for the not-exist expression
}
r2 := &ResolverResult{
@@ -704,31 +704,3 @@ func (e *manyVsOneExpr) Build(db *dbx.DB, params dbx.Params) string {
whereExpr.Build(db, params),
)
}
func multiMatchAfterBuildFunc(op fexpr.SignOp, multiMatchAliases ...string) func(dbx.Expression) dbx.Expression {
return func(expr dbx.Expression) dbx.Expression {
expr = dbx.Not(expr) // inverse for the not-exist expression
if op == fexpr.SignEq {
return expr
}
orExprs := make([]dbx.Expression, len(multiMatchAliases)+1)
orExprs[0] = expr
// Add an optional "IS NULL" condition(s) to handle the empty rows result.
//
// For example, let's assume that some "rel" field is [nonemptyRel1, nonemptyRel2, emptyRel3],
// The filter "rel.total > 0" ensures that the above will return true only if all relations
// are existing and match the condition.
//
// The "=" operator is excluded because it will never equal directly with NULL anyway
// and also because we want in case "rel.id = ''" is specified to allow
// matching the empty relations (they will match due to the applied COALESCE).
for i, mAlias := range multiMatchAliases {
orExprs[i+1] = dbx.NewExp("[[" + mAlias + ".multiMatchValue]] IS NULL")
}
return dbx.Enclose(dbx.Or(orExprs...))
}
}
+20
View File
@@ -23,6 +23,26 @@ var identifierMacros = map[string]func() (any, error){
return d.String(), nil
},
"@yesterday": func() (any, error) {
yesterday := timeNow().UTC().AddDate(0, 0, -1)
d, err := types.ParseDateTime(yesterday)
if err != nil {
return "", fmt.Errorf("@yesterday: %w", err)
}
return d.String(), nil
},
"@tomorrow": func() (any, error) {
tomorrow := timeNow().UTC().AddDate(0, 0, 1)
d, err := types.ParseDateTime(tomorrow)
if err != nil {
return "", fmt.Errorf("@tomorrow: %w", err)
}
return d.String(), nil
},
"@second": func() (any, error) {
return timeNow().UTC().Second(), nil
},
+2
View File
@@ -14,6 +14,8 @@ func TestIdentifierMacros(t *testing.T) {
testMacros := map[string]any{
"@now": "2023-02-03 04:05:06.000Z",
"@yesterday": "2023-02-02 04:05:06.000Z",
"@tomorrow": "2023-02-04 04:05:06.000Z",
"@second": 6,
"@minute": 5,
"@hour": 4,
+2 -2
View File
@@ -97,7 +97,7 @@ func NewProvider(fieldResolver FieldResolver) *Provider {
}
}
// MaxFilterExpressions changes the default max allowed filter expressions.
// MaxFilterExprLimit changes the default max allowed filter expressions.
//
// Note that currently the limit is applied individually for each separate filter.
func (s *Provider) MaxFilterExprLimit(max int) *Provider {
@@ -105,7 +105,7 @@ func (s *Provider) MaxFilterExprLimit(max int) *Provider {
return s
}
// MaxSortExpressions changes the default max allowed sort expressions.
// MaxSortExprLimit changes the default max allowed sort expressions.
func (s *Provider) MaxSortExprLimit(max int) *Provider {
s.maxSortExprLimit = max
return s
+27 -27
View File
@@ -9,15 +9,15 @@ import (
const ShrinkThreshold = 200 // the number is arbitrary chosen
// Store defines a concurrent safe in memory key-value data store.
type Store[T any] struct {
data map[string]T
type Store[K comparable, T any] struct {
data map[K]T
mu sync.RWMutex
deleted int64
}
// New creates a new Store[T] instance with a shallow copy of the provided data (if any).
func New[T any](data map[string]T) *Store[T] {
s := &Store[T]{}
func New[K comparable, T any](data map[K]T) *Store[K, T] {
s := &Store[K, T]{}
s.Reset(data)
@@ -26,24 +26,24 @@ func New[T any](data map[string]T) *Store[T] {
// Reset clears the store and replaces the store data with a
// shallow copy of the provided newData.
func (s *Store[T]) Reset(newData map[string]T) {
func (s *Store[K, T]) Reset(newData map[K]T) {
s.mu.Lock()
defer s.mu.Unlock()
if len(newData) > 0 {
s.data = make(map[string]T, len(newData))
s.data = make(map[K]T, len(newData))
for k, v := range newData {
s.data[k] = v
}
} else {
s.data = make(map[string]T)
s.data = make(map[K]T)
}
s.deleted = 0
}
// Length returns the current number of elements in the store.
func (s *Store[T]) Length() int {
func (s *Store[K, T]) Length() int {
s.mu.RLock()
defer s.mu.RUnlock()
@@ -51,14 +51,14 @@ func (s *Store[T]) Length() int {
}
// RemoveAll removes all the existing store entries.
func (s *Store[T]) RemoveAll() {
func (s *Store[K, T]) RemoveAll() {
s.Reset(nil)
}
// Remove removes a single entry from the store.
//
// Remove does nothing if key doesn't exist in the store.
func (s *Store[T]) Remove(key string) {
func (s *Store[K, T]) Remove(key K) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -69,7 +69,7 @@ func (s *Store[T]) Remove(key string) {
//
// @todo remove after https://github.com/golang/go/issues/20135
if s.deleted >= ShrinkThreshold {
newData := make(map[string]T, len(s.data))
newData := make(map[K]T, len(s.data))
for k, v := range s.data {
newData[k] = v
}
@@ -79,7 +79,7 @@ func (s *Store[T]) Remove(key string) {
}
// Has checks if element with the specified key exist or not.
func (s *Store[T]) Has(key string) bool {
func (s *Store[K, T]) Has(key K) bool {
s.mu.RLock()
defer s.mu.RUnlock()
@@ -91,7 +91,7 @@ func (s *Store[T]) Has(key string) bool {
// Get returns a single element value from the store.
//
// If key is not set, the zero T value is returned.
func (s *Store[T]) Get(key string) T {
func (s *Store[K, T]) Get(key K) T {
s.mu.RLock()
defer s.mu.RUnlock()
@@ -99,7 +99,7 @@ func (s *Store[T]) Get(key string) T {
}
// GetOk is similar to Get but returns also a boolean indicating whether the key exists or not.
func (s *Store[T]) GetOk(key string) (T, bool) {
func (s *Store[K, T]) GetOk(key K) (T, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
@@ -109,11 +109,11 @@ func (s *Store[T]) GetOk(key string) (T, bool) {
}
// GetAll returns a shallow copy of the current store data.
func (s *Store[T]) GetAll() map[string]T {
func (s *Store[K, T]) GetAll() map[K]T {
s.mu.RLock()
defer s.mu.RUnlock()
var clone = make(map[string]T, len(s.data))
var clone = make(map[K]T, len(s.data))
for k, v := range s.data {
clone[k] = v
@@ -123,7 +123,7 @@ func (s *Store[T]) GetAll() map[string]T {
}
// Values returns a slice with all of the current store values.
func (s *Store[T]) Values() []T {
func (s *Store[K, T]) Values() []T {
s.mu.RLock()
defer s.mu.RUnlock()
@@ -137,12 +137,12 @@ func (s *Store[T]) Values() []T {
}
// Set sets (or overwrite if already exist) a new value for key.
func (s *Store[T]) Set(key string, value T) {
func (s *Store[K, T]) Set(key K, value T) {
s.mu.Lock()
defer s.mu.Unlock()
if s.data == nil {
s.data = make(map[string]T)
s.data = make(map[K]T)
}
s.data[key] = value
@@ -150,7 +150,7 @@ func (s *Store[T]) Set(key string, value T) {
// GetOrSet retrieves a single existing value for the provided key
// or stores a new one if it doesn't exist.
func (s *Store[T]) GetOrSet(key string, setFunc func() T) T {
func (s *Store[K, T]) GetOrSet(key K, setFunc func() T) T {
// lock only reads to minimize locks contention
s.mu.RLock()
v, ok := s.data[key]
@@ -160,7 +160,7 @@ func (s *Store[T]) GetOrSet(key string, setFunc func() T) T {
s.mu.Lock()
v = setFunc()
if s.data == nil {
s.data = make(map[string]T)
s.data = make(map[K]T)
}
s.data[key] = v
s.mu.Unlock()
@@ -174,12 +174,12 @@ func (s *Store[T]) GetOrSet(key string, setFunc func() T) T {
// This method is similar to Set() but **it will skip adding new elements**
// to the store if the store length has reached the specified limit.
// false is returned if maxAllowedElements limit is reached.
func (s *Store[T]) SetIfLessThanLimit(key string, value T, maxAllowedElements int) bool {
func (s *Store[K, T]) SetIfLessThanLimit(key K, value T, maxAllowedElements int) bool {
s.mu.Lock()
defer s.mu.Unlock()
if s.data == nil {
s.data = make(map[string]T)
s.data = make(map[K]T)
}
// check for existing item
@@ -200,8 +200,8 @@ func (s *Store[T]) SetIfLessThanLimit(key string, value T, maxAllowedElements in
// provided JSON data into the store.
//
// The store entries that match with the ones from the data will be overwritten with the new value.
func (s *Store[T]) UnmarshalJSON(data []byte) error {
raw := map[string]T{}
func (s *Store[K, T]) UnmarshalJSON(data []byte) error {
raw := map[K]T{}
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
@@ -210,7 +210,7 @@ func (s *Store[T]) UnmarshalJSON(data []byte) error {
defer s.mu.Unlock()
if s.data == nil {
s.data = make(map[string]T)
s.data = make(map[K]T)
}
for k, v := range raw {
@@ -222,6 +222,6 @@ func (s *Store[T]) UnmarshalJSON(data []byte) error {
// MarshalJSON implements [json.Marshaler] and export the current
// store data into valid JSON.
func (s *Store[T]) MarshalJSON() ([]byte, error) {
func (s *Store[K, T]) MarshalJSON() ([]byte, error) {
return json.Marshal(s.GetAll())
}
+5 -5
View File
@@ -227,7 +227,7 @@ func TestValues(t *testing.T) {
}
func TestSet(t *testing.T) {
s := store.Store[int]{}
s := store.Store[string, int]{}
data := map[string]int{"test1": 0, "test2": 1, "test3": 3}
@@ -281,7 +281,7 @@ func TestGetOrSet(t *testing.T) {
}
func TestSetIfLessThanLimit(t *testing.T) {
s := store.Store[int]{}
s := store.Store[string, int]{}
limit := 2
@@ -316,7 +316,7 @@ func TestSetIfLessThanLimit(t *testing.T) {
}
func TestUnmarshalJSON(t *testing.T) {
s := store.Store[string]{}
s := store.Store[string, string]{}
s.Set("b", "old") // should be overwritten
s.Set("c", "test3") // ensures that the old values are not removed
@@ -339,7 +339,7 @@ func TestUnmarshalJSON(t *testing.T) {
}
func TestMarshalJSON(t *testing.T) {
s := &store.Store[string]{}
s := &store.Store[string, string]{}
s.Set("a", "test1")
s.Set("b", "test2")
@@ -356,7 +356,7 @@ func TestMarshalJSON(t *testing.T) {
}
func TestShrink(t *testing.T) {
s := &store.Store[int]{}
s := &store.Store[string, int]{}
total := 1000
+2 -2
View File
@@ -9,13 +9,13 @@ import (
// Broker defines a struct for managing subscriptions clients.
type Broker struct {
store *store.Store[Client]
store *store.Store[string, Client]
}
// NewBroker initializes and returns a new Broker instance.
func NewBroker() *Broker {
return &Broker{
store: store.New[Client](nil),
store: store.New[string, Client](nil),
}
}
+2 -2
View File
@@ -37,7 +37,7 @@ import (
// Use the Registry.Load* methods to load templates into the registry.
func NewRegistry() *Registry {
return &Registry{
cache: store.New[*Renderer](nil),
cache: store.New[string, *Renderer](nil),
funcs: template.FuncMap{
"raw": func(str string) template.HTML {
return template.HTML(str)
@@ -50,7 +50,7 @@ func NewRegistry() *Registry {
//
// Use the Registry.Load* methods to load templates into the registry.
type Registry struct {
cache *store.Store[*Renderer]
cache *store.Store[string, *Renderer]
funcs template.FuncMap
}
+7 -7
View File
@@ -1,12 +1,12 @@
# all environments should start with 'PB_' prefix
PB_BACKEND_URL = "../"
PB_MFA_DOCS = "https://pocketbase.io/docs/authentication/#multi-factor-authentication"
PB_OAUTH2_EXAMPLE = "https://pocketbase.io/docs/authentication/#authenticate-with-oauth2"
PB_RULES_SYNTAX_DOCS = "https://pocketbase.io/docs/api-rules-and-filters/"
PB_FILE_UPLOAD_DOCS = "https://pocketbase.io/docs/files-handling/"
PB_PROTECTED_FILE_DOCS = "https://pocketbase.io/docs/files-handling/#protected-files"
PB_DOCS_URL = "https://pocketbase.io/docs/"
PB_MFA_DOCS = "https://pocketbase.io/docs/authentication#multi-factor-authentication"
PB_OAUTH2_EXAMPLE = "https://pocketbase.io/docs/authentication#authenticate-with-oauth2"
PB_RULES_SYNTAX_DOCS = "https://pocketbase.io/docs/api-rules-and-filters"
PB_FILE_UPLOAD_DOCS = "https://pocketbase.io/docs/files-handling"
PB_PROTECTED_FILE_DOCS = "https://pocketbase.io/docs/files-handling#protected-files"
PB_DOCS_URL = "https://pocketbase.io/docs"
PB_JS_SDK_URL = "https://github.com/pocketbase/js-sdk"
PB_DART_SDK_URL = "https://github.com/pocketbase/dart-sdk"
PB_RELEASES = "https://github.com/pocketbase/pocketbase/releases"
PB_VERSION = "v0.23.12"
PB_VERSION = "v0.24.4"
@@ -1,4 +1,4 @@
import{S as Be,i as Ce,s as Te,V as Le,X as J,h as c,z as w,j as k,c as ae,k as h,n as d,o as a,m as ne,H as Q,Y as $e,Z as Se,E as je,_ as qe,G as Ee,t as z,a as I,v as u,d as ie,p as oe,J as He,l as N,q as Re,W as Ue}from"./index-Bpzzn7x2.js";import{F as De}from"./FieldsQueryParam-CrrI0KIL.js";function we(n,s,l){const o=n.slice();return o[8]=s[l],o}function Me(n,s,l){const o=n.slice();return o[8]=s[l],o}function Ae(n,s){let l,o=s[8].code+"",p,b,i,f;function m(){return s[6](s[8])}return{key:n,first:null,c(){l=c("button"),p=w(o),b=k(),h(l,"class","tab-item"),N(l,"active",s[1]===s[8].code),this.first=l},m(v,y){d(v,l,y),a(l,p),a(l,b),i||(f=Re(l,"click",m),i=!0)},p(v,y){s=v,y&4&&o!==(o=s[8].code+"")&&Q(p,o),y&6&&N(l,"active",s[1]===s[8].code)},d(v){v&&u(l),i=!1,f()}}}function Pe(n,s){let l,o,p,b;return o=new Ue({props:{content:s[8].body}}),{key:n,first:null,c(){l=c("div"),ae(o.$$.fragment),p=k(),h(l,"class","tab-item"),N(l,"active",s[1]===s[8].code),this.first=l},m(i,f){d(i,l,f),ne(o,l,null),a(l,p),b=!0},p(i,f){s=i;const m={};f&4&&(m.content=s[8].body),o.$set(m),(!b||f&6)&&N(l,"active",s[1]===s[8].code)},i(i){b||(z(o.$$.fragment,i),b=!0)},o(i){I(o.$$.fragment,i),b=!1},d(i){i&&u(l),ie(o)}}}function Fe(n){var ke,ge;let s,l,o=n[0].name+"",p,b,i,f,m,v,y,g=n[0].name+"",O,ce,V,M,W,L,X,A,U,re,D,S,de,Y,F=n[0].name+"",Z,ue,K,j,x,P,ee,fe,te,T,le,q,se,B,E,$=[],me=new Map,pe,H,_=[],be=new Map,C;M=new Le({props:{js:`
import{S as Be,i as Ce,s as Te,V as Le,X as J,h as c,z as w,j as k,c as ae,k as h,n as d,o as a,m as ne,H as Q,Y as $e,Z as Se,E as je,_ as qe,G as Ee,t as z,a as I,v as u,d as ie,p as oe,J as He,l as N,q as Re,W as Ue}from"./index-0HOqdotm.js";import{F as De}from"./FieldsQueryParam-DT9Tnt7Y.js";function we(n,s,l){const o=n.slice();return o[8]=s[l],o}function Me(n,s,l){const o=n.slice();return o[8]=s[l],o}function Ae(n,s){let l,o=s[8].code+"",p,b,i,f;function m(){return s[6](s[8])}return{key:n,first:null,c(){l=c("button"),p=w(o),b=k(),h(l,"class","tab-item"),N(l,"active",s[1]===s[8].code),this.first=l},m(v,y){d(v,l,y),a(l,p),a(l,b),i||(f=Re(l,"click",m),i=!0)},p(v,y){s=v,y&4&&o!==(o=s[8].code+"")&&Q(p,o),y&6&&N(l,"active",s[1]===s[8].code)},d(v){v&&u(l),i=!1,f()}}}function Pe(n,s){let l,o,p,b;return o=new Ue({props:{content:s[8].body}}),{key:n,first:null,c(){l=c("div"),ae(o.$$.fragment),p=k(),h(l,"class","tab-item"),N(l,"active",s[1]===s[8].code),this.first=l},m(i,f){d(i,l,f),ne(o,l,null),a(l,p),b=!0},p(i,f){s=i;const m={};f&4&&(m.content=s[8].body),o.$set(m),(!b||f&6)&&N(l,"active",s[1]===s[8].code)},i(i){b||(z(o.$$.fragment,i),b=!0)},o(i){I(o.$$.fragment,i),b=!1},d(i){i&&u(l),ie(o)}}}function Fe(n){var ke,ge;let s,l,o=n[0].name+"",p,b,i,f,m,v,y,g=n[0].name+"",O,ce,V,M,W,L,X,A,U,re,D,S,de,Y,F=n[0].name+"",Z,ue,K,j,x,P,ee,fe,te,T,le,q,se,B,E,$=[],me=new Map,pe,H,_=[],be=new Map,C;M=new Le({props:{js:`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${n[3]}');
@@ -1,4 +1,4 @@
import{S as Ue,i as xe,s as Ke,V as Qe,W as Ne,X as K,h as s,z as k,j as p,c as Q,k as b,n as d,o,m as W,H as de,Y as Le,Z as We,E as Ge,_ as Ie,G as Xe,t as V,a as z,v as u,d as G,J as Oe,p as Ye,l as I,q as Ze}from"./index-Bpzzn7x2.js";import{F as et}from"./FieldsQueryParam-CrrI0KIL.js";function Ve(r,a,l){const n=r.slice();return n[5]=a[l],n}function ze(r,a,l){const n=r.slice();return n[5]=a[l],n}function je(r,a){let l,n=a[5].code+"",m,_,i,h;function g(){return a[4](a[5])}return{key:r,first:null,c(){l=s("button"),m=k(n),_=p(),b(l,"class","tab-item"),I(l,"active",a[1]===a[5].code),this.first=l},m(v,w){d(v,l,w),o(l,m),o(l,_),i||(h=Ze(l,"click",g),i=!0)},p(v,w){a=v,w&4&&n!==(n=a[5].code+"")&&de(m,n),w&6&&I(l,"active",a[1]===a[5].code)},d(v){v&&u(l),i=!1,h()}}}function Je(r,a){let l,n,m,_;return n=new Ne({props:{content:a[5].body}}),{key:r,first:null,c(){l=s("div"),Q(n.$$.fragment),m=p(),b(l,"class","tab-item"),I(l,"active",a[1]===a[5].code),this.first=l},m(i,h){d(i,l,h),W(n,l,null),o(l,m),_=!0},p(i,h){a=i;const g={};h&4&&(g.content=a[5].body),n.$set(g),(!_||h&6)&&I(l,"active",a[1]===a[5].code)},i(i){_||(V(n.$$.fragment,i),_=!0)},o(i){z(n.$$.fragment,i),_=!1},d(i){i&&u(l),G(n)}}}function tt(r){var De,Fe;let a,l,n=r[0].name+"",m,_,i,h,g,v,w,B,X,S,j,ue,J,M,pe,Y,N=r[0].name+"",Z,he,fe,U,ee,D,te,T,oe,be,F,C,ae,me,le,_e,f,ke,P,ge,ve,$e,se,ye,ne,Se,we,Te,re,Ce,Re,q,ie,H,ce,R,E,y=[],Pe=new Map,qe,L,$=[],Ae=new Map,A;v=new Qe({props:{js:`
import{S as Ue,i as xe,s as Ke,V as Qe,W as Ne,X as K,h as s,z as k,j as p,c as Q,k as b,n as d,o,m as W,H as de,Y as Le,Z as We,E as Ge,_ as Ie,G as Xe,t as V,a as z,v as u,d as G,J as Oe,p as Ye,l as I,q as Ze}from"./index-0HOqdotm.js";import{F as et}from"./FieldsQueryParam-DT9Tnt7Y.js";function Ve(r,a,l){const n=r.slice();return n[5]=a[l],n}function ze(r,a,l){const n=r.slice();return n[5]=a[l],n}function je(r,a){let l,n=a[5].code+"",m,_,i,h;function g(){return a[4](a[5])}return{key:r,first:null,c(){l=s("button"),m=k(n),_=p(),b(l,"class","tab-item"),I(l,"active",a[1]===a[5].code),this.first=l},m(v,w){d(v,l,w),o(l,m),o(l,_),i||(h=Ze(l,"click",g),i=!0)},p(v,w){a=v,w&4&&n!==(n=a[5].code+"")&&de(m,n),w&6&&I(l,"active",a[1]===a[5].code)},d(v){v&&u(l),i=!1,h()}}}function Je(r,a){let l,n,m,_;return n=new Ne({props:{content:a[5].body}}),{key:r,first:null,c(){l=s("div"),Q(n.$$.fragment),m=p(),b(l,"class","tab-item"),I(l,"active",a[1]===a[5].code),this.first=l},m(i,h){d(i,l,h),W(n,l,null),o(l,m),_=!0},p(i,h){a=i;const g={};h&4&&(g.content=a[5].body),n.$set(g),(!_||h&6)&&I(l,"active",a[1]===a[5].code)},i(i){_||(V(n.$$.fragment,i),_=!0)},o(i){z(n.$$.fragment,i),_=!1},d(i){i&&u(l),G(n)}}}function tt(r){var De,Fe;let a,l,n=r[0].name+"",m,_,i,h,g,v,w,B,X,S,j,ue,J,M,pe,Y,N=r[0].name+"",Z,he,fe,U,ee,D,te,T,oe,be,F,C,ae,me,le,_e,f,ke,P,ge,ve,$e,se,ye,ne,Se,we,Te,re,Ce,Re,q,ie,H,ce,R,E,y=[],Pe=new Map,qe,L,$=[],Ae=new Map,A;v=new Qe({props:{js:`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${r[3]}');
@@ -1,4 +1,4 @@
import{S as Ee,i as Je,s as xe,V as ze,W as Ve,X as Q,h as o,z as _,j as h,c as G,k as p,n as r,o as a,m as I,H as pe,Y as Ue,Z as Ne,E as Qe,_ as Ge,G as Ie,t as V,a as E,v as c,d as K,J as Be,p as Ke,l as X,q as Xe}from"./index-Bpzzn7x2.js";import{F as Ye}from"./FieldsQueryParam-CrrI0KIL.js";function Fe(s,l,n){const i=s.slice();return i[5]=l[n],i}function He(s,l,n){const i=s.slice();return i[5]=l[n],i}function Le(s,l){let n,i=l[5].code+"",f,g,d,b;function k(){return l[4](l[5])}return{key:s,first:null,c(){n=o("button"),f=_(i),g=h(),p(n,"class","tab-item"),X(n,"active",l[1]===l[5].code),this.first=n},m(v,O){r(v,n,O),a(n,f),a(n,g),d||(b=Xe(n,"click",k),d=!0)},p(v,O){l=v,O&4&&i!==(i=l[5].code+"")&&pe(f,i),O&6&&X(n,"active",l[1]===l[5].code)},d(v){v&&c(n),d=!1,b()}}}function je(s,l){let n,i,f,g;return i=new Ve({props:{content:l[5].body}}),{key:s,first:null,c(){n=o("div"),G(i.$$.fragment),f=h(),p(n,"class","tab-item"),X(n,"active",l[1]===l[5].code),this.first=n},m(d,b){r(d,n,b),I(i,n,null),a(n,f),g=!0},p(d,b){l=d;const k={};b&4&&(k.content=l[5].body),i.$set(k),(!g||b&6)&&X(n,"active",l[1]===l[5].code)},i(d){g||(V(i.$$.fragment,d),g=!0)},o(d){E(i.$$.fragment,d),g=!1},d(d){d&&c(n),K(i)}}}function Ze(s){let l,n,i=s[0].name+"",f,g,d,b,k,v,O,D,Y,A,J,be,x,P,me,Z,z=s[0].name+"",ee,fe,te,M,ae,W,le,U,ne,y,oe,ge,B,S,se,_e,ie,ke,m,ve,C,we,$e,Oe,re,Ae,ce,ye,Se,Te,de,Ce,qe,q,ue,F,he,T,H,$=[],Re=new Map,De,L,w=[],Pe=new Map,R;v=new ze({props:{js:`
import{S as Ee,i as Je,s as xe,V as ze,W as Ve,X as Q,h as o,z as _,j as h,c as G,k as p,n as r,o as a,m as I,H as pe,Y as Ue,Z as Ne,E as Qe,_ as Ge,G as Ie,t as V,a as E,v as c,d as K,J as Be,p as Ke,l as X,q as Xe}from"./index-0HOqdotm.js";import{F as Ye}from"./FieldsQueryParam-DT9Tnt7Y.js";function Fe(s,l,n){const i=s.slice();return i[5]=l[n],i}function He(s,l,n){const i=s.slice();return i[5]=l[n],i}function Le(s,l){let n,i=l[5].code+"",f,g,d,b;function k(){return l[4](l[5])}return{key:s,first:null,c(){n=o("button"),f=_(i),g=h(),p(n,"class","tab-item"),X(n,"active",l[1]===l[5].code),this.first=n},m(v,O){r(v,n,O),a(n,f),a(n,g),d||(b=Xe(n,"click",k),d=!0)},p(v,O){l=v,O&4&&i!==(i=l[5].code+"")&&pe(f,i),O&6&&X(n,"active",l[1]===l[5].code)},d(v){v&&c(n),d=!1,b()}}}function je(s,l){let n,i,f,g;return i=new Ve({props:{content:l[5].body}}),{key:s,first:null,c(){n=o("div"),G(i.$$.fragment),f=h(),p(n,"class","tab-item"),X(n,"active",l[1]===l[5].code),this.first=n},m(d,b){r(d,n,b),I(i,n,null),a(n,f),g=!0},p(d,b){l=d;const k={};b&4&&(k.content=l[5].body),i.$set(k),(!g||b&6)&&X(n,"active",l[1]===l[5].code)},i(d){g||(V(i.$$.fragment,d),g=!0)},o(d){E(i.$$.fragment,d),g=!1},d(d){d&&c(n),K(i)}}}function Ze(s){let l,n,i=s[0].name+"",f,g,d,b,k,v,O,D,Y,A,J,be,x,P,me,Z,z=s[0].name+"",ee,fe,te,M,ae,W,le,U,ne,y,oe,ge,B,S,se,_e,ie,ke,m,ve,C,we,$e,Oe,re,Ae,ce,ye,Se,Te,de,Ce,qe,q,ue,F,he,T,H,$=[],Re=new Map,De,L,w=[],Pe=new Map,R;v=new ze({props:{js:`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${s[3]}');
@@ -46,7 +46,7 @@ import{S as Ee,i as Je,s as xe,V as ze,W as Ve,X as Q,h as o,z as _,j as h,c as
// "logout"
pb.authStore.clear();
`}}),C=new Ve({props:{content:"?expand=relField1,relField2.subRelField"}}),q=new Ye({props:{prefix:"record."}});let N=Q(s[2]);const Me=e=>e[5].code;for(let e=0;e<N.length;e+=1){let t=He(s,N,e),u=Me(t);Re.set(u,$[e]=Le(u,t))}let j=Q(s[2]);const We=e=>e[5].code;for(let e=0;e<j.length;e+=1){let t=Fe(s,j,e),u=We(t);Pe.set(u,w[e]=je(u,t))}return{c(){l=o("h3"),n=_("Auth with OAuth2 ("),f=_(i),g=_(")"),d=h(),b=o("div"),b.innerHTML=`<p>Authenticate with an OAuth2 provider and returns a new auth token and record data.</p> <p>For more details please check the
<a href="https://pocketbase.io/docs/authentication/#authenticate-with-oauth2" target="_blank" rel="noopener noreferrer">OAuth2 integration documentation
<a href="https://pocketbase.io/docs/authentication#authenticate-with-oauth2" target="_blank" rel="noopener noreferrer">OAuth2 integration documentation
</a>.</p>`,k=h(),G(v.$$.fragment),O=h(),D=o("h6"),D.textContent="API details",Y=h(),A=o("div"),J=o("strong"),J.textContent="POST",be=h(),x=o("div"),P=o("p"),me=_("/api/collections/"),Z=o("strong"),ee=_(z),fe=_("/auth-with-oauth2"),te=h(),M=o("div"),M.textContent="Body Parameters",ae=h(),W=o("table"),W.innerHTML=`<thead><tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr></thead> <tbody><tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>provider</span></div></td> <td><span class="label">String</span></td> <td>The name of the OAuth2 client provider (eg. &quot;google&quot;).</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>code</span></div></td> <td><span class="label">String</span></td> <td>The authorization code returned from the initial request.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>codeVerifier</span></div></td> <td><span class="label">String</span></td> <td>The code verifier sent with the initial request as part of the code_challenge.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>redirectURL</span></div></td> <td><span class="label">String</span></td> <td>The redirect url sent with the initial request.</td></tr> <tr><td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>createData</span></div></td> <td><span class="label">Object</span></td> <td><p>Optional data that will be used when creating the auth record on OAuth2 sign-up.</p> <p>The created auth record must comply with the same requirements and validations in the
regular <strong>create</strong> action.
<br/> <em>The data can only be in <code>json</code>, aka. <code>multipart/form-data</code> and files
-130
View File
@@ -1,130 +0,0 @@
import{S as ee,i as te,s as le,X as U,h as p,j as S,z as H,k as g,n as b,o as h,H as X,Y as Z,Z as ge,E as Q,_ as ke,G as x,t as J,a as L,v as _,J as oe,l as Y,q as ae,W as we,c as F,m as G,d as K,V as $e,$ as se,p as Te,a0 as ne}from"./index-Bpzzn7x2.js";function ie(s,t,e){const a=s.slice();return a[4]=t[e],a}function ce(s,t,e){const a=s.slice();return a[4]=t[e],a}function re(s,t){let e,a=t[4].code+"",d,c,r,n;function u(){return t[3](t[4])}return{key:s,first:null,c(){e=p("button"),d=H(a),c=S(),g(e,"class","tab-item"),Y(e,"active",t[1]===t[4].code),this.first=e},m(m,y){b(m,e,y),h(e,d),h(e,c),r||(n=ae(e,"click",u),r=!0)},p(m,y){t=m,y&4&&a!==(a=t[4].code+"")&&X(d,a),y&6&&Y(e,"active",t[1]===t[4].code)},d(m){m&&_(e),r=!1,n()}}}function de(s,t){let e,a,d,c;return a=new we({props:{content:t[4].body}}),{key:s,first:null,c(){e=p("div"),F(a.$$.fragment),d=S(),g(e,"class","tab-item"),Y(e,"active",t[1]===t[4].code),this.first=e},m(r,n){b(r,e,n),G(a,e,null),h(e,d),c=!0},p(r,n){t=r;const u={};n&4&&(u.content=t[4].body),a.$set(u),(!c||n&6)&&Y(e,"active",t[1]===t[4].code)},i(r){c||(J(a.$$.fragment,r),c=!0)},o(r){L(a.$$.fragment,r),c=!1},d(r){r&&_(e),K(a)}}}function Pe(s){let t,e,a,d,c,r,n,u=s[0].name+"",m,y,M,C,B,A,V,R,W,q,P,w=[],$=new Map,E,D,k=[],N=new Map,I,i=U(s[2]);const v=l=>l[4].code;for(let l=0;l<i.length;l+=1){let o=ce(s,i,l),f=v(o);$.set(f,w[l]=re(f,o))}let O=U(s[2]);const j=l=>l[4].code;for(let l=0;l<O.length;l+=1){let o=ie(s,O,l),f=j(o);N.set(f,k[l]=de(f,o))}return{c(){t=p("div"),e=p("strong"),e.textContent="POST",a=S(),d=p("div"),c=p("p"),r=H("/api/collections/"),n=p("strong"),m=H(u),y=H("/auth-with-otp"),M=S(),C=p("div"),C.textContent="Body Parameters",B=S(),A=p("table"),A.innerHTML='<thead><tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr></thead> <tbody><tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>otpId</span></div></td> <td><span class="label">String</span></td> <td>The id of the OTP request.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>password</span></div></td> <td><span class="label">String</span></td> <td>The one-time password.</td></tr></tbody>',V=S(),R=p("div"),R.textContent="Responses",W=S(),q=p("div"),P=p("div");for(let l=0;l<w.length;l+=1)w[l].c();E=S(),D=p("div");for(let l=0;l<k.length;l+=1)k[l].c();g(e,"class","label label-primary"),g(d,"class","content"),g(t,"class","alert alert-success"),g(C,"class","section-title"),g(A,"class","table-compact table-border m-b-base"),g(R,"class","section-title"),g(P,"class","tabs-header compact combined left"),g(D,"class","tabs-content"),g(q,"class","tabs")},m(l,o){b(l,t,o),h(t,e),h(t,a),h(t,d),h(d,c),h(c,r),h(c,n),h(n,m),h(c,y),b(l,M,o),b(l,C,o),b(l,B,o),b(l,A,o),b(l,V,o),b(l,R,o),b(l,W,o),b(l,q,o),h(q,P);for(let f=0;f<w.length;f+=1)w[f]&&w[f].m(P,null);h(q,E),h(q,D);for(let f=0;f<k.length;f+=1)k[f]&&k[f].m(D,null);I=!0},p(l,[o]){(!I||o&1)&&u!==(u=l[0].name+"")&&X(m,u),o&6&&(i=U(l[2]),w=Z(w,o,v,1,l,i,$,P,ge,re,null,ce)),o&6&&(O=U(l[2]),Q(),k=Z(k,o,j,1,l,O,N,D,ke,de,null,ie),x())},i(l){if(!I){for(let o=0;o<O.length;o+=1)J(k[o]);I=!0}},o(l){for(let o=0;o<k.length;o+=1)L(k[o]);I=!1},d(l){l&&(_(t),_(M),_(C),_(B),_(A),_(V),_(R),_(W),_(q));for(let o=0;o<w.length;o+=1)w[o].d();for(let o=0;o<k.length;o+=1)k[o].d()}}}function Oe(s,t,e){let{collection:a}=t,d=200,c=[];const r=n=>e(1,d=n.code);return s.$$set=n=>{"collection"in n&&e(0,a=n.collection)},s.$$.update=()=>{s.$$.dirty&1&&e(2,c=[{code:200,body:JSON.stringify({token:"JWT_TOKEN",record:oe.dummyCollectionRecord(a)},null,2)},{code:400,body:`
{
"code": 400,
"message": "Failed to authenticate.",
"data": {
"otpId": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`}])},[a,d,c,r]}class Se extends ee{constructor(t){super(),te(this,t,Oe,Pe,le,{collection:0})}}function ue(s,t,e){const a=s.slice();return a[4]=t[e],a}function he(s,t,e){const a=s.slice();return a[4]=t[e],a}function pe(s,t){let e,a=t[4].code+"",d,c,r,n;function u(){return t[3](t[4])}return{key:s,first:null,c(){e=p("button"),d=H(a),c=S(),g(e,"class","tab-item"),Y(e,"active",t[1]===t[4].code),this.first=e},m(m,y){b(m,e,y),h(e,d),h(e,c),r||(n=ae(e,"click",u),r=!0)},p(m,y){t=m,y&4&&a!==(a=t[4].code+"")&&X(d,a),y&6&&Y(e,"active",t[1]===t[4].code)},d(m){m&&_(e),r=!1,n()}}}function fe(s,t){let e,a,d,c;return a=new we({props:{content:t[4].body}}),{key:s,first:null,c(){e=p("div"),F(a.$$.fragment),d=S(),g(e,"class","tab-item"),Y(e,"active",t[1]===t[4].code),this.first=e},m(r,n){b(r,e,n),G(a,e,null),h(e,d),c=!0},p(r,n){t=r;const u={};n&4&&(u.content=t[4].body),a.$set(u),(!c||n&6)&&Y(e,"active",t[1]===t[4].code)},i(r){c||(J(a.$$.fragment,r),c=!0)},o(r){L(a.$$.fragment,r),c=!1},d(r){r&&_(e),K(a)}}}function qe(s){let t,e,a,d,c,r,n,u=s[0].name+"",m,y,M,C,B,A,V,R,W,q,P,w=[],$=new Map,E,D,k=[],N=new Map,I,i=U(s[2]);const v=l=>l[4].code;for(let l=0;l<i.length;l+=1){let o=he(s,i,l),f=v(o);$.set(f,w[l]=pe(f,o))}let O=U(s[2]);const j=l=>l[4].code;for(let l=0;l<O.length;l+=1){let o=ue(s,O,l),f=j(o);N.set(f,k[l]=fe(f,o))}return{c(){t=p("div"),e=p("strong"),e.textContent="POST",a=S(),d=p("div"),c=p("p"),r=H("/api/collections/"),n=p("strong"),m=H(u),y=H("/request-otp"),M=S(),C=p("div"),C.textContent="Body Parameters",B=S(),A=p("table"),A.innerHTML='<thead><tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr></thead> <tbody><tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>email</span></div></td> <td><span class="label">String</span></td> <td>The auth record email address to send the OTP request (if exists).</td></tr></tbody>',V=S(),R=p("div"),R.textContent="Responses",W=S(),q=p("div"),P=p("div");for(let l=0;l<w.length;l+=1)w[l].c();E=S(),D=p("div");for(let l=0;l<k.length;l+=1)k[l].c();g(e,"class","label label-primary"),g(d,"class","content"),g(t,"class","alert alert-success"),g(C,"class","section-title"),g(A,"class","table-compact table-border m-b-base"),g(R,"class","section-title"),g(P,"class","tabs-header compact combined left"),g(D,"class","tabs-content"),g(q,"class","tabs")},m(l,o){b(l,t,o),h(t,e),h(t,a),h(t,d),h(d,c),h(c,r),h(c,n),h(n,m),h(c,y),b(l,M,o),b(l,C,o),b(l,B,o),b(l,A,o),b(l,V,o),b(l,R,o),b(l,W,o),b(l,q,o),h(q,P);for(let f=0;f<w.length;f+=1)w[f]&&w[f].m(P,null);h(q,E),h(q,D);for(let f=0;f<k.length;f+=1)k[f]&&k[f].m(D,null);I=!0},p(l,[o]){(!I||o&1)&&u!==(u=l[0].name+"")&&X(m,u),o&6&&(i=U(l[2]),w=Z(w,o,v,1,l,i,$,P,ge,pe,null,he)),o&6&&(O=U(l[2]),Q(),k=Z(k,o,j,1,l,O,N,D,ke,fe,null,ue),x())},i(l){if(!I){for(let o=0;o<O.length;o+=1)J(k[o]);I=!0}},o(l){for(let o=0;o<k.length;o+=1)L(k[o]);I=!1},d(l){l&&(_(t),_(M),_(C),_(B),_(A),_(V),_(R),_(W),_(q));for(let o=0;o<w.length;o+=1)w[o].d();for(let o=0;o<k.length;o+=1)k[o].d()}}}function ye(s,t,e){let{collection:a}=t,d=200,c=[];const r=n=>e(1,d=n.code);return s.$$set=n=>{"collection"in n&&e(0,a=n.collection)},e(2,c=[{code:200,body:JSON.stringify({otpId:oe.randomString(15)},null,2)},{code:400,body:`
{
"code": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"email": {
"code": "validation_is_email",
"message": "Must be a valid email address."
}
}
}
`},{code:429,body:`
{
"code": 429,
"message": "You've send too many OTP requests, please try again later.",
"data": {}
}
`}]),[a,d,c,r]}class Ae extends ee{constructor(t){super(),te(this,t,ye,qe,le,{collection:0})}}function me(s,t,e){const a=s.slice();return a[5]=t[e],a[7]=e,a}function be(s,t,e){const a=s.slice();return a[5]=t[e],a[7]=e,a}function _e(s){let t,e,a,d,c;function r(){return s[4](s[7])}return{c(){t=p("button"),e=p("div"),e.textContent=`${s[5].title}`,a=S(),g(e,"class","txt"),g(t,"class","tab-item"),Y(t,"active",s[1]==s[7])},m(n,u){b(n,t,u),h(t,e),h(t,a),d||(c=ae(t,"click",r),d=!0)},p(n,u){s=n,u&2&&Y(t,"active",s[1]==s[7])},d(n){n&&_(t),d=!1,c()}}}function ve(s){let t,e,a,d;var c=s[5].component;function r(n,u){return{props:{collection:n[0]}}}return c&&(e=ne(c,r(s))),{c(){t=p("div"),e&&F(e.$$.fragment),a=S(),g(t,"class","tab-item"),Y(t,"active",s[1]==s[7])},m(n,u){b(n,t,u),e&&G(e,t,null),h(t,a),d=!0},p(n,u){if(c!==(c=n[5].component)){if(e){Q();const m=e;L(m.$$.fragment,1,0,()=>{K(m,1)}),x()}c?(e=ne(c,r(n)),F(e.$$.fragment),J(e.$$.fragment,1),G(e,t,a)):e=null}else if(c){const m={};u&1&&(m.collection=n[0]),e.$set(m)}(!d||u&2)&&Y(t,"active",n[1]==n[7])},i(n){d||(e&&J(e.$$.fragment,n),d=!0)},o(n){e&&L(e.$$.fragment,n),d=!1},d(n){n&&_(t),e&&K(e)}}}function Re(s){var D,k,N,I;let t,e,a=s[0].name+"",d,c,r,n,u,m,y,M,C,B,A,V,R,W;m=new $e({props:{js:`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${s[2]}');
...
// send OTP email to the provided auth record
const req = await pb.collection('${(D=s[0])==null?void 0:D.name}').requestOTP('test@example.com');
// ... show a screen/popup to enter the password from the email ...
// authenticate with the requested OTP id and the email password
const authData = await pb.collection('${(k=s[0])==null?void 0:k.name}').authWithOTP(
req.otpId,
"YOUR_OTP",
);
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.record.id);
// "logout"
pb.authStore.clear();
`,dart:`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${s[2]}');
...
// send OTP email to the provided auth record
final req = await pb.collection('${(N=s[0])==null?void 0:N.name}').requestOTP('test@example.com');
// ... show a screen/popup to enter the password from the email ...
// authenticate with the requested OTP id and the email password
final authData = await pb.collection('${(I=s[0])==null?void 0:I.name}').authWithOTP(
req.otpId,
"YOUR_OTP",
);
// after the above you can also access the auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.record.id);
// "logout"
pb.authStore.clear();
`}});let q=U(s[3]),P=[];for(let i=0;i<q.length;i+=1)P[i]=_e(be(s,q,i));let w=U(s[3]),$=[];for(let i=0;i<w.length;i+=1)$[i]=ve(me(s,w,i));const E=i=>L($[i],1,1,()=>{$[i]=null});return{c(){t=p("h3"),e=H("Auth with OTP ("),d=H(a),c=H(")"),r=S(),n=p("div"),n.innerHTML=`<p>Authenticate with an one-time password (OTP).</p> <p>Note that when requesting an OTP we return an <code>otpId</code> even if a user with the provided email
doesn&#39;t exist as a very basic enumeration protection.</p>`,u=S(),F(m.$$.fragment),y=S(),M=p("h6"),M.textContent="API details",C=S(),B=p("div"),A=p("div");for(let i=0;i<P.length;i+=1)P[i].c();V=S(),R=p("div");for(let i=0;i<$.length;i+=1)$[i].c();g(t,"class","m-b-sm"),g(n,"class","content txt-lg m-b-sm"),g(M,"class","m-b-xs"),g(A,"class","tabs-header compact"),g(R,"class","tabs-content"),g(B,"class","tabs")},m(i,v){b(i,t,v),h(t,e),h(t,d),h(t,c),b(i,r,v),b(i,n,v),b(i,u,v),G(m,i,v),b(i,y,v),b(i,M,v),b(i,C,v),b(i,B,v),h(B,A);for(let O=0;O<P.length;O+=1)P[O]&&P[O].m(A,null);h(B,V),h(B,R);for(let O=0;O<$.length;O+=1)$[O]&&$[O].m(R,null);W=!0},p(i,[v]){var j,l,o,f;(!W||v&1)&&a!==(a=i[0].name+"")&&X(d,a);const O={};if(v&5&&(O.js=`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${i[2]}');
...
// send OTP email to the provided auth record
const req = await pb.collection('${(j=i[0])==null?void 0:j.name}').requestOTP('test@example.com');
// ... show a screen/popup to enter the password from the email ...
// authenticate with the requested OTP id and the email password
const authData = await pb.collection('${(l=i[0])==null?void 0:l.name}').authWithOTP(
req.otpId,
"YOUR_OTP",
);
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.record.id);
// "logout"
pb.authStore.clear();
`),v&5&&(O.dart=`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${i[2]}');
...
// send OTP email to the provided auth record
final req = await pb.collection('${(o=i[0])==null?void 0:o.name}').requestOTP('test@example.com');
// ... show a screen/popup to enter the password from the email ...
// authenticate with the requested OTP id and the email password
final authData = await pb.collection('${(f=i[0])==null?void 0:f.name}').authWithOTP(
req.otpId,
"YOUR_OTP",
);
// after the above you can also access the auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.record.id);
// "logout"
pb.authStore.clear();
`),m.$set(O),v&10){q=U(i[3]);let T;for(T=0;T<q.length;T+=1){const z=be(i,q,T);P[T]?P[T].p(z,v):(P[T]=_e(z),P[T].c(),P[T].m(A,null))}for(;T<P.length;T+=1)P[T].d(1);P.length=q.length}if(v&11){w=U(i[3]);let T;for(T=0;T<w.length;T+=1){const z=me(i,w,T);$[T]?($[T].p(z,v),J($[T],1)):($[T]=ve(z),$[T].c(),J($[T],1),$[T].m(R,null))}for(Q(),T=w.length;T<$.length;T+=1)E(T);x()}},i(i){if(!W){J(m.$$.fragment,i);for(let v=0;v<w.length;v+=1)J($[v]);W=!0}},o(i){L(m.$$.fragment,i),$=$.filter(Boolean);for(let v=0;v<$.length;v+=1)L($[v]);W=!1},d(i){i&&(_(t),_(r),_(n),_(u),_(y),_(M),_(C),_(B)),K(m,i),se(P,i),se($,i)}}}function Ce(s,t,e){let a,{collection:d}=t;const c=[{title:"OTP Request",component:Ae},{title:"OTP Auth",component:Se}];let r=0;const n=u=>e(1,r=u);return s.$$set=u=>{"collection"in u&&e(0,d=u.collection)},e(2,a=oe.getApiExampleUrl(Te.baseURL)),[d,r,a,c,n]}class De extends ee{constructor(t){super(),te(this,t,Ce,Re,le,{collection:0})}}export{De as default};
+136
View File
@@ -0,0 +1,136 @@
import{S as be,i as _e,s as ve,W as ge,X as V,h as d,j as T,z as R,c as x,k as g,n as b,o as s,m as ee,H as ce,Y as de,Z as je,E as ue,_ as Qe,G as he,t as E,a as j,v as _,d as te,J as ke,l as J,q as $e,V as ze,$ as Me,p as Ge,a0 as Be}from"./index-0HOqdotm.js";import{F as Ke}from"./FieldsQueryParam-DT9Tnt7Y.js";function De(a,t,e){const l=a.slice();return l[4]=t[e],l}function Ie(a,t,e){const l=a.slice();return l[4]=t[e],l}function We(a,t){let e,l=t[4].code+"",h,i,c,n;function m(){return t[3](t[4])}return{key:a,first:null,c(){e=d("button"),h=R(l),i=T(),g(e,"class","tab-item"),J(e,"active",t[1]===t[4].code),this.first=e},m(v,C){b(v,e,C),s(e,h),s(e,i),c||(n=$e(e,"click",m),c=!0)},p(v,C){t=v,C&4&&l!==(l=t[4].code+"")&&ce(h,l),C&6&&J(e,"active",t[1]===t[4].code)},d(v){v&&_(e),c=!1,n()}}}function Fe(a,t){let e,l,h,i;return l=new ge({props:{content:t[4].body}}),{key:a,first:null,c(){e=d("div"),x(l.$$.fragment),h=T(),g(e,"class","tab-item"),J(e,"active",t[1]===t[4].code),this.first=e},m(c,n){b(c,e,n),ee(l,e,null),s(e,h),i=!0},p(c,n){t=c;const m={};n&4&&(m.content=t[4].body),l.$set(m),(!i||n&6)&&J(e,"active",t[1]===t[4].code)},i(c){i||(E(l.$$.fragment,c),i=!0)},o(c){j(l.$$.fragment,c),i=!1},d(c){c&&_(e),te(l)}}}function Xe(a){let t,e,l,h,i,c,n,m=a[0].name+"",v,C,F,D,I,M,Q,B,H,y,O,q,k,L,Y,A,G,N,o,$,P,X,u,p,S,w,K,we,Te,Pe,pe,Oe,ye,le,fe,oe,me,Z,ae,z=[],Se=new Map,qe,ne,U=[],Ce=new Map,se;P=new ge({props:{content:"?expand=relField1,relField2.subRelField"}}),le=new Ke({props:{prefix:"record."}});let re=V(a[2]);const Ae=r=>r[4].code;for(let r=0;r<re.length;r+=1){let f=Ie(a,re,r),W=Ae(f);Se.set(W,z[r]=We(W,f))}let ie=V(a[2]);const Re=r=>r[4].code;for(let r=0;r<ie.length;r+=1){let f=De(a,ie,r),W=Re(f);Ce.set(W,U[r]=Fe(W,f))}return{c(){t=d("div"),e=d("strong"),e.textContent="POST",l=T(),h=d("div"),i=d("p"),c=R("/api/collections/"),n=d("strong"),v=R(m),C=R("/auth-with-otp"),F=T(),D=d("div"),D.textContent="Body Parameters",I=T(),M=d("table"),M.innerHTML='<thead><tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr></thead> <tbody><tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>otpId</span></div></td> <td><span class="label">String</span></td> <td>The id of the OTP request.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>password</span></div></td> <td><span class="label">String</span></td> <td>The one-time password.</td></tr></tbody>',Q=T(),B=d("div"),B.textContent="Query parameters",H=T(),y=d("table"),O=d("thead"),O.innerHTML='<tr><th>Param</th> <th>Type</th> <th width="60%">Description</th></tr>',q=T(),k=d("tbody"),L=d("tr"),Y=d("td"),Y.textContent="expand",A=T(),G=d("td"),G.innerHTML='<span class="label">String</span>',N=T(),o=d("td"),$=R(`Auto expand record relations. Ex.:
`),x(P.$$.fragment),X=R(`
Supports up to 6-levels depth nested relations expansion. `),u=d("br"),p=R(`
The expanded relations will be appended to the record under the
`),S=d("code"),S.textContent="expand",w=R(" property (eg. "),K=d("code"),K.textContent='"expand": {"relField1": {...}, ...}',we=R(`).
`),Te=d("br"),Pe=R(`
Only the relations to which the request user has permissions to `),pe=d("strong"),pe.textContent="view",Oe=R(" will be expanded."),ye=T(),x(le.$$.fragment),fe=T(),oe=d("div"),oe.textContent="Responses",me=T(),Z=d("div"),ae=d("div");for(let r=0;r<z.length;r+=1)z[r].c();qe=T(),ne=d("div");for(let r=0;r<U.length;r+=1)U[r].c();g(e,"class","label label-primary"),g(h,"class","content"),g(t,"class","alert alert-success"),g(D,"class","section-title"),g(M,"class","table-compact table-border m-b-base"),g(B,"class","section-title"),g(y,"class","table-compact table-border m-b-base"),g(oe,"class","section-title"),g(ae,"class","tabs-header compact combined left"),g(ne,"class","tabs-content"),g(Z,"class","tabs")},m(r,f){b(r,t,f),s(t,e),s(t,l),s(t,h),s(h,i),s(i,c),s(i,n),s(n,v),s(i,C),b(r,F,f),b(r,D,f),b(r,I,f),b(r,M,f),b(r,Q,f),b(r,B,f),b(r,H,f),b(r,y,f),s(y,O),s(y,q),s(y,k),s(k,L),s(L,Y),s(L,A),s(L,G),s(L,N),s(L,o),s(o,$),ee(P,o,null),s(o,X),s(o,u),s(o,p),s(o,S),s(o,w),s(o,K),s(o,we),s(o,Te),s(o,Pe),s(o,pe),s(o,Oe),s(k,ye),ee(le,k,null),b(r,fe,f),b(r,oe,f),b(r,me,f),b(r,Z,f),s(Z,ae);for(let W=0;W<z.length;W+=1)z[W]&&z[W].m(ae,null);s(Z,qe),s(Z,ne);for(let W=0;W<U.length;W+=1)U[W]&&U[W].m(ne,null);se=!0},p(r,[f]){(!se||f&1)&&m!==(m=r[0].name+"")&&ce(v,m),f&6&&(re=V(r[2]),z=de(z,f,Ae,1,r,re,Se,ae,je,We,null,Ie)),f&6&&(ie=V(r[2]),ue(),U=de(U,f,Re,1,r,ie,Ce,ne,Qe,Fe,null,De),he())},i(r){if(!se){E(P.$$.fragment,r),E(le.$$.fragment,r);for(let f=0;f<ie.length;f+=1)E(U[f]);se=!0}},o(r){j(P.$$.fragment,r),j(le.$$.fragment,r);for(let f=0;f<U.length;f+=1)j(U[f]);se=!1},d(r){r&&(_(t),_(F),_(D),_(I),_(M),_(Q),_(B),_(H),_(y),_(fe),_(oe),_(me),_(Z)),te(P),te(le);for(let f=0;f<z.length;f+=1)z[f].d();for(let f=0;f<U.length;f+=1)U[f].d()}}}function Ze(a,t,e){let{collection:l}=t,h=200,i=[];const c=n=>e(1,h=n.code);return a.$$set=n=>{"collection"in n&&e(0,l=n.collection)},a.$$.update=()=>{a.$$.dirty&1&&e(2,i=[{code:200,body:JSON.stringify({token:"JWT_TOKEN",record:ke.dummyCollectionRecord(l)},null,2)},{code:400,body:`
{
"code": 400,
"message": "Failed to authenticate.",
"data": {
"otpId": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`}])},[l,h,i,c]}class xe extends be{constructor(t){super(),_e(this,t,Ze,Xe,ve,{collection:0})}}function He(a,t,e){const l=a.slice();return l[4]=t[e],l}function Ue(a,t,e){const l=a.slice();return l[4]=t[e],l}function Le(a,t){let e,l=t[4].code+"",h,i,c,n;function m(){return t[3](t[4])}return{key:a,first:null,c(){e=d("button"),h=R(l),i=T(),g(e,"class","tab-item"),J(e,"active",t[1]===t[4].code),this.first=e},m(v,C){b(v,e,C),s(e,h),s(e,i),c||(n=$e(e,"click",m),c=!0)},p(v,C){t=v,C&4&&l!==(l=t[4].code+"")&&ce(h,l),C&6&&J(e,"active",t[1]===t[4].code)},d(v){v&&_(e),c=!1,n()}}}function Ye(a,t){let e,l,h,i;return l=new ge({props:{content:t[4].body}}),{key:a,first:null,c(){e=d("div"),x(l.$$.fragment),h=T(),g(e,"class","tab-item"),J(e,"active",t[1]===t[4].code),this.first=e},m(c,n){b(c,e,n),ee(l,e,null),s(e,h),i=!0},p(c,n){t=c;const m={};n&4&&(m.content=t[4].body),l.$set(m),(!i||n&6)&&J(e,"active",t[1]===t[4].code)},i(c){i||(E(l.$$.fragment,c),i=!0)},o(c){j(l.$$.fragment,c),i=!1},d(c){c&&_(e),te(l)}}}function et(a){let t,e,l,h,i,c,n,m=a[0].name+"",v,C,F,D,I,M,Q,B,H,y,O,q=[],k=new Map,L,Y,A=[],G=new Map,N,o=V(a[2]);const $=u=>u[4].code;for(let u=0;u<o.length;u+=1){let p=Ue(a,o,u),S=$(p);k.set(S,q[u]=Le(S,p))}let P=V(a[2]);const X=u=>u[4].code;for(let u=0;u<P.length;u+=1){let p=He(a,P,u),S=X(p);G.set(S,A[u]=Ye(S,p))}return{c(){t=d("div"),e=d("strong"),e.textContent="POST",l=T(),h=d("div"),i=d("p"),c=R("/api/collections/"),n=d("strong"),v=R(m),C=R("/request-otp"),F=T(),D=d("div"),D.textContent="Body Parameters",I=T(),M=d("table"),M.innerHTML='<thead><tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr></thead> <tbody><tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>email</span></div></td> <td><span class="label">String</span></td> <td>The auth record email address to send the OTP request (if exists).</td></tr></tbody>',Q=T(),B=d("div"),B.textContent="Responses",H=T(),y=d("div"),O=d("div");for(let u=0;u<q.length;u+=1)q[u].c();L=T(),Y=d("div");for(let u=0;u<A.length;u+=1)A[u].c();g(e,"class","label label-primary"),g(h,"class","content"),g(t,"class","alert alert-success"),g(D,"class","section-title"),g(M,"class","table-compact table-border m-b-base"),g(B,"class","section-title"),g(O,"class","tabs-header compact combined left"),g(Y,"class","tabs-content"),g(y,"class","tabs")},m(u,p){b(u,t,p),s(t,e),s(t,l),s(t,h),s(h,i),s(i,c),s(i,n),s(n,v),s(i,C),b(u,F,p),b(u,D,p),b(u,I,p),b(u,M,p),b(u,Q,p),b(u,B,p),b(u,H,p),b(u,y,p),s(y,O);for(let S=0;S<q.length;S+=1)q[S]&&q[S].m(O,null);s(y,L),s(y,Y);for(let S=0;S<A.length;S+=1)A[S]&&A[S].m(Y,null);N=!0},p(u,[p]){(!N||p&1)&&m!==(m=u[0].name+"")&&ce(v,m),p&6&&(o=V(u[2]),q=de(q,p,$,1,u,o,k,O,je,Le,null,Ue)),p&6&&(P=V(u[2]),ue(),A=de(A,p,X,1,u,P,G,Y,Qe,Ye,null,He),he())},i(u){if(!N){for(let p=0;p<P.length;p+=1)E(A[p]);N=!0}},o(u){for(let p=0;p<A.length;p+=1)j(A[p]);N=!1},d(u){u&&(_(t),_(F),_(D),_(I),_(M),_(Q),_(B),_(H),_(y));for(let p=0;p<q.length;p+=1)q[p].d();for(let p=0;p<A.length;p+=1)A[p].d()}}}function tt(a,t,e){let{collection:l}=t,h=200,i=[];const c=n=>e(1,h=n.code);return a.$$set=n=>{"collection"in n&&e(0,l=n.collection)},e(2,i=[{code:200,body:JSON.stringify({otpId:ke.randomString(15)},null,2)},{code:400,body:`
{
"code": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"email": {
"code": "validation_is_email",
"message": "Must be a valid email address."
}
}
}
`},{code:429,body:`
{
"code": 429,
"message": "You've send too many OTP requests, please try again later.",
"data": {}
}
`}]),[l,h,i,c]}class lt extends be{constructor(t){super(),_e(this,t,tt,et,ve,{collection:0})}}function Ve(a,t,e){const l=a.slice();return l[5]=t[e],l[7]=e,l}function Ee(a,t,e){const l=a.slice();return l[5]=t[e],l[7]=e,l}function Je(a){let t,e,l,h,i;function c(){return a[4](a[7])}return{c(){t=d("button"),e=d("div"),e.textContent=`${a[5].title}`,l=T(),g(e,"class","txt"),g(t,"class","tab-item"),J(t,"active",a[1]==a[7])},m(n,m){b(n,t,m),s(t,e),s(t,l),h||(i=$e(t,"click",c),h=!0)},p(n,m){a=n,m&2&&J(t,"active",a[1]==a[7])},d(n){n&&_(t),h=!1,i()}}}function Ne(a){let t,e,l,h;var i=a[5].component;function c(n,m){return{props:{collection:n[0]}}}return i&&(e=Be(i,c(a))),{c(){t=d("div"),e&&x(e.$$.fragment),l=T(),g(t,"class","tab-item"),J(t,"active",a[1]==a[7])},m(n,m){b(n,t,m),e&&ee(e,t,null),s(t,l),h=!0},p(n,m){if(i!==(i=n[5].component)){if(e){ue();const v=e;j(v.$$.fragment,1,0,()=>{te(v,1)}),he()}i?(e=Be(i,c(n)),x(e.$$.fragment),E(e.$$.fragment,1),ee(e,t,l)):e=null}else if(i){const v={};m&1&&(v.collection=n[0]),e.$set(v)}(!h||m&2)&&J(t,"active",n[1]==n[7])},i(n){h||(e&&E(e.$$.fragment,n),h=!0)},o(n){e&&j(e.$$.fragment,n),h=!1},d(n){n&&_(t),e&&te(e)}}}function ot(a){var Y,A,G,N;let t,e,l=a[0].name+"",h,i,c,n,m,v,C,F,D,I,M,Q,B,H;v=new ze({props:{js:`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${a[2]}');
...
// send OTP email to the provided auth record
const req = await pb.collection('${(Y=a[0])==null?void 0:Y.name}').requestOTP('test@example.com');
// ... show a screen/popup to enter the password from the email ...
// authenticate with the requested OTP id and the email password
const authData = await pb.collection('${(A=a[0])==null?void 0:A.name}').authWithOTP(
req.otpId,
"YOUR_OTP",
);
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.record.id);
// "logout"
pb.authStore.clear();
`,dart:`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${a[2]}');
...
// send OTP email to the provided auth record
final req = await pb.collection('${(G=a[0])==null?void 0:G.name}').requestOTP('test@example.com');
// ... show a screen/popup to enter the password from the email ...
// authenticate with the requested OTP id and the email password
final authData = await pb.collection('${(N=a[0])==null?void 0:N.name}').authWithOTP(
req.otpId,
"YOUR_OTP",
);
// after the above you can also access the auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.record.id);
// "logout"
pb.authStore.clear();
`}});let y=V(a[3]),O=[];for(let o=0;o<y.length;o+=1)O[o]=Je(Ee(a,y,o));let q=V(a[3]),k=[];for(let o=0;o<q.length;o+=1)k[o]=Ne(Ve(a,q,o));const L=o=>j(k[o],1,1,()=>{k[o]=null});return{c(){t=d("h3"),e=R("Auth with OTP ("),h=R(l),i=R(")"),c=T(),n=d("div"),n.innerHTML=`<p>Authenticate with an one-time password (OTP).</p> <p>Note that when requesting an OTP we return an <code>otpId</code> even if a user with the provided email
doesn&#39;t exist as a very basic enumeration protection.</p>`,m=T(),x(v.$$.fragment),C=T(),F=d("h6"),F.textContent="API details",D=T(),I=d("div"),M=d("div");for(let o=0;o<O.length;o+=1)O[o].c();Q=T(),B=d("div");for(let o=0;o<k.length;o+=1)k[o].c();g(t,"class","m-b-sm"),g(n,"class","content txt-lg m-b-sm"),g(F,"class","m-b-xs"),g(M,"class","tabs-header compact"),g(B,"class","tabs-content"),g(I,"class","tabs")},m(o,$){b(o,t,$),s(t,e),s(t,h),s(t,i),b(o,c,$),b(o,n,$),b(o,m,$),ee(v,o,$),b(o,C,$),b(o,F,$),b(o,D,$),b(o,I,$),s(I,M);for(let P=0;P<O.length;P+=1)O[P]&&O[P].m(M,null);s(I,Q),s(I,B);for(let P=0;P<k.length;P+=1)k[P]&&k[P].m(B,null);H=!0},p(o,[$]){var X,u,p,S;(!H||$&1)&&l!==(l=o[0].name+"")&&ce(h,l);const P={};if($&5&&(P.js=`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${o[2]}');
...
// send OTP email to the provided auth record
const req = await pb.collection('${(X=o[0])==null?void 0:X.name}').requestOTP('test@example.com');
// ... show a screen/popup to enter the password from the email ...
// authenticate with the requested OTP id and the email password
const authData = await pb.collection('${(u=o[0])==null?void 0:u.name}').authWithOTP(
req.otpId,
"YOUR_OTP",
);
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.record.id);
// "logout"
pb.authStore.clear();
`),$&5&&(P.dart=`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${o[2]}');
...
// send OTP email to the provided auth record
final req = await pb.collection('${(p=o[0])==null?void 0:p.name}').requestOTP('test@example.com');
// ... show a screen/popup to enter the password from the email ...
// authenticate with the requested OTP id and the email password
final authData = await pb.collection('${(S=o[0])==null?void 0:S.name}').authWithOTP(
req.otpId,
"YOUR_OTP",
);
// after the above you can also access the auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.record.id);
// "logout"
pb.authStore.clear();
`),v.$set(P),$&10){y=V(o[3]);let w;for(w=0;w<y.length;w+=1){const K=Ee(o,y,w);O[w]?O[w].p(K,$):(O[w]=Je(K),O[w].c(),O[w].m(M,null))}for(;w<O.length;w+=1)O[w].d(1);O.length=y.length}if($&11){q=V(o[3]);let w;for(w=0;w<q.length;w+=1){const K=Ve(o,q,w);k[w]?(k[w].p(K,$),E(k[w],1)):(k[w]=Ne(K),k[w].c(),E(k[w],1),k[w].m(B,null))}for(ue(),w=q.length;w<k.length;w+=1)L(w);he()}},i(o){if(!H){E(v.$$.fragment,o);for(let $=0;$<q.length;$+=1)E(k[$]);H=!0}},o(o){j(v.$$.fragment,o),k=k.filter(Boolean);for(let $=0;$<k.length;$+=1)j(k[$]);H=!1},d(o){o&&(_(t),_(c),_(n),_(m),_(C),_(F),_(D),_(I)),te(v,o),Me(O,o),Me(k,o)}}}function at(a,t,e){let l,{collection:h}=t;const i=[{title:"OTP Request",component:lt},{title:"OTP Auth",component:xe}];let c=0;const n=m=>e(1,c=m);return a.$$set=m=>{"collection"in m&&e(0,h=m.collection)},e(2,l=ke.getApiExampleUrl(Ge.baseURL)),[h,c,l,i,n]}class it extends be{constructor(t){super(),_e(this,t,at,ot,ve,{collection:0})}}export{it as default};
@@ -1,4 +1,4 @@
import{S as kt,i as gt,s as vt,V as St,X as B,W as _t,h as s,z as f,j as u,c as ae,k,n as c,o as t,m as oe,H as X,Y as ct,Z as wt,E as yt,_ as $t,G as Pt,t as G,a as K,v as d,d as se,$ as Rt,J as dt,p as Ct,l as ne,q as Ot}from"./index-Bpzzn7x2.js";import{F as Tt}from"./FieldsQueryParam-CrrI0KIL.js";function pt(i,o,a){const n=i.slice();return n[7]=o[a],n}function ut(i,o,a){const n=i.slice();return n[7]=o[a],n}function ht(i,o,a){const n=i.slice();return n[12]=o[a],n[14]=a,n}function At(i){let o;return{c(){o=f("or")},m(a,n){c(a,o,n)},d(a){a&&d(o)}}}function bt(i){let o,a,n=i[12]+"",m,b=i[14]>0&&At();return{c(){b&&b.c(),o=u(),a=s("strong"),m=f(n)},m(r,h){b&&b.m(r,h),c(r,o,h),c(r,a,h),t(a,m)},p(r,h){h&2&&n!==(n=r[12]+"")&&X(m,n)},d(r){r&&(d(o),d(a)),b&&b.d(r)}}}function ft(i,o){let a,n=o[7].code+"",m,b,r,h;function g(){return o[6](o[7])}return{key:i,first:null,c(){a=s("button"),m=f(n),b=u(),k(a,"class","tab-item"),ne(a,"active",o[2]===o[7].code),this.first=a},m($,_){c($,a,_),t(a,m),t(a,b),r||(h=Ot(a,"click",g),r=!0)},p($,_){o=$,_&8&&n!==(n=o[7].code+"")&&X(m,n),_&12&&ne(a,"active",o[2]===o[7].code)},d($){$&&d(a),r=!1,h()}}}function mt(i,o){let a,n,m,b;return n=new _t({props:{content:o[7].body}}),{key:i,first:null,c(){a=s("div"),ae(n.$$.fragment),m=u(),k(a,"class","tab-item"),ne(a,"active",o[2]===o[7].code),this.first=a},m(r,h){c(r,a,h),oe(n,a,null),t(a,m),b=!0},p(r,h){o=r;const g={};h&8&&(g.content=o[7].body),n.$set(g),(!b||h&12)&&ne(a,"active",o[2]===o[7].code)},i(r){b||(G(n.$$.fragment,r),b=!0)},o(r){K(n.$$.fragment,r),b=!1},d(r){r&&d(a),se(n)}}}function Dt(i){var ot,st;let o,a,n=i[0].name+"",m,b,r,h,g,$,_,Z=i[1].join("/")+"",ie,De,re,We,ce,R,de,H,pe,C,x,Fe,ee,L,Me,ue,te=i[0].name+"",he,Ue,be,j,fe,O,me,qe,Y,T,_e,Be,ke,He,E,ge,Le,ve,Se,V,we,A,ye,je,N,D,$e,Ye,Pe,Ee,v,Ve,M,Ne,Je,Ie,Re,Qe,Ce,ze,Ge,Ke,Oe,Xe,Ze,U,Te,J,Ae,W,I,P=[],xe=new Map,et,Q,w=[],tt=new Map,F;R=new St({props:{js:`
import{S as kt,i as gt,s as vt,V as St,X as B,W as _t,h as s,z as f,j as u,c as ae,k,n as c,o as t,m as oe,H as X,Y as ct,Z as wt,E as yt,_ as $t,G as Pt,t as G,a as K,v as d,d as se,$ as Rt,J as dt,p as Ct,l as ne,q as Ot}from"./index-0HOqdotm.js";import{F as Tt}from"./FieldsQueryParam-DT9Tnt7Y.js";function pt(i,o,a){const n=i.slice();return n[7]=o[a],n}function ut(i,o,a){const n=i.slice();return n[7]=o[a],n}function ht(i,o,a){const n=i.slice();return n[12]=o[a],n[14]=a,n}function At(i){let o;return{c(){o=f("or")},m(a,n){c(a,o,n)},d(a){a&&d(o)}}}function bt(i){let o,a,n=i[12]+"",m,b=i[14]>0&&At();return{c(){b&&b.c(),o=u(),a=s("strong"),m=f(n)},m(r,h){b&&b.m(r,h),c(r,o,h),c(r,a,h),t(a,m)},p(r,h){h&2&&n!==(n=r[12]+"")&&X(m,n)},d(r){r&&(d(o),d(a)),b&&b.d(r)}}}function ft(i,o){let a,n=o[7].code+"",m,b,r,h;function g(){return o[6](o[7])}return{key:i,first:null,c(){a=s("button"),m=f(n),b=u(),k(a,"class","tab-item"),ne(a,"active",o[2]===o[7].code),this.first=a},m($,_){c($,a,_),t(a,m),t(a,b),r||(h=Ot(a,"click",g),r=!0)},p($,_){o=$,_&8&&n!==(n=o[7].code+"")&&X(m,n),_&12&&ne(a,"active",o[2]===o[7].code)},d($){$&&d(a),r=!1,h()}}}function mt(i,o){let a,n,m,b;return n=new _t({props:{content:o[7].body}}),{key:i,first:null,c(){a=s("div"),ae(n.$$.fragment),m=u(),k(a,"class","tab-item"),ne(a,"active",o[2]===o[7].code),this.first=a},m(r,h){c(r,a,h),oe(n,a,null),t(a,m),b=!0},p(r,h){o=r;const g={};h&8&&(g.content=o[7].body),n.$set(g),(!b||h&12)&&ne(a,"active",o[2]===o[7].code)},i(r){b||(G(n.$$.fragment,r),b=!0)},o(r){K(n.$$.fragment,r),b=!1},d(r){r&&d(a),se(n)}}}function Dt(i){var ot,st;let o,a,n=i[0].name+"",m,b,r,h,g,$,_,Z=i[1].join("/")+"",ie,De,re,We,ce,R,de,H,pe,C,x,Fe,ee,L,Me,ue,te=i[0].name+"",he,Ue,be,j,fe,O,me,qe,Y,T,_e,Be,ke,He,E,ge,Le,ve,Se,V,we,A,ye,je,N,D,$e,Ye,Pe,Ee,v,Ve,M,Ne,Je,Ie,Re,Qe,Ce,ze,Ge,Ke,Oe,Xe,Ze,U,Te,J,Ae,W,I,P=[],xe=new Map,et,Q,w=[],tt=new Map,F;R=new St({props:{js:`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${i[5]}');
@@ -1,4 +1,4 @@
import{S as St,i as Lt,s as jt,V as At,W as It,X as Z,h as o,z as _,j as i,c as Re,k as b,n as d,o as t,m as Te,C as Mt,D as Nt,H as Ut,Y as Pt,Z as zt,E as Jt,_ as Wt,G as Gt,t as Q,a as x,v as u,d as Pe,J as Ft,p as Kt,l as ee,q as Vt}from"./index-Bpzzn7x2.js";function Bt(a,s,n){const c=a.slice();return c[6]=s[n],c}function Et(a,s,n){const c=a.slice();return c[6]=s[n],c}function Ot(a,s){let n,c,y;function f(){return s[5](s[6])}return{key:a,first:null,c(){n=o("button"),n.textContent=`${s[6].code} `,b(n,"class","tab-item"),ee(n,"active",s[1]===s[6].code),this.first=n},m(r,h){d(r,n,h),c||(y=Vt(n,"click",f),c=!0)},p(r,h){s=r,h&10&&ee(n,"active",s[1]===s[6].code)},d(r){r&&u(n),c=!1,y()}}}function Ht(a,s){let n,c,y,f;return c=new It({props:{content:s[6].body}}),{key:a,first:null,c(){n=o("div"),Re(c.$$.fragment),y=i(),b(n,"class","tab-item"),ee(n,"active",s[1]===s[6].code),this.first=n},m(r,h){d(r,n,h),Te(c,n,null),t(n,y),f=!0},p(r,h){s=r,(!f||h&10)&&ee(n,"active",s[1]===s[6].code)},i(r){f||(Q(c.$$.fragment,r),f=!0)},o(r){x(c.$$.fragment,r),f=!1},d(r){r&&u(n),Pe(c)}}}function Xt(a){var pt,mt,bt,ht,ft,_t,yt,kt;let s,n,c=a[0].name+"",y,f,r,h,F,g,U,Fe,P,B,Be,E,Ee,Oe,te,le,q,oe,O,ae,H,se,I,ne,z,ie,w,ce,He,re,S,J,Ie,k,W,Se,de,Le,C,G,je,ue,Ae,K,Me,pe,Ne,D,Ue,me,ze,Je,We,V,Ge,X,Ke,be,Ve,he,Xe,fe,Ye,p,_e,Ze,ye,Qe,ke,xe,$e,et,ge,tt,ve,lt,ot,at,Ce,st,R,De,L,qe,T,j,v=[],nt=new Map,it,A,$=[],ct=new Map,M,we,rt;q=new At({props:{js:`
import{S as St,i as Lt,s as jt,V as At,W as It,X as Z,h as o,z as _,j as i,c as Re,k as b,n as d,o as t,m as Te,C as Mt,D as Nt,H as Ut,Y as Pt,Z as zt,E as Jt,_ as Wt,G as Gt,t as Q,a as x,v as u,d as Pe,J as Ft,p as Kt,l as ee,q as Vt}from"./index-0HOqdotm.js";function Bt(a,s,n){const c=a.slice();return c[6]=s[n],c}function Et(a,s,n){const c=a.slice();return c[6]=s[n],c}function Ot(a,s){let n,c,y;function f(){return s[5](s[6])}return{key:a,first:null,c(){n=o("button"),n.textContent=`${s[6].code} `,b(n,"class","tab-item"),ee(n,"active",s[1]===s[6].code),this.first=n},m(r,h){d(r,n,h),c||(y=Vt(n,"click",f),c=!0)},p(r,h){s=r,h&10&&ee(n,"active",s[1]===s[6].code)},d(r){r&&u(n),c=!1,y()}}}function Ht(a,s){let n,c,y,f;return c=new It({props:{content:s[6].body}}),{key:a,first:null,c(){n=o("div"),Re(c.$$.fragment),y=i(),b(n,"class","tab-item"),ee(n,"active",s[1]===s[6].code),this.first=n},m(r,h){d(r,n,h),Te(c,n,null),t(n,y),f=!0},p(r,h){s=r,(!f||h&10)&&ee(n,"active",s[1]===s[6].code)},i(r){f||(Q(c.$$.fragment,r),f=!0)},o(r){x(c.$$.fragment,r),f=!1},d(r){r&&u(n),Pe(c)}}}function Xt(a){var pt,mt,bt,ht,ft,_t,yt,kt;let s,n,c=a[0].name+"",y,f,r,h,F,g,U,Fe,P,B,Be,E,Ee,Oe,te,le,q,oe,O,ae,H,se,I,ne,z,ie,w,ce,He,re,S,J,Ie,k,W,Se,de,Le,C,G,je,ue,Ae,K,Me,pe,Ne,D,Ue,me,ze,Je,We,V,Ge,X,Ke,be,Ve,he,Xe,fe,Ye,p,_e,Ze,ye,Qe,ke,xe,$e,et,ge,tt,ve,lt,ot,at,Ce,st,R,De,L,qe,T,j,v=[],nt=new Map,it,A,$=[],ct=new Map,M,we,rt;q=new At({props:{js:`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${a[2]}');
@@ -1,4 +1,4 @@
import{S as wt,i as Tt,s as vt,h as qt,k as Rt,a1 as OO,n as _t,I as IO,v as jt,O as Yt,T as Vt,U as Wt,Q as Ut,J as Gt,y as zt}from"./index-Bpzzn7x2.js";import{P as Ct,N as Et,w as At,D as Mt,x as jO,T as tO,I as YO,y as I,z as n,A as Lt,L as D,B as J,F as Y,G as K,H as VO,J as F,v as z,K as _e,M as Bt,O as Nt,Q as je,R as Ye,U as Ve,E as j,V as We,W as g,X as It,Y as Dt,b as C,e as Jt,f as Kt,g as Ft,i as Ht,j as Oa,k as ea,u as ta,l as aa,m as ra,r as ia,n as sa,o as la,c as na,d as oa,s as Qa,h as ca,a as pa,p as ha,q as DO,C as eO}from"./index-CH-gyH77.js";var JO={};class sO{constructor(O,t,a,r,s,i,l,o,c,h=0,Q){this.p=O,this.stack=t,this.state=a,this.reducePos=r,this.pos=s,this.score=i,this.buffer=l,this.bufferBase=o,this.curContext=c,this.lookAhead=h,this.parent=Q}toString(){return`[${this.stack.filter((O,t)=>t%3==0).concat(this.state)}]@${this.pos}${this.score?"!"+this.score:""}`}static start(O,t,a=0){let r=O.parser.context;return new sO(O,[],t,a,a,0,[],0,r?new KO(r,r.start):null,0,null)}get context(){return this.curContext?this.curContext.context:null}pushState(O,t){this.stack.push(this.state,t,this.bufferBase+this.buffer.length),this.state=O}reduce(O){var t;let a=O>>19,r=O&65535,{parser:s}=this.p,i=this.reducePos<this.pos-25;i&&this.setLookAhead(this.pos);let l=s.dynamicPrecedence(r);if(l&&(this.score+=l),a==0){this.pushState(s.getGoto(this.state,r,!0),this.reducePos),r<s.minRepeatTerm&&this.storeNode(r,this.reducePos,this.reducePos,i?8:4,!0),this.reduceContext(r,this.reducePos);return}let o=this.stack.length-(a-1)*3-(O&262144?6:0),c=o?this.stack[o-2]:this.p.ranges[0].from,h=this.reducePos-c;h>=2e3&&!(!((t=this.p.parser.nodeSet.types[r])===null||t===void 0)&&t.isAnonymous)&&(c==this.p.lastBigReductionStart?(this.p.bigReductionCount++,this.p.lastBigReductionSize=h):this.p.lastBigReductionSize<h&&(this.p.bigReductionCount=1,this.p.lastBigReductionStart=c,this.p.lastBigReductionSize=h));let Q=o?this.stack[o-1]:0,f=this.bufferBase+this.buffer.length-Q;if(r<s.minRepeatTerm||O&131072){let p=s.stateFlag(this.state,1)?this.pos:this.reducePos;this.storeNode(r,c,p,f+4,!0)}if(O&262144)this.state=this.stack[o];else{let p=this.stack[o-3];this.state=s.getGoto(p,r,!0)}for(;this.stack.length>o;)this.stack.pop();this.reduceContext(r,c)}storeNode(O,t,a,r=4,s=!1){if(O==0&&(!this.stack.length||this.stack[this.stack.length-1]<this.buffer.length+this.bufferBase)){let i=this,l=this.buffer.length;if(l==0&&i.parent&&(l=i.bufferBase-i.parent.bufferBase,i=i.parent),l>0&&i.buffer[l-4]==0&&i.buffer[l-1]>-1){if(t==a)return;if(i.buffer[l-2]>=t){i.buffer[l-2]=a;return}}}if(!s||this.pos==a)this.buffer.push(O,t,a,r);else{let i=this.buffer.length;if(i>0&&this.buffer[i-4]!=0){let l=!1;for(let o=i;o>0&&this.buffer[o-2]>a;o-=4)if(this.buffer[o-1]>=0){l=!0;break}if(l)for(;i>0&&this.buffer[i-2]>a;)this.buffer[i]=this.buffer[i-4],this.buffer[i+1]=this.buffer[i-3],this.buffer[i+2]=this.buffer[i-2],this.buffer[i+3]=this.buffer[i-1],i-=4,r>4&&(r-=4)}this.buffer[i]=O,this.buffer[i+1]=t,this.buffer[i+2]=a,this.buffer[i+3]=r}}shift(O,t,a,r){if(O&131072)this.pushState(O&65535,this.pos);else if(O&262144)this.pos=r,this.shiftContext(t,a),t<=this.p.parser.maxNode&&this.buffer.push(t,a,r,4);else{let s=O,{parser:i}=this.p;(r>this.pos||t<=i.maxNode)&&(this.pos=r,i.stateFlag(s,1)||(this.reducePos=r)),this.pushState(s,a),this.shiftContext(t,a),t<=i.maxNode&&this.buffer.push(t,a,r,4)}}apply(O,t,a,r){O&65536?this.reduce(O):this.shift(O,t,a,r)}useNode(O,t){let a=this.p.reused.length-1;(a<0||this.p.reused[a]!=O)&&(this.p.reused.push(O),a++);let r=this.pos;this.reducePos=this.pos=r+O.length,this.pushState(t,r),this.buffer.push(a,r,this.reducePos,-1),this.curContext&&this.updateContext(this.curContext.tracker.reuse(this.curContext.context,O,this,this.p.stream.reset(this.pos-O.length)))}split(){let O=this,t=O.buffer.length;for(;t>0&&O.buffer[t-2]>O.reducePos;)t-=4;let a=O.buffer.slice(t),r=O.bufferBase+t;for(;O&&r==O.bufferBase;)O=O.parent;return new sO(this.p,this.stack.slice(),this.state,this.reducePos,this.pos,this.score,a,r,this.curContext,this.lookAhead,O)}recoverByDelete(O,t){let a=O<=this.p.parser.maxNode;a&&this.storeNode(O,this.pos,t,4),this.storeNode(0,this.pos,t,a?8:4),this.pos=this.reducePos=t,this.score-=190}canShift(O){for(let t=new ua(this);;){let a=this.p.parser.stateSlot(t.state,4)||this.p.parser.hasAction(t.state,O);if(a==0)return!1;if(!(a&65536))return!0;t.reduce(a)}}recoverByInsert(O){if(this.stack.length>=300)return[];let t=this.p.parser.nextStates(this.state);if(t.length>8||this.stack.length>=120){let r=[];for(let s=0,i;s<t.length;s+=2)(i=t[s+1])!=this.state&&this.p.parser.hasAction(i,O)&&r.push(t[s],i);if(this.stack.length<120)for(let s=0;r.length<8&&s<t.length;s+=2){let i=t[s+1];r.some((l,o)=>o&1&&l==i)||r.push(t[s],i)}t=r}let a=[];for(let r=0;r<t.length&&a.length<4;r+=2){let s=t[r+1];if(s==this.state)continue;let i=this.split();i.pushState(s,this.pos),i.storeNode(0,i.pos,i.pos,4,!0),i.shiftContext(t[r],this.pos),i.reducePos=this.pos,iLine truncated
import{S as wt,i as Tt,s as vt,h as qt,k as Rt,a1 as OO,n as _t,I as IO,v as jt,O as Yt,T as Vt,U as Wt,Q as Ut,J as Gt,y as zt}from"./index-0HOqdotm.js";import{P as Ct,N as Et,w as At,D as Mt,x as jO,T as tO,I as YO,y as I,z as n,A as Lt,L as D,B as J,F as Y,G as K,H as VO,J as F,v as z,K as _e,M as Bt,O as Nt,Q as je,R as Ye,U as Ve,E as j,V as We,W as g,X as It,Y as Dt,b as C,e as Jt,f as Kt,g as Ft,i as Ht,j as Oa,k as ea,u as ta,l as aa,m as ra,r as ia,n as sa,o as la,c as na,d as oa,s as Qa,h as ca,a as pa,p as ha,q as DO,C as eO}from"./index-Sijz3BEY.js";var JO={};class sO{constructor(O,t,a,r,s,i,l,o,c,h=0,Q){this.p=O,this.stack=t,this.state=a,this.reducePos=r,this.pos=s,this.score=i,this.buffer=l,this.bufferBase=o,this.curContext=c,this.lookAhead=h,this.parent=Q}toString(){return`[${this.stack.filter((O,t)=>t%3==0).concat(this.state)}]@${this.pos}${this.score?"!"+this.score:""}`}static start(O,t,a=0){let r=O.parser.context;return new sO(O,[],t,a,a,0,[],0,r?new KO(r,r.start):null,0,null)}get context(){return this.curContext?this.curContext.context:null}pushState(O,t){this.stack.push(this.state,t,this.bufferBase+this.buffer.length),this.state=O}reduce(O){var t;let a=O>>19,r=O&65535,{parser:s}=this.p,i=this.reducePos<this.pos-25;i&&this.setLookAhead(this.pos);let l=s.dynamicPrecedence(r);if(l&&(this.score+=l),a==0){this.pushState(s.getGoto(this.state,r,!0),this.reducePos),r<s.minRepeatTerm&&this.storeNode(r,this.reducePos,this.reducePos,i?8:4,!0),this.reduceContext(r,this.reducePos);return}let o=this.stack.length-(a-1)*3-(O&262144?6:0),c=o?this.stack[o-2]:this.p.ranges[0].from,h=this.reducePos-c;h>=2e3&&!(!((t=this.p.parser.nodeSet.types[r])===null||t===void 0)&&t.isAnonymous)&&(c==this.p.lastBigReductionStart?(this.p.bigReductionCount++,this.p.lastBigReductionSize=h):this.p.lastBigReductionSize<h&&(this.p.bigReductionCount=1,this.p.lastBigReductionStart=c,this.p.lastBigReductionSize=h));let Q=o?this.stack[o-1]:0,f=this.bufferBase+this.buffer.length-Q;if(r<s.minRepeatTerm||O&131072){let p=s.stateFlag(this.state,1)?this.pos:this.reducePos;this.storeNode(r,c,p,f+4,!0)}if(O&262144)this.state=this.stack[o];else{let p=this.stack[o-3];this.state=s.getGoto(p,r,!0)}for(;this.stack.length>o;)this.stack.pop();this.reduceContext(r,c)}storeNode(O,t,a,r=4,s=!1){if(O==0&&(!this.stack.length||this.stack[this.stack.length-1]<this.buffer.length+this.bufferBase)){let i=this,l=this.buffer.length;if(l==0&&i.parent&&(l=i.bufferBase-i.parent.bufferBase,i=i.parent),l>0&&i.buffer[l-4]==0&&i.buffer[l-1]>-1){if(t==a)return;if(i.buffer[l-2]>=t){i.buffer[l-2]=a;return}}}if(!s||this.pos==a)this.buffer.push(O,t,a,r);else{let i=this.buffer.length;if(i>0&&this.buffer[i-4]!=0){let l=!1;for(let o=i;o>0&&this.buffer[o-2]>a;o-=4)if(this.buffer[o-1]>=0){l=!0;break}if(l)for(;i>0&&this.buffer[i-2]>a;)this.buffer[i]=this.buffer[i-4],this.buffer[i+1]=this.buffer[i-3],this.buffer[i+2]=this.buffer[i-2],this.buffer[i+3]=this.buffer[i-1],i-=4,r>4&&(r-=4)}this.buffer[i]=O,this.buffer[i+1]=t,this.buffer[i+2]=a,this.buffer[i+3]=r}}shift(O,t,a,r){if(O&131072)this.pushState(O&65535,this.pos);else if(O&262144)this.pos=r,this.shiftContext(t,a),t<=this.p.parser.maxNode&&this.buffer.push(t,a,r,4);else{let s=O,{parser:i}=this.p;(r>this.pos||t<=i.maxNode)&&(this.pos=r,i.stateFlag(s,1)||(this.reducePos=r)),this.pushState(s,a),this.shiftContext(t,a),t<=i.maxNode&&this.buffer.push(t,a,r,4)}}apply(O,t,a,r){O&65536?this.reduce(O):this.shift(O,t,a,r)}useNode(O,t){let a=this.p.reused.length-1;(a<0||this.p.reused[a]!=O)&&(this.p.reused.push(O),a++);let r=this.pos;this.reducePos=this.pos=r+O.length,this.pushState(t,r),this.buffer.push(a,r,this.reducePos,-1),this.curContext&&this.updateContext(this.curContext.tracker.reuse(this.curContext.context,O,this,this.p.stream.reset(this.pos-O.length)))}split(){let O=this,t=O.buffer.length;for(;t>0&&O.buffer[t-2]>O.reducePos;)t-=4;let a=O.buffer.slice(t),r=O.bufferBase+t;for(;O&&r==O.bufferBase;)O=O.parent;return new sO(this.p,this.stack.slice(),this.state,this.reducePos,this.pos,this.score,a,r,this.curContext,this.lookAhead,O)}recoverByDelete(O,t){let a=O<=this.p.parser.maxNode;a&&this.storeNode(O,this.pos,t,4),this.storeNode(0,this.pos,t,a?8:4),this.pos=this.reducePos=t,this.score-=190}canShift(O){for(let t=new ua(this);;){let a=this.p.parser.stateSlot(t.state,4)||this.p.parser.hasAction(t.state,O);if(a==0)return!1;if(!(a&65536))return!0;t.reduce(a)}}recoverByInsert(O){if(this.stack.length>=300)return[];let t=this.p.parser.nextStates(this.state);if(t.length>8||this.stack.length>=120){let r=[];for(let s=0,i;s<t.length;s+=2)(i=t[s+1])!=this.state&&this.p.parser.hasAction(i,O)&&r.push(t[s],i);if(this.stack.length<120)for(let s=0;r.length<8&&s<t.length;s+=2){let i=t[s+1];r.some((l,o)=>o&1&&l==i)||r.push(t[s],i)}t=r}let a=[];for(let r=0;r<t.length&&a.length<4;r+=2){let s=t[r+1];if(s==this.state)continue;let i=this.split();i.pushState(s,this.pos),i.storeNode(0,i.pos,i.pos,4,!0),i.shiftContext(t[r],this.pos),i.reducePos=this.pos,iLine truncated
\${}
} catch (\${error}) {
\${}
@@ -10,5 +10,5 @@ import{S as wt,i as Tt,s as vt,h as qt,k as Rt,a1 as OO,n as _t,I as IO,v as jt,
constructor(\${params}) {
\${}
}
}`,{label:"class",detail:"definition",type:"keyword"}),g('import {${names}} from "${module}"\n${}',{label:"import",detail:"named",type:"keyword"}),g('import ${name} from "${module}"\n${}',{label:"import",detail:"default",type:"keyword"})],ci=tt.concat([g("interface ${name} {\n ${}\n}",{label:"interface",detail:"definition",type:"keyword"}),g("type ${name} = ${type}",{label:"type",detail:"definition",type:"keyword"}),g("enum ${name} {\n ${}\n}",{label:"enum",detail:"definition",type:"keyword"})]),$e=new _e,at=new Set(["Script","Block","FunctionExpression","FunctionDeclaration","ArrowFunction","MethodDeclaration","ForStatement"]);function E(e){return(O,t)=>{let a=O.node.getChild("VariableDefinition");return a&&t(a,e),!0}}const pi=["FunctionDeclaration"],hi={FunctionDeclaration:E("function"),ClassDeclaration:E("class"),ClassExpression:()=>!0,EnumDeclaration:E("constant"),TypeAliasDeclaration:E("type"),NamespaceDeclaration:E("namespace"),VariableDefinition(e,O){e.matchContext(pi)||O(e,"variable")},TypeDefinition(e,O){O(e,"type")},__proto__:null};function rt(e,O){let t=$e.get(O);if(t)return t;let a=[],r=!0;function s(i,l){let o=e.sliceString(i.from,i.to);a.push({label:o,type:l})}return O.cursor(YO.IncludeAnonymous).iterate(i=>{if(r)r=!1;else if(i.name){let l=hi[i.name];if(l&&l(i,s)||at.has(i.name))return!1}else if(i.to-i.from>8192){for(let l of rt(e,i.node))a.push(l);return!1}}),$e.set(O,a),a}const Pe=/^[\w$\xa1-\uffff][\w$\d\xa1-\uffff]*$/,it=["TemplateString","String","RegExp","LineComment","BlockComment","VariableDefinition","TypeDefinition","Label","PropertyDefinition","PropertyName","PrivatePropertyDefinition","PrivatePropertyName",".","?."];function ui(e){let O=z(e.state).resolveInner(e.pos,-1);if(it.indexOf(O.name)>-1)return null;let t=O.name=="VariableName"||O.to-O.from<20&&Pe.test(e.state.sliceDoc(O.from,O.to));if(!t&&!e.explicit)return null;let a=[];for(let r=O;r;r=r.parent)at.has(r.name)&&(a=a.concat(rt(e.state.doc,r)));return{options:a,from:t?O.from:e.pos,validFor:Pe}}const y=D.define({name:"javascript",parser:Qi.configure({props:[J.add({IfStatement:Y({except:/^\s*({|else\b)/}),TryStatement:Y({except:/^\s*({|catch\b|finally\b)/}),LabeledStatement:Bt,SwitchBody:e=>{let O=e.textAfter,t=/^\s*\}/.test(O),a=/^\s*(case|default)\b/.test(O);return e.baseIndent+(t?0:a?1:2)*e.unit},Block:Nt({closing:"}"}),ArrowFunction:e=>e.baseIndent+e.unit,"TemplateString BlockComment":()=>null,"Statement Property":Y({except:/^{/}),JSXElement(e){let O=/^\s*<\//.test(e.textAfter);return e.lineIndent(e.node.from)+(O?0:e.unit)},JSXEscape(e){let O=/\s*\}/.test(e.textAfter);return e.lineIndent(e.node.from)+(O?0:e.unit)},"JSXOpenTag JSXSelfClosingTag"(e){return e.column(e.node.from)+e.unit}}),K.add({"Block ClassBody SwitchBody EnumBody ObjectExpression ArrayExpression ObjectType":VO,BlockComment(e){return{from:e.from+2,to:e.to-2}}})]}),languageData:{closeBrackets:{brackets:["(","[","{","'",'"',"`"]},commentTokens:{line:"//",block:{open:"/*",close:"*/"}},indentOnInput:/^\s*(?:case |default:|\{|\}|<\/)$/,wordChars:"$"}}),st={test:e=>/^JSX/.test(e.name),facet:It({commentTokens:{block:{open:"{/*",close:"*/}"}}})},lt=y.configure({dialect:"ts"},"typescript"),nt=y.configure({dialect:"jsx",props:[je.add(e=>e.isTop?[st]:void 0)]}),ot=y.configure({dialect:"jsx ts",props:[je.add(e=>e.isTop?[st]:void 0)]},"typescript");let Qt=e=>({label:e,type:"keyword"});const ct="break case const continue default delete export extends false finally in instanceof let new return static super switch this throw true typeof var yield".split(" ").map(Qt),di=ct.concat(["declare","implements","private","protected","public"].map(Qt));function pt(e={}){let O=e.jsx?e.typescript?ot:nt:e.typescript?lt:y,t=e.typescript?ci.concat(di):tt.concat(ct);return new F(O,[y.data.of({autocomplete:Ye(it,Ve(t))}),y.data.of({autocomplete:ui}),e.jsx?Pi:[]])}function fi(e){for(;;){if(e.name=="JSXOpenTag"||e.name=="JSXSelfClosingTag"||e.name=="JSXFragmentTag")return e;if(e.name=="JSXEscape"||!e.parent)return null;e=e.parent}}function Se(e,O,t=e.length){for(let a=O==null?void 0:O.firstChild;a;a=a.nextSibling)if(a.name=="JSXIdentifier"||a.name=="JSXBuiltin"||a.name=="JSXNamespacedName"||a.name=="JSXMemberExpression")return e.sliceString(a.from,Math.min(a.to,t));return""}const $i=typeof navigator=="object"&&/Android\b/.test(navigator.userAgent),Pi=j.inputHandler.of((e,O,t,a,r)=>{if(($i?e.composing:e.compositionStarted)||e.state.readOnly||O!=t||a!=">"&&a!="/"||!y.isActiveAt(e.state,O,-1))return!1;let s=r(),{state:i}=s,l=i.changeByRange(o=>{var c;let{head:h}=o,Q=z(i).resolveInner(h-1,-1),f;if(Q.name=="JSXStartTag"&&(Q=Q.parent),!(i.doc.sliceString(h-1,h)!=a||Q.name=="JSXAttributeValue"&&Q.to>h)){if(a==">"&&Q.name=="JSXFragmentTag")return{range:o,changes:{from:h,insert:"</>"}};if(a=="/"&&Q.name=="JSXStartCloseTag"){let p=Q.parent,d=p.parent;if(d&&p.from==h-2&&((f=Se(i.doc,d.firstChild,h))||((c=d.firstChild)===null||c===void 0?void 0:c.name)=="JSXFragmentTag")){let P=`${f}>`;return{range:We.cu Line truncated
}`,{label:"class",detail:"definition",type:"keyword"}),g('import {${names}} from "${module}"\n${}',{label:"import",detail:"named",type:"keyword"}),g('import ${name} from "${module}"\n${}',{label:"import",detail:"default",type:"keyword"})],ci=tt.concat([g("interface ${name} {\n ${}\n}",{label:"interface",detail:"definition",type:"keyword"}),g("type ${name} = ${type}",{label:"type",detail:"definition",type:"keyword"}),g("enum ${name} {\n ${}\n}",{label:"enum",detail:"definition",type:"keyword"})]),$e=new _e,at=new Set(["Script","Block","FunctionExpression","FunctionDeclaration","ArrowFunction","MethodDeclaration","ForStatement"]);function E(e){return(O,t)=>{let a=O.node.getChild("VariableDefinition");return a&&t(a,e),!0}}const pi=["FunctionDeclaration"],hi={FunctionDeclaration:E("function"),ClassDeclaration:E("class"),ClassExpression:()=>!0,EnumDeclaration:E("constant"),TypeAliasDeclaration:E("type"),NamespaceDeclaration:E("namespace"),VariableDefinition(e,O){e.matchContext(pi)||O(e,"variable")},TypeDefinition(e,O){O(e,"type")},__proto__:null};function rt(e,O){let t=$e.get(O);if(t)return t;let a=[],r=!0;function s(i,l){let o=e.sliceString(i.from,i.to);a.push({label:o,type:l})}return O.cursor(YO.IncludeAnonymous).iterate(i=>{if(r)r=!1;else if(i.name){let l=hi[i.name];if(l&&l(i,s)||at.has(i.name))return!1}else if(i.to-i.from>8192){for(let l of rt(e,i.node))a.push(l);return!1}}),$e.set(O,a),a}const Pe=/^[\w$\xa1-\uffff][\w$\d\xa1-\uffff]*$/,it=["TemplateString","String","RegExp","LineComment","BlockComment","VariableDefinition","TypeDefinition","Label","PropertyDefinition","PropertyName","PrivatePropertyDefinition","PrivatePropertyName",".","?."];function ui(e){let O=z(e.state).resolveInner(e.pos,-1);if(it.indexOf(O.name)>-1)return null;let t=O.name=="VariableName"||O.to-O.from<20&&Pe.test(e.state.sliceDoc(O.from,O.to));if(!t&&!e.explicit)return null;let a=[];for(let r=O;r;r=r.parent)at.has(r.name)&&(a=a.concat(rt(e.state.doc,r)));return{options:a,from:t?O.from:e.pos,validFor:Pe}}const y=D.define({name:"javascript",parser:Qi.configure({props:[J.add({IfStatement:Y({except:/^\s*({|else\b)/}),TryStatement:Y({except:/^\s*({|catch\b|finally\b)/}),LabeledStatement:Bt,SwitchBody:e=>{let O=e.textAfter,t=/^\s*\}/.test(O),a=/^\s*(case|default)\b/.test(O);return e.baseIndent+(t?0:a?1:2)*e.unit},Block:Nt({closing:"}"}),ArrowFunction:e=>e.baseIndent+e.unit,"TemplateString BlockComment":()=>null,"Statement Property":Y({except:/^{/}),JSXElement(e){let O=/^\s*<\//.test(e.textAfter);return e.lineIndent(e.node.from)+(O?0:e.unit)},JSXEscape(e){let O=/\s*\}/.test(e.textAfter);return e.lineIndent(e.node.from)+(O?0:e.unit)},"JSXOpenTag JSXSelfClosingTag"(e){return e.column(e.node.from)+e.unit}}),K.add({"Block ClassBody SwitchBody EnumBody ObjectExpression ArrayExpression ObjectType":VO,BlockComment(e){return{from:e.from+2,to:e.to-2}}})]}),languageData:{closeBrackets:{brackets:["(","[","{","'",'"',"`"]},commentTokens:{line:"//",block:{open:"/*",close:"*/"}},indentOnInput:/^\s*(?:case |default:|\{|\}|<\/)$/,wordChars:"$"}}),st={test:e=>/^JSX/.test(e.name),facet:It({commentTokens:{block:{open:"{/*",close:"*/}"}}})},lt=y.configure({dialect:"ts"},"typescript"),nt=y.configure({dialect:"jsx",props:[je.add(e=>e.isTop?[st]:void 0)]}),ot=y.configure({dialect:"jsx ts",props:[je.add(e=>e.isTop?[st]:void 0)]},"typescript");let Qt=e=>({label:e,type:"keyword"});const ct="break case const continue default delete export extends false finally in instanceof let new return static super switch this throw true typeof var yield".split(" ").map(Qt),di=ct.concat(["declare","implements","private","protected","public"].map(Qt));function pt(e={}){let O=e.jsx?e.typescript?ot:nt:e.typescript?lt:y,t=e.typescript?ci.concat(di):tt.concat(ct);return new F(O,[y.data.of({autocomplete:Ye(it,Ve(t))}),y.data.of({autocomplete:ui}),e.jsx?Pi:[]])}function fi(e){for(;;){if(e.name=="JSXOpenTag"||e.name=="JSXSelfClosingTag"||e.name=="JSXFragmentTag")return e;if(e.name=="JSXEscape"||!e.parent)return null;e=e.parent}}function Se(e,O,t=e.length){for(let a=O==null?void 0:O.firstChild;a;a=a.nextSibling)if(a.name=="JSXIdentifier"||a.name=="JSXBuiltin"||a.name=="JSXNamespacedName"||a.name=="JSXMemberExpression")return e.sliceString(a.from,Math.min(a.to,t));return""}const $i=typeof navigator=="object"&&/Android\b/.test(navigator.userAgent),Pi=j.inputHandler.of((e,O,t,a,r)=>{if(($i?e.composing:e.compositionStarted)||e.state.readOnly||O!=t||a!=">"&&a!="/"||!y.isActiveAt(e.state,O,-1))return!1;let s=r(),{state:i}=s,l=i.changeByRange(o=>{var c;let{head:h}=o,Q=z(i).resolveInner(h-1,-1),f;if(Q.name=="JSXStartTag"&&(Q=Q.parent),!(i.doc.sliceString(h-1,h)!=a||Q.name=="JSXAttributeValue"&&Q.to>h)){if(a==">"&&Q.name=="JSXFragmentTag")return{range:o,changes:{from:h,insert:"</>"}};if(a=="/"&&Q.name=="JSXStartCloseTag"){let p=Q.parent,d=p.parent;if(d&&p.from==h-2&&((f=Se(i.doc,d.firstChild,h))||((c=d.firstChild)===null||c===void 0?void 0:c.name)=="JSXFragmentTag")){let P=`${f}>`;return{range:We.cu Line truncated
`;function Zt(e,O,t){let a=Object.create(null);a.true=a.false=ji,a.null=a.unknown=Yi;for(let r of e.split(" "))r&&(a[r]=mt);for(let r of O.split(" "))r&&(a[r]=gt);for(let r of(t||"").split(" "))r&&(a[r]=Di);return a}const Os="array binary bit boolean char character clob date decimal double float int integer interval large national nchar nclob numeric object precision real smallint time timestamp varchar varying ",es="absolute action add after all allocate alter and any are as asc assertion at authorization before begin between both breadth by call cascade cascaded case cast catalog check close collate collation column commit condition connect connection constraint constraints constructor continue corresponding count create cross cube current current_date current_default_transform_group current_transform_group_for_type current_path current_role current_time current_timestamp current_user cursor cycle data day deallocate declare default deferrable deferred delete depth deref desc describe descriptor deterministic diagnostics disconnect distinct do domain drop dynamic each else elseif end end-exec equals escape except exception exec execute exists exit external fetch first for foreign found from free full function general get global go goto grant group grouping handle having hold hour identity if immediate in indicator initially inner inout input insert intersect into is isolation join key language last lateral leading leave left level like limit local localtime localtimestamp locator loop map match method minute modifies module month names natural nesting new next no none not of old on only open option or order ordinality out outer output overlaps pad parameter partial path prepare preserve primary prior privileges procedure public read reads recursive redo ref references referencing relative release repeat resignal restrict result return returns revoke right role rollback rollup routine row rows savepoint schema scroll search second section select session session_user set sets signal similar size some space specific specifictype sql sqlexception sqlstate sqlwarning start state static system_user table temporary then timezone_hour timezone_minute to trailing transaction translation treat trigger under undo union unique unnest until update usage user using value values view when whenever where while with without work write year zone ",RO={backslashEscapes:!1,hashComments:!1,spaceAfterDashes:!1,slashComments:!1,doubleQuotedStrings:!1,doubleDollarQuotedStrings:!1,unquotedBitLiterals:!1,treatBitsAsBytes:!1,charSetCasts:!1,plsqlQuotingMechanism:!1,operatorChars:"*+-%<>!=&|~^/",specialVar:"?",identifierQuotes:'"',caseInsensitiveIdentifiers:!1,words:Zt(es,Os)};function ts(e,O,t,a){let r={};for(let s in RO)r[s]=(e.hasOwnProperty(s)?e:RO)[s];return O&&(r.words=Zt(O,t||"",a)),r}function bt(e){return new x(O=>{var t;let{next:a}=O;if(O.advance(),R(a,kO)){for(;R(O.next,kO);)O.advance();O.acceptToken(Ri)}else if(a==36&&e.doubleDollarQuotedStrings){let r=qO(O,"");O.next==36&&(O.advance(),Ki(O,r),O.acceptToken(V))}else if(a==39||a==34&&e.doubleQuotedStrings)_(O,a,e.backslashEscapes),O.acceptToken(V);else if(a==35&&e.hashComments||a==47&&O.next==47&&e.slashComments)we(O),O.acceptToken(be);else if(a==45&&O.next==45&&(!e.spaceAfterDashes||O.peek(1)==32))we(O),O.acceptToken(be);else if(a==47&&O.next==42){O.advance();for(let r=1;;){let s=O.next;if(O.next<0)break;if(O.advance(),s==42&&O.next==47){if(r--,O.advance(),!r)break}else s==47&&O.next==42&&(r++,O.advance())}O.acceptToken(_i)}else if((a==101||a==69)&&O.next==39)O.advance(),_(O,39,!0),O.acceptToken(V);else if((a==110||a==78)&&O.next==39&&e.charSetCasts)O.advance(),_(O,39,e.backslashEscapes),O.acceptToken(V);else if(a==95&&e.charSetCasts)for(let r=0;;r++){if(O.next==39&&r>1){O.advance(),_(O,39,e.backslashEscapes),O.acceptToken(V);break}if(!vO(O.next))break;O.advance()}else if(e.plsqlQuotingMechanism&&(a==113||a==81)&&O.next==39&&O.peek(1)>0&&!R(O.peek(1),kO)){let r=O.peek(1);O.advance(2),Fi(O,r),O.acceptToken(V)}else if(a==40)O.acceptToken(Vi);else if(a==41)O.acceptToken(Wi);else if(a==123)O.acceptToken(Ui);else if(a==125)O.acceptToken(Gi);else if(a==91)O.acceptToken(zi);else if(a==93)O.acceptToken(Ci);else if(a==59)O.acceptToken(Ei);else if(e.unquotedBitLiterals&&a==48&&O.next==98)O.advance(),Xe(O),O.acceptToken(ke);else if((a==98||a==66)&&(O.next==39||O.next==34)){const r=O.next;O.advance(),e.treatBitsAsBytes?(_(O,r,e.backslashEscapes),O.acceptToken(Ii)):(Xe(O,r),O.acceptToken(ke))}else if(a==48&&(O.next==120||O.next==88)||(a==120||a==88)&&O.next==39){let r=O.next==39;for(O.advance();Ji(O.next);)O.advance();r&&O.next==39&&O.advance(),O.acceptToken(xO)}else if(a==46&&O.next>=48&&O.next<=57)ye(O,!0),O.acceptToken(xO);else if(a==46)O.acceptToken(Ai);else if(a>=48&&a<=57)ye(O,!1),O.acceptToken(xO);else if(R(a,e.operatorChars)){for(;R(O.next,e.operatorChars);)O.advance();O.acceptToken(Mi)}else if(R(a,e.specialVar))O.next==a&&O.advance(),Hi(O),O.acceptToken(Bi);else if(R(a,e.ide Line truncated
@@ -1,4 +1,4 @@
import{S as $t,i as qt,s as St,V as Tt,J as ee,X as ue,W as Ct,h as s,z as _,j as p,c as $e,k as w,n as r,o as i,m as qe,H as oe,Y as Ve,Z as pt,E as Ot,_ as Mt,G as Lt,t as ye,a as ve,v as d,d as Se,p as Pt,l as Te,q as Ft,I as we,L as Ht}from"./index-Bpzzn7x2.js";import{F as Rt}from"./FieldsQueryParam-CrrI0KIL.js";function mt(a,e,t){const l=a.slice();return l[10]=e[t],l}function bt(a,e,t){const l=a.slice();return l[10]=e[t],l}function _t(a,e,t){const l=a.slice();return l[15]=e[t],l}function kt(a){let e;return{c(){e=s("p"),e.innerHTML="Requires superuser <code>Authorization:TOKEN</code> header",w(e,"class","txt-hint txt-sm txt-right")},m(t,l){r(t,e,l)},d(t){t&&d(e)}}}function ht(a){let e,t,l,u,c,f,b,m,$,h,g,A,T,O,R,M,I,J,S,Q,L,q,k,P,te,Y,U,re,G,K,X;function fe(y,C){var V,W,H;return C&1&&(f=null),f==null&&(f=!!((H=(W=(V=y[0])==null?void 0:V.fields)==null?void 0:W.find(Kt))!=null&&H.required)),f?At:jt}let le=fe(a,-1),E=le(a);function Z(y,C){var V,W,H;return C&1&&(I=null),I==null&&(I=!!((H=(W=(V=y[0])==null?void 0:V.fields)==null?void 0:W.find(Gt))!=null&&H.required)),I?Vt:Bt}let x=Z(a,-1),F=x(a);return{c(){e=s("tr"),e.innerHTML='<td colspan="3" class="txt-hint txt-bold">Auth specific fields</td>',t=p(),l=s("tr"),u=s("td"),c=s("div"),E.c(),b=p(),m=s("span"),m.textContent="email",$=p(),h=s("td"),h.innerHTML='<span class="label">String</span>',g=p(),A=s("td"),A.textContent="Auth record email address.",T=p(),O=s("tr"),R=s("td"),M=s("div"),F.c(),J=p(),S=s("span"),S.textContent="emailVisibility",Q=p(),L=s("td"),L.innerHTML='<span class="label">Boolean</span>',q=p(),k=s("td"),k.textContent="Whether to show/hide the auth record email when fetching the record data.",P=p(),te=s("tr"),te.innerHTML='<td><div class="inline-flex"><span class="label label-success">Required</span> <span>password</span></div></td> <td><span class="label">String</span></td> <td>Auth record password.</td>',Y=p(),U=s("tr"),U.innerHTML='<td><div class="inline-flex"><span class="label label-success">Required</span> <span>passwordConfirm</span></div></td> <td><span class="label">String</span></td> <td>Auth record password confirmation.</td>',re=p(),G=s("tr"),G.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>verified</span></div></td> <td><span class="label">Boolean</span></td> <td>Indicates whether the auth record is verified or not.
import{S as $t,i as qt,s as St,V as Tt,J as ee,X as ue,W as Ct,h as s,z as _,j as p,c as $e,k as w,n as r,o as i,m as qe,H as oe,Y as Ve,Z as pt,E as Ot,_ as Mt,G as Lt,t as ye,a as ve,v as d,d as Se,p as Pt,l as Te,q as Ft,I as we,L as Ht}from"./index-0HOqdotm.js";import{F as Rt}from"./FieldsQueryParam-DT9Tnt7Y.js";function mt(a,e,t){const l=a.slice();return l[10]=e[t],l}function bt(a,e,t){const l=a.slice();return l[10]=e[t],l}function _t(a,e,t){const l=a.slice();return l[15]=e[t],l}function kt(a){let e;return{c(){e=s("p"),e.innerHTML="Requires superuser <code>Authorization:TOKEN</code> header",w(e,"class","txt-hint txt-sm txt-right")},m(t,l){r(t,e,l)},d(t){t&&d(e)}}}function ht(a){let e,t,l,u,c,f,b,m,$,h,g,A,T,O,R,M,I,J,S,Q,L,q,k,P,te,Y,U,re,G,K,X;function fe(y,C){var V,W,H;return C&1&&(f=null),f==null&&(f=!!((H=(W=(V=y[0])==null?void 0:V.fields)==null?void 0:W.find(Kt))!=null&&H.required)),f?At:jt}let le=fe(a,-1),E=le(a);function Z(y,C){var V,W,H;return C&1&&(I=null),I==null&&(I=!!((H=(W=(V=y[0])==null?void 0:V.fields)==null?void 0:W.find(Gt))!=null&&H.required)),I?Vt:Bt}let x=Z(a,-1),F=x(a);return{c(){e=s("tr"),e.innerHTML='<td colspan="3" class="txt-hint txt-bold">Auth specific fields</td>',t=p(),l=s("tr"),u=s("td"),c=s("div"),E.c(),b=p(),m=s("span"),m.textContent="email",$=p(),h=s("td"),h.innerHTML='<span class="label">String</span>',g=p(),A=s("td"),A.textContent="Auth record email address.",T=p(),O=s("tr"),R=s("td"),M=s("div"),F.c(),J=p(),S=s("span"),S.textContent="emailVisibility",Q=p(),L=s("td"),L.innerHTML='<span class="label">Boolean</span>',q=p(),k=s("td"),k.textContent="Whether to show/hide the auth record email when fetching the record data.",P=p(),te=s("tr"),te.innerHTML='<td><div class="inline-flex"><span class="label label-success">Required</span> <span>password</span></div></td> <td><span class="label">String</span></td> <td>Auth record password.</td>',Y=p(),U=s("tr"),U.innerHTML='<td><div class="inline-flex"><span class="label label-success">Required</span> <span>passwordConfirm</span></div></td> <td><span class="label">String</span></td> <td>Auth record password confirmation.</td>',re=p(),G=s("tr"),G.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>verified</span></div></td> <td><span class="label">Boolean</span></td> <td>Indicates whether the auth record is verified or not.
<br/>
This field can be set only by superusers or auth records with &quot;Manage&quot; access.</td>`,K=p(),X=s("tr"),X.innerHTML='<td colspan="3" class="txt-hint txt-bold">Other fields</td>',w(c,"class","inline-flex"),w(M,"class","inline-flex")},m(y,C){r(y,e,C),r(y,t,C),r(y,l,C),i(l,u),i(u,c),E.m(c,null),i(c,b),i(c,m),i(l,$),i(l,h),i(l,g),i(l,A),r(y,T,C),r(y,O,C),i(O,R),i(R,M),F.m(M,null),i(M,J),i(M,S),i(O,Q),i(O,L),i(O,q),i(O,k),r(y,P,C),r(y,te,C),r(y,Y,C),r(y,U,C),r(y,re,C),r(y,G,C),r(y,K,C),r(y,X,C)},p(y,C){le!==(le=fe(y,C))&&(E.d(1),E=le(y),E&&(E.c(),E.m(c,b))),x!==(x=Z(y,C))&&(F.d(1),F=x(y),F&&(F.c(),F.m(M,J)))},d(y){y&&(d(e),d(t),d(l),d(T),d(O),d(P),d(te),d(Y),d(U),d(re),d(G),d(K),d(X)),E.d(),F.d()}}}function jt(a){let e;return{c(){e=s("span"),e.textContent="Optional",w(e,"class","label label-warning")},m(t,l){r(t,e,l)},d(t){t&&d(e)}}}function At(a){let e;return{c(){e=s("span"),e.textContent="Required",w(e,"class","label label-success")},m(t,l){r(t,e,l)},d(t){t&&d(e)}}}function Bt(a){let e;return{c(){e=s("span"),e.textContent="Optional",w(e,"class","label label-warning")},m(t,l){r(t,e,l)},d(t){t&&d(e)}}}function Vt(a){let e;return{c(){e=s("span"),e.textContent="Required",w(e,"class","label label-success")},m(t,l){r(t,e,l)},d(t){t&&d(e)}}}function Nt(a){let e;return{c(){e=s("span"),e.textContent="Required",w(e,"class","label label-success")},m(t,l){r(t,e,l)},d(t){t&&d(e)}}}function Dt(a){let e;return{c(){e=s("span"),e.textContent="Optional",w(e,"class","label label-warning")},m(t,l){r(t,e,l)},d(t){t&&d(e)}}}function Jt(a){let e,t=a[15].maxSelect===1?"id":"ids",l,u;return{c(){e=_("Relation record "),l=_(t),u=_(".")},m(c,f){r(c,e,f),r(c,l,f),r(c,u,f)},p(c,f){f&64&&t!==(t=c[15].maxSelect===1?"id":"ids")&&oe(l,t)},d(c){c&&(d(e),d(l),d(u))}}}function Et(a){let e,t,l,u,c,f,b,m,$;return{c(){e=_("File object."),t=s("br"),l=_(`
Set to empty value (`),u=s("code"),u.textContent="null",c=_(", "),f=s("code"),f.textContent='""',b=_(" or "),m=s("code"),m.textContent="[]",$=_(`) to delete
@@ -35,7 +35,7 @@ await pb.collection('${(rt=a[0])==null?void 0:rt.name}').requestVerification('te
<code>multipart/form-data</code>.`,M=p(),I=s("p"),I.innerHTML=`File upload is supported only via <code>multipart/form-data</code>.
<br/>
For more info and examples you could check the detailed
<a href="https://pocketbase.io/docs/files-handling/" target="_blank" rel="noopener noreferrer">Files upload and handling docs
<a href="https://pocketbase.io/docs/files-handling" target="_blank" rel="noopener noreferrer">Files upload and handling docs
</a>.`,J=p(),$e(S.$$.fragment),Q=p(),L=s("h6"),L.textContent="API details",q=p(),k=s("div"),P=s("strong"),P.textContent="POST",te=p(),Y=s("div"),U=s("p"),re=_("/api/collections/"),G=s("strong"),X=_(K),fe=_("/records"),le=p(),D&&D.c(),E=p(),Z=s("div"),Z.textContent="Body Parameters",x=p(),F=s("table"),y=s("thead"),y.innerHTML='<tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr>',C=p(),V=s("tbody"),B&&B.c(),W=p();for(let n=0;n<H.length;n+=1)H[n].c();Oe=p(),pe=s("div"),pe.textContent="Query parameters",Me=p(),ne=s("table"),Le=s("thead"),Le.innerHTML='<tr><th>Param</th> <th>Type</th> <th width="60%">Description</th></tr>',De=p(),me=s("tbody"),ie=s("tr"),Pe=s("td"),Pe.textContent="expand",Je=p(),Fe=s("td"),Fe.innerHTML='<span class="label">String</span>',Ee=p(),j=s("td"),Ie=_(`Auto expand relations when returning the created record. Ex.:
`),$e(de.$$.fragment),Ue=_(`
Supports up to 6-levels depth nested relations expansion. `),ze=s("br"),Qe=_(`
@@ -1,4 +1,4 @@
import{S as Re,i as Ee,s as Pe,V as Te,X as U,h as c,z as y,j as k,c as De,k as m,n as p,o as i,m as Ce,H as ee,Y as he,Z as Be,E as Oe,_ as Ie,G as qe,t as te,a as le,v as f,d as we,J as Ae,p as Me,l as z,q as He,W as Le}from"./index-Bpzzn7x2.js";function ke(a,l,s){const o=a.slice();return o[6]=l[s],o}function ge(a,l,s){const o=a.slice();return o[6]=l[s],o}function ve(a){let l;return{c(){l=c("p"),l.innerHTML="Requires superuser <code>Authorization:TOKEN</code> header",m(l,"class","txt-hint txt-sm txt-right")},m(s,o){p(s,l,o)},d(s){s&&f(l)}}}function ye(a,l){let s,o,h;function r(){return l[5](l[6])}return{key:a,first:null,c(){s=c("button"),s.textContent=`${l[6].code} `,m(s,"class","tab-item"),z(s,"active",l[2]===l[6].code),this.first=s},m(n,d){p(n,s,d),o||(h=He(s,"click",r),o=!0)},p(n,d){l=n,d&20&&z(s,"active",l[2]===l[6].code)},d(n){n&&f(s),o=!1,h()}}}function $e(a,l){let s,o,h,r;return o=new Le({props:{content:l[6].body}}),{key:a,first:null,c(){s=c("div"),De(o.$$.fragment),h=k(),m(s,"class","tab-item"),z(s,"active",l[2]===l[6].code),this.first=s},m(n,d){p(n,s,d),Ce(o,s,null),i(s,h),r=!0},p(n,d){l=n,(!r||d&20)&&z(s,"active",l[2]===l[6].code)},i(n){r||(te(o.$$.fragment,n),r=!0)},o(n){le(o.$$.fragment,n),r=!1},d(n){n&&f(s),we(o)}}}function Se(a){var fe,me;let l,s,o=a[0].name+"",h,r,n,d,$,D,F,M=a[0].name+"",G,se,J,C,K,P,N,g,H,ae,L,E,oe,V,S=a[0].name+"",W,ne,X,ie,Y,T,Z,B,Q,O,x,w,I,v=[],ce=new Map,re,q,b=[],de=new Map,R;C=new Te({props:{js:`
import{S as Re,i as Ee,s as Pe,V as Te,X as U,h as c,z as y,j as k,c as De,k as m,n as p,o as i,m as Ce,H as ee,Y as he,Z as Be,E as Oe,_ as Ie,G as qe,t as te,a as le,v as f,d as we,J as Ae,p as Me,l as z,q as He,W as Le}from"./index-0HOqdotm.js";function ke(a,l,s){const o=a.slice();return o[6]=l[s],o}function ge(a,l,s){const o=a.slice();return o[6]=l[s],o}function ve(a){let l;return{c(){l=c("p"),l.innerHTML="Requires superuser <code>Authorization:TOKEN</code> header",m(l,"class","txt-hint txt-sm txt-right")},m(s,o){p(s,l,o)},d(s){s&&f(l)}}}function ye(a,l){let s,o,h;function r(){return l[5](l[6])}return{key:a,first:null,c(){s=c("button"),s.textContent=`${l[6].code} `,m(s,"class","tab-item"),z(s,"active",l[2]===l[6].code),this.first=s},m(n,d){p(n,s,d),o||(h=He(s,"click",r),o=!0)},p(n,d){l=n,d&20&&z(s,"active",l[2]===l[6].code)},d(n){n&&f(s),o=!1,h()}}}function $e(a,l){let s,o,h,r;return o=new Le({props:{content:l[6].body}}),{key:a,first:null,c(){s=c("div"),De(o.$$.fragment),h=k(),m(s,"class","tab-item"),z(s,"active",l[2]===l[6].code),this.first=s},m(n,d){p(n,s,d),Ce(o,s,null),i(s,h),r=!0},p(n,d){l=n,(!r||d&20)&&z(s,"active",l[2]===l[6].code)},i(n){r||(te(o.$$.fragment,n),r=!0)},o(n){le(o.$$.fragment,n),r=!1},d(n){n&&f(s),we(o)}}}function Se(a){var fe,me;let l,s,o=a[0].name+"",h,r,n,d,$,D,F,M=a[0].name+"",G,se,J,C,K,P,N,g,H,ae,L,E,oe,V,S=a[0].name+"",W,ne,X,ie,Y,T,Z,B,Q,O,x,w,I,v=[],ce=new Map,re,q,b=[],de=new Map,R;C=new Te({props:{js:`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${a[3]}');
@@ -1,4 +1,4 @@
import{S as se,i as oe,s as ie,X as I,h as p,j as C,z as U,k as b,n as g,o as u,H as F,Y as le,Z as Re,E as ne,_ as Se,G as ae,t as V,a as X,v,l as K,q as ce,W as Oe,c as x,m as ee,d as te,V as Me,$ as _e,J as Be,p as De,a0 as be}from"./index-Bpzzn7x2.js";function ge(n,e,t){const l=n.slice();return l[4]=e[t],l}function ve(n,e,t){const l=n.slice();return l[4]=e[t],l}function ke(n,e){let t,l=e[4].code+"",d,i,r,a;function m(){return e[3](e[4])}return{key:n,first:null,c(){t=p("button"),d=U(l),i=C(),b(t,"class","tab-item"),K(t,"active",e[1]===e[4].code),this.first=t},m(k,A){g(k,t,A),u(t,d),u(t,i),r||(a=ce(t,"click",m),r=!0)},p(k,A){e=k,A&4&&l!==(l=e[4].code+"")&&F(d,l),A&6&&K(t,"active",e[1]===e[4].code)},d(k){k&&v(t),r=!1,a()}}}function $e(n,e){let t,l,d,i;return l=new Oe({props:{content:e[4].body}}),{key:n,first:null,c(){t=p("div"),x(l.$$.fragment),d=C(),b(t,"class","tab-item"),K(t,"active",e[1]===e[4].code),this.first=t},m(r,a){g(r,t,a),ee(l,t,null),u(t,d),i=!0},p(r,a){e=r;const m={};a&4&&(m.content=e[4].body),l.$set(m),(!i||a&6)&&K(t,"active",e[1]===e[4].code)},i(r){i||(V(l.$$.fragment,r),i=!0)},o(r){X(l.$$.fragment,r),i=!1},d(r){r&&v(t),te(l)}}}function He(n){let e,t,l,d,i,r,a,m=n[0].name+"",k,A,Y,W,J,L,z,B,D,S,H,q=[],O=new Map,P,j,T=[],N=new Map,w,E=I(n[2]);const M=c=>c[4].code;for(let c=0;c<E.length;c+=1){let f=ve(n,E,c),s=M(f);O.set(s,q[c]=ke(s,f))}let _=I(n[2]);const Z=c=>c[4].code;for(let c=0;c<_.length;c+=1){let f=ge(n,_,c),s=Z(f);N.set(s,T[c]=$e(s,f))}return{c(){e=p("div"),t=p("strong"),t.textContent="POST",l=C(),d=p("div"),i=p("p"),r=U("/api/collections/"),a=p("strong"),k=U(m),A=U("/confirm-email-change"),Y=C(),W=p("div"),W.textContent="Body Parameters",J=C(),L=p("table"),L.innerHTML='<thead><tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr></thead> <tbody><tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>token</span></div></td> <td><span class="label">String</span></td> <td>The token from the change email request email.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>password</span></div></td> <td><span class="label">String</span></td> <td>The account password to confirm the email change.</td></tr></tbody>',z=C(),B=p("div"),B.textContent="Responses",D=C(),S=p("div"),H=p("div");for(let c=0;c<q.length;c+=1)q[c].c();P=C(),j=p("div");for(let c=0;c<T.length;c+=1)T[c].c();b(t,"class","label label-primary"),b(d,"class","content"),b(e,"class","alert alert-success"),b(W,"class","section-title"),b(L,"class","table-compact table-border m-b-base"),b(B,"class","section-title"),b(H,"class","tabs-header compact combined left"),b(j,"class","tabs-content"),b(S,"class","tabs")},m(c,f){g(c,e,f),u(e,t),u(e,l),u(e,d),u(d,i),u(i,r),u(i,a),u(a,k),u(i,A),g(c,Y,f),g(c,W,f),g(c,J,f),g(c,L,f),g(c,z,f),g(c,B,f),g(c,D,f),g(c,S,f),u(S,H);for(let s=0;s<q.length;s+=1)q[s]&&q[s].m(H,null);u(S,P),u(S,j);for(let s=0;s<T.length;s+=1)T[s]&&T[s].m(j,null);w=!0},p(c,[f]){(!w||f&1)&&m!==(m=c[0].name+"")&&F(k,m),f&6&&(E=I(c[2]),q=le(q,f,M,1,c,E,O,H,Re,ke,null,ve)),f&6&&(_=I(c[2]),ne(),T=le(T,f,Z,1,c,_,N,j,Se,$e,null,ge),ae())},i(c){if(!w){for(let f=0;f<_.length;f+=1)V(T[f]);w=!0}},o(c){for(let f=0;f<T.length;f+=1)X(T[f]);w=!1},d(c){c&&(v(e),v(Y),v(W),v(J),v(L),v(z),v(B),v(D),v(S));for(let f=0;f<q.length;f+=1)q[f].d();for(let f=0;f<T.length;f+=1)T[f].d()}}}function Ne(n,e,t){let{collection:l}=e,d=204,i=[];const r=a=>t(1,d=a.code);return n.$$set=a=>{"collection"in a&&t(0,l=a.collection)},t(2,i=[{code:204,body:"null"},{code:400,body:`
import{S as se,i as oe,s as ie,X as I,h as p,j as C,z as U,k as b,n as g,o as u,H as F,Y as le,Z as Re,E as ne,_ as Se,G as ae,t as V,a as X,v,l as K,q as ce,W as Oe,c as x,m as ee,d as te,V as Me,$ as _e,J as Be,p as De,a0 as be}from"./index-0HOqdotm.js";function ge(n,e,t){const l=n.slice();return l[4]=e[t],l}function ve(n,e,t){const l=n.slice();return l[4]=e[t],l}function ke(n,e){let t,l=e[4].code+"",d,i,r,a;function m(){return e[3](e[4])}return{key:n,first:null,c(){t=p("button"),d=U(l),i=C(),b(t,"class","tab-item"),K(t,"active",e[1]===e[4].code),this.first=t},m(k,A){g(k,t,A),u(t,d),u(t,i),r||(a=ce(t,"click",m),r=!0)},p(k,A){e=k,A&4&&l!==(l=e[4].code+"")&&F(d,l),A&6&&K(t,"active",e[1]===e[4].code)},d(k){k&&v(t),r=!1,a()}}}function $e(n,e){let t,l,d,i;return l=new Oe({props:{content:e[4].body}}),{key:n,first:null,c(){t=p("div"),x(l.$$.fragment),d=C(),b(t,"class","tab-item"),K(t,"active",e[1]===e[4].code),this.first=t},m(r,a){g(r,t,a),ee(l,t,null),u(t,d),i=!0},p(r,a){e=r;const m={};a&4&&(m.content=e[4].body),l.$set(m),(!i||a&6)&&K(t,"active",e[1]===e[4].code)},i(r){i||(V(l.$$.fragment,r),i=!0)},o(r){X(l.$$.fragment,r),i=!1},d(r){r&&v(t),te(l)}}}function He(n){let e,t,l,d,i,r,a,m=n[0].name+"",k,A,Y,W,J,L,z,B,D,S,H,q=[],O=new Map,P,j,T=[],N=new Map,w,E=I(n[2]);const M=c=>c[4].code;for(let c=0;c<E.length;c+=1){let f=ve(n,E,c),s=M(f);O.set(s,q[c]=ke(s,f))}let _=I(n[2]);const Z=c=>c[4].code;for(let c=0;c<_.length;c+=1){let f=ge(n,_,c),s=Z(f);N.set(s,T[c]=$e(s,f))}return{c(){e=p("div"),t=p("strong"),t.textContent="POST",l=C(),d=p("div"),i=p("p"),r=U("/api/collections/"),a=p("strong"),k=U(m),A=U("/confirm-email-change"),Y=C(),W=p("div"),W.textContent="Body Parameters",J=C(),L=p("table"),L.innerHTML='<thead><tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr></thead> <tbody><tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>token</span></div></td> <td><span class="label">String</span></td> <td>The token from the change email request email.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>password</span></div></td> <td><span class="label">String</span></td> <td>The account password to confirm the email change.</td></tr></tbody>',z=C(),B=p("div"),B.textContent="Responses",D=C(),S=p("div"),H=p("div");for(let c=0;c<q.length;c+=1)q[c].c();P=C(),j=p("div");for(let c=0;c<T.length;c+=1)T[c].c();b(t,"class","label label-primary"),b(d,"class","content"),b(e,"class","alert alert-success"),b(W,"class","section-title"),b(L,"class","table-compact table-border m-b-base"),b(B,"class","section-title"),b(H,"class","tabs-header compact combined left"),b(j,"class","tabs-content"),b(S,"class","tabs")},m(c,f){g(c,e,f),u(e,t),u(e,l),u(e,d),u(d,i),u(i,r),u(i,a),u(a,k),u(i,A),g(c,Y,f),g(c,W,f),g(c,J,f),g(c,L,f),g(c,z,f),g(c,B,f),g(c,D,f),g(c,S,f),u(S,H);for(let s=0;s<q.length;s+=1)q[s]&&q[s].m(H,null);u(S,P),u(S,j);for(let s=0;s<T.length;s+=1)T[s]&&T[s].m(j,null);w=!0},p(c,[f]){(!w||f&1)&&m!==(m=c[0].name+"")&&F(k,m),f&6&&(E=I(c[2]),q=le(q,f,M,1,c,E,O,H,Re,ke,null,ve)),f&6&&(_=I(c[2]),ne(),T=le(T,f,Z,1,c,_,N,j,Se,$e,null,ge),ae())},i(c){if(!w){for(let f=0;f<_.length;f+=1)V(T[f]);w=!0}},o(c){for(let f=0;f<T.length;f+=1)X(T[f]);w=!1},d(c){c&&(v(e),v(Y),v(W),v(J),v(L),v(z),v(B),v(D),v(S));for(let f=0;f<q.length;f+=1)q[f].d();for(let f=0;f<T.length;f+=1)T[f].d()}}}function Ne(n,e,t){let{collection:l}=e,d=204,i=[];const r=a=>t(1,d=a.code);return n.$$set=a=>{"collection"in a&&t(0,l=a.collection)},t(2,i=[{code:204,body:"null"},{code:400,body:`
{
"code": 400,
"message": "An error occurred while validating the submitted data.",
@@ -1,4 +1,4 @@
import{S as I,i as J,s as N,W as O,h as t,j as c,z as i,c as P,k as Q,n as R,o as e,m as W,H as A,t as D,a as G,v as K,d as U}from"./index-Bpzzn7x2.js";function V(f){let n,o,u,d,v,s,p,y,g,F,r,S,_,w,b,E,C,a,$,H,L,q,M,T,m,j,k,z,x;return r=new O({props:{content:"?fields=*,"+f[0]+"expand.relField.name"}}),{c(){n=t("tr"),o=t("td"),o.textContent="fields",u=c(),d=t("td"),d.innerHTML='<span class="label">String</span>',v=c(),s=t("td"),p=t("p"),y=i(`Comma separated string of the fields to return in the JSON response
import{S as I,i as J,s as N,W as O,h as t,j as c,z as i,c as P,k as Q,n as R,o as e,m as W,H as A,t as D,a as G,v as K,d as U}from"./index-0HOqdotm.js";function V(f){let n,o,u,d,v,s,p,y,g,F,r,S,_,w,b,E,C,a,$,H,L,q,M,T,m,j,k,z,x;return r=new O({props:{content:"?fields=*,"+f[0]+"expand.relField.name"}}),{c(){n=t("tr"),o=t("td"),o.textContent="fields",u=c(),d=t("td"),d.innerHTML='<span class="label">String</span>',v=c(),s=t("td"),p=t("p"),y=i(`Comma separated string of the fields to return in the JSON response
`),g=t("em"),g.textContent="(by default returns all fields)",F=i(`. Ex.:
`),P(r.$$.fragment),S=c(),_=t("p"),_.innerHTML="<code>*</code> targets all keys from the specific depth level.",w=c(),b=t("p"),b.textContent="In addition, the following field modifiers are also supported:",E=c(),C=t("ul"),a=t("li"),$=t("code"),$.textContent=":excerpt(maxLength, withEllipsis?)",H=c(),L=t("br"),q=i(`
Returns a short plain text version of the field string value.
-1
View File
@@ -1 +0,0 @@
import{S as $,i as ee,s as te,h as ne,k as re,n as ie,I,v as ae,O as oe,T as le,U as se,Q as de,J as u,y as ce}from"./index-Bpzzn7x2.js";import{c as fe,d as ue,s as ge,h as he,a as ye,E,b as S,e as pe,f as ke,g as me,i as xe,j as be,k as we,l as Ee,m as Se,r as Ke,n as Ce,o as Re,p as Le,q as Q,C as R,S as qe,t as ve,u as Oe,v as We}from"./index-CH-gyH77.js";function _e(e){return new Worker(""+new URL("autocomplete.worker-BdIjQwYp.js",import.meta.url).href,{name:e==null?void 0:e.name})}function Ie(e){j(e,"start");var r={},t=e.languageData||{},g=!1;for(var h in e)if(h!=t&&e.hasOwnProperty(h))for(var f=r[h]=[],a=e[h],i=0;i<a.length;i++){var o=a[i];f.push(new Ae(o,e)),(o.indent||o.dedent)&&(g=!0)}return{name:t.name,startState:function(){return{state:"start",pending:null,indent:g?[]:null}},copyState:function(s){var y={state:s.state,pending:s.pending,indent:s.indent&&s.indent.slice(0)};return s.stack&&(y.stack=s.stack.slice(0)),y},token:Be(r),indent:Me(r,t),languageData:t}}function j(e,r){if(!e.hasOwnProperty(r))throw new Error("Undefined state "+r+" in simple mode")}function De(e,r){if(!e)return/(?:)/;var t="";return e instanceof RegExp?(e.ignoreCase&&(t="i"),e=e.source):e=String(e),new RegExp("^(?:"+e+")",t)}function Je(e){if(!e)return null;if(e.apply)return e;if(typeof e=="string")return e.replace(/\./g," ");for(var r=[],t=0;t<e.length;t++)r.push(e[t]&&e[t].replace(/\./g," "));return r}function Ae(e,r){(e.next||e.push)&&j(r,e.next||e.push),this.regex=De(e.regex),this.token=Je(e.token),this.data=e}function Be(e){return function(r,t){if(t.pending){var g=t.pending.shift();return t.pending.length==0&&(t.pending=null),r.pos+=g.text.length,g.token}for(var h=e[t.state],f=0;f<h.length;f++){var a=h[f],i=(!a.data.sol||r.sol())&&r.match(a.regex);if(i){a.data.next?t.state=a.data.next:a.data.push?((t.stack||(t.stack=[])).push(t.state),t.state=a.data.push):a.data.pop&&t.stack&&t.stack.length&&(t.state=t.stack.pop()),a.data.indent&&t.indent.push(r.indentation()+r.indentUnit),a.data.dedent&&t.indent.pop();var o=a.token;if(o&&o.apply&&(o=o(i)),i.length>2&&a.token&&typeof a.token!="string"){t.pending=[];for(var s=2;s<i.length;s++)i[s]&&t.pending.push({text:i[s],token:a.token[s-1]});return r.backUp(i[0].length-(i[1]?i[1].length:0)),o[0]}else return o&&o.join?o[0]:o}}return r.next(),null}}function Me(e,r){return function(t,g){if(t.indent==null||r.dontIndentStates&&r.doneIndentState.indexOf(t.state)>-1)return null;var h=t.indent.length-1,f=e[t.state];e:for(;;){for(var a=0;a<f.length;a++){var i=f[a];if(i.data.dedent&&i.data.dedentIfLineStart!==!1){var o=i.regex.exec(g);if(o&&o[0]){h--,(i.next||i.push)&&(f=e[i.next||i.push]),g=g.slice(o[0].length);continue e}}}break}return h<0?0:t.indent[h]}}function Te(e){let r;return{c(){r=ne("div"),re(r,"class","code-editor")},m(t,g){ie(t,r,g),e[15](r)},p:I,i:I,o:I,d(t){t&&ae(r),e[15](null)}}}function He(e){return JSON.stringify([e==null?void 0:e.name,e==null?void 0:e.type,e==null?void 0:e.fields])}function Ue(e,r,t){let g;oe(e,le,n=>t(21,g=n));const h=se();let{id:f=""}=r,{value:a=""}=r,{disabled:i=!1}=r,{placeholder:o=""}=r,{baseCollection:s=null}=r,{singleLine:y=!1}=r,{extraAutocompleteKeys:L=[]}=r,{disableRequestKeys:b=!1}=r,{disableCollectionJoinKeys:m=!1}=r,d,p,q=i,D=new R,J=new R,A=new R,B=new R,v=new _e,M=[],T=[],H=[],K="",O="";function W(){d==null||d.focus()}let _=null;v.onmessage=n=>{H=n.data.baseKeys||[],M=n.data.requestKeys||[],T=n.data.collectionJoinKeys||[]};function G(){clearTimeout(_),_=setTimeout(()=>{v.postMessage({baseCollection:s,collections:V(g),disableRequestKeys:b,disableCollectionJoinKeys:m})},250)}function V(n){let c=n.slice();return s&&u.pushOrReplaceByKey(c,s,"id"),c}function U(){p==null||p.dispatchEvent(new CustomEvent("change",{detail:{value:a},bubbles:!0}))}function F(){if(!f)return;const n=document.querySelectorAll('[for="'+f+'"]');for(let c of n)c.removeEventListener("click",W)}function N(){if(!f)return;F();const n=document.querySelectorAll('[for="'+f+'"]');for(let c of n)c.addEventListener("click",W)}function Y(n=!0,c=!0){let l=[].concat(L);return l=l.concat(H||[]),n&&(l=l.concat(M||[])),c&&(l=l.concat(T||[])),l}function z(n){var w;let c=n.matchBefore(/[\'\"\@\w\.]*/);if(c&&c.from==c.to&&!n.explicit)return null;let l=We(n.state).resolveInner(n.pos,-1);if(((w=l==null?void 0:l.type)==null?void 0:w.name)=="comment")return null;let x=[{label:"false"},{label:"true"},{label:"@now"},{label:"@second"},{label:"@minute"},{label:"@hour"},{label:"@year"},{label:"@day"},{label:"@month"},{label:"@weekday"},{label:"@todayStart"},{label:"@todayEnd"},{label:"@monthStart"},{label:"@monthEnd"},{label:"@yearStart"},{label:"@yearEnd"}];m||x.push({label:"@collection.*",apply:"@collection."});let C=Y(!b&&c.text.startsWith("@r"),!m&&c.text.startsWith("@c"));for(const k of C)x.push({label:k.endsWith(".")?k+"*":k,apply:k,boost:k.indexOf("_via_")>0?-1:0});return{from:c.from,options:x}}function P(){return qe.define(Ie({start:[{regex:/true|false|null/,token:"atom"},{regex:/\/\/.*/,token:"comLine truncated
+1
View File
@@ -0,0 +1 @@
import{S as $,i as ee,s as te,h as ne,k as re,n as ae,I as J,v as ie,O as oe,T as le,U as se,Q as de,J as u,y as ce}from"./index-0HOqdotm.js";import{c as fe,d as ue,s as ge,h as he,a as ye,E,b as S,e as pe,f as ke,g as me,i as xe,j as be,k as we,l as Ee,m as Se,r as Ce,n as Ke,o as Re,p as Le,q as G,C as R,S as qe,t as ve,u as Oe,v as We}from"./index-Sijz3BEY.js";function _e(e){return new Worker(""+new URL("autocomplete.worker-C13JoMbu.js",import.meta.url).href,{name:e==null?void 0:e.name})}function Je(e){Q(e,"start");var r={},t=e.languageData||{},g=!1;for(var h in e)if(h!=t&&e.hasOwnProperty(h))for(var f=r[h]=[],i=e[h],a=0;a<i.length;a++){var o=i[a];f.push(new Me(o,e)),(o.indent||o.dedent)&&(g=!0)}return{name:t.name,startState:function(){return{state:"start",pending:null,indent:g?[]:null}},copyState:function(s){var y={state:s.state,pending:s.pending,indent:s.indent&&s.indent.slice(0)};return s.stack&&(y.stack=s.stack.slice(0)),y},token:Ae(r),indent:Te(r,t),languageData:t}}function Q(e,r){if(!e.hasOwnProperty(r))throw new Error("Undefined state "+r+" in simple mode")}function De(e,r){if(!e)return/(?:)/;var t="";return e instanceof RegExp?(e.ignoreCase&&(t="i"),e=e.source):e=String(e),new RegExp("^(?:"+e+")",t)}function Ie(e){if(!e)return null;if(e.apply)return e;if(typeof e=="string")return e.replace(/\./g," ");for(var r=[],t=0;t<e.length;t++)r.push(e[t]&&e[t].replace(/\./g," "));return r}function Me(e,r){(e.next||e.push)&&Q(r,e.next||e.push),this.regex=De(e.regex),this.token=Ie(e.token),this.data=e}function Ae(e){return function(r,t){if(t.pending){var g=t.pending.shift();return t.pending.length==0&&(t.pending=null),r.pos+=g.text.length,g.token}for(var h=e[t.state],f=0;f<h.length;f++){var i=h[f],a=(!i.data.sol||r.sol())&&r.match(i.regex);if(a){i.data.next?t.state=i.data.next:i.data.push?((t.stack||(t.stack=[])).push(t.state),t.state=i.data.push):i.data.pop&&t.stack&&t.stack.length&&(t.state=t.stack.pop()),i.data.indent&&t.indent.push(r.indentation()+r.indentUnit),i.data.dedent&&t.indent.pop();var o=i.token;if(o&&o.apply&&(o=o(a)),a.length>2&&i.token&&typeof i.token!="string"){t.pending=[];for(var s=2;s<a.length;s++)a[s]&&t.pending.push({text:a[s],token:i.token[s-1]});return r.backUp(a[0].length-(a[1]?a[1].length:0)),o[0]}else return o&&o.join?o[0]:o}}return r.next(),null}}function Te(e,r){return function(t,g){if(t.indent==null||r.dontIndentStates&&r.doneIndentState.indexOf(t.state)>-1)return null;var h=t.indent.length-1,f=e[t.state];e:for(;;){for(var i=0;i<f.length;i++){var a=f[i];if(a.data.dedent&&a.data.dedentIfLineStart!==!1){var o=a.regex.exec(g);if(o&&o[0]){h--,(a.next||a.push)&&(f=e[a.next||a.push]),g=g.slice(o[0].length);continue e}}}break}return h<0?0:t.indent[h]}}function Be(e){let r;return{c(){r=ne("div"),re(r,"class","code-editor")},m(t,g){ae(t,r,g),e[15](r)},p:J,i:J,o:J,d(t){t&&ie(r),e[15](null)}}}function He(e){return JSON.stringify([e==null?void 0:e.name,e==null?void 0:e.type,e==null?void 0:e.fields])}function Ue(e,r,t){let g;oe(e,le,n=>t(21,g=n));const h=se();let{id:f=""}=r,{value:i=""}=r,{disabled:a=!1}=r,{placeholder:o=""}=r,{baseCollection:s=null}=r,{singleLine:y=!1}=r,{extraAutocompleteKeys:L=[]}=r,{disableRequestKeys:b=!1}=r,{disableCollectionJoinKeys:m=!1}=r,d,p,q=a,D=new R,I=new R,M=new R,A=new R,v=new _e,T=[],B=[],H=[],C="",O="";function W(){d==null||d.focus()}let _=null;v.onmessage=n=>{H=n.data.baseKeys||[],T=n.data.requestKeys||[],B=n.data.collectionJoinKeys||[]};function V(){clearTimeout(_),_=setTimeout(()=>{v.postMessage({baseCollection:s,collections:j(g),disableRequestKeys:b,disableCollectionJoinKeys:m})},250)}function j(n){let c=n.slice();return s&&u.pushOrReplaceByKey(c,s,"id"),c}function U(){p==null||p.dispatchEvent(new CustomEvent("change",{detail:{value:i},bubbles:!0}))}function F(){if(!f)return;const n=document.querySelectorAll('[for="'+f+'"]');for(let c of n)c.removeEventListener("click",W)}function N(){if(!f)return;F();const n=document.querySelectorAll('[for="'+f+'"]');for(let c of n)c.addEventListener("click",W)}function z(n=!0,c=!0){let l=[].concat(L);return l=l.concat(H||[]),n&&(l=l.concat(T||[])),c&&(l=l.concat(B||[])),l}function X(n){var w;let c=n.matchBefore(/[\'\"\@\w\.]*/);if(c&&c.from==c.to&&!n.explicit)return null;let l=We(n.state).resolveInner(n.pos,-1);if(((w=l==null?void 0:l.type)==null?void 0:w.name)=="comment")return null;let x=[{label:"false"},{label:"true"},{label:"@now"},{label:"@second"},{label:"@minute"},{label:"@hour"},{label:"@year"},{label:"@day"},{label:"@month"},{label:"@weekday"},{label:"@yesterday"},{label:"@tomorrow"},{label:"@todayStart"},{label:"@todayEnd"},{label:"@monthStart"},{label:"@monthEnd"},{label:"@yearStart"},{label:"@yearEnd"}];m||x.push({label:"@collection.*",apply:"@collection."});let K=z(!b&&c.text.startsWith("@r"),!m&&c.text.startsWith("@c"));for(const k of K)x.push({label:k.endsWith(".")?k+"*":k,apply:k,boost:k.indexOf("_via_")>0?-1:0});return{from:c.from,options:x}}function P(){return qe.define(Je({start:[{regex:/true|false|Line truncated
@@ -1,4 +1,4 @@
import{S as el,i as ll,s as sl,h as e,j as s,L as ol,k as a,n as m,q as nl,I as Ue,v as h,z as g,o as t,V as al,W as Le,X as ae,c as Kt,m as Qt,H as ve,Y as Je,Z as il,E as rl,_ as cl,G as dl,t as Ct,a as kt,d as Vt,$ as pl,J as Te,p as fl,l as Ae}from"./index-Bpzzn7x2.js";import{F as ul}from"./FieldsQueryParam-CrrI0KIL.js";function ml(r){let n,o,i;return{c(){n=e("span"),n.textContent="Show details",o=s(),i=e("i"),a(n,"class","txt"),a(i,"class","ri-arrow-down-s-line")},m(f,b){m(f,n,b),m(f,o,b),m(f,i,b)},d(f){f&&(h(n),h(o),h(i))}}}function hl(r){let n,o,i;return{c(){n=e("span"),n.textContent="Hide details",o=s(),i=e("i"),a(n,"class","txt"),a(i,"class","ri-arrow-up-s-line")},m(f,b){m(f,n,b),m(f,o,b),m(f,i,b)},d(f){f&&(h(n),h(o),h(i))}}}function Ke(r){let n,o,i,f,b,p,u,C,_,x,d,Y,yt,Wt,S,Xt,H,it,P,Z,ie,j,z,re,rt,vt,tt,Ft,ce,ct,dt,et,q,Yt,Lt,k,lt,At,Zt,Tt,U,st,Pt,te,Rt,v,pt,Ot,de,ft,pe,D,Et,nt,St,F,ut,fe,J,qt,ee,Nt,le,Ht,ue,L,mt,me,ht,he,M,be,T,Dt,ot,Mt,K,bt,ge,I,It,y,Bt,at,Gt,_e,Q,gt,we,_t,xe,jt,$e,B,zt,Ce,G,ke,wt,se,R,xt,V,W,O,Ut,ne,X;return{c(){n=e("p"),n.innerHTML=`The syntax basically follows the format
import{S as el,i as ll,s as sl,h as e,j as s,L as ol,k as a,n as m,q as nl,I as Ue,v as h,z as g,o as t,V as al,W as Le,X as ae,c as Kt,m as Qt,H as ve,Y as Je,Z as il,E as rl,_ as cl,G as dl,t as Ct,a as kt,d as Vt,$ as pl,J as Te,p as fl,l as Ae}from"./index-0HOqdotm.js";import{F as ul}from"./FieldsQueryParam-DT9Tnt7Y.js";function ml(r){let n,o,i;return{c(){n=e("span"),n.textContent="Show details",o=s(),i=e("i"),a(n,"class","txt"),a(i,"class","ri-arrow-down-s-line")},m(f,b){m(f,n,b),m(f,o,b),m(f,i,b)},d(f){f&&(h(n),h(o),h(i))}}}function hl(r){let n,o,i;return{c(){n=e("span"),n.textContent="Hide details",o=s(),i=e("i"),a(n,"class","txt"),a(i,"class","ri-arrow-up-s-line")},m(f,b){m(f,n,b),m(f,o,b),m(f,i,b)},d(f){f&&(h(n),h(o),h(i))}}}function Ke(r){let n,o,i,f,b,p,u,C,_,x,d,Y,yt,Wt,S,Xt,H,it,P,Z,ie,j,z,re,rt,vt,tt,Ft,ce,ct,dt,et,q,Yt,Lt,k,lt,At,Zt,Tt,U,st,Pt,te,Rt,v,pt,Ot,de,ft,pe,D,Et,nt,St,F,ut,fe,J,qt,ee,Nt,le,Ht,ue,L,mt,me,ht,he,M,be,T,Dt,ot,Mt,K,bt,ge,I,It,y,Bt,at,Gt,_e,Q,gt,we,_t,xe,jt,$e,B,zt,Ce,G,ke,wt,se,R,xt,V,W,O,Ut,ne,X;return{c(){n=e("p"),n.innerHTML=`The syntax basically follows the format
<code><span class="txt-success">OPERAND</span> <span class="txt-danger">OPERATOR</span> <span class="txt-success">OPERAND</span></code>, where:`,o=s(),i=e("ul"),f=e("li"),f.innerHTML=`<code class="txt-success">OPERAND</code> - could be any of the above field literal, string (single
or double quoted), number, null, true, false`,b=s(),p=e("li"),u=e("code"),u.textContent="OPERATOR",C=g(` - is one of:
`),_=e("br"),x=s(),d=e("ul"),Y=e("li"),yt=e("code"),yt.textContent="=",Wt=s(),S=e("span"),S.textContent="Equal",Xt=s(),H=e("li"),it=e("code"),it.textContent="!=",P=s(),Z=e("span"),Z.textContent="NOT equal",ie=s(),j=e("li"),z=e("code"),z.textContent=">",re=s(),rt=e("span"),rt.textContent="Greater than",vt=s(),tt=e("li"),Ft=e("code"),Ft.textContent=">=",ce=s(),ct=e("span"),ct.textContent="Greater than or equal",dt=s(),et=e("li"),q=e("code"),q.textContent="<",Yt=s(),Lt=e("span"),Lt.textContent="Less than",k=s(),lt=e("li"),At=e("code"),At.textContent="<=",Zt=s(),Tt=e("span"),Tt.textContent="Less than or equal",U=s(),st=e("li"),Pt=e("code"),Pt.textContent="~",te=s(),Rt=e("span"),Rt.textContent=`Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for
@@ -1,3 +1,3 @@
import{S as W,i as G,s as J,F as Q,c as S,m as E,t as O,a as j,d as D,r as M,g as V,p as C,b as X,e as Y,f as K,h as k,j as q,k as r,l as z,n as m,o as T,q as I,u as Z,v as h,w as x,x as ee,y as U,z as N,A,B as te}from"./index-Bpzzn7x2.js";function ne(s){let t,o,u,n,e,p,_,d;return{c(){t=k("label"),o=N("Email"),n=q(),e=k("input"),r(t,"for",u=s[20]),r(e,"type","email"),r(e,"autocomplete","off"),r(e,"id",p=s[20]),e.disabled=s[7],e.required=!0},m(a,i){m(a,t,i),T(t,o),m(a,n,i),m(a,e,i),s[11](e),A(e,s[2]),_||(d=I(e,"input",s[12]),_=!0)},p(a,i){i&1048576&&u!==(u=a[20])&&r(t,"for",u),i&1048576&&p!==(p=a[20])&&r(e,"id",p),i&128&&(e.disabled=a[7]),i&4&&e.value!==a[2]&&A(e,a[2])},d(a){a&&(h(t),h(n),h(e)),s[11](null),_=!1,d()}}}function le(s){let t,o,u,n,e,p,_,d,a,i;return{c(){t=k("label"),o=N("Password"),n=q(),e=k("input"),_=q(),d=k("div"),d.textContent="Recommended at least 10 characters.",r(t,"for",u=s[20]),r(e,"type","password"),r(e,"autocomplete","new-password"),r(e,"minlength","10"),r(e,"id",p=s[20]),e.disabled=s[7],e.required=!0,r(d,"class","help-block")},m(c,g){m(c,t,g),T(t,o),m(c,n,g),m(c,e,g),A(e,s[3]),m(c,_,g),m(c,d,g),a||(i=I(e,"input",s[13]),a=!0)},p(c,g){g&1048576&&u!==(u=c[20])&&r(t,"for",u),g&1048576&&p!==(p=c[20])&&r(e,"id",p),g&128&&(e.disabled=c[7]),g&8&&e.value!==c[3]&&A(e,c[3])},d(c){c&&(h(t),h(n),h(e),h(_),h(d)),a=!1,i()}}}function se(s){let t,o,u,n,e,p,_,d;return{c(){t=k("label"),o=N("Password confirm"),n=q(),e=k("input"),r(t,"for",u=s[20]),r(e,"type","password"),r(e,"minlength","10"),r(e,"id",p=s[20]),e.disabled=s[7],e.required=!0},m(a,i){m(a,t,i),T(t,o),m(a,n,i),m(a,e,i),A(e,s[4]),_||(d=I(e,"input",s[14]),_=!0)},p(a,i){i&1048576&&u!==(u=a[20])&&r(t,"for",u),i&1048576&&p!==(p=a[20])&&r(e,"id",p),i&128&&(e.disabled=a[7]),i&16&&e.value!==a[4]&&A(e,a[4])},d(a){a&&(h(t),h(n),h(e)),_=!1,d()}}}function ie(s){let t,o,u,n,e,p,_,d,a,i,c,g,B,w,F,$,v,y,L;return n=new K({props:{class:"form-field required",name:"email",$$slots:{default:[ne,({uniqueId:l})=>({20:l}),({uniqueId:l})=>l?1048576:0]},$$scope:{ctx:s}}}),p=new K({props:{class:"form-field required",name:"password",$$slots:{default:[le,({uniqueId:l})=>({20:l}),({uniqueId:l})=>l?1048576:0]},$$scope:{ctx:s}}}),d=new K({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[se,({uniqueId:l})=>({20:l}),({uniqueId:l})=>l?1048576:0]},$$scope:{ctx:s}}}),{c(){t=k("form"),o=k("div"),o.innerHTML="<h4>Create your first superuser account in order to continue</h4>",u=q(),S(n.$$.fragment),e=q(),S(p.$$.fragment),_=q(),S(d.$$.fragment),a=q(),i=k("button"),i.innerHTML='<span class="txt">Create superuser and login</span> <i class="ri-arrow-right-line"></i>',c=q(),g=k("hr"),B=q(),w=k("label"),w.innerHTML='<i class="ri-upload-cloud-line"></i> <span class="txt">Or initialize from backup</span>',F=q(),$=k("input"),r(o,"class","content txt-center m-b-base"),r(i,"type","submit"),r(i,"class","btn btn-lg btn-block btn-next"),z(i,"btn-disabled",s[7]),z(i,"btn-loading",s[0]),r(t,"class","block"),r(t,"autocomplete","off"),r(w,"for","backupFileInput"),r(w,"class","btn btn-lg btn-hint btn-transparent btn-block"),z(w,"btn-disabled",s[7]),z(w,"btn-loading",s[1]),r($,"id","backupFileInput"),r($,"type","file"),r($,"class","hidden"),r($,"accept",".zip")},m(l,b){m(l,t,b),T(t,o),T(t,u),E(n,t,null),T(t,e),E(p,t,null),T(t,_),E(d,t,null),T(t,a),T(t,i),m(l,c,b),m(l,g,b),m(l,B,b),m(l,w,b),m(l,F,b),m(l,$,b),s[15]($),v=!0,y||(L=[I(t,"submit",Z(s[8])),I($,"change",s[16])],y=!0)},p(l,b){const H={};b&3145892&&(H.$$scope={dirty:b,ctx:l}),n.$set(H);const f={};b&3145864&&(f.$$scope={dirty:b,ctx:l}),p.$set(f);const P={};b&3145872&&(P.$$scope={dirty:b,ctx:l}),d.$set(P),(!v||b&128)&&z(i,"btn-disabled",l[7]),(!v||b&1)&&z(i,"btn-loading",l[0]),(!v||b&128)&&z(w,"btn-disabled",l[7]),(!v||b&2)&&z(w,"btn-loading",l[1])},i(l){v||(O(n.$$.fragment,l),O(p.$$.fragment,l),O(d.$$.fragment,l),v=!0)},o(l){j(n.$$.fragment,l),j(p.$$.fragment,l),j(d.$$.fragment,l),v=!1},d(l){l&&(h(t),h(c),h(g),h(B),h(w),h(F),h($)),D(n),D(p),D(d),s[15](null),y=!1,x(L)}}}function ae(s){let t,o;return t=new Q({props:{$$slots:{default:[ie]},$$scope:{ctx:s}}}),{c(){S(t.$$.fragment)},m(u,n){E(t,u,n),o=!0},p(u,[n]){const e={};n&2097407&&(e.$$scope={dirty:n,ctx:u}),t.$set(e)},i(u){o||(O(t.$$.fragment,u),o=!0)},o(u){j(t.$$.fragment,u),o=!1},d(u){D(t,u)}}}function oe(s,t,o){let u,{params:n}=t,e="",p="",_="",d=!1,a=!1,i,c;g();async function g(){if(!(n!=null&&n.token))return M("/");o(0,d=!0);try{const f=V(n==null?void 0:n.token);await C.collection("_superusers").getOne(f.id,{requestKey:"installer_token_check",headers:{Authorization:n==null?void 0:n.token}})}catch(f){f!=null&&f.isAbort||(X("The installer token is invalid or has expired."),M("/"))}o(0,d=!1),await Y(),i==null||i.focus()}async function B(){if(!u){o(0,d=!0);try{await C.collection("_superusers").create({email:e,password:p,passwordConfirm:_},{headers:{Authorization:n==null?void 0:n.token}}),await C.collection("_superusers").authWithPassword(e,p),M("/")}catch(f){C.error(Line truncated
import{S as W,i as G,s as J,F as Q,c as S,m as E,t as O,a as j,d as D,r as M,g as V,p as C,b as X,e as Y,f as K,h as k,j as q,k as r,l as z,n as m,o as T,q as I,u as Z,v as h,w as x,x as ee,y as U,z as N,A,B as te}from"./index-0HOqdotm.js";function ne(s){let t,o,u,n,e,p,_,d;return{c(){t=k("label"),o=N("Email"),n=q(),e=k("input"),r(t,"for",u=s[20]),r(e,"type","email"),r(e,"autocomplete","off"),r(e,"id",p=s[20]),e.disabled=s[7],e.required=!0},m(a,i){m(a,t,i),T(t,o),m(a,n,i),m(a,e,i),s[11](e),A(e,s[2]),_||(d=I(e,"input",s[12]),_=!0)},p(a,i){i&1048576&&u!==(u=a[20])&&r(t,"for",u),i&1048576&&p!==(p=a[20])&&r(e,"id",p),i&128&&(e.disabled=a[7]),i&4&&e.value!==a[2]&&A(e,a[2])},d(a){a&&(h(t),h(n),h(e)),s[11](null),_=!1,d()}}}function le(s){let t,o,u,n,e,p,_,d,a,i;return{c(){t=k("label"),o=N("Password"),n=q(),e=k("input"),_=q(),d=k("div"),d.textContent="Recommended at least 10 characters.",r(t,"for",u=s[20]),r(e,"type","password"),r(e,"autocomplete","new-password"),r(e,"minlength","10"),r(e,"id",p=s[20]),e.disabled=s[7],e.required=!0,r(d,"class","help-block")},m(c,g){m(c,t,g),T(t,o),m(c,n,g),m(c,e,g),A(e,s[3]),m(c,_,g),m(c,d,g),a||(i=I(e,"input",s[13]),a=!0)},p(c,g){g&1048576&&u!==(u=c[20])&&r(t,"for",u),g&1048576&&p!==(p=c[20])&&r(e,"id",p),g&128&&(e.disabled=c[7]),g&8&&e.value!==c[3]&&A(e,c[3])},d(c){c&&(h(t),h(n),h(e),h(_),h(d)),a=!1,i()}}}function se(s){let t,o,u,n,e,p,_,d;return{c(){t=k("label"),o=N("Password confirm"),n=q(),e=k("input"),r(t,"for",u=s[20]),r(e,"type","password"),r(e,"minlength","10"),r(e,"id",p=s[20]),e.disabled=s[7],e.required=!0},m(a,i){m(a,t,i),T(t,o),m(a,n,i),m(a,e,i),A(e,s[4]),_||(d=I(e,"input",s[14]),_=!0)},p(a,i){i&1048576&&u!==(u=a[20])&&r(t,"for",u),i&1048576&&p!==(p=a[20])&&r(e,"id",p),i&128&&(e.disabled=a[7]),i&16&&e.value!==a[4]&&A(e,a[4])},d(a){a&&(h(t),h(n),h(e)),_=!1,d()}}}function ie(s){let t,o,u,n,e,p,_,d,a,i,c,g,B,w,F,$,v,y,L;return n=new K({props:{class:"form-field required",name:"email",$$slots:{default:[ne,({uniqueId:l})=>({20:l}),({uniqueId:l})=>l?1048576:0]},$$scope:{ctx:s}}}),p=new K({props:{class:"form-field required",name:"password",$$slots:{default:[le,({uniqueId:l})=>({20:l}),({uniqueId:l})=>l?1048576:0]},$$scope:{ctx:s}}}),d=new K({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[se,({uniqueId:l})=>({20:l}),({uniqueId:l})=>l?1048576:0]},$$scope:{ctx:s}}}),{c(){t=k("form"),o=k("div"),o.innerHTML="<h4>Create your first superuser account in order to continue</h4>",u=q(),S(n.$$.fragment),e=q(),S(p.$$.fragment),_=q(),S(d.$$.fragment),a=q(),i=k("button"),i.innerHTML='<span class="txt">Create superuser and login</span> <i class="ri-arrow-right-line"></i>',c=q(),g=k("hr"),B=q(),w=k("label"),w.innerHTML='<i class="ri-upload-cloud-line"></i> <span class="txt">Or initialize from backup</span>',F=q(),$=k("input"),r(o,"class","content txt-center m-b-base"),r(i,"type","submit"),r(i,"class","btn btn-lg btn-block btn-next"),z(i,"btn-disabled",s[7]),z(i,"btn-loading",s[0]),r(t,"class","block"),r(t,"autocomplete","off"),r(w,"for","backupFileInput"),r(w,"class","btn btn-lg btn-hint btn-transparent btn-block"),z(w,"btn-disabled",s[7]),z(w,"btn-loading",s[1]),r($,"id","backupFileInput"),r($,"type","file"),r($,"class","hidden"),r($,"accept",".zip")},m(l,b){m(l,t,b),T(t,o),T(t,u),E(n,t,null),T(t,e),E(p,t,null),T(t,_),E(d,t,null),T(t,a),T(t,i),m(l,c,b),m(l,g,b),m(l,B,b),m(l,w,b),m(l,F,b),m(l,$,b),s[15]($),v=!0,y||(L=[I(t,"submit",Z(s[8])),I($,"change",s[16])],y=!0)},p(l,b){const H={};b&3145892&&(H.$$scope={dirty:b,ctx:l}),n.$set(H);const f={};b&3145864&&(f.$$scope={dirty:b,ctx:l}),p.$set(f);const P={};b&3145872&&(P.$$scope={dirty:b,ctx:l}),d.$set(P),(!v||b&128)&&z(i,"btn-disabled",l[7]),(!v||b&1)&&z(i,"btn-loading",l[0]),(!v||b&128)&&z(w,"btn-disabled",l[7]),(!v||b&2)&&z(w,"btn-loading",l[1])},i(l){v||(O(n.$$.fragment,l),O(p.$$.fragment,l),O(d.$$.fragment,l),v=!0)},o(l){j(n.$$.fragment,l),j(p.$$.fragment,l),j(d.$$.fragment,l),v=!1},d(l){l&&(h(t),h(c),h(g),h(B),h(w),h(F),h($)),D(n),D(p),D(d),s[15](null),y=!1,x(L)}}}function ae(s){let t,o;return t=new Q({props:{$$slots:{default:[ie]},$$scope:{ctx:s}}}),{c(){S(t.$$.fragment)},m(u,n){E(t,u,n),o=!0},p(u,[n]){const e={};n&2097407&&(e.$$scope={dirty:n,ctx:u}),t.$set(e)},i(u){o||(O(t.$$.fragment,u),o=!0)},o(u){j(t.$$.fragment,u),o=!1},d(u){D(t,u)}}}function oe(s,t,o){let u,{params:n}=t,e="",p="",_="",d=!1,a=!1,i,c;g();async function g(){if(!(n!=null&&n.token))return M("/");o(0,d=!0);try{const f=V(n==null?void 0:n.token);await C.collection("_superusers").getOne(f.id,{requestKey:"installer_token_check",headers:{Authorization:n==null?void 0:n.token}})}catch(f){f!=null&&f.isAbort||(X("The installer token is invalid or has expired."),M("/"))}o(0,d=!1),await Y(),i==null||i.focus()}async function B(){if(!u){o(0,d=!0);try{await C.collection("_superusers").create({email:e,password:p,passwordConfirm:_},{headers:{Authorization:n==null?void 0:n.token}}),await C.collection("_superusers").authWithPassword(e,p),M("/")}catch(f){C.error(Line truncated
Do you really want to upload and initialize "${f.name}"?`,()=>{$(f)},()=>{w()})}async function $(f){if(!(!f||u)){o(1,a=!0);try{await C.backups.upload({file:f},{headers:{Authorization:n==null?void 0:n.token}}),await C.backups.restore(f.name,{headers:{Authorization:n==null?void 0:n.token}}),te("Please wait while extracting the uploaded archive!"),await new Promise(P=>setTimeout(P,2e3)),M("/")}catch(P){C.error(P)}w(),o(1,a=!1)}}function v(f){U[f?"unshift":"push"](()=>{i=f,o(5,i)})}function y(){e=this.value,o(2,e)}function L(){p=this.value,o(3,p)}function l(){_=this.value,o(4,_)}function b(f){U[f?"unshift":"push"](()=>{c=f,o(6,c)})}const H=f=>{var P,R;F((R=(P=f.target)==null?void 0:P.files)==null?void 0:R[0])};return s.$$set=f=>{"params"in f&&o(10,n=f.params)},s.$$.update=()=>{s.$$.dirty&3&&o(7,u=d||a)},[d,a,e,p,_,i,c,u,B,F,n,v,y,L,l,b,H]}class re extends W{constructor(t){super(),G(this,t,oe,ae,J,{params:10})}}export{re as default};
@@ -1 +1 @@
import{S as r,i as c,s as l,h as u,k as h,n as p,I as n,v as d,O as f,P as m,Q as g,R as o}from"./index-Bpzzn7x2.js";function _(s){let t;return{c(){t=u("div"),t.innerHTML='<h3 class="m-b-sm">Auth failed.</h3> <h5>You can close this window and go back to the app to try again.</h5>',h(t,"class","content txt-hint txt-center p-base")},m(e,a){p(e,t,a)},p:n,i:n,o:n,d(e){e&&d(t)}}}function b(s,t,e){let a;return f(s,o,i=>e(0,a=i)),m(o,a="OAuth2 auth failed",a),g(()=>{window.close()}),[]}class x extends r{constructor(t){super(),c(this,t,b,_,l,{})}}export{x as default};
import{S as r,i as c,s as l,h as u,k as h,n as p,I as n,v as d,O as f,P as m,Q as g,R as o}from"./index-0HOqdotm.js";function _(s){let t;return{c(){t=u("div"),t.innerHTML='<h3 class="m-b-sm">Auth failed.</h3> <h5>You can close this window and go back to the app to try again.</h5>',h(t,"class","content txt-hint txt-center p-base")},m(e,a){p(e,t,a)},p:n,i:n,o:n,d(e){e&&d(t)}}}function b(s,t,e){let a;return f(s,o,i=>e(0,a=i)),m(o,a="OAuth2 auth failed",a),g(()=>{window.close()}),[]}class x extends r{constructor(t){super(),c(this,t,b,_,l,{})}}export{x as default};
@@ -1 +1 @@
import{S as i,i as r,s as u,h as l,k as p,n as h,I as n,v as d,O as m,P as f,Q as _,R as o}from"./index-Bpzzn7x2.js";function b(a){let t;return{c(){t=l("div"),t.innerHTML='<h3 class="m-b-sm">Auth completed.</h3> <h5>You can close this window and go back to the app.</h5>',p(t,"class","content txt-hint txt-center p-base")},m(e,s){h(e,t,s)},p:n,i:n,o:n,d(e){e&&d(t)}}}function g(a,t,e){let s;return m(a,o,c=>e(0,s=c)),f(o,s="OAuth2 auth completed",s),_(()=>{window.close()}),[]}class x extends i{constructor(t){super(),r(this,t,g,b,u,{})}}export{x as default};
import{S as i,i as r,s as u,h as l,k as p,n as h,I as n,v as d,O as m,P as f,Q as _,R as o}from"./index-0HOqdotm.js";function b(a){let t;return{c(){t=l("div"),t.innerHTML='<h3 class="m-b-sm">Auth completed.</h3> <h5>You can close this window and go back to the app.</h5>',p(t,"class","content txt-hint txt-center p-base")},m(e,s){h(e,t,s)},p:n,i:n,o:n,d(e){e&&d(t)}}}function g(a,t,e){let s;return m(a,o,c=>e(0,s=c)),f(o,s="OAuth2 auth completed",s),_(()=>{window.close()}),[]}class x extends i{constructor(t){super(),r(this,t,g,b,u,{})}}export{x as default};
@@ -1,2 +1,2 @@
import{S as J,i as M,s as j,F as z,c as L,m as S,t as h,a as v,d as I,J as A,L as G,n as _,E as N,G as R,v as b,M as W,g as Y,p as B,f as D,h as m,z as y,j as C,k as p,l as T,o as g,q as P,u as K,I as E,H as O,A as F}from"./index-Bpzzn7x2.js";function Q(i){let e,t,n,l,s,o,f,a,r,u,k,$,d=i[3]&&H(i);return o=new D({props:{class:"form-field required",name:"password",$$slots:{default:[V,({uniqueId:c})=>({8:c}),({uniqueId:c})=>c?256:0]},$$scope:{ctx:i}}}),{c(){e=m("form"),t=m("div"),n=m("h5"),l=y(`Type your password to confirm changing your email address
import{S as J,i as M,s as j,F as z,c as L,m as S,t as h,a as v,d as I,J as A,L as G,n as _,E as N,G as R,v as b,M as W,g as Y,p as B,f as D,h as m,z as y,j as C,k as p,l as T,o as g,q as P,u as K,I as E,H as O,A as F}from"./index-0HOqdotm.js";function Q(i){let e,t,n,l,s,o,f,a,r,u,k,$,d=i[3]&&H(i);return o=new D({props:{class:"form-field required",name:"password",$$slots:{default:[V,({uniqueId:c})=>({8:c}),({uniqueId:c})=>c?256:0]},$$scope:{ctx:i}}}),{c(){e=m("form"),t=m("div"),n=m("h5"),l=y(`Type your password to confirm changing your email address
`),d&&d.c(),s=C(),L(o.$$.fragment),f=C(),a=m("button"),r=m("span"),r.textContent="Confirm new email",p(t,"class","content txt-center m-b-base"),p(r,"class","txt"),p(a,"type","submit"),p(a,"class","btn btn-lg btn-block"),a.disabled=i[1],T(a,"btn-loading",i[1])},m(c,w){_(c,e,w),g(e,t),g(t,n),g(n,l),d&&d.m(n,null),g(e,s),S(o,e,null),g(e,f),g(e,a),g(a,r),u=!0,k||($=P(e,"submit",K(i[4])),k=!0)},p(c,w){c[3]?d?d.p(c,w):(d=H(c),d.c(),d.m(n,null)):d&&(d.d(1),d=null);const q={};w&769&&(q.$$scope={dirty:w,ctx:c}),o.$set(q),(!u||w&2)&&(a.disabled=c[1]),(!u||w&2)&&T(a,"btn-loading",c[1])},i(c){u||(h(o.$$.fragment,c),u=!0)},o(c){v(o.$$.fragment,c),u=!1},d(c){c&&b(e),d&&d.d(),I(o),k=!1,$()}}}function U(i){let e,t,n,l,s;return{c(){e=m("div"),e.innerHTML='<div class="icon"><i class="ri-checkbox-circle-line"></i></div> <div class="content txt-bold"><p>Successfully changed the user email address.</p> <p>You can now sign in with your new email address.</p></div>',t=C(),n=m("button"),n.textContent="Close",p(e,"class","alert alert-success"),p(n,"type","button"),p(n,"class","btn btn-transparent btn-block")},m(o,f){_(o,e,f),_(o,t,f),_(o,n,f),l||(s=P(n,"click",i[6]),l=!0)},p:E,i:E,o:E,d(o){o&&(b(e),b(t),b(n)),l=!1,s()}}}function H(i){let e,t,n;return{c(){e=y("to "),t=m("strong"),n=y(i[3]),p(t,"class","txt-nowrap")},m(l,s){_(l,e,s),_(l,t,s),g(t,n)},p(l,s){s&8&&O(n,l[3])},d(l){l&&(b(e),b(t))}}}function V(i){let e,t,n,l,s,o,f,a;return{c(){e=m("label"),t=y("Password"),l=C(),s=m("input"),p(e,"for",n=i[8]),p(s,"type","password"),p(s,"id",o=i[8]),s.required=!0,s.autofocus=!0},m(r,u){_(r,e,u),g(e,t),_(r,l,u),_(r,s,u),F(s,i[0]),s.focus(),f||(a=P(s,"input",i[7]),f=!0)},p(r,u){u&256&&n!==(n=r[8])&&p(e,"for",n),u&256&&o!==(o=r[8])&&p(s,"id",o),u&1&&s.value!==r[0]&&F(s,r[0])},d(r){r&&(b(e),b(l),b(s)),f=!1,a()}}}function X(i){let e,t,n,l;const s=[U,Q],o=[];function f(a,r){return a[2]?0:1}return e=f(i),t=o[e]=s[e](i),{c(){t.c(),n=G()},m(a,r){o[e].m(a,r),_(a,n,r),l=!0},p(a,r){let u=e;e=f(a),e===u?o[e].p(a,r):(N(),v(o[u],1,1,()=>{o[u]=null}),R(),t=o[e],t?t.p(a,r):(t=o[e]=s[e](a),t.c()),h(t,1),t.m(n.parentNode,n))},i(a){l||(h(t),l=!0)},o(a){v(t),l=!1},d(a){a&&b(n),o[e].d(a)}}}function Z(i){let e,t;return e=new z({props:{nobranding:!0,$$slots:{default:[X]},$$scope:{ctx:i}}}),{c(){L(e.$$.fragment)},m(n,l){S(e,n,l),t=!0},p(n,[l]){const s={};l&527&&(s.$$scope={dirty:l,ctx:n}),e.$set(s)},i(n){t||(h(e.$$.fragment,n),t=!0)},o(n){v(e.$$.fragment,n),t=!1},d(n){I(e,n)}}}function x(i,e,t){let n,{params:l}=e,s="",o=!1,f=!1;async function a(){if(o)return;t(1,o=!0);const k=new W("../");try{const $=Y(l==null?void 0:l.token);await k.collection($.collectionId).confirmEmailChange(l==null?void 0:l.token,s),t(2,f=!0)}catch($){B.error($)}t(1,o=!1)}const r=()=>window.close();function u(){s=this.value,t(0,s)}return i.$$set=k=>{"params"in k&&t(5,l=k.params)},i.$$.update=()=>{i.$$.dirty&32&&t(3,n=A.getJWTPayload(l==null?void 0:l.token).newEmail||"")},[s,o,f,n,a,l,r,u]}class te extends J{constructor(e){super(),M(this,e,x,Z,j,{params:5})}}export{te as default};
Loaded 100 of 136 files, more files were not shown because too many files have changed in this diff. Show more