Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
ganigeorgiev committed Feb 26, 2024
2 parents aaa6e97 + d084800 commit f414e70
Show file tree
Hide file tree
Showing 120 changed files with 8,406 additions and 7,234 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 20.8.1
node-version: 20.11.0

- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: '>=1.21.6'
go-version: '>=1.21.7'

# 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.
Expand Down
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
## v0.22.0

- Added Planning Center OAuth2 provider (thanks @alxjsn).

- Admin UI improvements:
- Autosync collection changes across multiple open browser tabs.
- Fixed vertical image popup preview scrolling.
- Added options to export a subset of collections.
- Added option to import a subset of collections without deleting the others ([#3403](https://github.com/pocketbase/pocketbase/issues/3403)).

- Added support for back/indirect relation `filter`/`sort` (single and multiple).
The syntax to reference back relation fields is `yourCollection_via_yourRelField.*`.
⚠️ To avoid excessive joins, the nested relations resolver is now limited to max 6 level depth (the same as `expand`).
_Note that in the future there will be also more advanced and granular options to specify a subset of the fields that are filterable/sortable._

- Added support for multiple back/indirect relation `expand` and updated the keys to use the `_via_` reference syntax (`yourCollection_via_yourRelField`).
_To minimize the breaking changes, the old parenthesis reference syntax (`yourCollection(yourRelField)`) will still continue to work but it is soft-deprecated and there will be a console log reminding you to change it to the new one._

- ⚠️ Collections and fields are no longer allowed to have `_via_` in their name to avoid collisions with the back/indirect relation reference syntax.

- Added `jsvm.Config.OnInit` optional config function to allow registering custom Go bindings to the JSVM.

- Added `@request.context` rule field that can be used to apply a different set of constrtaints based on the API rule execution context.
For example, to disallow user creation by an OAuth2 auth, you could set for the users Create API rule `@request.context != "oauth2"`.
The currently supported `@request.context` values are:
```
default
realtime
protectedFile
oauth2
```

- Adjusted the `cron.Start()` to start the ticker at the `00` second of the cron interval ([#4394](https://github.com/pocketbase/pocketbase/discussions/4394)).
_Note that the cron format has only minute granularity and there is still no guarantee that the sheduled job will be always executed at the `00` second._

- Fixed auto backups cron not reloading properly after app settings change ([#4431](https://github.com/pocketbase/pocketbase/discussions/4431)).

- Upgraded to `aws-sdk-go-v2` and added special handling for GCS to workaround the previous [GCS headers signature issue](https://github.com/pocketbase/pocketbase/issues/2231) that we had with v2.
_This should also fix the SVG/JSON zero response when using Cloudflare R2 ([#4287](https://github.com/pocketbase/pocketbase/issues/4287#issuecomment-1925168142), [#2068](https://github.com/pocketbase/pocketbase/discussions/2068), [#2952](https://github.com/pocketbase/pocketbase/discussions/2952))._
_⚠️ If you are using S3 for uploaded files or backups, please verify that you have a green check in the Admin UI for your S3 configuration (I've tested the new version with GCS, MinIO, Cloudflare R2 and Wasabi)._

- Added `:each` modifier support for `file` and `relation` type fields (_previously it was supported only for `select` type fields_).

- Other minor improvements (updated the `ghupdate` plugin to use the configured executable name when printing to the console, fixed the error reporting of `admin update/delete` commands, etc.).


## v0.21.3

- Ignore the JS required validations for disabled OIDC providers ([#4322](https://github.com/pocketbase/pocketbase/issues/4322)).
Expand Down
3 changes: 2 additions & 1 deletion apis/collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,8 @@ func TestCollectionUpdate(t *testing.T) {
{"type":"text","name":"password"},
{"type":"text","name":"passwordConfirm"},
{"type":"text","name":"oldPassword"}
]
],
"indexes": []
}`),
RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
Expand Down
1 change: 1 addition & 0 deletions apis/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ func (api *fileApi) download(c echo.Context) error {

// create a copy of the cached request data and adjust it for the current auth model
requestInfo := *RequestInfo(c)
requestInfo.Context = models.RequestInfoContextProtectedFile
requestInfo.Admin = nil
requestInfo.AuthRecord = nil
if adminOrAuthRecord != nil {
Expand Down
1 change: 1 addition & 0 deletions apis/realtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ func (api *realtimeApi) broadcastRecord(action string, record *models.Record, dr

// mock request data
requestInfo := &models.RequestInfo{
Context: models.RequestInfoContextRealtime,
Method: "GET",
Query: options.Query,
Headers: options.Headers,
Expand Down
2 changes: 2 additions & 0 deletions apis/record_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,10 @@ func (api *recordAuthApi) authWithOAuth2(c echo.Context) error {
form.SetBeforeNewRecordCreateFunc(func(createForm *forms.RecordUpsert, authRecord *models.Record, authUser *auth.AuthUser) error {
return createForm.DrySubmit(func(txDao *daos.Dao) error {
event.IsNewRecord = true

// clone the current request data and assign the form create data as its body data
requestInfo := *RequestInfo(c)
requestInfo.Context = models.RequestInfoContextOAuth2
requestInfo.Data = form.CreateData

createRuleFunc := func(q *dbx.SelectQuery) error {
Expand Down
1 change: 1 addition & 0 deletions apis/record_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func RequestInfo(c echo.Context) *models.RequestInfo {
}

result := &models.RequestInfo{
Context: models.RequestInfoContextDefault,
Method: c.Request().Method,
Query: map[string]any{},
Data: map[string]any{},
Expand Down
3 changes: 3 additions & 0 deletions apis/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func TestSettingsList(t *testing.T) {
`"patreonAuth":{`,
`"mailcowAuth":{`,
`"bitbucketAuth":{`,
`"planningcenterAuth":{`,
`"secret":"******"`,
`"clientSecret":"******"`,
},
Expand Down Expand Up @@ -171,6 +172,7 @@ func TestSettingsSet(t *testing.T) {
`"patreonAuth":{`,
`"mailcowAuth":{`,
`"bitbucketAuth":{`,
`"planningcenterAuth":{`,
`"secret":"******"`,
`"clientSecret":"******"`,
`"appName":"acme_test"`,
Expand Down Expand Up @@ -244,6 +246,7 @@ func TestSettingsSet(t *testing.T) {
`"patreonAuth":{`,
`"mailcowAuth":{`,
`"bitbucketAuth":{`,
`"planningcenterAuth":{`,
`"secret":"******"`,
`"clientSecret":"******"`,
`"appName":"update_test"`,
Expand Down
20 changes: 8 additions & 12 deletions cmd/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,10 @@ func adminCreateCommand(app core.App) *cobra.Command {

func adminUpdateCommand(app core.App) *cobra.Command {
command := &cobra.Command{
Use: "update",
Example: "admin update test@example.com 1234567890",
Short: "Changes the password of a single admin account",
// prevents printing the error log twice
SilenceErrors: true,
SilenceUsage: true,
Use: "update",
Example: "admin update test@example.com 1234567890",
Short: "Changes the password of a single admin account",
SilenceUsage: true,
RunE: func(command *cobra.Command, args []string) error {
if len(args) != 2 {
return errors.New("Missing email and password arguments.")
Expand Down Expand Up @@ -111,12 +109,10 @@ func adminUpdateCommand(app core.App) *cobra.Command {

func adminDeleteCommand(app core.App) *cobra.Command {
command := &cobra.Command{
Use: "delete",
Example: "admin delete test@example.com",
Short: "Deletes an existing admin account",
// prevents printing the error log twice
SilenceErrors: true,
SilenceUsage: true,
Use: "delete",
Example: "admin delete test@example.com",
Short: "Deletes an existing admin account",
SilenceUsage: true,
RunE: func(command *cobra.Command, args []string) error {
if len(args) == 0 || args[0] == "" || is.EmailFormat.Validate(args[0]) != nil {
return errors.New("Invalid or missing email address.")
Expand Down
37 changes: 23 additions & 14 deletions core/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -599,10 +599,10 @@ func (app *BaseApp) RefreshSettings() error {
return err
}

// reload handler level (if initialized and not in dev mode)
if !app.IsDev() && app.Logger() != nil {
// reload handler level (if initialized)
if app.Logger() != nil {
if h, ok := app.Logger().Handler().(*logger.BatchHandler); ok {
h.SetLevel(slog.Level(app.settings.Logs.MinLevel))
h.SetLevel(app.getLoggerMinLevel())
}
}

Expand Down Expand Up @@ -1184,25 +1184,34 @@ func (app *BaseApp) registerDefaultHooks() {
}
}

func (app *BaseApp) initLogger() error {
duration := 3 * time.Second
ticker := time.NewTicker(duration)
done := make(chan bool)

// 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.
// getLoggerMinLevel returns the logger min level based on the
// app configurations (dev mode, settings, etc.).
//
// If not in dev mode - returns the level from the app settings.
//
// If the app is in dev mode it returns -9999 level allowing to print
// practically all logs to the terminal.
// In this case 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.
func (app *BaseApp) getLoggerMinLevel() slog.Level {
var minLevel slog.Level

if app.IsDev() {
minLevel = -9999
} else if app.Settings() != nil {
minLevel = slog.Level(app.Settings().Logs.MinLevel)
}

return minLevel
}

func (app *BaseApp) initLogger() error {
duration := 3 * time.Second
ticker := time.NewTicker(duration)
done := make(chan bool)

handler := logger.NewBatchHandler(logger.BatchOptions{
Level: minLevel,
Level: app.getLoggerMinLevel(),
BatchSize: 200,
BeforeAddFunc: func(ctx context.Context, log *logger.Log) bool {
if app.IsDev() {
Expand Down
10 changes: 10 additions & 0 deletions core/base_backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,16 @@ func (app *BaseApp) initAutobackupHooks() error {
loadJob := func() {
c.Stop()

// make sure that app.Settings() is always up to date
//
// @todo remove with the refactoring as core.App and daos.Dao will be one.
if err := app.RefreshSettings(); err != nil {
app.Logger().Debug(
"[Backup cron] Failed to get the latest app settings",
slog.String("error", err.Error()),
)
}

rawSchedule := app.Settings().Backups.Cron
if rawSchedule == "" || !isServe || !app.IsBootstrapped() {
return
Expand Down
57 changes: 30 additions & 27 deletions daos/collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,11 @@ func TestFindCollectionReferences(t *testing.T) {
"rel_one_no_cascade",
"rel_one_no_cascade_required",
"rel_one_cascade",
"rel_one_unique",
"rel_many_no_cascade",
"rel_many_no_cascade_required",
"rel_many_cascade",
"rel_many_unique",
}

for col, fields := range result {
Expand Down Expand Up @@ -756,7 +758,7 @@ func TestImportCollections(t *testing.T) {
"demo1": 15,
"demo2": 2,
"demo3": 2,
"demo4": 11,
"demo4": 13,
"demo5": 6,
"new_import": 1,
}
Expand All @@ -774,37 +776,38 @@ func TestImportCollections(t *testing.T) {
},
}

for _, scenario := range scenarios {
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()

importedCollections := []*models.Collection{}
importedCollections := []*models.Collection{}

// load data
loadErr := json.Unmarshal([]byte(scenario.jsonData), &importedCollections)
if loadErr != nil {
t.Fatalf("[%s] Failed to load data: %v", scenario.name, loadErr)
continue
}
// load data
loadErr := json.Unmarshal([]byte(s.jsonData), &importedCollections)
if loadErr != nil {
t.Fatalf("Failed to load data: %v", loadErr)
}

err := testApp.Dao().ImportCollections(importedCollections, scenario.deleteMissing, scenario.beforeRecordsSync)
err := testApp.Dao().ImportCollections(importedCollections, s.deleteMissing, s.beforeRecordsSync)

hasErr := err != nil
if hasErr != scenario.expectError {
t.Errorf("[%s] Expected hasErr to be %v, got %v (%v)", scenario.name, scenario.expectError, hasErr, err)
}
hasErr := err != nil
if hasErr != s.expectError {
t.Fatalf("Expected hasErr to be %v, got %v (%v)", s.expectError, hasErr, err)
}

// check collections count
collections := []*models.Collection{}
if err := testApp.Dao().CollectionQuery().All(&collections); err != nil {
t.Fatal(err)
}
if len(collections) != scenario.expectCollectionsCount {
t.Errorf("[%s] Expected %d collections, got %d", scenario.name, scenario.expectCollectionsCount, len(collections))
}
// check collections count
collections := []*models.Collection{}
if err := testApp.Dao().CollectionQuery().All(&collections); err != nil {
t.Fatal(err)
}
if len(collections) != s.expectCollectionsCount {
t.Fatalf("Expected %d collections, got %d", s.expectCollectionsCount, len(collections))
}

if scenario.afterTestFunc != nil {
scenario.afterTestFunc(testApp, collections)
}
if s.afterTestFunc != nil {
s.afterTestFunc(testApp, collections)
}
})
}
}
2 changes: 0 additions & 2 deletions daos/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,6 @@ func (dao *Dao) FindRecordsByIds(
return records, nil
}

// @todo consider to depricate as it may be easier to just use dao.RecordQuery()
//
// FindRecordsByExpr finds all records by the specified db expression.
//
// Returns all collection records if no expressions are provided.
Expand Down
Loading

0 comments on commit f414e70

Please sign in to comment.