Compare commits

...
32 Commits
Author SHA1 Message Date
Gani Georgiev 931f6bc0cb updated go deps 2024-01-13 11:36:22 +02:00
Gani Georgiev 2e3ae1b60a [#4145] fixed JSVM types generation for functions with omitted arg types 2024-01-13 11:28:15 +02:00
Gani Georgiev eb695cc6d3 updated go crypto and other go deps 2024-01-05 20:48:15 +02:00
Gani Georgiev 28d15e86eb fixed optional migration condition
note: practically even the previous version should work ok because the json field didn't have previous options anyway and if it was nil the migration will fail
2024-01-05 20:35:09 +02:00
Gani Georgiev d0352aa3f9 [#4079] fixed popup searchbar css styles to prevent hiding the additional controls 2024-01-04 16:20:47 +02:00
Gani Georgiev 592b7e85c6 specify the exact license and changelog files to include in the release archive 2024-01-03 15:04:34 +02:00
Gani Georgiev 3792d44c35 updated changelog 2024-01-03 14:25:51 +02:00
Gani Georgiev a021fcaa75 [#4072] added non-json value dummy object wrap normalization 2024-01-03 14:16:12 +02:00
Gani Georgiev 982f876a93 updated jsvm types 2024-01-03 11:08:30 +02:00
Gani Georgiev 1fcc2d8683 updated CHANGELOG and added t.Parallel to some of the tests 2024-01-03 10:58:25 +02:00
Gani Georgiev 4f2492290e [#4068] fixed the json field query comparisons to work correctly with plain JSON values 2024-01-03 10:43:46 +02:00
Gani Georgiev 8f625daa2f updated some of the tests to use t.Parallel 2024-01-03 04:30:20 +02:00
Gani Georgiev 0599955676 sort cascadeDelete refs for deterministic tests output 2024-01-03 04:29:30 +02:00
Gani Georgiev c4116e3a7d added jsvm sleep binding 2023-12-29 23:29:00 +02:00
Gani Georgiev 64cee264f0 bumped app version 2023-12-29 22:00:47 +02:00
Gani Georgiev 0ae9f24a81 updated fields query param examples for the auth actions 2023-12-29 21:47:04 +02:00
Gani Georgiev 6d942c7d30 docs fixes commits from develop 2023-12-29 21:25:32 +02:00
Gani Georgiev 9f67c5d563 regenerated jsvm types 2023-12-29 09:58:21 +02:00
mookrs 1ac7330e0b [#4043] fixed typos in godoc comments 2023-12-29 09:56:36 +02:00
Gani Georgiev 461886f64e fixed the monospace font loading in the Admin UI 2023-12-27 10:47:18 +02:00
Gani Georgiev 8d97eb0769 [#4022] fixed multi-line text paste in the Admin UI search bar 2023-12-24 11:13:17 +02:00
Gani Georgiev 1208edec92 regenerated jsvm types 2023-12-17 00:20:04 +02:00
Gani Georgiev 5555e63116 updated dev debug log text message color to be slightly more visible 2023-12-17 00:17:29 +02:00
Gani Georgiev d6569b445c added timestamp to the generated JSVM types file to prevent creating it every time on app startup 2023-12-16 23:20:38 +02:00
Gani Georgiev 0b4f3b2adf combine the logs listing label span 2023-12-16 23:16:07 +02:00
Gani Georgiev 8bd968ed06 split changelog in chunks 2023-12-16 18:22:18 +02:00
Gani Georgiev 5c961f8537 [#3918] added --dev flag, dev log printer and some minor log UI enhacements 2023-12-16 18:15:36 +02:00
Gani Georgiev bf5eba0384 added bool to the view query sql syntax highlighter and autocompletion 2023-12-13 09:07:18 +02:00
Gani Georgiev c213d9313e fixed changelog typo 2023-12-12 19:48:28 +02:00
Gani Georgiev b31cf984a5 [#3930] replaced the default 100ms api tests timeout in favor of new ApiScenario.Timeout field 2023-12-12 19:46:58 +02:00
Gani Georgiev 8671debc35 removed the blank current time entry from the logs chart 2023-12-11 09:49:06 +02:00
Gani Georgiev b0f027d27a updated changelog formatting and temp moved the admin only rule checks to the record_helpers 2023-12-10 21:06:02 +02:00
155 changed files with 8092 additions and 6807 deletions

No files matched your search

+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '>=1.21.3'
go-version: '>=1.21.6'
# 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.
+2 -2
View File
@@ -51,8 +51,8 @@ archives:
builds: [build_noncgo]
format: zip
files:
- LICENSE*
- CHANGELOG*
- LICENSE.md
- CHANGELOG.md
checksum:
name_template: 'checksums.txt'
+92 -1407
View File
File diff suppressed because it is too large. Load diff
+1384
View File
File diff suppressed because it is too large. Load diff
+18
View File
@@ -17,6 +17,8 @@ import (
)
func TestAdminAuthWithPassword(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "empty data",
@@ -119,6 +121,8 @@ func TestAdminAuthWithPassword(t *testing.T) {
}
func TestAdminRequestPasswordReset(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "empty data",
@@ -188,6 +192,8 @@ func TestAdminRequestPasswordReset(t *testing.T) {
}
func TestAdminConfirmPasswordReset(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "empty data",
@@ -277,6 +283,8 @@ func TestAdminConfirmPasswordReset(t *testing.T) {
}
func TestAdminRefresh(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -350,6 +358,8 @@ func TestAdminRefresh(t *testing.T) {
}
func TestAdminsList(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -453,6 +463,8 @@ func TestAdminsList(t *testing.T) {
}
func TestAdminView(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -508,6 +520,8 @@ func TestAdminView(t *testing.T) {
}
func TestAdminDelete(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -603,6 +617,8 @@ func TestAdminDelete(t *testing.T) {
}
func TestAdminCreate(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized (while having at least 1 existing admin)",
@@ -757,6 +773,8 @@ func TestAdminCreate(t *testing.T) {
}
func TestAdminUpdate(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
+12
View File
@@ -10,6 +10,8 @@ import (
)
func TestNewApiErrorWithRawData(t *testing.T) {
t.Parallel()
e := apis.NewApiError(
300,
"message_test",
@@ -33,6 +35,8 @@ func TestNewApiErrorWithRawData(t *testing.T) {
}
func TestNewApiErrorWithValidationData(t *testing.T) {
t.Parallel()
e := apis.NewApiError(
300,
"message_test",
@@ -66,6 +70,8 @@ func TestNewApiErrorWithValidationData(t *testing.T) {
}
func TestNewNotFoundError(t *testing.T) {
t.Parallel()
scenarios := []struct {
message string
data any
@@ -87,6 +93,8 @@ func TestNewNotFoundError(t *testing.T) {
}
func TestNewBadRequestError(t *testing.T) {
t.Parallel()
scenarios := []struct {
message string
data any
@@ -108,6 +116,8 @@ func TestNewBadRequestError(t *testing.T) {
}
func TestNewForbiddenError(t *testing.T) {
t.Parallel()
scenarios := []struct {
message string
data any
@@ -129,6 +139,8 @@ func TestNewForbiddenError(t *testing.T) {
}
func TestNewUnauthorizedError(t *testing.T) {
t.Parallel()
scenarios := []struct {
message string
data any
+12
View File
@@ -17,6 +17,8 @@ import (
)
func TestBackupsList(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -84,6 +86,8 @@ func TestBackupsList(t *testing.T) {
}
func TestBackupsCreate(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -198,6 +202,8 @@ func TestBackupsCreate(t *testing.T) {
}
func TestBackupsUpload(t *testing.T) {
t.Parallel()
// create dummy form data bodies
type body struct {
buffer io.Reader
@@ -330,6 +336,8 @@ func TestBackupsUpload(t *testing.T) {
}
func TestBackupsDownload(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -485,6 +493,8 @@ func TestBackupsDownload(t *testing.T) {
}
func TestBackupsDelete(t *testing.T) {
t.Parallel()
noTestBackupFilesChanges := func(t *testing.T, app *tests.TestApp) {
files, err := getBackupFiles(app)
if err != nil {
@@ -645,6 +655,8 @@ func TestBackupsDelete(t *testing.T) {
}
func TestBackupsRestore(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
+10
View File
@@ -15,6 +15,8 @@ import (
)
func Test404(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Method: http.MethodGet,
@@ -53,6 +55,8 @@ func Test404(t *testing.T) {
}
func TestCustomRoutesAndErrorsHandling(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "custom route",
@@ -142,6 +146,8 @@ func TestCustomRoutesAndErrorsHandling(t *testing.T) {
}
func TestRemoveTrailingSlashMiddleware(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "non /api/* route (exact match)",
@@ -215,6 +221,8 @@ func TestRemoveTrailingSlashMiddleware(t *testing.T) {
}
func TestEagerRequestInfoCache(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "custom non-api group route",
@@ -316,6 +324,8 @@ func TestEagerRequestInfoCache(t *testing.T) {
}
func TestErrorHandler(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "apis.ApiError",
+12
View File
@@ -17,6 +17,8 @@ import (
)
func TestCollectionsList(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -125,6 +127,8 @@ func TestCollectionsList(t *testing.T) {
}
func TestCollectionView(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -193,6 +197,8 @@ func TestCollectionView(t *testing.T) {
}
func TestCollectionDelete(t *testing.T) {
t.Parallel()
ensureDeletedFiles := func(app *tests.TestApp, collectionId string) {
storageDir := filepath.Join(app.DataDir(), "storage", collectionId)
@@ -338,6 +344,8 @@ func TestCollectionDelete(t *testing.T) {
}
func TestCollectionCreate(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -715,6 +723,8 @@ func TestCollectionCreate(t *testing.T) {
}
func TestCollectionUpdate(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -1082,6 +1092,8 @@ func TestCollectionUpdate(t *testing.T) {
}
func TestCollectionsImport(t *testing.T) {
t.Parallel()
totalCollections := 11
scenarios := []tests.ApiScenario{
+6
View File
@@ -20,6 +20,8 @@ import (
)
func TestFileToken(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -92,6 +94,8 @@ func TestFileToken(t *testing.T) {
}
func TestFileDownload(t *testing.T) {
t.Parallel()
_, currentFile, _, _ := runtime.Caller(0)
dataDirRelPath := "../tests/data/"
@@ -391,6 +395,8 @@ func TestFileDownload(t *testing.T) {
}
func TestConcurrentThumbsGeneration(t *testing.T) {
t.Parallel()
app, err := tests.NewTestApp()
if err != nil {
t.Fatal(err)
+2
View File
@@ -8,6 +8,8 @@ import (
)
func TestHealthAPI(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "health status returns 200",
+6
View File
@@ -9,6 +9,8 @@ import (
)
func TestLogsList(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -78,6 +80,8 @@ func TestLogsList(t *testing.T) {
}
func TestLogView(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -136,6 +140,8 @@ func TestLogView(t *testing.T) {
}
func TestLogsStats(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
+11 -3
View File
@@ -5,6 +5,7 @@ import (
"log/slog"
"net"
"net/http"
"net/url"
"strings"
"time"
@@ -315,7 +316,7 @@ func logRequest(app core.App, c echo.Context, err *ApiError) {
httpResponse := c.Response()
method := strings.ToUpper(httpRequest.Method)
status := httpResponse.Status
url := httpRequest.URL.RequestURI()
requestUri := httpRequest.URL.RequestURI()
// parse the request error
if err != nil {
@@ -336,7 +337,7 @@ func logRequest(app core.App, c echo.Context, err *ApiError) {
attrs = append(
attrs,
slog.String("url", url),
slog.String("url", requestUri),
slog.String("method", method),
slog.Int("status", status),
slog.String("auth", requestAuth),
@@ -355,7 +356,14 @@ func logRequest(app core.App, c echo.Context, err *ApiError) {
// don't block on logs write
routine.FireAndForget(func() {
message := method + " " + url
message := method + " "
if escaped, err := url.PathUnescape(requestUri); err == nil {
message += escaped
} else {
message += requestUri
}
if err != nil {
app.Logger().Error(message, attrs...)
} else {
+16
View File
@@ -10,6 +10,8 @@ import (
)
func TestRequireGuestOnly(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "valid record token",
@@ -104,6 +106,8 @@ func TestRequireGuestOnly(t *testing.T) {
}
func TestRequireRecordAuth(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "guest",
@@ -242,6 +246,8 @@ func TestRequireRecordAuth(t *testing.T) {
}
func TestRequireSameContextRecordAuth(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "guest",
@@ -358,6 +364,8 @@ func TestRequireSameContextRecordAuth(t *testing.T) {
}
func TestRequireAdminAuth(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "guest",
@@ -452,6 +460,8 @@ func TestRequireAdminAuth(t *testing.T) {
}
func TestRequireAdminAuthOnlyIfAny(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "guest (while having at least 1 existing admin)",
@@ -571,6 +581,8 @@ func TestRequireAdminAuthOnlyIfAny(t *testing.T) {
}
func TestRequireAdminOrRecordAuth(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "guest",
@@ -731,6 +743,8 @@ func TestRequireAdminOrRecordAuth(t *testing.T) {
}
func TestRequireAdminOrOwnerAuth(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "guest",
@@ -869,6 +883,8 @@ func TestRequireAdminOrOwnerAuth(t *testing.T) {
}
func TestLoadCollectionContext(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "missing collection",
+4
View File
@@ -568,6 +568,10 @@ func (api *realtimeApi) canAccessRecord(
return true // no further checks needed
}
if err := checkForAdminOnlyRuleFields(requestInfo); err != nil {
return false
}
ruleFunc := func(q *dbx.SelectQuery) error {
resolver := resolvers.NewRecordFieldResolver(api.app.Dao(), record.Collection(), requestInfo, false)
+4
View File
@@ -5,6 +5,7 @@ import (
"net/http"
"strings"
"testing"
"time"
"github.com/labstack/echo/v5"
"github.com/pocketbase/dbx"
@@ -22,6 +23,7 @@ func TestRealtimeConnect(t *testing.T) {
{
Method: http.MethodGet,
Url: "/api/realtime",
Timeout: 100 * time.Millisecond,
ExpectedStatus: 200,
ExpectedContent: []string{
`id:`,
@@ -44,6 +46,7 @@ func TestRealtimeConnect(t *testing.T) {
Name: "PB_CONNECT interrupt",
Method: http.MethodGet,
Url: "/api/realtime",
Timeout: 100 * time.Millisecond,
ExpectedStatus: 200,
ExpectedEvents: map[string]int{
"OnRealtimeConnectRequest": 1,
@@ -68,6 +71,7 @@ func TestRealtimeConnect(t *testing.T) {
Name: "Skipping/ignoring messages",
Method: http.MethodGet,
Url: "/api/realtime",
Timeout: 100 * time.Millisecond,
ExpectedStatus: 200,
ExpectedEvents: map[string]int{
"OnRealtimeConnectRequest": 1,
+24
View File
@@ -17,6 +17,8 @@ import (
)
func TestRecordAuthMethodsList(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "missing collection",
@@ -71,6 +73,8 @@ func TestRecordAuthMethodsList(t *testing.T) {
}
func TestRecordAuthWithPassword(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "invalid body format",
@@ -356,6 +360,8 @@ func TestRecordAuthWithPassword(t *testing.T) {
}
func TestRecordAuthRefresh(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -483,6 +489,8 @@ func TestRecordAuthRefresh(t *testing.T) {
}
func TestRecordAuthRequestPasswordReset(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "not an auth collection",
@@ -568,6 +576,8 @@ func TestRecordAuthRequestPasswordReset(t *testing.T) {
}
func TestRecordAuthConfirmPasswordReset(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "empty data",
@@ -681,6 +691,8 @@ func TestRecordAuthConfirmPasswordReset(t *testing.T) {
}
func TestRecordAuthRequestVerification(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "not an auth collection",
@@ -774,6 +786,8 @@ func TestRecordAuthRequestVerification(t *testing.T) {
}
func TestRecordAuthConfirmVerification(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "empty data",
@@ -902,6 +916,8 @@ func TestRecordAuthConfirmVerification(t *testing.T) {
}
func TestRecordAuthRequestEmailChange(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -1004,6 +1020,8 @@ func TestRecordAuthRequestEmailChange(t *testing.T) {
}
func TestRecordAuthConfirmEmailChange(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "not an auth collection",
@@ -1124,6 +1142,8 @@ func TestRecordAuthConfirmEmailChange(t *testing.T) {
}
func TestRecordAuthListExternalsAuths(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -1224,6 +1244,8 @@ func TestRecordAuthListExternalsAuths(t *testing.T) {
}
func TestRecordAuthUnlinkExternalsAuth(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -1353,6 +1375,8 @@ func TestRecordAuthUnlinkExternalsAuth(t *testing.T) {
}
func TestRecordAuthOAuth2Redirect(t *testing.T) {
t.Parallel()
c1 := subscriptions.NewDefaultClient()
c2 := subscriptions.NewDefaultClient()
+3 -22
View File
@@ -4,7 +4,6 @@ import (
"fmt"
"log/slog"
"net/http"
"strings"
"github.com/labstack/echo/v5"
"github.com/pocketbase/dbx"
@@ -43,13 +42,13 @@ func (api *recordApi) list(c echo.Context) error {
return NewNotFoundError("", "Missing collection context.")
}
requestInfo := RequestInfo(c)
// forbid users and guests to query special filter/sort fields
if err := api.checkForForbiddenQueryFields(c); err != nil {
if err := checkForAdminOnlyRuleFields(requestInfo); err != nil {
return err
}
requestInfo := RequestInfo(c)
if requestInfo.Admin == nil && collection.ListRule == nil {
// only admins can access if the rule is nil
return NewForbiddenError("Only admins can perform this action.", nil)
@@ -409,21 +408,3 @@ func (api *recordApi) delete(c echo.Context) error {
})
})
}
func (api *recordApi) checkForForbiddenQueryFields(c echo.Context) error {
admin, _ := c.Get(ContextAdminKey).(*models.Admin)
if admin != nil {
return nil // admins are allowed to query everything
}
decodedQuery := c.QueryParam(search.FilterQueryParam) + c.QueryParam(search.SortQueryParam)
forbiddenFields := []string{"@collection.", "@request."}
for _, field := range forbiddenFields {
if strings.Contains(decodedQuery, field) {
return NewForbiddenError("Only admins can filter by @collection and @request query params", nil)
}
}
return nil
}
+19 -2
View File
@@ -17,6 +17,8 @@ import (
)
func TestRecordCrudList(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "missing collection",
@@ -43,9 +45,16 @@ func TestRecordCrudList(t *testing.T) {
ExpectedContent: []string{`"data":{}`},
},
{
Name: "public collection but with admin only filter/sort (aka. @collection)",
Name: "public collection but with admin only filter param (aka. @collection, @request, etc.)",
Method: http.MethodGet,
Url: "/api/collections/demo2/records?filter=@collection.demo2.title='test1'",
Url: "/api/collections/demo2/records?filter=%40collection.demo2.title='test1'",
ExpectedStatus: 403,
ExpectedContent: []string{`"data":{}`},
},
{
Name: "public collection but with admin only sort param (aka. @collection, @request, etc.)",
Method: http.MethodGet,
Url: "/api/collections/demo2/records?sort=@request.auth.title",
ExpectedStatus: 403,
ExpectedContent: []string{`"data":{}`},
},
@@ -486,6 +495,8 @@ func TestRecordCrudList(t *testing.T) {
}
func TestRecordCrudView(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "missing collection",
@@ -765,6 +776,8 @@ func TestRecordCrudView(t *testing.T) {
}
func TestRecordCrudDelete(t *testing.T) {
t.Parallel()
ensureDeletedFiles := func(app *tests.TestApp, collectionId string, recordId string) {
storageDir := filepath.Join(app.DataDir(), "storage", collectionId, recordId)
@@ -1008,6 +1021,8 @@ func TestRecordCrudDelete(t *testing.T) {
}
func TestRecordCrudCreate(t *testing.T) {
t.Parallel()
formData, mp, err := tests.MockMultipartData(map[string]string{
"title": "title_test",
}, "files")
@@ -1584,6 +1599,8 @@ func TestRecordCrudCreate(t *testing.T) {
}
func TestRecordCrudUpdate(t *testing.T) {
t.Parallel()
formData, mp, err := tests.MockMultipartData(map[string]string{
"title": "title_test",
}, "files")
+28
View File
@@ -314,3 +314,31 @@ func hasAuthManageAccess(
return findErr == nil
}
var ruleQueryParams = []string{search.FilterQueryParam, search.SortQueryParam}
var adminOnlyRuleFields = []string{"@collection.", "@request."}
// @todo consider moving the rules check to the RecordFieldResolver.
//
// checkForAdminOnlyRuleFields loosely checks and returns an error if
// the provided RequestInfo contains rule fields that only the admin can use.
func checkForAdminOnlyRuleFields(requestInfo *models.RequestInfo) error {
if requestInfo.Admin != nil || len(requestInfo.Query) == 0 {
return nil // admin or nothing to check
}
for _, param := range ruleQueryParams {
v, _ := requestInfo.Query[param].(string)
if v == "" {
continue
}
for _, field := range adminOnlyRuleFields {
if strings.Contains(v, field) {
return NewForbiddenError("Only admins can filter by "+field, nil)
}
}
}
return nil
}
+6
View File
@@ -14,6 +14,8 @@ import (
)
func TestRequestInfo(t *testing.T) {
t.Parallel()
e := echo.New()
req := httptest.NewRequest(http.MethodPost, "/?test=123", strings.NewReader(`{"test":456}`))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
@@ -67,6 +69,8 @@ func TestRequestInfo(t *testing.T) {
}
func TestRecordAuthResponse(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -189,6 +193,8 @@ func TestRecordAuthResponse(t *testing.T) {
}
func TestEnrichRecords(t *testing.T) {
t.Parallel()
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/?expand=rel_many", nil)
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
+10
View File
@@ -18,6 +18,8 @@ import (
)
func TestSettingsList(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -97,6 +99,8 @@ func TestSettingsList(t *testing.T) {
}
func TestSettingsSet(t *testing.T) {
t.Parallel()
validData := `{"meta":{"appName":"update_test"}}`
scenarios := []tests.ApiScenario{
@@ -278,6 +282,8 @@ func TestSettingsSet(t *testing.T) {
}
func TestSettingsTestS3(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -344,6 +350,8 @@ func TestSettingsTestS3(t *testing.T) {
}
func TestSettingsTestEmail(t *testing.T) {
t.Parallel()
scenarios := []tests.ApiScenario{
{
Name: "unauthorized",
@@ -508,6 +516,8 @@ func TestSettingsTestEmail(t *testing.T) {
}
func TestGenerateAppleClientSecret(t *testing.T) {
t.Parallel()
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
+2
View File
@@ -8,6 +8,8 @@ import (
)
func TestAdminCreateCommand(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+5 -2
View File
@@ -59,6 +59,9 @@ type App interface {
// (used for settings encryption).
EncryptionEnv() string
// IsDev returns whether the app is in dev mode.
IsDev() bool
// Settings returns the loaded app settings.
Settings() *settings.Settings
@@ -288,14 +291,14 @@ type App interface {
// SSE client connection.
OnRealtimeDisconnectRequest() *hook.Hook[*RealtimeDisconnectEvent]
// OnRealtimeBeforeMessage hook is triggered right before sending
// OnRealtimeBeforeMessageSend hook is triggered right before sending
// an SSE message to a client.
//
// Returning [hook.StopPropagation] will prevent sending the message.
// Returning any other non-nil error will close the realtime connection.
OnRealtimeBeforeMessageSend() *hook.Hook[*RealtimeMessageEvent]
// OnRealtimeBeforeMessage hook is triggered right after sending
// OnRealtimeAfterMessageSend hook is triggered right after sending
// an SSE message to a client.
OnRealtimeAfterMessageSend() *hook.Hook[*RealtimeMessageEvent]
+45 -16
View File
@@ -47,6 +47,7 @@ type BaseApp struct {
// @todo consider introducing a mutex to allow safe concurrent config changes during runtime
// configurable parameters
isDev bool
dataDir string
encryptionEnv string
dataMaxOpenConns int
@@ -174,6 +175,7 @@ type BaseApp struct {
// BaseAppConfig defines a BaseApp configuration option
type BaseAppConfig struct {
IsDev bool
DataDir string
EncryptionEnv string
DataMaxOpenConns int // default to 500
@@ -188,6 +190,7 @@ type BaseAppConfig struct {
// To initialize the app, you need to call `app.Bootstrap()`.
func NewBaseApp(config BaseAppConfig) *BaseApp {
app := &BaseApp{
isDev: config.IsDev,
dataDir: config.DataDir,
encryptionEnv: config.EncryptionEnv,
dataMaxOpenConns: config.DataMaxOpenConns,
@@ -458,6 +461,13 @@ func (app *BaseApp) EncryptionEnv() string {
return app.encryptionEnv
}
// IsDev returns whether the app is in dev mode.
//
// When enabled logs, executed sql statements, etc. are printed to the stderr.
func (app *BaseApp) IsDev() bool {
return app.isDev
}
// Settings returns the loaded app settings.
func (app *BaseApp) Settings() *settings.Settings {
return app.settings
@@ -589,9 +599,11 @@ func (app *BaseApp) RefreshSettings() error {
return err
}
// reload handler level (if initialized)
if h, ok := app.Logger().Handler().(*logger.BatchHandler); ok {
h.SetLevel(slog.Level(app.settings.Logs.MinLevel))
// reload handler level (if initialized and not in dev mode)
if !app.IsDev() && app.Logger() != nil {
if h, ok := app.Logger().Handler().(*logger.BatchHandler); ok {
h.SetLevel(slog.Level(app.settings.Logs.MinLevel))
}
}
return nil
@@ -1054,15 +1066,16 @@ func (app *BaseApp) initDataDB() error {
nonconcurrentDB.DB().SetMaxIdleConns(1)
nonconcurrentDB.DB().SetConnMaxIdleTime(3 * time.Minute)
// @todo benchmark whether it will have an impact if always enabled as TRACE log
// nonconcurrentDB.QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {
// color.HiBlack("[%.2fms] %v\n", float64(t.Milliseconds()), sql)
// }
// concurrentDB.QueryLogFunc = nonconcurrentDB.QueryLogFunc
// nonconcurrentDB.ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {
// color.HiBlack("[%.2fms] %v\n", float64(t.Milliseconds()), sql)
// }
// concurrentDB.ExecLogFunc = nonconcurrentDB.ExecLogFunc
if app.IsDev() {
nonconcurrentDB.QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {
color.HiBlack("[%.2fms] %v\n", float64(t.Milliseconds()), sql)
}
nonconcurrentDB.ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {
color.HiBlack("[%.2fms] %v\n", float64(t.Milliseconds()), sql)
}
concurrentDB.QueryLogFunc = nonconcurrentDB.QueryLogFunc
concurrentDB.ExecLogFunc = nonconcurrentDB.ExecLogFunc
}
app.dao = app.createDaoWithHooks(concurrentDB, nonconcurrentDB)
@@ -1176,15 +1189,31 @@ func (app *BaseApp) initLogger() error {
ticker := time.NewTicker(duration)
done := make(chan bool)
level := slog.LevelInfo
if app.Settings() != nil {
level = slog.Level(app.Settings().Logs.MinLevel)
// Apply the min level only if it is not in develop
// to allow printing the logs to the console.
//
// DB logs are still filtered but the checks for the min level are done
// in the BatchOptions.BeforeAddFunc instead of the slog.Handler.Enabled() method.
var minLevel slog.Level
if app.IsDev() {
minLevel = -9999
} else if app.Settings() != nil {
minLevel = slog.Level(app.Settings().Logs.MinLevel)
}
handler := logger.NewBatchHandler(logger.BatchOptions{
Level: level,
Level: minLevel,
BatchSize: 200,
BeforeAddFunc: func(ctx context.Context, log *logger.Log) bool {
if app.IsDev() {
printLog(log)
// manually check the log level and skip if necessary
if log.Level < slog.Level(app.Settings().Logs.MinLevel) {
return false
}
}
ticker.Reset(duration)
return app.Settings().Logs.MaxDays > 0
+253 -46
View File
@@ -1,11 +1,17 @@
package core
import (
"fmt"
"log/slog"
"os"
"testing"
"time"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/migrations/logs"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/tools/list"
"github.com/pocketbase/pocketbase/tools/logger"
"github.com/pocketbase/pocketbase/tools/mailer"
"github.com/pocketbase/pocketbase/tools/migrate"
@@ -18,6 +24,7 @@ func TestNewBaseApp(t *testing.T) {
app := NewBaseApp(BaseAppConfig{
DataDir: testDataDir,
EncryptionEnv: "test_env",
IsDev: true,
})
if app.dataDir != testDataDir {
@@ -28,6 +35,10 @@ func TestNewBaseApp(t *testing.T) {
t.Fatalf("expected encryptionEnv test_env, got %q", app.dataDir)
}
if !app.isDev {
t.Fatalf("expected isDev true, got %v", app.isDev)
}
if app.store == nil {
t.Fatal("expected store to be set, got nil")
}
@@ -132,6 +143,7 @@ func TestBaseAppGetters(t *testing.T) {
app := NewBaseApp(BaseAppConfig{
DataDir: testDataDir,
EncryptionEnv: "pb_test_env",
IsDev: true,
})
defer app.ResetBootstrapState()
@@ -163,6 +175,10 @@ func TestBaseAppGetters(t *testing.T) {
t.Fatalf("Expected app.EncryptionEnv %v, got %v", app.EncryptionEnv(), app.encryptionEnv)
}
if app.isDev != app.IsDev() {
t.Fatalf("Expected app.IsDev %v, got %v", app.IsDev(), app.isDev)
}
if app.settings != app.Settings() {
t.Fatalf("Expected app.Settings %v, got %v", app.Settings(), app.settings)
}
@@ -185,13 +201,11 @@ func TestBaseAppGetters(t *testing.T) {
}
func TestBaseAppNewMailClient(t *testing.T) {
const testDataDir = "./pb_base_app_test_data_dir/"
defer os.RemoveAll(testDataDir)
app := NewBaseApp(BaseAppConfig{
DataDir: testDataDir,
EncryptionEnv: "pb_test_env",
})
app, cleanup, err := initTestBaseApp()
if err != nil {
t.Fatal(err)
}
defer cleanup()
client1 := app.NewMailClient()
if val, ok := client1.(*mailer.Sendmail); !ok {
@@ -207,13 +221,11 @@ func TestBaseAppNewMailClient(t *testing.T) {
}
func TestBaseAppNewFilesystem(t *testing.T) {
const testDataDir = "./pb_base_app_test_data_dir/"
defer os.RemoveAll(testDataDir)
app := NewBaseApp(BaseAppConfig{
DataDir: testDataDir,
EncryptionEnv: "pb_test_env",
})
app, cleanup, err := initTestBaseApp()
if err != nil {
t.Fatal(err)
}
defer cleanup()
// local
local, localErr := app.NewFilesystem()
@@ -236,13 +248,11 @@ func TestBaseAppNewFilesystem(t *testing.T) {
}
func TestBaseAppNewBackupsFilesystem(t *testing.T) {
const testDataDir = "./pb_base_app_test_data_dir/"
defer os.RemoveAll(testDataDir)
app := NewBaseApp(BaseAppConfig{
DataDir: testDataDir,
EncryptionEnv: "pb_test_env",
})
app, cleanup, err := initTestBaseApp()
if err != nil {
t.Fatal(err)
}
defer cleanup()
// local
local, localErr := app.NewBackupsFilesystem()
@@ -265,31 +275,25 @@ func TestBaseAppNewBackupsFilesystem(t *testing.T) {
}
func TestBaseAppLoggerWrites(t *testing.T) {
testDataDir, err := os.MkdirTemp("", "logger_writes")
app, cleanup, err := initTestBaseApp()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(testDataDir)
app := NewBaseApp(BaseAppConfig{
DataDir: testDataDir,
})
if err := app.Bootstrap(); err != nil {
t.Fatal(err)
}
// init logs migrations
runner, err := migrate.NewRunner(app.LogsDB(), logs.LogsMigrations)
if err != nil {
t.Fatalf("Logs runner error: %v", err)
}
if _, err := runner.Up(); err != nil {
t.Fatalf("Logs migration execution error: %v", err)
}
defer cleanup()
threshold := 200
totalLogs := func(app App, t *testing.T) int {
var total int
err := app.LogsDao().LogQuery().Select("count(*)").Row(&total)
if err != nil {
t.Fatalf("Failed to fetch total logs: %v", err)
}
return total
}
// disabled logs retention
{
app.Settings().Logs.MaxDays = 0
@@ -333,13 +337,216 @@ func TestBaseAppLoggerWrites(t *testing.T) {
}
}
func totalLogs(app App, t *testing.T) int {
var total int
err := app.LogsDao().LogQuery().Select("count(*)").Row(&total)
func TestBaseAppRefreshSettingsLoggerMinLevelEnabled(t *testing.T) {
app, cleanup, err := initTestBaseApp()
if err != nil {
t.Fatalf("Failed to fetch total logs: %v", err)
t.Fatal(err)
}
defer cleanup()
handler, ok := app.Logger().Handler().(*logger.BatchHandler)
if !ok {
t.Fatalf("Expected BatchHandler, got %v", app.Logger().Handler())
}
return total
scenarios := []struct {
name string
isDev bool
level int
// level->enabled map
expectations map[int]bool
}{
{
"dev mode",
true,
4,
map[int]bool{
3: true,
4: true,
5: true,
},
},
{
"nondev mode",
false,
4,
map[int]bool{
3: false,
4: true,
5: true,
},
},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
app.isDev = s.isDev
app.Settings().Logs.MinLevel = s.level
if err := app.Dao().SaveSettings(app.Settings()); err != nil {
t.Fatalf("Failed to save settings: %v", err)
}
if err := app.RefreshSettings(); err != nil {
t.Fatalf("Failed to refresh app settings: %v", err)
}
for level, enabled := range s.expectations {
if v := handler.Enabled(nil, slog.Level(level)); v != enabled {
t.Fatalf("Expected level %d Enabled() to be %v, got %v", level, enabled, v)
}
}
})
}
}
func TestBaseAppLoggerLevelDevPrint(t *testing.T) {
app, cleanup, err := initTestBaseApp()
if err != nil {
t.Fatal(err)
}
defer cleanup()
testLogLevel := 4
app.Settings().Logs.MinLevel = testLogLevel
if err := app.Dao().SaveSettings(app.Settings()); err != nil {
t.Fatal(err)
}
scenarios := []struct {
name string
isDev bool
levels []int
printedLevels []int
persistedLevels []int
}{
{
"dev mode",
true,
[]int{testLogLevel - 1, testLogLevel, testLogLevel + 1},
[]int{testLogLevel - 1, testLogLevel, testLogLevel + 1},
[]int{testLogLevel, testLogLevel + 1},
},
{
"nondev mode",
false,
[]int{testLogLevel - 1, testLogLevel, testLogLevel + 1},
[]int{},
[]int{testLogLevel, testLogLevel + 1},
},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
var printedLevels []int
var persistedLevels []int
app.isDev = s.isDev
// trigger slog handler min level refresh
if err := app.RefreshSettings(); err != nil {
t.Fatal(err)
}
// track printed logs
originalPrintLog := printLog
defer func() {
printLog = originalPrintLog
}()
printLog = func(log *logger.Log) {
printedLevels = append(printedLevels, int(log.Level))
}
// track persisted logs
app.LogsDao().AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) error {
l, ok := m.(*models.Log)
if ok {
persistedLevels = append(persistedLevels, l.Level)
}
return nil
}
// write and persist logs
for _, l := range s.levels {
app.Logger().Log(nil, slog.Level(l), "test")
}
handler, ok := app.Logger().Handler().(*logger.BatchHandler)
if !ok {
t.Fatalf("Expected BatchHandler, got %v", app.Logger().Handler())
}
if err := handler.WriteAll(nil); err != nil {
t.Fatalf("Failed to write all logs: %v", err)
}
// check persisted log levels
if len(s.persistedLevels) != len(persistedLevels) {
t.Fatalf("Expected persisted levels \n%v\ngot\n%v", s.persistedLevels, persistedLevels)
}
for _, l := range persistedLevels {
if !list.ExistInSlice(l, s.persistedLevels) {
t.Fatalf("Missing expected persisted level %v in %v", l, persistedLevels)
}
}
// check printed log levels
if len(s.printedLevels) != len(printedLevels) {
t.Fatalf("Expected printed levels \n%v\ngot\n%v", s.printedLevels, printedLevels)
}
for _, l := range printedLevels {
if !list.ExistInSlice(l, s.printedLevels) {
t.Fatalf("Missing expected printed level %v in %v", l, printedLevels)
}
}
})
}
}
// -------------------------------------------------------------------
// note: make sure to call `defer cleanup()` when the app is no longer needed.
func initTestBaseApp() (app *BaseApp, cleanup func(), err error) {
testDataDir, err := os.MkdirTemp("", "test_base_app")
if err != nil {
return nil, nil, err
}
cleanup = func() {
os.RemoveAll(testDataDir)
}
app = NewBaseApp(BaseAppConfig{
DataDir: testDataDir,
})
initErr := func() error {
if err := app.Bootstrap(); err != nil {
return fmt.Errorf("bootstrap error: %w", err)
}
logsRunner, err := migrate.NewRunner(app.LogsDB(), logs.LogsMigrations)
if err != nil {
return fmt.Errorf("logsRunner error: %w", err)
}
if _, err := logsRunner.Up(); err != nil {
return fmt.Errorf("logsRunner migrations execution error: %w", err)
}
dataRunner, err := migrate.NewRunner(app.DB(), migrations.AppMigrations)
if err != nil {
return fmt.Errorf("logsRunner error: %w", err)
}
if _, err := dataRunner.Up(); err != nil {
return fmt.Errorf("dataRunner migrations execution error: %w", err)
}
return nil
}()
if initErr != nil {
cleanup()
return nil, nil, initErr
}
return app, cleanup, nil
}
+67
View File
@@ -0,0 +1,67 @@
package core
import (
"fmt"
"log/slog"
"strings"
"github.com/fatih/color"
"github.com/pocketbase/pocketbase/tools/logger"
"github.com/pocketbase/pocketbase/tools/store"
"github.com/spf13/cast"
)
var cachedColors = store.New[*color.Color](nil)
// getColor returns [color.Color] object and cache it (if not already).
func getColor(attrs ...color.Attribute) (c *color.Color) {
cacheKey := fmt.Sprint(attrs)
if c = cachedColors.Get(cacheKey); c == nil {
c = color.New(attrs...)
cachedColors.Set(cacheKey, c)
}
return
}
// printLog prints the provided log to the stderr.
// (note: defined as variable to overwriting in the tests)
var printLog = func(log *logger.Log) {
var str strings.Builder
switch log.Level {
case slog.LevelDebug:
str.WriteString(getColor(color.Bold, color.FgHiBlack).Sprint("DEBUG "))
str.WriteString(getColor(color.FgWhite).Sprint(log.Message))
case slog.LevelInfo:
str.WriteString(getColor(color.Bold, color.FgWhite).Sprint("INFO "))
str.WriteString(getColor(color.FgWhite).Sprint(log.Message))
case slog.LevelWarn:
str.WriteString(getColor(color.Bold, color.FgYellow).Sprint("WARN "))
str.WriteString(getColor(color.FgYellow).Sprint(log.Message))
case slog.LevelError:
str.WriteString(getColor(color.Bold, color.FgRed).Sprint("ERROR "))
str.WriteString(getColor(color.FgRed).Sprint(log.Message))
default:
str.WriteString(getColor(color.Bold, color.FgCyan).Sprintf("[%d] ", log.Level))
str.WriteString(getColor(color.FgCyan).Sprint(log.Message))
}
str.WriteString("\n")
if v, ok := log.Data["type"]; ok && cast.ToString(v) == "request" {
padding := 0
keys := []string{"error", "details"}
for _, k := range keys {
if v := log.Data[k]; v != nil {
str.WriteString(getColor(color.FgHiRed).Sprintf("%s└─ %v", strings.Repeat(" ", padding), v))
str.WriteString("\n")
padding += 3
}
}
} else if len(log.Data) > 0 {
str.WriteString(getColor(color.FgHiBlack).Sprintf("└─ %v", log.Data))
str.WriteString("\n")
}
fmt.Print(str.String())
}
+2 -2
View File
@@ -46,9 +46,9 @@ func (dao *Dao) FindAdminByEmail(email string) (*models.Admin, error) {
return model, nil
}
// FindAdminByToken finds the admin associated with the provided JWT token.
// FindAdminByToken finds the admin associated with the provided JWT.
//
// Returns an error if the JWT token is invalid or expired.
// Returns an error if the JWT is invalid or expired.
func (dao *Dao) FindAdminByToken(token string, baseTokenKey string) (*models.Admin, error) {
// @todo consider caching the unverified claims
unverifiedClaims, err := security.ParseUnverifiedJWT(token)
+16
View File
@@ -8,6 +8,8 @@ import (
)
func TestAdminQuery(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -20,6 +22,8 @@ func TestAdminQuery(t *testing.T) {
}
func TestFindAdminById(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -47,6 +51,8 @@ func TestFindAdminById(t *testing.T) {
}
func TestFindAdminByEmail(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -76,6 +82,8 @@ func TestFindAdminByEmail(t *testing.T) {
}
func TestFindAdminByToken(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -131,6 +139,8 @@ func TestFindAdminByToken(t *testing.T) {
}
func TestTotalAdmins(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -155,6 +165,8 @@ func TestTotalAdmins(t *testing.T) {
}
func TestIsAdminEmailUnique(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -180,6 +192,8 @@ func TestIsAdminEmailUnique(t *testing.T) {
}
func TestDeleteAdmin(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -225,6 +239,8 @@ func TestDeleteAdmin(t *testing.T) {
}
func TestSaveAdmin(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+4
View File
@@ -6,6 +6,8 @@ import (
)
func TestGetDefaultRetryInterval(t *testing.T) {
t.Parallel()
if i := getDefaultRetryInterval(-1); i.Milliseconds() != 1000 {
t.Fatalf("Expected 1000ms, got %v", i)
}
@@ -20,6 +22,8 @@ func TestGetDefaultRetryInterval(t *testing.T) {
}
func TestBaseLockRetry(t *testing.T) {
t.Parallel()
scenarios := []struct {
err error
failUntilAttempt int
+22
View File
@@ -16,6 +16,8 @@ import (
)
func TestCollectionQuery(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -28,6 +30,8 @@ func TestCollectionQuery(t *testing.T) {
}
func TestFindCollectionsByType(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -63,6 +67,8 @@ func TestFindCollectionsByType(t *testing.T) {
}
func TestFindCollectionByNameOrId(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -92,6 +98,8 @@ func TestFindCollectionByNameOrId(t *testing.T) {
}
func TestIsCollectionNameUnique(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -116,6 +124,8 @@ func TestIsCollectionNameUnique(t *testing.T) {
}
func TestFindCollectionReferences(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -164,6 +174,8 @@ func TestFindCollectionReferences(t *testing.T) {
}
func TestDeleteCollection(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -251,6 +263,8 @@ func TestDeleteCollection(t *testing.T) {
}
func TestSaveCollectionCreate(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -297,6 +311,8 @@ func TestSaveCollectionCreate(t *testing.T) {
}
func TestSaveCollectionUpdate(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -336,6 +352,8 @@ func TestSaveCollectionUpdate(t *testing.T) {
// indirect update of a field used in view should cause view(s) update
func TestSaveCollectionIndirectViewsUpdate(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -395,6 +413,8 @@ func TestSaveCollectionIndirectViewsUpdate(t *testing.T) {
}
func TestSaveCollectionViewWrapping(t *testing.T) {
t.Parallel()
viewName := "test_wrapping"
scenarios := []struct {
@@ -504,6 +524,8 @@ func TestSaveCollectionViewWrapping(t *testing.T) {
}
func TestImportCollections(t *testing.T) {
t.Parallel()
totalCollections := 11
scenarios := []struct {
+12
View File
@@ -9,6 +9,8 @@ import (
)
func TestExternalAuthQuery(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -21,6 +23,8 @@ func TestExternalAuthQuery(t *testing.T) {
}
func TestFindAllExternalAuthsByRecord(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -58,6 +62,8 @@ func TestFindAllExternalAuthsByRecord(t *testing.T) {
}
func TestFindFirstExternalAuthByExpr(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -89,6 +95,8 @@ func TestFindFirstExternalAuthByExpr(t *testing.T) {
}
func TestFindExternalAuthByRecordAndProvider(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -125,6 +133,8 @@ func TestFindExternalAuthByRecordAndProvider(t *testing.T) {
}
func TestSaveExternalAuth(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -161,6 +171,8 @@ func TestSaveExternalAuth(t *testing.T) {
}
func TestDeleteExternalAuth(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+10
View File
@@ -12,6 +12,8 @@ import (
)
func TestLogQuery(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -24,6 +26,8 @@ func TestLogQuery(t *testing.T) {
}
func TestFindLogById(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -54,6 +58,8 @@ func TestFindLogById(t *testing.T) {
}
func TestLogsStats(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -75,6 +81,8 @@ func TestLogsStats(t *testing.T) {
}
func TestDeleteOldLogs(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -115,6 +123,8 @@ func TestDeleteOldLogs(t *testing.T) {
}
func TestSaveLog(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+10
View File
@@ -11,6 +11,8 @@ import (
)
func TestParamQuery(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -23,6 +25,8 @@ func TestParamQuery(t *testing.T) {
}
func TestFindParamByKey(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -50,6 +54,8 @@ func TestFindParamByKey(t *testing.T) {
}
func TestSaveParam(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -92,6 +98,8 @@ func TestSaveParam(t *testing.T) {
}
func TestSaveParamEncrypted(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -126,6 +134,8 @@ func TestSaveParamEncrypted(t *testing.T) {
}
func TestDeleteParam(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+20 -5
View File
@@ -5,6 +5,7 @@ import (
"database/sql"
"errors"
"fmt"
"sort"
"strings"
"github.com/pocketbase/dbx"
@@ -409,9 +410,9 @@ func (dao *Dao) IsRecordValueUnique(
return query.Row(&exists) == nil && !exists
}
// FindAuthRecordByToken finds the auth record associated with the provided JWT token.
// FindAuthRecordByToken finds the auth record associated with the provided JWT.
//
// Returns an error if the JWT token is invalid, expired or not associated to an auth collection record.
// Returns an error if the JWT is invalid, expired or not associated to an auth collection record.
func (dao *Dao) FindAuthRecordByToken(token string, baseTokenKey string) (*models.Record, error) {
unverifiedClaims, err := security.ParseUnverifiedJWT(token)
if err != nil {
@@ -660,9 +661,23 @@ func (dao *Dao) DeleteRecord(record *models.Record) error {
func (dao *Dao) cascadeRecordDelete(mainRecord *models.Record, refs map[*models.Collection][]*schema.SchemaField) error {
uniqueJsonEachAlias := "__je__" + security.PseudorandomString(4)
for refCollection, fields := range refs {
if refCollection.IsView() {
continue // skip view collections
// @todo consider changing refs to a slice
//
// Sort the refs keys to ensure that the cascade events firing order is always the same.
// This is not necessary for the operation to function correctly but it helps having deterministic output during testing.
sortedRefKeys := make([]*models.Collection, 0, len(refs))
for k := range refs {
sortedRefKeys = append(sortedRefKeys, k)
}
sort.Slice(sortedRefKeys, func(i, j int) bool {
return sortedRefKeys[i].Name < sortedRefKeys[j].Name
})
for _, refCollection := range sortedRefKeys {
fields, ok := refs[refCollection]
if refCollection.IsView() || !ok {
continue // skip missing or view collections
}
for _, field := range fields {
+6
View File
@@ -14,6 +14,8 @@ import (
)
func TestExpandRecords(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -222,6 +224,8 @@ func TestExpandRecords(t *testing.T) {
}
func TestExpandRecord(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -372,6 +376,8 @@ func TestExpandRecord(t *testing.T) {
}
func TestIndirectExpandSingeVsArrayResult(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+4
View File
@@ -14,6 +14,8 @@ import (
)
func TestSyncRecordTableSchema(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -132,6 +134,8 @@ func TestSyncRecordTableSchema(t *testing.T) {
}
func TestSingleVsMultipleValuesNormalization(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+51
View File
@@ -19,6 +19,8 @@ import (
)
func TestRecordQueryWithDifferentCollectionValues(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -58,6 +60,8 @@ func TestRecordQueryWithDifferentCollectionValues(t *testing.T) {
}
func TestRecordQueryOneWithRecord(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -82,6 +86,8 @@ func TestRecordQueryOneWithRecord(t *testing.T) {
}
func TestRecordQueryAllWithRecordsSlices(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -143,6 +149,8 @@ func TestRecordQueryAllWithRecordsSlices(t *testing.T) {
}
func TestFindRecordById(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -203,6 +211,8 @@ func TestFindRecordById(t *testing.T) {
}
func TestFindRecordsByIds(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -294,6 +304,8 @@ func TestFindRecordsByIds(t *testing.T) {
}
func TestFindRecordsByExpr(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -364,6 +376,8 @@ func TestFindRecordsByExpr(t *testing.T) {
}
func TestFindFirstRecordByData(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -427,6 +441,8 @@ func TestFindFirstRecordByData(t *testing.T) {
}
func TestFindRecordsByFilter(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -530,6 +546,19 @@ func TestFindRecordsByFilter(t *testing.T) {
"llvuca81nly1qls",
},
},
{
"with json filter and sort",
"demo4",
"json_object != null && json_object.a.b = 'test'",
"-json_object.a",
10,
0,
[]dbx.Params{{"active": false}},
false,
[]string{
"i9naidtvr6qsgb4",
},
},
}
for _, s := range scenarios {
@@ -566,6 +595,8 @@ func TestFindRecordsByFilter(t *testing.T) {
}
func TestFindFirstRecordByFilter(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -647,6 +678,8 @@ func TestFindFirstRecordByFilter(t *testing.T) {
}
func TestCanAccessRecord(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -814,6 +847,8 @@ func TestCanAccessRecord(t *testing.T) {
}
func TestIsRecordValueUnique(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -863,6 +898,8 @@ func TestIsRecordValueUnique(t *testing.T) {
}
func TestFindAuthRecordByToken(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -925,6 +962,8 @@ func TestFindAuthRecordByToken(t *testing.T) {
}
func TestFindAuthRecordByEmail(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -956,6 +995,8 @@ func TestFindAuthRecordByEmail(t *testing.T) {
}
func TestFindAuthRecordByUsername(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -988,6 +1029,8 @@ func TestFindAuthRecordByUsername(t *testing.T) {
}
func TestSuggestUniqueAuthRecordUsername(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -1025,6 +1068,8 @@ func TestSuggestUniqueAuthRecordUsername(t *testing.T) {
}
func TestSaveRecord(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -1058,6 +1103,8 @@ func TestSaveRecord(t *testing.T) {
}
func TestSaveRecordWithIdFromOtherCollection(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -1090,6 +1137,8 @@ func TestSaveRecordWithIdFromOtherCollection(t *testing.T) {
}
func TestDeleteRecord(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -1167,6 +1216,8 @@ func TestDeleteRecord(t *testing.T) {
}
func TestDeleteRecordBatchProcessing(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+2
View File
@@ -8,6 +8,8 @@ import (
)
func TestSaveAndFindSettings(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+12
View File
@@ -12,6 +12,8 @@ import (
)
func TestHasTable(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -36,6 +38,8 @@ func TestHasTable(t *testing.T) {
}
func TestTableColumns(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -64,6 +68,8 @@ func TestTableColumns(t *testing.T) {
}
func TestTableInfo(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -92,6 +98,8 @@ func TestTableInfo(t *testing.T) {
}
func TestDeleteTable(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -116,6 +124,8 @@ func TestDeleteTable(t *testing.T) {
}
func TestVacuum(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -141,6 +151,8 @@ func TestVacuum(t *testing.T) {
}
func TestTableIndexes(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+10
View File
@@ -33,6 +33,8 @@ func ensureNoTempViews(app core.App, t *testing.T) {
}
func TestDeleteView(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -60,6 +62,8 @@ func TestDeleteView(t *testing.T) {
}
func TestSaveView(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -180,6 +184,8 @@ func TestSaveView(t *testing.T) {
}
func TestCreateViewSchemaWithDiscardedNestedTransaction(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -196,6 +202,8 @@ func TestCreateViewSchemaWithDiscardedNestedTransaction(t *testing.T) {
}
func TestCreateViewSchema(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -486,6 +494,8 @@ func TestCreateViewSchema(t *testing.T) {
}
func TestFindRecordByViewFile(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+4
View File
@@ -10,6 +10,8 @@ import (
)
func TestAdminLoginValidateAndSubmit(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -51,6 +53,8 @@ func TestAdminLoginValidateAndSubmit(t *testing.T) {
}
func TestAdminLoginInterceptors(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -11,6 +11,8 @@ import (
)
func TestAdminPasswordResetConfirmValidateAndSubmit(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -99,6 +101,8 @@ func TestAdminPasswordResetConfirmValidateAndSubmit(t *testing.T) {
}
func TestAdminPasswordResetConfirmInterceptors(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -10,6 +10,8 @@ import (
)
func TestAdminPasswordResetRequestValidateAndSubmit(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -74,6 +76,8 @@ func TestAdminPasswordResetRequestValidateAndSubmit(t *testing.T) {
}
func TestAdminPasswordResetRequestInterceptors(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
+8
View File
@@ -12,6 +12,8 @@ import (
)
func TestNewAdminUpsert(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -31,6 +33,8 @@ func TestNewAdminUpsert(t *testing.T) {
}
func TestAdminUpsertValidateAndSubmit(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -183,6 +187,8 @@ func TestAdminUpsertValidateAndSubmit(t *testing.T) {
}
func TestAdminUpsertSubmitInterceptors(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -231,6 +237,8 @@ func TestAdminUpsertSubmitInterceptors(t *testing.T) {
}
func TestAdminUpsertWithCustomId(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+2 -2
View File
@@ -12,7 +12,7 @@ import (
var privateKeyRegex = regexp.MustCompile(`(?m)-----BEGIN PRIVATE KEY----[\s\S]+-----END PRIVATE KEY-----`)
// AppleClientSecretCreate is a [models.Admin] upsert (create/update) form.
// AppleClientSecretCreate is a form struct to generate a new Apple Client Secret.
//
// Reference: https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
type AppleClientSecretCreate struct {
@@ -33,7 +33,7 @@ type AppleClientSecretCreate struct {
// Usually wrapped within -----BEGIN PRIVATE KEY----- X -----END PRIVATE KEY-----.
PrivateKey string `form:"privateKey" json:"privateKey"`
// Duration specifies how long the generated JWT token should be considered valid.
// Duration specifies how long the generated JWT should be considered valid.
// The specified value must be in seconds and max 15777000 (~6months).
Duration int `form:"duration" json:"duration"`
}
+2
View File
@@ -15,6 +15,8 @@ import (
)
func TestAppleClientSecretCreateValidateAndSubmit(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+2
View File
@@ -10,6 +10,8 @@ import (
)
func TestBackupCreateValidateAndSubmit(t *testing.T) {
t.Parallel()
scenarios := []struct {
name string
backupName string
+2
View File
@@ -12,6 +12,8 @@ import (
)
func TestBackupUploadValidateAndSubmit(t *testing.T) {
t.Parallel()
var zb bytes.Buffer
zw := zip.NewWriter(&zb)
if err := zw.Close(); err != nil {
+8
View File
@@ -16,6 +16,8 @@ import (
)
func TestNewCollectionUpsert(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -88,6 +90,8 @@ func TestNewCollectionUpsert(t *testing.T) {
}
func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -654,6 +658,8 @@ func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
}
func TestCollectionUpsertSubmitInterceptors(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -704,6 +710,8 @@ func TestCollectionUpsertSubmitInterceptors(t *testing.T) {
}
func TestCollectionUpsertWithCustomId(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+6
View File
@@ -11,6 +11,8 @@ import (
)
func TestCollectionsImportValidate(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -38,6 +40,8 @@ func TestCollectionsImportValidate(t *testing.T) {
}
func TestCollectionsImportSubmit(t *testing.T) {
t.Parallel()
totalCollections := 11
scenarios := []struct {
@@ -461,6 +465,8 @@ func TestCollectionsImportSubmit(t *testing.T) {
}
func TestCollectionsImportSubmitInterceptors(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+2
View File
@@ -8,6 +8,8 @@ import (
)
func TestRealtimeSubscribeValidate(t *testing.T) {
t.Parallel()
scenarios := []struct {
clientId string
expectError bool
@@ -13,6 +13,8 @@ import (
)
func TestRecordEmailChangeConfirmValidateAndSubmit(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -145,6 +147,8 @@ func TestRecordEmailChangeConfirmValidateAndSubmit(t *testing.T) {
}
func TestRecordEmailChangeConfirmInterceptors(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -12,6 +12,8 @@ import (
)
func TestRecordEmailChangeRequestValidateAndSubmit(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -106,6 +108,8 @@ func TestRecordEmailChangeRequestValidateAndSubmit(t *testing.T) {
}
func TestRecordEmailChangeRequestInterceptors(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
+2
View File
@@ -10,6 +10,8 @@ import (
)
func TestUserOauth2LoginValidate(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+4
View File
@@ -10,6 +10,8 @@ import (
)
func TestRecordPasswordLoginValidateAndSubmit(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -132,6 +134,8 @@ func TestRecordPasswordLoginValidateAndSubmit(t *testing.T) {
}
func TestRecordPasswordLoginInterceptors(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -13,6 +13,8 @@ import (
)
func TestRecordPasswordResetConfirmValidateAndSubmit(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -136,6 +138,8 @@ func TestRecordPasswordResetConfirmValidateAndSubmit(t *testing.T) {
}
func TestRecordPasswordResetConfirmInterceptors(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -13,6 +13,8 @@ import (
)
func TestRecordPasswordResetRequestSubmit(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -116,6 +118,8 @@ func TestRecordPasswordResetRequestSubmit(t *testing.T) {
}
func TestRecordPasswordResetRequestInterceptors(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -12,6 +12,8 @@ import (
)
func TestRecordVerificationConfirmValidateAndSubmit(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -98,6 +100,8 @@ func TestRecordVerificationConfirmValidateAndSubmit(t *testing.T) {
}
func TestRecordVerificationConfirmInterceptors(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -13,6 +13,8 @@ import (
)
func TestRecordVerificationRequestSubmit(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -134,6 +136,8 @@ func TestRecordVerificationRequestSubmit(t *testing.T) {
}
func TestRecordVerificationRequestInterceptors(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
+6
View File
@@ -14,6 +14,8 @@ import (
)
func TestNewSettingsUpsert(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -30,6 +32,8 @@ func TestNewSettingsUpsert(t *testing.T) {
}
func TestSettingsUpsertValidateAndSubmit(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -127,6 +131,8 @@ func TestSettingsUpsertValidateAndSubmit(t *testing.T) {
}
func TestSettingsUpsertSubmitInterceptors(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+2
View File
@@ -10,6 +10,8 @@ import (
)
func TestEmailSendValidateAndSubmit(t *testing.T) {
t.Parallel()
scenarios := []struct {
template string
email string
+4
View File
@@ -9,6 +9,8 @@ import (
)
func TestS3FilesystemValidate(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -66,6 +68,8 @@ func TestS3FilesystemValidate(t *testing.T) {
}
func TestS3FilesystemSubmitFailure(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+4
View File
@@ -12,6 +12,8 @@ import (
)
func TestUploadedFileSize(t *testing.T) {
t.Parallel()
data, mp, err := tests.MockMultipartData(nil, "test")
if err != nil {
t.Fatal(err)
@@ -52,6 +54,8 @@ func TestUploadedFileSize(t *testing.T) {
}
func TestUploadedFileMimeType(t *testing.T) {
t.Parallel()
data, mp, err := tests.MockMultipartData(nil, "test")
if err != nil {
t.Fatal(err)
+2
View File
@@ -8,6 +8,8 @@ import (
)
func TestUniqueId(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+2
View File
@@ -45,6 +45,8 @@ func TestRecordDataValidatorEmptyAndUnknown(t *testing.T) {
}
func TestRecordDataValidatorValidateText(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+2
View File
@@ -7,6 +7,8 @@ import (
)
func TestCompare(t *testing.T) {
t.Parallel()
scenarios := []struct {
valA string
valB string
+34 -34
View File
@@ -4,7 +4,7 @@ go 1.21
require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/aws/aws-sdk-go v1.48.16
github.com/aws/aws-sdk-go v1.49.20
github.com/disintegration/imaging v1.6.2
github.com/domodwyer/mailyak/v3 v3.6.2
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d
@@ -17,39 +17,39 @@ require (
github.com/goccy/go-json v0.10.2
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
github.com/mattn/go-sqlite3 v1.14.18
github.com/mattn/go-sqlite3 v1.14.19
github.com/pocketbase/dbx v1.10.1
github.com/pocketbase/tygoja v0.0.0-20231111102932-5420517293f4
github.com/pocketbase/tygoja v0.0.0-20240113091827-17918475d342
github.com/spf13/cast v1.6.0
github.com/spf13/cobra v1.8.0
gocloud.dev v0.35.0
golang.org/x/crypto v0.16.0
golang.org/x/net v0.19.0
golang.org/x/oauth2 v0.15.0
golang.org/x/sync v0.5.0
modernc.org/sqlite v1.27.0
gocloud.dev v0.36.0
golang.org/x/crypto v0.18.0
golang.org/x/net v0.20.0
golang.org/x/oauth2 v0.16.0
golang.org/x/sync v0.6.0
modernc.org/sqlite v1.28.0
)
require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2 v1.24.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect
github.com/aws/aws-sdk-go-v2/config v1.26.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.12 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 // indirect
github.com/aws/aws-sdk-go-v2/config v1.26.3 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.14 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
github.com/aws/smithy-go v1.19.0 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
@@ -57,7 +57,7 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/google/wire v0.5.0 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
@@ -71,23 +71,23 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/image v0.14.0 // indirect
golang.org/x/image v0.15.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/term v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.16.0 // indirect
golang.org/x/tools v0.17.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/api v0.153.0 // indirect
google.golang.org/api v0.156.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
google.golang.org/grpc v1.60.1 // indirect
google.golang.org/protobuf v1.32.0 // indirect
lukechampine.com/uint128 v1.3.0 // indirect
modernc.org/cc/v3 v3.41.0 // indirect
modernc.org/ccgo/v3 v3.16.15 // indirect
modernc.org/libc v1.37.0 // indirect
modernc.org/libc v1.40.1 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/opt v0.1.3 // indirect
+86
View File
@@ -19,42 +19,80 @@ 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.48.16 h1:mcj2/9J/MJ55Dov+ocMevhR8Jv6jW/fAxbrn4a1JFc8=
github.com/aws/aws-sdk-go v1.48.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.49.15 h1:aH9bSV4kL4ziH0AMtuYbukGIVebXddXBL0cKZ1zj15k=
github.com/aws/aws-sdk-go v1.49.15/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.49.20 h1:VgEUq2/ZbUkLbqPyDcxrirfXB+PgiZUUF5XbsgWe2S0=
github.com/aws/aws-sdk-go v1.49.20/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go-v2 v1.24.0 h1:890+mqQ+hTpNuw0gGP6/4akolQkSToDJgHfQE7AwGuk=
github.com/aws/aws-sdk-go-v2 v1.24.0/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo=
github.com/aws/aws-sdk-go-v2/config v1.26.1 h1:z6DqMxclFGL3Zfo+4Q0rLnAZ6yVkzCRxhRMsiRQnD1o=
github.com/aws/aws-sdk-go-v2/config v1.26.1/go.mod h1:ZB+CuKHRbb5v5F0oJtGdhFTelmrxd4iWO1lf0rQwSAg=
github.com/aws/aws-sdk-go-v2/config v1.26.3 h1:dKuc2jdp10y13dEEvPqWxqLoc0vF3Z9FC45MvuQSxOA=
github.com/aws/aws-sdk-go-v2/config v1.26.3/go.mod h1:Bxgi+DeeswYofcYO0XyGClwlrq3DZEXli0kLf4hkGA0=
github.com/aws/aws-sdk-go-v2/credentials v1.16.12 h1:v/WgB8NxprNvr5inKIiVVrXPuuTegM+K8nncFkr1usU=
github.com/aws/aws-sdk-go-v2/credentials v1.16.12/go.mod h1:X21k0FjEJe+/pauud82HYiQbEr9jRKY3kXEIQ4hXeTQ=
github.com/aws/aws-sdk-go-v2/credentials v1.16.14 h1:mMDTwwYO9A0/JbOCOG7EOZHtYM+o7OfGWfu0toa23VE=
github.com/aws/aws-sdk-go-v2/credentials v1.16.14/go.mod h1:cniAUh3ErQPHtCQGPT5ouvSAQ0od8caTO9OOuufZOAE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 h1:w98BT5w+ao1/r5sUuiH6JkVzjowOKeOJRHERyy1vh58=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10/go.mod h1:K2WGI7vUvkIv1HoNbfBA1bvIZ+9kL3YVmWxeKuLQsiw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7 h1:FnLf60PtjXp8ZOzQfhJVsqF0OtYKQZWQfqOLshh8YXg=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7/go.mod h1:tDVvl8hyU6E9B8TrnNrZQEVkQlB8hjJwcgpPhgtlnNg=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.10 h1:SdMso4tShJKrwGmwZPMO6urFilhTYkEZUPsndW0unfM=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.10/go.mod h1:qi+Nerp7JHgl+eyVtiRPA7T4bV5onFRWgpnF2JzPW60=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11 h1:I6lAa3wBWfCz/cKkOpAcumsETRkFAl70sWi8ItcMEsM=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11/go.mod h1:be1NIO30kJA23ORBLqPo1LttEM6tPNSEcjkd1eKzNW0=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 h1:v+HbZaCGmOwnTTVS86Fleq0vPzOd7tnJGbFhP0stNLs=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9/go.mod h1:Xjqy+Nyj7VDLBtCMkQYOw1QYfAEZCVLrfI0ezve8wd4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 h1:N94sVhRACtXyVcjXxrwK1SKFIJrA9pOJ5yu2eSHnmls=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9/go.mod h1:hqamLz7g1/4EJP+GH5NBhcUMLjW+gKLQabgyz6/7WAU=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 h1:ugD6qzjYtB7zM5PN/ZIeaAIyefPaD82G8+SJopgvUpw=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9/go.mod h1:YD0aYBWCrPENpHolhKw2XDlTIWae2GKXT1T4o6N6hiM=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 h1:5oE2WzJE56/mVveuDZPJESKlg/00AaS2pY2QZcnxg4M=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10/go.mod h1:FHbKWQtRBYUz4vO5WBWjzMD2by126ny5y/1EoaWoLfI=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 h1:/90OR2XbSYfXucBMJ4U14wrjlfleq/0SB6dZDPncgmo=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9/go.mod h1:dN/Of9/fNZet7UrQQ6kTDo/VSwKPIq94vjlU16bRARc=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 h1:L0ai8WICYHozIKK+OtPzVJBugL7culcuM4E4JOpIEm8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10/go.mod h1:byqfyxJBshFk0fF9YmK0M0ugIO8OWjzH2T3bPG4eGuA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 h1:Nf2sHxjMJR8CSImIVCONRi4g0Su3J+TSTbS7G0pUeMU=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9/go.mod h1:idky4TER38YIjr2cADF1/ugFMKvZV7p//pVeV5LZbF0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 h1:iEAeF6YC3l4FzlJPP9H3Ko1TXpdjdqWffxXjp8SY6uk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9/go.mod h1:kjsXoK23q9Z/tLBrckZLLyvjhZoS+AGrzqzUfEClvMM=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 h1:KOxnQeWy5sXyS37fdKEvAsGHOr9fa/qvwxfJurR/BzE=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10/go.mod h1:jMx5INQFYFYB3lQD9W0D8Ohgq6Wnl7NYOJ2TQndbulI=
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 h1:Keso8lIOS+IzI2MkPZyK6G0LYcK3My2LQ+T5bxghEAY=
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5/go.mod h1:vADO6Jn+Rq4nDtfwNjhgR84qkZwiC6FqCaXdw/kYwjA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.8 h1:vPmag9qVmGho0jvtK5+nLwixJeX6Smd0IZE1OJIQ7wE=
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.8/go.mod h1:4qXHrG1Ne3VGIMZPCB8OjH/pLFO94sKABIusjh0KWPU=
github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 h1:PJTdBMsyvra6FtED7JZtDpQrIAflYDHFoZAu/sKYkwU=
github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0/go.mod h1:4qXHrG1Ne3VGIMZPCB8OjH/pLFO94sKABIusjh0KWPU=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 h1:ldSFWz9tEHAwHNmjx2Cvy1MjP5/L9kNoR0skc6wyOOM=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5/go.mod h1:CaFfXLYL376jgbP7VKC96uFcU8Rlavak0UlAwk1Dlhc=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 h1:dGrs+Q/WzhsiUKh82SfTVN66QzyulXuMDTV/G8ZxOac=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.6/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 h1:2k9KmFawS63euAkY4/ixVNsYYwrwnd5fIvgEKkfZFNM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5/go.mod h1:W+nd4wWDVkSUIox9bacmkBP5NMFQeTJ/xqNabpzSR38=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 h1:Yf2MIo9x+0tyv76GljxzqA3WtC5mw7NmazD2chwjxE4=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 h1:5UYvv8JUvllZsRnfrcMQ+hJ9jNICmcgKPAO1CER25Wg=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5/go.mod h1:XX5gh4CB7wAs4KhcF46G6C8a2i7eupU19dcAAE+EydU=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -155,6 +193,8 @@ github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
@@ -192,6 +232,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
@@ -201,6 +243,8 @@ github.com/pocketbase/dbx v1.10.1 h1:cw+vsyfCJD8YObOVeqb93YErnlxwYMkNZ4rwN0G0AaA
github.com/pocketbase/dbx v1.10.1/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
github.com/pocketbase/tygoja v0.0.0-20231111102932-5420517293f4 h1:85kAYIKrKEeau7WgXg8B7Km8etrVavJAyH7XcR5MkFw=
github.com/pocketbase/tygoja v0.0.0-20231111102932-5420517293f4/go.mod h1:dOJ+pCyqm/jRn5kO/TX598J0e5xGDcJAZerK5atCrKI=
github.com/pocketbase/tygoja v0.0.0-20240113091827-17918475d342 h1:OcAwewen3hs/zY8i0syt8CcMTGBJhQwQRVDLcoQVXVk=
github.com/pocketbase/tygoja v0.0.0-20240113091827-17918475d342/go.mod h1:dOJ+pCyqm/jRn5kO/TX598J0e5xGDcJAZerK5atCrKI=
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=
@@ -233,15 +277,23 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
gocloud.dev v0.35.0 h1:x/Gtt5OJdT4j+ir1AXAIXb7bBnFawXAAaJptCUGk3HU=
gocloud.dev v0.35.0/go.mod h1:wbyF+BhfdtLWyUtVEWRW13hFLb1vXnV2ovEhYGQe3ck=
gocloud.dev v0.36.0 h1:q5zoXux4xkOZP473e1EZbG8Gq9f0vlg1VNH5Du/ybus=
gocloud.dev v0.36.0/go.mod h1:bLxah6JQVKBaIxzsr5BQLYB4IYdWHkMZdzCXlo6F0gg=
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.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
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.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@@ -260,15 +312,21 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
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=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -283,10 +341,14 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.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.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
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=
@@ -308,12 +370,20 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
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-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/api v0.153.0 h1:N1AwGhielyKFaUqH07/ZSIQR3uNPcV7NVw0vj+j4iR4=
google.golang.org/api v0.153.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY=
google.golang.org/api v0.155.0 h1:vBmGhCYs0djJttDNynWo44zosHlPvHmA0XiN2zP2DtA=
google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk=
google.golang.org/api v0.156.0 h1:yloYcGbBtVYjLKQe4enCunxvwn3s2w/XPrrhVf6MsvQ=
google.golang.org/api v0.156.0/go.mod h1:bUSmn4KFO0Q+69zo9CNIDp4Psi6BqM0np0CbzKRSiSY=
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=
@@ -326,8 +396,14 @@ google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f h1:Vn+VyHU5guc9KjB
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY=
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY=
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI=
google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3 h1:EWIeHfGuUf00zrVZGEgYFxok7plSAXBGcH7NNdMAWvA=
google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 h1:DC7wcm+i+P1rN3Ff07vL+OndGg5OhNddHyTA+ocPqYE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA=
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=
@@ -335,6 +411,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -348,6 +426,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -373,6 +453,10 @@ modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.37.0 h1:WerjebcsP6A7Jy+f2lCnHAkiSTLf7IaSftBYUtoswak=
modernc.org/libc v1.37.0/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/libc v1.38.0 h1:o4Lpk0zNDSdsjfEXnF1FGXWQ9PDi1NOdWcLP5n13FGo=
modernc.org/libc v1.38.0/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/libc v1.40.1 h1:ZhRylEBcj3GyQbPVC8JxIg7SdrT4JOxIDJoUon0NfF8=
modernc.org/libc v1.40.1/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
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.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
@@ -381,6 +465,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8=
modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
+2
View File
@@ -9,6 +9,8 @@ import (
)
func TestSendAdminPasswordReset(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
+6
View File
@@ -9,6 +9,8 @@ import (
)
func TestSendRecordPasswordReset(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -37,6 +39,8 @@ func TestSendRecordPasswordReset(t *testing.T) {
}
func TestSendRecordVerification(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -62,6 +66,8 @@ func TestSendRecordVerification(t *testing.T) {
}
func TestSendRecordChangeEmail(t *testing.T) {
t.Parallel()
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
@@ -28,7 +28,7 @@ func init() {
}
options, _ := f.Options.(*schema.JsonOptions)
if options != nil {
if options == nil {
options = &schema.JsonOptions{}
}
options.MaxSize = 2000000 // 2mb
+8
View File
@@ -8,6 +8,8 @@ import (
)
func TestAdminTableName(t *testing.T) {
t.Parallel()
m := models.Admin{}
if m.TableName() != "_admins" {
t.Fatalf("Unexpected table name, got %q", m.TableName())
@@ -15,6 +17,8 @@ func TestAdminTableName(t *testing.T) {
}
func TestAdminValidatePassword(t *testing.T) {
t.Parallel()
scenarios := []struct {
admin models.Admin
password string
@@ -61,6 +65,8 @@ func TestAdminValidatePassword(t *testing.T) {
}
func TestAdminSetPassword(t *testing.T) {
t.Parallel()
m := models.Admin{
// 123456
PasswordHash: "$2a$10$SKk/Y/Yc925PBtsSYBvq3Ous9Jy18m4KTn6b/PQQ.Y9QVjy3o/Fv.",
@@ -93,6 +99,8 @@ func TestAdminSetPassword(t *testing.T) {
}
func TestAdminRefreshTokenKey(t *testing.T) {
t.Parallel()
m := models.Admin{TokenKey: "test"}
m.RefreshTokenKey()
+10
View File
@@ -7,6 +7,8 @@ import (
)
func TestBaseModelHasId(t *testing.T) {
t.Parallel()
scenarios := []struct {
model models.BaseModel
expected bool
@@ -34,6 +36,8 @@ func TestBaseModelHasId(t *testing.T) {
}
func TestBaseModelId(t *testing.T) {
t.Parallel()
m := models.BaseModel{}
if m.GetId() != "" {
@@ -54,6 +58,8 @@ func TestBaseModelId(t *testing.T) {
}
func TestBaseModelIsNew(t *testing.T) {
t.Parallel()
m0 := models.BaseModel{}
m1 := models.BaseModel{Id: ""}
m2 := models.BaseModel{Id: "test"}
@@ -96,6 +102,8 @@ func TestBaseModelIsNew(t *testing.T) {
}
func TestBaseModelCreated(t *testing.T) {
t.Parallel()
m := models.BaseModel{}
if !m.GetCreated().IsZero() {
@@ -110,6 +118,8 @@ func TestBaseModelCreated(t *testing.T) {
}
func TestBaseModelUpdated(t *testing.T) {
t.Parallel()
m := models.BaseModel{}
if !m.GetUpdated().IsZero() {
+28
View File
@@ -11,6 +11,8 @@ import (
)
func TestCollectionTableName(t *testing.T) {
t.Parallel()
m := models.Collection{}
if m.TableName() != "_collections" {
t.Fatalf("Unexpected table name, got %q", m.TableName())
@@ -18,6 +20,8 @@ func TestCollectionTableName(t *testing.T) {
}
func TestCollectionBaseFilesPath(t *testing.T) {
t.Parallel()
m := models.Collection{}
m.RefreshId()
@@ -29,6 +33,8 @@ func TestCollectionBaseFilesPath(t *testing.T) {
}
func TestCollectionIsBase(t *testing.T) {
t.Parallel()
scenarios := []struct {
collection models.Collection
expected bool
@@ -48,6 +54,8 @@ func TestCollectionIsBase(t *testing.T) {
}
func TestCollectionIsAuth(t *testing.T) {
t.Parallel()
scenarios := []struct {
collection models.Collection
expected bool
@@ -67,6 +75,8 @@ func TestCollectionIsAuth(t *testing.T) {
}
func TestCollectionMarshalJSON(t *testing.T) {
t.Parallel()
scenarios := []struct {
name string
collection models.Collection
@@ -109,6 +119,8 @@ func TestCollectionMarshalJSON(t *testing.T) {
}
func TestCollectionBaseOptions(t *testing.T) {
t.Parallel()
scenarios := []struct {
name string
collection models.Collection
@@ -153,6 +165,8 @@ func TestCollectionBaseOptions(t *testing.T) {
}
func TestCollectionAuthOptions(t *testing.T) {
t.Parallel()
options := types.JsonMap{"test": 123, "minPasswordLength": 4}
expectedSerialization := `{"manageRule":null,"allowOAuth2Auth":false,"allowUsernameAuth":false,"allowEmailAuth":false,"requireEmail":false,"exceptEmailDomains":null,"onlyVerified":false,"onlyEmailDomains":null,"minPasswordLength":4}`
@@ -200,6 +214,8 @@ func TestCollectionAuthOptions(t *testing.T) {
}
func TestCollectionViewOptions(t *testing.T) {
t.Parallel()
options := types.JsonMap{"query": "select id from demo1", "minPasswordLength": 4}
expectedSerialization := `{"query":"select id from demo1"}`
@@ -247,6 +263,8 @@ func TestCollectionViewOptions(t *testing.T) {
}
func TestNormalizeOptions(t *testing.T) {
t.Parallel()
scenarios := []struct {
name string
collection models.Collection
@@ -288,6 +306,8 @@ func TestNormalizeOptions(t *testing.T) {
}
func TestDecodeOptions(t *testing.T) {
t.Parallel()
m := models.Collection{
Options: types.JsonMap{"test": 123},
}
@@ -306,6 +326,8 @@ func TestDecodeOptions(t *testing.T) {
}
func TestSetOptions(t *testing.T) {
t.Parallel()
scenarios := []struct {
name string
collection models.Collection
@@ -357,6 +379,8 @@ func TestSetOptions(t *testing.T) {
}
func TestCollectionBaseOptionsValidate(t *testing.T) {
t.Parallel()
opt := models.CollectionBaseOptions{}
if err := opt.Validate(); err != nil {
t.Fatal(err)
@@ -364,6 +388,8 @@ func TestCollectionBaseOptionsValidate(t *testing.T) {
}
func TestCollectionAuthOptionsValidate(t *testing.T) {
t.Parallel()
scenarios := []struct {
name string
options models.CollectionAuthOptions
@@ -451,6 +477,8 @@ func TestCollectionAuthOptionsValidate(t *testing.T) {
}
func TestCollectionViewOptionsValidate(t *testing.T) {
t.Parallel()
scenarios := []struct {
name string
options models.CollectionViewOptions
+2
View File
@@ -7,6 +7,8 @@ import (
)
func TestExternalAuthTableName(t *testing.T) {
t.Parallel()
m := models.ExternalAuth{}
if m.TableName() != "_externalAuths" {
t.Fatalf("Unexpected table name, got %q", m.TableName())
+2
View File
@@ -7,6 +7,8 @@ import (
)
func TestParamTableName(t *testing.T) {
t.Parallel()
m := models.Param{}
if m.TableName() != "_params" {
t.Fatalf("Unexpected table name, got %q", m.TableName())
+82
View File
@@ -15,6 +15,8 @@ import (
)
func TestNewRecord(t *testing.T) {
t.Parallel()
collection := &models.Collection{
Name: "test_collection",
Schema: schema.NewSchema(
@@ -37,6 +39,8 @@ func TestNewRecord(t *testing.T) {
}
func TestNewRecordFromNullStringMap(t *testing.T) {
t.Parallel()
collection := &models.Collection{
Name: "test",
Schema: schema.NewSchema(
@@ -200,6 +204,8 @@ func TestNewRecordFromNullStringMap(t *testing.T) {
}
func TestNewRecordsFromNullStringMaps(t *testing.T) {
t.Parallel()
collection := &models.Collection{
Name: "test",
Schema: schema.NewSchema(
@@ -310,6 +316,8 @@ func TestNewRecordsFromNullStringMaps(t *testing.T) {
}
func TestRecordTableName(t *testing.T) {
t.Parallel()
collection := &models.Collection{}
collection.Name = "test"
collection.RefreshId()
@@ -322,6 +330,8 @@ func TestRecordTableName(t *testing.T) {
}
func TestRecordCollection(t *testing.T) {
t.Parallel()
collection := &models.Collection{}
collection.RefreshId()
@@ -333,6 +343,8 @@ func TestRecordCollection(t *testing.T) {
}
func TestRecordOriginalCopy(t *testing.T) {
t.Parallel()
m := models.NewRecord(&models.Collection{})
m.Load(map[string]any{"f": "123"})
@@ -360,6 +372,8 @@ func TestRecordOriginalCopy(t *testing.T) {
}
func TestRecordCleanCopy(t *testing.T) {
t.Parallel()
m := models.NewRecord(&models.Collection{
Name: "cname",
Type: models.CollectionTypeAuth,
@@ -392,6 +406,8 @@ func TestRecordCleanCopy(t *testing.T) {
}
func TestRecordSetAndGetExpand(t *testing.T) {
t.Parallel()
collection := &models.Collection{}
m := models.NewRecord(collection)
@@ -409,6 +425,8 @@ func TestRecordSetAndGetExpand(t *testing.T) {
}
func TestRecordMergeExpand(t *testing.T) {
t.Parallel()
collection := &models.Collection{}
m := models.NewRecord(collection)
m.Id = "m"
@@ -501,6 +519,8 @@ func TestRecordMergeExpand(t *testing.T) {
}
func TestRecordMergeExpandNilCheck(t *testing.T) {
t.Parallel()
collection := &models.Collection{}
scenarios := []struct {
@@ -542,6 +562,8 @@ func TestRecordMergeExpandNilCheck(t *testing.T) {
}
func TestRecordExpandedRel(t *testing.T) {
t.Parallel()
collection := &models.Collection{}
main := models.NewRecord(collection)
@@ -574,6 +596,8 @@ func TestRecordExpandedRel(t *testing.T) {
}
func TestRecordExpandedAll(t *testing.T) {
t.Parallel()
collection := &models.Collection{}
main := models.NewRecord(collection)
@@ -606,6 +630,8 @@ func TestRecordExpandedAll(t *testing.T) {
}
func TestRecordSchemaData(t *testing.T) {
t.Parallel()
collection := &models.Collection{
Type: models.CollectionTypeAuth,
Schema: schema.NewSchema(
@@ -639,6 +665,8 @@ func TestRecordSchemaData(t *testing.T) {
}
func TestRecordUnknownData(t *testing.T) {
t.Parallel()
collection := &models.Collection{
Schema: schema.NewSchema(
&schema.SchemaField{
@@ -719,6 +747,8 @@ func TestRecordUnknownData(t *testing.T) {
}
func TestRecordSetAndGet(t *testing.T) {
t.Parallel()
collection := &models.Collection{
Schema: schema.NewSchema(
&schema.SchemaField{
@@ -799,6 +829,8 @@ func TestRecordSetAndGet(t *testing.T) {
}
func TestRecordGetBool(t *testing.T) {
t.Parallel()
scenarios := []struct {
value any
expected bool
@@ -830,6 +862,8 @@ func TestRecordGetBool(t *testing.T) {
}
func TestRecordGetString(t *testing.T) {
t.Parallel()
scenarios := []struct {
value any
expected string
@@ -860,6 +894,8 @@ func TestRecordGetString(t *testing.T) {
}
func TestRecordGetInt(t *testing.T) {
t.Parallel()
scenarios := []struct {
value any
expected int
@@ -892,6 +928,8 @@ func TestRecordGetInt(t *testing.T) {
}
func TestRecordGetFloat(t *testing.T) {
t.Parallel()
scenarios := []struct {
value any
expected float64
@@ -924,6 +962,8 @@ func TestRecordGetFloat(t *testing.T) {
}
func TestRecordGetTime(t *testing.T) {
t.Parallel()
nowTime := time.Now()
testTime, _ := time.Parse(types.DefaultDateLayout, "2022-01-01 08:00:40.000Z")
@@ -957,6 +997,8 @@ func TestRecordGetTime(t *testing.T) {
}
func TestRecordGetDateTime(t *testing.T) {
t.Parallel()
nowTime := time.Now()
testTime, _ := time.Parse(types.DefaultDateLayout, "2022-01-01 08:00:40.000Z")
@@ -990,6 +1032,8 @@ func TestRecordGetDateTime(t *testing.T) {
}
func TestRecordGetStringSlice(t *testing.T) {
t.Parallel()
nowTime := time.Now()
scenarios := []struct {
@@ -1031,6 +1075,8 @@ func TestRecordGetStringSlice(t *testing.T) {
}
func TestRecordUnmarshalJSONField(t *testing.T) {
t.Parallel()
collection := &models.Collection{
Schema: schema.NewSchema(&schema.SchemaField{
Name: "field",
@@ -1086,6 +1132,8 @@ func TestRecordUnmarshalJSONField(t *testing.T) {
}
func TestRecordBaseFilesPath(t *testing.T) {
t.Parallel()
collection := &models.Collection{}
collection.RefreshId()
collection.Name = "test"
@@ -1102,6 +1150,8 @@ func TestRecordBaseFilesPath(t *testing.T) {
}
func TestRecordFindFileFieldByFile(t *testing.T) {
t.Parallel()
collection := &models.Collection{
Schema: schema.NewSchema(
&schema.SchemaField{
@@ -1159,6 +1209,8 @@ func TestRecordFindFileFieldByFile(t *testing.T) {
}
func TestRecordLoadAndData(t *testing.T) {
t.Parallel()
collection := &models.Collection{
Schema: schema.NewSchema(
&schema.SchemaField{
@@ -1232,6 +1284,8 @@ func TestRecordLoadAndData(t *testing.T) {
}
func TestRecordColumnValueMap(t *testing.T) {
t.Parallel()
collection := &models.Collection{
Schema: schema.NewSchema(
&schema.SchemaField{
@@ -1319,6 +1373,8 @@ func TestRecordColumnValueMap(t *testing.T) {
}
func TestRecordPublicExportAndMarshalJSON(t *testing.T) {
t.Parallel()
collection := &models.Collection{
Name: "c_name",
Schema: schema.NewSchema(
@@ -1463,6 +1519,8 @@ func TestRecordPublicExportAndMarshalJSON(t *testing.T) {
}
func TestRecordUnmarshalJSON(t *testing.T) {
t.Parallel()
collection := &models.Collection{
Schema: schema.NewSchema(
&schema.SchemaField{
@@ -1551,6 +1609,8 @@ func TestRecordUnmarshalJSON(t *testing.T) {
}
func TestRecordReplaceModifers(t *testing.T) {
t.Parallel()
collection := &models.Collection{
Schema: schema.NewSchema(
&schema.SchemaField{
@@ -1658,6 +1718,8 @@ func TestRecordReplaceModifers(t *testing.T) {
// -------------------------------------------------------------------
func TestRecordUsername(t *testing.T) {
t.Parallel()
scenarios := []struct {
collectionType string
expectError bool
@@ -1699,6 +1761,8 @@ func TestRecordUsername(t *testing.T) {
}
func TestRecordEmail(t *testing.T) {
t.Parallel()
scenarios := []struct {
collectionType string
expectError bool
@@ -1740,6 +1804,8 @@ func TestRecordEmail(t *testing.T) {
}
func TestRecordEmailVisibility(t *testing.T) {
t.Parallel()
scenarios := []struct {
collectionType string
value bool
@@ -1782,6 +1848,8 @@ func TestRecordEmailVisibility(t *testing.T) {
}
func TestRecordEmailVerified(t *testing.T) {
t.Parallel()
scenarios := []struct {
collectionType string
value bool
@@ -1824,6 +1892,8 @@ func TestRecordEmailVerified(t *testing.T) {
}
func TestRecordTokenKey(t *testing.T) {
t.Parallel()
scenarios := []struct {
collectionType string
expectError bool
@@ -1865,6 +1935,8 @@ func TestRecordTokenKey(t *testing.T) {
}
func TestRecordRefreshTokenKey(t *testing.T) {
t.Parallel()
scenarios := []struct {
collectionType string
expectError bool
@@ -1904,6 +1976,8 @@ func TestRecordRefreshTokenKey(t *testing.T) {
}
func TestRecordLastResetSentAt(t *testing.T) {
t.Parallel()
scenarios := []struct {
collectionType string
expectError bool
@@ -1948,6 +2022,8 @@ func TestRecordLastResetSentAt(t *testing.T) {
}
func TestRecordLastVerificationSentAt(t *testing.T) {
t.Parallel()
scenarios := []struct {
collectionType string
expectError bool
@@ -1992,6 +2068,8 @@ func TestRecordLastVerificationSentAt(t *testing.T) {
}
func TestRecordPasswordHash(t *testing.T) {
t.Parallel()
m := models.NewRecord(&models.Collection{})
if v := m.PasswordHash(); v != "" {
@@ -2006,6 +2084,8 @@ func TestRecordPasswordHash(t *testing.T) {
}
func TestRecordValidatePassword(t *testing.T) {
t.Parallel()
// 123456
hash := "$2a$10$YKU8mPP8sTE3xZrpuM.xQuq27KJ7aIJB2oUeKPsDDqZshbl5g5cDK"
@@ -2034,6 +2114,8 @@ func TestRecordValidatePassword(t *testing.T) {
}
func TestRecordSetPassword(t *testing.T) {
t.Parallel()
scenarios := []struct {
collectionType string
password string
+1
View File
@@ -11,6 +11,7 @@ const (
RequestAuthRecord = "authRecord"
)
// Deprecated: Replaced by the Log model and will be removed in a future version.
type Request struct {
BaseModel
+2
View File
@@ -7,6 +7,8 @@ import (
)
func TestRequestInfoHasModifierDataKeys(t *testing.T) {
t.Parallel()
scenarios := []struct {
name string
requestInfo *models.RequestInfo
+4
View File
@@ -299,6 +299,10 @@ func baseBinds(vm *goja.Runtime) {
return string(bodyBytes), nil
})
vm.Set("sleep", func(milliseconds int64) {
time.Sleep(time.Duration(milliseconds) * time.Millisecond)
})
vm.Set("arrayOf", func(model any) any {
mt := reflect.TypeOf(model)
st := reflect.SliceOf(mt)
+23 -1
View File
@@ -46,7 +46,29 @@ func TestBaseBindsCount(t *testing.T) {
vm := goja.New()
baseBinds(vm)
testBindsCount(vm, "this", 16, t)
testBindsCount(vm, "this", 17, t)
}
func TestBaseBindsSleep(t *testing.T) {
app, _ := tests.NewTestApp()
defer app.Cleanup()
vm := goja.New()
baseBinds(vm)
vm.Set("reader", strings.NewReader("test"))
start := time.Now()
_, err := vm.RunString(`
sleep(100);
`)
if err != nil {
t.Fatal(err)
}
lasted := time.Since(start).Milliseconds()
if lasted < 100 || lasted > 150 {
t.Fatalf("Expected to sleep for ~100ms, got %d", lasted)
}
}
func TestBaseBindsReaderToString(t *testing.T) {
File diff suppressed because it is too large. Load diff
+20
View File
@@ -1,12 +1,14 @@
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"time"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/plugins/jsvm"
@@ -209,6 +211,20 @@ declare var $template: template.Registry
*/
declare function readerToString(reader: any, maxBytes?: number): string;
/**
* sleep pauses the current goroutine for at least the specified user duration (in ms).
* A zero or negative duration returns immediately.
*
* Example:
*
* ` + "```" + `js
* sleep(250) // sleeps for 250ms
* ` + "```" + `
*
* @group PocketBase
*/
declare function sleep(milliseconds: number): void;
/**
* arrayOf creates a placeholder array of the specified models.
* Usually used to populate DB result into an array of models.
@@ -1095,6 +1111,10 @@ func main() {
result = strings.ReplaceAll(result, "ORIGINAL_CORE_APP", "core.App")
result = strings.ReplaceAll(result, "ORIGINAL_POCKETBASE", "pocketbase.PocketBase")
// prepend a timestamp with the generation time
// so that it can be compared without reading the entire file
result = fmt.Sprintf("// %d\n%s", time.Now().Unix(), result)
parentDir := filepath.Dir(filename)
typesFile := filepath.Join(parentDir, "generated", "types.d.ts")
+22 -9
View File
@@ -9,8 +9,10 @@
package jsvm
import (
"bytes"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
@@ -77,6 +79,9 @@ type Config struct {
// TypeScript declarations file.
//
// If not set it fallbacks to "pb_data".
//
// Note: Avoid using the same directory as the HooksDir when HooksWatch is enabled
// to prevent unnecessary app restarts when the types file is initially created.
TypesDir string
}
@@ -117,10 +122,9 @@ func Register(app core.App, config Config) error {
}
p.app.OnAfterBootstrap().Add(func(e *core.BootstrapEvent) error {
// always update the app types on start to ensure that
// the user has the latest generated declarations
if err := p.saveTypesFile(); err != nil {
color.Yellow("Unable to save app types file: %v", err)
// ensure that the user has the latest types declaration
if err := p.refreshTypesFile(); err != nil {
color.Yellow("Unable to refresh app types file: %v", err)
}
return nil
@@ -423,8 +427,8 @@ func (p *plugin) relativeTypesPath(basepath string) string {
return rel
}
// saveTypesFile saves the embedded TS declarations as a file on the disk.
func (p *plugin) saveTypesFile() error {
// refreshTypesFile saves the embedded TS declarations as a file on the disk.
func (p *plugin) refreshTypesFile() error {
fullPath := p.fullTypesPath()
// ensure that the types directory exists
@@ -439,11 +443,20 @@ func (p *plugin) saveTypesFile() error {
return err
}
if err := os.WriteFile(fullPath, data, 0644); err != nil {
return err
// read the first timestamp line of the old file (if exists) and compare it to the embedded one
// (note: ignore errors to allow always overwriting the file if it is invalid)
existingFile, err := os.Open(fullPath)
if err == nil {
timestamp := make([]byte, 13)
io.ReadFull(existingFile, timestamp)
existingFile.Close()
if len(data) >= len(timestamp) && bytes.Equal(data[:13], timestamp) {
return nil // nothing new to save
}
}
return nil
return os.WriteFile(fullPath, data, 0644)
}
// prependToEmptyFile prepends the specified text to an empty file.
+16 -1
View File
@@ -32,6 +32,7 @@ type appWrapper struct {
type PocketBase struct {
*appWrapper
devFlag bool
dataDirFlag string
encryptionEnvFlag string
hideStartBanner bool
@@ -43,6 +44,7 @@ type PocketBase struct {
// Config is the PocketBase initialization config struct.
type Config struct {
// optional default values for the console flags
DefaultDev bool
DefaultDataDir string // if not set, it will fallback to "./pb_data"
DefaultEncryptionEnv string
@@ -65,7 +67,11 @@ type Config struct {
// If you want to initialize the application before calling [Start()],
// then you'll have to manually call [Bootstrap()].
func New() *PocketBase {
return NewWithConfig(Config{})
_, isUsingGoRun := inspectRuntime()
return NewWithConfig(Config{
DefaultDev: isUsingGoRun,
})
}
// NewWithConfig creates a new PocketBase instance with the provided config.
@@ -95,6 +101,7 @@ func NewWithConfig(config Config) *PocketBase {
DisableDefaultCmd: true,
},
},
devFlag: config.DefaultDev,
dataDirFlag: config.DefaultDataDir,
encryptionEnvFlag: config.DefaultEncryptionEnv,
hideStartBanner: config.HideStartBanner,
@@ -109,6 +116,7 @@ func NewWithConfig(config Config) *PocketBase {
// initialize the app instance
pb.appWrapper = &appWrapper{core.NewBaseApp(core.BaseAppConfig{
IsDev: pb.devFlag,
DataDir: pb.dataDirFlag,
EncryptionEnv: pb.encryptionEnvFlag,
DataMaxOpenConns: config.DataMaxOpenConns,
@@ -191,6 +199,13 @@ func (pb *PocketBase) eagerParseFlags(config *Config) error {
"the env variable whose value of 32 characters will be used \nas encryption key for the app settings (default none)",
)
pb.RootCmd.PersistentFlags().BoolVar(
&pb.devFlag,
"dev",
config.DefaultDev,
"enable dev mode, aka. printing logs and sql statements to the console",
)
return pb.RootCmd.ParseFlags(os.Args[1:])
}
+27 -13
View File
@@ -453,6 +453,17 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
result.MultiMatchSubQuery = r.multiMatch
}
// wrap in json_extract to ensure that top-level primitives
// stored as json work correctly when compared to their SQL equivalent
// (https://github.com/pocketbase/pocketbase/issues/4068)
if field.Type == schema.FieldTypeJson {
result.NoCoalesce = true
result.Identifier = jsonExtract(r.activeTableAlias+"."+cleanFieldName, "")
if r.withMultiMatch {
r.multiMatch.valueIdentifier = jsonExtract(r.multiMatchActiveTableAlias+"."+cleanFieldName, "")
}
}
return result, nil
}
@@ -467,7 +478,6 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
// check if it is a json field
if field.Type == schema.FieldTypeJson {
var jsonPath strings.Builder
jsonPath.WriteString("$")
for _, p := range r.activeProps[i+1:] {
if _, err := strconv.Atoi(p); err == nil {
jsonPath.WriteString("[")
@@ -478,23 +488,15 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
jsonPath.WriteString(inflector.Columnify(p))
}
}
jsonPathStr := jsonPath.String()
result := &search.ResolverResult{
Identifier: fmt.Sprintf(
"JSON_EXTRACT([[%s.%s]], '%s')",
r.activeTableAlias,
inflector.Columnify(prop),
jsonPath.String(),
),
NoCoalesce: true,
Identifier: jsonExtract(r.activeTableAlias+"."+inflector.Columnify(prop), jsonPathStr),
}
if r.withMultiMatch {
r.multiMatch.valueIdentifier = fmt.Sprintf(
"JSON_EXTRACT([[%s.%s]], '%s')",
r.multiMatchActiveTableAlias,
inflector.Columnify(prop),
jsonPath.String(),
)
r.multiMatch.valueIdentifier = jsonExtract(r.multiMatchActiveTableAlias+"."+inflector.Columnify(prop), jsonPathStr)
result.MultiMatchSubQuery = r.multiMatch
}
@@ -601,6 +603,18 @@ func jsonEach(tableColumnPair string) string {
)
}
func jsonExtract(tableColumnPair string, path string) string {
return fmt.Sprintf(
// note: the extra object wrapping is needed to workaround the cases where a json_extract is used with non-json columns.
"(CASE WHEN json_valid([[%s]]) THEN JSON_EXTRACT([[%s]], '$%s') ELSE JSON_EXTRACT(json_object('pb', [[%s]]), '$.pb%s') END)",
tableColumnPair,
tableColumnPair,
path,
tableColumnPair,
path,
)
}
func resolvableSystemFieldNames(collection *models.Collection) []string {
result := schema.BaseModelFieldNames()
+36 -21
View File
@@ -143,14 +143,14 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
"demo4",
"self_rel_many.title ?= 'test' || self_rel_one.json_object.a ?> 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_one` ON [[demo4_self_rel_one.id]] = [[demo4.self_rel_one]] WHERE ([[demo4_self_rel_many.title]] = {:TEST} OR JSON_EXTRACT([[demo4_self_rel_one.json_object]], '$.a') > 1)",
"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_one` ON [[demo4_self_rel_one.id]] = [[demo4.self_rel_one]] WHERE ([[demo4_self_rel_many.title]] = {:TEST} OR (CASE WHEN json_valid([[demo4_self_rel_one.json_object]]) THEN JSON_EXTRACT([[demo4_self_rel_one.json_object]], '$.a') ELSE JSON_EXTRACT(json_object('pb', [[demo4_self_rel_one.json_object]]), '$.pb.a') END) > 1)",
},
{
"multiple rels (multi-match operators)",
"demo4",
"self_rel_many.title = 'test' || self_rel_one.json_object.a > 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_one` ON [[demo4_self_rel_one.id]] = [[demo4.self_rel_one]] WHERE ((([[demo4_self_rel_many.title]] = {:TEST}) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo4_self_rel_many.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]] WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] = {:TEST})))) OR JSON_EXTRACT([[demo4_self_rel_one.json_object]], '$.a') > 1)",
"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_one` ON [[demo4_self_rel_one.id]] = [[demo4.self_rel_one]] WHERE ((([[demo4_self_rel_many.title]] = {:TEST}) AND (NOT EXISTS (SELECT 1 FROM (SELECT [[__mm_demo4_self_rel_many.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]] WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE NOT ([[__smTEST.multiMatchValue]] = {:TEST})))) OR (CASE WHEN json_valid([[demo4_self_rel_one.json_object]]) THEN JSON_EXTRACT([[demo4_self_rel_one.json_object]], '$.a') ELSE JSON_EXTRACT(json_object('pb', [[demo4_self_rel_one.json_object]]), '$.pb.a') END) > 1)",
},
{
"@collection join (opt/any operators)",
@@ -301,7 +301,21 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
"demo4",
"json_object.a.b = '' && self_rel_many:length != 2 && json_object.a.b > 3 && self_rel_many:length <= 4",
false,
"SELECT `demo4`.* FROM `demo4` WHERE ((JSON_EXTRACT([[demo4.json_object]], '$.a.b') = '' OR JSON_EXTRACT([[demo4.json_object]], '$.a.b') IS NULL) AND json_array_length(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE (CASE WHEN [[demo4.self_rel_many]] = '' OR [[demo4.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4.self_rel_many]]) END) END) IS NOT {:TEST} AND JSON_EXTRACT([[demo4.json_object]], '$.a.b') > {:TEST} AND json_array_length(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE (CASE WHEN [[demo4.self_rel_many]] = '' OR [[demo4.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4.self_rel_many]]) END) END) <= {:TEST})",
"SELECT `demo4`.* FROM `demo4` WHERE ((CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$.a.b') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb.a.b') END) IS {:TEST} AND json_array_length(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE (CASE WHEN [[demo4.self_rel_many]] = '' OR [[demo4.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4.self_rel_many]]) END) END) IS NOT {:TEST} AND (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$.a.b') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb.a.b') END) > {:TEST} AND json_array_length(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE (CASE WHEN [[demo4.self_rel_many]] = '' OR [[demo4.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4.self_rel_many]]) END) END) <= {:TEST})",
},
{
"json field equal normalization checks",
"demo4",
"json_object = '' || json_object != '' || '' = json_object || '' != json_object ||" +
"json_object = null || json_object != null || null = json_object || null != json_object ||" +
"json_object = true || json_object != true || true = json_object || true != json_object ||" +
"json_object = json_object || json_object != json_object ||" +
"json_object = title || title != json_object ||" +
// multimatch expressions
"self_rel_many.json_object = '' || null = self_rel_many.json_object ||" +
"self_rel_many.json_object = self_rel_many.json_object",
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 ((CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS {:TEST} OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS NOT {:TEST} OR {:TEST} IS (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR {:TEST} IS NOT (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS NULL OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS NOT NULL OR NULL IS (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR NULL IS NOT (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS 1 OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS NOT 1 OR 1 IS (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR 1 IS NOT (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS NOT (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) IS [[demo4.title]] OR [[demo4.title]] IS NOT (CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb') END) OR (((CASE WHEN json_valid([[demo4_self_rel_many.json_object]]) THEN JSON_EXTRACT([[demo4_self_rel_many.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4_self_rel_many.json_object]]), '$.pb') END) IS {:TEST}) AND (NOT EXISTS (SELECT 1 FROM (SELECT (CASE WHEN json_valid([[__mm_demo4_self_rel_many.json_object]]) THEN JSON_EXTRACT([[__mm_demo4_self_rel_many.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[__mm_demo4_self_rel_many.json_object]]), '$.pb') END) 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]] IS {:TEST})))) OR ((NULL IS (CASE WHEN json_valid([[demo4_self_rel_many.json_object]]) THEN JSON_EXTRACT([[demo4_self_rel_many.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[demo4_self_rel_many.json_object]]), '$.pb') END)) AND (NOT EXISTS (SELECT 1 FROM (SELECT (CASE WHEN json_valid([[__mm_demo4_self_rel_many.json_object]]) THEN JSON_EXTRACT([[__mm_demo4_self_rel_many.json_object]], '$') ELSE JSON_EXTRACT(json_object('pb', [[__mm_demo4_self_rel_many.json_object]]), '$.pb') END) as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN json_each(CASE WHEN jsonLine truncated
},
}
@@ -381,8 +395,8 @@ func TestRecordFieldResolverResolveSchemaFields(t *testing.T) {
{"self_rel_many.title", false, "[[demo4_self_rel_many.title]]"},
{"self_rel_many.self_rel_one.self_rel_many.title", false, "[[demo4_self_rel_many_self_rel_one_self_rel_many.title]]"},
// json_extract
{"json_array.0", false, "JSON_EXTRACT([[demo4.json_array]], '$[0]')"},
{"json_object.a.b.c", false, "JSON_EXTRACT([[demo4.json_object]], '$.a.b.c')"},
{"json_array.0", false, "(CASE WHEN json_valid([[demo4.json_array]]) THEN JSON_EXTRACT([[demo4.json_array]], '$[0]') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_array]]), '$.pb[0]') END)"},
{"json_object.a.b.c", false, "(CASE WHEN json_valid([[demo4.json_object]]) THEN JSON_EXTRACT([[demo4.json_object]], '$.a.b.c') ELSE JSON_EXTRACT(json_object('pb', [[demo4.json_object]]), '$.pb.a.b.c') END)"},
// @request.auth relation join:
{"@request.auth.rel", false, "[[__auth_users.rel]]"},
{"@request.auth.rel.title", false, "[[__auth_users_rel.title]]"},
@@ -405,26 +419,27 @@ func TestRecordFieldResolverResolveSchemaFields(t *testing.T) {
}
for _, s := range scenarios {
r, err := r.Resolve(s.fieldName)
t.Run(s.fieldName, func(t *testing.T) {
r, err := r.Resolve(s.fieldName)
hasErr := err != nil
if hasErr != s.expectError {
t.Errorf("(%q) Expected hasErr %v, got %v (%v)", s.fieldName, s.expectError, hasErr, err)
continue
}
hasErr := err != nil
if hasErr != s.expectError {
t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err)
}
if hasErr {
continue
}
if hasErr {
return
}
if r.Identifier != s.expectName {
t.Errorf("(%q) Expected r.Identifier %q, got %q", s.fieldName, s.expectName, r.Identifier)
}
if r.Identifier != s.expectName {
t.Fatalf("Expected r.Identifier %q, got %q", s.expectName, r.Identifier)
}
// params should be empty for non @request fields
if len(r.Params) != 0 {
t.Errorf("(%q) Expected 0 r.Params, got %v", s.fieldName, r.Params)
}
// params should be empty for non @request fields
if len(r.Params) != 0 {
t.Fatalf("Expected 0 r.Params, got %v", r.Params)
}
})
}
}
+16 -3
View File
@@ -29,6 +29,11 @@ type ApiScenario struct {
// to ensure that all fired non-awaited go routines have finished
Delay time.Duration
// Timeout specifies how long to wait before cancelling the request context.
//
// A zero or negative value means that there will be no timeout.
Timeout time.Duration
// expectations
// ---
ExpectedStatus int
@@ -90,9 +95,17 @@ func (scenario *ApiScenario) test(t *testing.T) {
// add middleware to timeout long-running requests (eg. keep-alive routes)
e.Pre(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
ctx, cancelFunc := context.WithTimeout(c.Request().Context(), 100*time.Millisecond)
defer cancelFunc()
c.SetRequest(c.Request().Clone(ctx))
slowTimer := time.AfterFunc(3*time.Second, func() {
t.Logf("[WARN] Long running test %q", scenario.Name)
})
defer slowTimer.Stop()
if scenario.Timeout > 0 {
ctx, cancelFunc := context.WithTimeout(c.Request().Context(), scenario.Timeout)
defer cancelFunc()
c.SetRequest(c.Request().Clone(ctx))
}
return next(c)
}
})
Binary file not shown.
+6
View File
@@ -8,6 +8,8 @@ import (
)
func TestNewAdminAuthToken(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -31,6 +33,8 @@ func TestNewAdminAuthToken(t *testing.T) {
}
func TestNewAdminResetPasswordToken(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -54,6 +58,8 @@ func TestNewAdminResetPasswordToken(t *testing.T) {
}
func TestNewAdminFileToken(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+10
View File
@@ -8,6 +8,8 @@ import (
)
func TestNewRecordAuthToken(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -31,6 +33,8 @@ func TestNewRecordAuthToken(t *testing.T) {
}
func TestNewRecordVerifyToken(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -54,6 +58,8 @@ func TestNewRecordVerifyToken(t *testing.T) {
}
func TestNewRecordResetPasswordToken(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -77,6 +83,8 @@ func TestNewRecordResetPasswordToken(t *testing.T) {
}
func TestNewRecordChangeEmailToken(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
@@ -100,6 +108,8 @@ func TestNewRecordChangeEmailToken(t *testing.T) {
}
func TestNewRecordFileToken(t *testing.T) {
t.Parallel()
app, _ := tests.NewTestApp()
defer app.Cleanup()
+16
View File
@@ -7,6 +7,8 @@ import (
)
func TestCronNew(t *testing.T) {
t.Parallel()
c := New()
expectedInterval := 1 * time.Minute
@@ -29,6 +31,8 @@ func TestCronNew(t *testing.T) {
}
func TestCronSetInterval(t *testing.T) {
t.Parallel()
c := New()
interval := 2 * time.Minute
@@ -41,6 +45,8 @@ func TestCronSetInterval(t *testing.T) {
}
func TestCronSetTimezone(t *testing.T) {
t.Parallel()
c := New()
timezone, _ := time.LoadLocation("Asia/Tokyo")
@@ -53,6 +59,8 @@ func TestCronSetTimezone(t *testing.T) {
}
func TestCronAddAndRemove(t *testing.T) {
t.Parallel()
c := New()
if err := c.Add("test0", "* * * * *", nil); err == nil {
@@ -126,6 +134,8 @@ func TestCronAddAndRemove(t *testing.T) {
}
func TestCronMustAdd(t *testing.T) {
t.Parallel()
c := New()
defer func() {
@@ -144,6 +154,8 @@ func TestCronMustAdd(t *testing.T) {
}
func TestCronRemoveAll(t *testing.T) {
t.Parallel()
c := New()
if err := c.Add("test1", "* * * * *", func() {}); err != nil {
@@ -170,6 +182,8 @@ func TestCronRemoveAll(t *testing.T) {
}
func TestCronTotal(t *testing.T) {
t.Parallel()
c := New()
if v := c.Total(); v != 0 {
@@ -195,6 +209,8 @@ func TestCronTotal(t *testing.T) {
}
func TestCronStartStop(t *testing.T) {
t.Parallel()
c := New()
c.SetInterval(1 * time.Second)
+6
View File
@@ -9,6 +9,8 @@ import (
)
func TestNewMoment(t *testing.T) {
t.Parallel()
date, err := time.Parse("2006-01-02 15:04", "2023-05-09 15:20")
if err != nil {
t.Fatal(err)
@@ -38,6 +40,8 @@ func TestNewMoment(t *testing.T) {
}
func TestNewSchedule(t *testing.T) {
t.Parallel()
scenarios := []struct {
cronExpr string
expectError bool
@@ -272,6 +276,8 @@ func TestNewSchedule(t *testing.T) {
}
func TestScheduleIsDue(t *testing.T) {
t.Parallel()
scenarios := []struct {
cronExpr string
moment *cron.Moment
+30 -12
View File
@@ -184,14 +184,15 @@ func buildResolversExpr(
if !isAnyMatchOp(op) {
if left.MultiMatchSubQuery != nil && right.MultiMatchSubQuery != nil {
mm := &manyVsManyExpr{
leftSubQuery: left.MultiMatchSubQuery,
rightSubQuery: right.MultiMatchSubQuery,
op: op,
left: left,
right: right,
op: op,
}
expr = dbx.Enclose(dbx.And(expr, mm))
} else if left.MultiMatchSubQuery != nil {
mm := &manyVsOneExpr{
noCoalesce: left.NoCoalesce,
subQuery: left.MultiMatchSubQuery,
op: op,
otherOperand: right,
@@ -200,6 +201,7 @@ func buildResolversExpr(
expr = dbx.Enclose(dbx.And(expr, mm))
} else if right.MultiMatchSubQuery != nil {
mm := &manyVsOneExpr{
noCoalesce: right.NoCoalesce,
subQuery: right.MultiMatchSubQuery,
op: op,
otherOperand: left,
@@ -290,17 +292,29 @@ func resolveEqualExpr(equal bool, left, right *ResolverResult) dbx.Expression {
isRightEmpty := isEmptyIdentifier(right) || (len(right.Params) == 1 && hasEmptyParamValue(right))
equalOp := "="
nullEqualOp := "IS"
concatOp := "OR"
nullExpr := "IS NULL"
if !equal {
// use `IS NOT` instead of `!=` because direct non-equal comparisons
// always use `IS NOT` instead of `!=` because direct non-equal comparisons
// to nullable column values that are actually NULL yields to NULL instead of TRUE, eg.:
// `'example' != nullableColumn` -> NULL even if nullableColumn row value is NULL
equalOp = "IS NOT"
nullEqualOp = equalOp
concatOp = "AND"
nullExpr = "IS NOT NULL"
}
// no coalesce (eg. compare to a json field)
// a IS b
// a IS NOT b
if left.NoCoalesce || right.NoCoalesce {
return dbx.NewExp(
fmt.Sprintf("%s %s %s", left.Identifier, nullEqualOp, right.Identifier),
mergeParams(left.Params, right.Params),
)
}
// both operands are empty
if isLeftEmpty && isRightEmpty {
return dbx.NewExp(fmt.Sprintf("'' %s ''", equalOp), mergeParams(left.Params, right.Params))
@@ -459,8 +473,8 @@ var _ dbx.Expression = (*concatExpr)(nil)
// concatExpr defines an expression that concatenates multiple
// other expressions with a specified separator.
type concatExpr struct {
parts []dbx.Expression
separator string
parts []dbx.Expression
}
// Build converts the expression into a SQL fragment.
@@ -503,16 +517,16 @@ var _ dbx.Expression = (*manyVsManyExpr)(nil)
// Expects leftSubQuery and rightSubQuery to return a subquery with a
// single "multiMatchValue" column.
type manyVsManyExpr struct {
leftSubQuery dbx.Expression
rightSubQuery dbx.Expression
op fexpr.SignOp
left *ResolverResult
right *ResolverResult
op fexpr.SignOp
}
// Build converts the expression into a SQL fragment.
//
// Implements [dbx.Expression] interface.
func (e *manyVsManyExpr) Build(db *dbx.DB, params dbx.Params) string {
if e.leftSubQuery == nil || e.rightSubQuery == nil {
if e.left.MultiMatchSubQuery == nil || e.right.MultiMatchSubQuery == nil {
return "0=1"
}
@@ -521,10 +535,12 @@ func (e *manyVsManyExpr) Build(db *dbx.DB, params dbx.Params) string {
whereExpr, buildErr := buildResolversExpr(
&ResolverResult{
NoCoalesce: e.left.NoCoalesce,
Identifier: "[[" + lAlias + ".multiMatchValue]]",
},
e.op,
&ResolverResult{
NoCoalesce: e.right.NoCoalesce,
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
@@ -538,9 +554,9 @@ func (e *manyVsManyExpr) Build(db *dbx.DB, params dbx.Params) string {
return fmt.Sprintf(
"NOT EXISTS (SELECT 1 FROM (%s) {{%s}} LEFT JOIN (%s) {{%s}} WHERE %s)",
e.leftSubQuery.Build(db, params),
e.left.MultiMatchSubQuery.Build(db, params),
lAlias,
e.rightSubQuery.Build(db, params),
e.right.MultiMatchSubQuery.Build(db, params),
rAlias,
whereExpr.Build(db, params),
)
@@ -556,10 +572,11 @@ var _ dbx.Expression = (*manyVsOneExpr)(nil)
//
// You can set inverse=false to reverse the condition sides (aka. one<->many).
type manyVsOneExpr struct {
otherOperand *ResolverResult
subQuery dbx.Expression
op fexpr.SignOp
otherOperand *ResolverResult
inverse bool
noCoalesce bool
}
// Build converts the expression into a SQL fragment.
@@ -573,6 +590,7 @@ func (e *manyVsOneExpr) Build(db *dbx.DB, params dbx.Params) string {
alias := "__sm" + security.PseudorandomString(5)
r1 := &ResolverResult{
NoCoalesce: e.noCoalesce,
Identifier: "[[" + alias + ".multiMatchValue]]",
AfterBuild: multiMatchAfterBuildFunc(e.op, alias),
}
+7 -1
View File
@@ -13,7 +13,7 @@ import (
)
func TestFilterDataBuildExpr(t *testing.T) {
resolver := search.NewSimpleFieldResolver("test1", "test2", "test3", `^test4_\w+$`)
resolver := search.NewSimpleFieldResolver("test1", "test2", "test3", `^test4_\w+$`, `^test5\.[\w\.\:]*\w+$`)
scenarios := []struct {
name string
@@ -93,6 +93,12 @@ func TestFilterDataBuildExpr(t *testing.T) {
false,
"[[test1]] NOT LIKE {:TEST} ESCAPE '\\'",
},
{
"nested json no coalesce",
"test5.a = test5.b || test5.c != test5.d",
false,
"(JSON_EXTRACT([[test5]], '$.a') IS JSON_EXTRACT([[test5]], '$.b') OR JSON_EXTRACT([[test5]], '$.c') IS NOT JSON_EXTRACT([[test5]], '$.d'))",
},
{
"macros",
`
+5
View File
@@ -16,6 +16,10 @@ type ResolverResult struct {
// in the final db expression as left or right operand.
Identifier string
// NoCoalesce instructs to not use COALESCE or NULL fallbacks
// when building the identifier expression.
NoCoalesce bool
// Params is a map with db placeholder->value pairs that will be added
// to the query when building both resolved operands/sides in a single expression.
Params dbx.Params
@@ -99,6 +103,7 @@ func (r *SimpleFieldResolver) Resolve(field string) (*ResolverResult, error) {
}
return &ResolverResult{
NoCoalesce: true,
Identifier: fmt.Sprintf(
"JSON_EXTRACT([[%s]], '%s')",
inflector.Columnify(parts[0]),
+4 -4
View File
@@ -7,7 +7,7 @@ import (
"github.com/golang-jwt/jwt/v4"
)
// ParseUnverifiedJWT parses JWT token and returns its claims
// ParseUnverifiedJWT parses JWT and returns its claims
// but DOES NOT verify the signature.
//
// It verifies only the exp, iat and nbf claims.
@@ -24,7 +24,7 @@ func ParseUnverifiedJWT(token string) (jwt.MapClaims, error) {
return claims, err
}
// ParseJWT verifies and parses JWT token and returns its claims.
// ParseJWT verifies and parses JWT and returns its claims.
func ParseJWT(token string, verificationKey string) (jwt.MapClaims, error) {
parser := jwt.NewParser(jwt.WithValidMethods([]string{"HS256"}))
@@ -42,7 +42,7 @@ func ParseJWT(token string, verificationKey string) (jwt.MapClaims, error) {
return nil, errors.New("Unable to parse token.")
}
// NewJWT generates and returns new HS256 signed JWT token.
// NewJWT generates and returns new HS256 signed JWT.
func NewJWT(payload jwt.MapClaims, signingKey string, secondsDuration int64) (string, error) {
seconds := time.Duration(secondsDuration) * time.Second
@@ -60,7 +60,7 @@ func NewJWT(payload jwt.MapClaims, signingKey string, secondsDuration int64) (st
// Deprecated:
// Consider replacing with NewJWT().
//
// NewToken is a legacy alias for NewJWT that generates a HS256 signed JWT token.
// NewToken is a legacy alias for NewJWT that generates a HS256 signed JWT.
func NewToken(payload jwt.MapClaims, signingKey string, secondsDuration int64) (string, error) {
return NewJWT(payload, signingKey, secondsDuration)
}
+10 -10
View File
@@ -8,7 +8,7 @@ import (
)
func TestParseUnverifiedJWT(t *testing.T) {
// invalid formatted JWT token
// invalid formatted JWT
result1, err1 := security.ParseUnverifiedJWT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCJ9")
if err1 == nil {
t.Error("Expected error got nil")
@@ -17,7 +17,7 @@ func TestParseUnverifiedJWT(t *testing.T) {
t.Error("Expected no parsed claims, got", result1)
}
// properly formatted JWT token with INVALID claims
// properly formatted JWT with INVALID claims
// {"name": "test", "exp": 1516239022}
result2, err2 := security.ParseUnverifiedJWT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImV4cCI6MTUxNjIzOTAyMn0.xYHirwESfSEW3Cq2BL47CEASvD_p_ps3QCA54XtNktU")
if err2 == nil {
@@ -27,7 +27,7 @@ func TestParseUnverifiedJWT(t *testing.T) {
t.Errorf("Expected to have 2 claims, got %v", result2)
}
// properly formatted JWT token with VALID claims
// properly formatted JWT with VALID claims
// {"name": "test"}
result3, err3 := security.ParseUnverifiedJWT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCJ9.ml0QsTms3K9wMygTu41ZhKlTyjmW9zHQtoS8FUsCCjU")
if err3 != nil {
@@ -45,14 +45,14 @@ func TestParseJWT(t *testing.T) {
expectError bool
expectClaims jwt.MapClaims
}{
// invalid formatted JWT token
// invalid formatted JWT
{
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCJ9",
"test",
true,
nil,
},
// properly formatted JWT token with INVALID claims and INVALID secret
// properly formatted JWT with INVALID claims and INVALID secret
// {"name": "test", "exp": 1516239022}
{
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImV4cCI6MTUxNjIzOTAyMn0.xYHirwESfSEW3Cq2BL47CEASvD_p_ps3QCA54XtNktU",
@@ -60,7 +60,7 @@ func TestParseJWT(t *testing.T) {
true,
nil,
},
// properly formatted JWT token with INVALID claims and VALID secret
// properly formatted JWT with INVALID claims and VALID secret
// {"name": "test", "exp": 1516239022}
{
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImV4cCI6MTUxNjIzOTAyMn0.xYHirwESfSEW3Cq2BL47CEASvD_p_ps3QCA54XtNktU",
@@ -68,7 +68,7 @@ func TestParseJWT(t *testing.T) {
true,
nil,
},
// properly formatted JWT token with VALID claims and INVALID secret
// properly formatted JWT with VALID claims and INVALID secret
// {"name": "test", "exp": 1898636137}
{
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImV4cCI6MTg5ODYzNjEzN30.gqRkHjpK5s1PxxBn9qPaWEWxTbpc1PPSD-an83TsXRY",
@@ -76,7 +76,7 @@ func TestParseJWT(t *testing.T) {
true,
nil,
},
// properly formatted EXPIRED JWT token with VALID secret
// properly formatted EXPIRED JWT with VALID secret
// {"name": "test", "exp": 1652097610}
{
"eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidGVzdCIsImV4cCI6OTU3ODczMzc0fQ.0oUUKUnsQHs4nZO1pnxQHahKtcHspHu4_AplN2sGC4A",
@@ -84,7 +84,7 @@ func TestParseJWT(t *testing.T) {
true,
nil,
},
// properly formatted JWT token with VALID claims and VALID secret
// properly formatted JWT with VALID claims and VALID secret
// {"name": "test", "exp": 1898636137}
{
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCIsImV4cCI6MTg5ODYzNjEzN30.gqRkHjpK5s1PxxBn9qPaWEWxTbpc1PPSD-an83TsXRY",
@@ -92,7 +92,7 @@ func TestParseJWT(t *testing.T) {
false,
jwt.MapClaims{"name": "test", "exp": 1898636137.0},
},
// properly formatted JWT token with VALID claims (without exp) and VALID secret
// properly formatted JWT with VALID claims (without exp) and VALID secret
// {"name": "test"}
{
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCJ9.ml0QsTms3K9wMygTu41ZhKlTyjmW9zHQtoS8FUsCCjU",
+1 -1
View File
@@ -9,4 +9,4 @@ 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.20.0"
PB_VERSION = "v0.20.6"
Loaded 100 of 155 files, more files were not shown because too many files have changed in this diff. Show more