mirror of
https://github.com/pocketbase/pocketbase.git
synced 2026-10-03 13:01:43 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
931f6bc0cb | ||
|
|
2e3ae1b60a | ||
|
|
eb695cc6d3 | ||
|
|
28d15e86eb | ||
|
|
d0352aa3f9 | ||
|
|
592b7e85c6 | ||
|
|
3792d44c35 | ||
|
|
a021fcaa75 | ||
|
|
982f876a93 | ||
|
|
1fcc2d8683 | ||
|
|
4f2492290e | ||
|
|
8f625daa2f | ||
|
|
0599955676 | ||
|
|
c4116e3a7d | ||
|
|
64cee264f0 | ||
|
|
0ae9f24a81 | ||
|
|
6d942c7d30 | ||
|
|
9f67c5d563 | ||
|
|
1ac7330e0b | ||
|
|
461886f64e | ||
|
|
8d97eb0769 | ||
|
|
1208edec92 | ||
|
|
5555e63116 | ||
|
|
d6569b445c | ||
|
|
0b4f3b2adf | ||
|
|
8bd968ed06 | ||
|
|
5c961f8537 | ||
|
|
bf5eba0384 | ||
|
|
c213d9313e | ||
|
|
b31cf984a5 | ||
|
|
8671debc35 | ||
|
|
b0f027d27a |
No files matched your search
@@ -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
@@ -51,8 +51,8 @@ archives:
|
||||
builds: [build_noncgo]
|
||||
format: zip
|
||||
files:
|
||||
- LICENSE*
|
||||
- CHANGELOG*
|
||||
- LICENSE.md
|
||||
- CHANGELOG.md
|
||||
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
|
||||
+92
-1407
File diff suppressed because it is too large.
Load diff
+1384
File diff suppressed because it is too large.
Load diff
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestHealthAPI(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "health status returns 200",
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestAdminCreateCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
+5
-2
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestSaveAndFindSettings(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
)
|
||||
|
||||
func TestAppleClientSecretCreateValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func TestBackupCreateValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
backupName string
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func TestUserOauth2LoginValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func TestEmailSendValidateAndSubmit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
template string
|
||||
email string
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestUniqueId(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ func TestRecordDataValidatorEmptyAndUnknown(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRecordDataValidatorValidateText(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func TestCompare(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
valA string
|
||||
valB string
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
)
|
||||
|
||||
func TestSendAdminPasswordReset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
|
||||
@@ -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,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()
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRequestInfoHasModifierDataKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
requestInfo *models.RequestInfo
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+4978
-4955
File diff suppressed because it is too large.
Load diff
@@ -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
@@ -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
@@ -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:])
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
@@ -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.
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
`
|
||||
|
||||
@@ -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]),
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
Reference in new issue
Block a user