mirror of
https://github.com/pocketbase/pocketbase.git
synced 2026-09-20 13:30:50 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cfabc61e6 | ||
|
|
34a25f640b | ||
|
|
4ac0954546 | ||
|
|
4745bb4286 | ||
|
|
7734d63e51 | ||
|
|
4286812a09 | ||
|
|
8264ead4de | ||
|
|
4dc8a10af5 | ||
|
|
a9d468a863 | ||
|
|
ebc1ed6598 | ||
|
|
c951d4bc94 | ||
|
|
bee8e0826a | ||
|
|
d5fc74d973 | ||
|
|
63bcffb223 | ||
|
|
ac76166cb2 | ||
|
|
37dd9c8645 | ||
|
|
9eb3ff5833 | ||
|
|
9090979a8d | ||
|
|
7ce2545b8b | ||
|
|
31f2ba89e8 | ||
|
|
0122d4f527 | ||
|
|
b596bbdc3e | ||
|
|
04927178e5 | ||
|
|
98ba003921 | ||
|
|
309c4fe6fe | ||
|
|
03cec9a5ac | ||
|
|
48153d4542 | ||
|
|
be40803d31 | ||
|
|
a5eff395b4 | ||
|
|
20653ef786 | ||
|
|
0f1b73a4f5 | ||
|
|
adab0da179 | ||
|
|
e5e2519f88 | ||
|
|
0afc380a11 | ||
|
|
3551dea44a | ||
|
|
eff09852a4 | ||
|
|
90c313cf09 | ||
|
|
5574fe39ce | ||
|
|
6695aba758 | ||
|
|
1eeacf0204 | ||
|
|
4a1736a785 | ||
|
|
186d2ed328 | ||
|
|
35d7b5f056 | ||
|
|
bb410e7e0d | ||
|
|
9babca5f77 | ||
|
|
b845d3dbea | ||
|
|
39d24ba897 | ||
|
|
631957fa32 | ||
|
|
f414e70ffa | ||
|
|
d084800c45 | ||
|
|
f1a6c19309 | ||
|
|
53ee5212bc | ||
|
|
548fce20b5 | ||
|
|
1014c92d86 | ||
|
|
88c56cd539 | ||
|
|
a8b363ed76 | ||
|
|
6132fb4a03 | ||
|
|
20fba0f686 | ||
|
|
4f46222de9 | ||
|
|
4fba93e834 | ||
|
|
5a715cc60a | ||
|
|
f2ed186540 | ||
|
|
4937acb3e2 | ||
|
|
4743c1ce72 | ||
|
|
a11abef84b | ||
|
|
6aaf98215d | ||
|
|
2662d875b9 | ||
|
|
959c6b6d6c | ||
|
|
d4a2f05075 | ||
|
|
4c14c6cccf | ||
|
|
aaa6e971a3 | ||
|
|
27b6d0c505 | ||
|
|
a46815ed69 | ||
|
|
a5fdbeae79 | ||
|
|
8599754e45 | ||
|
|
71141dde69 | ||
|
|
388f61aed6 | ||
|
|
c32f272123 | ||
|
|
81ef6f1127 | ||
|
|
8f8a7c3268 | ||
|
|
1b89aabf14 | ||
|
|
5f1b2fda74 | ||
|
|
9f1c1c2e33 | ||
|
|
7ef118581b | ||
|
|
b7447f3e27 | ||
|
|
368af1f0fc | ||
|
|
41aa9b189c | ||
|
|
ed9cc2f33c | ||
|
|
bada2338f7 | ||
|
|
722a74994f | ||
|
|
442b286b1d | ||
|
|
5105612a45 | ||
|
|
b9029010d9 | ||
|
|
03a3f9876e | ||
|
|
49adba6947 | ||
|
|
ef965aafbb | ||
|
|
fa8e3d83b5 | ||
|
|
8402938191 | ||
|
|
8a0eed22fa | ||
|
|
3b6fcf265a | ||
|
|
fb78a39161 | ||
|
|
9436efb7fd | ||
|
|
05556e7cbc | ||
|
|
2862119c1f | ||
|
|
eaf121ead7 | ||
|
|
aabe820e35 | ||
|
|
80d65a198b | ||
|
|
3013e0299a | ||
|
|
6fd2e7ab0f | ||
|
|
a44a73a17c | ||
|
|
bf30af393e | ||
|
|
f0410a7625 | ||
|
|
ba56623245 | ||
|
|
702b4aa1c2 | ||
|
|
3f7db19fdd | ||
|
|
9855397a22 | ||
|
|
d9b219d64f | ||
|
|
c642a860ca | ||
|
|
b2b792b763 | ||
|
|
fc18e69183 | ||
|
|
fa65038fc1 | ||
|
|
c9bc2f07aa | ||
|
|
28fc186f5c | ||
|
|
cdb539dcc8 | ||
|
|
af7c6d8d9b | ||
|
|
cd2fc536ca | ||
|
|
2a28f6ff33 | ||
|
|
036c0da05f | ||
|
|
d795a6671b | ||
|
|
8cff94f27c | ||
|
|
6155e6426a | ||
|
|
0f95a11fc1 | ||
|
|
b033109654 | ||
|
|
3b7d0e84f6 | ||
|
|
d123e19e61 | ||
|
|
97a8409a65 | ||
|
|
422eb30797 | ||
|
|
705d7f48e7 | ||
|
|
e73b3a32d2 | ||
|
|
370862fa2e | ||
|
|
6b3780c630 | ||
|
|
c807f66c59 | ||
|
|
5f5f9ca426 | ||
|
|
4e91be6d74 |
@@ -1,13 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: pocketbase
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: ['https://www.paypal.com/donate/?hosted_button_id=4DVXNL4B8WT98']
|
||||
@@ -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.22.2'
|
||||
|
||||
# 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.
|
||||
|
||||
+182
@@ -1,3 +1,185 @@
|
||||
## v0.22.9
|
||||
|
||||
- Fixed Admin UI OAuth2 "Clear all fields" btn action to properly unset all form fields ([#4737](https://github.com/pocketbase/pocketbase/issues/4737)).
|
||||
|
||||
|
||||
## v0.22.8
|
||||
|
||||
- Fixed '~' auto wildcard wrapping when the param has escaped `%` character ([#4704](https://github.com/pocketbase/pocketbase/discussions/4704)).
|
||||
|
||||
- Other minor UI improvements (added `aria-expanded=true/false` to the dropdown triggers, added contrasting border around the default mail template btn style, etc.).
|
||||
|
||||
- Updated Go deps and bumped the min Go version in the GitHub release action to Go 1.22.2 since it comes with [some `net/http` security and bug fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.22.2).
|
||||
|
||||
|
||||
## v0.22.7
|
||||
|
||||
- Replaced the default `s3blob` driver with a trimmed vendored version to reduce the binary size with ~10MB.
|
||||
_It can be further reduced with another ~10MB once we replace entirely the `aws-sdk-go-v2` dependency but I stumbled on some edge cases related to the headers signing and for now is on hold._
|
||||
|
||||
- Other minor improvements (updated GitLab OAuth2 provider logo [#4650](https://github.com/pocketbase/pocketbase/pull/4650), normalized error messages, updated npm dependencies, etc.)
|
||||
|
||||
|
||||
## v0.22.6
|
||||
|
||||
- Admin UI accessibility improvements:
|
||||
- Fixed the dropdowns tab/enter/space keyboard navigation ([#4607](https://github.com/pocketbase/pocketbase/issues/4607)).
|
||||
- Added `role`, `aria-label`, `aria-hidden` attributes to some of the elements in attempt to better assist screen readers.
|
||||
|
||||
|
||||
## v0.22.5
|
||||
|
||||
- Minor test helpers fixes ([#4600](https://github.com/pocketbase/pocketbase/issues/4600)):
|
||||
- Call the `OnTerminate` hook on `TestApp.Cleanup()`.
|
||||
- Automatically run the DB migrations on initializing the test app with `tests.NewTestApp()`.
|
||||
|
||||
- Added more elaborate warning message when restoring a backup explaining how the operation works.
|
||||
|
||||
- Skip irregular files (symbolic links, sockets, etc.) when restoring a backup zip from the Admin UI or calling `archive.Extract(src, dst)` because they come with too many edge cases and ambiguities.
|
||||
<details>
|
||||
<summary><b><i>More details</i></b></summary>
|
||||
|
||||
This was initially reported as security issue (_thanks Harvey Spec_) but in the PocketBase context it is not something that can be exploited without an admin intervention and since the general expectations are that the PocketBase admins can do anything and they are the one who manage their server, this should be treated with the same diligence when using `scp`/`rsync`/`rclone`/etc. with untrusted file sources.
|
||||
|
||||
It is not possible (_or at least I'm not aware how to do that easily_) to perform virus/malicious content scanning on the uploaded backup archive files and some caution is always required when using the Admin UI or running shell commands, hence the backup-restore warning text.
|
||||
|
||||
**Or in other words, if someone sends you a file and tell you to upload it to your server (either as backup zip or manually via scp) obviously you shouldn't do that unless you really trust them.**
|
||||
|
||||
PocketBase is like any other regular application that you run on your server and there is no builtin "sandbox" for what the PocketBase process can execute. This is left to the developers to restrict on application or OS level depending on their needs. If you are self-hosting PocketBase you usually don't have to do that, but if you are offering PocketBase as a service and allow strangers to run their own PocketBase instances on your server then you'll need to implement the isolation mechanisms on your own.
|
||||
</details>
|
||||
|
||||
|
||||
## v0.22.4
|
||||
|
||||
- Removed conflicting styles causing the detailed codeblock log data preview to not visualize properly ([#4505](https://github.com/pocketbase/pocketbase/pull/4505)).
|
||||
|
||||
- Minor JSVM improvements:
|
||||
- Added `$filesystem.fileFromUrl(url, optSecTimeout)` helper.
|
||||
- Implemented the `FormData` interface and added support for sending `multipart/form-data` requests with `$http.send()` ([#4544](https://github.com/pocketbase/pocketbase/discussions/4544)).
|
||||
|
||||
|
||||
## v0.22.3
|
||||
|
||||
- Fixed the z-index of the current admin dropdown on Safari ([#4492](https://github.com/pocketbase/pocketbase/issues/4492)).
|
||||
|
||||
- Fixed `OnAfterApiError` debug log `nil` error reference ([#4498](https://github.com/pocketbase/pocketbase/issues/4498)).
|
||||
|
||||
- Added the field name as part of the `@request.data.someRelField.*` join to handle the case when a collection has 2 or more relation fields pointing to the same place ([#4500](https://github.com/pocketbase/pocketbase/issues/4500)).
|
||||
|
||||
- Updated Go deps and bumped the min Go version in the GitHub release action to Go 1.22.1 since it comes with [some security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.22.1).
|
||||
|
||||
|
||||
## v0.22.2
|
||||
|
||||
- Fixed a small regression introduced with v0.22.0 that was causing some missing unknown fields to always return an error instead of applying the specific `nullifyMisingField` resolver option to the query.
|
||||
|
||||
|
||||
## v0.22.1
|
||||
|
||||
- Fixed Admin UI record and collection panels not reinitializing properly on browser back/forward navigation ([#4462](https://github.com/pocketbase/pocketbase/issues/4462)).
|
||||
|
||||
- Initialize `RecordAuthWithOAuth2Event.IsNewRecord` for the `OnRecordBeforeAuthWithOAuth2Request` hook ([#4437](https://github.com/pocketbase/pocketbase/discussions/4437)).
|
||||
|
||||
- Added error checks to the autogenerated Go migrations ([#4448](https://github.com/pocketbase/pocketbase/issues/4448)).
|
||||
|
||||
|
||||
## v0.22.0
|
||||
|
||||
- Added Planning Center OAuth2 provider ([#4393](https://github.com/pocketbase/pocketbase/pull/4393); 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 constraints 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 scheduled 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)).
|
||||
|
||||
- Allow `HEAD` requests to the `/api/health` endpoint ([#4310](https://github.com/pocketbase/pocketbase/issues/4310)).
|
||||
|
||||
- Fixed the `editor` field value when visualized inside the View collection preview panel.
|
||||
|
||||
- Manually clear all TinyMCE events on editor removal (_workaround for [tinymce#9377](https://github.com/tinymce/tinymce/issues/9377)_).
|
||||
|
||||
|
||||
## v0.21.2
|
||||
|
||||
- Fixed `@request.auth.*` initialization side-effect which caused the current authenticated user email to not being returned in the user auth response ([#2173](https://github.com/pocketbase/pocketbase/issues/2173#issuecomment-1932332038)).
|
||||
_The current authenticated user email should be accessible always no matter of the `emailVisibility` state._
|
||||
|
||||
- Fixed `RecordUpsert.RemoveFiles` godoc example.
|
||||
|
||||
- Bumped to `NumCPU()+2` the `thumbGenSem` limit as some users reported that it was too restrictive.
|
||||
|
||||
|
||||
## v0.21.1
|
||||
|
||||
- Small fix for the Admin UI related to the _Settings > Sync_ menu not being visible even when the "Hide controls" toggle is off.
|
||||
|
||||
|
||||
## v0.21.0
|
||||
|
||||
- Added Bitbucket OAuth2 provider ([#3948](https://github.com/pocketbase/pocketbase/pull/3948); thanks @aabajyan).
|
||||
|
||||
- Mark user as verified on confirm password reset ([#4066](https://github.com/pocketbase/pocketbase/issues/4066)).
|
||||
_If the user email has changed after issuing the reset token (eg. updated by an admin), then the `verified` user state remains unchanged._
|
||||
|
||||
- Added support for loading a serialized json payload for `multipart/form-data` requests using the special `@jsonPayload` key.
|
||||
_This is intended to be used primarily by the SDKs to resolve [js-sdk#274](https://github.com/pocketbase/js-sdk/issues/274)._
|
||||
|
||||
- Added graceful OAuth2 redirect error handling ([#4177](https://github.com/pocketbase/pocketbase/issues/4177)).
|
||||
_Previously on redirect error we were returning directly a standard json error response. Now on redirect error we'll redirect to a generic OAuth2 failure screen (similar to the success one) and will attempt to auto close the OAuth2 popup._
|
||||
_The SDKs are also updated to handle the OAuth2 redirect error and it will be returned as Promise rejection of the `authWithOAuth2()` call._
|
||||
|
||||
- Exposed `$apis.gzip()` and `$apis.bodyLimit(bytes)` middlewares to the JSVM.
|
||||
|
||||
- Added `TestMailer.SentMessages` field that holds all sent test app emails until cleanup.
|
||||
|
||||
- Optimized the cascade delete of records with multiple `relation` fields.
|
||||
|
||||
- Updated the `serve` and `admin` commands error reporting.
|
||||
|
||||
- Minor Admin UI improvements (reduced the min table row height, added option to duplicate fields, added new TinyMCE codesample plugin languages, hide the collection sync settings when the `Settings.Meta.HideControls` is enabled, etc.)
|
||||
|
||||
|
||||
## v0.20.7
|
||||
|
||||
- Fixed the Admin UI auto indexes update when renaming fields with a common prefix ([#4160](https://github.com/pocketbase/pocketbase/issues/4160)).
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@ This document describes how to prepare a PR for a change in the main repository.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Go 1.19+ (for making changes in the Go code)
|
||||
- Node 16+ (for making changes in the Admin UI)
|
||||
- Go 1.21+ (for making changes in the Go code)
|
||||
- Node 18+ (for making changes in the Admin UI)
|
||||
|
||||
If you haven't already, you can fork the main repository and clone your fork so that you can work locally:
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/pocketbase/pocketbase/actions/workflows/release.yaml" target="_blank" rel="noopener"><img src="https://github.com/pocketbase/pocketbase/actions/workflows/release.yaml/badge.svg" alt="build" /></a>
|
||||
<a href="https://github.com/pocketbase/pocketbase/releases" target="_blank" rel="noopener"><img src="https://img.shields.io/github/release/pocketbase/pocketbase.svg" alt="Latest releases" /></a>
|
||||
<a href="https://pkg.go.dev/github.com/pocketbase/pocketbase" target="_blank" rel="noopener"><img src="https://godoc.org/github.com/ganigeorgiev/fexpr?status.svg" alt="Go package documentation" /></a>
|
||||
<a href="https://pkg.go.dev/github.com/pocketbase/pocketbase" target="_blank" rel="noopener"><img src="https://godoc.org/github.com/pocketbase/pocketbase?status.svg" alt="Go package documentation" /></a>
|
||||
</p>
|
||||
|
||||
[PocketBase](https://pocketbase.io) is an open source Go backend, consisting of:
|
||||
@@ -32,64 +32,76 @@ The easiest way to interact with the API is to use one of the official SDK clien
|
||||
|
||||
## Overview
|
||||
|
||||
PocketBase could be [downloaded directly as a standalone app](https://github.com/pocketbase/pocketbase/releases) or it could be used as a Go framework/toolkit which allows you to build
|
||||
### Use as standalone app
|
||||
|
||||
You could download the prebuilt executable for your platform from the [Releases page](https://github.com/pocketbase/pocketbase/releases).
|
||||
Once downloaded, extract the archive and run `./pocketbase serve` in the extracted directory.
|
||||
|
||||
The prebuilt executables are based on the [`examples/base/main.go` file](https://github.com/pocketbase/pocketbase/blob/master/examples/base/main.go) and comes with the JS VM plugin enabled by default which allows to extend PocketBase with JavaScript (_for more details please refer to [Extend with JavaScript](https://pocketbase.io/docs/js-overview/)_).
|
||||
|
||||
### Use as a Go framework/toolkit
|
||||
|
||||
PocketBase is distributed as a regular Go library package which allows you to build
|
||||
your own custom app specific business logic and still have a single portable executable at the end.
|
||||
|
||||
### Installation
|
||||
Here is a minimal example:
|
||||
|
||||
```sh
|
||||
# go 1.21+
|
||||
go get github.com/pocketbase/pocketbase
|
||||
```
|
||||
0. [Install Go 1.21+](https://go.dev/doc/install) (_if you haven't already_)
|
||||
|
||||
### Example
|
||||
1. Create a new project directory with the following `main.go` file inside it:
|
||||
```go
|
||||
package main
|
||||
|
||||
```go
|
||||
package main
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
func main() {
|
||||
app := pocketbase.New()
|
||||
|
||||
func main() {
|
||||
app := pocketbase.New()
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
// add new "GET /hello" route to the app router (echo)
|
||||
e.Router.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/hello",
|
||||
Handler: func(c echo.Context) error {
|
||||
return c.String(200, "Hello world!")
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
},
|
||||
})
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
// add new "GET /hello" route to the app router (echo)
|
||||
e.Router.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/hello",
|
||||
Handler: func(c echo.Context) error {
|
||||
return c.String(200, "Hello world!")
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
},
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := app.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
if err := app.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### Running and building
|
||||
2. To init the dependencies, run `go mod init myapp && go mod tidy`.
|
||||
|
||||
Running/building the application is the same as for any other Go program, aka. just `go run` and `go build`.
|
||||
3. To start the application, run `go run main.go serve`.
|
||||
|
||||
**PocketBase embeds SQLite, but doesn't require CGO.**
|
||||
4. To build a statically linked executable, you can run `CGO_ENABLED=0 go build` and then start the created executable with `./myapp serve`.
|
||||
|
||||
If CGO is enabled (aka. `CGO_ENABLED=1`), it will use [mattn/go-sqlite3](https://pkg.go.dev/github.com/mattn/go-sqlite3) driver, otherwise - [modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite).
|
||||
Enable CGO only if you really need to squeeze the read/write query performance at the expense of complicating cross compilation.
|
||||
> [!NOTE]
|
||||
> PocketBase embeds SQLite, but doesn't require CGO.
|
||||
>
|
||||
> If CGO is enabled (aka. `CGO_ENABLED=1`), it will use [mattn/go-sqlite3](https://pkg.go.dev/github.com/mattn/go-sqlite3) driver, otherwise - [modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite).
|
||||
> Enable CGO only if you really need to squeeze the read/write query performance at the expense of complicating cross compilation.
|
||||
|
||||
_For more details please refer to [Extend with Go](https://pocketbase.io/docs/go-overview/)._
|
||||
|
||||
### Building and running the repo main.go example
|
||||
|
||||
To build the minimal standalone executable, like the prebuilt ones in the releases page, you can simply run `go build` inside the `examples/base` directory:
|
||||
|
||||
@@ -100,7 +112,7 @@ To build the minimal standalone executable, like the prebuilt ones in the releas
|
||||
(_https://go.dev/doc/install/source#environment_)
|
||||
4. Start the created executable by running `./base serve`.
|
||||
|
||||
The supported build targets by the non-cgo driver at the moment are:
|
||||
Note that the supported build targets by the pure Go SQLite driver at the moment are:
|
||||
|
||||
```
|
||||
darwin amd64
|
||||
@@ -121,7 +133,7 @@ windows arm64
|
||||
### Testing
|
||||
|
||||
PocketBase comes with mixed bag of unit and integration tests.
|
||||
To run them, use the default `go test` command:
|
||||
To run them, use the standard `go test` command:
|
||||
|
||||
```sh
|
||||
go test ./...
|
||||
@@ -144,7 +156,6 @@ You could help continuing its development by:
|
||||
|
||||
- [Contribute to the source code](CONTRIBUTING.md)
|
||||
- [Suggest new features and report issues](https://github.com/pocketbase/pocketbase/issues)
|
||||
- [Donate a small amount](https://pocketbase.io/support-us)
|
||||
|
||||
PRs for new OAuth2 providers, bug fixes, code optimizations and documentation improvements are more than welcome.
|
||||
|
||||
@@ -153,7 +164,3 @@ PocketBase has a [roadmap](https://github.com/orgs/pocketbase/projects/2) and I
|
||||
|
||||
Don't get upset if I close your PR, even if it is well executed and tested. This doesn't mean that it will never be merged.
|
||||
Later we can always refer to it and/or take pieces of your implementation when the time comes to work on the issue (don't worry you'll be credited in the release notes).
|
||||
|
||||
> [!NOTE]
|
||||
> PocketBase was initially created to serve as a new backend for my other open source project - [Presentator](https://presentator.io) (see [#183](https://github.com/presentator/presentator/issues/183)),
|
||||
> so all feature requests will be first aligned with what we need for Presentator v3.
|
||||
|
||||
+3
-2
@@ -28,6 +28,7 @@ const trailedAdminPath = "/_/"
|
||||
func InitApi(app core.App) (*echo.Echo, error) {
|
||||
e := echo.New()
|
||||
e.Debug = false
|
||||
e.Binder = &rest.MultiBinder{}
|
||||
e.JSONSerializer = &rest.Serializer{
|
||||
FieldsParam: fieldsQueryParam,
|
||||
}
|
||||
@@ -82,7 +83,7 @@ func InitApi(app core.App) (*echo.Echo, error) {
|
||||
logRequest(app, c, apiErr)
|
||||
|
||||
if c.Response().Committed {
|
||||
return // already commited
|
||||
return // already committed
|
||||
}
|
||||
|
||||
event := new(core.ApiErrorEvent)
|
||||
@@ -105,7 +106,7 @@ func InitApi(app core.App) (*echo.Echo, error) {
|
||||
|
||||
if hookErr == nil {
|
||||
if err := app.OnAfterApiError().Trigger(event); err != nil {
|
||||
app.Logger().Debug("OnAfterApiError failure", slog.String("error", hookErr.Error()))
|
||||
app.Logger().Debug("OnAfterApiError failure", slog.String("error", err.Error()))
|
||||
}
|
||||
} else {
|
||||
app.Logger().Debug("OnBeforeApiError error (truly rare case, eg. client already disconnected)", slog.String("error", hookErr.Error()))
|
||||
|
||||
+51
-40
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/tests"
|
||||
"github.com/pocketbase/pocketbase/tools/rest"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
@@ -220,15 +221,24 @@ func TestRemoveTrailingSlashMiddleware(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEagerRequestInfoCache(t *testing.T) {
|
||||
func TestMultiBinder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rawJson := `{"name":"test123"}`
|
||||
|
||||
formData, mp, err := tests.MockMultipartData(map[string]string{
|
||||
rest.MultipartJsonKey: rawJson,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "custom non-api group route",
|
||||
Name: "non-api group route",
|
||||
Method: "POST",
|
||||
Url: "/custom",
|
||||
Body: strings.NewReader(`{"name":"test123"}`),
|
||||
Body: strings.NewReader(rawJson),
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: "POST",
|
||||
@@ -242,11 +252,10 @@ func TestEagerRequestInfoCache(t *testing.T) {
|
||||
return err
|
||||
}
|
||||
|
||||
// since the unknown method is not eager cache support
|
||||
// it should fail reading the json body twice
|
||||
// try to read the body again
|
||||
r := apis.RequestInfo(c)
|
||||
if v := cast.ToString(r.Data["name"]); v != "" {
|
||||
t.Fatalf("Expected empty request data body, got, %v", r.Data)
|
||||
if v := cast.ToString(r.Data["name"]); v != "test123" {
|
||||
t.Fatalf("Expected request data with name %q, got, %q", "test123", v)
|
||||
}
|
||||
|
||||
return c.NoContent(200)
|
||||
@@ -256,41 +265,10 @@ func TestEagerRequestInfoCache(t *testing.T) {
|
||||
ExpectedStatus: 200,
|
||||
},
|
||||
{
|
||||
Name: "api group route with unsupported eager cache request method",
|
||||
Name: "api group route",
|
||||
Method: "GET",
|
||||
Url: "/api/admins",
|
||||
Body: strings.NewReader(`{"name":"test123"}`),
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
// it is not important whether the route handler return an error since
|
||||
// we just need to ensure that the eagerRequestInfoCache was registered
|
||||
next(c)
|
||||
|
||||
// ensure that the body was read at least once
|
||||
data := &struct {
|
||||
Name string `json:"name"`
|
||||
}{}
|
||||
c.Bind(data)
|
||||
|
||||
// since the unknown method is not eager cache support
|
||||
// it should fail reading the json body twice
|
||||
r := apis.RequestInfo(c)
|
||||
if v := cast.ToString(r.Data["name"]); v != "" {
|
||||
t.Fatalf("Expected empty request data body, got, %v", r.Data)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
})
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
},
|
||||
{
|
||||
Name: "api group route with supported eager cache request method",
|
||||
Method: "POST",
|
||||
Url: "/api/admins",
|
||||
Body: strings.NewReader(`{"name":"test123"}`),
|
||||
Body: strings.NewReader(rawJson),
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
@@ -316,6 +294,39 @@ func TestEagerRequestInfoCache(t *testing.T) {
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
},
|
||||
{
|
||||
Name: "custom route with @jsonPayload as multipart body",
|
||||
Method: "POST",
|
||||
Url: "/custom",
|
||||
Body: formData,
|
||||
RequestHeaders: map[string]string{
|
||||
"Content-Type": mp.FormDataContentType(),
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: "POST",
|
||||
Path: "/custom",
|
||||
Handler: func(c echo.Context) error {
|
||||
data := &struct {
|
||||
Name string `json:"name"`
|
||||
}{}
|
||||
|
||||
if err := c.Bind(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// try to read the body again
|
||||
r := apis.RequestInfo(c)
|
||||
if v := cast.ToString(r.Data["name"]); v != "test123" {
|
||||
t.Fatalf("Expected request data with name %q, got, %q", "test123", v)
|
||||
}
|
||||
|
||||
return c.NoContent(200)
|
||||
},
|
||||
})
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
},
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
|
||||
@@ -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",
|
||||
|
||||
+2
-1
@@ -30,7 +30,7 @@ var defaultThumbSizes = []string{"100x100"}
|
||||
func bindFileApi(app core.App, rg *echo.Group) {
|
||||
api := fileApi{
|
||||
app: app,
|
||||
thumbGenSem: semaphore.NewWeighted(int64(runtime.NumCPU() + 1)), // the value is arbitrary chosen and may change in the future
|
||||
thumbGenSem: semaphore.NewWeighted(int64(runtime.NumCPU() + 2)), // the value is arbitrary chosen and may change in the future
|
||||
thumbGenPending: new(singleflight.Group),
|
||||
thumbGenMaxWait: 60 * time.Second,
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
+6
-1
@@ -12,6 +12,7 @@ func bindHealthApi(app core.App, rg *echo.Group) {
|
||||
api := healthApi{app: app}
|
||||
|
||||
subGroup := rg.Group("/health")
|
||||
subGroup.HEAD("", api.healthCheck)
|
||||
subGroup.GET("", api.healthCheck)
|
||||
}
|
||||
|
||||
@@ -20,8 +21,8 @@ type healthApi struct {
|
||||
}
|
||||
|
||||
type healthCheckResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Code int `json:"code"`
|
||||
Data struct {
|
||||
CanBackup bool `json:"canBackup"`
|
||||
} `json:"data"`
|
||||
@@ -29,6 +30,10 @@ type healthCheckResponse struct {
|
||||
|
||||
// healthCheck returns a 200 OK response if the server is healthy.
|
||||
func (api *healthApi) healthCheck(c echo.Context) error {
|
||||
if c.Request().Method == http.MethodHead {
|
||||
return c.NoContent(http.StatusOK)
|
||||
}
|
||||
|
||||
resp := new(healthCheckResponse)
|
||||
resp.Code = http.StatusOK
|
||||
resp.Message = "API is healthy."
|
||||
|
||||
+7
-1
@@ -12,7 +12,13 @@ func TestHealthAPI(t *testing.T) {
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "health status returns 200",
|
||||
Name: "HEAD health status",
|
||||
Method: http.MethodHead,
|
||||
Url: "/api/health",
|
||||
ExpectedStatus: 200,
|
||||
},
|
||||
{
|
||||
Name: "GET health status",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/health",
|
||||
ExpectedStatus: 200,
|
||||
|
||||
@@ -402,6 +402,8 @@ func realUserIp(r *http.Request, fallbackIp string) string {
|
||||
return fallbackIp
|
||||
}
|
||||
|
||||
// @todo consider removing as this may no longer be needed due to the custom rest.MultiBinder.
|
||||
//
|
||||
// eagerRequestInfoCache ensures that the request data is cached in the request
|
||||
// context to allow reading for example the json request body data more than once.
|
||||
func eagerRequestInfoCache(app core.App) echo.MiddlewareFunc {
|
||||
|
||||
@@ -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,
|
||||
|
||||
+33
-13
@@ -117,7 +117,7 @@ func (api *recordAuthApi) authMethods(c echo.Context) error {
|
||||
|
||||
provider, err := auth.NewProviderByName(name)
|
||||
if err != nil {
|
||||
api.app.Logger().Debug("Missing or invalid provier name", slog.String("name", name))
|
||||
api.app.Logger().Debug("Missing or invalid provider name", slog.String("name", name))
|
||||
continue // skip provider
|
||||
}
|
||||
|
||||
@@ -201,13 +201,14 @@ func (api *recordAuthApi) authWithOAuth2(c echo.Context) error {
|
||||
event.HttpContext = c
|
||||
event.Collection = collection
|
||||
event.ProviderName = form.Provider
|
||||
event.IsNewRecord = false
|
||||
|
||||
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 {
|
||||
@@ -246,6 +247,7 @@ func (api *recordAuthApi) authWithOAuth2(c echo.Context) error {
|
||||
event.Record = data.Record
|
||||
event.OAuth2User = data.OAuth2User
|
||||
event.ProviderClient = data.ProviderClient
|
||||
event.IsNewRecord = data.Record == nil
|
||||
|
||||
return api.app.OnRecordBeforeAuthWithOAuth2Request().Trigger(event, func(e *core.RecordAuthWithOAuth2Event) error {
|
||||
data.Record = e.Record
|
||||
@@ -656,29 +658,42 @@ func (api *recordAuthApi) unlinkExternalAuth(c echo.Context) error {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const oauth2SubscriptionTopic = "@oauth2"
|
||||
const (
|
||||
oauth2SubscriptionTopic string = "@oauth2"
|
||||
oauth2RedirectFailurePath string = "../_/#/auth/oauth2-redirect-failure"
|
||||
oauth2RedirectSuccessPath string = "../_/#/auth/oauth2-redirect-success"
|
||||
)
|
||||
|
||||
type oauth2EventMessage struct {
|
||||
State string `json:"state"`
|
||||
Code string `json:"code"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (api *recordAuthApi) oauth2SubscriptionRedirect(c echo.Context) error {
|
||||
state := c.QueryParam("state")
|
||||
code := c.QueryParam("code")
|
||||
|
||||
if code == "" || state == "" {
|
||||
return NewBadRequestError("Invalid OAuth2 redirect parameters.", nil)
|
||||
if state == "" {
|
||||
api.app.Logger().Debug("Missing OAuth2 state parameter")
|
||||
return c.Redirect(http.StatusTemporaryRedirect, oauth2RedirectFailurePath)
|
||||
}
|
||||
|
||||
client, err := api.app.SubscriptionsBroker().ClientById(state)
|
||||
if err != nil || client.IsDiscarded() || !client.HasSubscription(oauth2SubscriptionTopic) {
|
||||
return NewNotFoundError("Missing or invalid OAuth2 subscription client.", err)
|
||||
api.app.Logger().Debug("Missing or invalid OAuth2 subscription client", "error", err, "clientId", state)
|
||||
return c.Redirect(http.StatusTemporaryRedirect, oauth2RedirectFailurePath)
|
||||
}
|
||||
defer client.Unsubscribe(oauth2SubscriptionTopic)
|
||||
|
||||
data := map[string]string{
|
||||
"state": state,
|
||||
"code": code,
|
||||
data := oauth2EventMessage{
|
||||
State: state,
|
||||
Code: c.QueryParam("code"),
|
||||
Error: c.QueryParam("error"),
|
||||
}
|
||||
|
||||
encodedData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return NewBadRequestError("Failed to marshalize OAuth2 redirect data.", err)
|
||||
api.app.Logger().Debug("Failed to marshalize OAuth2 redirect data", "error", err)
|
||||
return c.Redirect(http.StatusTemporaryRedirect, oauth2RedirectFailurePath)
|
||||
}
|
||||
|
||||
msg := subscriptions.Message{
|
||||
@@ -688,5 +703,10 @@ func (api *recordAuthApi) oauth2SubscriptionRedirect(c echo.Context) error {
|
||||
|
||||
client.Send(msg)
|
||||
|
||||
return c.Redirect(http.StatusTemporaryRedirect, "../_/#/auth/oauth2-redirect")
|
||||
if data.Error != "" || data.Code == "" {
|
||||
api.app.Logger().Debug("Failed OAuth2 redirect due to an error or missing code parameter", "error", data.Error, "clientId", data.State)
|
||||
return c.Redirect(http.StatusTemporaryRedirect, oauth2RedirectFailurePath)
|
||||
}
|
||||
|
||||
return c.Redirect(http.StatusTemporaryRedirect, oauth2RedirectSuccessPath)
|
||||
}
|
||||
|
||||
+304
-87
@@ -644,7 +644,7 @@ func TestRecordAuthConfirmPasswordReset(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "valid token and data",
|
||||
Name: "valid token and data (unverified user)",
|
||||
Method: http.MethodPost,
|
||||
Url: "/api/collections/users/confirm-password-reset",
|
||||
Body: strings.NewReader(`{
|
||||
@@ -659,6 +659,132 @@ func TestRecordAuthConfirmPasswordReset(t *testing.T) {
|
||||
"OnRecordBeforeConfirmPasswordResetRequest": 1,
|
||||
"OnRecordAfterConfirmPasswordResetRequest": 1,
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
user, err := app.Dao().FindAuthRecordByEmail("users", "test@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||
}
|
||||
|
||||
if user.Verified() {
|
||||
t.Fatalf("Expected the user to be unverified")
|
||||
}
|
||||
},
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
user, err := app.Dao().FindAuthRecordByToken(
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiZXhwIjoyMjA4OTg1MjYxfQ.R_4FOSUHIuJQ5Crl3PpIPCXMsoHzuTaNlccpXg_3FOg",
|
||||
app.Settings().RecordPasswordResetToken.Secret,
|
||||
)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected the password reset token to be invalidated")
|
||||
}
|
||||
|
||||
user, err = app.Dao().FindAuthRecordByEmail("users", "test@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||
}
|
||||
|
||||
if !user.Verified() {
|
||||
t.Fatalf("Expected the user to be marked as verified")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "valid token and data (unverified user with different email from the one in the token)",
|
||||
Method: http.MethodPost,
|
||||
Url: "/api/collections/users/confirm-password-reset",
|
||||
Body: strings.NewReader(`{
|
||||
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiZXhwIjoyMjA4OTg1MjYxfQ.R_4FOSUHIuJQ5Crl3PpIPCXMsoHzuTaNlccpXg_3FOg",
|
||||
"password":"12345678",
|
||||
"passwordConfirm":"12345678"
|
||||
}`),
|
||||
ExpectedStatus: 204,
|
||||
ExpectedEvents: map[string]int{
|
||||
"OnModelAfterUpdate": 1,
|
||||
"OnModelBeforeUpdate": 1,
|
||||
"OnRecordBeforeConfirmPasswordResetRequest": 1,
|
||||
"OnRecordAfterConfirmPasswordResetRequest": 1,
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
user, err := app.Dao().FindAuthRecordByEmail("users", "test@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||
}
|
||||
|
||||
if user.Verified() {
|
||||
t.Fatalf("Expected the user to be unverified")
|
||||
}
|
||||
|
||||
// manually change the email to check whether the verified state will be updated
|
||||
user.SetEmail("test_update@example.com")
|
||||
if err := app.Dao().WithoutHooks().SaveRecord(user); err != nil {
|
||||
t.Fatalf("Failed to update user test email")
|
||||
}
|
||||
},
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
user, err := app.Dao().FindAuthRecordByToken(
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiZXhwIjoyMjA4OTg1MjYxfQ.R_4FOSUHIuJQ5Crl3PpIPCXMsoHzuTaNlccpXg_3FOg",
|
||||
app.Settings().RecordPasswordResetToken.Secret,
|
||||
)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected the password reset token to be invalidated")
|
||||
}
|
||||
|
||||
user, err = app.Dao().FindAuthRecordByEmail("users", "test_update@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||
}
|
||||
|
||||
if user.Verified() {
|
||||
t.Fatalf("Expected the user to remain unverified")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "valid token and data (verified user)",
|
||||
Method: http.MethodPost,
|
||||
Url: "/api/collections/users/confirm-password-reset",
|
||||
Body: strings.NewReader(`{
|
||||
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiZXhwIjoyMjA4OTg1MjYxfQ.R_4FOSUHIuJQ5Crl3PpIPCXMsoHzuTaNlccpXg_3FOg",
|
||||
"password":"12345678",
|
||||
"passwordConfirm":"12345678"
|
||||
}`),
|
||||
ExpectedStatus: 204,
|
||||
ExpectedEvents: map[string]int{
|
||||
"OnModelAfterUpdate": 1,
|
||||
"OnModelBeforeUpdate": 1,
|
||||
"OnRecordBeforeConfirmPasswordResetRequest": 1,
|
||||
"OnRecordAfterConfirmPasswordResetRequest": 1,
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
user, err := app.Dao().FindAuthRecordByEmail("users", "test@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||
}
|
||||
|
||||
// ensure that the user is already verified
|
||||
user.SetVerified(true)
|
||||
if err := app.Dao().WithoutHooks().SaveRecord(user); err != nil {
|
||||
t.Fatalf("Failed to update user verified state")
|
||||
}
|
||||
},
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
user, err := app.Dao().FindAuthRecordByToken(
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiZXhwIjoyMjA4OTg1MjYxfQ.R_4FOSUHIuJQ5Crl3PpIPCXMsoHzuTaNlccpXg_3FOg",
|
||||
app.Settings().RecordPasswordResetToken.Secret,
|
||||
)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected the password reset token to be invalidated")
|
||||
}
|
||||
|
||||
user, err = app.Dao().FindAuthRecordByEmail("users", "test@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||
}
|
||||
|
||||
if !user.Verified() {
|
||||
t.Fatalf("Expected the user to remain verified")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "OnRecordAfterConfirmPasswordResetRequest error response",
|
||||
@@ -1377,114 +1503,205 @@ func TestRecordAuthUnlinkExternalsAuth(t *testing.T) {
|
||||
func TestRecordAuthOAuth2Redirect(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c1 := subscriptions.NewDefaultClient()
|
||||
clientStubs := make([]map[string]subscriptions.Client, 0, 10)
|
||||
|
||||
c2 := subscriptions.NewDefaultClient()
|
||||
c2.Subscribe("@oauth2")
|
||||
for i := 0; i < 10; i++ {
|
||||
c1 := subscriptions.NewDefaultClient()
|
||||
|
||||
c3 := subscriptions.NewDefaultClient()
|
||||
c3.Subscribe("test1", "@oauth2")
|
||||
c2 := subscriptions.NewDefaultClient()
|
||||
c2.Subscribe("@oauth2")
|
||||
|
||||
c4 := subscriptions.NewDefaultClient()
|
||||
c4.Subscribe("test1", "test2")
|
||||
c3 := subscriptions.NewDefaultClient()
|
||||
c3.Subscribe("test1", "@oauth2")
|
||||
|
||||
c5 := subscriptions.NewDefaultClient()
|
||||
c5.Subscribe("@oauth2")
|
||||
c5.Discard()
|
||||
c4 := subscriptions.NewDefaultClient()
|
||||
c4.Subscribe("test1", "test2")
|
||||
|
||||
beforeTestFunc := func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
app.SubscriptionsBroker().Register(c1)
|
||||
app.SubscriptionsBroker().Register(c2)
|
||||
app.SubscriptionsBroker().Register(c3)
|
||||
app.SubscriptionsBroker().Register(c4)
|
||||
app.SubscriptionsBroker().Register(c5)
|
||||
c5 := subscriptions.NewDefaultClient()
|
||||
c5.Subscribe("@oauth2")
|
||||
c5.Discard()
|
||||
|
||||
clientStubs = append(clientStubs, map[string]subscriptions.Client{
|
||||
"c1": c1,
|
||||
"c2": c2,
|
||||
"c3": c3,
|
||||
"c4": c4,
|
||||
"c5": c5,
|
||||
})
|
||||
}
|
||||
|
||||
checkFailureRedirect := func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
loc := res.Header.Get("Location")
|
||||
if !strings.Contains(loc, "/oauth2-redirect-failure") {
|
||||
t.Fatalf("Expected failure redirect, got %q", loc)
|
||||
}
|
||||
}
|
||||
|
||||
checkSuccessRedirect := func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
loc := res.Header.Get("Location")
|
||||
if !strings.Contains(loc, "/oauth2-redirect-success") {
|
||||
t.Fatalf("Expected success redirect, got %q", loc)
|
||||
}
|
||||
}
|
||||
|
||||
checkClientMessages := func(t *testing.T, clientId string, msg subscriptions.Message, expectedMessages map[string][]string) {
|
||||
if len(expectedMessages[clientId]) == 0 {
|
||||
t.Fatalf("Unexpected client %q message, got %s:\n%s", clientId, msg.Name, msg.Data)
|
||||
}
|
||||
|
||||
if msg.Name != "@oauth2" {
|
||||
t.Fatalf("Expected @oauth2 msg.Name, got %q", msg.Name)
|
||||
}
|
||||
|
||||
for _, txt := range expectedMessages[clientId] {
|
||||
if !strings.Contains(string(msg.Data), txt) {
|
||||
t.Fatalf("Failed to find %q in \n%s", txt, msg.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beforeTestFunc := func(
|
||||
clients map[string]subscriptions.Client,
|
||||
expectedMessages map[string][]string,
|
||||
) func(*testing.T, *tests.TestApp, *echo.Echo) {
|
||||
return func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
for _, client := range clients {
|
||||
app.SubscriptionsBroker().Register(client)
|
||||
}
|
||||
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
|
||||
// add to the app store so that it can be cancelled manually after test completion
|
||||
app.Store().Set("cancelFunc", cancelFunc)
|
||||
|
||||
go func() {
|
||||
defer cancelFunc()
|
||||
|
||||
for {
|
||||
select {
|
||||
case msg := <-clients["c1"].Channel():
|
||||
checkClientMessages(t, "c1", msg, expectedMessages)
|
||||
case msg := <-clients["c2"].Channel():
|
||||
checkClientMessages(t, "c2", msg, expectedMessages)
|
||||
case msg := <-clients["c3"].Channel():
|
||||
checkClientMessages(t, "c3", msg, expectedMessages)
|
||||
case msg := <-clients["c4"].Channel():
|
||||
checkClientMessages(t, "c4", msg, expectedMessages)
|
||||
case msg := <-clients["c5"].Channel():
|
||||
checkClientMessages(t, "c5", msg, expectedMessages)
|
||||
case <-ctx.Done():
|
||||
for _, c := range clients {
|
||||
close(c.Channel())
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "no state query param",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123",
|
||||
ExpectedStatus: 400,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
Name: "no state query param",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123",
|
||||
BeforeTestFunc: beforeTestFunc(clientStubs[0], nil),
|
||||
ExpectedStatus: http.StatusTemporaryRedirect,
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
app.Store().Get("cancelFunc").(context.CancelFunc)()
|
||||
|
||||
checkFailureRedirect(t, app, res)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "no code query param",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?state=" + c3.Id(),
|
||||
ExpectedStatus: 400,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
Name: "invalid or missing client",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123&state=missing",
|
||||
BeforeTestFunc: beforeTestFunc(clientStubs[1], nil),
|
||||
ExpectedStatus: http.StatusTemporaryRedirect,
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
app.Store().Get("cancelFunc").(context.CancelFunc)()
|
||||
|
||||
checkFailureRedirect(t, app, res)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "missing client",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123&state=missing",
|
||||
ExpectedStatus: 404,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
Name: "no code query param",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?state=" + clientStubs[2]["c3"].Id(),
|
||||
BeforeTestFunc: beforeTestFunc(clientStubs[2], map[string][]string{
|
||||
"c3": {`"state":"` + clientStubs[2]["c3"].Id(), `"code":""`},
|
||||
}),
|
||||
ExpectedStatus: http.StatusTemporaryRedirect,
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
app.Store().Get("cancelFunc").(context.CancelFunc)()
|
||||
|
||||
checkFailureRedirect(t, app, res)
|
||||
|
||||
if clientStubs[2]["c3"].HasSubscription("@oauth2") {
|
||||
t.Fatalf("Expected oauth2 subscription to be removed")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "discarded client with @oauth2 subscription",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123&state=" + c5.Id(),
|
||||
BeforeTestFunc: beforeTestFunc,
|
||||
ExpectedStatus: 404,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
Name: "error query param",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?error=example&code=123&state=" + clientStubs[3]["c3"].Id(),
|
||||
BeforeTestFunc: beforeTestFunc(clientStubs[3], map[string][]string{
|
||||
"c3": {`"state":"` + clientStubs[3]["c3"].Id(), `"code":"123"`, `"error":"example"`},
|
||||
}),
|
||||
ExpectedStatus: http.StatusTemporaryRedirect,
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
app.Store().Get("cancelFunc").(context.CancelFunc)()
|
||||
|
||||
checkFailureRedirect(t, app, res)
|
||||
|
||||
if clientStubs[3]["c3"].HasSubscription("@oauth2") {
|
||||
t.Fatalf("Expected oauth2 subscription to be removed")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "client without @oauth2 subscription",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123&state=" + c4.Id(),
|
||||
BeforeTestFunc: beforeTestFunc,
|
||||
ExpectedStatus: 404,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
Name: "discarded client with @oauth2 subscription",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123&state=" + clientStubs[4]["c5"].Id(),
|
||||
BeforeTestFunc: beforeTestFunc(clientStubs[4], nil),
|
||||
ExpectedStatus: http.StatusTemporaryRedirect,
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
app.Store().Get("cancelFunc").(context.CancelFunc)()
|
||||
|
||||
checkFailureRedirect(t, app, res)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "client without @oauth2 subscription",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123&state=" + clientStubs[4]["c4"].Id(),
|
||||
BeforeTestFunc: beforeTestFunc(clientStubs[5], nil),
|
||||
ExpectedStatus: http.StatusTemporaryRedirect,
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
app.Store().Get("cancelFunc").(context.CancelFunc)()
|
||||
|
||||
checkFailureRedirect(t, app, res)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "client with @oauth2 subscription",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/oauth2-redirect?code=123&state=" + c3.Id(),
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
beforeTestFunc(t, app, e)
|
||||
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
|
||||
go func() {
|
||||
defer cancelFunc()
|
||||
L:
|
||||
for {
|
||||
select {
|
||||
case <-c1.Channel():
|
||||
t.Error("Unexpected c1 message")
|
||||
break L
|
||||
case <-c2.Channel():
|
||||
t.Error("Unexpected c2 message")
|
||||
break L
|
||||
case msg := <-c3.Channel():
|
||||
if msg.Name != "@oauth2" {
|
||||
t.Errorf("Expected @oauth2 msg.Name, got %q", msg.Name)
|
||||
}
|
||||
|
||||
expectedParams := []string{`"state"`, `"code"`}
|
||||
for _, p := range expectedParams {
|
||||
if !strings.Contains(string(msg.Data), p) {
|
||||
t.Errorf("Couldn't find %s in \n%v", p, msg.Data)
|
||||
}
|
||||
}
|
||||
|
||||
break L
|
||||
case <-c4.Channel():
|
||||
t.Error("Unexpected c4 message")
|
||||
break L
|
||||
case <-c5.Channel():
|
||||
t.Error("Unexpected c5 message")
|
||||
break L
|
||||
case <-ctx.Done():
|
||||
t.Error("Context timeout reached")
|
||||
break L
|
||||
}
|
||||
}
|
||||
}()
|
||||
},
|
||||
Url: "/api/oauth2-redirect?code=123&state=" + clientStubs[6]["c3"].Id(),
|
||||
BeforeTestFunc: beforeTestFunc(clientStubs[6], map[string][]string{
|
||||
"c3": {`"state":"` + clientStubs[6]["c3"].Id(), `"code":"123"`},
|
||||
}),
|
||||
ExpectedStatus: http.StatusTemporaryRedirect,
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||
app.Store().Get("cancelFunc").(context.CancelFunc)()
|
||||
|
||||
checkSuccessRedirect(t, app, res)
|
||||
|
||||
if clientStubs[6]["c3"].HasSubscription("@oauth2") {
|
||||
t.Fatalf("Expected oauth2 subscription to be removed")
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tests"
|
||||
"github.com/pocketbase/pocketbase/tools/rest"
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
func TestRecordCrudList(t *testing.T) {
|
||||
@@ -1030,6 +1032,20 @@ func TestRecordCrudCreate(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
formData2, mp2, err2 := tests.MockMultipartData(map[string]string{
|
||||
rest.MultipartJsonKey: `{"title": "title_test2", "testPayload": 123}`,
|
||||
}, "files")
|
||||
if err2 != nil {
|
||||
t.Fatal(err2)
|
||||
}
|
||||
|
||||
formData3, mp3, err3 := tests.MockMultipartData(map[string]string{
|
||||
rest.MultipartJsonKey: `{"title": "title_test3", "testPayload": 123}`,
|
||||
}, "files")
|
||||
if err3 != nil {
|
||||
t.Fatal(err3)
|
||||
}
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "missing collection",
|
||||
@@ -1237,6 +1253,58 @@ func TestRecordCrudCreate(t *testing.T) {
|
||||
"OnModelAfterCreate": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "submit via multipart form data with @jsonPayload key and unsatisfied @request.data rule",
|
||||
Method: http.MethodPost,
|
||||
Url: "/api/collections/demo3/records",
|
||||
Body: formData2,
|
||||
RequestHeaders: map[string]string{
|
||||
"Content-Type": mp2.FormDataContentType(),
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
collection, err := app.Dao().FindCollectionByNameOrId("demo3")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find demo3 collection: %v", err)
|
||||
}
|
||||
collection.CreateRule = types.Pointer("@request.data.testPayload != 123")
|
||||
if err := app.Dao().WithoutHooks().SaveCollection(collection); err != nil {
|
||||
t.Fatalf("failed to update demo3 collection create rule: %v", err)
|
||||
}
|
||||
},
|
||||
ExpectedStatus: 400,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
},
|
||||
{
|
||||
Name: "submit via multipart form data with @jsonPayload key and satisfied @request.data rule",
|
||||
Method: http.MethodPost,
|
||||
Url: "/api/collections/demo3/records",
|
||||
Body: formData3,
|
||||
RequestHeaders: map[string]string{
|
||||
"Content-Type": mp3.FormDataContentType(),
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
collection, err := app.Dao().FindCollectionByNameOrId("demo3")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find demo3 collection: %v", err)
|
||||
}
|
||||
collection.CreateRule = types.Pointer("@request.data.testPayload = 123")
|
||||
if err := app.Dao().WithoutHooks().SaveCollection(collection); err != nil {
|
||||
t.Fatalf("failed to update demo3 collection create rule: %v", err)
|
||||
}
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContent: []string{
|
||||
`"id":"`,
|
||||
`"title":"title_test3"`,
|
||||
`"files":["`,
|
||||
},
|
||||
ExpectedEvents: map[string]int{
|
||||
"OnRecordBeforeCreateRequest": 1,
|
||||
"OnRecordAfterCreateRequest": 1,
|
||||
"OnModelBeforeCreate": 1,
|
||||
"OnModelAfterCreate": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "unique field error check",
|
||||
Method: http.MethodPost,
|
||||
@@ -1608,6 +1676,20 @@ func TestRecordCrudUpdate(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
formData2, mp2, err2 := tests.MockMultipartData(map[string]string{
|
||||
rest.MultipartJsonKey: `{"title": "title_test2", "testPayload": 123}`,
|
||||
}, "files")
|
||||
if err2 != nil {
|
||||
t.Fatal(err2)
|
||||
}
|
||||
|
||||
formData3, mp3, err3 := tests.MockMultipartData(map[string]string{
|
||||
rest.MultipartJsonKey: `{"title": "title_test3", "testPayload": 123}`,
|
||||
}, "files")
|
||||
if err3 != nil {
|
||||
t.Fatal(err3)
|
||||
}
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "missing collection",
|
||||
@@ -1830,6 +1912,58 @@ func TestRecordCrudUpdate(t *testing.T) {
|
||||
"OnModelAfterUpdate": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "submit via multipart form data with @jsonPayload key and unsatisfied @request.data rule",
|
||||
Method: http.MethodPatch,
|
||||
Url: "/api/collections/demo3/records/mk5fmymtx4wsprk",
|
||||
Body: formData2,
|
||||
RequestHeaders: map[string]string{
|
||||
"Content-Type": mp2.FormDataContentType(),
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
collection, err := app.Dao().FindCollectionByNameOrId("demo3")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find demo3 collection: %v", err)
|
||||
}
|
||||
collection.UpdateRule = types.Pointer("@request.data.testPayload != 123")
|
||||
if err := app.Dao().WithoutHooks().SaveCollection(collection); err != nil {
|
||||
t.Fatalf("failed to update demo3 collection update rule: %v", err)
|
||||
}
|
||||
},
|
||||
ExpectedStatus: 404,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
},
|
||||
{
|
||||
Name: "submit via multipart form data with @jsonPayload key and satisfied @request.data rule",
|
||||
Method: http.MethodPatch,
|
||||
Url: "/api/collections/demo3/records/mk5fmymtx4wsprk",
|
||||
Body: formData3,
|
||||
RequestHeaders: map[string]string{
|
||||
"Content-Type": mp3.FormDataContentType(),
|
||||
},
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
collection, err := app.Dao().FindCollectionByNameOrId("demo3")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find demo3 collection: %v", err)
|
||||
}
|
||||
collection.UpdateRule = types.Pointer("@request.data.testPayload = 123")
|
||||
if err := app.Dao().WithoutHooks().SaveCollection(collection); err != nil {
|
||||
t.Fatalf("failed to update demo3 collection update rule: %v", err)
|
||||
}
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContent: []string{
|
||||
`"id":"mk5fmymtx4wsprk"`,
|
||||
`"title":"title_test3"`,
|
||||
`"files":["`,
|
||||
},
|
||||
ExpectedEvents: map[string]int{
|
||||
"OnRecordBeforeUpdateRequest": 1,
|
||||
"OnRecordAfterUpdateRequest": 1,
|
||||
"OnModelBeforeUpdate": 1,
|
||||
"OnModelAfterUpdate": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "OnRecordAfterUpdateRequest error response",
|
||||
Method: http.MethodPatch,
|
||||
|
||||
@@ -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{},
|
||||
@@ -140,7 +141,7 @@ func RecordAuthResponse(
|
||||
// EnrichRecord parses the request context and enrich the provided record:
|
||||
// - expands relations (if defaultExpands and/or ?expand query param is set)
|
||||
// - ensures that the emails of the auth record and its expanded auth relations
|
||||
// are visibe only for the current logged admin, record owner or record with manage access
|
||||
// are visible only for the current logged admin, record owner or record with manage access
|
||||
func EnrichRecord(c echo.Context, dao *daos.Dao, record *models.Record, defaultExpands ...string) error {
|
||||
return EnrichRecords(c, dao, []*models.Record{record}, defaultExpands...)
|
||||
}
|
||||
@@ -148,7 +149,7 @@ func EnrichRecord(c echo.Context, dao *daos.Dao, record *models.Record, defaultE
|
||||
// EnrichRecords parses the request context and enriches the provided records:
|
||||
// - expands relations (if defaultExpands and/or ?expand query param is set)
|
||||
// - ensures that the emails of the auth records and their expanded auth relations
|
||||
// are visibe only for the current logged admin, record owner or record with manage access
|
||||
// are visible only for the current logged admin, record owner or record with manage access
|
||||
func EnrichRecords(c echo.Context, dao *daos.Dao, records []*models.Record, defaultExpands ...string) error {
|
||||
requestInfo := RequestInfo(c)
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ func TestRecordAuthResponse(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
unverfiedAuthRecord, err := app.Dao().FindRecordById("clients", "o1y0dd0spd786md")
|
||||
unverifiedAuthRecord, err := app.Dao().FindRecordById("clients", "o1y0dd0spd786md")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -108,7 +108,7 @@ func TestRecordAuthResponse(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "valid auth record but with unverified email in onlyVerified collection",
|
||||
record: unverfiedAuthRecord,
|
||||
record: unverifiedAuthRecord,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -84,6 +84,8 @@ func TestSettingsList(t *testing.T) {
|
||||
`"yandexAuth":{`,
|
||||
`"patreonAuth":{`,
|
||||
`"mailcowAuth":{`,
|
||||
`"bitbucketAuth":{`,
|
||||
`"planningcenterAuth":{`,
|
||||
`"secret":"******"`,
|
||||
`"clientSecret":"******"`,
|
||||
},
|
||||
@@ -169,6 +171,8 @@ func TestSettingsSet(t *testing.T) {
|
||||
`"yandexAuth":{`,
|
||||
`"patreonAuth":{`,
|
||||
`"mailcowAuth":{`,
|
||||
`"bitbucketAuth":{`,
|
||||
`"planningcenterAuth":{`,
|
||||
`"secret":"******"`,
|
||||
`"clientSecret":"******"`,
|
||||
`"appName":"acme_test"`,
|
||||
@@ -241,6 +245,8 @@ func TestSettingsSet(t *testing.T) {
|
||||
`"yandexAuth":{`,
|
||||
`"patreonAuth":{`,
|
||||
`"mailcowAuth":{`,
|
||||
`"bitbucketAuth":{`,
|
||||
`"planningcenterAuth":{`,
|
||||
`"secret":"******"`,
|
||||
`"clientSecret":"******"`,
|
||||
`"appName":"update_test"`,
|
||||
|
||||
+24
-18
@@ -28,12 +28,10 @@ func NewAdminCommand(app core.App) *cobra.Command {
|
||||
|
||||
func adminCreateCommand(app core.App) *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: "create",
|
||||
Example: "admin create test@example.com 1234567890",
|
||||
Short: "Creates a new admin account",
|
||||
// prevents printing the error log twice
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
Use: "create",
|
||||
Example: "admin create test@example.com 1234567890",
|
||||
Short: "Creates a new admin account",
|
||||
SilenceUsage: true,
|
||||
RunE: func(command *cobra.Command, args []string) error {
|
||||
if len(args) != 2 {
|
||||
return errors.New("Missing email and password arguments.")
|
||||
@@ -51,6 +49,10 @@ func adminCreateCommand(app core.App) *cobra.Command {
|
||||
admin.Email = args[0]
|
||||
admin.SetPassword(args[1])
|
||||
|
||||
if !app.Dao().HasTable(admin.TableName()) {
|
||||
return errors.New("Migration are not initialized yet. Please run 'migrate up' and try again.")
|
||||
}
|
||||
|
||||
if err := app.Dao().SaveAdmin(admin); err != nil {
|
||||
return fmt.Errorf("Failed to create new admin account: %v", err)
|
||||
}
|
||||
@@ -65,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.")
|
||||
@@ -84,6 +84,10 @@ func adminUpdateCommand(app core.App) *cobra.Command {
|
||||
return errors.New("The new password must be at least 8 chars long.")
|
||||
}
|
||||
|
||||
if !app.Dao().HasTable((&models.Admin{}).TableName()) {
|
||||
return errors.New("Migration are not initialized yet. Please run 'migrate up' and try again.")
|
||||
}
|
||||
|
||||
admin, err := app.Dao().FindAdminByEmail(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Admin with email %s doesn't exist.", args[0])
|
||||
@@ -105,17 +109,19 @@ 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.")
|
||||
}
|
||||
|
||||
if !app.Dao().HasTable((&models.Admin{}).TableName()) {
|
||||
return errors.New("Migration are not initialized yet. Please run 'migrate up' and try again.")
|
||||
}
|
||||
|
||||
admin, err := app.Dao().FindAdminByEmail(args[0])
|
||||
if err != nil {
|
||||
color.Yellow("Admin %s is already deleted.", args[0])
|
||||
|
||||
+10
-7
@@ -1,7 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"log"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
@@ -17,10 +17,11 @@ func NewServeCommand(app core.App, showStartBanner bool) *cobra.Command {
|
||||
var httpsAddr string
|
||||
|
||||
command := &cobra.Command{
|
||||
Use: "serve [domain(s)]",
|
||||
Args: cobra.ArbitraryArgs,
|
||||
Short: "Starts the web server (default to 127.0.0.1:8090 if no domain is specified)",
|
||||
Run: func(command *cobra.Command, args []string) {
|
||||
Use: "serve [domain(s)]",
|
||||
Args: cobra.ArbitraryArgs,
|
||||
Short: "Starts the web server (default to 127.0.0.1:8090 if no domain is specified)",
|
||||
SilenceUsage: true,
|
||||
RunE: func(command *cobra.Command, args []string) error {
|
||||
// set default listener addresses if at least one domain is specified
|
||||
if len(args) > 0 {
|
||||
if httpAddr == "" {
|
||||
@@ -43,9 +44,11 @@ func NewServeCommand(app core.App, showStartBanner bool) *cobra.Command {
|
||||
CertificateDomains: args,
|
||||
})
|
||||
|
||||
if err != http.ErrServerClosed {
|
||||
log.Fatalln(err)
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+29
-15
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
@@ -1270,7 +1279,7 @@ func (app *BaseApp) initLogger() error {
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
handler.WriteAll(ctx)
|
||||
return
|
||||
case <-ticker.C:
|
||||
handler.WriteAll(ctx)
|
||||
}
|
||||
@@ -1280,8 +1289,13 @@ func (app *BaseApp) initLogger() error {
|
||||
app.logger = slog.New(handler)
|
||||
|
||||
app.OnTerminate().PreAdd(func(e *TerminateEvent) error {
|
||||
// write all remaining logs before ticker.Stop to avoid races with ResetBootstrap user calls
|
||||
handler.WriteAll(context.Background())
|
||||
|
||||
ticker.Stop()
|
||||
|
||||
done <- true
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -59,7 +59,7 @@ func (dao *Dao) FindAdminByToken(token string, baseTokenKey string) (*models.Adm
|
||||
// check required claims
|
||||
id, _ := unverifiedClaims["id"].(string)
|
||||
if id == "" {
|
||||
return nil, errors.New("Missing or invalid token claims.")
|
||||
return nil, errors.New("missing or invalid token claims")
|
||||
}
|
||||
|
||||
admin, err := dao.FindAdminById(id)
|
||||
@@ -116,7 +116,7 @@ func (dao *Dao) DeleteAdmin(admin *models.Admin) error {
|
||||
}
|
||||
|
||||
if total == 1 {
|
||||
return errors.New("You cannot delete the only existing admin.")
|
||||
return errors.New("you cannot delete the only existing admin")
|
||||
}
|
||||
|
||||
return dao.Delete(admin)
|
||||
|
||||
+14
-14
@@ -18,7 +18,7 @@ func New(db dbx.Builder) *Dao {
|
||||
return NewMultiDB(db, db)
|
||||
}
|
||||
|
||||
// New creates a new Dao instance with the provided dedicated
|
||||
// NewMultiDB creates a new Dao instance with the provided dedicated
|
||||
// async and sync db builders.
|
||||
func NewMultiDB(concurrentDB, nonconcurrentDB dbx.Builder) *Dao {
|
||||
return &Dao{
|
||||
@@ -87,16 +87,16 @@ func (dao *Dao) Clone() *Dao {
|
||||
// WithoutHooks returns a new Dao with the same configuration options
|
||||
// as the current one, but without create/update/delete hooks.
|
||||
func (dao *Dao) WithoutHooks() *Dao {
|
||||
new := dao.Clone()
|
||||
clone := dao.Clone()
|
||||
|
||||
new.BeforeCreateFunc = nil
|
||||
new.AfterCreateFunc = nil
|
||||
new.BeforeUpdateFunc = nil
|
||||
new.AfterUpdateFunc = nil
|
||||
new.BeforeDeleteFunc = nil
|
||||
new.AfterDeleteFunc = nil
|
||||
clone.BeforeCreateFunc = nil
|
||||
clone.AfterCreateFunc = nil
|
||||
clone.BeforeUpdateFunc = nil
|
||||
clone.AfterUpdateFunc = nil
|
||||
clone.BeforeDeleteFunc = nil
|
||||
clone.AfterDeleteFunc = nil
|
||||
|
||||
return new
|
||||
return clone
|
||||
}
|
||||
|
||||
// ModelQuery creates a new preconfigured select query with preset
|
||||
@@ -119,9 +119,9 @@ func (dao *Dao) FindById(m models.Model, id string) error {
|
||||
}
|
||||
|
||||
type afterCallGroup struct {
|
||||
Action string
|
||||
EventDao *Dao
|
||||
Model models.Model
|
||||
EventDao *Dao
|
||||
Action string
|
||||
}
|
||||
|
||||
// RunInTransaction wraps fn into a transaction.
|
||||
@@ -169,19 +169,19 @@ func (dao *Dao) RunInTransaction(fn func(txDao *Dao) error) error {
|
||||
|
||||
if dao.AfterCreateFunc != nil {
|
||||
txDao.AfterCreateFunc = func(eventDao *Dao, m models.Model) error {
|
||||
afterCalls = append(afterCalls, afterCallGroup{"create", eventDao, m})
|
||||
afterCalls = append(afterCalls, afterCallGroup{m, eventDao, "create"})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if dao.AfterUpdateFunc != nil {
|
||||
txDao.AfterUpdateFunc = func(eventDao *Dao, m models.Model) error {
|
||||
afterCalls = append(afterCalls, afterCallGroup{"update", eventDao, m})
|
||||
afterCalls = append(afterCalls, afterCallGroup{m, eventDao, "update"})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if dao.AfterDeleteFunc != nil {
|
||||
txDao.AfterDeleteFunc = func(eventDao *Dao, m models.Model) error {
|
||||
afterCalls = append(afterCalls, afterCallGroup{"delete", eventDao, m})
|
||||
afterCalls = append(afterCalls, afterCallGroup{m, eventDao, "delete"})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -121,7 +121,7 @@ func (dao *Dao) FindCollectionReferences(collection *models.Collection, excludeI
|
||||
// - is referenced as part of a relation field in another collection
|
||||
func (dao *Dao) DeleteCollection(collection *models.Collection) error {
|
||||
if collection.System {
|
||||
return fmt.Errorf("System collection %q cannot be deleted.", collection.Name)
|
||||
return fmt.Errorf("system collection %q cannot be deleted", collection.Name)
|
||||
}
|
||||
|
||||
// ensure that there aren't any existing references.
|
||||
@@ -135,7 +135,7 @@ func (dao *Dao) DeleteCollection(collection *models.Collection) error {
|
||||
for ref := range result {
|
||||
names = append(names, ref.Name)
|
||||
}
|
||||
return fmt.Errorf("The collection %q has external relation field references (%s).", collection.Name, strings.Join(names, ", "))
|
||||
return fmt.Errorf("the collection %q has external relation field references (%s)", collection.Name, strings.Join(names, ", "))
|
||||
}
|
||||
|
||||
return dao.RunInTransaction(func(txDao *Dao) error {
|
||||
@@ -152,7 +152,7 @@ func (dao *Dao) DeleteCollection(collection *models.Collection) error {
|
||||
|
||||
// trigger views resave to check for dependencies
|
||||
if err := txDao.resaveViewsWithChangedSchema(collection.Id); err != nil {
|
||||
return fmt.Errorf("The collection has a view dependency - %w", err)
|
||||
return fmt.Errorf("the collection has a view dependency - %w", err)
|
||||
}
|
||||
|
||||
return txDao.Delete(collection)
|
||||
@@ -162,8 +162,8 @@ func (dao *Dao) DeleteCollection(collection *models.Collection) error {
|
||||
// SaveCollection persists the provided Collection model and updates
|
||||
// its related records table schema.
|
||||
//
|
||||
// If collecction.IsNew() is true, the method will perform a create, otherwise an update.
|
||||
// To explicitly mark a collection for update you can use collecction.MarkAsNotNew().
|
||||
// If collection.IsNew() is true, the method will perform a create, otherwise an update.
|
||||
// To explicitly mark a collection for update you can use collection.MarkAsNotNew().
|
||||
func (dao *Dao) SaveCollection(collection *models.Collection) error {
|
||||
var oldCollection *models.Collection
|
||||
|
||||
@@ -227,7 +227,7 @@ func (dao *Dao) ImportCollections(
|
||||
afterSync func(txDao *Dao, mappedImported, mappedExisting map[string]*models.Collection) error,
|
||||
) error {
|
||||
if len(importedCollections) == 0 {
|
||||
return errors.New("No collections to import")
|
||||
return errors.New("no collections to import")
|
||||
}
|
||||
|
||||
return dao.RunInTransaction(func(txDao *Dao) error {
|
||||
@@ -263,11 +263,11 @@ func (dao *Dao) ImportCollections(
|
||||
|
||||
// extend existing schema
|
||||
if !deleteMissing {
|
||||
schema, _ := existing.Schema.Clone()
|
||||
schemaClone, _ := existing.Schema.Clone()
|
||||
for _, f := range imported.Schema.Fields() {
|
||||
schema.AddField(f) // add or replace
|
||||
schemaClone.AddField(f) // add or replace
|
||||
}
|
||||
imported.Schema = *schema
|
||||
imported.Schema = *schemaClone
|
||||
}
|
||||
} else {
|
||||
imported.MarkAsNew()
|
||||
@@ -285,7 +285,7 @@ func (dao *Dao) ImportCollections(
|
||||
}
|
||||
|
||||
if existing.System {
|
||||
return fmt.Errorf("System collection %q cannot be deleted.", existing.Name)
|
||||
return fmt.Errorf("system collection %q cannot be deleted", existing.Name)
|
||||
}
|
||||
|
||||
// delete the related records table or view
|
||||
|
||||
+30
-27
@@ -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 {
|
||||
@@ -756,7 +758,7 @@ func TestImportCollections(t *testing.T) {
|
||||
"demo1": 15,
|
||||
"demo2": 2,
|
||||
"demo3": 2,
|
||||
"demo4": 11,
|
||||
"demo4": 13,
|
||||
"demo5": 6,
|
||||
"new_import": 1,
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+13
-15
@@ -40,16 +40,16 @@ func (dao *Dao) RecordQuery(collectionModelOrIdentifier any) *dbx.SelectQuery {
|
||||
collection, collectionErr = dao.FindCollectionByNameOrId(c)
|
||||
if collection != nil {
|
||||
tableName = collection.Name
|
||||
} else {
|
||||
// update with some fake table name for easier debugging
|
||||
tableName = "@@__missing_" + c
|
||||
}
|
||||
default:
|
||||
// update with some fake table name for easier debugging
|
||||
tableName = "@@__invalidCollectionModelOrIdentifier"
|
||||
collectionErr = errors.New("unsupported collection identifier, must be collection model, id or name")
|
||||
}
|
||||
|
||||
// update with some fake table name for easier debugging
|
||||
if tableName == "" {
|
||||
tableName = "@@__invalidCollectionModelOrIdentifier"
|
||||
}
|
||||
|
||||
selectCols := fmt.Sprintf("%s.*", dao.DB().QuoteSimpleColumnName(tableName))
|
||||
|
||||
query := dao.DB().Select(selectCols).From(tableName)
|
||||
@@ -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.
|
||||
@@ -432,7 +430,7 @@ func (dao *Dao) FindAuthRecordByToken(token string, baseTokenKey string) (*model
|
||||
}
|
||||
|
||||
if !record.Collection().IsAuth() {
|
||||
return nil, errors.New("The token is not associated to an auth collection record.")
|
||||
return nil, errors.New("the token is not associated to an auth collection record")
|
||||
}
|
||||
|
||||
verificationKey := record.TokenKey() + baseTokenKey
|
||||
@@ -659,8 +657,6 @@ func (dao *Dao) DeleteRecord(record *models.Record) error {
|
||||
//
|
||||
// NB! This method is expected to be called inside a transaction.
|
||||
func (dao *Dao) cascadeRecordDelete(mainRecord *models.Record, refs map[*models.Collection][]*schema.SchemaField) error {
|
||||
uniqueJsonEachAlias := "__je__" + security.PseudorandomString(4)
|
||||
|
||||
// @todo consider changing refs to a slice
|
||||
//
|
||||
// Sort the refs keys to ensure that the cascade events firing order is always the same.
|
||||
@@ -684,15 +680,17 @@ func (dao *Dao) cascadeRecordDelete(mainRecord *models.Record, refs map[*models.
|
||||
recordTableName := inflector.Columnify(refCollection.Name)
|
||||
prefixedFieldName := recordTableName + "." + inflector.Columnify(field.Name)
|
||||
|
||||
query := dao.RecordQuery(refCollection).Distinct(true)
|
||||
query := dao.RecordQuery(refCollection)
|
||||
|
||||
if opt, ok := field.Options.(schema.MultiValuer); !ok || !opt.IsMultiple() {
|
||||
query.AndWhere(dbx.HashExp{prefixedFieldName: mainRecord.Id})
|
||||
} else {
|
||||
query.InnerJoin(fmt.Sprintf(
|
||||
`json_each(CASE WHEN json_valid([[%s]]) THEN [[%s]] ELSE json_array([[%s]]) END) as {{%s}}`,
|
||||
prefixedFieldName, prefixedFieldName, prefixedFieldName, uniqueJsonEachAlias,
|
||||
), dbx.HashExp{uniqueJsonEachAlias + ".value": mainRecord.Id})
|
||||
query.AndWhere(dbx.Exists(dbx.NewExp(fmt.Sprintf(
|
||||
`SELECT 1 FROM json_each(CASE WHEN json_valid([[%s]]) THEN [[%s]] ELSE json_array([[%s]]) END) {{__je__}} WHERE [[__je__.value]]={:jevalue}`,
|
||||
prefixedFieldName, prefixedFieldName, prefixedFieldName,
|
||||
), dbx.Params{
|
||||
"jevalue": mainRecord.Id,
|
||||
})))
|
||||
}
|
||||
|
||||
if refCollection.Id == mainRecord.Collection().Id {
|
||||
|
||||
+61
-39
@@ -1,7 +1,9 @@
|
||||
package daos
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -9,13 +11,14 @@ import (
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/models/schema"
|
||||
"github.com/pocketbase/pocketbase/tools/dbutils"
|
||||
"github.com/pocketbase/pocketbase/tools/inflector"
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
// MaxExpandDepth specifies the max allowed nested expand depth path.
|
||||
//
|
||||
// @todo Consider eventually reusing resolvers.maxNestedRels
|
||||
const MaxExpandDepth = 6
|
||||
|
||||
// ExpandFetchFunc defines the function that is used to fetch the expanded relation records.
|
||||
@@ -51,13 +54,15 @@ func (dao *Dao) ExpandRecords(records []*models.Record, expands []string, optFet
|
||||
return failed
|
||||
}
|
||||
|
||||
var indirectExpandRegex = regexp.MustCompile(`^(\w+)\((\w+)\)$`)
|
||||
// Deprecated
|
||||
var indirectExpandRegexOld = regexp.MustCompile(`^(\w+)\((\w+)\)$`)
|
||||
|
||||
var indirectExpandRegex = regexp.MustCompile(`^(\w+)_via_(\w+)$`)
|
||||
|
||||
// notes:
|
||||
// - if fetchFunc is nil, dao.FindRecordsByIds will be used
|
||||
// - all records are expected to be from the same collection
|
||||
// - if MaxExpandDepth is reached, the function returns nil ignoring the remaining expand path
|
||||
// - indirect expands are supported only with single relation fields
|
||||
func (dao *Dao) expandRecords(records []*models.Record, expandPath string, fetchFunc ExpandFetchFunc, recursionLevel int) error {
|
||||
if fetchFunc == nil {
|
||||
// load a default fetchFunc
|
||||
@@ -77,70 +82,87 @@ func (dao *Dao) expandRecords(records []*models.Record, expandPath string, fetch
|
||||
var relCollection *models.Collection
|
||||
|
||||
parts := strings.SplitN(expandPath, ".", 2)
|
||||
matches := indirectExpandRegex.FindStringSubmatch(parts[0])
|
||||
var matches []string
|
||||
|
||||
// @todo remove the old syntax support
|
||||
if strings.Contains(parts[0], "(") {
|
||||
matches = indirectExpandRegexOld.FindStringSubmatch(parts[0])
|
||||
if len(matches) == 3 {
|
||||
log.Printf(
|
||||
"%s expand format is deprecated and will be removed in the future. Consider replacing it with %s_via_%s.\n",
|
||||
matches[0],
|
||||
matches[1],
|
||||
matches[2],
|
||||
)
|
||||
}
|
||||
} else {
|
||||
matches = indirectExpandRegex.FindStringSubmatch(parts[0])
|
||||
}
|
||||
|
||||
if len(matches) == 3 {
|
||||
indirectRel, _ := dao.FindCollectionByNameOrId(matches[1])
|
||||
if indirectRel == nil {
|
||||
return fmt.Errorf("Couldn't find indirect related collection %q.", matches[1])
|
||||
return fmt.Errorf("couldn't find back-related collection %q", matches[1])
|
||||
}
|
||||
|
||||
indirectRelField := indirectRel.Schema.GetFieldByName(matches[2])
|
||||
if indirectRelField == nil || indirectRelField.Type != schema.FieldTypeRelation {
|
||||
return fmt.Errorf("Couldn't find indirect relation field %q in collection %q.", matches[2], mainCollection.Name)
|
||||
return fmt.Errorf("couldn't find back-relation field %q in collection %q", matches[2], indirectRel.Name)
|
||||
}
|
||||
|
||||
indirectRelField.InitOptions()
|
||||
indirectRelFieldOptions, _ := indirectRelField.Options.(*schema.RelationOptions)
|
||||
if indirectRelFieldOptions == nil || indirectRelFieldOptions.CollectionId != mainCollection.Id {
|
||||
return fmt.Errorf("Invalid indirect relation field path %q.", parts[0])
|
||||
}
|
||||
if indirectRelFieldOptions.IsMultiple() {
|
||||
// for now don't allow multi-relation indirect fields expand
|
||||
// due to eventual poor query performance with large data sets.
|
||||
return fmt.Errorf("Multi-relation fields cannot be indirectly expanded in %q.", parts[0])
|
||||
return fmt.Errorf("invalid back-relation field path %q", parts[0])
|
||||
}
|
||||
|
||||
recordIds := make([]any, len(records))
|
||||
for i, record := range records {
|
||||
recordIds[i] = record.Id
|
||||
}
|
||||
// add the related id(s) as a dynamic relation field value to
|
||||
// allow further expand checks at later stage in a more unified manner
|
||||
prepErr := func() error {
|
||||
q := dao.DB().Select("id").
|
||||
From(indirectRel.Name).
|
||||
Limit(1000) // the limit is arbitrary chosen and may change in the future
|
||||
|
||||
// @todo after the index optimizations consider allowing
|
||||
// indirect expand for multi-relation fields
|
||||
indirectRecords, err := dao.FindRecordsByExpr(
|
||||
indirectRel.Id,
|
||||
dbx.In(inflector.Columnify(matches[2]), recordIds...),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mappedIndirectRecordIds := make(map[string][]string, len(indirectRecords))
|
||||
for _, indirectRecord := range indirectRecords {
|
||||
recId := indirectRecord.GetString(matches[2])
|
||||
if recId != "" {
|
||||
mappedIndirectRecordIds[recId] = append(mappedIndirectRecordIds[recId], indirectRecord.Id)
|
||||
if indirectRelFieldOptions.IsMultiple() {
|
||||
q.AndWhere(dbx.Exists(dbx.NewExp(fmt.Sprintf(
|
||||
"SELECT 1 FROM %s je WHERE je.value = {:id}",
|
||||
dbutils.JsonEach(indirectRelField.Name),
|
||||
))))
|
||||
} else {
|
||||
q.AndWhere(dbx.NewExp("[[" + indirectRelField.Name + "]] = {:id}"))
|
||||
}
|
||||
}
|
||||
|
||||
// add the indirect relation ids as a new relation field value
|
||||
for _, record := range records {
|
||||
relIds, ok := mappedIndirectRecordIds[record.Id]
|
||||
if ok && len(relIds) > 0 {
|
||||
record.Set(parts[0], relIds)
|
||||
pq := q.Build().Prepare()
|
||||
|
||||
for _, record := range records {
|
||||
var relIds []string
|
||||
|
||||
err := pq.Bind(dbx.Params{"id": record.Id}).Column(&relIds)
|
||||
if err != nil {
|
||||
return errors.Join(err, pq.Close())
|
||||
}
|
||||
|
||||
if len(relIds) > 0 {
|
||||
record.Set(parts[0], relIds)
|
||||
}
|
||||
}
|
||||
|
||||
return pq.Close()
|
||||
}()
|
||||
if prepErr != nil {
|
||||
return prepErr
|
||||
}
|
||||
|
||||
relFieldOptions = &schema.RelationOptions{
|
||||
MaxSelect: nil,
|
||||
CollectionId: indirectRel.Id,
|
||||
}
|
||||
if isRelFieldUnique(indirectRel, indirectRelField.Name) {
|
||||
if dbutils.HasSingleColumnUniqueIndex(indirectRelField.Name, indirectRel.Indexes) {
|
||||
relFieldOptions.MaxSelect = types.Pointer(1)
|
||||
}
|
||||
// indirect relation
|
||||
// indirect/back relation
|
||||
relField = &schema.SchemaField{
|
||||
Id: "indirect_" + security.PseudorandomString(5),
|
||||
Id: "_" + parts[0] + security.PseudorandomString(3),
|
||||
Type: schema.FieldTypeRelation,
|
||||
Name: parts[0],
|
||||
Options: relFieldOptions,
|
||||
|
||||
+61
-13
@@ -163,7 +163,7 @@ func TestExpandRecords(t *testing.T) {
|
||||
0,
|
||||
},
|
||||
{
|
||||
"simple indirect expand",
|
||||
"simple back single relation field expand (deprecated syntax)",
|
||||
"demo3",
|
||||
[]string{"lcl9d87w22ml6jy"},
|
||||
[]string{"demo4(rel_one_no_cascade_required)"},
|
||||
@@ -174,11 +174,22 @@ func TestExpandRecords(t *testing.T) {
|
||||
0,
|
||||
},
|
||||
{
|
||||
"nested indirect expand",
|
||||
"simple back expand via single relation field",
|
||||
"demo3",
|
||||
[]string{"lcl9d87w22ml6jy"},
|
||||
[]string{"demo4_via_rel_one_no_cascade_required"},
|
||||
func(c *models.Collection, ids []string) ([]*models.Record, error) {
|
||||
return app.Dao().FindRecordsByIds(c.Id, ids, nil)
|
||||
},
|
||||
1,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"nested back expand via single relation field",
|
||||
"demo3",
|
||||
[]string{"lcl9d87w22ml6jy"},
|
||||
[]string{
|
||||
"demo4(rel_one_no_cascade_required).self_rel_many.self_rel_many.self_rel_one",
|
||||
"demo4_via_rel_one_no_cascade_required.self_rel_many.self_rel_many.self_rel_one",
|
||||
},
|
||||
func(c *models.Collection, ids []string) ([]*models.Record, error) {
|
||||
return app.Dao().FindRecordsByIds(c.Id, ids, nil)
|
||||
@@ -186,6 +197,19 @@ func TestExpandRecords(t *testing.T) {
|
||||
5,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"nested back expand via multiple relation field",
|
||||
"demo3",
|
||||
[]string{"lcl9d87w22ml6jy"},
|
||||
[]string{
|
||||
"demo4_via_rel_many_no_cascade_required.self_rel_many.rel_many_no_cascade_required.demo4_via_rel_many_no_cascade_required",
|
||||
},
|
||||
func(c *models.Collection, ids []string) ([]*models.Record, error) {
|
||||
return app.Dao().FindRecordsByIds(c.Id, ids, nil)
|
||||
},
|
||||
7,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"expand multiple relations sharing a common path",
|
||||
"demo4",
|
||||
@@ -332,7 +356,7 @@ func TestExpandRecord(t *testing.T) {
|
||||
0,
|
||||
},
|
||||
{
|
||||
"simple indirect expand",
|
||||
"simple indirect expand via single relation field (deprecated syntax)",
|
||||
"demo3",
|
||||
"lcl9d87w22ml6jy",
|
||||
[]string{"demo4(rel_one_no_cascade_required)"},
|
||||
@@ -343,7 +367,18 @@ func TestExpandRecord(t *testing.T) {
|
||||
0,
|
||||
},
|
||||
{
|
||||
"nested indirect expand",
|
||||
"simple indirect expand via single relation field",
|
||||
"demo3",
|
||||
"lcl9d87w22ml6jy",
|
||||
[]string{"demo4_via_rel_one_no_cascade_required"},
|
||||
func(c *models.Collection, ids []string) ([]*models.Record, error) {
|
||||
return app.Dao().FindRecordsByIds(c.Id, ids, nil)
|
||||
},
|
||||
1,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"nested indirect expand via single relation field",
|
||||
"demo3",
|
||||
"lcl9d87w22ml6jy",
|
||||
[]string{
|
||||
@@ -355,6 +390,19 @@ func TestExpandRecord(t *testing.T) {
|
||||
5,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"nested indirect expand via single relation field",
|
||||
"demo3",
|
||||
"lcl9d87w22ml6jy",
|
||||
[]string{
|
||||
"demo4_via_rel_many_no_cascade_required.self_rel_many.rel_many_no_cascade_required.demo4_via_rel_many_no_cascade_required",
|
||||
},
|
||||
func(c *models.Collection, ids []string) ([]*models.Record, error) {
|
||||
return app.Dao().FindRecordsByIds(c.Id, ids, nil)
|
||||
},
|
||||
7,
|
||||
0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
@@ -388,21 +436,23 @@ func TestIndirectExpandSingeVsArrayResult(t *testing.T) {
|
||||
|
||||
// non-unique indirect expand
|
||||
{
|
||||
errs := app.Dao().ExpandRecord(record, []string{"demo4(rel_one_cascade)"}, func(c *models.Collection, ids []string) ([]*models.Record, error) {
|
||||
errs := app.Dao().ExpandRecord(record, []string{"demo4_via_rel_one_cascade"}, func(c *models.Collection, ids []string) ([]*models.Record, error) {
|
||||
return app.Dao().FindRecordsByIds(c.Id, ids, nil)
|
||||
})
|
||||
if len(errs) > 0 {
|
||||
t.Fatal(errs)
|
||||
}
|
||||
|
||||
result, ok := record.Expand()["demo4(rel_one_cascade)"].([]*models.Record)
|
||||
result, ok := record.Expand()["demo4_via_rel_one_cascade"].([]*models.Record)
|
||||
if !ok {
|
||||
t.Fatalf("Expected the expanded result to be a slice, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
// mock a unique constraint for the rel_one_cascade field
|
||||
// unique indirect expand
|
||||
{
|
||||
// mock a unique constraint for the rel_one_cascade field
|
||||
// ---
|
||||
demo4, err := app.Dao().FindCollectionByNameOrId("demo4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -413,18 +463,16 @@ func TestIndirectExpandSingeVsArrayResult(t *testing.T) {
|
||||
if err := app.Dao().SaveCollection(demo4); err != nil {
|
||||
t.Fatalf("Failed to mock unique constraint: %v", err)
|
||||
}
|
||||
}
|
||||
// ---
|
||||
|
||||
// non-unique indirect expand
|
||||
{
|
||||
errs := app.Dao().ExpandRecord(record, []string{"demo4(rel_one_cascade)"}, func(c *models.Collection, ids []string) ([]*models.Record, error) {
|
||||
errs := app.Dao().ExpandRecord(record, []string{"demo4_via_rel_one_cascade"}, func(c *models.Collection, ids []string) ([]*models.Record, error) {
|
||||
return app.Dao().FindRecordsByIds(c.Id, ids, nil)
|
||||
})
|
||||
if len(errs) > 0 {
|
||||
t.Fatal(errs)
|
||||
}
|
||||
|
||||
result, ok := record.Expand()["demo4(rel_one_cascade)"].(*models.Record)
|
||||
result, ok := record.Expand()["demo4_via_rel_one_cascade"].(*models.Record)
|
||||
if !ok {
|
||||
t.Fatalf("Expected the expanded result to be a single model, got %v", result)
|
||||
}
|
||||
|
||||
+2
-2
@@ -1205,11 +1205,11 @@ func TestDeleteRecord(t *testing.T) {
|
||||
}
|
||||
// ensure that the json rel fields were prefixed
|
||||
joinedQueries := strings.Join(calledQueries, " ")
|
||||
expectedRelManyPart := "`demo1` INNER JOIN json_each(CASE WHEN json_valid([[demo1.rel_many]]) THEN [[demo1.rel_many]] ELSE json_array([[demo1.rel_many]]) END)"
|
||||
expectedRelManyPart := "SELECT `demo1`.* FROM `demo1` WHERE EXISTS (SELECT 1 FROM json_each(CASE WHEN json_valid([[demo1.rel_many]]) THEN [[demo1.rel_many]] ELSE json_array([[demo1.rel_many]]) END) {{__je__}} WHERE [[__je__.value]]='"
|
||||
if !strings.Contains(joinedQueries, expectedRelManyPart) {
|
||||
t.Fatalf("(rec3) Expected the cascade delete to call the query \n%v, got \n%v", expectedRelManyPart, calledQueries)
|
||||
}
|
||||
expectedRelOnePart := "SELECT DISTINCT `demo1`.* FROM `demo1` WHERE (`demo1`.`rel_one`="
|
||||
expectedRelOnePart := "SELECT `demo1`.* FROM `demo1` WHERE (`demo1`.`rel_one`='"
|
||||
if !strings.Contains(joinedQueries, expectedRelOnePart) {
|
||||
t.Fatalf("(rec3) Expected the cascade delete to call the query \n%v, got \n%v", expectedRelOnePart, calledQueries)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
@@ -131,6 +132,7 @@ func (form *CollectionUpsert) Validate() error {
|
||||
validation.Match(collectionNameRegex),
|
||||
validation.By(form.ensureNoSystemNameChange),
|
||||
validation.By(form.checkUniqueName),
|
||||
validation.By(form.checkForVia),
|
||||
),
|
||||
// validates using the type's own validation rules + some collection's specifics
|
||||
validation.Field(
|
||||
@@ -163,6 +165,19 @@ func (form *CollectionUpsert) Validate() error {
|
||||
)
|
||||
}
|
||||
|
||||
func (form *CollectionUpsert) checkForVia(value any) error {
|
||||
v, _ := value.(string)
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.Contains(strings.ToLower(v), "_via_") {
|
||||
return validation.NewError("validation_invalid_name", "The name of the collection cannot contain '_via_'.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (form *CollectionUpsert) checkUniqueName(value any) error {
|
||||
v, _ := value.(string)
|
||||
|
||||
|
||||
@@ -105,6 +105,17 @@ func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
|
||||
{"empty create (auth)", "", `{"type":"auth"}`, []string{"name"}},
|
||||
{"empty create (view)", "", `{"type":"view"}`, []string{"name", "options"}},
|
||||
{"empty update", "demo2", "{}", []string{}},
|
||||
{
|
||||
"collection and field with _via_ names",
|
||||
"",
|
||||
`{
|
||||
"name": "a_via_b",
|
||||
"schema": [
|
||||
{"name":"c_via_d","type":"text"}
|
||||
]
|
||||
}`,
|
||||
[]string{"name", "schema"},
|
||||
},
|
||||
{
|
||||
"create failure",
|
||||
"",
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
"github.com/pocketbase/pocketbase/forms/validators"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// RecordPasswordResetConfirm is an auth record password reset confirmation form.
|
||||
@@ -91,9 +93,21 @@ func (form *RecordPasswordResetConfirm) Submit(interceptors ...InterceptorFunc[*
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !authRecord.Verified() {
|
||||
payload, err := security.ParseUnverifiedJWT(form.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// mark as verified if the email hasn't changed
|
||||
if authRecord.Email() == cast.ToString(payload["email"]) {
|
||||
authRecord.SetVerified(true)
|
||||
}
|
||||
}
|
||||
|
||||
interceptorsErr := runInterceptors(authRecord, func(m *models.Record) error {
|
||||
authRecord = m
|
||||
return form.dao.SaveRecord(m)
|
||||
return form.dao.SaveRecord(authRecord)
|
||||
}, interceptors...)
|
||||
|
||||
if interceptorsErr != nil {
|
||||
|
||||
@@ -46,7 +46,7 @@ func (form *RecordPasswordResetRequest) SetDao(dao *daos.Dao) {
|
||||
|
||||
// Validate makes the form validatable by implementing [validation.Validatable] interface.
|
||||
//
|
||||
// This method doesn't checks whether auth record with `form.Email` exists (this is done on Submit).
|
||||
// This method doesn't check whether auth record with `form.Email` exists (this is done on Submit).
|
||||
func (form *RecordPasswordResetRequest) Validate() error {
|
||||
return validation.ValidateStruct(form,
|
||||
validation.Field(
|
||||
|
||||
+14
-4
@@ -165,7 +165,7 @@ func (form *RecordUpsert) extractMultipartFormData(
|
||||
|
||||
data := map[string]any{}
|
||||
filesToUpload := map[string][]*filesystem.File{}
|
||||
arrayValueSupportTypes := schema.ArraybleFieldTypes()
|
||||
arraybleFieldTypes := schema.ArraybleFieldTypes()
|
||||
|
||||
for fullKey, values := range r.PostForm {
|
||||
key := fullKey
|
||||
@@ -178,8 +178,18 @@ func (form *RecordUpsert) extractMultipartFormData(
|
||||
continue
|
||||
}
|
||||
|
||||
// special case for multipart json encoded fields
|
||||
if key == rest.MultipartJsonKey {
|
||||
for _, v := range values {
|
||||
if err := json.Unmarshal([]byte(v), &data); err != nil {
|
||||
form.app.Logger().Debug("Failed to decode @json value into the data map", "error", err, "value", v)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
field := form.record.Collection().Schema.GetFieldByName(key)
|
||||
if field != nil && list.ExistInSlice(field.Type, arrayValueSupportTypes) {
|
||||
if field != nil && list.ExistInSlice(field.Type, arraybleFieldTypes) {
|
||||
data[key] = values
|
||||
} else {
|
||||
data[key] = values[0]
|
||||
@@ -313,10 +323,10 @@ func (form *RecordUpsert) AddFiles(key string, files ...*filesystem.File) error
|
||||
// Example
|
||||
//
|
||||
// // mark only only 2 files for removal
|
||||
// form.AddFiles("documents", "file1_aw4bdrvws6.txt", "file2_xwbs36bafv.txt")
|
||||
// form.RemoveFiles("documents", "file1_aw4bdrvws6.txt", "file2_xwbs36bafv.txt")
|
||||
//
|
||||
// // mark all "documents" files for removal
|
||||
// form.AddFiles("documents")
|
||||
// form.RemoveFiles("documents")
|
||||
func (form *RecordUpsert) RemoveFiles(key string, toDelete ...string) error {
|
||||
field := form.record.Collection().Schema.GetFieldByName(key)
|
||||
if field == nil || field.Type != schema.FieldTypeFile {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/pocketbase/pocketbase/tests"
|
||||
"github.com/pocketbase/pocketbase/tools/filesystem"
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
"github.com/pocketbase/pocketbase/tools/rest"
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
@@ -150,9 +151,10 @@ func TestRecordUpsertLoadRequestMultipart(t *testing.T) {
|
||||
}
|
||||
|
||||
formData, mp, err := tests.MockMultipartData(map[string]string{
|
||||
"a.b.id": "test_id",
|
||||
"a.b.text": "test123",
|
||||
"a.b.unknown": "test456",
|
||||
"a.b.id": "test_id",
|
||||
"a.b.text": "test123",
|
||||
"a.b.unknown": "test456",
|
||||
"a.b." + rest.MultipartJsonKey: `{"json":["a","b"],"email":"test3@example.com"}`,
|
||||
// file fields unset/delete
|
||||
"a.b.file_one-": "test_d61b33QdDU.txt", // delete with modifier
|
||||
"a.b.file_many.0": "", // delete by index
|
||||
@@ -184,6 +186,19 @@ func TestRecordUpsertLoadRequestMultipart(t *testing.T) {
|
||||
t.Fatalf("Didn't expect unknown field to be set, got %v", v)
|
||||
}
|
||||
|
||||
if v, ok := form.Data()["email"]; !ok || v != "test3@example.com" {
|
||||
t.Fatalf("Expect email field to be %q, got %q", "test3@example.com", v)
|
||||
}
|
||||
|
||||
rawJsonValue, ok := form.Data()["json"].(types.JsonRaw)
|
||||
if !ok {
|
||||
t.Fatal("Expect json field to be set")
|
||||
}
|
||||
expectedJsonValue := `["a","b"]`
|
||||
if rawJsonValue.String() != expectedJsonValue {
|
||||
t.Fatalf("Expect json field %v, got %v", expectedJsonValue, rawJsonValue)
|
||||
}
|
||||
|
||||
fileOne, ok := form.Data()["file_one"]
|
||||
if !ok {
|
||||
t.Fatal("Expect file_one field to be set")
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
)
|
||||
|
||||
// Compare checks whether the provided model id exists.
|
||||
// UniqueId checks whether the provided model id already exists.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
|
||||
@@ -4,7 +4,12 @@ go 1.21
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/aws/aws-sdk-go v1.49.20
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.11
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.11
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.15
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1
|
||||
github.com/aws/smithy-go v1.20.2
|
||||
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,80 +22,71 @@ 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.19
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/pocketbase/dbx v1.10.1
|
||||
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.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
|
||||
gocloud.dev v0.37.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/oauth2 v0.19.0
|
||||
golang.org/x/sync v0.7.0
|
||||
modernc.org/sqlite v1.29.6
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // 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.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.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.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/aws/aws-sdk-go v1.51.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||
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.5.0 // indirect
|
||||
github.com/google/wire v0.5.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.3 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
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.15.0 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/term v0.16.0 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/term v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.17.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // 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-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.40.1 // indirect
|
||||
google.golang.org/api v0.172.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240412170617-26222e5d3d56 // indirect
|
||||
google.golang.org/grpc v1.63.2 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect
|
||||
modernc.org/libc v1.49.3 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.2 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
modernc.org/token v1.1.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y=
|
||||
cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic=
|
||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
||||
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
||||
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
|
||||
cloud.google.com/go/compute v1.25.0 h1:H1/4SqSUhjPFE7L5ddzHOfY2bCAvjwNRZPNl6Ni5oYU=
|
||||
cloud.google.com/go/compute v1.25.0/go.mod h1:GR7F0ZPZH8EhChlMo9FkLd7eUTwEymjqQagxzilIxIE=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
|
||||
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
|
||||
cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w=
|
||||
cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
|
||||
cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc=
|
||||
cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI=
|
||||
cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY=
|
||||
cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
@@ -17,84 +19,46 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDe
|
||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
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/aws/aws-sdk-go v1.51.11 h1:El5VypsMIz7sFwAAj/j06JX9UGs4KAbAIEaZ57bNY4s=
|
||||
github.com/aws/aws-sdk-go v1.51.11/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.11 h1:f47rANd2LQEYHda2ddSCKYId18/8BhSRM4BULGmfgNA=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.11/go.mod h1:SMsV78RIOYdve1vf36z8LmnszlRWkwMQtomCAI0/mIE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHHvIq0l/pX3fwO+Tzs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.15 h1:7Zwtt/lP3KNRkeZre7soMELMGNoBrutx8nobg1jKWmo=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.15/go.mod h1:436h2adoHb57yd+8W+gYPrrA9U/R/SuAuOO42Ushzhw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5 h1:81KE7vaZzrl7yHBYHVEzYB8sypz11NMOZ40YlWvPxsU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5/go.mod h1:LIt2rg7Mcgn09Ygbdh/RdIm0rQ+3BNkbP1gyVMFtRK0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7 h1:ZMeFZ5yk+Ek+jNr1+uwCd2tG89t6oTS5yVWpa6yy2es=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7/go.mod h1:mxV05U+4JiHqIpGqqYXOHLPKUC6bDXC44bsUhNjOEwY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 h1:f9RyWNtS8oH7cZlbn+/JNPpjUk5+5fLd5lM9M0i49Ys=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5/go.mod h1:h5CoMZV2VF297/VLhRhO1WF+XYWOzXo+4HsObA4HjBQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1 h1:6cnno47Me9bRykw9AEv9zkXE+5or7jz8TsskTTccbgc=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1/go.mod h1:qmdkIIAC+GCLASF7R2whgNrJADz0QZPX+Seiw/i4S3o=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 h1:vN8hEbpRnL7+Hopy9dzmRle1xmDc7o8tmY0klsr175w=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.5/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 h1:Jux+gDDyi1Lruk+KHF91tK2KCuY61kzoCpvtvJJBtOE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 h1:cwIxeBttqPN3qkaAjcEcsh8NYr8n2HZPkcKgPAi1phU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
|
||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
|
||||
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
|
||||
@@ -131,6 +95,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
@@ -139,13 +105,17 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/ganigeorgiev/fexpr v0.4.0 h1:ojitI+VMNZX/odeNL1x3RzTTE8qAIVvnSSYPNAnQFDI=
|
||||
github.com/ganigeorgiev/fexpr v0.4.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4=
|
||||
github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
@@ -165,42 +135,32 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE=
|
||||
github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk=
|
||||
github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk=
|
||||
github.com/google/go-replayers/httpreplay v1.2.0/go.mod h1:WahEFFZZ7a1P4VM1qEeHy+tME4bwyqPcwWbNlUI1Mcg=
|
||||
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
|
||||
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
|
||||
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ=
|
||||
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
|
||||
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA=
|
||||
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
@@ -230,19 +190,17 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/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/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
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=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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=
|
||||
@@ -275,31 +233,33 @@ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
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=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
gocloud.dev v0.37.0 h1:XF1rN6R0qZI/9DYjN16Uy0durAmSlf58DHOcb28GPro=
|
||||
gocloud.dev v0.37.0/go.mod h1:7/O4kqdInCNsc6LqgmuFnS0GRew4XNNYWpA44yQnwco=
|
||||
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/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
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=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -310,23 +270,17 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.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/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
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/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
|
||||
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
|
||||
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/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.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=
|
||||
@@ -339,16 +293,12 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.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/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.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/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
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=
|
||||
@@ -364,55 +314,36 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.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/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
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/api v0.172.0 h1:/1OcMZGPmW1rX2LCu2CmGUD1KXK1+pfzxotxyRUCCdk=
|
||||
google.golang.org/api v0.172.0/go.mod h1:+fJZq6QXWfa9pXhnIzsjx4yI22d4aI9ZpLb58gvXjis=
|
||||
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=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f h1:Vn+VyHU5guc9KjB5KrjI2q0wCOWEOIh0OEsleqakHJg=
|
||||
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/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s=
|
||||
google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311173647-c811ad7063a7 h1:oqta3O3AnlWbmIE3bFnWbu4bRxZjfbWCp0cKSuZh01E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311173647-c811ad7063a7/go.mod h1:VQW3tUculP/D4B+xVCo+VgSq8As6wA9ZjHl//pmk+6s=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240412170617-26222e5d3d56 h1:zviK8GX4VlMstrK3JkexM5UHjH1VOkRebH9y3jhSBGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240412170617-26222e5d3d56/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
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=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
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/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
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=
|
||||
@@ -422,12 +353,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
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=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.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=
|
||||
@@ -441,37 +368,29 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q=
|
||||
modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
|
||||
modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0=
|
||||
modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
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/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk=
|
||||
modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA=
|
||||
modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8=
|
||||
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg=
|
||||
modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo=
|
||||
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=
|
||||
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
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/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4=
|
||||
modernc.org/sqlite v1.29.6/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
|
||||
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=
|
||||
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
|
||||
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ func SendRecordVerification(app core.App, authRecord *models.Record) error {
|
||||
})
|
||||
}
|
||||
|
||||
// SendUserChangeEmail sends a change email confirmation email to the specified user.
|
||||
// SendRecordChangeEmail sends a change email confirmation email to the specified user.
|
||||
func SendRecordChangeEmail(app core.App, record *models.Record, newEmail string) error {
|
||||
token, tokenErr := tokens.NewRecordChangeEmailToken(app, record, newEmail)
|
||||
if tokenErr != nil {
|
||||
|
||||
@@ -53,7 +53,7 @@ const Layout = `
|
||||
.btn {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
border: 0;
|
||||
border: 1px solid #e5e5e5;
|
||||
cursor: pointer;
|
||||
color: #fff !important;
|
||||
background: #16161a !important;
|
||||
|
||||
@@ -6,9 +6,17 @@ import (
|
||||
"github.com/pocketbase/pocketbase/models/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
RequestInfoContextDefault = "default"
|
||||
RequestInfoContextRealtime = "realtime"
|
||||
RequestInfoContextProtectedFile = "protectedFile"
|
||||
RequestInfoContextOAuth2 = "oauth2"
|
||||
)
|
||||
|
||||
// RequestInfo defines a HTTP request data struct, usually used
|
||||
// as part of the `@request.*` filter resolver.
|
||||
type RequestInfo struct {
|
||||
Context string `json:"context"`
|
||||
Query map[string]any `json:"query"`
|
||||
Data map[string]any `json:"data"`
|
||||
Headers map[string]any `json:"headers"`
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
"github.com/go-ozzo/ozzo-validation/v4/is"
|
||||
@@ -211,6 +212,7 @@ func (f SchemaField) Validate() error {
|
||||
validation.Length(1, 255),
|
||||
validation.Match(schemaFieldNameRegex),
|
||||
validation.NotIn(list.ToInterfaceSlice(excludeNames)...),
|
||||
validation.By(f.checkForVia),
|
||||
),
|
||||
validation.Field(&f.Type, validation.Required, validation.In(list.ToInterfaceSlice(FieldTypes())...)),
|
||||
// currently file fields cannot be unique because a proper
|
||||
@@ -228,6 +230,20 @@ func (f *SchemaField) checkOptions(value any) error {
|
||||
return v.Validate()
|
||||
}
|
||||
|
||||
// @todo merge with the collections during the refactoring
|
||||
func (f *SchemaField) checkForVia(value any) error {
|
||||
v, _ := value.(string)
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.Contains(strings.ToLower(v), "_via_") {
|
||||
return validation.NewError("validation_invalid_name", "The name of the field cannot contain '_via_'.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitOptions initializes the current field options based on its type.
|
||||
//
|
||||
// Returns error on unknown field type.
|
||||
|
||||
@@ -298,6 +298,15 @@ func TestSchemaFieldValidate(t *testing.T) {
|
||||
},
|
||||
[]string{"name"},
|
||||
},
|
||||
{
|
||||
"name with _via_",
|
||||
schema.SchemaField{
|
||||
Type: schema.FieldTypeText,
|
||||
Id: "1234567890",
|
||||
Name: "a_via_b",
|
||||
},
|
||||
[]string{"name"},
|
||||
},
|
||||
{
|
||||
"reserved name (null)",
|
||||
schema.SchemaField{
|
||||
|
||||
+62
-47
@@ -42,29 +42,31 @@ type Settings struct {
|
||||
// Deprecated: Will be removed in v0.9+
|
||||
EmailAuth EmailAuthConfig `form:"emailAuth" json:"emailAuth"`
|
||||
|
||||
GoogleAuth AuthProviderConfig `form:"googleAuth" json:"googleAuth"`
|
||||
FacebookAuth AuthProviderConfig `form:"facebookAuth" json:"facebookAuth"`
|
||||
GithubAuth AuthProviderConfig `form:"githubAuth" json:"githubAuth"`
|
||||
GitlabAuth AuthProviderConfig `form:"gitlabAuth" json:"gitlabAuth"`
|
||||
DiscordAuth AuthProviderConfig `form:"discordAuth" json:"discordAuth"`
|
||||
TwitterAuth AuthProviderConfig `form:"twitterAuth" json:"twitterAuth"`
|
||||
MicrosoftAuth AuthProviderConfig `form:"microsoftAuth" json:"microsoftAuth"`
|
||||
SpotifyAuth AuthProviderConfig `form:"spotifyAuth" json:"spotifyAuth"`
|
||||
KakaoAuth AuthProviderConfig `form:"kakaoAuth" json:"kakaoAuth"`
|
||||
TwitchAuth AuthProviderConfig `form:"twitchAuth" json:"twitchAuth"`
|
||||
StravaAuth AuthProviderConfig `form:"stravaAuth" json:"stravaAuth"`
|
||||
GiteeAuth AuthProviderConfig `form:"giteeAuth" json:"giteeAuth"`
|
||||
LivechatAuth AuthProviderConfig `form:"livechatAuth" json:"livechatAuth"`
|
||||
GiteaAuth AuthProviderConfig `form:"giteaAuth" json:"giteaAuth"`
|
||||
OIDCAuth AuthProviderConfig `form:"oidcAuth" json:"oidcAuth"`
|
||||
OIDC2Auth AuthProviderConfig `form:"oidc2Auth" json:"oidc2Auth"`
|
||||
OIDC3Auth AuthProviderConfig `form:"oidc3Auth" json:"oidc3Auth"`
|
||||
AppleAuth AuthProviderConfig `form:"appleAuth" json:"appleAuth"`
|
||||
InstagramAuth AuthProviderConfig `form:"instagramAuth" json:"instagramAuth"`
|
||||
VKAuth AuthProviderConfig `form:"vkAuth" json:"vkAuth"`
|
||||
YandexAuth AuthProviderConfig `form:"yandexAuth" json:"yandexAuth"`
|
||||
PatreonAuth AuthProviderConfig `form:"patreonAuth" json:"patreonAuth"`
|
||||
MailcowAuth AuthProviderConfig `form:"mailcowAuth" json:"mailcowAuth"`
|
||||
GoogleAuth AuthProviderConfig `form:"googleAuth" json:"googleAuth"`
|
||||
FacebookAuth AuthProviderConfig `form:"facebookAuth" json:"facebookAuth"`
|
||||
GithubAuth AuthProviderConfig `form:"githubAuth" json:"githubAuth"`
|
||||
GitlabAuth AuthProviderConfig `form:"gitlabAuth" json:"gitlabAuth"`
|
||||
DiscordAuth AuthProviderConfig `form:"discordAuth" json:"discordAuth"`
|
||||
TwitterAuth AuthProviderConfig `form:"twitterAuth" json:"twitterAuth"`
|
||||
MicrosoftAuth AuthProviderConfig `form:"microsoftAuth" json:"microsoftAuth"`
|
||||
SpotifyAuth AuthProviderConfig `form:"spotifyAuth" json:"spotifyAuth"`
|
||||
KakaoAuth AuthProviderConfig `form:"kakaoAuth" json:"kakaoAuth"`
|
||||
TwitchAuth AuthProviderConfig `form:"twitchAuth" json:"twitchAuth"`
|
||||
StravaAuth AuthProviderConfig `form:"stravaAuth" json:"stravaAuth"`
|
||||
GiteeAuth AuthProviderConfig `form:"giteeAuth" json:"giteeAuth"`
|
||||
LivechatAuth AuthProviderConfig `form:"livechatAuth" json:"livechatAuth"`
|
||||
GiteaAuth AuthProviderConfig `form:"giteaAuth" json:"giteaAuth"`
|
||||
OIDCAuth AuthProviderConfig `form:"oidcAuth" json:"oidcAuth"`
|
||||
OIDC2Auth AuthProviderConfig `form:"oidc2Auth" json:"oidc2Auth"`
|
||||
OIDC3Auth AuthProviderConfig `form:"oidc3Auth" json:"oidc3Auth"`
|
||||
AppleAuth AuthProviderConfig `form:"appleAuth" json:"appleAuth"`
|
||||
InstagramAuth AuthProviderConfig `form:"instagramAuth" json:"instagramAuth"`
|
||||
VKAuth AuthProviderConfig `form:"vkAuth" json:"vkAuth"`
|
||||
YandexAuth AuthProviderConfig `form:"yandexAuth" json:"yandexAuth"`
|
||||
PatreonAuth AuthProviderConfig `form:"patreonAuth" json:"patreonAuth"`
|
||||
MailcowAuth AuthProviderConfig `form:"mailcowAuth" json:"mailcowAuth"`
|
||||
BitbucketAuth AuthProviderConfig `form:"bitbucketAuth" json:"bitbucketAuth"`
|
||||
PlanningcenterAuth AuthProviderConfig `form:"planningcenterAuth" json:"planningcenterAuth"`
|
||||
}
|
||||
|
||||
// New creates and returns a new default Settings instance.
|
||||
@@ -196,6 +198,12 @@ func New() *Settings {
|
||||
MailcowAuth: AuthProviderConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
BitbucketAuth: AuthProviderConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
PlanningcenterAuth: AuthProviderConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +249,8 @@ func (s *Settings) Validate() error {
|
||||
validation.Field(&s.YandexAuth),
|
||||
validation.Field(&s.PatreonAuth),
|
||||
validation.Field(&s.MailcowAuth),
|
||||
validation.Field(&s.BitbucketAuth),
|
||||
validation.Field(&s.PlanningcenterAuth),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -309,6 +319,8 @@ func (s *Settings) RedactClone() (*Settings, error) {
|
||||
&clone.YandexAuth.ClientSecret,
|
||||
&clone.PatreonAuth.ClientSecret,
|
||||
&clone.MailcowAuth.ClientSecret,
|
||||
&clone.BitbucketAuth.ClientSecret,
|
||||
&clone.PlanningcenterAuth.ClientSecret,
|
||||
}
|
||||
|
||||
// mask all sensitive fields
|
||||
@@ -328,29 +340,31 @@ func (s *Settings) NamedAuthProviderConfigs() map[string]AuthProviderConfig {
|
||||
defer s.mux.RUnlock()
|
||||
|
||||
return map[string]AuthProviderConfig{
|
||||
auth.NameGoogle: s.GoogleAuth,
|
||||
auth.NameFacebook: s.FacebookAuth,
|
||||
auth.NameGithub: s.GithubAuth,
|
||||
auth.NameGitlab: s.GitlabAuth,
|
||||
auth.NameDiscord: s.DiscordAuth,
|
||||
auth.NameTwitter: s.TwitterAuth,
|
||||
auth.NameMicrosoft: s.MicrosoftAuth,
|
||||
auth.NameSpotify: s.SpotifyAuth,
|
||||
auth.NameKakao: s.KakaoAuth,
|
||||
auth.NameTwitch: s.TwitchAuth,
|
||||
auth.NameStrava: s.StravaAuth,
|
||||
auth.NameGitee: s.GiteeAuth,
|
||||
auth.NameLivechat: s.LivechatAuth,
|
||||
auth.NameGitea: s.GiteaAuth,
|
||||
auth.NameOIDC: s.OIDCAuth,
|
||||
auth.NameOIDC + "2": s.OIDC2Auth,
|
||||
auth.NameOIDC + "3": s.OIDC3Auth,
|
||||
auth.NameApple: s.AppleAuth,
|
||||
auth.NameInstagram: s.InstagramAuth,
|
||||
auth.NameVK: s.VKAuth,
|
||||
auth.NameYandex: s.YandexAuth,
|
||||
auth.NamePatreon: s.PatreonAuth,
|
||||
auth.NameMailcow: s.MailcowAuth,
|
||||
auth.NameGoogle: s.GoogleAuth,
|
||||
auth.NameFacebook: s.FacebookAuth,
|
||||
auth.NameGithub: s.GithubAuth,
|
||||
auth.NameGitlab: s.GitlabAuth,
|
||||
auth.NameDiscord: s.DiscordAuth,
|
||||
auth.NameTwitter: s.TwitterAuth,
|
||||
auth.NameMicrosoft: s.MicrosoftAuth,
|
||||
auth.NameSpotify: s.SpotifyAuth,
|
||||
auth.NameKakao: s.KakaoAuth,
|
||||
auth.NameTwitch: s.TwitchAuth,
|
||||
auth.NameStrava: s.StravaAuth,
|
||||
auth.NameGitee: s.GiteeAuth,
|
||||
auth.NameLivechat: s.LivechatAuth,
|
||||
auth.NameGitea: s.GiteaAuth,
|
||||
auth.NameOIDC: s.OIDCAuth,
|
||||
auth.NameOIDC + "2": s.OIDC2Auth,
|
||||
auth.NameOIDC + "3": s.OIDC3Auth,
|
||||
auth.NameApple: s.AppleAuth,
|
||||
auth.NameInstagram: s.InstagramAuth,
|
||||
auth.NameVK: s.VKAuth,
|
||||
auth.NameYandex: s.YandexAuth,
|
||||
auth.NamePatreon: s.PatreonAuth,
|
||||
auth.NameMailcow: s.MailcowAuth,
|
||||
auth.NameBitbucket: s.BitbucketAuth,
|
||||
auth.NamePlanningcenter: s.PlanningcenterAuth,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,7 +463,7 @@ type BackupsConfig struct {
|
||||
// Leave it empty to disable the auto backups functionality.
|
||||
Cron string `form:"cron" json:"cron"`
|
||||
|
||||
// CronMaxKeep is the the max number of cron generated backups to
|
||||
// CronMaxKeep is the max number of cron generated backups to
|
||||
// keep before removing older entries.
|
||||
//
|
||||
// This field works only when the cron config has valid cron expression.
|
||||
@@ -516,6 +530,7 @@ type EmailTemplate struct {
|
||||
Body string `form:"body" json:"body"`
|
||||
Subject string `form:"subject" json:"subject"`
|
||||
ActionUrl string `form:"actionUrl" json:"actionUrl"`
|
||||
Hidden bool `form:"hidden" json:"hidden"`
|
||||
}
|
||||
|
||||
// Validate makes EmailTemplate validatable by implementing [validation.Validatable] interface.
|
||||
|
||||
@@ -78,6 +78,10 @@ func TestSettingsValidate(t *testing.T) {
|
||||
s.PatreonAuth.ClientId = ""
|
||||
s.MailcowAuth.Enabled = true
|
||||
s.MailcowAuth.ClientId = ""
|
||||
s.BitbucketAuth.Enabled = true
|
||||
s.BitbucketAuth.ClientId = ""
|
||||
s.PlanningcenterAuth.Enabled = true
|
||||
s.PlanningcenterAuth.ClientId = ""
|
||||
|
||||
// check if Validate() is triggering the members validate methods.
|
||||
err := s.Validate()
|
||||
@@ -121,6 +125,8 @@ func TestSettingsValidate(t *testing.T) {
|
||||
`"yandexAuth":{`,
|
||||
`"patreonAuth":{`,
|
||||
`"mailcowAuth":{`,
|
||||
`"bitbucketAuth":{`,
|
||||
`"planningcenterAuth":{`,
|
||||
}
|
||||
|
||||
errBytes, _ := json.Marshal(err)
|
||||
@@ -198,6 +204,10 @@ func TestSettingsMerge(t *testing.T) {
|
||||
s2.PatreonAuth.ClientId = "patreon_test"
|
||||
s2.MailcowAuth.Enabled = true
|
||||
s2.MailcowAuth.ClientId = "mailcow_test"
|
||||
s2.BitbucketAuth.Enabled = true
|
||||
s2.BitbucketAuth.ClientId = "bitbucket_test"
|
||||
s2.PlanningcenterAuth.Enabled = true
|
||||
s2.PlanningcenterAuth.ClientId = "planningcenter_test"
|
||||
|
||||
if err := s1.Merge(s2); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -290,6 +300,8 @@ func TestSettingsRedactClone(t *testing.T) {
|
||||
s1.YandexAuth.ClientSecret = testSecret
|
||||
s1.PatreonAuth.ClientSecret = testSecret
|
||||
s1.MailcowAuth.ClientSecret = testSecret
|
||||
s1.BitbucketAuth.ClientSecret = testSecret
|
||||
s1.PlanningcenterAuth.ClientSecret = testSecret
|
||||
|
||||
s1Bytes, err := json.Marshal(s1)
|
||||
if err != nil {
|
||||
@@ -350,6 +362,8 @@ func TestNamedAuthProviderConfigs(t *testing.T) {
|
||||
s.YandexAuth.ClientId = "yandex_test"
|
||||
s.PatreonAuth.ClientId = "patreon_test"
|
||||
s.MailcowAuth.ClientId = "mailcow_test"
|
||||
s.BitbucketAuth.ClientId = "bitbucket_test"
|
||||
s.PlanningcenterAuth.ClientId = "planningcenter_test"
|
||||
|
||||
result := s.NamedAuthProviderConfigs()
|
||||
|
||||
@@ -383,6 +397,8 @@ func TestNamedAuthProviderConfigs(t *testing.T) {
|
||||
`"yandex":{"enabled":false,"clientId":"yandex_test"`,
|
||||
`"patreon":{"enabled":false,"clientId":"patreon_test"`,
|
||||
`"mailcow":{"enabled":false,"clientId":"mailcow_test"`,
|
||||
`"bitbucket":{"enabled":false,"clientId":"bitbucket_test"`,
|
||||
`"planningcenter":{"enabled":false,"clientId":"planningcenter_test"`,
|
||||
}
|
||||
for _, p := range expectedParts {
|
||||
if !strings.Contains(encodedStr, p) {
|
||||
|
||||
@@ -97,8 +97,8 @@ func Register(app core.App, rootCmd *cobra.Command, config Config) error {
|
||||
|
||||
type plugin struct {
|
||||
app core.App
|
||||
currentVersion string
|
||||
config Config
|
||||
currentVersion string
|
||||
}
|
||||
|
||||
func (p *plugin) updateCmd() *cobra.Command {
|
||||
@@ -106,7 +106,7 @@ func (p *plugin) updateCmd() *cobra.Command {
|
||||
|
||||
command := &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Automatically updates the current PocketBase executable with the latest available version",
|
||||
Short: "Automatically updates the current app executable with the latest available version",
|
||||
SilenceUsage: true,
|
||||
RunE: func(command *cobra.Command, args []string) error {
|
||||
var needConfirm bool
|
||||
@@ -160,7 +160,7 @@ func (p *plugin) update(withBackup bool) error {
|
||||
}
|
||||
|
||||
if compareVersions(strings.TrimPrefix(p.currentVersion, "v"), strings.TrimPrefix(latest.Tag, "v")) <= 0 {
|
||||
color.Green("You already have the latest PocketBase %s.", p.currentVersion)
|
||||
color.Green("You already have the latest version %s.", p.currentVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ func (p *plugin) update(withBackup bool) error {
|
||||
fmt.Print("\n")
|
||||
color.Cyan("Here is a list with some of the %s changes:", latest.Tag)
|
||||
// remove the update command note to avoid "stuttering"
|
||||
releaseNotes := strings.TrimSpace(strings.Replace(latest.Body, "> _To update the prebuilt executable you can run `./pocketbase update`._", "", 1))
|
||||
releaseNotes := strings.TrimSpace(strings.Replace(latest.Body, "> _To update the prebuilt executable you can run `./"+p.config.ArchiveExecutable+" update`._", "", 1))
|
||||
color.Cyan(releaseNotes)
|
||||
fmt.Print("\n")
|
||||
}
|
||||
|
||||
+77
-18
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/dop251/goja"
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/labstack/echo/v5/middleware"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
@@ -37,6 +38,7 @@ import (
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
"github.com/pocketbase/pocketbase/tools/subscriptions"
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -375,7 +377,7 @@ func baseBinds(vm *goja.Runtime) {
|
||||
})
|
||||
|
||||
vm.Set("RequestInfo", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &models.RequestInfo{}
|
||||
instance := &models.RequestInfo{Context: models.RequestInfoContextDefault}
|
||||
return structConstructor(vm, call, instance)
|
||||
})
|
||||
|
||||
@@ -533,6 +535,16 @@ func filesystemBinds(vm *goja.Runtime) {
|
||||
obj.Set("fileFromPath", filesystem.NewFileFromPath)
|
||||
obj.Set("fileFromBytes", filesystem.NewFileFromBytes)
|
||||
obj.Set("fileFromMultipart", filesystem.NewFileFromMultipart)
|
||||
obj.Set("fileFromUrl", func(url string, secTimeout int) (*filesystem.File, error) {
|
||||
if secTimeout == 0 {
|
||||
secTimeout = 120
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(secTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
return filesystem.NewFileFromUrl(ctx, url)
|
||||
})
|
||||
}
|
||||
|
||||
func filepathBinds(vm *goja.Runtime) {
|
||||
@@ -618,6 +630,8 @@ func apisBinds(vm *goja.Runtime) {
|
||||
obj.Set("requireAdminOrRecordAuth", apis.RequireAdminOrRecordAuth)
|
||||
obj.Set("requireAdminOrOwnerAuth", apis.RequireAdminOrOwnerAuth)
|
||||
obj.Set("activityLogger", apis.ActivityLogger)
|
||||
obj.Set("gzip", middleware.Gzip)
|
||||
obj.Set("bodyLimit", middleware.BodyLimit)
|
||||
|
||||
// record helpers
|
||||
obj.Set("requestInfo", apis.RequestInfo)
|
||||
@@ -637,34 +651,61 @@ func httpClientBinds(vm *goja.Runtime) {
|
||||
obj := vm.NewObject()
|
||||
vm.Set("$http", obj)
|
||||
|
||||
vm.Set("FormData", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := FormData{}
|
||||
|
||||
instanceValue := vm.ToValue(instance).(*goja.Object)
|
||||
instanceValue.SetPrototype(call.This.Prototype())
|
||||
|
||||
return instanceValue
|
||||
})
|
||||
|
||||
type sendResult struct {
|
||||
StatusCode int `json:"statusCode"`
|
||||
Json any `json:"json"`
|
||||
Headers map[string][]string `json:"headers"`
|
||||
Cookies map[string]*http.Cookie `json:"cookies"`
|
||||
Raw string `json:"raw"`
|
||||
Json any `json:"json"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
}
|
||||
|
||||
type sendConfig struct {
|
||||
// Deprecated: consider using Body instead
|
||||
Data map[string]any
|
||||
|
||||
Body any // raw string or FormData
|
||||
Headers map[string]string
|
||||
Method string
|
||||
Url string
|
||||
Body string
|
||||
Headers map[string]string
|
||||
Timeout int // seconds (default to 120)
|
||||
Data map[string]any // deprecated, consider using Body instead
|
||||
Timeout int // seconds (default to 120)
|
||||
}
|
||||
|
||||
obj.Set("send", func(params map[string]any) (*sendResult, error) {
|
||||
rawParams, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := sendConfig{
|
||||
Method: "GET",
|
||||
}
|
||||
if err := json.Unmarshal(rawParams, &config); err != nil {
|
||||
return nil, err
|
||||
|
||||
if v, ok := params["data"]; ok {
|
||||
config.Data = cast.ToStringMap(v)
|
||||
}
|
||||
|
||||
if v, ok := params["body"]; ok {
|
||||
config.Body = v
|
||||
}
|
||||
|
||||
if v, ok := params["headers"]; ok {
|
||||
config.Headers = cast.ToStringMapString(v)
|
||||
}
|
||||
|
||||
if v, ok := params["method"]; ok {
|
||||
config.Method = cast.ToString(v)
|
||||
}
|
||||
|
||||
if v, ok := params["url"]; ok {
|
||||
config.Url = cast.ToString(v)
|
||||
}
|
||||
|
||||
if v, ok := params["timeout"]; ok {
|
||||
config.Timeout = cast.ToInt(v)
|
||||
}
|
||||
|
||||
if config.Timeout <= 0 {
|
||||
@@ -675,6 +716,7 @@ func httpClientBinds(vm *goja.Runtime) {
|
||||
defer cancel()
|
||||
|
||||
var reqBody io.Reader
|
||||
var contentType string
|
||||
|
||||
// legacy json body data
|
||||
if len(config.Data) != 0 {
|
||||
@@ -683,10 +725,19 @@ func httpClientBinds(vm *goja.Runtime) {
|
||||
return nil, err
|
||||
}
|
||||
reqBody = bytes.NewReader(encoded)
|
||||
}
|
||||
} else {
|
||||
switch v := config.Body.(type) {
|
||||
case FormData:
|
||||
body, mp, err := v.toMultipart()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.Body != "" {
|
||||
reqBody = strings.NewReader(config.Body)
|
||||
reqBody = body
|
||||
contentType = mp.FormDataContentType()
|
||||
default:
|
||||
reqBody = strings.NewReader(cast.ToString(config.Body))
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, strings.ToUpper(config.Method), config.Url, reqBody)
|
||||
@@ -698,7 +749,15 @@ func httpClientBinds(vm *goja.Runtime) {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
|
||||
// set default content-type header (if missing)
|
||||
// set the explicit content type
|
||||
// (overwriting the user provided header value if any)
|
||||
if contentType != "" {
|
||||
req.Header.Set("content-type", contentType)
|
||||
}
|
||||
|
||||
// @todo consider removing during the refactoring
|
||||
//
|
||||
// fallback to json content-type
|
||||
if req.Header.Get("content-type") == "" {
|
||||
req.Header.Set("content-type", "application/json")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package jsvm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
@@ -890,13 +891,23 @@ func TestFilesystemBinds(t *testing.T) {
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/error" {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "test")
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
vm := goja.New()
|
||||
vm.Set("mh", &multipart.FileHeader{Filename: "test"})
|
||||
vm.Set("testFile", filepath.Join(app.DataDir(), "data.db"))
|
||||
vm.Set("baseUrl", srv.URL)
|
||||
baseBinds(vm)
|
||||
filesystemBinds(vm)
|
||||
|
||||
testBindsCount(vm, "$filesystem", 3, t)
|
||||
testBindsCount(vm, "$filesystem", 4, t)
|
||||
|
||||
// fileFromPath
|
||||
{
|
||||
@@ -939,6 +950,28 @@ func TestFilesystemBinds(t *testing.T) {
|
||||
t.Fatalf("[fileFromMultipart] Expected file with name %q, got %v", file.OriginalName, file)
|
||||
}
|
||||
}
|
||||
|
||||
// fileFromUrl (success)
|
||||
{
|
||||
v, err := vm.RunString(`$filesystem.fileFromUrl(baseUrl + "/test")`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
file, _ := v.Export().(*filesystem.File)
|
||||
|
||||
if file == nil || file.OriginalName != "test" {
|
||||
t.Fatalf("[fileFromUrl] Expected file with name %q, got %v", file.OriginalName, file)
|
||||
}
|
||||
}
|
||||
|
||||
// fileFromUrl (failure)
|
||||
{
|
||||
_, err := vm.RunString(`$filesystem.fileFromUrl(baseUrl + "/error")`)
|
||||
if err == nil {
|
||||
t.Fatal("Expected url fetch error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormsBinds(t *testing.T) {
|
||||
@@ -956,7 +989,7 @@ func TestApisBindsCount(t *testing.T) {
|
||||
apisBinds(vm)
|
||||
|
||||
testBindsCount(vm, "this", 6, t)
|
||||
testBindsCount(vm, "$apis", 12, t)
|
||||
testBindsCount(vm, "$apis", 14, t)
|
||||
}
|
||||
|
||||
func TestApisBindsApiError(t *testing.T) {
|
||||
@@ -1121,6 +1154,7 @@ func TestHttpClientBindsCount(t *testing.T) {
|
||||
vm := goja.New()
|
||||
httpClientBinds(vm)
|
||||
|
||||
testBindsCount(vm, "this", 2, t) // + FormData
|
||||
testBindsCount(vm, "$http", 1, t)
|
||||
}
|
||||
|
||||
@@ -1223,6 +1257,15 @@ func TestHttpClientBindsSend(t *testing.T) {
|
||||
headers: {"content-type": "text/plain"},
|
||||
})
|
||||
|
||||
// with FormData
|
||||
const formData = new FormData()
|
||||
formData.append("title", "123")
|
||||
const test3 = $http.send({
|
||||
url: testUrl,
|
||||
body: formData,
|
||||
headers: {"content-type": "text/plain"}, // should be ignored
|
||||
})
|
||||
|
||||
const scenarios = [
|
||||
[test0, {
|
||||
"statusCode": "400",
|
||||
@@ -1244,6 +1287,18 @@ func TestHttpClientBindsSend(t *testing.T) {
|
||||
"json.method": "GET",
|
||||
"json.headers.content_type": "text/plain",
|
||||
}],
|
||||
[test3, {
|
||||
"statusCode": "200",
|
||||
"headers.X-Custom.0": "custom_header",
|
||||
"cookies.sessionId.value": "123456",
|
||||
"json.method": "GET",
|
||||
"json.body": [
|
||||
"\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n123\r\n--",
|
||||
],
|
||||
"json.headers.content_type": [
|
||||
"multipart/form-data; boundary="
|
||||
],
|
||||
}],
|
||||
]
|
||||
|
||||
for (let scenario of scenarios) {
|
||||
@@ -1251,8 +1306,20 @@ func TestHttpClientBindsSend(t *testing.T) {
|
||||
const expectations = scenario[1];
|
||||
|
||||
for (let key in expectations) {
|
||||
if (getNestedVal(result, key) != expectations[key]) {
|
||||
throw new Error('Expected ' + key + ' ' + expectations[key] + ', got: ' + result.raw);
|
||||
const value = getNestedVal(result, key);
|
||||
const expectation = expectations[key]
|
||||
if (Array.isArray(expectation)) {
|
||||
// check for partial match(es)
|
||||
for (let exp of expectation) {
|
||||
if (!value.includes(exp)) {
|
||||
throw new Error('Expected ' + key + ' to contain ' + exp + ', got: ' + result.raw);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// check for direct match
|
||||
if (value != expectation) {
|
||||
throw new Error('Expected ' + key + ' ' + expectation + ', got: ' + result.raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package jsvm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/filesystem"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// FormData represents an interface similar to the browser's [FormData].
|
||||
//
|
||||
// The value of each FormData entry must be a string or [*filesystem.File] instance.
|
||||
//
|
||||
// It is intended to be used together with the JSVM `$http.send` when
|
||||
// sending multipart/form-data requests.
|
||||
//
|
||||
// [FormData]: https://developer.mozilla.org/en-US/docs/Web/API/FormData.
|
||||
type FormData map[string][]any
|
||||
|
||||
// Append appends a new value onto an existing key inside the current FormData,
|
||||
// or adds the key if it does not already exist.
|
||||
func (data FormData) Append(key string, value any) {
|
||||
data[key] = append(data[key], value)
|
||||
}
|
||||
|
||||
// Set sets a new value for an existing key inside the current FormData,
|
||||
// or adds the key/value if it does not already exist.
|
||||
func (data FormData) Set(key string, value any) {
|
||||
data[key] = []any{value}
|
||||
}
|
||||
|
||||
// Delete deletes a key and its value(s) from the current FormData.
|
||||
func (data FormData) Delete(key string) {
|
||||
delete(data, key)
|
||||
}
|
||||
|
||||
// Get returns the first value associated with a given key from
|
||||
// within the current FormData.
|
||||
//
|
||||
// If you expect multiple values and want all of them,
|
||||
// use the [FormData.GetAll] method instead.
|
||||
func (data FormData) Get(key string) any {
|
||||
values, ok := data[key]
|
||||
if !ok || len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return values[0]
|
||||
}
|
||||
|
||||
// GetAll returns all the values associated with a given key
|
||||
// from within the current FormData.
|
||||
func (data FormData) GetAll(key string) []any {
|
||||
values, ok := data[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
// Has returns whether a FormData object contains a certain key.
|
||||
func (data FormData) Has(key string) bool {
|
||||
values, ok := data[key]
|
||||
|
||||
return ok && len(values) > 0
|
||||
}
|
||||
|
||||
// Keys returns all keys contained in the current FormData.
|
||||
func (data FormData) Keys() []string {
|
||||
result := make([]string, 0, len(data))
|
||||
|
||||
for k := range data {
|
||||
result = append(result, k)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Keys returns all values contained in the current FormData.
|
||||
func (data FormData) Values() []any {
|
||||
result := make([]any, 0, len(data))
|
||||
|
||||
for _, values := range data {
|
||||
for _, v := range values {
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Entries returns a [key, value] slice pair for each FormData entry.
|
||||
func (data FormData) Entries() [][]any {
|
||||
result := make([][]any, 0, len(data))
|
||||
|
||||
for k, values := range data {
|
||||
for _, v := range values {
|
||||
result = append(result, []any{k, v})
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// toMultipart converts the current FormData entries into multipart encoded data.
|
||||
func (data FormData) toMultipart() (*bytes.Buffer, *multipart.Writer, error) {
|
||||
body := new(bytes.Buffer)
|
||||
|
||||
mp := multipart.NewWriter(body)
|
||||
defer mp.Close()
|
||||
|
||||
for k, values := range data {
|
||||
for _, rawValue := range values {
|
||||
switch v := rawValue.(type) {
|
||||
case *filesystem.File:
|
||||
err := func() error {
|
||||
mpw, err := mp.CreateFormFile(k, v.OriginalName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := v.Reader.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err := io.Copy(mpw, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
default:
|
||||
if err := mp.WriteField(k, cast.ToString(v)); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return body, mp, nil
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package jsvm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/filesystem"
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
)
|
||||
|
||||
func TestFormDataAppendAndSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := FormData{}
|
||||
|
||||
data.Append("a", 1)
|
||||
data.Append("a", 2)
|
||||
|
||||
data.Append("b", 3)
|
||||
data.Append("b", 4)
|
||||
data.Set("b", 5) // should overwrite the previous 2 calls
|
||||
|
||||
data.Set("c", 6)
|
||||
data.Set("c", 7)
|
||||
|
||||
if len(data["a"]) != 2 {
|
||||
t.Fatalf("Expected 2 'a' values, got %v", data["a"])
|
||||
}
|
||||
if data["a"][0] != 1 || data["a"][1] != 2 {
|
||||
t.Fatalf("Expected 1 and 2 'a' key values, got %v", data["a"])
|
||||
}
|
||||
|
||||
if len(data["b"]) != 1 {
|
||||
t.Fatalf("Expected 1 'b' values, got %v", data["b"])
|
||||
}
|
||||
if data["b"][0] != 5 {
|
||||
t.Fatalf("Expected 5 as 'b' key value, got %v", data["b"])
|
||||
}
|
||||
|
||||
if len(data["c"]) != 1 {
|
||||
t.Fatalf("Expected 1 'c' values, got %v", data["c"])
|
||||
}
|
||||
if data["c"][0] != 7 {
|
||||
t.Fatalf("Expected 7 as 'c' key value, got %v", data["c"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormDataDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := FormData{}
|
||||
data.Append("a", 1)
|
||||
data.Append("a", 2)
|
||||
data.Append("b", 3)
|
||||
|
||||
data.Delete("missing") // should do nothing
|
||||
data.Delete("a")
|
||||
|
||||
if len(data) != 1 {
|
||||
t.Fatalf("Expected exactly 1 data remaining key, got %v", data)
|
||||
}
|
||||
|
||||
if data["b"][0] != 3 {
|
||||
t.Fatalf("Expected 3 as 'b' key value, got %v", data["b"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormDataGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := FormData{}
|
||||
data.Append("a", 1)
|
||||
data.Append("a", 2)
|
||||
|
||||
if v := data.Get("missing"); v != nil {
|
||||
t.Fatalf("Expected %v for key 'missing', got %v", nil, v)
|
||||
}
|
||||
|
||||
if v := data.Get("a"); v != 1 {
|
||||
t.Fatalf("Expected %v for key 'a', got %v", 1, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormDataGetAll(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := FormData{}
|
||||
data.Append("a", 1)
|
||||
data.Append("a", 2)
|
||||
|
||||
if v := data.GetAll("missing"); v != nil {
|
||||
t.Fatalf("Expected %v for key 'a', got %v", nil, v)
|
||||
}
|
||||
|
||||
values := data.GetAll("a")
|
||||
if len(values) != 2 || values[0] != 1 || values[1] != 2 {
|
||||
t.Fatalf("Expected 1 and 2 values, got %v", values)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormDataHas(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := FormData{}
|
||||
data.Append("a", 1)
|
||||
|
||||
if v := data.Has("missing"); v {
|
||||
t.Fatalf("Expected key 'missing' to not exist: %v", v)
|
||||
}
|
||||
|
||||
if v := data.Has("a"); !v {
|
||||
t.Fatalf("Expected key 'a' to exist: %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormDataKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := FormData{}
|
||||
data.Append("a", 1)
|
||||
data.Append("b", 1)
|
||||
data.Append("c", 1)
|
||||
data.Append("a", 1)
|
||||
|
||||
keys := data.Keys()
|
||||
|
||||
expectedKeys := []string{"a", "b", "c"}
|
||||
|
||||
for _, expected := range expectedKeys {
|
||||
if !list.ExistInSlice(expected, keys) {
|
||||
t.Fatalf("Expected key %s to exists in %v", expected, keys)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormDataValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := FormData{}
|
||||
data.Append("a", 1)
|
||||
data.Append("b", 2)
|
||||
data.Append("c", 3)
|
||||
data.Append("a", 4)
|
||||
|
||||
values := data.Values()
|
||||
|
||||
expectedKeys := []any{1, 2, 3, 4}
|
||||
|
||||
for _, expected := range expectedKeys {
|
||||
if !list.ExistInSlice(expected, values) {
|
||||
t.Fatalf("Expected key %s to exists in %v", expected, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormDataEntries(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := FormData{}
|
||||
data.Append("a", 1)
|
||||
data.Append("b", 2)
|
||||
data.Append("c", 3)
|
||||
data.Append("a", 4)
|
||||
|
||||
entries := data.Entries()
|
||||
|
||||
rawEntries, err := json.Marshal(entries)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(entries) != 4 {
|
||||
t.Fatalf("Expected 4 entries")
|
||||
}
|
||||
|
||||
expectedEntries := []string{`["a",1]`, `["a",4]`, `["b",2]`, `["c",3]`}
|
||||
for _, expected := range expectedEntries {
|
||||
if !bytes.Contains(rawEntries, []byte(expected)) {
|
||||
t.Fatalf("Expected entry %s to exists in %s", expected, rawEntries)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormDataToMultipart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f, err := filesystem.NewFileFromBytes([]byte("abc"), "test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data := FormData{}
|
||||
data.Append("a", 1) // should be casted
|
||||
data.Append("b", "test1")
|
||||
data.Append("b", "test2")
|
||||
data.Append("c", f)
|
||||
|
||||
body, mp, err := data.toMultipart()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bodyStr := body.String()
|
||||
|
||||
// content type checks
|
||||
contentType := mp.FormDataContentType()
|
||||
expectedContentType := "multipart/form-data; boundary="
|
||||
if !strings.Contains(contentType, expectedContentType) {
|
||||
t.Fatalf("Expected to find content-type %s in %s", expectedContentType, contentType)
|
||||
}
|
||||
|
||||
// body checks
|
||||
expectedBodyParts := []string{
|
||||
"Content-Disposition: form-data; name=\"a\"\r\n\r\n1",
|
||||
"Content-Disposition: form-data; name=\"b\"\r\n\r\ntest1",
|
||||
"Content-Disposition: form-data; name=\"b\"\r\n\r\ntest2",
|
||||
"Content-Disposition: form-data; name=\"c\"; filename=\"test\"\r\nContent-Type: application/octet-stream\r\n\r\nabc",
|
||||
}
|
||||
for _, part := range expectedBodyParts {
|
||||
if !strings.Contains(bodyStr, part) {
|
||||
t.Fatalf("Expected to find %s in body\n%s", part, bodyStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
+3855
-3707
File diff suppressed because it is too large
Load Diff
@@ -96,7 +96,7 @@ declare function routerAdd(
|
||||
* ` + "```" + `js
|
||||
* routerUse((next) => {
|
||||
* return (c) => {
|
||||
* console.log(c.Path())
|
||||
* console.log(c.path())
|
||||
* return next(c)
|
||||
* }
|
||||
* })
|
||||
@@ -641,6 +641,22 @@ declare namespace $filesystem {
|
||||
let fileFromPath: filesystem.newFileFromPath
|
||||
let fileFromBytes: filesystem.newFileFromBytes
|
||||
let fileFromMultipart: filesystem.newFileFromMultipart
|
||||
|
||||
/**
|
||||
* fileFromUrl creates a new File from the provided url by
|
||||
* downloading the resource and creating a BytesReader.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* // with default max timeout of 120sec
|
||||
* const file1 = $filesystem.fileFromUrl("https://...")
|
||||
*
|
||||
* // with custom timeout of 15sec
|
||||
* const file2 = $filesystem.fileFromUrl("https://...", 15)
|
||||
* ` + "```" + `
|
||||
*/
|
||||
export function fileFromUrl(url: string, secTimeout?: number): filesystem.File
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
@@ -978,6 +994,8 @@ declare namespace $apis {
|
||||
let activityLogger: apis.activityLogger
|
||||
let requestInfo: apis.requestInfo
|
||||
let recordAuthResponse: apis.recordAuthResponse
|
||||
let gzip: middleware.gzip
|
||||
let bodyLimit: middleware.bodyLimit
|
||||
let enrichRecord: apis.enrichRecord
|
||||
let enrichRecords: apis.enrichRecords
|
||||
}
|
||||
@@ -986,6 +1004,12 @@ declare namespace $apis {
|
||||
// httpClientBinds
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// extra FormData overload to prevent TS warnings when used with non File/Blob value.
|
||||
interface FormData {
|
||||
append(key:string, value:any): void
|
||||
set(key:string, value:any): void
|
||||
}
|
||||
|
||||
/**
|
||||
* ` + "`" + `$http` + "`" + ` defines common methods for working with HTTP requests.
|
||||
*
|
||||
@@ -1000,7 +1024,7 @@ declare namespace $http {
|
||||
* ` + "```" + `js
|
||||
* const res = $http.send({
|
||||
* url: "https://example.com",
|
||||
* data: {"title": "test"}
|
||||
* body: JSON.stringify({"title": "test"})
|
||||
* method: "post",
|
||||
* })
|
||||
*
|
||||
@@ -1013,7 +1037,7 @@ declare namespace $http {
|
||||
*/
|
||||
function send(config: {
|
||||
url: string,
|
||||
body?: string,
|
||||
body?: string|FormData,
|
||||
method?: string, // default to "GET"
|
||||
headers?: { [key:string]: string },
|
||||
timeout?: number, // default to 120
|
||||
@@ -1053,6 +1077,7 @@ func main() {
|
||||
|
||||
gen := tygoja.New(tygoja.Config{
|
||||
Packages: map[string][]string{
|
||||
"github.com/labstack/echo/v5/middleware": {"Gzip", "BodyLimit"},
|
||||
"github.com/go-ozzo/ozzo-validation/v4": {"Error"},
|
||||
"github.com/pocketbase/dbx": {"*"},
|
||||
"github.com/pocketbase/pocketbase/tools/security": {"*"},
|
||||
|
||||
+19
-1
@@ -41,6 +41,11 @@ const (
|
||||
|
||||
// Config defines the config options of the jsvm plugin.
|
||||
type Config struct {
|
||||
// OnInit is an optional function that will be called
|
||||
// after a JS runtime is initialized, allowing you to
|
||||
// attach custom Go variables and functions.
|
||||
OnInit func(vm *goja.Runtime)
|
||||
|
||||
// HooksWatch enables auto app restarts when a JS app hook file changes.
|
||||
//
|
||||
// Note that currently the application cannot be automatically restarted on Windows
|
||||
@@ -90,7 +95,12 @@ type Config struct {
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// jsvm.MustRegister(app, jsvm.Config{})
|
||||
// jsvm.MustRegister(app, jsvm.Config{
|
||||
// OnInit: func(vm *goja.Runtime) {
|
||||
// // register custom bindings
|
||||
// vm.Set("myCustomVar", 123)
|
||||
// }
|
||||
// })
|
||||
func MustRegister(app core.App, config Config) {
|
||||
if err := Register(app, config); err != nil {
|
||||
panic(err)
|
||||
@@ -173,6 +183,10 @@ func (p *plugin) registerMigrations() error {
|
||||
m.AppMigrations.Register(up, down, file)
|
||||
})
|
||||
|
||||
if p.config.OnInit != nil {
|
||||
p.config.OnInit(vm)
|
||||
}
|
||||
|
||||
_, err := vm.RunString(string(content))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run migration %s: %w", file, err)
|
||||
@@ -253,6 +267,10 @@ func (p *plugin) registerHooks() error {
|
||||
vm.Set("$app", p.app)
|
||||
vm.Set("$template", templateRegistry)
|
||||
vm.Set("__hooks", absHooksDir)
|
||||
|
||||
if p.config.OnInit != nil {
|
||||
p.config.OnInit(vm)
|
||||
}
|
||||
}
|
||||
|
||||
// initiliaze the executor vms
|
||||
|
||||
@@ -532,19 +532,23 @@ func init() {
|
||||
collection.DeleteRule = nil
|
||||
|
||||
options := map[string]any{}
|
||||
json.Unmarshal([]byte(` + "`" + `{}` + "`" + `), &options)
|
||||
if err := json.Unmarshal([]byte(` + "`" + `{}` + "`" + `), &options); err != nil {
|
||||
return err
|
||||
}
|
||||
collection.SetOptions(options)
|
||||
|
||||
json.Unmarshal([]byte(` + "`" + `[
|
||||
if err := json.Unmarshal([]byte(` + "`" + `[
|
||||
"create index test1 on test456_update (f1_name)"
|
||||
]` + "`" + `), &collection.Indexes)
|
||||
]` + "`" + `), &collection.Indexes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// remove
|
||||
collection.Schema.RemoveField("f3_id")
|
||||
|
||||
// add
|
||||
new_f4_name := &schema.SchemaField{}
|
||||
json.Unmarshal([]byte(` + "`" + `{
|
||||
if err := json.Unmarshal([]byte(` + "`" + `{
|
||||
"system": false,
|
||||
"id": "f4_id",
|
||||
"name": "f4_name",
|
||||
@@ -557,12 +561,14 @@ func init() {
|
||||
"max": null,
|
||||
"pattern": ` + "\"` + \"`\" + `test backtick` + \"`\" + `123\"" + `
|
||||
}
|
||||
}` + "`" + `), new_f4_name)
|
||||
}` + "`" + `), new_f4_name); err != nil {
|
||||
return err
|
||||
}
|
||||
collection.Schema.AddField(new_f4_name)
|
||||
|
||||
// update
|
||||
edit_f2_name_new := &schema.SchemaField{}
|
||||
json.Unmarshal([]byte(` + "`" + `{
|
||||
if err := json.Unmarshal([]byte(` + "`" + `{
|
||||
"system": false,
|
||||
"id": "f2_id",
|
||||
"name": "f2_name_new",
|
||||
@@ -575,7 +581,9 @@ func init() {
|
||||
"max": null,
|
||||
"noDecimal": false
|
||||
}
|
||||
}` + "`" + `), edit_f2_name_new)
|
||||
}` + "`" + `), edit_f2_name_new); err != nil {
|
||||
return err
|
||||
}
|
||||
collection.Schema.AddField(edit_f2_name_new)
|
||||
|
||||
return dao.SaveCollection(collection)
|
||||
@@ -600,7 +608,7 @@ func init() {
|
||||
collection.DeleteRule = types.Pointer("id = \"3\"")
|
||||
|
||||
options := map[string]any{}
|
||||
json.Unmarshal([]byte(` + "`" + `{
|
||||
if err := json.Unmarshal([]byte(` + "`" + `{
|
||||
"allowEmailAuth": false,
|
||||
"allowOAuth2Auth": false,
|
||||
"allowUsernameAuth": false,
|
||||
@@ -610,16 +618,20 @@ func init() {
|
||||
"onlyEmailDomains": null,
|
||||
"onlyVerified": false,
|
||||
"requireEmail": false
|
||||
}` + "`" + `), &options)
|
||||
}` + "`" + `), &options); err != nil {
|
||||
return err
|
||||
}
|
||||
collection.SetOptions(options)
|
||||
|
||||
json.Unmarshal([]byte(` + "`" + `[
|
||||
if err := json.Unmarshal([]byte(` + "`" + `[
|
||||
"create index test1 on test456 (f1_name)"
|
||||
]` + "`" + `), &collection.Indexes)
|
||||
]` + "`" + `), &collection.Indexes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add
|
||||
del_f3_name := &schema.SchemaField{}
|
||||
json.Unmarshal([]byte(` + "`" + `{
|
||||
if err := json.Unmarshal([]byte(` + "`" + `{
|
||||
"system": false,
|
||||
"id": "f3_id",
|
||||
"name": "f3_name",
|
||||
@@ -628,7 +640,9 @@ func init() {
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
}` + "`" + `), del_f3_name)
|
||||
}` + "`" + `), del_f3_name); err != nil {
|
||||
return err
|
||||
}
|
||||
collection.Schema.AddField(del_f3_name)
|
||||
|
||||
// remove
|
||||
@@ -636,7 +650,7 @@ func init() {
|
||||
|
||||
// update
|
||||
edit_f2_name_new := &schema.SchemaField{}
|
||||
json.Unmarshal([]byte(` + "`" + `{
|
||||
if err := json.Unmarshal([]byte(` + "`" + `{
|
||||
"system": false,
|
||||
"id": "f2_id",
|
||||
"name": "f2_name",
|
||||
@@ -649,7 +663,9 @@ func init() {
|
||||
"max": null,
|
||||
"noDecimal": false
|
||||
}
|
||||
}` + "`" + `), edit_f2_name_new)
|
||||
}` + "`" + `), edit_f2_name_new); err != nil {
|
||||
return err
|
||||
}
|
||||
collection.Schema.AddField(edit_f2_name_new)
|
||||
|
||||
return dao.SaveCollection(collection)
|
||||
|
||||
@@ -598,11 +598,11 @@ func (p *plugin) goDiffTemplate(new *models.Collection, old *models.Collection)
|
||||
}
|
||||
if !bytes.Equal(rawNewOptions, rawOldOptions) {
|
||||
upParts = append(upParts, "options := map[string]any{}")
|
||||
upParts = append(upParts, fmt.Sprintf("json.Unmarshal([]byte(`%s`), &options)", escapeBacktick(string(rawNewOptions))))
|
||||
upParts = append(upParts, goErrIf(fmt.Sprintf("json.Unmarshal([]byte(`%s`), &options)", escapeBacktick(string(rawNewOptions)))))
|
||||
upParts = append(upParts, fmt.Sprintf("%s.SetOptions(options)\n", varName))
|
||||
// ---
|
||||
downParts = append(downParts, "options := map[string]any{}")
|
||||
downParts = append(downParts, fmt.Sprintf("json.Unmarshal([]byte(`%s`), &options)", escapeBacktick(string(rawOldOptions))))
|
||||
downParts = append(downParts, goErrIf(fmt.Sprintf("json.Unmarshal([]byte(`%s`), &options)", escapeBacktick(string(rawOldOptions)))))
|
||||
downParts = append(downParts, fmt.Sprintf("%s.SetOptions(options)\n", varName))
|
||||
}
|
||||
|
||||
@@ -616,9 +616,9 @@ func (p *plugin) goDiffTemplate(new *models.Collection, old *models.Collection)
|
||||
return "", err
|
||||
}
|
||||
if !bytes.Equal(rawNewIndexes, rawOldIndexes) {
|
||||
upParts = append(upParts, fmt.Sprintf("json.Unmarshal([]byte(`%s`), &%s.Indexes)\n", escapeBacktick(string(rawNewIndexes)), varName))
|
||||
upParts = append(upParts, goErrIf(fmt.Sprintf("json.Unmarshal([]byte(`%s`), &%s.Indexes)", escapeBacktick(string(rawNewIndexes)), varName))+"\n")
|
||||
// ---
|
||||
downParts = append(downParts, fmt.Sprintf("json.Unmarshal([]byte(`%s`), &%s.Indexes)\n", escapeBacktick(string(rawOldIndexes)), varName))
|
||||
downParts = append(downParts, goErrIf(fmt.Sprintf("json.Unmarshal([]byte(`%s`), &%s.Indexes)", escapeBacktick(string(rawOldIndexes)), varName))+"\n")
|
||||
}
|
||||
|
||||
// Schema
|
||||
@@ -641,7 +641,7 @@ func (p *plugin) goDiffTemplate(new *models.Collection, old *models.Collection)
|
||||
|
||||
downParts = append(downParts, "// add")
|
||||
downParts = append(downParts, fmt.Sprintf("%s := &schema.SchemaField{}", fieldVar))
|
||||
downParts = append(downParts, fmt.Sprintf("json.Unmarshal([]byte(`%s`), %s)", escapeBacktick(string(rawOldField)), fieldVar))
|
||||
downParts = append(downParts, goErrIf(fmt.Sprintf("json.Unmarshal([]byte(`%s`), %s)", escapeBacktick(string(rawOldField)), fieldVar)))
|
||||
downParts = append(downParts, fmt.Sprintf("%s.Schema.AddField(%s)\n", varName, fieldVar))
|
||||
}
|
||||
|
||||
@@ -660,7 +660,7 @@ func (p *plugin) goDiffTemplate(new *models.Collection, old *models.Collection)
|
||||
|
||||
upParts = append(upParts, "// add")
|
||||
upParts = append(upParts, fmt.Sprintf("%s := &schema.SchemaField{}", fieldVar))
|
||||
upParts = append(upParts, fmt.Sprintf("json.Unmarshal([]byte(`%s`), %s)", escapeBacktick(string(rawNewField)), fieldVar))
|
||||
upParts = append(upParts, goErrIf(fmt.Sprintf("json.Unmarshal([]byte(`%s`), %s)", escapeBacktick(string(rawNewField)), fieldVar)))
|
||||
upParts = append(upParts, fmt.Sprintf("%s.Schema.AddField(%s)\n", varName, fieldVar))
|
||||
|
||||
downParts = append(downParts, "// remove")
|
||||
@@ -692,12 +692,12 @@ func (p *plugin) goDiffTemplate(new *models.Collection, old *models.Collection)
|
||||
|
||||
upParts = append(upParts, "// update")
|
||||
upParts = append(upParts, fmt.Sprintf("%s := &schema.SchemaField{}", fieldVar))
|
||||
upParts = append(upParts, fmt.Sprintf("json.Unmarshal([]byte(`%s`), %s)", escapeBacktick(string(rawNewField)), fieldVar))
|
||||
upParts = append(upParts, goErrIf(fmt.Sprintf("json.Unmarshal([]byte(`%s`), %s)", escapeBacktick(string(rawNewField)), fieldVar)))
|
||||
upParts = append(upParts, fmt.Sprintf("%s.Schema.AddField(%s)\n", varName, fieldVar))
|
||||
|
||||
downParts = append(downParts, "// update")
|
||||
downParts = append(downParts, fmt.Sprintf("%s := &schema.SchemaField{}", fieldVar))
|
||||
downParts = append(downParts, fmt.Sprintf("json.Unmarshal([]byte(`%s`), %s)", escapeBacktick(string(rawOldField)), fieldVar))
|
||||
downParts = append(downParts, goErrIf(fmt.Sprintf("json.Unmarshal([]byte(`%s`), %s)", escapeBacktick(string(rawOldField)), fieldVar)))
|
||||
downParts = append(downParts, fmt.Sprintf("%s.Schema.AddField(%s)\n", varName, fieldVar))
|
||||
}
|
||||
// ---------------------------------------------------------------
|
||||
@@ -791,3 +791,7 @@ func marhshalWithoutEscape(v any, prefix string, indent string) ([]byte, error)
|
||||
func escapeBacktick(v string) string {
|
||||
return strings.ReplaceAll(v, "`", "` + \"`\" + `")
|
||||
}
|
||||
|
||||
func goErrIf(v string) string {
|
||||
return "if err := " + v + "; err != nil {\n\t\t\treturn err\n\t\t}"
|
||||
}
|
||||
|
||||
@@ -3,18 +3,23 @@ package resolvers
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/models/schema"
|
||||
"github.com/pocketbase/pocketbase/tools/dbutils"
|
||||
"github.com/pocketbase/pocketbase/tools/inflector"
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
"github.com/pocketbase/pocketbase/tools/search"
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
)
|
||||
|
||||
// maxNestedRels defines the max allowed nested relations depth.
|
||||
const maxNestedRels = 6
|
||||
|
||||
// parseAndRun starts a new one-off RecordFieldResolver.Resolve execution.
|
||||
func parseAndRun(fieldName string, resolver *RecordFieldResolver) (*search.ResolverResult, error) {
|
||||
r := &runner{
|
||||
@@ -90,9 +95,9 @@ func (r *runner) run() (*search.ResolverResult, error) {
|
||||
return r.processRequestInfoRelationField(dataField)
|
||||
}
|
||||
|
||||
// check for select:each field
|
||||
if modifier == eachModifier && dataField.Type == schema.FieldTypeSelect && len(r.activeProps) == 3 {
|
||||
return r.processRequestInfoSelectEachModifier(dataField)
|
||||
// check for data arrayble fields ":each" modifier
|
||||
if modifier == eachModifier && list.ExistInSlice(dataField.Type, schema.ArraybleFieldTypes()) && len(r.activeProps) == 3 {
|
||||
return r.processRequestInfoEachModifier(dataField)
|
||||
}
|
||||
|
||||
// check for data arrayble fields ":length" modifier
|
||||
@@ -235,22 +240,22 @@ func (r *runner) processRequestInfoLengthModifier(dataField *schema.SchemaField)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *runner) processRequestInfoSelectEachModifier(dataField *schema.SchemaField) (*search.ResolverResult, error) {
|
||||
options, ok := dataField.Options.(*schema.SelectOptions)
|
||||
func (r *runner) processRequestInfoEachModifier(dataField *schema.SchemaField) (*search.ResolverResult, error) {
|
||||
options, ok := dataField.Options.(schema.MultiValuer)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to initialize field %q options", dataField.Name)
|
||||
return nil, fmt.Errorf("field %q options are not initialized or doesn't support multivaluer operations", dataField.Name)
|
||||
}
|
||||
|
||||
dataItems := list.ToUniqueStringSlice(r.resolver.requestInfo.Data[dataField.Name])
|
||||
rawJson, err := json.Marshal(dataItems)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot marshalize the data select item for field %q", r.activeProps[2])
|
||||
return nil, fmt.Errorf("cannot serialize the data for field %q", r.activeProps[2])
|
||||
}
|
||||
|
||||
placeholder := "dataSelect" + security.PseudorandomString(4)
|
||||
placeholder := "dataEach" + security.PseudorandomString(4)
|
||||
cleanFieldName := inflector.Columnify(dataField.Name)
|
||||
jeTable := fmt.Sprintf("json_each({:%s})", placeholder)
|
||||
jeAlias := "__dataSelect_" + cleanFieldName + "_je"
|
||||
jeAlias := "__dataEach_" + cleanFieldName + "_je"
|
||||
r.resolver.registerJoin(jeTable, jeAlias, nil)
|
||||
|
||||
result := &search.ResolverResult{
|
||||
@@ -258,7 +263,7 @@ func (r *runner) processRequestInfoSelectEachModifier(dataField *schema.SchemaFi
|
||||
Params: dbx.Params{placeholder: rawJson},
|
||||
}
|
||||
|
||||
if options.MaxSelect != 1 {
|
||||
if options.IsMultiple() {
|
||||
r.withMultiMatch = true
|
||||
}
|
||||
|
||||
@@ -300,30 +305,33 @@ func (r *runner) processRequestInfoRelationField(dataField *schema.SchemaField)
|
||||
}
|
||||
|
||||
r.activeCollectionName = dataRelCollection.Name
|
||||
r.activeTableAlias = inflector.Columnify("__data_" + dataRelCollection.Name)
|
||||
r.activeTableAlias = inflector.Columnify("__data_" + dataRelCollection.Name + "_" + dataField.Name)
|
||||
|
||||
// join the data rel collection to the main collection
|
||||
r.resolver.registerJoin(
|
||||
inflector.Columnify(r.activeCollectionName),
|
||||
r.activeCollectionName,
|
||||
r.activeTableAlias,
|
||||
dbx.In(
|
||||
fmt.Sprintf("[[%s.id]]", inflector.Columnify(r.activeTableAlias)),
|
||||
fmt.Sprintf("[[%s.id]]", r.activeTableAlias),
|
||||
list.ToInterfaceSlice(dataRelIds)...,
|
||||
),
|
||||
)
|
||||
|
||||
if options.MaxSelect == nil || *options.MaxSelect != 1 {
|
||||
if options.IsMultiple() {
|
||||
r.withMultiMatch = true
|
||||
}
|
||||
|
||||
// join the data rel collection to the multi-match subquery
|
||||
r.multiMatchActiveTableAlias = inflector.Columnify("__data_mm_" + dataRelCollection.Name)
|
||||
r.multiMatchActiveTableAlias = inflector.Columnify("__data_mm_" + dataRelCollection.Name + "_" + dataField.Name)
|
||||
r.multiMatch.joins = append(
|
||||
r.multiMatch.joins,
|
||||
&join{
|
||||
tableName: inflector.Columnify(r.activeCollectionName),
|
||||
tableName: r.activeCollectionName,
|
||||
tableAlias: r.multiMatchActiveTableAlias,
|
||||
on: dbx.In(r.multiMatchActiveTableAlias+".id", list.ToInterfaceSlice(dataRelIds)...),
|
||||
on: dbx.In(
|
||||
fmt.Sprintf("[[%s.id]]", r.multiMatchActiveTableAlias),
|
||||
list.ToInterfaceSlice(dataRelIds)...,
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -334,6 +342,8 @@ func (r *runner) processRequestInfoRelationField(dataField *schema.SchemaField)
|
||||
return r.processActiveProps()
|
||||
}
|
||||
|
||||
var viaRegex = regexp.MustCompile(`^(\w+)_via_(\w+)$`)
|
||||
|
||||
func (r *runner) processActiveProps() (*search.ResolverResult, error) {
|
||||
totalProps := len(r.activeProps)
|
||||
|
||||
@@ -387,42 +397,41 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
|
||||
|
||||
cleanFieldName := inflector.Columnify(field.Name)
|
||||
|
||||
// arrayble fields ":length" modifier
|
||||
// arrayable fields with ":length" modifier
|
||||
// -------------------------------------------------------
|
||||
if modifier == lengthModifier && list.ExistInSlice(field.Type, schema.ArraybleFieldTypes()) {
|
||||
jePair := r.activeTableAlias + "." + cleanFieldName
|
||||
|
||||
result := &search.ResolverResult{
|
||||
Identifier: jsonArrayLength(jePair),
|
||||
Identifier: dbutils.JsonArrayLength(jePair),
|
||||
}
|
||||
|
||||
if r.withMultiMatch {
|
||||
jePair2 := r.multiMatchActiveTableAlias + "." + cleanFieldName
|
||||
r.multiMatch.valueIdentifier = jsonArrayLength(jePair2)
|
||||
r.multiMatch.valueIdentifier = dbutils.JsonArrayLength(jePair2)
|
||||
result.MultiMatchSubQuery = r.multiMatch
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// select field with ":each" modifier
|
||||
// arrayable fields with ":each" modifier
|
||||
// -------------------------------------------------------
|
||||
if field.Type == schema.FieldTypeSelect && modifier == eachModifier {
|
||||
if modifier == eachModifier && list.ExistInSlice(field.Type, schema.ArraybleFieldTypes()) {
|
||||
jePair := r.activeTableAlias + "." + cleanFieldName
|
||||
jeAlias := r.activeTableAlias + "_" + cleanFieldName + "_je"
|
||||
r.resolver.registerJoin(jsonEach(jePair), jeAlias, nil)
|
||||
r.resolver.registerJoin(dbutils.JsonEach(jePair), jeAlias, nil)
|
||||
|
||||
result := &search.ResolverResult{
|
||||
Identifier: fmt.Sprintf("[[%s.value]]", jeAlias),
|
||||
}
|
||||
|
||||
field.InitOptions()
|
||||
options, ok := field.Options.(*schema.SelectOptions)
|
||||
options, ok := field.Options.(schema.MultiValuer)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to initialize field %q options", prop)
|
||||
return nil, fmt.Errorf("field %q options are not initialized or doesn't multivaluer arrayable operations", prop)
|
||||
}
|
||||
|
||||
if options.MaxSelect != 1 {
|
||||
if options.IsMultiple() {
|
||||
r.withMultiMatch = true
|
||||
}
|
||||
|
||||
@@ -431,7 +440,7 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
|
||||
jeAlias2 := r.multiMatchActiveTableAlias + "_" + cleanFieldName + "_je"
|
||||
|
||||
r.multiMatch.joins = append(r.multiMatch.joins, &join{
|
||||
tableName: jsonEach(jePair2),
|
||||
tableName: dbutils.JsonEach(jePair2),
|
||||
tableAlias: jeAlias2,
|
||||
})
|
||||
r.multiMatch.valueIdentifier = fmt.Sprintf("[[%s.value]]", jeAlias2)
|
||||
@@ -458,9 +467,9 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
|
||||
// (https://github.com/pocketbase/pocketbase/issues/4068)
|
||||
if field.Type == schema.FieldTypeJson {
|
||||
result.NoCoalesce = true
|
||||
result.Identifier = jsonExtract(r.activeTableAlias+"."+cleanFieldName, "")
|
||||
result.Identifier = dbutils.JsonExtract(r.activeTableAlias+"."+cleanFieldName, "")
|
||||
if r.withMultiMatch {
|
||||
r.multiMatch.valueIdentifier = jsonExtract(r.multiMatchActiveTableAlias+"."+cleanFieldName, "")
|
||||
r.multiMatch.valueIdentifier = dbutils.JsonExtract(r.multiMatchActiveTableAlias+"."+cleanFieldName, "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,23 +477,19 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
|
||||
}
|
||||
|
||||
field := collection.Schema.GetFieldByName(prop)
|
||||
if field == nil {
|
||||
if r.nullifyMisingField {
|
||||
return &search.ResolverResult{Identifier: "NULL"}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown field %q", prop)
|
||||
}
|
||||
|
||||
// check if it is a json field
|
||||
if field.Type == schema.FieldTypeJson {
|
||||
// json field -> treat the rest of the props as json path
|
||||
if field != nil && field.Type == schema.FieldTypeJson {
|
||||
var jsonPath strings.Builder
|
||||
for _, p := range r.activeProps[i+1:] {
|
||||
for j, p := range r.activeProps[i+1:] {
|
||||
if _, err := strconv.Atoi(p); err == nil {
|
||||
jsonPath.WriteString("[")
|
||||
jsonPath.WriteString(inflector.Columnify(p))
|
||||
jsonPath.WriteString("]")
|
||||
} else {
|
||||
jsonPath.WriteString(".")
|
||||
if j > 0 {
|
||||
jsonPath.WriteString(".")
|
||||
}
|
||||
jsonPath.WriteString(inflector.Columnify(p))
|
||||
}
|
||||
}
|
||||
@@ -492,18 +497,141 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
|
||||
|
||||
result := &search.ResolverResult{
|
||||
NoCoalesce: true,
|
||||
Identifier: jsonExtract(r.activeTableAlias+"."+inflector.Columnify(prop), jsonPathStr),
|
||||
Identifier: dbutils.JsonExtract(r.activeTableAlias+"."+inflector.Columnify(prop), jsonPathStr),
|
||||
}
|
||||
|
||||
if r.withMultiMatch {
|
||||
r.multiMatch.valueIdentifier = jsonExtract(r.multiMatchActiveTableAlias+"."+inflector.Columnify(prop), jsonPathStr)
|
||||
r.multiMatch.valueIdentifier = dbutils.JsonExtract(r.multiMatchActiveTableAlias+"."+inflector.Columnify(prop), jsonPathStr)
|
||||
result.MultiMatchSubQuery = r.multiMatch
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// check if it is a relation field
|
||||
if i >= maxNestedRels {
|
||||
return nil, fmt.Errorf("max nested relations reached for field %q", prop)
|
||||
}
|
||||
|
||||
// check for back relation (eg. yourCollection_via_yourRelField)
|
||||
// -----------------------------------------------------------
|
||||
if field == nil {
|
||||
parts := viaRegex.FindStringSubmatch(prop)
|
||||
if len(parts) != 3 {
|
||||
if r.nullifyMisingField {
|
||||
return &search.ResolverResult{Identifier: "NULL"}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("failed to resolve field %q", prop)
|
||||
}
|
||||
|
||||
backCollection, err := r.resolver.loadCollection(parts[1])
|
||||
if err != nil {
|
||||
if r.nullifyMisingField {
|
||||
return &search.ResolverResult{Identifier: "NULL"}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("failed to load back relation field %q collection", prop)
|
||||
}
|
||||
|
||||
backField := backCollection.Schema.GetFieldByName(parts[2])
|
||||
if backField == nil {
|
||||
if r.nullifyMisingField {
|
||||
return &search.ResolverResult{Identifier: "NULL"}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("missing back relation field %q", parts[2])
|
||||
}
|
||||
if backField.Type != schema.FieldTypeRelation {
|
||||
return nil, fmt.Errorf("invalid back relation field %q", parts[2])
|
||||
}
|
||||
|
||||
backField.InitOptions()
|
||||
backFieldOptions, ok := backField.Options.(*schema.RelationOptions)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to initialize back relation field %q options", backField.Name)
|
||||
}
|
||||
if backFieldOptions.CollectionId != collection.Id {
|
||||
return nil, fmt.Errorf("invalid back relation field %q collection reference", backField.Name)
|
||||
}
|
||||
|
||||
// join the back relation to the main query
|
||||
// ---
|
||||
cleanProp := inflector.Columnify(prop)
|
||||
cleanBackFieldName := inflector.Columnify(backField.Name)
|
||||
newTableAlias := r.activeTableAlias + "_" + cleanProp
|
||||
newCollectionName := inflector.Columnify(backCollection.Name)
|
||||
|
||||
isBackRelMultiple := backFieldOptions.IsMultiple()
|
||||
if !isBackRelMultiple {
|
||||
// additionally check if the rel field has a single column unique index
|
||||
isBackRelMultiple = !dbutils.HasSingleColumnUniqueIndex(backField.Name, backCollection.Indexes)
|
||||
}
|
||||
|
||||
if !isBackRelMultiple {
|
||||
r.resolver.registerJoin(
|
||||
newCollectionName,
|
||||
newTableAlias,
|
||||
dbx.NewExp(fmt.Sprintf("[[%s.%s]] = [[%s.id]]", newTableAlias, cleanBackFieldName, r.activeTableAlias)),
|
||||
)
|
||||
} else {
|
||||
jeAlias := r.activeTableAlias + "_" + cleanProp + "_je"
|
||||
r.resolver.registerJoin(
|
||||
newCollectionName,
|
||||
newTableAlias,
|
||||
dbx.NewExp(fmt.Sprintf(
|
||||
"[[%s.id]] IN (SELECT [[%s.value]] FROM %s {{%s}})",
|
||||
r.activeTableAlias,
|
||||
jeAlias,
|
||||
dbutils.JsonEach(newTableAlias+"."+cleanBackFieldName),
|
||||
jeAlias,
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
r.activeCollectionName = newCollectionName
|
||||
r.activeTableAlias = newTableAlias
|
||||
// ---
|
||||
|
||||
// join the back relation to the multi-match subquery
|
||||
// ---
|
||||
if isBackRelMultiple {
|
||||
r.withMultiMatch = true // enable multimatch if not already
|
||||
}
|
||||
|
||||
newTableAlias2 := r.multiMatchActiveTableAlias + "_" + cleanProp
|
||||
|
||||
if !isBackRelMultiple {
|
||||
r.multiMatch.joins = append(
|
||||
r.multiMatch.joins,
|
||||
&join{
|
||||
tableName: newCollectionName,
|
||||
tableAlias: newTableAlias2,
|
||||
on: dbx.NewExp(fmt.Sprintf("[[%s.%s]] = [[%s.id]]", newTableAlias2, cleanBackFieldName, r.multiMatchActiveTableAlias)),
|
||||
},
|
||||
)
|
||||
} else {
|
||||
jeAlias2 := r.multiMatchActiveTableAlias + "_" + cleanProp + "_je"
|
||||
r.multiMatch.joins = append(
|
||||
r.multiMatch.joins,
|
||||
&join{
|
||||
tableName: newCollectionName,
|
||||
tableAlias: newTableAlias2,
|
||||
on: dbx.NewExp(fmt.Sprintf(
|
||||
"[[%s.id]] IN (SELECT [[%s.value]] FROM %s {{%s}})",
|
||||
r.multiMatchActiveTableAlias,
|
||||
jeAlias2,
|
||||
dbutils.JsonEach(newTableAlias2+"."+cleanBackFieldName),
|
||||
jeAlias2,
|
||||
)),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
r.multiMatchActiveTableAlias = newTableAlias2
|
||||
// ---
|
||||
|
||||
continue
|
||||
}
|
||||
// -----------------------------------------------------------
|
||||
|
||||
// check for direct relation
|
||||
if field.Type != schema.FieldTypeRelation {
|
||||
return nil, fmt.Errorf("field %q is not a valid relation", prop)
|
||||
}
|
||||
@@ -518,7 +646,7 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
|
||||
|
||||
relCollection, relErr := r.resolver.loadCollection(options.CollectionId)
|
||||
if relErr != nil {
|
||||
return nil, fmt.Errorf("failed to find field %q collection", prop)
|
||||
return nil, fmt.Errorf("failed to load field %q collection", prop)
|
||||
}
|
||||
|
||||
cleanFieldName := inflector.Columnify(field.Name)
|
||||
@@ -534,7 +662,7 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
|
||||
)
|
||||
} else {
|
||||
jeAlias := r.activeTableAlias + "_" + cleanFieldName + "_je"
|
||||
r.resolver.registerJoin(jsonEach(prefixedFieldName), jeAlias, nil)
|
||||
r.resolver.registerJoin(dbutils.JsonEach(prefixedFieldName), jeAlias, nil)
|
||||
r.resolver.registerJoin(
|
||||
inflector.Columnify(newCollectionName),
|
||||
newTableAlias,
|
||||
@@ -549,7 +677,7 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
|
||||
// join the relation to the multi-match subquery
|
||||
// ---
|
||||
if options.IsMultiple() {
|
||||
r.withMultiMatch = true
|
||||
r.withMultiMatch = true // enable multimatch if not already
|
||||
}
|
||||
|
||||
newTableAlias2 := r.multiMatchActiveTableAlias + "_" + cleanFieldName
|
||||
@@ -569,7 +697,7 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
|
||||
r.multiMatch.joins = append(
|
||||
r.multiMatch.joins,
|
||||
&join{
|
||||
tableName: jsonEach(prefixedFieldName2),
|
||||
tableName: dbutils.JsonEach(prefixedFieldName2),
|
||||
tableAlias: jeAlias2,
|
||||
},
|
||||
&join{
|
||||
@@ -587,34 +715,6 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
|
||||
return nil, fmt.Errorf("failed to resolve field %q", r.fieldName)
|
||||
}
|
||||
|
||||
func jsonArrayLength(tableColumnPair string) string {
|
||||
return fmt.Sprintf(
|
||||
// note: the case is used to normalize value access for single and multiple relations.
|
||||
`json_array_length(CASE WHEN json_valid([[%s]]) THEN [[%s]] ELSE (CASE WHEN [[%s]] = '' OR [[%s]] IS NULL THEN json_array() ELSE json_array([[%s]]) END) END)`,
|
||||
tableColumnPair, tableColumnPair, tableColumnPair, tableColumnPair, tableColumnPair,
|
||||
)
|
||||
}
|
||||
|
||||
func jsonEach(tableColumnPair string) string {
|
||||
return fmt.Sprintf(
|
||||
// note: the case is used to normalize value access for single and multiple relations.
|
||||
`json_each(CASE WHEN json_valid([[%s]]) THEN [[%s]] ELSE json_array([[%s]]) END)`,
|
||||
tableColumnPair, tableColumnPair, tableColumnPair,
|
||||
)
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ func NewRecordFieldResolver(
|
||||
loadedCollections: []*models.Collection{baseCollection},
|
||||
allowedFields: []string{
|
||||
`^\w+[\w\.\:]*$`,
|
||||
`^\@request\.context$`,
|
||||
`^\@request\.method$`,
|
||||
`^\@request\.auth\.[\w\.\:]*\w+$`,
|
||||
`^\@request\.data\.[\w\.\:]*\w+$`,
|
||||
@@ -100,15 +101,17 @@ func NewRecordFieldResolver(
|
||||
|
||||
r.staticRequestInfo = map[string]any{}
|
||||
if r.requestInfo != nil {
|
||||
r.staticRequestInfo["context"] = r.requestInfo.Context
|
||||
r.staticRequestInfo["method"] = r.requestInfo.Method
|
||||
r.staticRequestInfo["query"] = r.requestInfo.Query
|
||||
r.staticRequestInfo["headers"] = r.requestInfo.Headers
|
||||
r.staticRequestInfo["data"] = r.requestInfo.Data
|
||||
r.staticRequestInfo["auth"] = nil
|
||||
if r.requestInfo.AuthRecord != nil {
|
||||
r.requestInfo.AuthRecord.IgnoreEmailVisibility(true)
|
||||
r.staticRequestInfo["auth"] = r.requestInfo.AuthRecord.PublicExport()
|
||||
r.requestInfo.AuthRecord.IgnoreEmailVisibility(false)
|
||||
authData := r.requestInfo.AuthRecord.PublicExport()
|
||||
// always add the record email no matter of the emailVisibility field
|
||||
authData[schema.FieldNameEmail] = r.requestInfo.AuthRecord.Email()
|
||||
r.staticRequestInfo["auth"] = authData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +144,9 @@ func (r *RecordFieldResolver) UpdateQuery(query *dbx.SelectQuery) error {
|
||||
// id
|
||||
// someSelect.each
|
||||
// project.screen.status
|
||||
// @request.status
|
||||
// screen.project_via_prototype.name
|
||||
// @request.context
|
||||
// @request.method
|
||||
// @request.query.filter
|
||||
// @request.headers.x_token
|
||||
// @request.auth.someRelation.name
|
||||
|
||||
File diff suppressed because one or more lines are too long
+50
-4
@@ -9,8 +9,12 @@ import (
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/migrations"
|
||||
"github.com/pocketbase/pocketbase/migrations/logs"
|
||||
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||
"github.com/pocketbase/pocketbase/tools/migrate"
|
||||
)
|
||||
|
||||
// TestApp is a wrapper app instance used for testing.
|
||||
@@ -36,20 +40,27 @@ type TestApp struct {
|
||||
//
|
||||
// After this call, the app instance shouldn't be used anymore.
|
||||
func (t *TestApp) Cleanup() {
|
||||
t.ResetEventCalls()
|
||||
t.ResetBootstrapState()
|
||||
t.OnTerminate().Trigger(&core.TerminateEvent{App: t}, func(e *core.TerminateEvent) error {
|
||||
t.TestMailer.Reset()
|
||||
t.ResetEventCalls()
|
||||
t.ResetBootstrapState()
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if t.DataDir() != "" {
|
||||
os.RemoveAll(t.DataDir())
|
||||
}
|
||||
}
|
||||
|
||||
// NewMailClient initializes test app mail client.
|
||||
// NewMailClient initializes (if not already) a test app mail client.
|
||||
func (t *TestApp) NewMailClient() mailer.Mailer {
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
|
||||
t.TestMailer.Reset()
|
||||
if t.TestMailer == nil {
|
||||
t.TestMailer = &TestMailer{}
|
||||
}
|
||||
|
||||
return t.TestMailer
|
||||
}
|
||||
@@ -112,6 +123,11 @@ func NewTestApp(optTestDataDir ...string) (*TestApp, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// apply any missing migrations
|
||||
if err := runMigrations(app); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// force disable request logs because the logs db call execute in a separate
|
||||
// go routine and it is possible to panic due to earlier api test completion.
|
||||
app.Settings().Logs.MaxDays = 0
|
||||
@@ -542,3 +558,33 @@ func copyFile(src string, dest string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// @todo replace with app.RunMigrations on merge with the refactoring.
|
||||
func runMigrations(app core.App) error {
|
||||
connections := []struct {
|
||||
db *dbx.DB
|
||||
migrationsList migrate.MigrationsList
|
||||
}{
|
||||
{
|
||||
db: app.DB(),
|
||||
migrationsList: migrations.AppMigrations,
|
||||
},
|
||||
{
|
||||
db: app.LogsDB(),
|
||||
migrationsList: logs.LogsMigrations,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range connections {
|
||||
runner, err := migrate.NewRunner(c.db, c.migrationsList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := runner.Up(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Binary file not shown.
+11
-6
@@ -10,18 +10,23 @@ var _ mailer.Mailer = (*TestMailer)(nil)
|
||||
type TestMailer struct {
|
||||
TotalSend int
|
||||
LastMessage mailer.Message
|
||||
|
||||
// @todo consider deprecating the above 2 fields?
|
||||
SentMessages []mailer.Message
|
||||
}
|
||||
|
||||
// Reset clears any previously test collected data.
|
||||
func (m *TestMailer) Reset() {
|
||||
m.TotalSend = 0
|
||||
m.LastMessage = mailer.Message{}
|
||||
func (tm *TestMailer) Reset() {
|
||||
tm.TotalSend = 0
|
||||
tm.LastMessage = mailer.Message{}
|
||||
tm.SentMessages = nil
|
||||
}
|
||||
|
||||
// Send implements `mailer.Mailer` interface.
|
||||
func (c *TestMailer) Send(m *mailer.Message) error {
|
||||
c.TotalSend++
|
||||
c.LastMessage = *m
|
||||
func (tm *TestMailer) Send(m *mailer.Message) error {
|
||||
tm.TotalSend++
|
||||
tm.LastMessage = *m
|
||||
tm.SentMessages = append(tm.SentMessages, tm.LastMessage)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func TestCreateSuccess(t *testing.T) {
|
||||
t.Fatalf("Expected zip with name %q, got %q", zipName, name)
|
||||
}
|
||||
|
||||
expectedSize := int64(405)
|
||||
expectedSize := int64(544)
|
||||
if size := info.Size(); size != expectedSize {
|
||||
t.Fatalf("Expected zip with size %d, got %d", expectedSize, size)
|
||||
}
|
||||
@@ -116,5 +116,10 @@ func createTestDir(t *testing.T) string {
|
||||
f.Close()
|
||||
}
|
||||
|
||||
// symbolic link
|
||||
if err := os.Symlink(filepath.Join(dir, "test"), filepath.Join(dir, "test_symlink")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return dir
|
||||
}
|
||||
|
||||
@@ -9,7 +9,11 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Extract extracts the zip archive at src to dest.
|
||||
// Extract extracts the zip archive at "src" to "dest".
|
||||
//
|
||||
// Note that only dirs and regular files will be extracted.
|
||||
// Symbolic links, named pipes, sockets, or any other irregular files
|
||||
// are skipped because they come with too many edge cases and ambiguities.
|
||||
func Extract(src, dest string) error {
|
||||
zr, err := zip.OpenReader(src)
|
||||
if err != nil {
|
||||
@@ -46,11 +50,12 @@ func extractFile(zipFile *zip.File, basePath string) error {
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
// allow only dirs or regular files
|
||||
if zipFile.FileInfo().IsDir() {
|
||||
if err := os.MkdirAll(path, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
} else if zipFile.FileInfo().Mode().IsRegular() {
|
||||
// ensure that the file path directories are created
|
||||
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
|
||||
return err
|
||||
|
||||
@@ -37,7 +37,7 @@ func TestExtractSuccess(t *testing.T) {
|
||||
defer os.RemoveAll(extractedPath)
|
||||
|
||||
// zip testDir content (with exclude)
|
||||
if err := archive.Create(testDir, zipPath, "a/b/c", "test", "sub2"); err != nil {
|
||||
if err := archive.Create(testDir, zipPath, "a/b/c", "test2", "sub2"); err != nil {
|
||||
t.Fatalf("Failed to create archive: %v", err)
|
||||
}
|
||||
|
||||
@@ -64,8 +64,9 @@ func TestExtractSuccess(t *testing.T) {
|
||||
t.Fatalf("Failed to read the extracted dir: %v", walkErr)
|
||||
}
|
||||
|
||||
// (note: symbolic links and other regular files should be missing)
|
||||
expectedFiles := []string{
|
||||
filepath.Join(extractedPath, "test2"),
|
||||
filepath.Join(extractedPath, "test"),
|
||||
filepath.Join(extractedPath, "a/test"),
|
||||
filepath.Join(extractedPath, "a/b/sub1"),
|
||||
}
|
||||
|
||||
+5
-1
@@ -24,7 +24,7 @@ type AuthUser struct {
|
||||
|
||||
// Provider defines a common interface for an OAuth2 client.
|
||||
type Provider interface {
|
||||
// Scopes returns the context associated with the provider (if any).
|
||||
// Context returns the context associated with the provider (if any).
|
||||
Context() context.Context
|
||||
|
||||
// SetContext assigns the specified context to the current provider.
|
||||
@@ -154,6 +154,10 @@ func NewProviderByName(name string) (Provider, error) {
|
||||
return NewPatreonProvider(), nil
|
||||
case NameMailcow:
|
||||
return NewMailcowProvider(), nil
|
||||
case NameBitbucket:
|
||||
return NewBitbucketProvider(), nil
|
||||
case NamePlanningcenter:
|
||||
return NewPlanningcenterProvider(), nil
|
||||
default:
|
||||
return nil, errors.New("Missing provider " + name)
|
||||
}
|
||||
|
||||
@@ -225,4 +225,22 @@ func TestNewProviderByName(t *testing.T) {
|
||||
if _, ok := p.(*auth.Mailcow); !ok {
|
||||
t.Error("Expected to be instance of *auth.Mailcow")
|
||||
}
|
||||
|
||||
// bitbucket
|
||||
p, err = auth.NewProviderByName(auth.NameBitbucket)
|
||||
if err != nil {
|
||||
t.Errorf("Expected nil, got error %v", err)
|
||||
}
|
||||
if _, ok := p.(*auth.Bitbucket); !ok {
|
||||
t.Error("Expected to be instance of *auth.Bitbucket")
|
||||
}
|
||||
|
||||
// planningcenter
|
||||
p, err = auth.NewProviderByName(auth.NamePlanningcenter)
|
||||
if err != nil {
|
||||
t.Errorf("Expected nil, got error %v", err)
|
||||
}
|
||||
if _, ok := p.(*auth.Planningcenter); !ok {
|
||||
t.Error("Expected to be instance of *auth.Planningcenter")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var _ Provider = (*Bitbucket)(nil)
|
||||
|
||||
// NameBitbucket is the unique name of the Bitbucket provider.
|
||||
const NameBitbucket = "bitbucket"
|
||||
|
||||
// Bitbucket is an auth provider for Bitbucket.
|
||||
type Bitbucket struct {
|
||||
*baseProvider
|
||||
}
|
||||
|
||||
// NewBitbucketProvider creates a new Bitbucket provider instance with some defaults.
|
||||
func NewBitbucketProvider() *Bitbucket {
|
||||
return &Bitbucket{&baseProvider{
|
||||
ctx: context.Background(),
|
||||
displayName: "Bitbucket",
|
||||
pkce: false,
|
||||
scopes: []string{"account"},
|
||||
authUrl: "https://bitbucket.org/site/oauth2/authorize",
|
||||
tokenUrl: "https://bitbucket.org/site/oauth2/access_token",
|
||||
userApiUrl: "https://api.bitbucket.org/2.0/user",
|
||||
}}
|
||||
}
|
||||
|
||||
// FetchAuthUser returns an AuthUser instance based on the Bitbucket's user API.
|
||||
//
|
||||
// API reference: https://developer.atlassian.com/cloud/bitbucket/rest/api-group-users/#api-user-get
|
||||
func (p *Bitbucket) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
||||
data, err := p.FetchRawUserData(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawUser := map[string]any{}
|
||||
if err := json.Unmarshal(data, &rawUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extracted := struct {
|
||||
UUID string `json:"uuid"`
|
||||
Username string `json:"username"`
|
||||
DisplayName string `json:"display_name"`
|
||||
AccountStatus string `json:"account_status"`
|
||||
Links struct {
|
||||
Avatar struct {
|
||||
Href string `json:"href"`
|
||||
} `json:"avatar"`
|
||||
} `json:"links"`
|
||||
}{}
|
||||
if err := json.Unmarshal(data, &extracted); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if extracted.AccountStatus != "active" {
|
||||
return nil, errors.New("the Bitbucket user is not active")
|
||||
}
|
||||
|
||||
email, err := p.fetchPrimaryEmail(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := &AuthUser{
|
||||
Id: extracted.UUID,
|
||||
Name: extracted.DisplayName,
|
||||
Username: extracted.Username,
|
||||
Email: email,
|
||||
AvatarUrl: extracted.Links.Avatar.Href,
|
||||
RawUser: rawUser,
|
||||
AccessToken: token.AccessToken,
|
||||
RefreshToken: token.RefreshToken,
|
||||
}
|
||||
|
||||
user.Expiry, _ = types.ParseDateTime(token.Expiry)
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// fetchPrimaryEmail sends an API request to retrieve the first
|
||||
// verified primary email.
|
||||
//
|
||||
// NB! This method can succeed and still return an empty email.
|
||||
// Error responses that are result of insufficient scopes permissions are ignored.
|
||||
//
|
||||
// API reference: https://developer.atlassian.com/cloud/bitbucket/rest/api-group-users/#api-user-emails-get
|
||||
func (p *Bitbucket) fetchPrimaryEmail(token *oauth2.Token) (string, error) {
|
||||
response, err := p.Client(token).Get(p.userApiUrl + "/emails")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
// ignore common http errors caused by insufficient scope permissions
|
||||
// (the email field is optional, aka. return the auth user without it)
|
||||
if response.StatusCode >= 400 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
expected := struct {
|
||||
Values []struct {
|
||||
Email string `json:"email"`
|
||||
IsPrimary bool `json:"is_primary"`
|
||||
} `json:"values"`
|
||||
}{}
|
||||
if err := json.Unmarshal(data, &expected); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, v := range expected.Values {
|
||||
if v.IsPrimary {
|
||||
return v.Email, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var _ Provider = (*Planningcenter)(nil)
|
||||
|
||||
// NamePlanningcenter is the unique name of the Planningcenter provider.
|
||||
const NamePlanningcenter string = "planningcenter"
|
||||
|
||||
// Planningcenter allows authentication via Planningcenter OAuth2.
|
||||
type Planningcenter struct {
|
||||
*baseProvider
|
||||
}
|
||||
|
||||
// NewPlanningcenterProvider creates a new Planningcenter provider instance with some defaults.
|
||||
func NewPlanningcenterProvider() *Planningcenter {
|
||||
return &Planningcenter{&baseProvider{
|
||||
ctx: context.Background(),
|
||||
displayName: "Planning Center",
|
||||
pkce: true,
|
||||
scopes: []string{"people"},
|
||||
authUrl: "https://api.planningcenteronline.com/oauth/authorize",
|
||||
tokenUrl: "https://api.planningcenteronline.com/oauth/token",
|
||||
userApiUrl: "https://api.planningcenteronline.com/people/v2/me",
|
||||
}}
|
||||
}
|
||||
|
||||
// FetchAuthUser returns an AuthUser instance based on the Planningcenter's user api.
|
||||
//
|
||||
// API reference: https://developer.planning.center/docs/#/overview/authentication
|
||||
func (p *Planningcenter) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
||||
data, err := p.FetchRawUserData(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawUser := map[string]any{}
|
||||
if err := json.Unmarshal(data, &rawUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extracted := struct {
|
||||
Data struct {
|
||||
Id string `json:"id"`
|
||||
Attributes struct {
|
||||
Status string `json:"status"`
|
||||
Name string `json:"name"`
|
||||
AvatarUrl string `json:"avatar"`
|
||||
// don't map the email because users can have multiple assigned
|
||||
// and it's not clear if they are verified
|
||||
}
|
||||
}
|
||||
}{}
|
||||
if err := json.Unmarshal(data, &extracted); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if extracted.Data.Attributes.Status != "active" {
|
||||
return nil, errors.New("the user is not active")
|
||||
}
|
||||
|
||||
user := &AuthUser{
|
||||
Id: extracted.Data.Id,
|
||||
Name: extracted.Data.Attributes.Name,
|
||||
AvatarUrl: extracted.Data.Attributes.AvatarUrl,
|
||||
RawUser: rawUser,
|
||||
AccessToken: token.AccessToken,
|
||||
RefreshToken: token.RefreshToken,
|
||||
}
|
||||
|
||||
user.Expiry, _ = types.ParseDateTime(token.Expiry)
|
||||
|
||||
return user, nil
|
||||
}
|
||||
+43
-16
@@ -22,12 +22,14 @@ type job struct {
|
||||
|
||||
// Cron is a crontab-like struct for tasks/jobs scheduling.
|
||||
type Cron struct {
|
||||
sync.RWMutex
|
||||
timezone *time.Location
|
||||
ticker *time.Ticker
|
||||
startTimer *time.Timer
|
||||
jobs map[string]*job
|
||||
interval time.Duration
|
||||
tickerDone chan bool
|
||||
|
||||
interval time.Duration
|
||||
timezone *time.Location
|
||||
ticker *time.Ticker
|
||||
jobs map[string]*job
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// New create a new Cron struct with default tick interval of 1 minute
|
||||
@@ -37,9 +39,10 @@ type Cron struct {
|
||||
// You can change the default timezone with Cron.SetTimezone().
|
||||
func New() *Cron {
|
||||
return &Cron{
|
||||
interval: 1 * time.Minute,
|
||||
timezone: time.UTC,
|
||||
jobs: map[string]*job{},
|
||||
interval: 1 * time.Minute,
|
||||
timezone: time.UTC,
|
||||
jobs: map[string]*job{},
|
||||
tickerDone: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,10 +135,16 @@ func (c *Cron) Stop() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if c.startTimer != nil {
|
||||
c.startTimer.Stop()
|
||||
c.startTimer = nil
|
||||
}
|
||||
|
||||
if c.ticker == nil {
|
||||
return // already stopped
|
||||
}
|
||||
|
||||
c.tickerDone <- true
|
||||
c.ticker.Stop()
|
||||
c.ticker = nil
|
||||
}
|
||||
@@ -146,15 +155,33 @@ func (c *Cron) Stop() {
|
||||
func (c *Cron) Start() {
|
||||
c.Stop()
|
||||
|
||||
c.Lock()
|
||||
c.ticker = time.NewTicker(c.interval)
|
||||
c.Unlock()
|
||||
// delay the ticker to start at 00 of 1 c.interval duration
|
||||
now := time.Now()
|
||||
next := now.Add(c.interval).Truncate(c.interval)
|
||||
delay := next.Sub(now)
|
||||
|
||||
go func() {
|
||||
for t := range c.ticker.C {
|
||||
c.runDue(t)
|
||||
}
|
||||
}()
|
||||
c.Lock()
|
||||
c.startTimer = time.AfterFunc(delay, func() {
|
||||
c.Lock()
|
||||
c.ticker = time.NewTicker(c.interval)
|
||||
c.Unlock()
|
||||
|
||||
// run immediately at 00
|
||||
c.runDue(time.Now())
|
||||
|
||||
// run after each tick
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-c.tickerDone:
|
||||
return
|
||||
case t := <-c.ticker.C:
|
||||
c.runDue(t)
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
// HasStarted checks whether the current Cron ticker has been started.
|
||||
|
||||
+11
-9
@@ -211,13 +211,13 @@ func TestCronTotal(t *testing.T) {
|
||||
func TestCronStartStop(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c := New()
|
||||
|
||||
c.SetInterval(1 * time.Second)
|
||||
|
||||
test1 := 0
|
||||
test2 := 0
|
||||
|
||||
c := New()
|
||||
|
||||
c.SetInterval(500 * time.Millisecond)
|
||||
|
||||
c.Add("test1", "* * * * *", func() {
|
||||
test1++
|
||||
})
|
||||
@@ -226,13 +226,13 @@ func TestCronStartStop(t *testing.T) {
|
||||
test2++
|
||||
})
|
||||
|
||||
expectedCalls := 3
|
||||
expectedCalls := 2
|
||||
|
||||
// call twice Start to check if the previous ticker will be reseted
|
||||
c.Start()
|
||||
c.Start()
|
||||
|
||||
time.Sleep(3250 * time.Millisecond)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// call twice Stop to ensure that the second stop is no-op
|
||||
c.Stop()
|
||||
@@ -245,12 +245,14 @@ func TestCronStartStop(t *testing.T) {
|
||||
t.Fatalf("Expected %d test2, got %d", expectedCalls, test2)
|
||||
}
|
||||
|
||||
// resume for ~5 seconds
|
||||
// resume for 2 seconds
|
||||
c.Start()
|
||||
time.Sleep(5250 * time.Millisecond)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
c.Stop()
|
||||
|
||||
expectedCalls += 5
|
||||
expectedCalls += 4
|
||||
|
||||
if test1 != expectedCalls {
|
||||
t.Fatalf("Expected %d test1, got %d", expectedCalls, test1)
|
||||
|
||||
@@ -192,3 +192,16 @@ func ParseIndex(createIndexExpr string) Index {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// HasColumnUniqueIndex loosely checks whether the specified column has
|
||||
// a single column unique index (WHERE statements are ignored).
|
||||
func HasSingleColumnUniqueIndex(column string, indexes []string) bool {
|
||||
for _, idx := range indexes {
|
||||
parsed := ParseIndex(idx)
|
||||
if parsed.Unique && len(parsed.Columns) == 1 && strings.EqualFold(parsed.Columns[0].Name, column) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
+108
-21
@@ -3,6 +3,7 @@ package dbutils_test
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/dbutils"
|
||||
@@ -68,21 +69,23 @@ func TestParseIndex(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
result := dbutils.ParseIndex(s.index)
|
||||
t.Run(fmt.Sprintf("scenario_%d", i), func(t *testing.T) {
|
||||
result := dbutils.ParseIndex(s.index)
|
||||
|
||||
resultRaw, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
t.Fatalf("[%d] %v", i, err)
|
||||
}
|
||||
resultRaw, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
t.Fatalf("Faild to marshalize parse result: %v", err)
|
||||
}
|
||||
|
||||
expectedRaw, err := json.Marshal(s.expected)
|
||||
if err != nil {
|
||||
t.Fatalf("[%d] %v", i, err)
|
||||
}
|
||||
expectedRaw, err := json.Marshal(s.expected)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshalize expected index: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(resultRaw, expectedRaw) {
|
||||
t.Errorf("[%d] Expected \n%s \ngot \n%s", i, expectedRaw, resultRaw)
|
||||
}
|
||||
if !bytes.Equal(resultRaw, expectedRaw) {
|
||||
t.Errorf("Expected \n%s \ngot \n%s", expectedRaw, resultRaw)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,11 +149,12 @@ func TestIndexIsValid(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
result := s.index.IsValid()
|
||||
|
||||
if result != s.expected {
|
||||
t.Errorf("[%s] Expected %v, got %v", s.name, s.expected, result)
|
||||
}
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := s.index.IsValid()
|
||||
if result != s.expected {
|
||||
t.Fatalf("Expected %v, got %v", s.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,10 +222,93 @@ func TestIndexBuild(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
result := s.index.Build()
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := s.index.Build()
|
||||
if result != s.expected {
|
||||
t.Fatalf("Expected \n%v \ngot \n%v", s.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if result != s.expected {
|
||||
t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, result)
|
||||
}
|
||||
func TestHasSingleColumnUniqueIndex(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
column string
|
||||
indexes []string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
"empty indexes",
|
||||
"test",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"empty column",
|
||||
"",
|
||||
[]string{
|
||||
"CREATE UNIQUE INDEX `index1` ON `example` (`test`)",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"mismatched column",
|
||||
"test",
|
||||
[]string{
|
||||
"CREATE UNIQUE INDEX `index1` ON `example` (`test2`)",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"non unique index",
|
||||
"test",
|
||||
[]string{
|
||||
"CREATE INDEX `index1` ON `example` (`test`)",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"matching columnd and unique index",
|
||||
"test",
|
||||
[]string{
|
||||
"CREATE UNIQUE INDEX `index1` ON `example` (`test`)",
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"multiple columns",
|
||||
"test",
|
||||
[]string{
|
||||
"CREATE UNIQUE INDEX `index1` ON `example` (`test`, `test2`)",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"multiple indexes",
|
||||
"test",
|
||||
[]string{
|
||||
"CREATE UNIQUE INDEX `index1` ON `example` (`test`, `test2`)",
|
||||
"CREATE UNIQUE INDEX `index2` ON `example` (`test`)",
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"partial unique index",
|
||||
"test",
|
||||
[]string{
|
||||
"CREATE UNIQUE INDEX `index` ON `example` (`test`) where test != ''",
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := dbutils.HasSingleColumnUniqueIndex(s.column, s.indexes)
|
||||
if result != s.expected {
|
||||
t.Fatalf("Expected %v got %v", s.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package dbutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JsonEach returns JSON_EACH SQLite string expression with
|
||||
// some normalizations for non-json columns.
|
||||
func JsonEach(column string) string {
|
||||
return fmt.Sprintf(
|
||||
`json_each(CASE WHEN json_valid([[%s]]) THEN [[%s]] ELSE json_array([[%s]]) END)`,
|
||||
column, column, column,
|
||||
)
|
||||
}
|
||||
|
||||
// JsonArrayLength returns JSON_ARRAY_LENGTH SQLite string expression
|
||||
// with some normalizations for non-json columns.
|
||||
//
|
||||
// It works with both json and non-json column values.
|
||||
//
|
||||
// Returns 0 for empty string or NULL column values.
|
||||
func JsonArrayLength(column string) string {
|
||||
return fmt.Sprintf(
|
||||
`json_array_length(CASE WHEN json_valid([[%s]]) THEN [[%s]] ELSE (CASE WHEN [[%s]] = '' OR [[%s]] IS NULL THEN json_array() ELSE json_array([[%s]]) END) END)`,
|
||||
column, column, column, column, column,
|
||||
)
|
||||
}
|
||||
|
||||
// JsonExtract returns a JSON_EXTRACT SQLite string expression with
|
||||
// some normalizations for non-json columns.
|
||||
func JsonExtract(column string, path string) string {
|
||||
// prefix the path with dot if it is not starting with array notation
|
||||
if path != "" && !strings.HasPrefix(path, "[") {
|
||||
path = "." + path
|
||||
}
|
||||
|
||||
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)",
|
||||
column,
|
||||
column,
|
||||
path,
|
||||
column,
|
||||
path,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package dbutils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/dbutils"
|
||||
)
|
||||
|
||||
func TestJsonEach(t *testing.T) {
|
||||
result := dbutils.JsonEach("a.b")
|
||||
|
||||
expected := "json_each(CASE WHEN json_valid([[a.b]]) THEN [[a.b]] ELSE json_array([[a.b]]) END)"
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf("Expected\n%v\ngot\n%v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonArrayLength(t *testing.T) {
|
||||
result := dbutils.JsonArrayLength("a.b")
|
||||
|
||||
expected := "json_array_length(CASE WHEN json_valid([[a.b]]) THEN [[a.b]] ELSE (CASE WHEN [[a.b]] = '' OR [[a.b]] IS NULL THEN json_array() ELSE json_array([[a.b]]) END) END)"
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf("Expected\n%v\ngot\n%v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonExtract(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
column string
|
||||
path string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"empty path",
|
||||
"a.b",
|
||||
"",
|
||||
"(CASE WHEN json_valid([[a.b]]) THEN JSON_EXTRACT([[a.b]], '$') ELSE JSON_EXTRACT(json_object('pb', [[a.b]]), '$.pb') END)",
|
||||
},
|
||||
{
|
||||
"starting with array index",
|
||||
"a.b",
|
||||
"[1].a[2]",
|
||||
"(CASE WHEN json_valid([[a.b]]) THEN JSON_EXTRACT([[a.b]], '$[1].a[2]') ELSE JSON_EXTRACT(json_object('pb', [[a.b]]), '$.pb[1].a[2]') END)",
|
||||
},
|
||||
{
|
||||
"starting with key",
|
||||
"a.b",
|
||||
"a.b[2].c",
|
||||
"(CASE WHEN json_valid([[a.b]]) THEN JSON_EXTRACT([[a.b]], '$.a.b[2].c') ELSE JSON_EXTRACT(json_object('pb', [[a.b]]), '$.pb.a.b[2].c') END)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := dbutils.JsonExtract(s.column, s.path)
|
||||
|
||||
if result != s.expected {
|
||||
t.Fatalf("Expected\n%v\ngot\n%v", s.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,7 +26,7 @@ type FileReader interface {
|
||||
|
||||
// File defines a single file [io.ReadSeekCloser] resource.
|
||||
//
|
||||
// The file could be from a local path, multipipart/formdata header, etc.
|
||||
// The file could be from a local path, multipart/form-data header, etc.
|
||||
type File struct {
|
||||
Reader FileReader
|
||||
Name string
|
||||
|
||||
@@ -15,17 +15,19 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
"gocloud.dev/blob"
|
||||
"gocloud.dev/blob/fileblob"
|
||||
"gocloud.dev/blob/s3blob"
|
||||
)
|
||||
|
||||
var gcpIgnoreHeaders = []string{"Accept-Encoding"}
|
||||
|
||||
type System struct {
|
||||
ctx context.Context
|
||||
bucket *blob.Bucket
|
||||
@@ -44,19 +46,38 @@ func NewS3(
|
||||
) (*System, error) {
|
||||
ctx := context.Background() // default context
|
||||
|
||||
cred := credentials.NewStaticCredentials(accessKey, secretKey, "")
|
||||
cred := credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")
|
||||
|
||||
sess, err := session.NewSession(&aws.Config{
|
||||
Region: aws.String(region),
|
||||
Endpoint: aws.String(endpoint),
|
||||
Credentials: cred,
|
||||
S3ForcePathStyle: aws.Bool(s3ForcePathStyle),
|
||||
})
|
||||
cfg, err := config.LoadDefaultConfig(ctx,
|
||||
config.WithCredentialsProvider(cred),
|
||||
config.WithRegion(region),
|
||||
config.WithEndpointResolverWithOptions(aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
|
||||
// ensure that the endpoint has url scheme for
|
||||
// backward compatibility with v1 of the aws sdk
|
||||
prefixedEndpoint := endpoint
|
||||
if !strings.Contains(endpoint, "://") {
|
||||
prefixedEndpoint = "https://" + endpoint
|
||||
}
|
||||
|
||||
return aws.Endpoint{URL: prefixedEndpoint, SigningRegion: region}, nil
|
||||
})),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bucket, err := s3blob.OpenBucket(ctx, sess, bucketName, nil)
|
||||
client := s3.NewFromConfig(cfg, func(o *s3.Options) {
|
||||
o.UsePathStyle = s3ForcePathStyle
|
||||
|
||||
// Google Cloud Storage alters the Accept-Encoding header,
|
||||
// which breaks the v2 request signature
|
||||
// (https://github.com/aws/aws-sdk-go-v2/issues/1816)
|
||||
if strings.Contains(endpoint, "storage.googleapis.com") {
|
||||
ignoreSigningHeaders(o, gcpIgnoreHeaders)
|
||||
}
|
||||
})
|
||||
|
||||
bucket, err := OpenBucketV2(ctx, client, bucketName, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -259,7 +280,7 @@ func (s *System) DeletePrefix(prefix string) []error {
|
||||
failed := []error{}
|
||||
|
||||
if prefix == "" {
|
||||
failed = append(failed, errors.New("Prefix mustn't be empty."))
|
||||
failed = append(failed, errors.New("prefix mustn't be empty"))
|
||||
return failed
|
||||
}
|
||||
|
||||
@@ -400,7 +421,7 @@ var ThumbSizeRegex = regexp.MustCompile(`^(\d+)x(\d+)(t|b|f)?$`)
|
||||
func (s *System) CreateThumb(originalKey string, thumbKey, thumbSize string) error {
|
||||
sizeParts := ThumbSizeRegex.FindStringSubmatch(thumbSize)
|
||||
if len(sizeParts) != 4 {
|
||||
return errors.New("Thumb size must be in WxH, WxHt, WxHb or WxHf format.")
|
||||
return errors.New("thumb size must be in WxH, WxHt, WxHb or WxHf format")
|
||||
}
|
||||
|
||||
width, _ := strconv.Atoi(sizeParts[1])
|
||||
@@ -408,7 +429,7 @@ func (s *System) CreateThumb(originalKey string, thumbKey, thumbSize string) err
|
||||
resizeType := sizeParts[3]
|
||||
|
||||
if width == 0 && height == 0 {
|
||||
return errors.New("Thumb width and height cannot be zero at the same time.")
|
||||
return errors.New("thumb width and height cannot be zero at the same time")
|
||||
}
|
||||
|
||||
// fetch the original
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/smithy-go/middleware"
|
||||
smithyhttp "github.com/aws/smithy-go/transport/http"
|
||||
)
|
||||
|
||||
// ignoreSigningHeaders excludes the listed headers
|
||||
// from the request signing because some providers may alter them.
|
||||
//
|
||||
// See https://github.com/aws/aws-sdk-go-v2/issues/1816.
|
||||
func ignoreSigningHeaders(o *s3.Options, headers []string) {
|
||||
o.APIOptions = append(o.APIOptions, func(stack *middleware.Stack) error {
|
||||
if err := stack.Finalize.Insert(ignoreHeaders(headers), "Signing", middleware.Before); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := stack.Finalize.Insert(restoreIgnored(), "Signing", middleware.After); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
type ignoredHeadersKey struct{}
|
||||
|
||||
func ignoreHeaders(headers []string) middleware.FinalizeMiddleware {
|
||||
return middleware.FinalizeMiddlewareFunc(
|
||||
"IgnoreHeaders",
|
||||
func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (out middleware.FinalizeOutput, metadata middleware.Metadata, err error) {
|
||||
req, ok := in.Request.(*smithyhttp.Request)
|
||||
if !ok {
|
||||
return out, metadata, &v4.SigningError{Err: fmt.Errorf("(ignoreHeaders) unexpected request middleware type %T", in.Request)}
|
||||
}
|
||||
|
||||
ignored := make(map[string]string, len(headers))
|
||||
for _, h := range headers {
|
||||
ignored[h] = req.Header.Get(h)
|
||||
req.Header.Del(h)
|
||||
}
|
||||
|
||||
ctx = middleware.WithStackValue(ctx, ignoredHeadersKey{}, ignored)
|
||||
|
||||
return next.HandleFinalize(ctx, in)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func restoreIgnored() middleware.FinalizeMiddleware {
|
||||
return middleware.FinalizeMiddlewareFunc(
|
||||
"RestoreIgnored",
|
||||
func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (out middleware.FinalizeOutput, metadata middleware.Metadata, err error) {
|
||||
req, ok := in.Request.(*smithyhttp.Request)
|
||||
if !ok {
|
||||
return out, metadata, &v4.SigningError{Err: fmt.Errorf("(restoreIgnored) unexpected request middleware type %T", in.Request)}
|
||||
}
|
||||
|
||||
ignored, _ := middleware.GetStackValue(ctx, ignoredHeadersKey{}).(map[string]string)
|
||||
for k, v := range ignored {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
return next.HandleFinalize(ctx, in)
|
||||
},
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -68,8 +68,8 @@ func (s *Serializer) Serialize(c echo.Context, i any, indent string) error {
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// data := map[string]any{"a": 1, "b": 2, "c": map[string]any{"c1": 11, "c2": 22}}
|
||||
// PickFields(data, "a,c.c1") // map[string]any{"a": 1, "c": map[string]any{"c1": 11}}
|
||||
// data := map[string]any{"a": 1, "b": 2, "c": map[string]any{"c1": 11, "c2": 22}}
|
||||
// PickFields(data, "a,c.c1") // map[string]any{"a": 1, "c": map[string]any{"c1": 11}}
|
||||
func PickFields(data any, rawFields string) (any, error) {
|
||||
parsedFields, err := parseFields(rawFields)
|
||||
if err != nil {
|
||||
|
||||
@@ -12,6 +12,36 @@ import (
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// MultipartJsonKey is the key for the special multipart/form-data
|
||||
// handling allowing reading serialized json payload without normalization.
|
||||
const MultipartJsonKey string = "@jsonPayload"
|
||||
|
||||
// MultiBinder is similar to [echo.DefaultBinder] but uses slightly different
|
||||
// application/json and multipart/form-data bind methods to accommodate better
|
||||
// the PocketBase router needs.
|
||||
type MultiBinder struct{}
|
||||
|
||||
// Bind implements the [Binder.Bind] method.
|
||||
//
|
||||
// Bind is almost identical to [echo.DefaultBinder.Bind] but uses the
|
||||
// [rest.BindBody] function for binding the request body.
|
||||
func (b *MultiBinder) Bind(c echo.Context, i interface{}) (err error) {
|
||||
if err := echo.BindPathParams(c, i); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only bind query parameters for GET/DELETE/HEAD to avoid unexpected behavior with destination struct binding from body.
|
||||
// For example a request URL `&id=1&lang=en` with body `{"id":100,"lang":"de"}` would lead to precedence issues.
|
||||
method := c.Request().Method
|
||||
if method == http.MethodGet || method == http.MethodDelete || method == http.MethodHead {
|
||||
if err = echo.BindQueryParams(c, i); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return BindBody(c, i)
|
||||
}
|
||||
|
||||
// BindBody binds request body content to i.
|
||||
//
|
||||
// This is similar to `echo.BindBody()`, but for JSON requests uses
|
||||
@@ -43,8 +73,8 @@ func BindBody(c echo.Context, i any) error {
|
||||
func CopyJsonBody(r *http.Request, i any) error {
|
||||
body := r.Body
|
||||
|
||||
// this usually shouldn't be needed because the Server calls close for us
|
||||
// but we are changing the request body with a new reader
|
||||
// this usually shouldn't be needed because the Server calls close
|
||||
// for us but we are changing the request body with a new reader
|
||||
defer body.Close()
|
||||
|
||||
limitReader := io.LimitReader(body, DefaultMaxMemory)
|
||||
@@ -62,10 +92,8 @@ func CopyJsonBody(r *http.Request, i any) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// This is temp hotfix for properly binding multipart/form-data array values
|
||||
// when a map destination is used.
|
||||
//
|
||||
// It should be replaced with echo.BindBody(c, i) once the issue is fixed in echo.
|
||||
// Custom multipart/form-data binder that implements an additional handling like
|
||||
// loading a serialized json payload or properly scan array values when a map destination is used.
|
||||
func bindFormData(c echo.Context, i any) error {
|
||||
if i == nil {
|
||||
return nil
|
||||
@@ -80,6 +108,13 @@ func bindFormData(c echo.Context, i any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// special case to allow submitting json without normalization
|
||||
// alongside the other multipart/form-data values
|
||||
jsonPayloadValues := values[MultipartJsonKey]
|
||||
for _, payload := range jsonPayloadValues {
|
||||
json.Unmarshal([]byte(payload), i)
|
||||
}
|
||||
|
||||
rt := reflect.TypeOf(i).Elem()
|
||||
|
||||
// map
|
||||
@@ -87,6 +122,10 @@ func bindFormData(c echo.Context, i any) error {
|
||||
rv := reflect.ValueOf(i).Elem()
|
||||
|
||||
for k, v := range values {
|
||||
if k == MultipartJsonKey {
|
||||
continue
|
||||
}
|
||||
|
||||
if total := len(v); total == 1 {
|
||||
rv.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(normalizeMultipartValue(v[0])))
|
||||
} else {
|
||||
|
||||
@@ -13,6 +13,46 @@ import (
|
||||
"github.com/pocketbase/pocketbase/tools/rest"
|
||||
)
|
||||
|
||||
func TestMultiBinderBind(t *testing.T) {
|
||||
binder := rest.MultiBinder{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/test?query=123", strings.NewReader(`{"body":"456"}`))
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
e := echo.New()
|
||||
e.Any("/:name", func(c echo.Context) error {
|
||||
// bind twice to ensure that the json body reader copy is invoked
|
||||
for i := 0; i < 2; i++ {
|
||||
data := struct {
|
||||
Name string `param:"name"`
|
||||
Query string `query:"query"`
|
||||
Body string `form:"body"`
|
||||
}{}
|
||||
|
||||
if err := binder.Bind(c, &data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if data.Name != "test" {
|
||||
t.Fatalf("Expected Name %q, got %q", "test", data.Name)
|
||||
}
|
||||
|
||||
if data.Query != "123" {
|
||||
t.Fatalf("Expected Query %q, got %q", "123", data.Query)
|
||||
}
|
||||
|
||||
if data.Body != "456" {
|
||||
t.Fatalf("Expected Body %q, got %q", "456", data.Body)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
e.ServeHTTP(rec, req)
|
||||
}
|
||||
|
||||
func TestBindBody(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
body io.Reader
|
||||
@@ -41,16 +81,17 @@ func TestBindBody(t *testing.T) {
|
||||
{
|
||||
strings.NewReader(
|
||||
url.Values{
|
||||
"string": []string{"str"},
|
||||
"stings": []string{"str1", "str2", ""},
|
||||
"number": []string{"-123"},
|
||||
"numbers": []string{"123", "456.789"},
|
||||
"bool": []string{"true"},
|
||||
"bools": []string{"true", "false"},
|
||||
"string": []string{"str"},
|
||||
"stings": []string{"str1", "str2", ""},
|
||||
"number": []string{"-123"},
|
||||
"numbers": []string{"123", "456.789"},
|
||||
"bool": []string{"true"},
|
||||
"bools": []string{"true", "false"},
|
||||
rest.MultipartJsonKey: []string{`invalid`, `{"a":123}`, `{"b":456}`},
|
||||
}.Encode(),
|
||||
),
|
||||
echo.MIMEApplicationForm,
|
||||
`{"bool":true,"bools":[true,false],"number":-123,"numbers":[123,456.789],"stings":["str1","str2",""],"string":"str"}`,
|
||||
`{"a":123,"b":456,"bool":true,"bools":[true,false],"number":-123,"numbers":[123,456.789],"stings":["str1","str2",""],"string":"str"}`,
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
+62
-6
@@ -432,16 +432,15 @@ func mergeParams(params ...dbx.Params) dbx.Params {
|
||||
}
|
||||
|
||||
// wrapLikeParams wraps each provided param value string with `%`
|
||||
// if the string doesn't contains the `%` char (including its escape sequence).
|
||||
// if the param doesn't contain an explicit wildcard (`%`) character already.
|
||||
func wrapLikeParams(params dbx.Params) dbx.Params {
|
||||
result := dbx.Params{}
|
||||
|
||||
for k, v := range params {
|
||||
vStr := cast.ToString(v)
|
||||
if !strings.Contains(vStr, "%") {
|
||||
for i := 0; i < len(dbx.DefaultLikeEscape); i += 2 {
|
||||
vStr = strings.ReplaceAll(vStr, dbx.DefaultLikeEscape[i], dbx.DefaultLikeEscape[i+1])
|
||||
}
|
||||
if !containsUnescapedChar(vStr, '%') {
|
||||
// note: this is done to minimize the breaking changes and to preserve the original autoescape behavior
|
||||
vStr = escapeUnescapedChars(vStr, '\\', '%', '_')
|
||||
vStr = "%" + vStr + "%"
|
||||
}
|
||||
result[k] = vStr
|
||||
@@ -450,6 +449,63 @@ func wrapLikeParams(params dbx.Params) dbx.Params {
|
||||
return result
|
||||
}
|
||||
|
||||
func escapeUnescapedChars(str string, escapeChars ...rune) string {
|
||||
rs := []rune(str)
|
||||
total := len(rs)
|
||||
result := make([]rune, 0, total)
|
||||
|
||||
var match bool
|
||||
|
||||
for i := total - 1; i >= 0; i-- {
|
||||
if match {
|
||||
// check if already escaped
|
||||
if rs[i] != '\\' {
|
||||
result = append(result, '\\')
|
||||
}
|
||||
match = false
|
||||
} else {
|
||||
for _, ec := range escapeChars {
|
||||
if rs[i] == ec {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, rs[i])
|
||||
|
||||
// in case the matching char is at the beginning
|
||||
if i == 0 && match {
|
||||
result = append(result, '\\')
|
||||
}
|
||||
}
|
||||
|
||||
// reverse
|
||||
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
|
||||
result[i], result[j] = result[j], result[i]
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
||||
func containsUnescapedChar(str string, ch rune) bool {
|
||||
var prev rune
|
||||
|
||||
for _, c := range str {
|
||||
if c == ch && prev != '\\' {
|
||||
return true
|
||||
}
|
||||
|
||||
if c == '\\' && prev == '\\' {
|
||||
prev = rune(0) // reset escape sequence
|
||||
} else {
|
||||
prev = c
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
var _ dbx.Expression = (*opExpr)(nil)
|
||||
@@ -635,7 +691,7 @@ func multiMatchAfterBuildFunc(op fexpr.SignOp, multiMatchAliases ...string) func
|
||||
// Add an optional "IS NULL" condition(s) to handle the empty rows result.
|
||||
//
|
||||
// For example, let's assume that some "rel" field is [nonemptyRel1, nonemptyRel2, emptyRel3],
|
||||
// The filter "rel.total > 0" will ensures that the above will return true only if all relations
|
||||
// The filter "rel.total > 0" ensures that the above will return true only if all relations
|
||||
// are existing and match the condition.
|
||||
//
|
||||
// The "=" operator is excluded because it will never equal directly with NULL anyway
|
||||
|
||||
@@ -238,3 +238,68 @@ func TestFilterDataBuildExprWithParams(t *testing.T) {
|
||||
t.Fatalf("Expected query \n%s, \ngot \n%s", expectedQuery, calledQueries[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLikeParamsWrapping(t *testing.T) {
|
||||
// create a dummy db
|
||||
sqlDB, err := sql.Open("sqlite", "file::memory:?cache=shared")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
db := dbx.NewFromDB(sqlDB, "sqlite")
|
||||
|
||||
calledQueries := []string{}
|
||||
db.QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {
|
||||
calledQueries = append(calledQueries, sql)
|
||||
}
|
||||
db.ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {
|
||||
calledQueries = append(calledQueries, sql)
|
||||
}
|
||||
|
||||
resolver := search.NewSimpleFieldResolver(`^test\w+$`)
|
||||
|
||||
filter := search.FilterData(`
|
||||
test1 ~ {:p1} ||
|
||||
test2 ~ {:p2} ||
|
||||
test3 ~ {:p3} ||
|
||||
test4 ~ {:p4} ||
|
||||
test5 ~ {:p5} ||
|
||||
test6 ~ {:p6} ||
|
||||
test7 ~ {:p7} ||
|
||||
test8 ~ {:p8} ||
|
||||
test9 ~ {:p9} ||
|
||||
test10 ~ {:p10} ||
|
||||
test11 ~ {:p11} ||
|
||||
test12 ~ {:p12}
|
||||
`)
|
||||
|
||||
replacements := []dbx.Params{
|
||||
{"p1": `abc`},
|
||||
{"p2": `ab%c`},
|
||||
{"p3": `ab\%c`},
|
||||
{"p4": `%ab\%c`},
|
||||
{"p5": `ab\\%c`},
|
||||
{"p6": `ab\\\%c`},
|
||||
{"p7": `ab_c`},
|
||||
{"p8": `ab\_c`},
|
||||
{"p9": `%ab_c`},
|
||||
{"p10": `ab\c`},
|
||||
{"p11": `_ab\c_`},
|
||||
{"p12": `ab\c%`},
|
||||
}
|
||||
|
||||
expr, err := filter.BuildExpr(resolver, replacements...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db.Select().Where(expr).Build().Execute()
|
||||
|
||||
if len(calledQueries) != 1 {
|
||||
t.Fatalf("Expected 1 query, got %d", len(calledQueries))
|
||||
}
|
||||
|
||||
expectedQuery := `SELECT * WHERE ([[test1]] LIKE '%abc%' ESCAPE '\' OR [[test2]] LIKE 'ab%c' ESCAPE '\' OR [[test3]] LIKE 'ab\\%c' ESCAPE '\' OR [[test4]] LIKE '%ab\\%c' ESCAPE '\' OR [[test5]] LIKE 'ab\\\\%c' ESCAPE '\' OR [[test6]] LIKE 'ab\\\\\\%c' ESCAPE '\' OR [[test7]] LIKE '%ab\_c%' ESCAPE '\' OR [[test8]] LIKE '%ab\\\_c%' ESCAPE '\' OR [[test9]] LIKE '%ab_c' ESCAPE '\' OR [[test10]] LIKE '%ab\\c%' ESCAPE '\' OR [[test11]] LIKE '%\_ab\\c\_%' ESCAPE '\' OR [[test12]] LIKE 'ab\\c%' ESCAPE '\')`
|
||||
if expectedQuery != calledQueries[0] {
|
||||
t.Fatalf("Expected query \n%s, \ngot \n%s", expectedQuery, calledQueries[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,16 +24,16 @@ var identifierMacros = map[string]func() (any, error){
|
||||
return d.String(), nil
|
||||
},
|
||||
"@second": func() (any, error) {
|
||||
return int(timeNow().UTC().Second()), nil
|
||||
return timeNow().UTC().Second(), nil
|
||||
},
|
||||
"@minute": func() (any, error) {
|
||||
return int(timeNow().UTC().Minute()), nil
|
||||
return timeNow().UTC().Minute(), nil
|
||||
},
|
||||
"@hour": func() (any, error) {
|
||||
return int(timeNow().UTC().Hour()), nil
|
||||
return timeNow().UTC().Hour(), nil
|
||||
},
|
||||
"@day": func() (any, error) {
|
||||
return int(timeNow().UTC().Day()), nil
|
||||
return timeNow().UTC().Day(), nil
|
||||
},
|
||||
"@month": func() (any, error) {
|
||||
return int(timeNow().UTC().Month()), nil
|
||||
@@ -42,7 +42,7 @@ var identifierMacros = map[string]func() (any, error){
|
||||
return int(timeNow().UTC().Weekday()), nil
|
||||
},
|
||||
"@year": func() (any, error) {
|
||||
return int(timeNow().UTC().Year()), nil
|
||||
return timeNow().UTC().Year(), nil
|
||||
},
|
||||
"@todayStart": func() (any, error) {
|
||||
today := timeNow().UTC()
|
||||
|
||||
@@ -81,7 +81,7 @@ func (s *Provider) SkipTotal(skipTotal bool) *Provider {
|
||||
}
|
||||
|
||||
// CountCol allows changing the default column (id) that is used
|
||||
// to generated the COUNT SQL query statement.
|
||||
// to generate the COUNT SQL query statement.
|
||||
//
|
||||
// This field is ignored if skipTotal is true.
|
||||
func (s *Provider) CountCol(name string) *Provider {
|
||||
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Encrypt encrypts data with key (must be valid 32 char aes key).
|
||||
// Encrypt encrypts "data" with the specified "key" (must be valid 32 char AES key).
|
||||
//
|
||||
// This method uses AES-256-GCM block cypher mode.
|
||||
func Encrypt(data []byte, key string) (string, error) {
|
||||
block, err := aes.NewCipher([]byte(key))
|
||||
if err != nil {
|
||||
@@ -34,7 +36,9 @@ func Encrypt(data []byte, key string) (string, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Decrypt decrypts encrypted text with key (must be valid 32 chars aes key).
|
||||
// Decrypt decrypts encrypted text with key (must be valid 32 chars AES key).
|
||||
//
|
||||
// This method uses AES-256-GCM block cypher mode.
|
||||
func Decrypt(cipherText string, key string) ([]byte, error) {
|
||||
block, err := aes.NewCipher([]byte(key))
|
||||
if err != nil {
|
||||
|
||||
@@ -37,7 +37,7 @@ type Client interface {
|
||||
// Channel returns the client's communication channel.
|
||||
Channel() chan Message
|
||||
|
||||
// Subscriptions returns a shallow copy of the the client subscriptions matching the prefixes.
|
||||
// Subscriptions returns a shallow copy of the client subscriptions matching the prefixes.
|
||||
// If no prefix is specified, returns all subscriptions.
|
||||
Subscriptions(prefixes ...string) map[string]SubscriptionOptions
|
||||
|
||||
@@ -123,7 +123,7 @@ func (c *DefaultClient) Channel() chan Message {
|
||||
|
||||
// Subscriptions implements the [Client.Subscriptions] interface method.
|
||||
//
|
||||
// It returns a shallow copy of the the client subscriptions matching the prefixes.
|
||||
// It returns a shallow copy of the client subscriptions matching the prefixes.
|
||||
// If no prefix is specified, returns all subscriptions.
|
||||
func (c *DefaultClient) Subscriptions(prefixes ...string) map[string]SubscriptionOptions {
|
||||
c.mux.RLock()
|
||||
|
||||
@@ -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.7"
|
||||
PB_VERSION = "v0.22.9"
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
import{S as Se,i as ye,s as Te,O as G,e as c,w,b as k,c as se,f as p,g as d,h as a,m as ae,x as U,P as ve,Q as je,k as Ae,R as Be,n as Oe,t as W,a as V,o as u,d as ne,C as Fe,p as Qe,r as L,u as Ne,N as He}from"./index-ddbc82bb.js";import{S as Ke}from"./SdkTabs-ca27f837.js";import{F as qe}from"./FieldsQueryParam-2cf2725e.js";function Ce(n,l,o){const s=n.slice();return s[5]=l[o],s}function Pe(n,l,o){const s=n.slice();return s[5]=l[o],s}function $e(n,l){let o,s=l[5].code+"",_,f,i,h;function m(){return l[4](l[5])}return{key:n,first:null,c(){o=c("button"),_=w(s),f=k(),p(o,"class","tab-item"),L(o,"active",l[1]===l[5].code),this.first=o},m(v,C){d(v,o,C),a(o,_),a(o,f),i||(h=Ne(o,"click",m),i=!0)},p(v,C){l=v,C&4&&s!==(s=l[5].code+"")&&U(_,s),C&6&&L(o,"active",l[1]===l[5].code)},d(v){v&&u(o),i=!1,h()}}}function Me(n,l){let o,s,_,f;return s=new He({props:{content:l[5].body}}),{key:n,first:null,c(){o=c("div"),se(s.$$.fragment),_=k(),p(o,"class","tab-item"),L(o,"active",l[1]===l[5].code),this.first=o},m(i,h){d(i,o,h),ae(s,o,null),a(o,_),f=!0},p(i,h){l=i;const m={};h&4&&(m.content=l[5].body),s.$set(m),(!f||h&6)&&L(o,"active",l[1]===l[5].code)},i(i){f||(W(s.$$.fragment,i),f=!0)},o(i){V(s.$$.fragment,i),f=!1},d(i){i&&u(o),ne(s)}}}function ze(n){var be,ke;let l,o,s=n[0].name+"",_,f,i,h,m,v,C,H=n[0].name+"",E,ie,I,P,J,j,Y,$,K,ce,q,A,re,R,z=n[0].name+"",X,de,Z,B,x,M,ee,ue,te,T,le,O,oe,S,F,g=[],he=new Map,me,Q,b=[],fe=new Map,y;P=new Ke({props:{js:`
|
||||
import{S as Se,i as ye,s as Ae,O as G,e as c,v as w,b as k,c as se,f as p,g as d,h as a,m as ae,w as U,P as ve,Q as Te,k as je,R as Be,n as Oe,t as W,a as V,o as u,d as ne,C as Fe,A as Qe,q as L,r as Ne,N as qe}from"./index-CK4y5i6D.js";import{S as He}from"./SdkTabs-EzF7kdbJ.js";import{F as Ke}from"./FieldsQueryParam-2EybcaiH.js";function Ce(n,l,o){const s=n.slice();return s[5]=l[o],s}function Pe(n,l,o){const s=n.slice();return s[5]=l[o],s}function $e(n,l){let o,s=l[5].code+"",_,f,i,h;function m(){return l[4](l[5])}return{key:n,first:null,c(){o=c("button"),_=w(s),f=k(),p(o,"class","tab-item"),L(o,"active",l[1]===l[5].code),this.first=o},m(v,C){d(v,o,C),a(o,_),a(o,f),i||(h=Ne(o,"click",m),i=!0)},p(v,C){l=v,C&4&&s!==(s=l[5].code+"")&&U(_,s),C&6&&L(o,"active",l[1]===l[5].code)},d(v){v&&u(o),i=!1,h()}}}function Me(n,l){let o,s,_,f;return s=new qe({props:{content:l[5].body}}),{key:n,first:null,c(){o=c("div"),se(s.$$.fragment),_=k(),p(o,"class","tab-item"),L(o,"active",l[1]===l[5].code),this.first=o},m(i,h){d(i,o,h),ae(s,o,null),a(o,_),f=!0},p(i,h){l=i;const m={};h&4&&(m.content=l[5].body),s.$set(m),(!f||h&6)&&L(o,"active",l[1]===l[5].code)},i(i){f||(W(s.$$.fragment,i),f=!0)},o(i){V(s.$$.fragment,i),f=!1},d(i){i&&u(o),ne(s)}}}function ze(n){var be,ke;let l,o,s=n[0].name+"",_,f,i,h,m,v,C,q=n[0].name+"",E,ie,I,P,J,T,Y,$,H,ce,K,j,re,R,z=n[0].name+"",X,de,Z,B,x,M,ee,ue,te,A,le,O,oe,S,F,g=[],he=new Map,me,Q,b=[],fe=new Map,y;P=new He({props:{js:`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${n[3]}');
|
||||
@@ -14,7 +14,7 @@ import{S as Se,i as ye,s as Te,O as G,e as c,w,b as k,c as se,f as p,g as d,h as
|
||||
...
|
||||
|
||||
final result = await pb.collection('${(ke=n[0])==null?void 0:ke.name}').listAuthMethods();
|
||||
`}}),T=new qe({});let D=G(n[2]);const pe=e=>e[5].code;for(let e=0;e<D.length;e+=1){let t=Pe(n,D,e),r=pe(t);he.set(r,g[e]=$e(r,t))}let N=G(n[2]);const _e=e=>e[5].code;for(let e=0;e<N.length;e+=1){let t=Ce(n,N,e),r=_e(t);fe.set(r,b[e]=Me(r,t))}return{c(){l=c("h3"),o=w("List auth methods ("),_=w(s),f=w(")"),i=k(),h=c("div"),m=c("p"),v=w("Returns a public list with all allowed "),C=c("strong"),E=w(H),ie=w(" authentication methods."),I=k(),se(P.$$.fragment),J=k(),j=c("h6"),j.textContent="API details",Y=k(),$=c("div"),K=c("strong"),K.textContent="GET",ce=k(),q=c("div"),A=c("p"),re=w("/api/collections/"),R=c("strong"),X=w(z),de=w("/auth-methods"),Z=k(),B=c("div"),B.textContent="Query parameters",x=k(),M=c("table"),ee=c("thead"),ee.innerHTML='<tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr>',ue=k(),te=c("tbody"),se(T.$$.fragment),le=k(),O=c("div"),O.textContent="Responses",oe=k(),S=c("div"),F=c("div");for(let e=0;e<g.length;e+=1)g[e].c();me=k(),Q=c("div");for(let e=0;e<b.length;e+=1)b[e].c();p(l,"class","m-b-sm"),p(h,"class","content txt-lg m-b-sm"),p(j,"class","m-b-xs"),p(K,"class","label label-primary"),p(q,"class","content"),p($,"class","alert alert-info"),p(B,"class","section-title"),p(M,"class","table-compact table-border m-b-base"),p(O,"class","section-title"),p(F,"class","tabs-header compact combined left"),p(Q,"class","tabs-content"),p(S,"class","tabs")},m(e,t){d(e,l,t),a(l,o),a(l,_),a(l,f),d(e,i,t),d(e,h,t),a(h,m),a(m,v),a(m,C),a(C,E),a(m,ie),d(e,I,t),ae(P,e,t),d(e,J,t),d(e,j,t),d(e,Y,t),d(e,$,t),a($,K),a($,ce),a($,q),a(q,A),a(A,re),a(A,R),a(R,X),a(A,de),d(e,Z,t),d(e,B,t),d(e,x,t),d(e,M,t),a(M,ee),a(M,ue),a(M,te),ae(T,te,null),d(e,le,t),d(e,O,t),d(e,oe,t),d(e,S,t),a(S,F);for(let r=0;r<g.length;r+=1)g[r]&&g[r].m(F,null);a(S,me),a(S,Q);for(let r=0;r<b.length;r+=1)b[r]&&b[r].m(Q,null);y=!0},p(e,[t]){var ge,we;(!y||t&1)&&s!==(s=e[0].name+"")&&U(_,s),(!y||t&1)&&H!==(H=e[0].name+"")&&U(E,H);const r={};t&9&&(r.js=`
|
||||
`}}),A=new Ke({});let D=G(n[2]);const pe=e=>e[5].code;for(let e=0;e<D.length;e+=1){let t=Pe(n,D,e),r=pe(t);he.set(r,g[e]=$e(r,t))}let N=G(n[2]);const _e=e=>e[5].code;for(let e=0;e<N.length;e+=1){let t=Ce(n,N,e),r=_e(t);fe.set(r,b[e]=Me(r,t))}return{c(){l=c("h3"),o=w("List auth methods ("),_=w(s),f=w(")"),i=k(),h=c("div"),m=c("p"),v=w("Returns a public list with all allowed "),C=c("strong"),E=w(q),ie=w(" authentication methods."),I=k(),se(P.$$.fragment),J=k(),T=c("h6"),T.textContent="API details",Y=k(),$=c("div"),H=c("strong"),H.textContent="GET",ce=k(),K=c("div"),j=c("p"),re=w("/api/collections/"),R=c("strong"),X=w(z),de=w("/auth-methods"),Z=k(),B=c("div"),B.textContent="Query parameters",x=k(),M=c("table"),ee=c("thead"),ee.innerHTML='<tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr>',ue=k(),te=c("tbody"),se(A.$$.fragment),le=k(),O=c("div"),O.textContent="Responses",oe=k(),S=c("div"),F=c("div");for(let e=0;e<g.length;e+=1)g[e].c();me=k(),Q=c("div");for(let e=0;e<b.length;e+=1)b[e].c();p(l,"class","m-b-sm"),p(h,"class","content txt-lg m-b-sm"),p(T,"class","m-b-xs"),p(H,"class","label label-primary"),p(K,"class","content"),p($,"class","alert alert-info"),p(B,"class","section-title"),p(M,"class","table-compact table-border m-b-base"),p(O,"class","section-title"),p(F,"class","tabs-header compact combined left"),p(Q,"class","tabs-content"),p(S,"class","tabs")},m(e,t){d(e,l,t),a(l,o),a(l,_),a(l,f),d(e,i,t),d(e,h,t),a(h,m),a(m,v),a(m,C),a(C,E),a(m,ie),d(e,I,t),ae(P,e,t),d(e,J,t),d(e,T,t),d(e,Y,t),d(e,$,t),a($,H),a($,ce),a($,K),a(K,j),a(j,re),a(j,R),a(R,X),a(j,de),d(e,Z,t),d(e,B,t),d(e,x,t),d(e,M,t),a(M,ee),a(M,ue),a(M,te),ae(A,te,null),d(e,le,t),d(e,O,t),d(e,oe,t),d(e,S,t),a(S,F);for(let r=0;r<g.length;r+=1)g[r]&&g[r].m(F,null);a(S,me),a(S,Q);for(let r=0;r<b.length;r+=1)b[r]&&b[r].m(Q,null);y=!0},p(e,[t]){var ge,we;(!y||t&1)&&s!==(s=e[0].name+"")&&U(_,s),(!y||t&1)&&q!==(q=e[0].name+"")&&U(E,q);const r={};t&9&&(r.js=`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${e[3]}');
|
||||
@@ -30,7 +30,7 @@ import{S as Se,i as ye,s as Te,O as G,e as c,w,b as k,c as se,f as p,g as d,h as
|
||||
...
|
||||
|
||||
final result = await pb.collection('${(we=e[0])==null?void 0:we.name}').listAuthMethods();
|
||||
`),P.$set(r),(!y||t&1)&&z!==(z=e[0].name+"")&&U(X,z),t&6&&(D=G(e[2]),g=ve(g,t,pe,1,e,D,he,F,je,$e,null,Pe)),t&6&&(N=G(e[2]),Ae(),b=ve(b,t,_e,1,e,N,fe,Q,Be,Me,null,Ce),Oe())},i(e){if(!y){W(P.$$.fragment,e),W(T.$$.fragment,e);for(let t=0;t<N.length;t+=1)W(b[t]);y=!0}},o(e){V(P.$$.fragment,e),V(T.$$.fragment,e);for(let t=0;t<b.length;t+=1)V(b[t]);y=!1},d(e){e&&(u(l),u(i),u(h),u(I),u(J),u(j),u(Y),u($),u(Z),u(B),u(x),u(M),u(le),u(O),u(oe),u(S)),ne(P,e),ne(T);for(let t=0;t<g.length;t+=1)g[t].d();for(let t=0;t<b.length;t+=1)b[t].d()}}}function De(n,l,o){let s,{collection:_}=l,f=200,i=[];const h=m=>o(1,f=m.code);return n.$$set=m=>{"collection"in m&&o(0,_=m.collection)},o(3,s=Fe.getApiExampleUrl(Qe.baseUrl)),o(2,i=[{code:200,body:`
|
||||
`),P.$set(r),(!y||t&1)&&z!==(z=e[0].name+"")&&U(X,z),t&6&&(D=G(e[2]),g=ve(g,t,pe,1,e,D,he,F,Te,$e,null,Pe)),t&6&&(N=G(e[2]),je(),b=ve(b,t,_e,1,e,N,fe,Q,Be,Me,null,Ce),Oe())},i(e){if(!y){W(P.$$.fragment,e),W(A.$$.fragment,e);for(let t=0;t<N.length;t+=1)W(b[t]);y=!0}},o(e){V(P.$$.fragment,e),V(A.$$.fragment,e);for(let t=0;t<b.length;t+=1)V(b[t]);y=!1},d(e){e&&(u(l),u(i),u(h),u(I),u(J),u(T),u(Y),u($),u(Z),u(B),u(x),u(M),u(le),u(O),u(oe),u(S)),ne(P,e),ne(A);for(let t=0;t<g.length;t+=1)g[t].d();for(let t=0;t<b.length;t+=1)b[t].d()}}}function De(n,l,o){let s,{collection:_}=l,f=200,i=[];const h=m=>o(1,f=m.code);return n.$$set=m=>{"collection"in m&&o(0,_=m.collection)},o(3,s=Fe.getApiExampleUrl(Qe.baseUrl)),o(2,i=[{code:200,body:`
|
||||
{
|
||||
"usernamePassword": true,
|
||||
"emailPassword": true,
|
||||
@@ -61,4 +61,4 @@ import{S as Se,i as ye,s as Te,O as G,e as c,w,b as k,c as se,f as p,g as d,h as
|
||||
}
|
||||
]
|
||||
}
|
||||
`}]),[_,f,i,s,h]}class Ve extends Se{constructor(l){super(),ye(this,l,De,ze,Te,{collection:0})}}export{Ve as default};
|
||||
`}]),[_,f,i,s,h]}class Ve extends Se{constructor(l){super(),ye(this,l,De,ze,Ae,{collection:0})}}export{Ve as default};
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
import{S as Ue,i as je,s as Je,N as Qe,O as J,e as s,w as k,b as p,c as K,f as b,g as d,h as o,m as I,x as de,P as Ee,Q as Ke,k as Ie,R as We,n as Ge,t as N,a as V,o as u,d as W,C as Le,p as Xe,r as G,u as Ye}from"./index-ddbc82bb.js";import{S as Ze}from"./SdkTabs-ca27f837.js";import{F as et}from"./FieldsQueryParam-2cf2725e.js";function Ne(r,l,a){const n=r.slice();return n[5]=l[a],n}function Ve(r,l,a){const n=r.slice();return n[5]=l[a],n}function xe(r,l){let a,n=l[5].code+"",m,_,i,h;function g(){return l[4](l[5])}return{key:r,first:null,c(){a=s("button"),m=k(n),_=p(),b(a,"class","tab-item"),G(a,"active",l[1]===l[5].code),this.first=a},m(v,w){d(v,a,w),o(a,m),o(a,_),i||(h=Ye(a,"click",g),i=!0)},p(v,w){l=v,w&4&&n!==(n=l[5].code+"")&&de(m,n),w&6&&G(a,"active",l[1]===l[5].code)},d(v){v&&u(a),i=!1,h()}}}function ze(r,l){let a,n,m,_;return n=new Qe({props:{content:l[5].body}}),{key:r,first:null,c(){a=s("div"),K(n.$$.fragment),m=p(),b(a,"class","tab-item"),G(a,"active",l[1]===l[5].code),this.first=a},m(i,h){d(i,a,h),I(n,a,null),o(a,m),_=!0},p(i,h){l=i;const g={};h&4&&(g.content=l[5].body),n.$set(g),(!_||h&6)&&G(a,"active",l[1]===l[5].code)},i(i){_||(N(n.$$.fragment,i),_=!0)},o(i){V(n.$$.fragment,i),_=!1},d(i){i&&u(a),W(n)}}}function tt(r){var De,Fe;let l,a,n=r[0].name+"",m,_,i,h,g,v,w,M,X,S,x,ue,z,q,pe,Y,Q=r[0].name+"",Z,he,fe,U,ee,D,te,T,oe,be,F,C,le,me,ae,_e,f,ke,R,ge,ve,$e,se,ye,ne,Se,we,Te,re,Ce,Pe,A,ie,O,ce,P,H,y=[],Re=new Map,Ae,E,$=[],Be=new Map,B;v=new Ze({props:{js:`
|
||||
import{S as je,i as xe,s as Je,N as Ue,O as J,e as s,v as k,b as p,c as K,f as b,g as d,h as o,m as I,w as de,P as Ee,Q as Ke,k as Ie,R as We,n as Ge,t as N,a as V,o as u,d as W,C as Le,A as Xe,q as G,r as Ye}from"./index-CK4y5i6D.js";import{S as Ze}from"./SdkTabs-EzF7kdbJ.js";import{F as et}from"./FieldsQueryParam-2EybcaiH.js";function Ne(r,l,a){const n=r.slice();return n[5]=l[a],n}function Ve(r,l,a){const n=r.slice();return n[5]=l[a],n}function ze(r,l){let a,n=l[5].code+"",m,_,i,h;function g(){return l[4](l[5])}return{key:r,first:null,c(){a=s("button"),m=k(n),_=p(),b(a,"class","tab-item"),G(a,"active",l[1]===l[5].code),this.first=a},m(v,w){d(v,a,w),o(a,m),o(a,_),i||(h=Ye(a,"click",g),i=!0)},p(v,w){l=v,w&4&&n!==(n=l[5].code+"")&&de(m,n),w&6&&G(a,"active",l[1]===l[5].code)},d(v){v&&u(a),i=!1,h()}}}function Qe(r,l){let a,n,m,_;return n=new Ue({props:{content:l[5].body}}),{key:r,first:null,c(){a=s("div"),K(n.$$.fragment),m=p(),b(a,"class","tab-item"),G(a,"active",l[1]===l[5].code),this.first=a},m(i,h){d(i,a,h),I(n,a,null),o(a,m),_=!0},p(i,h){l=i;const g={};h&4&&(g.content=l[5].body),n.$set(g),(!_||h&6)&&G(a,"active",l[1]===l[5].code)},i(i){_||(N(n.$$.fragment,i),_=!0)},o(i){V(n.$$.fragment,i),_=!1},d(i){i&&u(a),W(n)}}}function tt(r){var De,Fe;let l,a,n=r[0].name+"",m,_,i,h,g,v,w,B,X,S,z,ue,Q,M,pe,Y,U=r[0].name+"",Z,he,fe,j,ee,D,te,T,oe,be,F,C,le,me,ae,_e,f,ke,R,ge,ve,$e,se,ye,ne,Se,we,Te,re,Ce,Pe,A,ie,O,ce,P,H,y=[],Re=new Map,Ae,E,$=[],qe=new Map,q;v=new Ze({props:{js:`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${r[3]}');
|
||||
@@ -24,15 +24,15 @@ import{S as Ue,i as je,s as Je,N as Qe,O as J,e as s,w as k,b as p,c as K,f as b
|
||||
print(pb.authStore.isValid);
|
||||
print(pb.authStore.token);
|
||||
print(pb.authStore.model.id);
|
||||
`}}),R=new Qe({props:{content:"?expand=relField1,relField2.subRelField"}}),A=new et({props:{prefix:"record."}});let j=J(r[2]);const Me=e=>e[5].code;for(let e=0;e<j.length;e+=1){let t=Ve(r,j,e),c=Me(t);Re.set(c,y[e]=xe(c,t))}let L=J(r[2]);const qe=e=>e[5].code;for(let e=0;e<L.length;e+=1){let t=Ne(r,L,e),c=qe(t);Be.set(c,$[e]=ze(c,t))}return{c(){l=s("h3"),a=k("Auth refresh ("),m=k(n),_=k(")"),i=p(),h=s("div"),h.innerHTML=`<p>Returns a new auth response (token and record data) for an
|
||||
`}}),R=new Ue({props:{content:"?expand=relField1,relField2.subRelField"}}),A=new et({props:{prefix:"record."}});let x=J(r[2]);const Be=e=>e[5].code;for(let e=0;e<x.length;e+=1){let t=Ve(r,x,e),c=Be(t);Re.set(c,y[e]=ze(c,t))}let L=J(r[2]);const Me=e=>e[5].code;for(let e=0;e<L.length;e+=1){let t=Ne(r,L,e),c=Me(t);qe.set(c,$[e]=Qe(c,t))}return{c(){l=s("h3"),a=k("Auth refresh ("),m=k(n),_=k(")"),i=p(),h=s("div"),h.innerHTML=`<p>Returns a new auth response (token and record data) for an
|
||||
<strong>already authenticated record</strong>.</p> <p><em>This method is usually called by users on page/screen reload to ensure that the previously stored
|
||||
data in <code>pb.authStore</code> is still valid and up-to-date.</em></p>`,g=p(),K(v.$$.fragment),w=p(),M=s("h6"),M.textContent="API details",X=p(),S=s("div"),x=s("strong"),x.textContent="POST",ue=p(),z=s("div"),q=s("p"),pe=k("/api/collections/"),Y=s("strong"),Z=k(Q),he=k("/auth-refresh"),fe=p(),U=s("p"),U.innerHTML="Requires record <code>Authorization:TOKEN</code> header",ee=p(),D=s("div"),D.textContent="Query parameters",te=p(),T=s("table"),oe=s("thead"),oe.innerHTML='<tr><th>Param</th> <th>Type</th> <th width="60%">Description</th></tr>',be=p(),F=s("tbody"),C=s("tr"),le=s("td"),le.textContent="expand",me=p(),ae=s("td"),ae.innerHTML='<span class="label">String</span>',_e=p(),f=s("td"),ke=k(`Auto expand record relations. Ex.:
|
||||
data in <code>pb.authStore</code> is still valid and up-to-date.</em></p>`,g=p(),K(v.$$.fragment),w=p(),B=s("h6"),B.textContent="API details",X=p(),S=s("div"),z=s("strong"),z.textContent="POST",ue=p(),Q=s("div"),M=s("p"),pe=k("/api/collections/"),Y=s("strong"),Z=k(U),he=k("/auth-refresh"),fe=p(),j=s("p"),j.innerHTML="Requires record <code>Authorization:TOKEN</code> header",ee=p(),D=s("div"),D.textContent="Query parameters",te=p(),T=s("table"),oe=s("thead"),oe.innerHTML='<tr><th>Param</th> <th>Type</th> <th width="60%">Description</th></tr>',be=p(),F=s("tbody"),C=s("tr"),le=s("td"),le.textContent="expand",me=p(),ae=s("td"),ae.innerHTML='<span class="label">String</span>',_e=p(),f=s("td"),ke=k(`Auto expand record relations. Ex.:
|
||||
`),K(R.$$.fragment),ge=k(`
|
||||
Supports up to 6-levels depth nested relations expansion. `),ve=s("br"),$e=k(`
|
||||
The expanded relations will be appended to the record under the
|
||||
`),se=s("code"),se.textContent="expand",ye=k(" property (eg. "),ne=s("code"),ne.textContent='"expand": {"relField1": {...}, ...}',Se=k(`).
|
||||
`),we=s("br"),Te=k(`
|
||||
Only the relations to which the request user has permissions to `),re=s("strong"),re.textContent="view",Ce=k(" will be expanded."),Pe=p(),K(A.$$.fragment),ie=p(),O=s("div"),O.textContent="Responses",ce=p(),P=s("div"),H=s("div");for(let e=0;e<y.length;e+=1)y[e].c();Ae=p(),E=s("div");for(let e=0;e<$.length;e+=1)$[e].c();b(l,"class","m-b-sm"),b(h,"class","content txt-lg m-b-sm"),b(M,"class","m-b-xs"),b(x,"class","label label-primary"),b(z,"class","content"),b(U,"class","txt-hint txt-sm txt-right"),b(S,"class","alert alert-success"),b(D,"class","section-title"),b(T,"class","table-compact table-border m-b-base"),b(O,"class","section-title"),b(H,"class","tabs-header compact combined left"),b(E,"class","tabs-content"),b(P,"class","tabs")},m(e,t){d(e,l,t),o(l,a),o(l,m),o(l,_),d(e,i,t),d(e,h,t),d(e,g,t),I(v,e,t),d(e,w,t),d(e,M,t),d(e,X,t),d(e,S,t),o(S,x),o(S,ue),o(S,z),o(z,q),o(q,pe),o(q,Y),o(Y,Z),o(q,he),o(S,fe),o(S,U),d(e,ee,t),d(e,D,t),d(e,te,t),d(e,T,t),o(T,oe),o(T,be),o(T,F),o(F,C),o(C,le),o(C,me),o(C,ae),o(C,_e),o(C,f),o(f,ke),I(R,f,null),o(f,ge),o(f,ve),o(f,$e),o(f,se),o(f,ye),o(f,ne),o(f,Se),o(f,we),o(f,Te),o(f,re),o(f,Ce),o(F,Pe),I(A,F,null),d(e,ie,t),d(e,O,t),d(e,ce,t),d(e,P,t),o(P,H);for(let c=0;c<y.length;c+=1)y[c]&&y[c].m(H,null);o(P,Ae),o(P,E);for(let c=0;c<$.length;c+=1)$[c]&&$[c].m(E,null);B=!0},p(e,[t]){var Oe,He;(!B||t&1)&&n!==(n=e[0].name+"")&&de(m,n);const c={};t&9&&(c.js=`
|
||||
Only the relations to which the request user has permissions to `),re=s("strong"),re.textContent="view",Ce=k(" will be expanded."),Pe=p(),K(A.$$.fragment),ie=p(),O=s("div"),O.textContent="Responses",ce=p(),P=s("div"),H=s("div");for(let e=0;e<y.length;e+=1)y[e].c();Ae=p(),E=s("div");for(let e=0;e<$.length;e+=1)$[e].c();b(l,"class","m-b-sm"),b(h,"class","content txt-lg m-b-sm"),b(B,"class","m-b-xs"),b(z,"class","label label-primary"),b(Q,"class","content"),b(j,"class","txt-hint txt-sm txt-right"),b(S,"class","alert alert-success"),b(D,"class","section-title"),b(T,"class","table-compact table-border m-b-base"),b(O,"class","section-title"),b(H,"class","tabs-header compact combined left"),b(E,"class","tabs-content"),b(P,"class","tabs")},m(e,t){d(e,l,t),o(l,a),o(l,m),o(l,_),d(e,i,t),d(e,h,t),d(e,g,t),I(v,e,t),d(e,w,t),d(e,B,t),d(e,X,t),d(e,S,t),o(S,z),o(S,ue),o(S,Q),o(Q,M),o(M,pe),o(M,Y),o(Y,Z),o(M,he),o(S,fe),o(S,j),d(e,ee,t),d(e,D,t),d(e,te,t),d(e,T,t),o(T,oe),o(T,be),o(T,F),o(F,C),o(C,le),o(C,me),o(C,ae),o(C,_e),o(C,f),o(f,ke),I(R,f,null),o(f,ge),o(f,ve),o(f,$e),o(f,se),o(f,ye),o(f,ne),o(f,Se),o(f,we),o(f,Te),o(f,re),o(f,Ce),o(F,Pe),I(A,F,null),d(e,ie,t),d(e,O,t),d(e,ce,t),d(e,P,t),o(P,H);for(let c=0;c<y.length;c+=1)y[c]&&y[c].m(H,null);o(P,Ae),o(P,E);for(let c=0;c<$.length;c+=1)$[c]&&$[c].m(E,null);q=!0},p(e,[t]){var Oe,He;(!q||t&1)&&n!==(n=e[0].name+"")&&de(m,n);const c={};t&9&&(c.js=`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${e[3]}');
|
||||
@@ -58,7 +58,7 @@ import{S as Ue,i as je,s as Je,N as Qe,O as J,e as s,w as k,b as p,c as K,f as b
|
||||
print(pb.authStore.isValid);
|
||||
print(pb.authStore.token);
|
||||
print(pb.authStore.model.id);
|
||||
`),v.$set(c),(!B||t&1)&&Q!==(Q=e[0].name+"")&&de(Z,Q),t&6&&(j=J(e[2]),y=Ee(y,t,Me,1,e,j,Re,H,Ke,xe,null,Ve)),t&6&&(L=J(e[2]),Ie(),$=Ee($,t,qe,1,e,L,Be,E,We,ze,null,Ne),Ge())},i(e){if(!B){N(v.$$.fragment,e),N(R.$$.fragment,e),N(A.$$.fragment,e);for(let t=0;t<L.length;t+=1)N($[t]);B=!0}},o(e){V(v.$$.fragment,e),V(R.$$.fragment,e),V(A.$$.fragment,e);for(let t=0;t<$.length;t+=1)V($[t]);B=!1},d(e){e&&(u(l),u(i),u(h),u(g),u(w),u(M),u(X),u(S),u(ee),u(D),u(te),u(T),u(ie),u(O),u(ce),u(P)),W(v,e),W(R),W(A);for(let t=0;t<y.length;t+=1)y[t].d();for(let t=0;t<$.length;t+=1)$[t].d()}}}function ot(r,l,a){let n,{collection:m}=l,_=200,i=[];const h=g=>a(1,_=g.code);return r.$$set=g=>{"collection"in g&&a(0,m=g.collection)},r.$$.update=()=>{r.$$.dirty&1&&a(2,i=[{code:200,body:JSON.stringify({token:"JWT_TOKEN",record:Le.dummyCollectionRecord(m)},null,2)},{code:401,body:`
|
||||
`),v.$set(c),(!q||t&1)&&U!==(U=e[0].name+"")&&de(Z,U),t&6&&(x=J(e[2]),y=Ee(y,t,Be,1,e,x,Re,H,Ke,ze,null,Ve)),t&6&&(L=J(e[2]),Ie(),$=Ee($,t,Me,1,e,L,qe,E,We,Qe,null,Ne),Ge())},i(e){if(!q){N(v.$$.fragment,e),N(R.$$.fragment,e),N(A.$$.fragment,e);for(let t=0;t<L.length;t+=1)N($[t]);q=!0}},o(e){V(v.$$.fragment,e),V(R.$$.fragment,e),V(A.$$.fragment,e);for(let t=0;t<$.length;t+=1)V($[t]);q=!1},d(e){e&&(u(l),u(i),u(h),u(g),u(w),u(B),u(X),u(S),u(ee),u(D),u(te),u(T),u(ie),u(O),u(ce),u(P)),W(v,e),W(R),W(A);for(let t=0;t<y.length;t+=1)y[t].d();for(let t=0;t<$.length;t+=1)$[t].d()}}}function ot(r,l,a){let n,{collection:m}=l,_=200,i=[];const h=g=>a(1,_=g.code);return r.$$set=g=>{"collection"in g&&a(0,m=g.collection)},r.$$.update=()=>{r.$$.dirty&1&&a(2,i=[{code:200,body:JSON.stringify({token:"JWT_TOKEN",record:Le.dummyCollectionRecord(m)},null,2)},{code:401,body:`
|
||||
{
|
||||
"code": 401,
|
||||
"message": "The request requires valid record authorization token to be set.",
|
||||
@@ -76,4 +76,4 @@ import{S as Ue,i as je,s as Je,N as Qe,O as J,e as s,w as k,b as p,c as K,f as b
|
||||
"message": "Missing auth record context.",
|
||||
"data": {}
|
||||
}
|
||||
`}])},a(3,n=Le.getApiExampleUrl(Xe.baseUrl)),[m,_,i,n,h]}class nt extends Ue{constructor(l){super(),je(this,l,ot,tt,Je,{collection:0})}}export{nt as default};
|
||||
`}])},a(3,n=Le.getApiExampleUrl(Xe.baseUrl)),[m,_,i,n,h]}class nt extends je{constructor(l){super(),xe(this,l,ot,tt,Je,{collection:0})}}export{nt as default};
|
||||
Vendored
+7
-7
@@ -1,4 +1,4 @@
|
||||
import{S as xe,i as Ee,s as Je,N as Le,O as z,e as o,w as k,b as h,c as I,f as p,g as r,h as a,m as K,x as pe,P as Ue,Q as Ne,k as Qe,R as ze,n as Ie,t as L,a as x,o as c,d as G,C as Be,p as Ke,r as X,u as Ge}from"./index-ddbc82bb.js";import{S as Xe}from"./SdkTabs-ca27f837.js";import{F as Ye}from"./FieldsQueryParam-2cf2725e.js";function Fe(s,l,n){const i=s.slice();return i[5]=l[n],i}function He(s,l,n){const i=s.slice();return i[5]=l[n],i}function je(s,l){let n,i=l[5].code+"",f,g,d,b;function _(){return l[4](l[5])}return{key:s,first:null,c(){n=o("button"),f=k(i),g=h(),p(n,"class","tab-item"),X(n,"active",l[1]===l[5].code),this.first=n},m(v,O){r(v,n,O),a(n,f),a(n,g),d||(b=Ge(n,"click",_),d=!0)},p(v,O){l=v,O&4&&i!==(i=l[5].code+"")&&pe(f,i),O&6&&X(n,"active",l[1]===l[5].code)},d(v){v&&c(n),d=!1,b()}}}function Ve(s,l){let n,i,f,g;return i=new Le({props:{content:l[5].body}}),{key:s,first:null,c(){n=o("div"),I(i.$$.fragment),f=h(),p(n,"class","tab-item"),X(n,"active",l[1]===l[5].code),this.first=n},m(d,b){r(d,n,b),K(i,n,null),a(n,f),g=!0},p(d,b){l=d;const _={};b&4&&(_.content=l[5].body),i.$set(_),(!g||b&6)&&X(n,"active",l[1]===l[5].code)},i(d){g||(L(i.$$.fragment,d),g=!0)},o(d){x(i.$$.fragment,d),g=!1},d(d){d&&c(n),G(i)}}}function Ze(s){let l,n,i=s[0].name+"",f,g,d,b,_,v,O,P,Y,A,E,be,J,R,me,Z,N=s[0].name+"",ee,fe,te,M,ae,W,le,U,ne,S,oe,ge,B,y,se,ke,ie,_e,m,ve,C,we,$e,Oe,re,Ae,ce,Se,ye,Te,de,Ce,qe,q,ue,F,he,T,H,$=[],De=new Map,Pe,j,w=[],Re=new Map,D;v=new Xe({props:{js:`
|
||||
import{S as Ee,i as Je,s as Ne,N as Le,O as z,e as o,v as k,b as h,c as I,f as p,g as r,h as a,m as K,w as pe,P as Ue,Q as Qe,k as xe,R as ze,n as Ie,t as L,a as E,o as c,d as G,C as Be,A as Ke,q as X,r as Ge}from"./index-CK4y5i6D.js";import{S as Xe}from"./SdkTabs-EzF7kdbJ.js";import{F as Ye}from"./FieldsQueryParam-2EybcaiH.js";function Fe(s,l,n){const i=s.slice();return i[5]=l[n],i}function He(s,l,n){const i=s.slice();return i[5]=l[n],i}function je(s,l){let n,i=l[5].code+"",f,g,d,m;function _(){return l[4](l[5])}return{key:s,first:null,c(){n=o("button"),f=k(i),g=h(),p(n,"class","tab-item"),X(n,"active",l[1]===l[5].code),this.first=n},m(v,O){r(v,n,O),a(n,f),a(n,g),d||(m=Ge(n,"click",_),d=!0)},p(v,O){l=v,O&4&&i!==(i=l[5].code+"")&&pe(f,i),O&6&&X(n,"active",l[1]===l[5].code)},d(v){v&&c(n),d=!1,m()}}}function Ve(s,l){let n,i,f,g;return i=new Le({props:{content:l[5].body}}),{key:s,first:null,c(){n=o("div"),I(i.$$.fragment),f=h(),p(n,"class","tab-item"),X(n,"active",l[1]===l[5].code),this.first=n},m(d,m){r(d,n,m),K(i,n,null),a(n,f),g=!0},p(d,m){l=d;const _={};m&4&&(_.content=l[5].body),i.$set(_),(!g||m&6)&&X(n,"active",l[1]===l[5].code)},i(d){g||(L(i.$$.fragment,d),g=!0)},o(d){E(i.$$.fragment,d),g=!1},d(d){d&&c(n),G(i)}}}function Ze(s){let l,n,i=s[0].name+"",f,g,d,m,_,v,O,P,Y,A,J,me,N,R,be,Z,Q=s[0].name+"",ee,fe,te,M,ae,W,le,U,ne,S,oe,ge,B,y,se,ke,ie,_e,b,ve,C,we,$e,Oe,re,Ae,ce,Se,ye,Te,de,Ce,qe,q,ue,F,he,T,H,$=[],De=new Map,Pe,j,w=[],Re=new Map,D;v=new Xe({props:{js:`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${s[3]}');
|
||||
@@ -45,18 +45,18 @@ import{S as xe,i as Ee,s as Je,N as Le,O as z,e as o,w as k,b as h,c as I,f as p
|
||||
|
||||
// "logout" the last authenticated model
|
||||
pb.authStore.clear();
|
||||
`}}),C=new Le({props:{content:"?expand=relField1,relField2.subRelField"}}),q=new Ye({props:{prefix:"record."}});let Q=z(s[2]);const Me=e=>e[5].code;for(let e=0;e<Q.length;e+=1){let t=He(s,Q,e),u=Me(t);De.set(u,$[e]=je(u,t))}let V=z(s[2]);const We=e=>e[5].code;for(let e=0;e<V.length;e+=1){let t=Fe(s,V,e),u=We(t);Re.set(u,w[e]=Ve(u,t))}return{c(){l=o("h3"),n=k("Auth with OAuth2 ("),f=k(i),g=k(")"),d=h(),b=o("div"),b.innerHTML=`<p>Authenticate with an OAuth2 provider and returns a new auth token and record data.</p> <p>For more details please check the
|
||||
`}}),C=new Le({props:{content:"?expand=relField1,relField2.subRelField"}}),q=new Ye({props:{prefix:"record."}});let x=z(s[2]);const Me=e=>e[5].code;for(let e=0;e<x.length;e+=1){let t=He(s,x,e),u=Me(t);De.set(u,$[e]=je(u,t))}let V=z(s[2]);const We=e=>e[5].code;for(let e=0;e<V.length;e+=1){let t=Fe(s,V,e),u=We(t);Re.set(u,w[e]=Ve(u,t))}return{c(){l=o("h3"),n=k("Auth with OAuth2 ("),f=k(i),g=k(")"),d=h(),m=o("div"),m.innerHTML=`<p>Authenticate with an OAuth2 provider and returns a new auth token and record data.</p> <p>For more details please check the
|
||||
<a href="https://pocketbase.io/docs/authentication/#oauth2-integration" target="_blank" rel="noopener noreferrer">OAuth2 integration documentation
|
||||
</a>.</p>`,_=h(),I(v.$$.fragment),O=h(),P=o("h6"),P.textContent="API details",Y=h(),A=o("div"),E=o("strong"),E.textContent="POST",be=h(),J=o("div"),R=o("p"),me=k("/api/collections/"),Z=o("strong"),ee=k(N),fe=k("/auth-with-oauth2"),te=h(),M=o("div"),M.textContent="Body Parameters",ae=h(),W=o("table"),W.innerHTML=`<thead><tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr></thead> <tbody><tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>provider</span></div></td> <td><span class="label">String</span></td> <td>The name of the OAuth2 client provider (eg. "google").</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>code</span></div></td> <td><span class="label">String</span></td> <td>The authorization code returned from the initial request.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>codeVerifier</span></div></td> <td><span class="label">String</span></td> <td>The code verifier sent with the initial request as part of the code_challenge.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>redirectUrl</span></div></td> <td><span class="label">String</span></td> <td>The redirect url sent with the initial request.</td></tr> <tr><td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>createData</span></div></td> <td><span class="label">Object</span></td> <td><p>Optional data that will be used when creating the auth record on OAuth2 sign-up.</p> <p>The created auth record must comply with the same requirements and validations in the
|
||||
</a>.</p>`,_=h(),I(v.$$.fragment),O=h(),P=o("h6"),P.textContent="API details",Y=h(),A=o("div"),J=o("strong"),J.textContent="POST",me=h(),N=o("div"),R=o("p"),be=k("/api/collections/"),Z=o("strong"),ee=k(Q),fe=k("/auth-with-oauth2"),te=h(),M=o("div"),M.textContent="Body Parameters",ae=h(),W=o("table"),W.innerHTML=`<thead><tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr></thead> <tbody><tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>provider</span></div></td> <td><span class="label">String</span></td> <td>The name of the OAuth2 client provider (eg. "google").</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>code</span></div></td> <td><span class="label">String</span></td> <td>The authorization code returned from the initial request.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>codeVerifier</span></div></td> <td><span class="label">String</span></td> <td>The code verifier sent with the initial request as part of the code_challenge.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>redirectUrl</span></div></td> <td><span class="label">String</span></td> <td>The redirect url sent with the initial request.</td></tr> <tr><td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>createData</span></div></td> <td><span class="label">Object</span></td> <td><p>Optional data that will be used when creating the auth record on OAuth2 sign-up.</p> <p>The created auth record must comply with the same requirements and validations in the
|
||||
regular <strong>create</strong> action.
|
||||
<br/> <em>The data can only be in <code>json</code>, aka. <code>multipart/form-data</code> and files
|
||||
upload currently are not supported during OAuth2 sign-ups.</em></p></td></tr></tbody>`,le=h(),U=o("div"),U.textContent="Query parameters",ne=h(),S=o("table"),oe=o("thead"),oe.innerHTML='<tr><th>Param</th> <th>Type</th> <th width="60%">Description</th></tr>',ge=h(),B=o("tbody"),y=o("tr"),se=o("td"),se.textContent="expand",ke=h(),ie=o("td"),ie.innerHTML='<span class="label">String</span>',_e=h(),m=o("td"),ve=k(`Auto expand record relations. Ex.:
|
||||
upload currently are not supported during OAuth2 sign-ups.</em></p></td></tr></tbody>`,le=h(),U=o("div"),U.textContent="Query parameters",ne=h(),S=o("table"),oe=o("thead"),oe.innerHTML='<tr><th>Param</th> <th>Type</th> <th width="60%">Description</th></tr>',ge=h(),B=o("tbody"),y=o("tr"),se=o("td"),se.textContent="expand",ke=h(),ie=o("td"),ie.innerHTML='<span class="label">String</span>',_e=h(),b=o("td"),ve=k(`Auto expand record relations. Ex.:
|
||||
`),I(C.$$.fragment),we=k(`
|
||||
Supports up to 6-levels depth nested relations expansion. `),$e=o("br"),Oe=k(`
|
||||
The expanded relations will be appended to the record under the
|
||||
`),re=o("code"),re.textContent="expand",Ae=k(" property (eg. "),ce=o("code"),ce.textContent='"expand": {"relField1": {...}, ...}',Se=k(`).
|
||||
`),ye=o("br"),Te=k(`
|
||||
Only the relations to which the request user has permissions to `),de=o("strong"),de.textContent="view",Ce=k(" will be expanded."),qe=h(),I(q.$$.fragment),ue=h(),F=o("div"),F.textContent="Responses",he=h(),T=o("div"),H=o("div");for(let e=0;e<$.length;e+=1)$[e].c();Pe=h(),j=o("div");for(let e=0;e<w.length;e+=1)w[e].c();p(l,"class","m-b-sm"),p(b,"class","content txt-lg m-b-sm"),p(P,"class","m-b-xs"),p(E,"class","label label-primary"),p(J,"class","content"),p(A,"class","alert alert-success"),p(M,"class","section-title"),p(W,"class","table-compact table-border m-b-base"),p(U,"class","section-title"),p(S,"class","table-compact table-border m-b-base"),p(F,"class","section-title"),p(H,"class","tabs-header compact combined left"),p(j,"class","tabs-content"),p(T,"class","tabs")},m(e,t){r(e,l,t),a(l,n),a(l,f),a(l,g),r(e,d,t),r(e,b,t),r(e,_,t),K(v,e,t),r(e,O,t),r(e,P,t),r(e,Y,t),r(e,A,t),a(A,E),a(A,be),a(A,J),a(J,R),a(R,me),a(R,Z),a(Z,ee),a(R,fe),r(e,te,t),r(e,M,t),r(e,ae,t),r(e,W,t),r(e,le,t),r(e,U,t),r(e,ne,t),r(e,S,t),a(S,oe),a(S,ge),a(S,B),a(B,y),a(y,se),a(y,ke),a(y,ie),a(y,_e),a(y,m),a(m,ve),K(C,m,null),a(m,we),a(m,$e),a(m,Oe),a(m,re),a(m,Ae),a(m,ce),a(m,Se),a(m,ye),a(m,Te),a(m,de),a(m,Ce),a(B,qe),K(q,B,null),r(e,ue,t),r(e,F,t),r(e,he,t),r(e,T,t),a(T,H);for(let u=0;u<$.length;u+=1)$[u]&&$[u].m(H,null);a(T,Pe),a(T,j);for(let u=0;u<w.length;u+=1)w[u]&&w[u].m(j,null);D=!0},p(e,[t]){(!D||t&1)&&i!==(i=e[0].name+"")&&pe(f,i);const u={};t&9&&(u.js=`
|
||||
Only the relations to which the request user has permissions to `),de=o("strong"),de.textContent="view",Ce=k(" will be expanded."),qe=h(),I(q.$$.fragment),ue=h(),F=o("div"),F.textContent="Responses",he=h(),T=o("div"),H=o("div");for(let e=0;e<$.length;e+=1)$[e].c();Pe=h(),j=o("div");for(let e=0;e<w.length;e+=1)w[e].c();p(l,"class","m-b-sm"),p(m,"class","content txt-lg m-b-sm"),p(P,"class","m-b-xs"),p(J,"class","label label-primary"),p(N,"class","content"),p(A,"class","alert alert-success"),p(M,"class","section-title"),p(W,"class","table-compact table-border m-b-base"),p(U,"class","section-title"),p(S,"class","table-compact table-border m-b-base"),p(F,"class","section-title"),p(H,"class","tabs-header compact combined left"),p(j,"class","tabs-content"),p(T,"class","tabs")},m(e,t){r(e,l,t),a(l,n),a(l,f),a(l,g),r(e,d,t),r(e,m,t),r(e,_,t),K(v,e,t),r(e,O,t),r(e,P,t),r(e,Y,t),r(e,A,t),a(A,J),a(A,me),a(A,N),a(N,R),a(R,be),a(R,Z),a(Z,ee),a(R,fe),r(e,te,t),r(e,M,t),r(e,ae,t),r(e,W,t),r(e,le,t),r(e,U,t),r(e,ne,t),r(e,S,t),a(S,oe),a(S,ge),a(S,B),a(B,y),a(y,se),a(y,ke),a(y,ie),a(y,_e),a(y,b),a(b,ve),K(C,b,null),a(b,we),a(b,$e),a(b,Oe),a(b,re),a(b,Ae),a(b,ce),a(b,Se),a(b,ye),a(b,Te),a(b,de),a(b,Ce),a(B,qe),K(q,B,null),r(e,ue,t),r(e,F,t),r(e,he,t),r(e,T,t),a(T,H);for(let u=0;u<$.length;u+=1)$[u]&&$[u].m(H,null);a(T,Pe),a(T,j);for(let u=0;u<w.length;u+=1)w[u]&&w[u].m(j,null);D=!0},p(e,[t]){(!D||t&1)&&i!==(i=e[0].name+"")&&pe(f,i);const u={};t&9&&(u.js=`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${e[3]}');
|
||||
@@ -103,7 +103,7 @@ import{S as xe,i as Ee,s as Je,N as Le,O as z,e as o,w as k,b as h,c as I,f as p
|
||||
|
||||
// "logout" the last authenticated model
|
||||
pb.authStore.clear();
|
||||
`),v.$set(u),(!D||t&1)&&N!==(N=e[0].name+"")&&pe(ee,N),t&6&&(Q=z(e[2]),$=Ue($,t,Me,1,e,Q,De,H,Ne,je,null,He)),t&6&&(V=z(e[2]),Qe(),w=Ue(w,t,We,1,e,V,Re,j,ze,Ve,null,Fe),Ie())},i(e){if(!D){L(v.$$.fragment,e),L(C.$$.fragment,e),L(q.$$.fragment,e);for(let t=0;t<V.length;t+=1)L(w[t]);D=!0}},o(e){x(v.$$.fragment,e),x(C.$$.fragment,e),x(q.$$.fragment,e);for(let t=0;t<w.length;t+=1)x(w[t]);D=!1},d(e){e&&(c(l),c(d),c(b),c(_),c(O),c(P),c(Y),c(A),c(te),c(M),c(ae),c(W),c(le),c(U),c(ne),c(S),c(ue),c(F),c(he),c(T)),G(v,e),G(C),G(q);for(let t=0;t<$.length;t+=1)$[t].d();for(let t=0;t<w.length;t+=1)w[t].d()}}}function et(s,l,n){let i,{collection:f}=l,g=200,d=[];const b=_=>n(1,g=_.code);return s.$$set=_=>{"collection"in _&&n(0,f=_.collection)},s.$$.update=()=>{s.$$.dirty&1&&n(2,d=[{code:200,body:JSON.stringify({token:"JWT_AUTH_TOKEN",record:Be.dummyCollectionRecord(f),meta:{id:"abc123",name:"John Doe",username:"john.doe",email:"test@example.com",avatarUrl:"https://example.com/avatar.png",accessToken:"...",refreshToken:"...",rawUser:{}}},null,2)},{code:400,body:`
|
||||
`),v.$set(u),(!D||t&1)&&Q!==(Q=e[0].name+"")&&pe(ee,Q),t&6&&(x=z(e[2]),$=Ue($,t,Me,1,e,x,De,H,Qe,je,null,He)),t&6&&(V=z(e[2]),xe(),w=Ue(w,t,We,1,e,V,Re,j,ze,Ve,null,Fe),Ie())},i(e){if(!D){L(v.$$.fragment,e),L(C.$$.fragment,e),L(q.$$.fragment,e);for(let t=0;t<V.length;t+=1)L(w[t]);D=!0}},o(e){E(v.$$.fragment,e),E(C.$$.fragment,e),E(q.$$.fragment,e);for(let t=0;t<w.length;t+=1)E(w[t]);D=!1},d(e){e&&(c(l),c(d),c(m),c(_),c(O),c(P),c(Y),c(A),c(te),c(M),c(ae),c(W),c(le),c(U),c(ne),c(S),c(ue),c(F),c(he),c(T)),G(v,e),G(C),G(q);for(let t=0;t<$.length;t+=1)$[t].d();for(let t=0;t<w.length;t+=1)w[t].d()}}}function et(s,l,n){let i,{collection:f}=l,g=200,d=[];const m=_=>n(1,g=_.code);return s.$$set=_=>{"collection"in _&&n(0,f=_.collection)},s.$$.update=()=>{s.$$.dirty&1&&n(2,d=[{code:200,body:JSON.stringify({token:"JWT_AUTH_TOKEN",record:Be.dummyCollectionRecord(f),meta:{id:"abc123",name:"John Doe",username:"john.doe",email:"test@example.com",avatarUrl:"https://example.com/avatar.png",accessToken:"...",refreshToken:"...",rawUser:{}}},null,2)},{code:400,body:`
|
||||
{
|
||||
"code": 400,
|
||||
"message": "An error occurred while submitting the form.",
|
||||
@@ -114,4 +114,4 @@ import{S as xe,i as Ee,s as Je,N as Le,O as z,e as o,w as k,b as h,c as I,f as p
|
||||
}
|
||||
}
|
||||
}
|
||||
`}])},n(3,i=Be.getApiExampleUrl(Ke.baseUrl)),[f,g,d,i,b]}class nt extends xe{constructor(l){super(),Ee(this,l,et,Ze,Je,{collection:0})}}export{nt as default};
|
||||
`}])},n(3,i=Be.getApiExampleUrl(Ke.baseUrl)),[f,g,d,i,m]}class nt extends Ee{constructor(l){super(),Je(this,l,et,Ze,Ne,{collection:0})}}export{nt as default};
|
||||
Vendored
+10
-10
@@ -1,4 +1,4 @@
|
||||
import{S as wt,i as yt,s as $t,N as vt,O as oe,e as n,w as p,b as d,c as ne,f as m,g as r,h as t,m as se,x as De,P as pt,Q as Pt,k as Rt,R as Ct,n as Ot,t as Z,a as x,o as c,d as ie,C as ft,p as At,r as re,u as Tt}from"./index-ddbc82bb.js";import{S as Ut}from"./SdkTabs-ca27f837.js";import{F as Mt}from"./FieldsQueryParam-2cf2725e.js";function ht(s,l,a){const i=s.slice();return i[8]=l[a],i}function bt(s,l,a){const i=s.slice();return i[8]=l[a],i}function Dt(s){let l;return{c(){l=p("email")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function Et(s){let l;return{c(){l=p("username")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function Wt(s){let l;return{c(){l=p("username/email")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function mt(s){let l;return{c(){l=n("strong"),l.textContent="username"},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function _t(s){let l;return{c(){l=p("or")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function kt(s){let l;return{c(){l=n("strong"),l.textContent="email"},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function gt(s,l){let a,i=l[8].code+"",g,b,f,u;function _(){return l[7](l[8])}return{key:s,first:null,c(){a=n("button"),g=p(i),b=d(),m(a,"class","tab-item"),re(a,"active",l[3]===l[8].code),this.first=a},m(R,C){r(R,a,C),t(a,g),t(a,b),f||(u=Tt(a,"click",_),f=!0)},p(R,C){l=R,C&16&&i!==(i=l[8].code+"")&&De(g,i),C&24&&re(a,"active",l[3]===l[8].code)},d(R){R&&c(a),f=!1,u()}}}function St(s,l){let a,i,g,b;return i=new vt({props:{content:l[8].body}}),{key:s,first:null,c(){a=n("div"),ne(i.$$.fragment),g=d(),m(a,"class","tab-item"),re(a,"active",l[3]===l[8].code),this.first=a},m(f,u){r(f,a,u),se(i,a,null),t(a,g),b=!0},p(f,u){l=f;const _={};u&16&&(_.content=l[8].body),i.$set(_),(!b||u&24)&&re(a,"active",l[3]===l[8].code)},i(f){b||(Z(i.$$.fragment,f),b=!0)},o(f){x(i.$$.fragment,f),b=!1},d(f){f&&c(a),ie(i)}}}function Lt(s){var rt,ct;let l,a,i=s[0].name+"",g,b,f,u,_,R,C,O,B,Ee,ce,T,de,N,ue,U,ee,We,te,I,Le,pe,le=s[0].name+"",fe,Be,he,V,be,M,me,qe,Q,D,_e,Fe,ke,He,$,Ye,ge,Se,ve,Ne,we,ye,j,$e,E,Pe,Ie,J,W,Re,Ve,Ce,Qe,k,je,q,Je,Ke,ze,Oe,Ge,Ae,Xe,Ze,xe,Te,et,tt,F,Ue,K,Me,L,z,A=[],lt=new Map,at,G,S=[],ot=new Map,H;function nt(e,o){if(e[1]&&e[2])return Wt;if(e[1])return Et;if(e[2])return Dt}let Y=nt(s),P=Y&&Y(s);T=new Ut({props:{js:`
|
||||
import{S as wt,i as yt,s as $t,N as vt,O as oe,e as n,v as p,b as d,c as ne,f as m,g as r,h as t,m as se,w as De,P as pt,Q as Pt,k as Rt,R as At,n as Ct,t as Z,a as x,o as c,d as ie,C as ft,A as Ot,q as re,r as Tt}from"./index-CK4y5i6D.js";import{S as Ut}from"./SdkTabs-EzF7kdbJ.js";import{F as Mt}from"./FieldsQueryParam-2EybcaiH.js";function ht(s,l,a){const i=s.slice();return i[8]=l[a],i}function bt(s,l,a){const i=s.slice();return i[8]=l[a],i}function Dt(s){let l;return{c(){l=p("email")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function Et(s){let l;return{c(){l=p("username")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function Wt(s){let l;return{c(){l=p("username/email")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function mt(s){let l;return{c(){l=n("strong"),l.textContent="username"},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function _t(s){let l;return{c(){l=p("or")},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function kt(s){let l;return{c(){l=n("strong"),l.textContent="email"},m(a,i){r(a,l,i)},d(a){a&&c(l)}}}function gt(s,l){let a,i=l[8].code+"",g,b,f,u;function _(){return l[7](l[8])}return{key:s,first:null,c(){a=n("button"),g=p(i),b=d(),m(a,"class","tab-item"),re(a,"active",l[3]===l[8].code),this.first=a},m(R,A){r(R,a,A),t(a,g),t(a,b),f||(u=Tt(a,"click",_),f=!0)},p(R,A){l=R,A&16&&i!==(i=l[8].code+"")&&De(g,i),A&24&&re(a,"active",l[3]===l[8].code)},d(R){R&&c(a),f=!1,u()}}}function St(s,l){let a,i,g,b;return i=new vt({props:{content:l[8].body}}),{key:s,first:null,c(){a=n("div"),ne(i.$$.fragment),g=d(),m(a,"class","tab-item"),re(a,"active",l[3]===l[8].code),this.first=a},m(f,u){r(f,a,u),se(i,a,null),t(a,g),b=!0},p(f,u){l=f;const _={};u&16&&(_.content=l[8].body),i.$set(_),(!b||u&24)&&re(a,"active",l[3]===l[8].code)},i(f){b||(Z(i.$$.fragment,f),b=!0)},o(f){x(i.$$.fragment,f),b=!1},d(f){f&&c(a),ie(i)}}}function Lt(s){var rt,ct;let l,a,i=s[0].name+"",g,b,f,u,_,R,A,C,q,Ee,ce,T,de,N,ue,U,ee,We,te,I,Le,pe,le=s[0].name+"",fe,qe,he,V,be,M,me,Be,Q,D,_e,Fe,ke,He,$,Ye,ge,Se,ve,Ne,we,ye,j,$e,E,Pe,Ie,J,W,Re,Ve,Ae,Qe,k,je,B,Je,Ke,ze,Ce,Ge,Oe,Xe,Ze,xe,Te,et,tt,F,Ue,K,Me,L,z,O=[],lt=new Map,at,G,S=[],ot=new Map,H;function nt(e,o){if(e[1]&&e[2])return Wt;if(e[1])return Et;if(e[2])return Dt}let Y=nt(s),P=Y&&Y(s);T=new Ut({props:{js:`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${s[6]}');
|
||||
@@ -36,17 +36,17 @@ import{S as wt,i as yt,s as $t,N as vt,O as oe,e as n,w as p,b as d,c as ne,f as
|
||||
|
||||
// "logout" the last authenticated account
|
||||
pb.authStore.clear();
|
||||
`}});let v=s[1]&&mt(),w=s[1]&&s[2]&&_t(),y=s[2]&&kt();q=new vt({props:{content:"?expand=relField1,relField2.subRelField"}}),F=new Mt({props:{prefix:"record."}});let ae=oe(s[4]);const st=e=>e[8].code;for(let e=0;e<ae.length;e+=1){let o=bt(s,ae,e),h=st(o);lt.set(h,A[e]=gt(h,o))}let X=oe(s[4]);const it=e=>e[8].code;for(let e=0;e<X.length;e+=1){let o=ht(s,X,e),h=it(o);ot.set(h,S[e]=St(h,o))}return{c(){l=n("h3"),a=p("Auth with password ("),g=p(i),b=p(")"),f=d(),u=n("div"),_=n("p"),R=p(`Returns new auth token and account data by a combination of
|
||||
`),C=n("strong"),P&&P.c(),O=p(`
|
||||
and `),B=n("strong"),B.textContent="password",Ee=p("."),ce=d(),ne(T.$$.fragment),de=d(),N=n("h6"),N.textContent="API details",ue=d(),U=n("div"),ee=n("strong"),ee.textContent="POST",We=d(),te=n("div"),I=n("p"),Le=p("/api/collections/"),pe=n("strong"),fe=p(le),Be=p("/auth-with-password"),he=d(),V=n("div"),V.textContent="Body Parameters",be=d(),M=n("table"),me=n("thead"),me.innerHTML='<tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr>',qe=d(),Q=n("tbody"),D=n("tr"),_e=n("td"),_e.innerHTML='<div class="inline-flex"><span class="label label-success">Required</span> <span>identity</span></div>',Fe=d(),ke=n("td"),ke.innerHTML='<span class="label">String</span>',He=d(),$=n("td"),Ye=p(`The
|
||||
`}});let v=s[1]&&mt(),w=s[1]&&s[2]&&_t(),y=s[2]&&kt();B=new vt({props:{content:"?expand=relField1,relField2.subRelField"}}),F=new Mt({props:{prefix:"record."}});let ae=oe(s[4]);const st=e=>e[8].code;for(let e=0;e<ae.length;e+=1){let o=bt(s,ae,e),h=st(o);lt.set(h,O[e]=gt(h,o))}let X=oe(s[4]);const it=e=>e[8].code;for(let e=0;e<X.length;e+=1){let o=ht(s,X,e),h=it(o);ot.set(h,S[e]=St(h,o))}return{c(){l=n("h3"),a=p("Auth with password ("),g=p(i),b=p(")"),f=d(),u=n("div"),_=n("p"),R=p(`Returns new auth token and account data by a combination of
|
||||
`),A=n("strong"),P&&P.c(),C=p(`
|
||||
and `),q=n("strong"),q.textContent="password",Ee=p("."),ce=d(),ne(T.$$.fragment),de=d(),N=n("h6"),N.textContent="API details",ue=d(),U=n("div"),ee=n("strong"),ee.textContent="POST",We=d(),te=n("div"),I=n("p"),Le=p("/api/collections/"),pe=n("strong"),fe=p(le),qe=p("/auth-with-password"),he=d(),V=n("div"),V.textContent="Body Parameters",be=d(),M=n("table"),me=n("thead"),me.innerHTML='<tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr>',Be=d(),Q=n("tbody"),D=n("tr"),_e=n("td"),_e.innerHTML='<div class="inline-flex"><span class="label label-success">Required</span> <span>identity</span></div>',Fe=d(),ke=n("td"),ke.innerHTML='<span class="label">String</span>',He=d(),$=n("td"),Ye=p(`The
|
||||
`),v&&v.c(),ge=d(),w&&w.c(),Se=d(),y&&y.c(),ve=p(`
|
||||
of the record to authenticate.`),Ne=d(),we=n("tr"),we.innerHTML='<td><div class="inline-flex"><span class="label label-success">Required</span> <span>password</span></div></td> <td><span class="label">String</span></td> <td>The auth record password.</td>',ye=d(),j=n("div"),j.textContent="Query parameters",$e=d(),E=n("table"),Pe=n("thead"),Pe.innerHTML='<tr><th>Param</th> <th>Type</th> <th width="60%">Description</th></tr>',Ie=d(),J=n("tbody"),W=n("tr"),Re=n("td"),Re.textContent="expand",Ve=d(),Ce=n("td"),Ce.innerHTML='<span class="label">String</span>',Qe=d(),k=n("td"),je=p(`Auto expand record relations. Ex.:
|
||||
`),ne(q.$$.fragment),Je=p(`
|
||||
of the record to authenticate.`),Ne=d(),we=n("tr"),we.innerHTML='<td><div class="inline-flex"><span class="label label-success">Required</span> <span>password</span></div></td> <td><span class="label">String</span></td> <td>The auth record password.</td>',ye=d(),j=n("div"),j.textContent="Query parameters",$e=d(),E=n("table"),Pe=n("thead"),Pe.innerHTML='<tr><th>Param</th> <th>Type</th> <th width="60%">Description</th></tr>',Ie=d(),J=n("tbody"),W=n("tr"),Re=n("td"),Re.textContent="expand",Ve=d(),Ae=n("td"),Ae.innerHTML='<span class="label">String</span>',Qe=d(),k=n("td"),je=p(`Auto expand record relations. Ex.:
|
||||
`),ne(B.$$.fragment),Je=p(`
|
||||
Supports up to 6-levels depth nested relations expansion. `),Ke=n("br"),ze=p(`
|
||||
The expanded relations will be appended to the record under the
|
||||
`),Oe=n("code"),Oe.textContent="expand",Ge=p(" property (eg. "),Ae=n("code"),Ae.textContent='"expand": {"relField1": {...}, ...}',Xe=p(`).
|
||||
`),Ce=n("code"),Ce.textContent="expand",Ge=p(" property (eg. "),Oe=n("code"),Oe.textContent='"expand": {"relField1": {...}, ...}',Xe=p(`).
|
||||
`),Ze=n("br"),xe=p(`
|
||||
Only the relations to which the request user has permissions to `),Te=n("strong"),Te.textContent="view",et=p(" will be expanded."),tt=d(),ne(F.$$.fragment),Ue=d(),K=n("div"),K.textContent="Responses",Me=d(),L=n("div"),z=n("div");for(let e=0;e<A.length;e+=1)A[e].c();at=d(),G=n("div");for(let e=0;e<S.length;e+=1)S[e].c();m(l,"class","m-b-sm"),m(u,"class","content txt-lg m-b-sm"),m(N,"class","m-b-xs"),m(ee,"class","label label-primary"),m(te,"class","content"),m(U,"class","alert alert-success"),m(V,"class","section-title"),m(M,"class","table-compact table-border m-b-base"),m(j,"class","section-title"),m(E,"class","table-compact table-border m-b-base"),m(K,"class","section-title"),m(z,"class","tabs-header compact combined left"),m(G,"class","tabs-content"),m(L,"class","tabs")},m(e,o){r(e,l,o),t(l,a),t(l,g),t(l,b),r(e,f,o),r(e,u,o),t(u,_),t(_,R),t(_,C),P&&P.m(C,null),t(_,O),t(_,B),t(_,Ee),r(e,ce,o),se(T,e,o),r(e,de,o),r(e,N,o),r(e,ue,o),r(e,U,o),t(U,ee),t(U,We),t(U,te),t(te,I),t(I,Le),t(I,pe),t(pe,fe),t(I,Be),r(e,he,o),r(e,V,o),r(e,be,o),r(e,M,o),t(M,me),t(M,qe),t(M,Q),t(Q,D),t(D,_e),t(D,Fe),t(D,ke),t(D,He),t(D,$),t($,Ye),v&&v.m($,null),t($,ge),w&&w.m($,null),t($,Se),y&&y.m($,null),t($,ve),t(Q,Ne),t(Q,we),r(e,ye,o),r(e,j,o),r(e,$e,o),r(e,E,o),t(E,Pe),t(E,Ie),t(E,J),t(J,W),t(W,Re),t(W,Ve),t(W,Ce),t(W,Qe),t(W,k),t(k,je),se(q,k,null),t(k,Je),t(k,Ke),t(k,ze),t(k,Oe),t(k,Ge),t(k,Ae),t(k,Xe),t(k,Ze),t(k,xe),t(k,Te),t(k,et),t(J,tt),se(F,J,null),r(e,Ue,o),r(e,K,o),r(e,Me,o),r(e,L,o),t(L,z);for(let h=0;h<A.length;h+=1)A[h]&&A[h].m(z,null);t(L,at),t(L,G);for(let h=0;h<S.length;h+=1)S[h]&&S[h].m(G,null);H=!0},p(e,[o]){var dt,ut;(!H||o&1)&&i!==(i=e[0].name+"")&&De(g,i),Y!==(Y=nt(e))&&(P&&P.d(1),P=Y&&Y(e),P&&(P.c(),P.m(C,null)));const h={};o&97&&(h.js=`
|
||||
Only the relations to which the request user has permissions to `),Te=n("strong"),Te.textContent="view",et=p(" will be expanded."),tt=d(),ne(F.$$.fragment),Ue=d(),K=n("div"),K.textContent="Responses",Me=d(),L=n("div"),z=n("div");for(let e=0;e<O.length;e+=1)O[e].c();at=d(),G=n("div");for(let e=0;e<S.length;e+=1)S[e].c();m(l,"class","m-b-sm"),m(u,"class","content txt-lg m-b-sm"),m(N,"class","m-b-xs"),m(ee,"class","label label-primary"),m(te,"class","content"),m(U,"class","alert alert-success"),m(V,"class","section-title"),m(M,"class","table-compact table-border m-b-base"),m(j,"class","section-title"),m(E,"class","table-compact table-border m-b-base"),m(K,"class","section-title"),m(z,"class","tabs-header compact combined left"),m(G,"class","tabs-content"),m(L,"class","tabs")},m(e,o){r(e,l,o),t(l,a),t(l,g),t(l,b),r(e,f,o),r(e,u,o),t(u,_),t(_,R),t(_,A),P&&P.m(A,null),t(_,C),t(_,q),t(_,Ee),r(e,ce,o),se(T,e,o),r(e,de,o),r(e,N,o),r(e,ue,o),r(e,U,o),t(U,ee),t(U,We),t(U,te),t(te,I),t(I,Le),t(I,pe),t(pe,fe),t(I,qe),r(e,he,o),r(e,V,o),r(e,be,o),r(e,M,o),t(M,me),t(M,Be),t(M,Q),t(Q,D),t(D,_e),t(D,Fe),t(D,ke),t(D,He),t(D,$),t($,Ye),v&&v.m($,null),t($,ge),w&&w.m($,null),t($,Se),y&&y.m($,null),t($,ve),t(Q,Ne),t(Q,we),r(e,ye,o),r(e,j,o),r(e,$e,o),r(e,E,o),t(E,Pe),t(E,Ie),t(E,J),t(J,W),t(W,Re),t(W,Ve),t(W,Ae),t(W,Qe),t(W,k),t(k,je),se(B,k,null),t(k,Je),t(k,Ke),t(k,ze),t(k,Ce),t(k,Ge),t(k,Oe),t(k,Xe),t(k,Ze),t(k,xe),t(k,Te),t(k,et),t(J,tt),se(F,J,null),r(e,Ue,o),r(e,K,o),r(e,Me,o),r(e,L,o),t(L,z);for(let h=0;h<O.length;h+=1)O[h]&&O[h].m(z,null);t(L,at),t(L,G);for(let h=0;h<S.length;h+=1)S[h]&&S[h].m(G,null);H=!0},p(e,[o]){var dt,ut;(!H||o&1)&&i!==(i=e[0].name+"")&&De(g,i),Y!==(Y=nt(e))&&(P&&P.d(1),P=Y&&Y(e),P&&(P.c(),P.m(A,null)));const h={};o&97&&(h.js=`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${e[6]}');
|
||||
@@ -84,7 +84,7 @@ import{S as wt,i as yt,s as $t,N as vt,O as oe,e as n,w as p,b as d,c as ne,f as
|
||||
|
||||
// "logout" the last authenticated account
|
||||
pb.authStore.clear();
|
||||
`),T.$set(h),(!H||o&1)&&le!==(le=e[0].name+"")&&De(fe,le),e[1]?v||(v=mt(),v.c(),v.m($,ge)):v&&(v.d(1),v=null),e[1]&&e[2]?w||(w=_t(),w.c(),w.m($,Se)):w&&(w.d(1),w=null),e[2]?y||(y=kt(),y.c(),y.m($,ve)):y&&(y.d(1),y=null),o&24&&(ae=oe(e[4]),A=pt(A,o,st,1,e,ae,lt,z,Pt,gt,null,bt)),o&24&&(X=oe(e[4]),Rt(),S=pt(S,o,it,1,e,X,ot,G,Ct,St,null,ht),Ot())},i(e){if(!H){Z(T.$$.fragment,e),Z(q.$$.fragment,e),Z(F.$$.fragment,e);for(let o=0;o<X.length;o+=1)Z(S[o]);H=!0}},o(e){x(T.$$.fragment,e),x(q.$$.fragment,e),x(F.$$.fragment,e);for(let o=0;o<S.length;o+=1)x(S[o]);H=!1},d(e){e&&(c(l),c(f),c(u),c(ce),c(de),c(N),c(ue),c(U),c(he),c(V),c(be),c(M),c(ye),c(j),c($e),c(E),c(Ue),c(K),c(Me),c(L)),P&&P.d(),ie(T,e),v&&v.d(),w&&w.d(),y&&y.d(),ie(q),ie(F);for(let o=0;o<A.length;o+=1)A[o].d();for(let o=0;o<S.length;o+=1)S[o].d()}}}function Bt(s,l,a){let i,g,b,f,{collection:u}=l,_=200,R=[];const C=O=>a(3,_=O.code);return s.$$set=O=>{"collection"in O&&a(0,u=O.collection)},s.$$.update=()=>{var O,B;s.$$.dirty&1&&a(2,g=(O=u==null?void 0:u.options)==null?void 0:O.allowEmailAuth),s.$$.dirty&1&&a(1,b=(B=u==null?void 0:u.options)==null?void 0:B.allowUsernameAuth),s.$$.dirty&6&&a(5,f=b&&g?"YOUR_USERNAME_OR_EMAIL":b?"YOUR_USERNAME":"YOUR_EMAIL"),s.$$.dirty&1&&a(4,R=[{code:200,body:JSON.stringify({token:"JWT_TOKEN",record:ft.dummyCollectionRecord(u)},null,2)},{code:400,body:`
|
||||
`),T.$set(h),(!H||o&1)&&le!==(le=e[0].name+"")&&De(fe,le),e[1]?v||(v=mt(),v.c(),v.m($,ge)):v&&(v.d(1),v=null),e[1]&&e[2]?w||(w=_t(),w.c(),w.m($,Se)):w&&(w.d(1),w=null),e[2]?y||(y=kt(),y.c(),y.m($,ve)):y&&(y.d(1),y=null),o&24&&(ae=oe(e[4]),O=pt(O,o,st,1,e,ae,lt,z,Pt,gt,null,bt)),o&24&&(X=oe(e[4]),Rt(),S=pt(S,o,it,1,e,X,ot,G,At,St,null,ht),Ct())},i(e){if(!H){Z(T.$$.fragment,e),Z(B.$$.fragment,e),Z(F.$$.fragment,e);for(let o=0;o<X.length;o+=1)Z(S[o]);H=!0}},o(e){x(T.$$.fragment,e),x(B.$$.fragment,e),x(F.$$.fragment,e);for(let o=0;o<S.length;o+=1)x(S[o]);H=!1},d(e){e&&(c(l),c(f),c(u),c(ce),c(de),c(N),c(ue),c(U),c(he),c(V),c(be),c(M),c(ye),c(j),c($e),c(E),c(Ue),c(K),c(Me),c(L)),P&&P.d(),ie(T,e),v&&v.d(),w&&w.d(),y&&y.d(),ie(B),ie(F);for(let o=0;o<O.length;o+=1)O[o].d();for(let o=0;o<S.length;o+=1)S[o].d()}}}function qt(s,l,a){let i,g,b,f,{collection:u}=l,_=200,R=[];const A=C=>a(3,_=C.code);return s.$$set=C=>{"collection"in C&&a(0,u=C.collection)},s.$$.update=()=>{var C,q;s.$$.dirty&1&&a(2,g=(C=u==null?void 0:u.options)==null?void 0:C.allowEmailAuth),s.$$.dirty&1&&a(1,b=(q=u==null?void 0:u.options)==null?void 0:q.allowUsernameAuth),s.$$.dirty&6&&a(5,f=b&&g?"YOUR_USERNAME_OR_EMAIL":b?"YOUR_USERNAME":"YOUR_EMAIL"),s.$$.dirty&1&&a(4,R=[{code:200,body:JSON.stringify({token:"JWT_TOKEN",record:ft.dummyCollectionRecord(u)},null,2)},{code:400,body:`
|
||||
{
|
||||
"code": 400,
|
||||
"message": "Failed to authenticate.",
|
||||
@@ -95,4 +95,4 @@ import{S as wt,i as yt,s as $t,N as vt,O as oe,e as n,w as p,b as d,c as ne,f as
|
||||
}
|
||||
}
|
||||
}
|
||||
`}])},a(6,i=ft.getApiExampleUrl(At.baseUrl)),[u,b,g,_,R,f,i,C]}class Yt extends wt{constructor(l){super(),yt(this,l,Bt,Lt,$t,{collection:0})}}export{Yt as default};
|
||||
`}])},a(6,i=ft.getApiExampleUrl(Ot.baseUrl)),[u,b,g,_,R,f,i,A]}class Yt extends wt{constructor(l){super(),yt(this,l,qt,Lt,$t,{collection:0})}}export{Yt as default};
|
||||
+14
File diff suppressed because one or more lines are too long
-14
File diff suppressed because one or more lines are too long
+3
-3
@@ -1,4 +1,4 @@
|
||||
import{S as Pe,i as Se,s as Oe,O as Y,e as r,w as v,b as k,c as Ce,f as b,g as d,h as n,m as $e,x as j,P as _e,Q as ye,k as Re,R as Te,n as Ee,t as ee,a as te,o as m,d as we,C as qe,p as Ae,r as H,u as Be,N as Ue}from"./index-ddbc82bb.js";import{S as De}from"./SdkTabs-ca27f837.js";function he(o,l,s){const a=o.slice();return a[5]=l[s],a}function ke(o,l,s){const a=o.slice();return a[5]=l[s],a}function ge(o,l){let s,a=l[5].code+"",_,u,i,p;function f(){return l[4](l[5])}return{key:o,first:null,c(){s=r("button"),_=v(a),u=k(),b(s,"class","tab-item"),H(s,"active",l[1]===l[5].code),this.first=s},m(C,$){d(C,s,$),n(s,_),n(s,u),i||(p=Be(s,"click",f),i=!0)},p(C,$){l=C,$&4&&a!==(a=l[5].code+"")&&j(_,a),$&6&&H(s,"active",l[1]===l[5].code)},d(C){C&&m(s),i=!1,p()}}}function ve(o,l){let s,a,_,u;return a=new Ue({props:{content:l[5].body}}),{key:o,first:null,c(){s=r("div"),Ce(a.$$.fragment),_=k(),b(s,"class","tab-item"),H(s,"active",l[1]===l[5].code),this.first=s},m(i,p){d(i,s,p),$e(a,s,null),n(s,_),u=!0},p(i,p){l=i;const f={};p&4&&(f.content=l[5].body),a.$set(f),(!u||p&6)&&H(s,"active",l[1]===l[5].code)},i(i){u||(ee(a.$$.fragment,i),u=!0)},o(i){te(a.$$.fragment,i),u=!1},d(i){i&&m(s),we(a)}}}function Ne(o){var pe,fe;let l,s,a=o[0].name+"",_,u,i,p,f,C,$,D=o[0].name+"",F,le,se,I,L,w,Q,y,z,P,N,ae,K,R,ne,G,M=o[0].name+"",J,oe,V,T,X,E,Z,q,x,S,A,g=[],ie=new Map,ce,B,h=[],re=new Map,O;w=new De({props:{js:`
|
||||
import{S as Pe,i as Se,s as Oe,O as Y,e as r,v,b as k,c as Ce,f as b,g as d,h as n,m as $e,w as j,P as _e,Q as ye,k as Re,R as Te,n as Ae,t as ee,a as te,o as m,d as we,C as Ee,A as qe,q as H,r as Be,N as Ue}from"./index-CK4y5i6D.js";import{S as De}from"./SdkTabs-EzF7kdbJ.js";function he(o,l,s){const a=o.slice();return a[5]=l[s],a}function ke(o,l,s){const a=o.slice();return a[5]=l[s],a}function ge(o,l){let s,a=l[5].code+"",_,u,i,p;function f(){return l[4](l[5])}return{key:o,first:null,c(){s=r("button"),_=v(a),u=k(),b(s,"class","tab-item"),H(s,"active",l[1]===l[5].code),this.first=s},m(C,$){d(C,s,$),n(s,_),n(s,u),i||(p=Be(s,"click",f),i=!0)},p(C,$){l=C,$&4&&a!==(a=l[5].code+"")&&j(_,a),$&6&&H(s,"active",l[1]===l[5].code)},d(C){C&&m(s),i=!1,p()}}}function ve(o,l){let s,a,_,u;return a=new Ue({props:{content:l[5].body}}),{key:o,first:null,c(){s=r("div"),Ce(a.$$.fragment),_=k(),b(s,"class","tab-item"),H(s,"active",l[1]===l[5].code),this.first=s},m(i,p){d(i,s,p),$e(a,s,null),n(s,_),u=!0},p(i,p){l=i;const f={};p&4&&(f.content=l[5].body),a.$set(f),(!u||p&6)&&H(s,"active",l[1]===l[5].code)},i(i){u||(ee(a.$$.fragment,i),u=!0)},o(i){te(a.$$.fragment,i),u=!1},d(i){i&&m(s),we(a)}}}function Ne(o){var pe,fe;let l,s,a=o[0].name+"",_,u,i,p,f,C,$,D=o[0].name+"",F,le,se,I,L,w,Q,y,z,P,N,ae,K,R,ne,G,M=o[0].name+"",J,oe,V,T,X,A,Z,E,x,S,q,g=[],ie=new Map,ce,B,h=[],re=new Map,O;w=new De({props:{js:`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${o[3]}');
|
||||
@@ -21,7 +21,7 @@ import{S as Pe,i as Se,s as Oe,O as Y,e as r,w as v,b as k,c as Ce,f as b,g as d
|
||||
'YOUR_PASSWORD',
|
||||
);
|
||||
`}});let W=Y(o[2]);const de=e=>e[5].code;for(let e=0;e<W.length;e+=1){let t=ke(o,W,e),c=de(t);ie.set(c,g[e]=ge(c,t))}let U=Y(o[2]);const me=e=>e[5].code;for(let e=0;e<U.length;e+=1){let t=he(o,U,e),c=me(t);re.set(c,h[e]=ve(c,t))}return{c(){l=r("h3"),s=v("Confirm email change ("),_=v(a),u=v(")"),i=k(),p=r("div"),f=r("p"),C=v("Confirms "),$=r("strong"),F=v(D),le=v(" email change request."),se=k(),I=r("p"),I.textContent=`After this request all previously issued tokens for the specific record will be automatically
|
||||
invalidated.`,L=k(),Ce(w.$$.fragment),Q=k(),y=r("h6"),y.textContent="API details",z=k(),P=r("div"),N=r("strong"),N.textContent="POST",ae=k(),K=r("div"),R=r("p"),ne=v("/api/collections/"),G=r("strong"),J=v(M),oe=v("/confirm-email-change"),V=k(),T=r("div"),T.textContent="Body Parameters",X=k(),E=r("table"),E.innerHTML='<thead><tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr></thead> <tbody><tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>token</span></div></td> <td><span class="label">String</span></td> <td>The token from the change email request email.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>password</span></div></td> <td><span class="label">String</span></td> <td>The account password to confirm the email change.</td></tr></tbody>',Z=k(),q=r("div"),q.textContent="Responses",x=k(),S=r("div"),A=r("div");for(let e=0;e<g.length;e+=1)g[e].c();ce=k(),B=r("div");for(let e=0;e<h.length;e+=1)h[e].c();b(l,"class","m-b-sm"),b(p,"class","content txt-lg m-b-sm"),b(y,"class","m-b-xs"),b(N,"class","label label-primary"),b(K,"class","content"),b(P,"class","alert alert-success"),b(T,"class","section-title"),b(E,"class","table-compact table-border m-b-base"),b(q,"class","section-title"),b(A,"class","tabs-header compact combined left"),b(B,"class","tabs-content"),b(S,"class","tabs")},m(e,t){d(e,l,t),n(l,s),n(l,_),n(l,u),d(e,i,t),d(e,p,t),n(p,f),n(f,C),n(f,$),n($,F),n(f,le),n(p,se),n(p,I),d(e,L,t),$e(w,e,t),d(e,Q,t),d(e,y,t),d(e,z,t),d(e,P,t),n(P,N),n(P,ae),n(P,K),n(K,R),n(R,ne),n(R,G),n(G,J),n(R,oe),d(e,V,t),d(e,T,t),d(e,X,t),d(e,E,t),d(e,Z,t),d(e,q,t),d(e,x,t),d(e,S,t),n(S,A);for(let c=0;c<g.length;c+=1)g[c]&&g[c].m(A,null);n(S,ce),n(S,B);for(let c=0;c<h.length;c+=1)h[c]&&h[c].m(B,null);O=!0},p(e,[t]){var ue,be;(!O||t&1)&&a!==(a=e[0].name+"")&&j(_,a),(!O||t&1)&&D!==(D=e[0].name+"")&&j(F,D);const c={};t&9&&(c.js=`
|
||||
invalidated.`,L=k(),Ce(w.$$.fragment),Q=k(),y=r("h6"),y.textContent="API details",z=k(),P=r("div"),N=r("strong"),N.textContent="POST",ae=k(),K=r("div"),R=r("p"),ne=v("/api/collections/"),G=r("strong"),J=v(M),oe=v("/confirm-email-change"),V=k(),T=r("div"),T.textContent="Body Parameters",X=k(),A=r("table"),A.innerHTML='<thead><tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr></thead> <tbody><tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>token</span></div></td> <td><span class="label">String</span></td> <td>The token from the change email request email.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>password</span></div></td> <td><span class="label">String</span></td> <td>The account password to confirm the email change.</td></tr></tbody>',Z=k(),E=r("div"),E.textContent="Responses",x=k(),S=r("div"),q=r("div");for(let e=0;e<g.length;e+=1)g[e].c();ce=k(),B=r("div");for(let e=0;e<h.length;e+=1)h[e].c();b(l,"class","m-b-sm"),b(p,"class","content txt-lg m-b-sm"),b(y,"class","m-b-xs"),b(N,"class","label label-primary"),b(K,"class","content"),b(P,"class","alert alert-success"),b(T,"class","section-title"),b(A,"class","table-compact table-border m-b-base"),b(E,"class","section-title"),b(q,"class","tabs-header compact combined left"),b(B,"class","tabs-content"),b(S,"class","tabs")},m(e,t){d(e,l,t),n(l,s),n(l,_),n(l,u),d(e,i,t),d(e,p,t),n(p,f),n(f,C),n(f,$),n($,F),n(f,le),n(p,se),n(p,I),d(e,L,t),$e(w,e,t),d(e,Q,t),d(e,y,t),d(e,z,t),d(e,P,t),n(P,N),n(P,ae),n(P,K),n(K,R),n(R,ne),n(R,G),n(G,J),n(R,oe),d(e,V,t),d(e,T,t),d(e,X,t),d(e,A,t),d(e,Z,t),d(e,E,t),d(e,x,t),d(e,S,t),n(S,q);for(let c=0;c<g.length;c+=1)g[c]&&g[c].m(q,null);n(S,ce),n(S,B);for(let c=0;c<h.length;c+=1)h[c]&&h[c].m(B,null);O=!0},p(e,[t]){var ue,be;(!O||t&1)&&a!==(a=e[0].name+"")&&j(_,a),(!O||t&1)&&D!==(D=e[0].name+"")&&j(F,D);const c={};t&9&&(c.js=`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${e[3]}');
|
||||
@@ -43,7 +43,7 @@ import{S as Pe,i as Se,s as Oe,O as Y,e as r,w as v,b as k,c as Ce,f as b,g as d
|
||||
'TOKEN',
|
||||
'YOUR_PASSWORD',
|
||||
);
|
||||
`),w.$set(c),(!O||t&1)&&M!==(M=e[0].name+"")&&j(J,M),t&6&&(W=Y(e[2]),g=_e(g,t,de,1,e,W,ie,A,ye,ge,null,ke)),t&6&&(U=Y(e[2]),Re(),h=_e(h,t,me,1,e,U,re,B,Te,ve,null,he),Ee())},i(e){if(!O){ee(w.$$.fragment,e);for(let t=0;t<U.length;t+=1)ee(h[t]);O=!0}},o(e){te(w.$$.fragment,e);for(let t=0;t<h.length;t+=1)te(h[t]);O=!1},d(e){e&&(m(l),m(i),m(p),m(L),m(Q),m(y),m(z),m(P),m(V),m(T),m(X),m(E),m(Z),m(q),m(x),m(S)),we(w,e);for(let t=0;t<g.length;t+=1)g[t].d();for(let t=0;t<h.length;t+=1)h[t].d()}}}function Ke(o,l,s){let a,{collection:_}=l,u=204,i=[];const p=f=>s(1,u=f.code);return o.$$set=f=>{"collection"in f&&s(0,_=f.collection)},s(3,a=qe.getApiExampleUrl(Ae.baseUrl)),s(2,i=[{code:204,body:"null"},{code:400,body:`
|
||||
`),w.$set(c),(!O||t&1)&&M!==(M=e[0].name+"")&&j(J,M),t&6&&(W=Y(e[2]),g=_e(g,t,de,1,e,W,ie,q,ye,ge,null,ke)),t&6&&(U=Y(e[2]),Re(),h=_e(h,t,me,1,e,U,re,B,Te,ve,null,he),Ae())},i(e){if(!O){ee(w.$$.fragment,e);for(let t=0;t<U.length;t+=1)ee(h[t]);O=!0}},o(e){te(w.$$.fragment,e);for(let t=0;t<h.length;t+=1)te(h[t]);O=!1},d(e){e&&(m(l),m(i),m(p),m(L),m(Q),m(y),m(z),m(P),m(V),m(T),m(X),m(A),m(Z),m(E),m(x),m(S)),we(w,e);for(let t=0;t<g.length;t+=1)g[t].d();for(let t=0;t<h.length;t+=1)h[t].d()}}}function Ke(o,l,s){let a,{collection:_}=l,u=204,i=[];const p=f=>s(1,u=f.code);return o.$$set=f=>{"collection"in f&&s(0,_=f.collection)},s(3,a=Ee.getApiExampleUrl(qe.baseUrl)),s(2,i=[{code:204,body:"null"},{code:400,body:`
|
||||
{
|
||||
"code": 400,
|
||||
"message": "Failed to authenticate.",
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
import{S as Ne,i as $e,s as Ce,O as K,e as c,w,b as k,c as Re,f as b,g as r,h as n,m as Ae,x as U,P as we,Q as Ee,k as ye,R as De,n as Te,t as ee,a as te,o as p,d as Oe,C as qe,p as Be,r as j,u as Me,N as Fe}from"./index-ddbc82bb.js";import{S as Ie}from"./SdkTabs-ca27f837.js";function Se(o,l,s){const a=o.slice();return a[5]=l[s],a}function Pe(o,l,s){const a=o.slice();return a[5]=l[s],a}function We(o,l){let s,a=l[5].code+"",_,m,i,u;function f(){return l[4](l[5])}return{key:o,first:null,c(){s=c("button"),_=w(a),m=k(),b(s,"class","tab-item"),j(s,"active",l[1]===l[5].code),this.first=s},m(S,P){r(S,s,P),n(s,_),n(s,m),i||(u=Me(s,"click",f),i=!0)},p(S,P){l=S,P&4&&a!==(a=l[5].code+"")&&U(_,a),P&6&&j(s,"active",l[1]===l[5].code)},d(S){S&&p(s),i=!1,u()}}}function ge(o,l){let s,a,_,m;return a=new Fe({props:{content:l[5].body}}),{key:o,first:null,c(){s=c("div"),Re(a.$$.fragment),_=k(),b(s,"class","tab-item"),j(s,"active",l[1]===l[5].code),this.first=s},m(i,u){r(i,s,u),Ae(a,s,null),n(s,_),m=!0},p(i,u){l=i;const f={};u&4&&(f.content=l[5].body),a.$set(f),(!m||u&6)&&j(s,"active",l[1]===l[5].code)},i(i){m||(ee(a.$$.fragment,i),m=!0)},o(i){te(a.$$.fragment,i),m=!1},d(i){i&&p(s),Oe(a)}}}function Ke(o){var ue,fe,me,be;let l,s,a=o[0].name+"",_,m,i,u,f,S,P,q=o[0].name+"",H,le,se,L,Q,W,z,O,G,g,B,ae,M,N,oe,J,F=o[0].name+"",V,ne,X,$,Y,C,Z,E,x,R,y,v=[],ie=new Map,de,D,h=[],ce=new Map,A;W=new Ie({props:{js:`
|
||||
import{S as Ne,i as $e,s as Ce,O as K,e as c,v as w,b as k,c as Ae,f as b,g as r,h as n,m as Re,w as U,P as we,Q as Ee,k as ye,R as De,n as Te,t as ee,a as te,o as p,d as Oe,C as qe,A as Be,q as j,r as Me,N as Fe}from"./index-CK4y5i6D.js";import{S as Ie}from"./SdkTabs-EzF7kdbJ.js";function Se(o,l,s){const a=o.slice();return a[5]=l[s],a}function Pe(o,l,s){const a=o.slice();return a[5]=l[s],a}function We(o,l){let s,a=l[5].code+"",_,m,i,u;function f(){return l[4](l[5])}return{key:o,first:null,c(){s=c("button"),_=w(a),m=k(),b(s,"class","tab-item"),j(s,"active",l[1]===l[5].code),this.first=s},m(S,P){r(S,s,P),n(s,_),n(s,m),i||(u=Me(s,"click",f),i=!0)},p(S,P){l=S,P&4&&a!==(a=l[5].code+"")&&U(_,a),P&6&&j(s,"active",l[1]===l[5].code)},d(S){S&&p(s),i=!1,u()}}}function ge(o,l){let s,a,_,m;return a=new Fe({props:{content:l[5].body}}),{key:o,first:null,c(){s=c("div"),Ae(a.$$.fragment),_=k(),b(s,"class","tab-item"),j(s,"active",l[1]===l[5].code),this.first=s},m(i,u){r(i,s,u),Re(a,s,null),n(s,_),m=!0},p(i,u){l=i;const f={};u&4&&(f.content=l[5].body),a.$set(f),(!m||u&6)&&j(s,"active",l[1]===l[5].code)},i(i){m||(ee(a.$$.fragment,i),m=!0)},o(i){te(a.$$.fragment,i),m=!1},d(i){i&&p(s),Oe(a)}}}function Ke(o){var ue,fe,me,be;let l,s,a=o[0].name+"",_,m,i,u,f,S,P,q=o[0].name+"",H,le,se,L,Q,W,z,O,G,g,B,ae,M,N,oe,J,F=o[0].name+"",V,ne,X,$,Y,C,Z,E,x,A,y,v=[],ie=new Map,de,D,h=[],ce=new Map,R;W=new Ie({props:{js:`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${o[3]}');
|
||||
@@ -35,7 +35,7 @@ import{S as Ne,i as $e,s as Ce,O as K,e as c,w,b as k,c as Re,f as b,g as r,h as
|
||||
// (after the above call all previously issued tokens are invalidated)
|
||||
await pb.collection('${(be=o[0])==null?void 0:be.name}').authWithPassword(oldAuth.email, 'NEW_PASSWORD');
|
||||
`}});let I=K(o[2]);const re=e=>e[5].code;for(let e=0;e<I.length;e+=1){let t=Pe(o,I,e),d=re(t);ie.set(d,v[e]=We(d,t))}let T=K(o[2]);const pe=e=>e[5].code;for(let e=0;e<T.length;e+=1){let t=Se(o,T,e),d=pe(t);ce.set(d,h[e]=ge(d,t))}return{c(){l=c("h3"),s=w("Confirm password reset ("),_=w(a),m=w(")"),i=k(),u=c("div"),f=c("p"),S=w("Confirms "),P=c("strong"),H=w(q),le=w(" password reset request and sets a new password."),se=k(),L=c("p"),L.textContent=`After this request all previously issued tokens for the specific record will be automatically
|
||||
invalidated.`,Q=k(),Re(W.$$.fragment),z=k(),O=c("h6"),O.textContent="API details",G=k(),g=c("div"),B=c("strong"),B.textContent="POST",ae=k(),M=c("div"),N=c("p"),oe=w("/api/collections/"),J=c("strong"),V=w(F),ne=w("/confirm-password-reset"),X=k(),$=c("div"),$.textContent="Body Parameters",Y=k(),C=c("table"),C.innerHTML='<thead><tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr></thead> <tbody><tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>token</span></div></td> <td><span class="label">String</span></td> <td>The token from the password reset request email.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>password</span></div></td> <td><span class="label">String</span></td> <td>The new password to set.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>passwordConfirm</span></div></td> <td><span class="label">String</span></td> <td>The new password confirmation.</td></tr></tbody>',Z=k(),E=c("div"),E.textContent="Responses",x=k(),R=c("div"),y=c("div");for(let e=0;e<v.length;e+=1)v[e].c();de=k(),D=c("div");for(let e=0;e<h.length;e+=1)h[e].c();b(l,"class","m-b-sm"),b(u,"class","content txt-lg m-b-sm"),b(O,"class","m-b-xs"),b(B,"class","label label-primary"),b(M,"class","content"),b(g,"class","alert alert-success"),b($,"class","section-title"),b(C,"class","table-compact table-border m-b-base"),b(E,"class","section-title"),b(y,"class","tabs-header compact combined left"),b(D,"class","tabs-content"),b(R,"class","tabs")},m(e,t){r(e,l,t),n(l,s),n(l,_),n(l,m),r(e,i,t),r(e,u,t),n(u,f),n(f,S),n(f,P),n(P,H),n(f,le),n(u,se),n(u,L),r(e,Q,t),Ae(W,e,t),r(e,z,t),r(e,O,t),r(e,G,t),r(e,g,t),n(g,B),n(g,ae),n(g,M),n(M,N),n(N,oe),n(N,J),n(J,V),n(N,ne),r(e,X,t),r(e,$,t),r(e,Y,t),r(e,C,t),r(e,Z,t),r(e,E,t),r(e,x,t),r(e,R,t),n(R,y);for(let d=0;d<v.length;d+=1)v[d]&&v[d].m(y,null);n(R,de),n(R,D);for(let d=0;d<h.length;d+=1)h[d]&&h[d].m(D,null);A=!0},p(e,[t]){var _e,he,ke,ve;(!A||t&1)&&a!==(a=e[0].name+"")&&U(_,a),(!A||t&1)&&q!==(q=e[0].name+"")&&U(H,q);const d={};t&9&&(d.js=`
|
||||
invalidated.`,Q=k(),Ae(W.$$.fragment),z=k(),O=c("h6"),O.textContent="API details",G=k(),g=c("div"),B=c("strong"),B.textContent="POST",ae=k(),M=c("div"),N=c("p"),oe=w("/api/collections/"),J=c("strong"),V=w(F),ne=w("/confirm-password-reset"),X=k(),$=c("div"),$.textContent="Body Parameters",Y=k(),C=c("table"),C.innerHTML='<thead><tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr></thead> <tbody><tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>token</span></div></td> <td><span class="label">String</span></td> <td>The token from the password reset request email.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>password</span></div></td> <td><span class="label">String</span></td> <td>The new password to set.</td></tr> <tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>passwordConfirm</span></div></td> <td><span class="label">String</span></td> <td>The new password confirmation.</td></tr></tbody>',Z=k(),E=c("div"),E.textContent="Responses",x=k(),A=c("div"),y=c("div");for(let e=0;e<v.length;e+=1)v[e].c();de=k(),D=c("div");for(let e=0;e<h.length;e+=1)h[e].c();b(l,"class","m-b-sm"),b(u,"class","content txt-lg m-b-sm"),b(O,"class","m-b-xs"),b(B,"class","label label-primary"),b(M,"class","content"),b(g,"class","alert alert-success"),b($,"class","section-title"),b(C,"class","table-compact table-border m-b-base"),b(E,"class","section-title"),b(y,"class","tabs-header compact combined left"),b(D,"class","tabs-content"),b(A,"class","tabs")},m(e,t){r(e,l,t),n(l,s),n(l,_),n(l,m),r(e,i,t),r(e,u,t),n(u,f),n(f,S),n(f,P),n(P,H),n(f,le),n(u,se),n(u,L),r(e,Q,t),Re(W,e,t),r(e,z,t),r(e,O,t),r(e,G,t),r(e,g,t),n(g,B),n(g,ae),n(g,M),n(M,N),n(N,oe),n(N,J),n(J,V),n(N,ne),r(e,X,t),r(e,$,t),r(e,Y,t),r(e,C,t),r(e,Z,t),r(e,E,t),r(e,x,t),r(e,A,t),n(A,y);for(let d=0;d<v.length;d+=1)v[d]&&v[d].m(y,null);n(A,de),n(A,D);for(let d=0;d<h.length;d+=1)h[d]&&h[d].m(D,null);R=!0},p(e,[t]){var _e,he,ke,ve;(!R||t&1)&&a!==(a=e[0].name+"")&&U(_,a),(!R||t&1)&&q!==(q=e[0].name+"")&&U(H,q);const d={};t&9&&(d.js=`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${e[3]}');
|
||||
@@ -71,7 +71,7 @@ import{S as Ne,i as $e,s as Ce,O as K,e as c,w,b as k,c as Re,f as b,g as r,h as
|
||||
// reauthenticate if needed
|
||||
// (after the above call all previously issued tokens are invalidated)
|
||||
await pb.collection('${(ve=e[0])==null?void 0:ve.name}').authWithPassword(oldAuth.email, 'NEW_PASSWORD');
|
||||
`),W.$set(d),(!A||t&1)&&F!==(F=e[0].name+"")&&U(V,F),t&6&&(I=K(e[2]),v=we(v,t,re,1,e,I,ie,y,Ee,We,null,Pe)),t&6&&(T=K(e[2]),ye(),h=we(h,t,pe,1,e,T,ce,D,De,ge,null,Se),Te())},i(e){if(!A){ee(W.$$.fragment,e);for(let t=0;t<T.length;t+=1)ee(h[t]);A=!0}},o(e){te(W.$$.fragment,e);for(let t=0;t<h.length;t+=1)te(h[t]);A=!1},d(e){e&&(p(l),p(i),p(u),p(Q),p(z),p(O),p(G),p(g),p(X),p($),p(Y),p(C),p(Z),p(E),p(x),p(R)),Oe(W,e);for(let t=0;t<v.length;t+=1)v[t].d();for(let t=0;t<h.length;t+=1)h[t].d()}}}function Ue(o,l,s){let a,{collection:_}=l,m=204,i=[];const u=f=>s(1,m=f.code);return o.$$set=f=>{"collection"in f&&s(0,_=f.collection)},s(3,a=qe.getApiExampleUrl(Be.baseUrl)),s(2,i=[{code:204,body:"null"},{code:400,body:`
|
||||
`),W.$set(d),(!R||t&1)&&F!==(F=e[0].name+"")&&U(V,F),t&6&&(I=K(e[2]),v=we(v,t,re,1,e,I,ie,y,Ee,We,null,Pe)),t&6&&(T=K(e[2]),ye(),h=we(h,t,pe,1,e,T,ce,D,De,ge,null,Se),Te())},i(e){if(!R){ee(W.$$.fragment,e);for(let t=0;t<T.length;t+=1)ee(h[t]);R=!0}},o(e){te(W.$$.fragment,e);for(let t=0;t<h.length;t+=1)te(h[t]);R=!1},d(e){e&&(p(l),p(i),p(u),p(Q),p(z),p(O),p(G),p(g),p(X),p($),p(Y),p(C),p(Z),p(E),p(x),p(A)),Oe(W,e);for(let t=0;t<v.length;t+=1)v[t].d();for(let t=0;t<h.length;t+=1)h[t].d()}}}function Ue(o,l,s){let a,{collection:_}=l,m=204,i=[];const u=f=>s(1,m=f.code);return o.$$set=f=>{"collection"in f&&s(0,_=f.collection)},s(3,a=qe.getApiExampleUrl(Be.baseUrl)),s(2,i=[{code:204,body:"null"},{code:400,body:`
|
||||
{
|
||||
"code": 400,
|
||||
"message": "Failed to authenticate.",
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
import{S as Se,i as Te,s as Be,O as D,e as r,w as g,b as k,c as ye,f as h,g as f,h as n,m as Ce,x as H,P as ke,Q as Re,k as qe,R as Oe,n as Ee,t as x,a as ee,o as d,d as Pe,C as Ne,p as Ve,r as F,u as Ke,N as Me}from"./index-ddbc82bb.js";import{S as Ae}from"./SdkTabs-ca27f837.js";function ve(o,l,s){const a=o.slice();return a[5]=l[s],a}function ge(o,l,s){const a=o.slice();return a[5]=l[s],a}function we(o,l){let s,a=l[5].code+"",b,m,i,p;function u(){return l[4](l[5])}return{key:o,first:null,c(){s=r("button"),b=g(a),m=k(),h(s,"class","tab-item"),F(s,"active",l[1]===l[5].code),this.first=s},m(w,$){f(w,s,$),n(s,b),n(s,m),i||(p=Ke(s,"click",u),i=!0)},p(w,$){l=w,$&4&&a!==(a=l[5].code+"")&&H(b,a),$&6&&F(s,"active",l[1]===l[5].code)},d(w){w&&d(s),i=!1,p()}}}function $e(o,l){let s,a,b,m;return a=new Me({props:{content:l[5].body}}),{key:o,first:null,c(){s=r("div"),ye(a.$$.fragment),b=k(),h(s,"class","tab-item"),F(s,"active",l[1]===l[5].code),this.first=s},m(i,p){f(i,s,p),Ce(a,s,null),n(s,b),m=!0},p(i,p){l=i;const u={};p&4&&(u.content=l[5].body),a.$set(u),(!m||p&6)&&F(s,"active",l[1]===l[5].code)},i(i){m||(x(a.$$.fragment,i),m=!0)},o(i){ee(a.$$.fragment,i),m=!1},d(i){i&&d(s),Pe(a)}}}function Ue(o){var fe,de,pe,ue;let l,s,a=o[0].name+"",b,m,i,p,u,w,$,K=o[0].name+"",I,te,L,y,Q,T,z,C,M,le,A,B,se,G,U=o[0].name+"",J,ae,W,R,X,q,Y,O,Z,P,E,v=[],oe=new Map,ne,N,_=[],ie=new Map,S;y=new Ae({props:{js:`
|
||||
import{S as Se,i as Te,s as Be,O as D,e as r,v as g,b as k,c as ye,f as h,g as f,h as n,m as Ce,w as H,P as ke,Q as qe,k as Re,R as Oe,n as Ae,t as x,a as ee,o as d,d as Pe,C as Ee,A as Ne,q as F,r as Ve,N as Ke}from"./index-CK4y5i6D.js";import{S as Me}from"./SdkTabs-EzF7kdbJ.js";function ve(o,l,s){const a=o.slice();return a[5]=l[s],a}function ge(o,l,s){const a=o.slice();return a[5]=l[s],a}function we(o,l){let s,a=l[5].code+"",b,m,i,p;function u(){return l[4](l[5])}return{key:o,first:null,c(){s=r("button"),b=g(a),m=k(),h(s,"class","tab-item"),F(s,"active",l[1]===l[5].code),this.first=s},m(w,$){f(w,s,$),n(s,b),n(s,m),i||(p=Ve(s,"click",u),i=!0)},p(w,$){l=w,$&4&&a!==(a=l[5].code+"")&&H(b,a),$&6&&F(s,"active",l[1]===l[5].code)},d(w){w&&d(s),i=!1,p()}}}function $e(o,l){let s,a,b,m;return a=new Ke({props:{content:l[5].body}}),{key:o,first:null,c(){s=r("div"),ye(a.$$.fragment),b=k(),h(s,"class","tab-item"),F(s,"active",l[1]===l[5].code),this.first=s},m(i,p){f(i,s,p),Ce(a,s,null),n(s,b),m=!0},p(i,p){l=i;const u={};p&4&&(u.content=l[5].body),a.$set(u),(!m||p&6)&&F(s,"active",l[1]===l[5].code)},i(i){m||(x(a.$$.fragment,i),m=!0)},o(i){ee(a.$$.fragment,i),m=!1},d(i){i&&d(s),Pe(a)}}}function Ue(o){var fe,de,pe,ue;let l,s,a=o[0].name+"",b,m,i,p,u,w,$,V=o[0].name+"",I,te,L,y,Q,T,z,C,K,le,M,B,se,G,U=o[0].name+"",J,ae,W,q,X,R,Y,O,Z,P,A,v=[],oe=new Map,ne,E,_=[],ie=new Map,S;y=new Me({props:{js:`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${o[3]}');
|
||||
@@ -20,7 +20,7 @@ import{S as Se,i as Te,s as Be,O as D,e as r,w as g,b as k,c as ye,f as h,g as f
|
||||
|
||||
// optionally refresh the previous authStore state with the latest record changes
|
||||
await pb.collection('${(ue=o[0])==null?void 0:ue.name}').authRefresh();
|
||||
`}});let j=D(o[2]);const ce=e=>e[5].code;for(let e=0;e<j.length;e+=1){let t=ge(o,j,e),c=ce(t);oe.set(c,v[e]=we(c,t))}let V=D(o[2]);const re=e=>e[5].code;for(let e=0;e<V.length;e+=1){let t=ve(o,V,e),c=re(t);ie.set(c,_[e]=$e(c,t))}return{c(){l=r("h3"),s=g("Confirm verification ("),b=g(a),m=g(")"),i=k(),p=r("div"),u=r("p"),w=g("Confirms "),$=r("strong"),I=g(K),te=g(" account verification request."),L=k(),ye(y.$$.fragment),Q=k(),T=r("h6"),T.textContent="API details",z=k(),C=r("div"),M=r("strong"),M.textContent="POST",le=k(),A=r("div"),B=r("p"),se=g("/api/collections/"),G=r("strong"),J=g(U),ae=g("/confirm-verification"),W=k(),R=r("div"),R.textContent="Body Parameters",X=k(),q=r("table"),q.innerHTML='<thead><tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr></thead> <tbody><tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>token</span></div></td> <td><span class="label">String</span></td> <td>The token from the verification request email.</td></tr></tbody>',Y=k(),O=r("div"),O.textContent="Responses",Z=k(),P=r("div"),E=r("div");for(let e=0;e<v.length;e+=1)v[e].c();ne=k(),N=r("div");for(let e=0;e<_.length;e+=1)_[e].c();h(l,"class","m-b-sm"),h(p,"class","content txt-lg m-b-sm"),h(T,"class","m-b-xs"),h(M,"class","label label-primary"),h(A,"class","content"),h(C,"class","alert alert-success"),h(R,"class","section-title"),h(q,"class","table-compact table-border m-b-base"),h(O,"class","section-title"),h(E,"class","tabs-header compact combined left"),h(N,"class","tabs-content"),h(P,"class","tabs")},m(e,t){f(e,l,t),n(l,s),n(l,b),n(l,m),f(e,i,t),f(e,p,t),n(p,u),n(u,w),n(u,$),n($,I),n(u,te),f(e,L,t),Ce(y,e,t),f(e,Q,t),f(e,T,t),f(e,z,t),f(e,C,t),n(C,M),n(C,le),n(C,A),n(A,B),n(B,se),n(B,G),n(G,J),n(B,ae),f(e,W,t),f(e,R,t),f(e,X,t),f(e,q,t),f(e,Y,t),f(e,O,t),f(e,Z,t),f(e,P,t),n(P,E);for(let c=0;c<v.length;c+=1)v[c]&&v[c].m(E,null);n(P,ne),n(P,N);for(let c=0;c<_.length;c+=1)_[c]&&_[c].m(N,null);S=!0},p(e,[t]){var me,he,be,_e;(!S||t&1)&&a!==(a=e[0].name+"")&&H(b,a),(!S||t&1)&&K!==(K=e[0].name+"")&&H(I,K);const c={};t&9&&(c.js=`
|
||||
`}});let j=D(o[2]);const ce=e=>e[5].code;for(let e=0;e<j.length;e+=1){let t=ge(o,j,e),c=ce(t);oe.set(c,v[e]=we(c,t))}let N=D(o[2]);const re=e=>e[5].code;for(let e=0;e<N.length;e+=1){let t=ve(o,N,e),c=re(t);ie.set(c,_[e]=$e(c,t))}return{c(){l=r("h3"),s=g("Confirm verification ("),b=g(a),m=g(")"),i=k(),p=r("div"),u=r("p"),w=g("Confirms "),$=r("strong"),I=g(V),te=g(" account verification request."),L=k(),ye(y.$$.fragment),Q=k(),T=r("h6"),T.textContent="API details",z=k(),C=r("div"),K=r("strong"),K.textContent="POST",le=k(),M=r("div"),B=r("p"),se=g("/api/collections/"),G=r("strong"),J=g(U),ae=g("/confirm-verification"),W=k(),q=r("div"),q.textContent="Body Parameters",X=k(),R=r("table"),R.innerHTML='<thead><tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr></thead> <tbody><tr><td><div class="inline-flex"><span class="label label-success">Required</span> <span>token</span></div></td> <td><span class="label">String</span></td> <td>The token from the verification request email.</td></tr></tbody>',Y=k(),O=r("div"),O.textContent="Responses",Z=k(),P=r("div"),A=r("div");for(let e=0;e<v.length;e+=1)v[e].c();ne=k(),E=r("div");for(let e=0;e<_.length;e+=1)_[e].c();h(l,"class","m-b-sm"),h(p,"class","content txt-lg m-b-sm"),h(T,"class","m-b-xs"),h(K,"class","label label-primary"),h(M,"class","content"),h(C,"class","alert alert-success"),h(q,"class","section-title"),h(R,"class","table-compact table-border m-b-base"),h(O,"class","section-title"),h(A,"class","tabs-header compact combined left"),h(E,"class","tabs-content"),h(P,"class","tabs")},m(e,t){f(e,l,t),n(l,s),n(l,b),n(l,m),f(e,i,t),f(e,p,t),n(p,u),n(u,w),n(u,$),n($,I),n(u,te),f(e,L,t),Ce(y,e,t),f(e,Q,t),f(e,T,t),f(e,z,t),f(e,C,t),n(C,K),n(C,le),n(C,M),n(M,B),n(B,se),n(B,G),n(G,J),n(B,ae),f(e,W,t),f(e,q,t),f(e,X,t),f(e,R,t),f(e,Y,t),f(e,O,t),f(e,Z,t),f(e,P,t),n(P,A);for(let c=0;c<v.length;c+=1)v[c]&&v[c].m(A,null);n(P,ne),n(P,E);for(let c=0;c<_.length;c+=1)_[c]&&_[c].m(E,null);S=!0},p(e,[t]){var me,he,be,_e;(!S||t&1)&&a!==(a=e[0].name+"")&&H(b,a),(!S||t&1)&&V!==(V=e[0].name+"")&&H(I,V);const c={};t&9&&(c.js=`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${e[3]}');
|
||||
@@ -42,7 +42,7 @@ import{S as Se,i as Te,s as Be,O as D,e as r,w as g,b as k,c as ye,f as h,g as f
|
||||
|
||||
// optionally refresh the previous authStore state with the latest record changes
|
||||
await pb.collection('${(_e=e[0])==null?void 0:_e.name}').authRefresh();
|
||||
`),y.$set(c),(!S||t&1)&&U!==(U=e[0].name+"")&&H(J,U),t&6&&(j=D(e[2]),v=ke(v,t,ce,1,e,j,oe,E,Re,we,null,ge)),t&6&&(V=D(e[2]),qe(),_=ke(_,t,re,1,e,V,ie,N,Oe,$e,null,ve),Ee())},i(e){if(!S){x(y.$$.fragment,e);for(let t=0;t<V.length;t+=1)x(_[t]);S=!0}},o(e){ee(y.$$.fragment,e);for(let t=0;t<_.length;t+=1)ee(_[t]);S=!1},d(e){e&&(d(l),d(i),d(p),d(L),d(Q),d(T),d(z),d(C),d(W),d(R),d(X),d(q),d(Y),d(O),d(Z),d(P)),Pe(y,e);for(let t=0;t<v.length;t+=1)v[t].d();for(let t=0;t<_.length;t+=1)_[t].d()}}}function je(o,l,s){let a,{collection:b}=l,m=204,i=[];const p=u=>s(1,m=u.code);return o.$$set=u=>{"collection"in u&&s(0,b=u.collection)},s(3,a=Ne.getApiExampleUrl(Ve.baseUrl)),s(2,i=[{code:204,body:"null"},{code:400,body:`
|
||||
`),y.$set(c),(!S||t&1)&&U!==(U=e[0].name+"")&&H(J,U),t&6&&(j=D(e[2]),v=ke(v,t,ce,1,e,j,oe,A,qe,we,null,ge)),t&6&&(N=D(e[2]),Re(),_=ke(_,t,re,1,e,N,ie,E,Oe,$e,null,ve),Ae())},i(e){if(!S){x(y.$$.fragment,e);for(let t=0;t<N.length;t+=1)x(_[t]);S=!0}},o(e){ee(y.$$.fragment,e);for(let t=0;t<_.length;t+=1)ee(_[t]);S=!1},d(e){e&&(d(l),d(i),d(p),d(L),d(Q),d(T),d(z),d(C),d(W),d(q),d(X),d(R),d(Y),d(O),d(Z),d(P)),Pe(y,e);for(let t=0;t<v.length;t+=1)v[t].d();for(let t=0;t<_.length;t+=1)_[t].d()}}}function je(o,l,s){let a,{collection:b}=l,m=204,i=[];const p=u=>s(1,m=u.code);return o.$$set=u=>{"collection"in u&&s(0,b=u.collection)},s(3,a=Ee.getApiExampleUrl(Ne.baseUrl)),s(2,i=[{code:204,body:"null"},{code:400,body:`
|
||||
{
|
||||
"code": 400,
|
||||
"message": "Failed to authenticate.",
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
import{S as qt,i as Ot,s as Mt,C as Q,O as ne,N as Tt,e as i,w as _,b as u,c as _e,f as v,g as r,h as n,m as he,x,P as Be,Q as ht,k as Ht,R as Lt,n as Pt,t as ue,a as fe,o as d,d as ke,p as Ft,r as ye,u as Rt,y as ae}from"./index-ddbc82bb.js";import{S as At}from"./SdkTabs-ca27f837.js";import{F as Bt}from"./FieldsQueryParam-2cf2725e.js";function kt(o,e,t){const a=o.slice();return a[8]=e[t],a}function yt(o,e,t){const a=o.slice();return a[8]=e[t],a}function vt(o,e,t){const a=o.slice();return a[13]=e[t],a}function gt(o){let e;return{c(){e=i("p"),e.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",v(e,"class","txt-hint txt-sm txt-right")},m(t,a){r(t,e,a)},d(t){t&&d(e)}}}function wt(o){let e,t,a,f,m,c,p,y,S,T,w,H,D,E,P,I,j,B,$,N,q,g,b;function O(h,C){var ee,K;return(K=(ee=h[0])==null?void 0:ee.options)!=null&&K.requireEmail?Dt:jt}let z=O(o),F=z(o);return{c(){e=i("tr"),e.innerHTML='<td colspan="3" class="txt-hint">Auth fields</td>',t=u(),a=i("tr"),a.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>username</span></div></td> <td><span class="label">String</span></td> <td>The username of the auth record.
|
||||
<br/>
|
||||
If not set, it will be auto generated.</td>`,f=u(),m=i("tr"),c=i("td"),p=i("div"),F.c(),y=u(),S=i("span"),S.textContent="email",T=u(),w=i("td"),w.innerHTML='<span class="label">String</span>',H=u(),D=i("td"),D.textContent="Auth record email address.",E=u(),P=i("tr"),P.innerHTML='<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>emailVisibility</span></div></td> <td><span class="label">Boolean</span></td> <td>Whether to show/hide the auth record email when fetching the record data.</td>',I=u(),j=i("tr"),j.innerHTML='<td><div class="inline-flex"><span class="label label-success">Required</span> <span>password</span></div></td> <td><span class="label">String</span></td> <td>Auth record password.</td>',B=u(),$=i("tr"),$.innerHTML='<td><div class="inline-flex"><span class="label label-success">Required</span> <span>passwordConfirm</span></div></td> <td><span class="label">String</span></td> <td>Auth record password confirmation.</td>',N=u(),q=i("tr"),q.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>verified</span></div></td> <td><span class="label">Boolean</span></td> <td>Indicates whether the auth record is verified or not.
|
||||
<br/>
|
||||
This field can be set only by admins or auth records with "Manage" access.</td>`,g=u(),b=i("tr"),b.innerHTML='<td colspan="3" class="txt-hint">Schema fields</td>',v(p,"class","inline-flex")},m(h,C){r(h,e,C),r(h,t,C),r(h,a,C),r(h,f,C),r(h,m,C),n(m,c),n(c,p),F.m(p,null),n(p,y),n(p,S),n(m,T),n(m,w),n(m,H),n(m,D),r(h,E,C),r(h,P,C),r(h,I,C),r(h,j,C),r(h,B,C),r(h,$,C),r(h,N,C),r(h,q,C),r(h,g,C),r(h,b,C)},p(h,C){z!==(z=O(h))&&(F.d(1),F=z(h),F&&(F.c(),F.m(p,y)))},d(h){h&&(d(e),d(t),d(a),d(f),d(m),d(E),d(P),d(I),d(j),d(B),d($),d(N),d(q),d(g),d(b)),F.d()}}}function jt(o){let e;return{c(){e=i("span"),e.textContent="Optional",v(e,"class","label label-warning")},m(t,a){r(t,e,a)},d(t){t&&d(e)}}}function Dt(o){let e;return{c(){e=i("span"),e.textContent="Required",v(e,"class","label label-success")},m(t,a){r(t,e,a)},d(t){t&&d(e)}}}function Nt(o){let e;return{c(){e=i("span"),e.textContent="Optional",v(e,"class","label label-warning")},m(t,a){r(t,e,a)},d(t){t&&d(e)}}}function Vt(o){let e;return{c(){e=i("span"),e.textContent="Required",v(e,"class","label label-success")},m(t,a){r(t,e,a)},d(t){t&&d(e)}}}function Jt(o){var m;let e,t=((m=o[13].options)==null?void 0:m.maxSelect)===1?"id":"ids",a,f;return{c(){e=_("Relation record "),a=_(t),f=_(".")},m(c,p){r(c,e,p),r(c,a,p),r(c,f,p)},p(c,p){var y;p&1&&t!==(t=((y=c[13].options)==null?void 0:y.maxSelect)===1?"id":"ids")&&x(a,t)},d(c){c&&(d(e),d(a),d(f))}}}function Et(o){let e,t,a,f,m;return{c(){e=_("File object."),t=i("br"),a=_(`
|
||||
Set to `),f=i("code"),f.textContent="null",m=_(" to delete already uploaded file(s).")},m(c,p){r(c,e,p),r(c,t,p),r(c,a,p),r(c,f,p),r(c,m,p)},p:ae,d(c){c&&(d(e),d(t),d(a),d(f),d(m))}}}function It(o){let e;return{c(){e=_("URL address.")},m(t,a){r(t,e,a)},p:ae,d(t){t&&d(e)}}}function Ut(o){let e;return{c(){e=_("Email address.")},m(t,a){r(t,e,a)},p:ae,d(t){t&&d(e)}}}function Qt(o){let e;return{c(){e=_("JSON array or object.")},m(t,a){r(t,e,a)},p:ae,d(t){t&&d(e)}}}function zt(o){let e;return{c(){e=_("Number value.")},m(t,a){r(t,e,a)},p:ae,d(t){t&&d(e)}}}function Kt(o){let e;return{c(){e=_("Plain text value.")},m(t,a){r(t,e,a)},p:ae,d(t){t&&d(e)}}}function $t(o,e){let t,a,f,m,c,p=e[13].name+"",y,S,T,w,H=Q.getFieldValueType(e[13])+"",D,E,P,I;function j(b,O){return b[13].required?Vt:Nt}let B=j(e),$=B(e);function N(b,O){if(b[13].type==="text")return Kt;if(b[13].type==="number")return zt;if(b[13].type==="json")return Qt;if(b[13].type==="email")return Ut;if(b[13].type==="url")return It;if(b[13].type==="file")return Et;if(b[13].type==="relation")return Jt}let q=N(e),g=q&&q(e);return{key:o,first:null,c(){t=i("tr"),a=i("td"),f=i("div"),$.c(),m=u(),c=i("span"),y=_(p),S=u(),T=i("td"),w=i("span"),D=_(H),E=u(),P=i("td"),g&&g.c(),I=u(),v(f,"class","inline-flex"),v(w,"class","label"),this.first=t},m(b,O){r(b,t,O),n(t,a),n(a,f),$.m(f,null),n(f,m),n(f,c),n(c,y),n(t,S),n(t,T),n(T,w),n(w,D),n(t,E),n(t,P),g&&g.m(P,null),n(t,I)},p(b,O){e=b,B!==(B=j(e))&&($.d(1),$=B(e),$&&($.c(),$.m(f,m))),O&1&&p!==(p=e[13].name+"")&&x(y,p),O&1&&H!==(H=Q.getFieldValueType(e[13])+"")&&x(D,H),q===(q=N(e))&&g?g.p(e,O):(g&&g.d(1),g=q&&q(e),g&&(g.c(),g.m(P,null)))},d(b){b&&d(t),$.d(),g&&g.d()}}}function Ct(o,e){let t,a=e[8].code+"",f,m,c,p;function y(){return e[7](e[8])}return{key:o,first:null,c(){t=i("button"),f=_(a),m=u(),v(t,"class","tab-item"),ye(t,"active",e[2]===e[8].code),this.first=t},m(S,T){r(S,t,T),n(t,f),n(t,m),c||(p=Rt(t,"click",y),c=!0)},p(S,T){e=S,T&8&&a!==(a=e[8].code+"")&&x(f,a),T&12&&ye(t,"active",e[2]===e[8].code)},d(S){S&&d(t),c=!1,p()}}}function St(o,e){let t,a,f,m;return a=new Tt({props:{content:e[8].body}}),{key:o,first:null,c(){t=i("div"),_e(a.$$.fragment),f=u(),v(t,"class","tab-item"),ye(t,"active",e[2]===e[8].code),this.first=t},m(c,p){r(c,t,p),he(a,t,null),n(t,f),m=!0},p(c,p){e=c;const y={};p&8&&(y.content=e[8].body),a.$set(y),(!m||p&12)&&ye(t,"active",e[2]===e[8].code)},i(c){m||(ue(a.$$.fragment,c),m=!0)},o(c){fe(a.$$.fragment,c),m=!1},d(c){c&&d(t),ke(a)}}}function Wt(o){var ot,rt,dt,ct,pt;let e,t,a=o[0].name+"",f,m,c,p,y,S,T,w=o[0].name+"",H,D,E,P,I,j,B,$,N,q,g,b,O,z,F,h,C,ee,K=o[0].name+"",ve,je,De,ge,se,we,W,$e,Ne,U,Ce,Ve,Se,V=[],Je=new Map,Te,ie,qe,Y,Oe,Ee,oe,G,Me,Ie,He,Ue,M,Qe,te,ze,Ke,We,Le,Ye,Pe,Ge,Xe,Ze,Fe,xe,et,le,Re,re,Ae,X,de,J=[],tt=new Map,lt,ce,R=[],nt=new Map,Z;$=new At({props:{js:`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${o[5]}');
|
||||
|
||||
...
|
||||
|
||||
// example create data
|
||||
const data = ${JSON.stringify(Object.assign({},o[4],Q.dummyCollectionSchemaData(o[0])),null,4)};
|
||||
|
||||
const record = await pb.collection('${(ot=o[0])==null?void 0:ot.name}').create(data);
|
||||
`+(o[1]?`
|
||||
// (optional) send an email verification request
|
||||
await pb.collection('${(rt=o[0])==null?void 0:rt.name}').requestVerification('test@example.com');
|
||||
`:""),dart:`
|
||||
import 'package:pocketbase/pocketbase.dart';
|
||||
|
||||
final pb = PocketBase('${o[5]}');
|
||||
|
||||
...
|
||||
|
||||
// example create body
|
||||
final body = <String, dynamic>${JSON.stringify(Object.assign({},o[4],Q.dummyCollectionSchemaData(o[0])),null,2)};
|
||||
|
||||
final record = await pb.collection('${(dt=o[0])==null?void 0:dt.name}').create(body: body);
|
||||
`+(o[1]?`
|
||||
// (optional) send an email verification request
|
||||
await pb.collection('${(ct=o[0])==null?void 0:ct.name}').requestVerification('test@example.com');
|
||||
`:"")}});let A=o[6]&>(),L=o[1]&&wt(o),me=ne((pt=o[0])==null?void 0:pt.schema);const at=l=>l[13].name;for(let l=0;l<me.length;l+=1){let s=vt(o,me,l),k=at(s);Je.set(k,V[l]=$t(k,s))}te=new Tt({props:{content:"?expand=relField1,relField2.subRelField"}}),le=new Bt({});let be=ne(o[3]);const st=l=>l[8].code;for(let l=0;l<be.length;l+=1){let s=yt(o,be,l),k=st(s);tt.set(k,J[l]=Ct(k,s))}let pe=ne(o[3]);const it=l=>l[8].code;for(let l=0;l<pe.length;l+=1){let s=kt(o,pe,l),k=it(s);nt.set(k,R[l]=St(k,s))}return{c(){e=i("h3"),t=_("Create ("),f=_(a),m=_(")"),c=u(),p=i("div"),y=i("p"),S=_("Create a new "),T=i("strong"),H=_(w),D=_(" record."),E=u(),P=i("p"),P.innerHTML=`Body parameters could be sent as <code>application/json</code> or
|
||||
<code>multipart/form-data</code>.`,I=u(),j=i("p"),j.innerHTML=`File upload is supported only via <code>multipart/form-data</code>.
|
||||
<br/>
|
||||
For more info and examples you could check the detailed
|
||||
<a href="https://pocketbase.io/docs/files-handling/" target="_blank" rel="noopener noreferrer">Files upload and handling docs
|
||||
</a>.`,B=u(),_e($.$$.fragment),N=u(),q=i("h6"),q.textContent="API details",g=u(),b=i("div"),O=i("strong"),O.textContent="POST",z=u(),F=i("div"),h=i("p"),C=_("/api/collections/"),ee=i("strong"),ve=_(K),je=_("/records"),De=u(),A&&A.c(),ge=u(),se=i("div"),se.textContent="Body Parameters",we=u(),W=i("table"),$e=i("thead"),$e.innerHTML='<tr><th>Param</th> <th>Type</th> <th width="50%">Description</th></tr>',Ne=u(),U=i("tbody"),Ce=i("tr"),Ce.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span> <span>id</span></div></td> <td><span class="label">String</span></td> <td><strong>15 characters string</strong> to store as record ID.
|
||||
<br/>
|
||||
If not set, it will be auto generated.</td>`,Ve=u(),L&&L.c(),Se=u();for(let l=0;l<V.length;l+=1)V[l].c();Te=u(),ie=i("div"),ie.textContent="Query parameters",qe=u(),Y=i("table"),Oe=i("thead"),Oe.innerHTML='<tr><th>Param</th> <th>Type</th> <th width="60%">Description</th></tr>',Ee=u(),oe=i("tbody"),G=i("tr"),Me=i("td"),Me.textContent="expand",Ie=u(),He=i("td"),He.innerHTML='<span class="label">String</span>',Ue=u(),M=i("td"),Qe=_(`Auto expand relations when returning the created record. Ex.:
|
||||
`),_e(te.$$.fragment),ze=_(`
|
||||
Supports up to 6-levels depth nested relations expansion. `),Ke=i("br"),We=_(`
|
||||
The expanded relations will be appended to the record under the
|
||||
`),Le=i("code"),Le.textContent="expand",Ye=_(" property (eg. "),Pe=i("code"),Pe.textContent='"expand": {"relField1": {...}, ...}',Ge=_(`).
|
||||
`),Xe=i("br"),Ze=_(`
|
||||
Only the relations to which the request user has permissions to `),Fe=i("strong"),Fe.textContent="view",xe=_(" will be expanded."),et=u(),_e(le.$$.fragment),Re=u(),re=i("div"),re.textContent="Responses",Ae=u(),X=i("div"),de=i("div");for(let l=0;l<J.length;l+=1)J[l].c();lt=u(),ce=i("div");for(let l=0;l<R.length;l+=1)R[l].c();v(e,"class","m-b-sm"),v(p,"class","content txt-lg m-b-sm"),v(q,"class","m-b-xs"),v(O,"class","label label-primary"),v(F,"class","content"),v(b,"class","alert alert-success"),v(se,"class","section-title"),v(W,"class","table-compact table-border m-b-base"),v(ie,"class","section-title"),v(Y,"class","table-compact table-border m-b-base"),v(re,"class","section-title"),v(de,"class","tabs-header compact combined left"),v(ce,"class","tabs-content"),v(X,"class","tabs")},m(l,s){r(l,e,s),n(e,t),n(e,f),n(e,m),r(l,c,s),r(l,p,s),n(p,y),n(y,S),n(y,T),n(T,H),n(y,D),n(p,E),n(p,P),n(p,I),n(p,j),r(l,B,s),he($,l,s),r(l,N,s),r(l,q,s),r(l,g,s),r(l,b,s),n(b,O),n(b,z),n(b,F),n(F,h),n(h,C),n(h,ee),n(ee,ve),n(h,je),n(b,De),A&&A.m(b,null),r(l,ge,s),r(l,se,s),r(l,we,s),r(l,W,s),n(W,$e),n(W,Ne),n(W,U),n(U,Ce),n(U,Ve),L&&L.m(U,null),n(U,Se);for(let k=0;k<V.length;k+=1)V[k]&&V[k].m(U,null);r(l,Te,s),r(l,ie,s),r(l,qe,s),r(l,Y,s),n(Y,Oe),n(Y,Ee),n(Y,oe),n(oe,G),n(G,Me),n(G,Ie),n(G,He),n(G,Ue),n(G,M),n(M,Qe),he(te,M,null),n(M,ze),n(M,Ke),n(M,We),n(M,Le),n(M,Ye),n(M,Pe),n(M,Ge),n(M,Xe),n(M,Ze),n(M,Fe),n(M,xe),n(oe,et),he(le,oe,null),r(l,Re,s),r(l,re,s),r(l,Ae,s),r(l,X,s),n(X,de);for(let k=0;k<J.length;k+=1)J[k]&&J[k].m(de,null);n(X,lt),n(X,ce);for(let k=0;k<R.length;k+=1)R[k]&&R[k].m(ce,null);Z=!0},p(l,[s]){var ut,ft,mt,bt,_t;(!Z||s&1)&&a!==(a=l[0].name+"")&&x(f,a),(!Z||s&1)&&w!==(w=l[0].name+"")&&x(H,w);const k={};s&51&&(k.js=`
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('${l[5]}');
|
||||
|
||||
...
|
||||
|
||||
// example create data
|
||||
const data = ${JSON.stringify(Object.assign({},l[4],Q.dummyCollectionSchemaData(l[0])),null,4)};
|
||||
|
||||
const record = await pb.collection('${(ut=l[0])==null?void 0:ut.name}').create(data);
|
||||
`+(l[1]?`
|
||||
// (optional) send an email verification request
|
||||
await pb.collection('${(ft=l[0])==null?void 0:ft.name}').requestVerification('test@example.com');
|
||||
`:"")),s&51&&(k.dart=`
|
||||
import 'package:pocketbase/pocketbase.dart';
|
||||
|
||||
final pb = PocketBase('${l[5]}');
|
||||
|
||||
...
|
||||
|
||||
// example create body
|
||||
final body = <String, dynamic>${JSON.stringify(Object.assign({},l[4],Q.dummyCollectionSchemaData(l[0])),null,2)};
|
||||
|
||||
final record = await pb.collection('${(mt=l[0])==null?void 0:mt.name}').create(body: body);
|
||||
`+(l[1]?`
|
||||
// (optional) send an email verification request
|
||||
await pb.collection('${(bt=l[0])==null?void 0:bt.name}').requestVerification('test@example.com');
|
||||
`:"")),$.$set(k),(!Z||s&1)&&K!==(K=l[0].name+"")&&x(ve,K),l[6]?A||(A=gt(),A.c(),A.m(b,null)):A&&(A.d(1),A=null),l[1]?L?L.p(l,s):(L=wt(l),L.c(),L.m(U,Se)):L&&(L.d(1),L=null),s&1&&(me=ne((_t=l[0])==null?void 0:_t.schema),V=Be(V,s,at,1,l,me,Je,U,ht,$t,null,vt)),s&12&&(be=ne(l[3]),J=Be(J,s,st,1,l,be,tt,de,ht,Ct,null,yt)),s&12&&(pe=ne(l[3]),Ht(),R=Be(R,s,it,1,l,pe,nt,ce,Lt,St,null,kt),Pt())},i(l){if(!Z){ue($.$$.fragment,l),ue(te.$$.fragment,l),ue(le.$$.fragment,l);for(let s=0;s<pe.length;s+=1)ue(R[s]);Z=!0}},o(l){fe($.$$.fragment,l),fe(te.$$.fragment,l),fe(le.$$.fragment,l);for(let s=0;s<R.length;s+=1)fe(R[s]);Z=!1},d(l){l&&(d(e),d(c),d(p),d(B),d(N),d(q),d(g),d(b),d(ge),d(se),d(we),d(W),d(Te),d(ie),d(qe),d(Y),d(Re),d(re),d(Ae),d(X)),ke($,l),A&&A.d(),L&&L.d();for(let s=0;s<V.length;s+=1)V[s].d();ke(te),ke(le);for(let s=0;s<J.length;s+=1)J[s].d();for(let s=0;s<R.length;s+=1)R[s].d()}}}function Yt(o,e,t){let a,f,m,{collection:c}=e,p=200,y=[],S={};const T=w=>t(2,p=w.code);return o.$$set=w=>{"collection"in w&&t(0,c=w.collection)},o.$$.update=()=>{var w,H;o.$$.dirty&1&&t(1,a=c.type==="auth"),o.$$.dirty&1&&t(6,f=(c==null?void 0:c.createRule)===null),o.$$.dirty&1&&t(3,y=[{code:200,body:JSON.stringify(Q.dummyCollectionRecord(c),null,2)},{code:400,body:`
|
||||
{
|
||||
"code": 400,
|
||||
"message": "Failed to create record.",
|
||||
"data": {
|
||||
"${(H=(w=c==null?void 0:c.schema)==null?void 0:w[0])==null?void 0:H.name}": {
|
||||
"code": "validation_required",
|
||||
"message": "Missing required value."
|
||||
}
|
||||
}
|
||||
}
|
||||
`},{code:403,body:`
|
||||
{
|
||||
"code": 403,
|
||||
"message": "You are not allowed to perform this request.",
|
||||
"data": {}
|
||||
}
|
||||
`}]),o.$$.dirty&2&&(a?t(4,S={username:"test_username",email:"test@example.com",emailVisibility:!0,password:"12345678",passwordConfirm:"12345678"}):t(4,S={}))},t(5,m=Q.getApiExampleUrl(Ft.baseUrl)),[c,a,p,y,S,m,f,T]}class xt extends qt{constructor(e){super(),Ot(this,e,Yt,Wt,Mt,{collection:0})}}export{xt as default};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user