mirror of
https://github.com/pocketbase/pocketbase.git
synced 2026-09-20 13:30:50 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
353248c34a | ||
|
|
93d48a85ac | ||
|
|
b84930f21a | ||
|
|
3cbab96f51 | ||
|
|
6c005c4a9a | ||
|
|
ccc3707fb6 | ||
|
|
3d36ff7e96 | ||
|
|
0b2eb24c6f | ||
|
|
954067860c | ||
|
|
9a8c9dd115 | ||
|
|
a1ad5004f8 | ||
|
|
7006e1f5d7 | ||
|
|
8be8f3f3cb | ||
|
|
00fd007d50 | ||
|
|
96bfc69c8e | ||
|
|
e542b0d8c5 | ||
|
|
9814dda8e4 | ||
|
|
978fdd3ce7 | ||
|
|
9cf89870e7 | ||
|
|
b8c54568e3 | ||
|
|
843bbf99cc | ||
|
|
daffb0f86e | ||
|
|
1f5c3328e5 | ||
|
|
ed5f3b86f5 | ||
|
|
1ba2d14231 | ||
|
|
6cda610ede | ||
|
|
2fa5233fa6 | ||
|
|
030dfc2690 | ||
|
|
8c11e2ef01 | ||
|
|
4b64e0910b | ||
|
|
4a6bc453de | ||
|
|
96d09a30c4 | ||
|
|
a0d7f23d77 | ||
|
|
74108d84ca | ||
|
|
4bc28abac4 | ||
|
|
b79a7982bf | ||
|
|
b717896232 | ||
|
|
9d53aec9ee | ||
|
|
9d30ca81cb | ||
|
|
93b3788448 | ||
|
|
0ac4c9e1fc | ||
|
|
df1a947b61 | ||
|
|
06a7f1af05 | ||
|
|
07ac5bf6a2 | ||
|
|
f56c52a1f7 | ||
|
|
f0b57c6b91 | ||
|
|
f61d0ec6f7 | ||
|
|
f5ff7193a9 | ||
|
|
9fe94f5c7d | ||
|
|
0f9ddbf7ec | ||
|
|
f14105b04a | ||
|
|
a908d20dcd | ||
|
|
49b084cf50 |
@@ -34,7 +34,7 @@ The easiest way to interact with the API is to use one of the official SDK clien
|
||||
|
||||
## Overview
|
||||
|
||||
PocketBase could be used as a standalone app or as a Go framework/toolkit that enables you to build
|
||||
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
|
||||
your own custom app specific business logic and still have a single portable executable at the end.
|
||||
|
||||
### Installation
|
||||
@@ -121,7 +121,11 @@ PocketBase is free and open source project licensed under the [MIT License](LICE
|
||||
You could help continuing its development by:
|
||||
|
||||
- [Contribute to the source code](CONTRIBUTING.md)
|
||||
|
||||
> **NB! PR for new features will not be merged until we manage to complete the users refactoring in [#376](https://github.com/pocketbase/pocketbase/issues/376)!**
|
||||
|
||||
- [Suggest new features and report issues](https://github.com/pocketbase/pocketbase/issues)
|
||||
|
||||
- [Donate a small amount](https://pocketbase.io/support-us)
|
||||
|
||||
> Please note that 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)),
|
||||
|
||||
+3
-3
@@ -119,7 +119,7 @@ func TestAdminRequestPasswordReset(t *testing.T) {
|
||||
Body: strings.NewReader(`{"email":"test@example.com"}`),
|
||||
Delay: 100 * time.Millisecond,
|
||||
ExpectedStatus: 204,
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
// simulate recent password request
|
||||
admin, err := app.Dao().FindAdminByEmail("test@example.com")
|
||||
if err != nil {
|
||||
@@ -446,7 +446,7 @@ func TestAdminDelete(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
// delete all admins except the authorized one
|
||||
adminModel := &models.Admin{}
|
||||
_, err := app.Dao().DB().Delete(adminModel.TableName(), dbx.Not(dbx.HashExp{
|
||||
@@ -483,7 +483,7 @@ func TestAdminCreate(t *testing.T) {
|
||||
Method: http.MethodPost,
|
||||
Url: "/api/admins",
|
||||
Body: strings.NewReader(`{"email":"testnew@example.com","password":"1234567890","passwordConfirm":"1234567890","avatar":3}`),
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
// delete all admins
|
||||
_, err := app.Dao().DB().NewQuery("DELETE FROM {{_admins}}").Execute()
|
||||
if err != nil {
|
||||
|
||||
+4
-4
@@ -54,7 +54,7 @@ func TestCustomRoutesAndErrorsHandling(t *testing.T) {
|
||||
Name: "custom route",
|
||||
Method: http.MethodGet,
|
||||
Url: "/custom",
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/custom",
|
||||
@@ -70,7 +70,7 @@ func TestCustomRoutesAndErrorsHandling(t *testing.T) {
|
||||
Name: "route with HTTPError",
|
||||
Method: http.MethodGet,
|
||||
Url: "/http-error",
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/http-error",
|
||||
@@ -86,7 +86,7 @@ func TestCustomRoutesAndErrorsHandling(t *testing.T) {
|
||||
Name: "route with api error",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api-error",
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/api-error",
|
||||
@@ -102,7 +102,7 @@ func TestCustomRoutesAndErrorsHandling(t *testing.T) {
|
||||
Name: "route with plain error",
|
||||
Method: http.MethodGet,
|
||||
Url: "/plain-error",
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/plain-error",
|
||||
|
||||
@@ -475,7 +475,7 @@ func TestCollectionImport(t *testing.T) {
|
||||
`"data":{`,
|
||||
`"collections":{"code":"validation_required"`,
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
collections := []*models.Collection{}
|
||||
if err := app.Dao().CollectionQuery().All(&collections); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -501,7 +501,7 @@ func TestCollectionImport(t *testing.T) {
|
||||
ExpectedEvents: map[string]int{
|
||||
"OnCollectionsBeforeImportRequest": 1,
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
collections := []*models.Collection{}
|
||||
if err := app.Dao().CollectionQuery().All(&collections); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -542,7 +542,7 @@ func TestCollectionImport(t *testing.T) {
|
||||
"OnCollectionsBeforeImportRequest": 1,
|
||||
"OnModelBeforeCreate": 2,
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
collections := []*models.Collection{}
|
||||
if err := app.Dao().CollectionQuery().All(&collections); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -590,7 +590,7 @@ func TestCollectionImport(t *testing.T) {
|
||||
"OnModelBeforeCreate": 2,
|
||||
"OnModelAfterCreate": 2,
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
collections := []*models.Collection{}
|
||||
if err := app.Dao().CollectionQuery().All(&collections); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -689,7 +689,7 @@ func TestCollectionImport(t *testing.T) {
|
||||
"OnModelBeforeCreate": 1,
|
||||
"OnModelAfterCreate": 1,
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
collections := []*models.Collection{}
|
||||
if err := app.Dao().CollectionQuery().All(&collections); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
+6
-6
@@ -34,7 +34,7 @@ func TestRequestsList(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
if err := tests.MockRequestLogsData(app); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -56,7 +56,7 @@ func TestRequestsList(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
if err := tests.MockRequestLogsData(app); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func TestRequestView(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
if err := tests.MockRequestLogsData(app); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func TestRequestView(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
if err := tests.MockRequestLogsData(app); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -161,7 +161,7 @@ func TestRequestsStats(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
if err := tests.MockRequestLogsData(app); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -178,7 +178,7 @@ func TestRequestsStats(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
if err := tests.MockRequestLogsData(app); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+26
-26
@@ -18,7 +18,7 @@ func TestRequireGuestOnly(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRkMDE5N2NjLTJiNGEtM2Y4My1hMjZiLWQ3N2JjODQyM2QzYyIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxODkzNDc0MDAwfQ.Wq5ac1q1f5WntIzEngXk22ydMj-eFgvfSRg7dhmPKic",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -40,7 +40,7 @@ func TestRequireGuestOnly(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -62,7 +62,7 @@ func TestRequireGuestOnly(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRkMDE5N2NjLTJiNGEtM2Y4My1hMjZiLWQ3N2JjODQyM2QzYyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxNjQwOTkxNjYxfQ.HkAldxpbn0EybkMfFGQKEJUIYKE5UJA0AjcsrV7Q6Io",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -81,7 +81,7 @@ func TestRequireGuestOnly(t *testing.T) {
|
||||
Name: "guest",
|
||||
Method: http.MethodGet,
|
||||
Url: "/my/test",
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -109,7 +109,7 @@ func TestRequireUserAuth(t *testing.T) {
|
||||
Name: "guest",
|
||||
Method: http.MethodGet,
|
||||
Url: "/my/test",
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -131,7 +131,7 @@ func TestRequireUserAuth(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRkMDE5N2NjLTJiNGEtM2Y4My1hMjZiLWQ3N2JjODQyM2QzYyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxNjQwOTkxNjYxfQ.HkAldxpbn0EybkMfFGQKEJUIYKE5UJA0AjcsrV7Q6Io",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -153,7 +153,7 @@ func TestRequireUserAuth(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -175,7 +175,7 @@ func TestRequireUserAuth(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRkMDE5N2NjLTJiNGEtM2Y4My1hMjZiLWQ3N2JjODQyM2QzYyIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxODkzNDc0MDAwfQ.Wq5ac1q1f5WntIzEngXk22ydMj-eFgvfSRg7dhmPKic",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -203,7 +203,7 @@ func TestRequireAdminAuth(t *testing.T) {
|
||||
Name: "guest",
|
||||
Method: http.MethodGet,
|
||||
Url: "/my/test",
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -225,7 +225,7 @@ func TestRequireAdminAuth(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTY0MTAxMzIwMH0.Gp_1b5WVhqjj2o3nJhNUlJmpdiwFLXN72LbMP-26gjA",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -247,7 +247,7 @@ func TestRequireAdminAuth(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRkMDE5N2NjLTJiNGEtM2Y4My1hMjZiLWQ3N2JjODQyM2QzYyIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxODkzNDc0MDAwfQ.Wq5ac1q1f5WntIzEngXk22ydMj-eFgvfSRg7dhmPKic",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -269,7 +269,7 @@ func TestRequireAdminAuth(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -297,7 +297,7 @@ func TestRequireAdminAuthOnlyIfAny(t *testing.T) {
|
||||
Name: "guest (while having at least 1 existing admin)",
|
||||
Method: http.MethodGet,
|
||||
Url: "/my/test",
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -316,7 +316,7 @@ func TestRequireAdminAuthOnlyIfAny(t *testing.T) {
|
||||
Name: "guest (while having 0 existing admins)",
|
||||
Method: http.MethodGet,
|
||||
Url: "/my/test",
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
// delete all admins
|
||||
_, err := app.Dao().DB().NewQuery("DELETE FROM {{_admins}}").Execute()
|
||||
if err != nil {
|
||||
@@ -344,7 +344,7 @@ func TestRequireAdminAuthOnlyIfAny(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTY0MTAxMzIwMH0.Gp_1b5WVhqjj2o3nJhNUlJmpdiwFLXN72LbMP-26gjA",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -366,7 +366,7 @@ func TestRequireAdminAuthOnlyIfAny(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRkMDE5N2NjLTJiNGEtM2Y4My1hMjZiLWQ3N2JjODQyM2QzYyIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxODkzNDc0MDAwfQ.Wq5ac1q1f5WntIzEngXk22ydMj-eFgvfSRg7dhmPKic",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -388,7 +388,7 @@ func TestRequireAdminAuthOnlyIfAny(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -416,7 +416,7 @@ func TestRequireAdminOrUserAuth(t *testing.T) {
|
||||
Name: "guest",
|
||||
Method: http.MethodGet,
|
||||
Url: "/my/test",
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -438,7 +438,7 @@ func TestRequireAdminOrUserAuth(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTY0MTAxMzIwMH0.Gp_1b5WVhqjj2o3nJhNUlJmpdiwFLXN72LbMP-26gjA",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -460,7 +460,7 @@ func TestRequireAdminOrUserAuth(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRkMDE5N2NjLTJiNGEtM2Y4My1hMjZiLWQ3N2JjODQyM2QzYyIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxODkzNDc0MDAwfQ.Wq5ac1q1f5WntIzEngXk22ydMj-eFgvfSRg7dhmPKic",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -482,7 +482,7 @@ func TestRequireAdminOrUserAuth(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test",
|
||||
@@ -510,7 +510,7 @@ func TestRequireAdminOrOwnerAuth(t *testing.T) {
|
||||
Name: "guest",
|
||||
Method: http.MethodGet,
|
||||
Url: "/my/test/4d0197cc-2b4a-3f83-a26b-d77bc8423d3c",
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test/:id",
|
||||
@@ -532,7 +532,7 @@ func TestRequireAdminOrOwnerAuth(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRkMDE5N2NjLTJiNGEtM2Y4My1hMjZiLWQ3N2JjODQyM2QzYyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxNjQwOTkxNjYxfQ.HkAldxpbn0EybkMfFGQKEJUIYKE5UJA0AjcsrV7Q6Io",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test/:id",
|
||||
@@ -555,7 +555,7 @@ func TestRequireAdminOrOwnerAuth(t *testing.T) {
|
||||
// test3@example.com
|
||||
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoidXNlciIsImVtYWlsIjoidGVzdDNAZXhhbXBsZS5jb20iLCJpZCI6Ijk3Y2MzZDNkLTZiYTItMzgzZi1iNDJhLTdiYzg0ZDI3NDEwYyIsImV4cCI6MTg5MzUxNTU3Nn0.Q965uvlTxxOsZbACXSgJQNXykYK0TKZ87nyPzemvN4E",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test/:id",
|
||||
@@ -577,7 +577,7 @@ func TestRequireAdminOrOwnerAuth(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRkMDE5N2NjLTJiNGEtM2Y4My1hMjZiLWQ3N2JjODQyM2QzYyIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxODkzNDc0MDAwfQ.Wq5ac1q1f5WntIzEngXk22ydMj-eFgvfSRg7dhmPKic",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test/:id",
|
||||
@@ -599,7 +599,7 @@ func TestRequireAdminOrOwnerAuth(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
e.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/my/test/:custom",
|
||||
|
||||
+7
-4
@@ -48,6 +48,9 @@ func (api *realtimeApi) connect(c echo.Context) error {
|
||||
c.Response().Header().Set("Content-Type", "text/event-stream; charset=UTF-8")
|
||||
c.Response().Header().Set("Cache-Control", "no-store")
|
||||
c.Response().Header().Set("Connection", "keep-alive")
|
||||
// https://github.com/pocketbase/pocketbase/discussions/480#discussioncomment-3657640
|
||||
// https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering
|
||||
c.Response().Header().Set("X-Accel-Buffering", "no")
|
||||
|
||||
event := &core.RealtimeConnectEvent{
|
||||
HttpContext: c,
|
||||
@@ -163,7 +166,7 @@ func (api *realtimeApi) bindEvents() {
|
||||
adminTable := (&models.Admin{}).TableName()
|
||||
|
||||
// update user/admin auth state
|
||||
api.app.OnModelAfterUpdate().Add(func(e *core.ModelEvent) error {
|
||||
api.app.OnModelAfterUpdate().PreAdd(func(e *core.ModelEvent) error {
|
||||
modelTable := e.Model.TableName()
|
||||
|
||||
var contextKey string
|
||||
@@ -187,7 +190,7 @@ func (api *realtimeApi) bindEvents() {
|
||||
})
|
||||
|
||||
// remove user/admin client(s)
|
||||
api.app.OnModelAfterDelete().Add(func(e *core.ModelEvent) error {
|
||||
api.app.OnModelAfterDelete().PreAdd(func(e *core.ModelEvent) error {
|
||||
modelTable := e.Model.TableName()
|
||||
|
||||
var contextKey string
|
||||
@@ -210,14 +213,14 @@ func (api *realtimeApi) bindEvents() {
|
||||
return nil
|
||||
})
|
||||
|
||||
api.app.OnModelAfterCreate().Add(func(e *core.ModelEvent) error {
|
||||
api.app.OnModelAfterCreate().PreAdd(func(e *core.ModelEvent) error {
|
||||
if record, ok := e.Model.(*models.Record); ok {
|
||||
api.broadcastRecord("create", record)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
api.app.OnModelAfterUpdate().Add(func(e *core.ModelEvent) error {
|
||||
api.app.OnModelAfterUpdate().PreAdd(func(e *core.ModelEvent) error {
|
||||
if record, ok := e.Model.(*models.Record); ok {
|
||||
api.broadcastRecord("update", record)
|
||||
}
|
||||
|
||||
+11
-11
@@ -27,7 +27,7 @@ func TestRealtimeConnect(t *testing.T) {
|
||||
ExpectedEvents: map[string]int{
|
||||
"OnRealtimeConnectRequest": 1,
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
if len(app.SubscriptionsBroker().Clients()) != 0 {
|
||||
t.Errorf("Expected the subscribers to be removed after connection close, found %d", len(app.SubscriptionsBroker().Clients()))
|
||||
}
|
||||
@@ -68,11 +68,11 @@ func TestRealtimeSubscribe(t *testing.T) {
|
||||
"OnRealtimeBeforeSubscribeRequest": 1,
|
||||
"OnRealtimeAfterSubscribeRequest": 1,
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
client.Subscribe("test0")
|
||||
app.SubscriptionsBroker().Register(client)
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
if len(client.Subscriptions()) != 0 {
|
||||
t.Errorf("Expected no subscriptions, got %v", client.Subscriptions())
|
||||
}
|
||||
@@ -89,11 +89,11 @@ func TestRealtimeSubscribe(t *testing.T) {
|
||||
"OnRealtimeBeforeSubscribeRequest": 1,
|
||||
"OnRealtimeAfterSubscribeRequest": 1,
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
client.Subscribe("test0")
|
||||
app.SubscriptionsBroker().Register(client)
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
expectedSubs := []string{"test1", "test2"}
|
||||
if len(expectedSubs) != len(client.Subscriptions()) {
|
||||
t.Errorf("Expected subscriptions %v, got %v", expectedSubs, client.Subscriptions())
|
||||
@@ -120,10 +120,10 @@ func TestRealtimeSubscribe(t *testing.T) {
|
||||
"OnRealtimeBeforeSubscribeRequest": 1,
|
||||
"OnRealtimeAfterSubscribeRequest": 1,
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
app.SubscriptionsBroker().Register(client)
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
admin, _ := client.Get(apis.ContextAdminKey).(*models.Admin)
|
||||
if admin == nil {
|
||||
t.Errorf("Expected admin auth model, got nil")
|
||||
@@ -144,10 +144,10 @@ func TestRealtimeSubscribe(t *testing.T) {
|
||||
"OnRealtimeBeforeSubscribeRequest": 1,
|
||||
"OnRealtimeAfterSubscribeRequest": 1,
|
||||
},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
app.SubscriptionsBroker().Register(client)
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
user, _ := client.Get(apis.ContextUserKey).(*models.User)
|
||||
if user == nil {
|
||||
t.Errorf("Expected user auth model, got nil")
|
||||
@@ -165,14 +165,14 @@ func TestRealtimeSubscribe(t *testing.T) {
|
||||
},
|
||||
ExpectedStatus: 403,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
initialAuth := &models.User{}
|
||||
initialAuth.RefreshId()
|
||||
client.Set(apis.ContextUserKey, initialAuth)
|
||||
|
||||
app.SubscriptionsBroker().Register(client)
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
user, _ := client.Get(apis.ContextUserKey).(*models.User)
|
||||
if user == nil {
|
||||
t.Errorf("Expected user auth model, got nil")
|
||||
|
||||
+5
-5
@@ -459,7 +459,7 @@ func TestRecordDelete(t *testing.T) {
|
||||
"OnModelBeforeDelete": 3, // +2 cascade delete related records
|
||||
"OnModelAfterDelete": 3, // +2 cascade delete related records
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
ensureDeletedFiles(app, "3f2888f8-075d-49fe-9d09-ea7e951000dc", "577bd676-aacb-4072-b7da-99d00ee210a4")
|
||||
ensureDeletedFiles(app, "2c1010aa-b8fe-41d9-a980-99534ca8a167", "94568ca2-0bee-49d7-b749-06cb97956fd9")
|
||||
ensureDeletedFiles(app, "2c1010aa-b8fe-41d9-a980-99534ca8a167", "63c2ab80-84ab-4057-a592-4604a731f78f")
|
||||
@@ -481,7 +481,7 @@ func TestRecordDelete(t *testing.T) {
|
||||
"OnModelBeforeDelete": 3, // +2 cascade delete related records
|
||||
"OnModelAfterDelete": 3, // +2 cascade delete related records
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
ensureDeletedFiles(app, "3f2888f8-075d-49fe-9d09-ea7e951000dc", "577bd676-aacb-4072-b7da-99d00ee210a4")
|
||||
ensureDeletedFiles(app, "2c1010aa-b8fe-41d9-a980-99534ca8a167", "94568ca2-0bee-49d7-b749-06cb97956fd9")
|
||||
ensureDeletedFiles(app, "2c1010aa-b8fe-41d9-a980-99534ca8a167", "63c2ab80-84ab-4057-a592-4604a731f78f")
|
||||
@@ -501,7 +501,7 @@ func TestRecordDelete(t *testing.T) {
|
||||
"OnModelBeforeDelete": 1,
|
||||
"OnModelAfterDelete": 1,
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
ensureDeletedFiles(app, "2c1010aa-b8fe-41d9-a980-99534ca8a167", "94568ca2-0bee-49d7-b749-06cb97956fd9")
|
||||
},
|
||||
},
|
||||
@@ -529,7 +529,7 @@ func TestRecordDelete(t *testing.T) {
|
||||
"OnModelBeforeDelete": 1,
|
||||
"OnModelAfterDelete": 1,
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
ensureDeletedFiles(app, "2c1010aa-b8fe-41d9-a980-99534ca8a167", "63c2ab80-84ab-4057-a592-4604a731f78f")
|
||||
},
|
||||
},
|
||||
@@ -562,7 +562,7 @@ func TestRecordDelete(t *testing.T) {
|
||||
"OnModelBeforeDelete": 3,
|
||||
"OnModelAfterDelete": 3,
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
recId := "63c2ab80-84ab-4057-a592-4604a731f78f"
|
||||
col, _ := app.Dao().FindCollectionByNameOrId("demo2")
|
||||
rec, _ := app.Dao().FindRecordById(col, recId, nil)
|
||||
|
||||
@@ -52,6 +52,7 @@ func TestSettingsList(t *testing.T) {
|
||||
`"facebookAuth":{`,
|
||||
`"githubAuth":{`,
|
||||
`"gitlabAuth":{`,
|
||||
`"discordAuth":{`,
|
||||
`"secret":"******"`,
|
||||
`"clientSecret":"******"`,
|
||||
},
|
||||
@@ -114,6 +115,7 @@ func TestSettingsSet(t *testing.T) {
|
||||
`"facebookAuth":{`,
|
||||
`"githubAuth":{`,
|
||||
`"gitlabAuth":{`,
|
||||
`"discordAuth":{`,
|
||||
`"secret":"******"`,
|
||||
`"clientSecret":"******"`,
|
||||
`"appName":"Acme"`,
|
||||
@@ -166,6 +168,7 @@ func TestSettingsSet(t *testing.T) {
|
||||
`"facebookAuth":{`,
|
||||
`"githubAuth":{`,
|
||||
`"gitlabAuth":{`,
|
||||
`"discordAuth":{`,
|
||||
`"secret":"******"`,
|
||||
`"clientSecret":"******"`,
|
||||
`"appName":"update_test"`,
|
||||
@@ -285,7 +288,7 @@ func TestSettingsTestEmail(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
if app.TestMailer.TotalSend != 1 {
|
||||
t.Fatalf("[verification] Expected 1 sent email, got %d", app.TestMailer.TotalSend)
|
||||
}
|
||||
@@ -316,7 +319,7 @@ func TestSettingsTestEmail(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
if app.TestMailer.TotalSend != 1 {
|
||||
t.Fatalf("[password-reset] Expected 1 sent email, got %d", app.TestMailer.TotalSend)
|
||||
}
|
||||
@@ -347,7 +350,7 @@ func TestSettingsTestEmail(t *testing.T) {
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
AfterFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
if app.TestMailer.TotalSend != 1 {
|
||||
t.Fatalf("[email-change] Expected 1 sent email, got %d", app.TestMailer.TotalSend)
|
||||
}
|
||||
|
||||
+68
-1
@@ -38,6 +38,8 @@ func BindUserApi(app core.App, rg *echo.Group) {
|
||||
subGroup.GET("/:id", api.view, RequireAdminOrOwnerAuth("id"))
|
||||
subGroup.PATCH("/:id", api.update, RequireAdminAuth())
|
||||
subGroup.DELETE("/:id", api.delete, RequireAdminOrOwnerAuth("id"))
|
||||
subGroup.GET("/:id/external-auths", api.listExternalAuths, RequireAdminOrOwnerAuth("id"))
|
||||
subGroup.DELETE("/:id/external-auths/:provider", api.unlinkExternalAuth, RequireAdminOrOwnerAuth("id"))
|
||||
}
|
||||
|
||||
type userApi struct {
|
||||
@@ -129,7 +131,7 @@ func (api *userApi) authMethods(c echo.Context) error {
|
||||
}
|
||||
|
||||
state := security.RandomString(30)
|
||||
codeVerifier := security.RandomString(30)
|
||||
codeVerifier := security.RandomString(43)
|
||||
codeChallenge := security.S256Challenge(codeVerifier)
|
||||
codeChallengeMethod := "S256"
|
||||
result.AuthProviders = append(result.AuthProviders, providerInfo{
|
||||
@@ -450,3 +452,68 @@ func (api *userApi) delete(c echo.Context) error {
|
||||
|
||||
return handlerErr
|
||||
}
|
||||
|
||||
func (api *userApi) listExternalAuths(c echo.Context) error {
|
||||
id := c.PathParam("id")
|
||||
if id == "" {
|
||||
return rest.NewNotFoundError("", nil)
|
||||
}
|
||||
|
||||
user, err := api.app.Dao().FindUserById(id)
|
||||
if err != nil || user == nil {
|
||||
return rest.NewNotFoundError("", err)
|
||||
}
|
||||
|
||||
externalAuths, err := api.app.Dao().FindAllExternalAuthsByUserId(user.Id)
|
||||
if err != nil {
|
||||
return rest.NewBadRequestError("Failed to fetch the external auths for the specified user.", err)
|
||||
}
|
||||
|
||||
event := &core.UserListExternalAuthsEvent{
|
||||
HttpContext: c,
|
||||
User: user,
|
||||
ExternalAuths: externalAuths,
|
||||
}
|
||||
|
||||
return api.app.OnUserListExternalAuths().Trigger(event, func(e *core.UserListExternalAuthsEvent) error {
|
||||
return e.HttpContext.JSON(http.StatusOK, e.ExternalAuths)
|
||||
})
|
||||
}
|
||||
|
||||
func (api *userApi) unlinkExternalAuth(c echo.Context) error {
|
||||
id := c.PathParam("id")
|
||||
provider := c.PathParam("provider")
|
||||
if id == "" || provider == "" {
|
||||
return rest.NewNotFoundError("", nil)
|
||||
}
|
||||
|
||||
user, err := api.app.Dao().FindUserById(id)
|
||||
if err != nil || user == nil {
|
||||
return rest.NewNotFoundError("", err)
|
||||
}
|
||||
|
||||
externalAuth, err := api.app.Dao().FindExternalAuthByUserIdAndProvider(user.Id, provider)
|
||||
if err != nil {
|
||||
return rest.NewNotFoundError("Missing external auth provider relation.", err)
|
||||
}
|
||||
|
||||
event := &core.UserUnlinkExternalAuthEvent{
|
||||
HttpContext: c,
|
||||
User: user,
|
||||
ExternalAuth: externalAuth,
|
||||
}
|
||||
|
||||
handlerErr := api.app.OnUserBeforeUnlinkExternalAuthRequest().Trigger(event, func(e *core.UserUnlinkExternalAuthEvent) error {
|
||||
if err := api.app.Dao().DeleteExternalAuth(externalAuth); err != nil {
|
||||
return rest.NewBadRequestError("Cannot unlink the external auth provider. Make sure that the user has other linked auth providers OR has an email address.", err)
|
||||
}
|
||||
|
||||
return e.HttpContext.NoContent(http.StatusNoContent)
|
||||
})
|
||||
|
||||
if handlerErr == nil {
|
||||
api.app.OnUserAfterUnlinkExternalAuthRequest().Trigger(event)
|
||||
}
|
||||
|
||||
return handlerErr
|
||||
}
|
||||
|
||||
+192
-7
@@ -84,7 +84,7 @@ func TestUserEmailAuth(t *testing.T) {
|
||||
Method: http.MethodPost,
|
||||
Url: "/api/users/auth-via-email",
|
||||
Body: strings.NewReader(`{"email":"test@example.com","password":"123456"}`),
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
app.Settings().EmailAuth.Enabled = false
|
||||
},
|
||||
ExpectedStatus: 400,
|
||||
@@ -159,7 +159,7 @@ func TestUserRequestPasswordReset(t *testing.T) {
|
||||
Body: strings.NewReader(`{"email":"test@example.com"}`),
|
||||
Delay: 100 * time.Millisecond,
|
||||
ExpectedStatus: 204,
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
// simulate recent password request
|
||||
user, err := app.Dao().FindUserByEmail("test@example.com")
|
||||
if err != nil {
|
||||
@@ -285,7 +285,7 @@ func TestUserRequestVerification(t *testing.T) {
|
||||
Body: strings.NewReader(`{"email":"test2@example.com"}`),
|
||||
Delay: 100 * time.Millisecond,
|
||||
ExpectedStatus: 204,
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
// simulate recent verification sent
|
||||
user, err := app.Dao().FindUserByEmail("test2@example.com")
|
||||
if err != nil {
|
||||
@@ -584,11 +584,12 @@ func TestUsersList(t *testing.T) {
|
||||
ExpectedContent: []string{
|
||||
`"page":1`,
|
||||
`"perPage":30`,
|
||||
`"totalItems":3`,
|
||||
`"totalItems":4`,
|
||||
`"items":[{`,
|
||||
`"id":"4d0197cc-2b4a-3f83-a26b-d77bc8423d3c"`,
|
||||
`"id":"7bc84d27-6ba2-b42a-383f-4197cc3d3d0c"`,
|
||||
`"id":"97cc3d3d-6ba2-383f-b42a-7bc84d27410c"`,
|
||||
`"id":"cx9u0dh2udo8xol"`,
|
||||
},
|
||||
ExpectedEvents: map[string]int{"OnUsersListRequest": 1},
|
||||
},
|
||||
@@ -603,8 +604,9 @@ func TestUsersList(t *testing.T) {
|
||||
ExpectedContent: []string{
|
||||
`"page":2`,
|
||||
`"perPage":2`,
|
||||
`"totalItems":3`,
|
||||
`"totalItems":4`,
|
||||
`"items":[{`,
|
||||
`"id":"7bc84d27-6ba2-b42a-383f-4197cc3d3d0c"`,
|
||||
`"id":"4d0197cc-2b4a-3f83-a26b-d77bc8423d3c"`,
|
||||
},
|
||||
ExpectedEvents: map[string]int{"OnUsersListRequest": 1},
|
||||
@@ -630,10 +632,11 @@ func TestUsersList(t *testing.T) {
|
||||
ExpectedContent: []string{
|
||||
`"page":1`,
|
||||
`"perPage":30`,
|
||||
`"totalItems":2`,
|
||||
`"totalItems":3`,
|
||||
`"items":[{`,
|
||||
`"id":"4d0197cc-2b4a-3f83-a26b-d77bc8423d3c"`,
|
||||
`"id":"97cc3d3d-6ba2-383f-b42a-7bc84d27410c"`,
|
||||
`"id":"cx9u0dh2udo8xol"`,
|
||||
},
|
||||
ExpectedEvents: map[string]int{"OnUsersListRequest": 1},
|
||||
},
|
||||
@@ -804,7 +807,7 @@ func TestUserCreate(t *testing.T) {
|
||||
Method: http.MethodPost,
|
||||
Url: "/api/users",
|
||||
Body: strings.NewReader(`{"email":"newuser@example.com","password":"123456789","passwordConfirm":"123456789"}`),
|
||||
BeforeFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
app.Settings().EmailAuth.Enabled = false
|
||||
},
|
||||
ExpectedStatus: 400,
|
||||
@@ -926,3 +929,185 @@ func TestUserUpdate(t *testing.T) {
|
||||
scenario.Test(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserListExternalsAuths(t *testing.T) {
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/users/cx9u0dh2udo8xol/external-auths",
|
||||
ExpectedStatus: 401,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
},
|
||||
{
|
||||
Name: "authorized as admin + nonexisting user id",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/users/000000000000000/external-auths",
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
ExpectedStatus: 404,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
},
|
||||
{
|
||||
Name: "authorized as admin + existing user id and no external auths",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/users/97cc3d3d-6ba2-383f-b42a-7bc84d27410c/external-auths",
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContent: []string{
|
||||
`[]`,
|
||||
},
|
||||
ExpectedEvents: map[string]int{"OnUserListExternalAuths": 1},
|
||||
},
|
||||
{
|
||||
Name: "authorized as admin + existing user id and 2 external auths",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/users/cx9u0dh2udo8xol/external-auths",
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContent: []string{
|
||||
`"id":"abcdefghijklmn1"`,
|
||||
`"id":"abcdefghijklmn0"`,
|
||||
`"userId":"cx9u0dh2udo8xol"`,
|
||||
},
|
||||
ExpectedEvents: map[string]int{"OnUserListExternalAuths": 1},
|
||||
},
|
||||
{
|
||||
Name: "authorized as user - trying to list another user external auths",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/users/cx9u0dh2udo8xol/external-auths",
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRkMDE5N2NjLTJiNGEtM2Y4My1hMjZiLWQ3N2JjODQyM2QzYyIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxODkzNDc0MDAwfQ.Wq5ac1q1f5WntIzEngXk22ydMj-eFgvfSRg7dhmPKic",
|
||||
},
|
||||
ExpectedStatus: 403,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
},
|
||||
{
|
||||
Name: "authorized as user - owner without external auths",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/users/4d0197cc-2b4a-3f83-a26b-d77bc8423d3c/external-auths",
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRkMDE5N2NjLTJiNGEtM2Y4My1hMjZiLWQ3N2JjODQyM2QzYyIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxODkzNDc0MDAwfQ.Wq5ac1q1f5WntIzEngXk22ydMj-eFgvfSRg7dhmPKic",
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContent: []string{
|
||||
`[]`,
|
||||
},
|
||||
ExpectedEvents: map[string]int{"OnUserListExternalAuths": 1},
|
||||
},
|
||||
{
|
||||
Name: "authorized as user - owner with 2 external auths",
|
||||
Method: http.MethodGet,
|
||||
Url: "/api/users/cx9u0dh2udo8xol/external-auths",
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImN4OXUwZGgydWRvOHhvbCIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxODkzNDc0MDAwfQ.NgFYG2D7PftFW1tcfe5E2oDi_AVakDR9J6WI6VUZQfw",
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContent: []string{
|
||||
`"id":"abcdefghijklmn1"`,
|
||||
`"id":"abcdefghijklmn0"`,
|
||||
`"userId":"cx9u0dh2udo8xol"`,
|
||||
},
|
||||
ExpectedEvents: map[string]int{"OnUserListExternalAuths": 1},
|
||||
},
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
scenario.Test(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserUnlinkExternalsAuth(t *testing.T) {
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
Method: http.MethodDelete,
|
||||
Url: "/api/users/cx9u0dh2udo8xol/external-auths/google",
|
||||
ExpectedStatus: 401,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
},
|
||||
{
|
||||
Name: "authorized as admin - nonexisting user id",
|
||||
Method: http.MethodDelete,
|
||||
Url: "/api/users/000000000000000/external-auths/google",
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
ExpectedStatus: 404,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
},
|
||||
{
|
||||
Name: "authorized as admin - nonexisting provider",
|
||||
Method: http.MethodDelete,
|
||||
Url: "/api/users/cx9u0dh2udo8xol/external-auths/facebook",
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
ExpectedStatus: 404,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
},
|
||||
{
|
||||
Name: "authorized as admin - existing provider",
|
||||
Method: http.MethodDelete,
|
||||
Url: "/api/users/cx9u0dh2udo8xol/external-auths/google",
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||
},
|
||||
ExpectedStatus: 204,
|
||||
ExpectedContent: []string{},
|
||||
ExpectedEvents: map[string]int{
|
||||
"OnModelAfterDelete": 1,
|
||||
"OnModelBeforeDelete": 1,
|
||||
"OnUserAfterUnlinkExternalAuthRequest": 1,
|
||||
"OnUserBeforeUnlinkExternalAuthRequest": 1,
|
||||
},
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
auth, _ := app.Dao().FindExternalAuthByUserIdAndProvider("cx9u0dh2udo8xol", "google")
|
||||
if auth != nil {
|
||||
t.Fatalf("Expected the google ExternalAuth to be deleted, got got \n%v", auth)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "authorized as user - trying to unlink another user external auth",
|
||||
Method: http.MethodDelete,
|
||||
Url: "/api/users/cx9u0dh2udo8xol/external-auths/google",
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRkMDE5N2NjLTJiNGEtM2Y4My1hMjZiLWQ3N2JjODQyM2QzYyIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxODkzNDc0MDAwfQ.Wq5ac1q1f5WntIzEngXk22ydMj-eFgvfSRg7dhmPKic",
|
||||
},
|
||||
ExpectedStatus: 403,
|
||||
ExpectedContent: []string{`"data":{}`},
|
||||
},
|
||||
{
|
||||
Name: "authorized as user - owner with existing external auth",
|
||||
Method: http.MethodDelete,
|
||||
Url: "/api/users/cx9u0dh2udo8xol/external-auths/google",
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImN4OXUwZGgydWRvOHhvbCIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxODkzNDc0MDAwfQ.NgFYG2D7PftFW1tcfe5E2oDi_AVakDR9J6WI6VUZQfw",
|
||||
},
|
||||
ExpectedStatus: 204,
|
||||
ExpectedContent: []string{},
|
||||
ExpectedEvents: map[string]int{
|
||||
"OnModelAfterDelete": 1,
|
||||
"OnModelBeforeDelete": 1,
|
||||
"OnUserAfterUnlinkExternalAuthRequest": 1,
|
||||
"OnUserBeforeUnlinkExternalAuthRequest": 1,
|
||||
},
|
||||
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||
auth, _ := app.Dao().FindExternalAuthByUserIdAndProvider("cx9u0dh2udo8xol", "google")
|
||||
if auth != nil {
|
||||
t.Fatalf("Expected the google ExternalAuth to be deleted, got got \n%v", auth)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
scenario.Test(t)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -2,7 +2,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -87,10 +86,11 @@ func NewServeCommand(app core.App, showStartBanner bool) *cobra.Command {
|
||||
if httpsAddr != "" {
|
||||
schema = "https"
|
||||
}
|
||||
regular := color.New()
|
||||
bold := color.New(color.Bold).Add(color.FgGreen)
|
||||
bold.Printf("> Server started at: %s\n", color.CyanString("%s://%s", schema, serverConfig.Addr))
|
||||
fmt.Printf(" - REST API: %s\n", color.CyanString("%s://%s/api/", schema, serverConfig.Addr))
|
||||
fmt.Printf(" - Admin UI: %s\n", color.CyanString("%s://%s/_/", schema, serverConfig.Addr))
|
||||
regular.Printf(" - REST API: %s\n", color.CyanString("%s://%s/api/", schema, serverConfig.Addr))
|
||||
regular.Printf(" - Admin UI: %s\n", color.CyanString("%s://%s/_/", schema, serverConfig.Addr))
|
||||
}
|
||||
|
||||
var serveErr error
|
||||
|
||||
+13
-8
@@ -317,16 +317,21 @@ type App interface {
|
||||
// authenticated user data and token.
|
||||
OnUserAuthRequest() *hook.Hook[*UserAuthEvent]
|
||||
|
||||
// OnUserBeforeOauth2Register hook is triggered before each User OAuth2
|
||||
// authentication request (when the client config has enabled new users registration).
|
||||
// OnUserListExternalAuths hook is triggered on each API user's external auths list request.
|
||||
//
|
||||
// Could be used to additionally validate or modify the new user
|
||||
// before persisting in the DB.
|
||||
OnUserBeforeOauth2Register() *hook.Hook[*UserOauth2RegisterEvent]
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
OnUserListExternalAuths() *hook.Hook[*UserListExternalAuthsEvent]
|
||||
|
||||
// OnUserAfterOauth2Register hook is triggered after each successful User
|
||||
// OAuth2 authentication sign-up request (right after the new user persistence).
|
||||
OnUserAfterOauth2Register() *hook.Hook[*UserOauth2RegisterEvent]
|
||||
// OnUserBeforeUnlinkExternalAuthRequest hook is triggered before each API user's
|
||||
// external auth unlink request (after models load and before the actual relation deletion).
|
||||
//
|
||||
// Could be used to additionally validate the request data or implement
|
||||
// completely different delete behavior (returning [hook.StopPropagation]).
|
||||
OnUserBeforeUnlinkExternalAuthRequest() *hook.Hook[*UserUnlinkExternalAuthEvent]
|
||||
|
||||
// OnUserAfterUnlinkExternalAuthRequest hook is triggered after each
|
||||
// successful API user's external auth unlink request.
|
||||
OnUserAfterUnlinkExternalAuthRequest() *hook.Hook[*UserUnlinkExternalAuthEvent]
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Record API event hooks
|
||||
|
||||
+36
-30
@@ -85,18 +85,19 @@ type BaseApp struct {
|
||||
onAdminAfterDeleteRequest *hook.Hook[*AdminDeleteEvent]
|
||||
onAdminAuthRequest *hook.Hook[*AdminAuthEvent]
|
||||
|
||||
// user api event hooks
|
||||
onUsersListRequest *hook.Hook[*UsersListEvent]
|
||||
onUserViewRequest *hook.Hook[*UserViewEvent]
|
||||
onUserBeforeCreateRequest *hook.Hook[*UserCreateEvent]
|
||||
onUserAfterCreateRequest *hook.Hook[*UserCreateEvent]
|
||||
onUserBeforeUpdateRequest *hook.Hook[*UserUpdateEvent]
|
||||
onUserAfterUpdateRequest *hook.Hook[*UserUpdateEvent]
|
||||
onUserBeforeDeleteRequest *hook.Hook[*UserDeleteEvent]
|
||||
onUserAfterDeleteRequest *hook.Hook[*UserDeleteEvent]
|
||||
onUserAuthRequest *hook.Hook[*UserAuthEvent]
|
||||
onUserBeforeOauth2Register *hook.Hook[*UserOauth2RegisterEvent]
|
||||
onUserAfterOauth2Register *hook.Hook[*UserOauth2RegisterEvent]
|
||||
// user api event hooks
|
||||
onUsersListRequest *hook.Hook[*UsersListEvent]
|
||||
onUserViewRequest *hook.Hook[*UserViewEvent]
|
||||
onUserBeforeCreateRequest *hook.Hook[*UserCreateEvent]
|
||||
onUserAfterCreateRequest *hook.Hook[*UserCreateEvent]
|
||||
onUserBeforeUpdateRequest *hook.Hook[*UserUpdateEvent]
|
||||
onUserAfterUpdateRequest *hook.Hook[*UserUpdateEvent]
|
||||
onUserBeforeDeleteRequest *hook.Hook[*UserDeleteEvent]
|
||||
onUserAfterDeleteRequest *hook.Hook[*UserDeleteEvent]
|
||||
onUserAuthRequest *hook.Hook[*UserAuthEvent]
|
||||
onUserListExternalAuths *hook.Hook[*UserListExternalAuthsEvent]
|
||||
onUserBeforeUnlinkExternalAuthRequest *hook.Hook[*UserUnlinkExternalAuthEvent]
|
||||
onUserAfterUnlinkExternalAuthRequest *hook.Hook[*UserUnlinkExternalAuthEvent]
|
||||
|
||||
// record api event hooks
|
||||
onRecordsListRequest *hook.Hook[*RecordsListEvent]
|
||||
@@ -180,17 +181,18 @@ func NewBaseApp(dataDir string, encryptionEnv string, isDebug bool) *BaseApp {
|
||||
onAdminAuthRequest: &hook.Hook[*AdminAuthEvent]{},
|
||||
|
||||
// user API event hooks
|
||||
onUsersListRequest: &hook.Hook[*UsersListEvent]{},
|
||||
onUserViewRequest: &hook.Hook[*UserViewEvent]{},
|
||||
onUserBeforeCreateRequest: &hook.Hook[*UserCreateEvent]{},
|
||||
onUserAfterCreateRequest: &hook.Hook[*UserCreateEvent]{},
|
||||
onUserBeforeUpdateRequest: &hook.Hook[*UserUpdateEvent]{},
|
||||
onUserAfterUpdateRequest: &hook.Hook[*UserUpdateEvent]{},
|
||||
onUserBeforeDeleteRequest: &hook.Hook[*UserDeleteEvent]{},
|
||||
onUserAfterDeleteRequest: &hook.Hook[*UserDeleteEvent]{},
|
||||
onUserAuthRequest: &hook.Hook[*UserAuthEvent]{},
|
||||
onUserBeforeOauth2Register: &hook.Hook[*UserOauth2RegisterEvent]{},
|
||||
onUserAfterOauth2Register: &hook.Hook[*UserOauth2RegisterEvent]{},
|
||||
onUsersListRequest: &hook.Hook[*UsersListEvent]{},
|
||||
onUserViewRequest: &hook.Hook[*UserViewEvent]{},
|
||||
onUserBeforeCreateRequest: &hook.Hook[*UserCreateEvent]{},
|
||||
onUserAfterCreateRequest: &hook.Hook[*UserCreateEvent]{},
|
||||
onUserBeforeUpdateRequest: &hook.Hook[*UserUpdateEvent]{},
|
||||
onUserAfterUpdateRequest: &hook.Hook[*UserUpdateEvent]{},
|
||||
onUserBeforeDeleteRequest: &hook.Hook[*UserDeleteEvent]{},
|
||||
onUserAfterDeleteRequest: &hook.Hook[*UserDeleteEvent]{},
|
||||
onUserAuthRequest: &hook.Hook[*UserAuthEvent]{},
|
||||
onUserListExternalAuths: &hook.Hook[*UserListExternalAuthsEvent]{},
|
||||
onUserBeforeUnlinkExternalAuthRequest: &hook.Hook[*UserUnlinkExternalAuthEvent]{},
|
||||
onUserAfterUnlinkExternalAuthRequest: &hook.Hook[*UserUnlinkExternalAuthEvent]{},
|
||||
|
||||
// record API event hooks
|
||||
onRecordsListRequest: &hook.Hook[*RecordsListEvent]{},
|
||||
@@ -611,12 +613,16 @@ func (app *BaseApp) OnUserAuthRequest() *hook.Hook[*UserAuthEvent] {
|
||||
return app.onUserAuthRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnUserBeforeOauth2Register() *hook.Hook[*UserOauth2RegisterEvent] {
|
||||
return app.onUserBeforeOauth2Register
|
||||
func (app *BaseApp) OnUserListExternalAuths() *hook.Hook[*UserListExternalAuthsEvent] {
|
||||
return app.onUserListExternalAuths
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnUserAfterOauth2Register() *hook.Hook[*UserOauth2RegisterEvent] {
|
||||
return app.onUserAfterOauth2Register
|
||||
func (app *BaseApp) OnUserBeforeUnlinkExternalAuthRequest() *hook.Hook[*UserUnlinkExternalAuthEvent] {
|
||||
return app.onUserBeforeUnlinkExternalAuthRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnUserAfterUnlinkExternalAuthRequest() *hook.Hook[*UserUnlinkExternalAuthEvent] {
|
||||
return app.onUserAfterUnlinkExternalAuthRequest
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
@@ -710,7 +716,7 @@ func (app *BaseApp) initLogsDB() error {
|
||||
return connectErr
|
||||
}
|
||||
|
||||
app.logsDao = app.createDao(app.logsDB)
|
||||
app.logsDao = daos.New(app.logsDB)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -734,12 +740,12 @@ func (app *BaseApp) initDataDB() error {
|
||||
}
|
||||
}
|
||||
|
||||
app.dao = app.createDao(app.db)
|
||||
app.dao = app.createDaoWithHooks(app.db)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *BaseApp) createDao(db dbx.Builder) *daos.Dao {
|
||||
func (app *BaseApp) createDaoWithHooks(db dbx.Builder) *daos.Dao {
|
||||
dao := daos.New(db)
|
||||
|
||||
dao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model) error {
|
||||
|
||||
+8
-4
@@ -319,12 +319,16 @@ func TestBaseAppGetters(t *testing.T) {
|
||||
t.Fatalf("Getter app.OnUserAuthRequest does not match or nil (%v vs %v)", app.OnUserAuthRequest(), app.onUserAuthRequest)
|
||||
}
|
||||
|
||||
if app.onUserBeforeOauth2Register != app.OnUserBeforeOauth2Register() || app.OnUserBeforeOauth2Register() == nil {
|
||||
t.Fatalf("Getter app.OnUserBeforeOauth2Register does not match or nil (%v vs %v)", app.OnUserBeforeOauth2Register(), app.onUserBeforeOauth2Register)
|
||||
if app.onUserListExternalAuths != app.OnUserListExternalAuths() || app.OnUserListExternalAuths() == nil {
|
||||
t.Fatalf("Getter app.OnUserListExternalAuths does not match or nil (%v vs %v)", app.OnUserListExternalAuths(), app.onUserListExternalAuths)
|
||||
}
|
||||
|
||||
if app.onUserAfterOauth2Register != app.OnUserAfterOauth2Register() || app.OnUserAfterOauth2Register() == nil {
|
||||
t.Fatalf("Getter app.OnUserAfterOauth2Register does not match or nil (%v vs %v)", app.OnUserAfterOauth2Register(), app.onUserAfterOauth2Register)
|
||||
if app.onUserBeforeUnlinkExternalAuthRequest != app.OnUserBeforeUnlinkExternalAuthRequest() || app.OnUserBeforeUnlinkExternalAuthRequest() == nil {
|
||||
t.Fatalf("Getter app.OnUserBeforeUnlinkExternalAuthRequest does not match or nil (%v vs %v)", app.OnUserBeforeUnlinkExternalAuthRequest(), app.onUserBeforeUnlinkExternalAuthRequest)
|
||||
}
|
||||
|
||||
if app.onUserAfterUnlinkExternalAuthRequest != app.OnUserAfterUnlinkExternalAuthRequest() || app.OnUserAfterUnlinkExternalAuthRequest() == nil {
|
||||
t.Fatalf("Getter app.OnUserAfterUnlinkExternalAuthRequest does not match or nil (%v vs %v)", app.OnUserAfterUnlinkExternalAuthRequest(), app.onUserAfterUnlinkExternalAuthRequest)
|
||||
}
|
||||
|
||||
if app.onRecordsListRequest != app.OnRecordsListRequest() || app.OnRecordsListRequest() == nil {
|
||||
|
||||
+10
-5
@@ -4,7 +4,6 @@ import (
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/models/schema"
|
||||
"github.com/pocketbase/pocketbase/tools/auth"
|
||||
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||
"github.com/pocketbase/pocketbase/tools/search"
|
||||
"github.com/pocketbase/pocketbase/tools/subscriptions"
|
||||
@@ -180,10 +179,16 @@ type UserAuthEvent struct {
|
||||
Meta any
|
||||
}
|
||||
|
||||
type UserOauth2RegisterEvent struct {
|
||||
HttpContext echo.Context
|
||||
User *models.User
|
||||
AuthData *auth.AuthUser
|
||||
type UserListExternalAuthsEvent struct {
|
||||
HttpContext echo.Context
|
||||
User *models.User
|
||||
ExternalAuths []*models.ExternalAuth
|
||||
}
|
||||
|
||||
type UserUnlinkExternalAuthEvent struct {
|
||||
HttpContext echo.Context
|
||||
User *models.User
|
||||
ExternalAuth *models.ExternalAuth
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
+18
-2
@@ -35,6 +35,8 @@ type Settings struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
// NewSettings creates and returns a new default Settings instance.
|
||||
@@ -106,6 +108,14 @@ func NewSettings() *Settings {
|
||||
Enabled: false,
|
||||
AllowRegistrations: true,
|
||||
},
|
||||
DiscordAuth: AuthProviderConfig{
|
||||
Enabled: false,
|
||||
AllowRegistrations: true,
|
||||
},
|
||||
TwitterAuth: AuthProviderConfig{
|
||||
Enabled: false,
|
||||
AllowRegistrations: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +140,8 @@ func (s *Settings) Validate() error {
|
||||
validation.Field(&s.FacebookAuth),
|
||||
validation.Field(&s.GithubAuth),
|
||||
validation.Field(&s.GitlabAuth),
|
||||
validation.Field(&s.DiscordAuth),
|
||||
validation.Field(&s.TwitterAuth),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -178,6 +190,8 @@ func (s *Settings) RedactClone() (*Settings, error) {
|
||||
&clone.FacebookAuth.ClientSecret,
|
||||
&clone.GithubAuth.ClientSecret,
|
||||
&clone.GitlabAuth.ClientSecret,
|
||||
&clone.DiscordAuth.ClientSecret,
|
||||
&clone.TwitterAuth.ClientSecret,
|
||||
}
|
||||
|
||||
// mask all sensitive fields
|
||||
@@ -201,6 +215,8 @@ func (s *Settings) NamedAuthProviderConfigs() map[string]AuthProviderConfig {
|
||||
auth.NameFacebook: s.FacebookAuth,
|
||||
auth.NameGithub: s.GithubAuth,
|
||||
auth.NameGitlab: s.GitlabAuth,
|
||||
auth.NameDiscord: s.DiscordAuth,
|
||||
auth.NameTwitter: s.TwitterAuth,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +231,7 @@ type TokenConfig struct {
|
||||
func (c TokenConfig) Validate() error {
|
||||
return validation.ValidateStruct(&c,
|
||||
validation.Field(&c.Secret, validation.Required, validation.Length(30, 300)),
|
||||
validation.Field(&c.Duration, validation.Required, validation.Min(5), validation.Max(31536000)),
|
||||
validation.Field(&c.Duration, validation.Required, validation.Min(5), validation.Max(63072000)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -285,7 +301,7 @@ func (c MetaConfig) Validate() error {
|
||||
validation.Field(&c.AppName, validation.Required, validation.Length(1, 255)),
|
||||
validation.Field(&c.AppUrl, validation.Required, is.URL),
|
||||
validation.Field(&c.SenderName, validation.Required, validation.Length(1, 255)),
|
||||
validation.Field(&c.SenderAddress, is.Email, validation.Required),
|
||||
validation.Field(&c.SenderAddress, is.EmailFormat, validation.Required),
|
||||
validation.Field(&c.VerificationTemplate, validation.Required),
|
||||
validation.Field(&c.ResetPasswordTemplate, validation.Required),
|
||||
validation.Field(&c.ConfirmEmailChangeTemplate, validation.Required),
|
||||
|
||||
+25
-4
@@ -37,6 +37,10 @@ func TestSettingsValidate(t *testing.T) {
|
||||
s.GithubAuth.ClientId = ""
|
||||
s.GitlabAuth.Enabled = true
|
||||
s.GitlabAuth.ClientId = ""
|
||||
s.DiscordAuth.Enabled = true
|
||||
s.DiscordAuth.ClientId = ""
|
||||
s.TwitterAuth.Enabled = true
|
||||
s.TwitterAuth.ClientId = ""
|
||||
|
||||
// check if Validate() is triggering the members validate methods.
|
||||
err := s.Validate()
|
||||
@@ -60,6 +64,7 @@ func TestSettingsValidate(t *testing.T) {
|
||||
`"facebookAuth":{`,
|
||||
`"githubAuth":{`,
|
||||
`"gitlabAuth":{`,
|
||||
`"discordAuth":{`,
|
||||
}
|
||||
|
||||
errBytes, _ := json.Marshal(err)
|
||||
@@ -98,6 +103,10 @@ func TestSettingsMerge(t *testing.T) {
|
||||
s2.GithubAuth.ClientId = "github_test"
|
||||
s2.GitlabAuth.Enabled = true
|
||||
s2.GitlabAuth.ClientId = "gitlab_test"
|
||||
s2.DiscordAuth.Enabled = true
|
||||
s2.DiscordAuth.ClientId = "discord_test"
|
||||
s2.TwitterAuth.Enabled = true
|
||||
s2.TwitterAuth.ClientId = "twitter_test"
|
||||
|
||||
if err := s1.Merge(s2); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -163,6 +172,8 @@ func TestSettingsRedactClone(t *testing.T) {
|
||||
s1.FacebookAuth.ClientSecret = "test123"
|
||||
s1.GithubAuth.ClientSecret = "test123"
|
||||
s1.GitlabAuth.ClientSecret = "test123"
|
||||
s1.DiscordAuth.ClientSecret = "test123"
|
||||
s1.TwitterAuth.ClientSecret = "test123"
|
||||
|
||||
s2, err := s1.RedactClone()
|
||||
if err != nil {
|
||||
@@ -174,7 +185,7 @@ func TestSettingsRedactClone(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := `{"meta":{"appName":"test123","appUrl":"http://localhost:8090","hideControls":false,"senderName":"Support","senderAddress":"support@example.com","verificationTemplate":{"body":"\u003cp\u003eHello,\u003c/p\u003e\n\u003cp\u003eThank you for joining us at {APP_NAME}.\u003c/p\u003e\n\u003cp\u003eClick on the button below to verify your email address.\u003c/p\u003e\n\u003cp\u003e\n \u003ca class=\"btn\" href=\"{ACTION_URL}\" target=\"_blank\" rel=\"noopener\"\u003eVerify\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e\n Thanks,\u003cbr/\u003e\n {APP_NAME} team\n\u003c/p\u003e","subject":"Verify your {APP_NAME} email","actionUrl":"{APP_URL}/_/#/users/confirm-verification/{TOKEN}"},"resetPasswordTemplate":{"body":"\u003cp\u003eHello,\u003c/p\u003e\n\u003cp\u003eClick on the button below to reset your password.\u003c/p\u003e\n\u003cp\u003e\n \u003ca class=\"btn\" href=\"{ACTION_URL}\" target=\"_blank\" rel=\"noopener\"\u003eReset password\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003ci\u003eIf you didn't ask to reset your password, you can ignore this email.\u003c/i\u003e\u003c/p\u003e\n\u003cp\u003e\n Thanks,\u003cbr/\u003e\n {APP_NAME} team\n\u003c/p\u003e","subject":"Reset your {APP_NAME} password","actionUrl":"{APP_URL}/_/#/users/confirm-password-reset/{TOKEN}"},"confirmEmailChangeTemplate":{"body":"\u003cp\u003eHello,\u003c/p\u003e\n\u003cp\u003eClick on the button below to confirm your new email address.\u003c/p\u003e\n\u003cp\u003e\n \u003ca class=\"btn\" href=\"{ACTION_URL}\" target=\"_blank\" rel=\"noopener\"\u003eConfirm new email\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003ci\u003eIf you didn't ask to change your email address, you can ignore this email.\u003c/i\u003e\u003c/p\u003e\n\u003cp\u003e\n Thanks,\u003cbr/\u003e\n {APP_NAME} team\n\u003c/p\u003e","subject":"Confirm your {APP_NAME} new email address","actionUrl":"{APP_URL}/_/#/users/confirm-email-change/{TOKEN}"}},"logs":{"maxDays":7},"smtp":{"enabled":false,"host":"smtp.example.com","port":587,"username":"","password":"******","tls":true},"s3":{"enabled":false,"bucket":"","region":"","endpoint":"","accessKey":"","secret":"******","forcePathStyle":false},"adminAuthToken":{"secret":"******","duration":1209600},"adminPasswordResetToken":{"secret":"******","duration":1800},"userAuthToken":{"secret":"******","duration":1209600},"userPasswordResetToken":{"secret":"******","duration":1800},"userEmailChangeToken":{"secret":"******","duration":1800},"userVerificationToken":{"secret":"******","duration":604800},"emailAuth":{"enabled":true,"exceptDomains":null,"onlyDomains":null,"minPasswordLength":8},"googleAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"facebookAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"githubAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"gitlabAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"}}`
|
||||
expected := `{"meta":{"appName":"test123","appUrl":"http://localhost:8090","hideControls":false,"senderName":"Support","senderAddress":"support@example.com","verificationTemplate":{"body":"\u003cp\u003eHello,\u003c/p\u003e\n\u003cp\u003eThank you for joining us at {APP_NAME}.\u003c/p\u003e\n\u003cp\u003eClick on the button below to verify your email address.\u003c/p\u003e\n\u003cp\u003e\n \u003ca class=\"btn\" href=\"{ACTION_URL}\" target=\"_blank\" rel=\"noopener\"\u003eVerify\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e\n Thanks,\u003cbr/\u003e\n {APP_NAME} team\n\u003c/p\u003e","subject":"Verify your {APP_NAME} email","actionUrl":"{APP_URL}/_/#/users/confirm-verification/{TOKEN}"},"resetPasswordTemplate":{"body":"\u003cp\u003eHello,\u003c/p\u003e\n\u003cp\u003eClick on the button below to reset your password.\u003c/p\u003e\n\u003cp\u003e\n \u003ca class=\"btn\" href=\"{ACTION_URL}\" target=\"_blank\" rel=\"noopener\"\u003eReset password\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003ci\u003eIf you didn't ask to reset your password, you can ignore this email.\u003c/i\u003e\u003c/p\u003e\n\u003cp\u003e\n Thanks,\u003cbr/\u003e\n {APP_NAME} team\n\u003c/p\u003e","subject":"Reset your {APP_NAME} password","actionUrl":"{APP_URL}/_/#/users/confirm-password-reset/{TOKEN}"},"confirmEmailChangeTemplate":{"body":"\u003cp\u003eHello,\u003c/p\u003e\n\u003cp\u003eClick on the button below to confirm your new email address.\u003c/p\u003e\n\u003cp\u003e\n \u003ca class=\"btn\" href=\"{ACTION_URL}\" target=\"_blank\" rel=\"noopener\"\u003eConfirm new email\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003ci\u003eIf you didn't ask to change your email address, you can ignore this email.\u003c/i\u003e\u003c/p\u003e\n\u003cp\u003e\n Thanks,\u003cbr/\u003e\n {APP_NAME} team\n\u003c/p\u003e","subject":"Confirm your {APP_NAME} new email address","actionUrl":"{APP_URL}/_/#/users/confirm-email-change/{TOKEN}"}},"logs":{"maxDays":7},"smtp":{"enabled":false,"host":"smtp.example.com","port":587,"username":"","password":"******","tls":true},"s3":{"enabled":false,"bucket":"","region":"","endpoint":"","accessKey":"","secret":"******","forcePathStyle":false},"adminAuthToken":{"secret":"******","duration":1209600},"adminPasswordResetToken":{"secret":"******","duration":1800},"userAuthToken":{"secret":"******","duration":1209600},"userPasswordResetToken":{"secret":"******","duration":1800},"userEmailChangeToken":{"secret":"******","duration":1800},"userVerificationToken":{"secret":"******","duration":604800},"emailAuth":{"enabled":true,"exceptDomains":null,"onlyDomains":null,"minPasswordLength":8},"googleAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"facebookAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"githubAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"gitlabAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"discordAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"twitterAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"}}`
|
||||
|
||||
if encodedStr := string(encoded); encodedStr != expected {
|
||||
t.Fatalf("Expected %v, got \n%v", expected, encodedStr)
|
||||
@@ -189,6 +200,8 @@ func TestNamedAuthProviderConfigs(t *testing.T) {
|
||||
s.GithubAuth.ClientId = "github_test"
|
||||
s.GitlabAuth.ClientId = "gitlab_test"
|
||||
s.GitlabAuth.Enabled = true
|
||||
s.DiscordAuth.ClientId = "discord_test"
|
||||
s.TwitterAuth.ClientId = "twitter_test"
|
||||
|
||||
result := s.NamedAuthProviderConfigs()
|
||||
|
||||
@@ -197,7 +210,7 @@ func TestNamedAuthProviderConfigs(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := `{"facebook":{"enabled":false,"allowRegistrations":true,"clientId":"facebook_test"},"github":{"enabled":false,"allowRegistrations":true,"clientId":"github_test"},"gitlab":{"enabled":true,"allowRegistrations":true,"clientId":"gitlab_test"},"google":{"enabled":false,"allowRegistrations":true,"clientId":"google_test"}}`
|
||||
expected := `{"discord":{"enabled":false,"allowRegistrations":true,"clientId":"discord_test"},"facebook":{"enabled":false,"allowRegistrations":true,"clientId":"facebook_test"},"github":{"enabled":false,"allowRegistrations":true,"clientId":"github_test"},"gitlab":{"enabled":true,"allowRegistrations":true,"clientId":"gitlab_test"},"google":{"enabled":false,"allowRegistrations":true,"clientId":"google_test"},"twitter":{"enabled":false,"allowRegistrations":true,"clientId":"twitter_test"}}`
|
||||
|
||||
if encodedStr := string(encoded); encodedStr != expected {
|
||||
t.Fatalf("Expected the same serialization, got %v", encodedStr)
|
||||
@@ -217,15 +230,23 @@ func TestTokenConfigValidate(t *testing.T) {
|
||||
// invalid data
|
||||
{
|
||||
core.TokenConfig{
|
||||
Secret: "test",
|
||||
Secret: strings.Repeat("a", 5),
|
||||
Duration: 4,
|
||||
},
|
||||
true,
|
||||
},
|
||||
// valid secret but invalid duration
|
||||
{
|
||||
core.TokenConfig{
|
||||
Secret: strings.Repeat("a", 30),
|
||||
Duration: 63072000 + 1,
|
||||
},
|
||||
true,
|
||||
},
|
||||
// valid data
|
||||
{
|
||||
core.TokenConfig{
|
||||
Secret: "testtesttesttesttesttesttestte",
|
||||
Secret: strings.Repeat("a", 30),
|
||||
Duration: 100,
|
||||
},
|
||||
false,
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package daos
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
// ExternalAuthQuery returns a new ExternalAuth select query.
|
||||
func (dao *Dao) ExternalAuthQuery() *dbx.SelectQuery {
|
||||
return dao.ModelQuery(&models.ExternalAuth{})
|
||||
}
|
||||
|
||||
/// FindAllExternalAuthsByUserId returns all ExternalAuth models
|
||||
/// linked to the provided userId.
|
||||
func (dao *Dao) FindAllExternalAuthsByUserId(userId string) ([]*models.ExternalAuth, error) {
|
||||
auths := []*models.ExternalAuth{}
|
||||
|
||||
err := dao.ExternalAuthQuery().
|
||||
AndWhere(dbx.HashExp{"userId": userId}).
|
||||
OrderBy("created ASC").
|
||||
All(&auths)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return auths, nil
|
||||
}
|
||||
|
||||
// FindExternalAuthByProvider returns the first available
|
||||
// ExternalAuth model for the specified provider and providerId.
|
||||
func (dao *Dao) FindExternalAuthByProvider(provider, providerId string) (*models.ExternalAuth, error) {
|
||||
model := &models.ExternalAuth{}
|
||||
|
||||
err := dao.ExternalAuthQuery().
|
||||
AndWhere(dbx.Not(dbx.HashExp{"providerId": ""})). // exclude empty providerIds
|
||||
AndWhere(dbx.HashExp{
|
||||
"provider": provider,
|
||||
"providerId": providerId,
|
||||
}).
|
||||
Limit(1).
|
||||
One(model)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return model, nil
|
||||
}
|
||||
|
||||
// FindExternalAuthByUserIdAndProvider returns the first available
|
||||
// ExternalAuth model for the specified userId and provider.
|
||||
func (dao *Dao) FindExternalAuthByUserIdAndProvider(userId, provider string) (*models.ExternalAuth, error) {
|
||||
model := &models.ExternalAuth{}
|
||||
|
||||
err := dao.ExternalAuthQuery().
|
||||
AndWhere(dbx.HashExp{
|
||||
"userId": userId,
|
||||
"provider": provider,
|
||||
}).
|
||||
Limit(1).
|
||||
One(model)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return model, nil
|
||||
}
|
||||
|
||||
// SaveExternalAuth upserts the provided ExternalAuth model.
|
||||
func (dao *Dao) SaveExternalAuth(model *models.ExternalAuth) error {
|
||||
// extra check the model data in case the provider's API response
|
||||
// has changed and no longer returns the expected fields
|
||||
if model.UserId == "" || model.Provider == "" || model.ProviderId == "" {
|
||||
return errors.New("Missing required ExternalAuth fields.")
|
||||
}
|
||||
|
||||
return dao.Save(model)
|
||||
}
|
||||
|
||||
// DeleteExternalAuth deletes the provided ExternalAuth model.
|
||||
//
|
||||
// The delete may fail if the linked user doesn't have an email and
|
||||
// there are no other linked ExternalAuth models available.
|
||||
func (dao *Dao) DeleteExternalAuth(model *models.ExternalAuth) error {
|
||||
user, err := dao.FindUserById(model.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if the user doesn't have an email, make sure that there
|
||||
// is at least one other external auth relation available
|
||||
if user.Email == "" {
|
||||
allExternalAuths, err := dao.FindAllExternalAuthsByUserId(user.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(allExternalAuths) <= 1 {
|
||||
return errors.New("You cannot delete the only available external auth relation because the user doesn't have an email address.")
|
||||
}
|
||||
}
|
||||
|
||||
return dao.Delete(model)
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package daos_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tests"
|
||||
)
|
||||
|
||||
func TestExternalAuthQuery(t *testing.T) {
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
expected := "SELECT {{_externalAuths}}.* FROM `_externalAuths`"
|
||||
|
||||
sql := app.Dao().ExternalAuthQuery().Build().SQL()
|
||||
if sql != expected {
|
||||
t.Errorf("Expected sql %s, got %s", expected, sql)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindAllExternalAuthsByUserId(t *testing.T) {
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
scenarios := []struct {
|
||||
userId string
|
||||
expectedCount int
|
||||
}{
|
||||
{"", 0},
|
||||
{"missing", 0},
|
||||
{"97cc3d3d-6ba2-383f-b42a-7bc84d27410c", 0},
|
||||
{"cx9u0dh2udo8xol", 2},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
auths, err := app.Dao().FindAllExternalAuthsByUserId(s.userId)
|
||||
if err != nil {
|
||||
t.Errorf("(%d) Unexpected error %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(auths) != s.expectedCount {
|
||||
t.Errorf("(%d) Expected %d auths, got %d", i, s.expectedCount, len(auths))
|
||||
}
|
||||
|
||||
for _, auth := range auths {
|
||||
if auth.UserId != s.userId {
|
||||
t.Errorf("(%d) Expected all auths to be linked to userId %s, got %v", i, s.userId, auth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindExternalAuthByProvider(t *testing.T) {
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
scenarios := []struct {
|
||||
provider string
|
||||
providerId string
|
||||
expectedId string
|
||||
}{
|
||||
{"", "", ""},
|
||||
{"github", "", ""},
|
||||
{"github", "id1", ""},
|
||||
{"github", "id2", ""},
|
||||
{"google", "id1", "abcdefghijklmn0"},
|
||||
{"gitlab", "id2", "abcdefghijklmn1"},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
auth, err := app.Dao().FindExternalAuthByProvider(s.provider, s.providerId)
|
||||
|
||||
hasErr := err != nil
|
||||
expectErr := s.expectedId == ""
|
||||
if hasErr != expectErr {
|
||||
t.Errorf("(%d) Expected hasErr %v, got %v", i, expectErr, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if auth != nil && auth.Id != s.expectedId {
|
||||
t.Errorf("(%d) Expected external auth with ID %s, got \n%v", i, s.expectedId, auth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindExternalAuthByUserIdAndProvider(t *testing.T) {
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
scenarios := []struct {
|
||||
userId string
|
||||
provider string
|
||||
expectedId string
|
||||
}{
|
||||
{"", "", ""},
|
||||
{"", "github", ""},
|
||||
{"123456", "github", ""}, // missing user and provider record
|
||||
{"123456", "google", ""}, // missing user but existing provider record
|
||||
{"97cc3d3d-6ba2-383f-b42a-7bc84d27410c", "google", ""},
|
||||
{"cx9u0dh2udo8xol", "google", "abcdefghijklmn0"},
|
||||
{"cx9u0dh2udo8xol", "gitlab", "abcdefghijklmn1"},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
auth, err := app.Dao().FindExternalAuthByUserIdAndProvider(s.userId, s.provider)
|
||||
|
||||
hasErr := err != nil
|
||||
expectErr := s.expectedId == ""
|
||||
if hasErr != expectErr {
|
||||
t.Errorf("(%d) Expected hasErr %v, got %v", i, expectErr, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if auth != nil && auth.Id != s.expectedId {
|
||||
t.Errorf("(%d) Expected external auth with ID %s, got \n%v", i, s.expectedId, auth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveExternalAuth(t *testing.T) {
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
// save with empty provider data
|
||||
emptyAuth := &models.ExternalAuth{}
|
||||
if err := app.Dao().SaveExternalAuth(emptyAuth); err == nil {
|
||||
t.Fatal("Expected error, got nil")
|
||||
}
|
||||
|
||||
auth := &models.ExternalAuth{
|
||||
UserId: "97cc3d3d-6ba2-383f-b42a-7bc84d27410c",
|
||||
Provider: "test",
|
||||
ProviderId: "test_id",
|
||||
}
|
||||
|
||||
if err := app.Dao().SaveExternalAuth(auth); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// check if it was really saved
|
||||
foundAuth, err := app.Dao().FindExternalAuthByProvider("test", "test_id")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if auth.Id != foundAuth.Id {
|
||||
t.Fatalf("Expected ExternalAuth with id %s, got \n%v", auth.Id, foundAuth)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteExternalAuth(t *testing.T) {
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
user, err := app.Dao().FindUserById("cx9u0dh2udo8xol")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
auths, err := app.Dao().FindAllExternalAuthsByUserId(user.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := app.Dao().DeleteExternalAuth(auths[0]); err != nil {
|
||||
t.Fatalf("Failed to delete the first ExternalAuth relation, got \n%v", err)
|
||||
}
|
||||
|
||||
if err := app.Dao().DeleteExternalAuth(auths[1]); err == nil {
|
||||
t.Fatal("Expected delete to fail, got nil")
|
||||
}
|
||||
|
||||
// update the user model and try again
|
||||
user.Email = "test_new@example.com"
|
||||
if err := app.Dao().SaveUser(user); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// try to delete auths[1] again
|
||||
if err := app.Dao().DeleteExternalAuth(auths[1]); err != nil {
|
||||
t.Fatalf("Failed to delete the last ExternalAuth relation, got \n%v", err)
|
||||
}
|
||||
|
||||
// check if the relations were really deleted
|
||||
newAuths, err := app.Dao().FindAllExternalAuthsByUserId(user.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(newAuths) != 0 {
|
||||
t.Fatalf("Expected all user %s ExternalAuth relations to be deleted, got \n%v", user.Id, newAuths)
|
||||
}
|
||||
}
|
||||
+30
-1
@@ -114,12 +114,41 @@ func (dao *Dao) expandRecords(records []*models.Record, expandPath string, fetch
|
||||
|
||||
expandData := model.GetExpand()
|
||||
|
||||
// normalize and set the expanded relations
|
||||
// normalize access to the previously expanded rel records (if any)
|
||||
var oldExpandedRels []*models.Record
|
||||
switch v := expandData[relField.Name].(type) {
|
||||
case nil:
|
||||
// no old expands
|
||||
case *models.Record:
|
||||
oldExpandedRels = []*models.Record{v}
|
||||
case []*models.Record:
|
||||
oldExpandedRels = v
|
||||
}
|
||||
|
||||
// merge expands
|
||||
for _, oldExpandedRel := range oldExpandedRels {
|
||||
// find a matching rel record
|
||||
for _, rel := range validRels {
|
||||
if rel.Id != oldExpandedRel.Id {
|
||||
continue
|
||||
}
|
||||
|
||||
oldRelExpand := oldExpandedRel.GetExpand()
|
||||
newRelExpand := rel.GetExpand()
|
||||
for k, v := range oldRelExpand {
|
||||
newRelExpand[k] = v
|
||||
}
|
||||
rel.SetExpand(newRelExpand)
|
||||
}
|
||||
}
|
||||
|
||||
// update the expanded data
|
||||
if relFieldOptions.MaxSelect == 1 {
|
||||
expandData[relField.Name] = validRels[0]
|
||||
} else {
|
||||
expandData[relField.Name] = validRels
|
||||
}
|
||||
|
||||
model.SetExpand(expandData)
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,18 @@ func TestExpandRecords(t *testing.T) {
|
||||
9,
|
||||
0,
|
||||
},
|
||||
// expand multiple relations sharing a common root path
|
||||
{
|
||||
[]string{
|
||||
"i15r5aa28ad06c8",
|
||||
},
|
||||
[]string{"manyrels.onerel.manyrels.onerel", "manyrels.onerel.onerel"},
|
||||
func(c *models.Collection, ids []string) ([]*models.Record, error) {
|
||||
return app.Dao().FindRecordsByIds(c, ids, nil)
|
||||
},
|
||||
4,
|
||||
0,
|
||||
},
|
||||
// single expand
|
||||
{
|
||||
[]string{
|
||||
|
||||
+2
-1
@@ -94,7 +94,7 @@ func (dao *Dao) FindUserById(id string) (*models.User, error) {
|
||||
return model, nil
|
||||
}
|
||||
|
||||
// FindUserByEmail finds a single User model by its email address.
|
||||
// FindUserByEmail finds a single User model by its non-empty email address.
|
||||
//
|
||||
// This method also auto loads the related user profile record
|
||||
// into the found model.
|
||||
@@ -102,6 +102,7 @@ func (dao *Dao) FindUserByEmail(email string) (*models.User, error) {
|
||||
model := &models.User{}
|
||||
|
||||
err := dao.UserQuery().
|
||||
AndWhere(dbx.Not(dbx.HashExp{"email": ""})).
|
||||
AndWhere(dbx.HashExp{"email": email}).
|
||||
Limit(1).
|
||||
One(model)
|
||||
|
||||
@@ -110,6 +110,7 @@ func TestFindUserByEmail(t *testing.T) {
|
||||
email string
|
||||
expectError bool
|
||||
}{
|
||||
{"", true},
|
||||
{"invalid", true},
|
||||
{"missing@example.com", true},
|
||||
{"test@example.com", false},
|
||||
|
||||
+23
-3
@@ -2,20 +2,40 @@ package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
func defaultPublicDir() string {
|
||||
if strings.HasPrefix(os.Args[0], os.TempDir()) {
|
||||
// most likely ran with go run
|
||||
return "./pb_public"
|
||||
}
|
||||
|
||||
return filepath.Join(os.Args[0], "../pb_public")
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := pocketbase.New()
|
||||
|
||||
var publicDirFlag string
|
||||
|
||||
// add "--publicDir" option flag
|
||||
app.RootCmd.PersistentFlags().StringVar(
|
||||
&publicDirFlag,
|
||||
"publicDir",
|
||||
defaultPublicDir(),
|
||||
"the directory to serve static files",
|
||||
)
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
// serves static files from the provided public dir (if exists)
|
||||
subFs := echo.MustSubFS(e.Router.Filesystem, "pb_public")
|
||||
e.Router.GET("/*", apis.StaticDirectoryHandler(subFs, false))
|
||||
e.Router.GET("/*", apis.StaticDirectoryHandler(os.DirFS(publicDirFlag), false))
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -22,15 +22,15 @@ type AdminLogin struct {
|
||||
//
|
||||
// NB! App is a required struct member.
|
||||
type AdminLoginConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
App core.App
|
||||
Dao *daos.Dao
|
||||
}
|
||||
|
||||
// NewAdminLogin creates a new [AdminLogin] form with initializer
|
||||
// config created from the provided [core.App] instance.
|
||||
//
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewAdminLoginWithConfig] with explicitly set TxDao.
|
||||
// [NewAdminLoginWithConfig] with explicitly set Dao.
|
||||
func NewAdminLogin(app core.App) *AdminLogin {
|
||||
return NewAdminLoginWithConfig(AdminLoginConfig{
|
||||
App: app,
|
||||
@@ -46,8 +46,8 @@ func NewAdminLoginWithConfig(config AdminLoginConfig) *AdminLogin {
|
||||
panic("Missing required config.App instance.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
return form
|
||||
@@ -68,7 +68,7 @@ func (form *AdminLogin) Submit() (*models.Admin, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
admin, err := form.config.TxDao.FindAdminByEmail(form.Email)
|
||||
admin, err := form.config.Dao.FindAdminByEmail(form.Email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -21,15 +21,15 @@ type AdminPasswordResetConfirm struct {
|
||||
//
|
||||
// NB! App is required struct member.
|
||||
type AdminPasswordResetConfirmConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
App core.App
|
||||
Dao *daos.Dao
|
||||
}
|
||||
|
||||
// NewAdminPasswordResetConfirm creates a new [AdminPasswordResetConfirm]
|
||||
// form with initializer config created from the provided [core.App] instance.
|
||||
//
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewAdminPasswordResetConfirmWithConfig] with explicitly set TxDao.
|
||||
// [NewAdminPasswordResetConfirmWithConfig] with explicitly set Dao.
|
||||
func NewAdminPasswordResetConfirm(app core.App) *AdminPasswordResetConfirm {
|
||||
return NewAdminPasswordResetConfirmWithConfig(AdminPasswordResetConfirmConfig{
|
||||
App: app,
|
||||
@@ -45,8 +45,8 @@ func NewAdminPasswordResetConfirmWithConfig(config AdminPasswordResetConfirmConf
|
||||
panic("Missing required config.App instance.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
return form
|
||||
@@ -67,7 +67,7 @@ func (form *AdminPasswordResetConfirm) checkToken(value any) error {
|
||||
return nil // nothing to check
|
||||
}
|
||||
|
||||
admin, err := form.config.TxDao.FindAdminByToken(
|
||||
admin, err := form.config.Dao.FindAdminByToken(
|
||||
v,
|
||||
form.config.App.Settings().AdminPasswordResetToken.Secret,
|
||||
)
|
||||
@@ -85,7 +85,7 @@ func (form *AdminPasswordResetConfirm) Submit() (*models.Admin, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
admin, err := form.config.TxDao.FindAdminByToken(
|
||||
admin, err := form.config.Dao.FindAdminByToken(
|
||||
form.Token,
|
||||
form.config.App.Settings().AdminPasswordResetToken.Secret,
|
||||
)
|
||||
@@ -97,7 +97,7 @@ func (form *AdminPasswordResetConfirm) Submit() (*models.Admin, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := form.config.TxDao.SaveAdmin(admin); err != nil {
|
||||
if err := form.config.Dao.SaveAdmin(admin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ type AdminPasswordResetRequest struct {
|
||||
// NB! App is required struct member.
|
||||
type AdminPasswordResetRequestConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
Dao *daos.Dao
|
||||
ResendThreshold float64 // in seconds
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ type AdminPasswordResetRequestConfig struct {
|
||||
// form with initializer config created from the provided [core.App] instance.
|
||||
//
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewAdminPasswordResetRequestWithConfig] with explicitly set TxDao.
|
||||
// [NewAdminPasswordResetRequestWithConfig] with explicitly set Dao.
|
||||
func NewAdminPasswordResetRequest(app core.App) *AdminPasswordResetRequest {
|
||||
return NewAdminPasswordResetRequestWithConfig(AdminPasswordResetRequestConfig{
|
||||
App: app,
|
||||
@@ -49,8 +49,8 @@ func NewAdminPasswordResetRequestWithConfig(config AdminPasswordResetRequestConf
|
||||
panic("Missing required config.App instance.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
return form
|
||||
@@ -65,7 +65,7 @@ func (form *AdminPasswordResetRequest) Validate() error {
|
||||
&form.Email,
|
||||
validation.Required,
|
||||
validation.Length(1, 255),
|
||||
is.Email,
|
||||
is.EmailFormat,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func (form *AdminPasswordResetRequest) Submit() error {
|
||||
return err
|
||||
}
|
||||
|
||||
admin, err := form.config.TxDao.FindAdminByEmail(form.Email)
|
||||
admin, err := form.config.Dao.FindAdminByEmail(form.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -95,5 +95,5 @@ func (form *AdminPasswordResetRequest) Submit() error {
|
||||
// update last sent timestamp
|
||||
admin.LastResetSentAt = types.NowDateTime()
|
||||
|
||||
return form.config.TxDao.SaveAdmin(admin)
|
||||
return form.config.Dao.SaveAdmin(admin)
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ type AdminUpsert struct {
|
||||
//
|
||||
// NB! App is a required struct member.
|
||||
type AdminUpsertConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
App core.App
|
||||
Dao *daos.Dao
|
||||
}
|
||||
|
||||
// NewAdminUpsert creates a new [AdminUpsert] form with initializer
|
||||
@@ -34,7 +34,7 @@ type AdminUpsertConfig struct {
|
||||
// (for create you could pass a pointer to an empty Admin - `&models.Admin{}`).
|
||||
//
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewAdminUpsertWithConfig] with explicitly set TxDao.
|
||||
// [NewAdminUpsertWithConfig] with explicitly set Dao.
|
||||
func NewAdminUpsert(app core.App, admin *models.Admin) *AdminUpsert {
|
||||
return NewAdminUpsertWithConfig(AdminUpsertConfig{
|
||||
App: app,
|
||||
@@ -54,8 +54,8 @@ func NewAdminUpsertWithConfig(config AdminUpsertConfig, admin *models.Admin) *Ad
|
||||
panic("Invalid initializer config or nil upsert model.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
// load defaults
|
||||
@@ -86,7 +86,7 @@ func (form *AdminUpsert) Validate() error {
|
||||
&form.Email,
|
||||
validation.Required,
|
||||
validation.Length(1, 255),
|
||||
is.Email,
|
||||
is.EmailFormat,
|
||||
validation.By(form.checkUniqueEmail),
|
||||
),
|
||||
validation.Field(
|
||||
@@ -105,7 +105,7 @@ func (form *AdminUpsert) Validate() error {
|
||||
func (form *AdminUpsert) checkUniqueEmail(value any) error {
|
||||
v, _ := value.(string)
|
||||
|
||||
if form.config.TxDao.IsAdminEmailUnique(v, form.admin.Id) {
|
||||
if form.config.Dao.IsAdminEmailUnique(v, form.admin.Id) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -135,6 +135,6 @@ func (form *AdminUpsert) Submit(interceptors ...InterceptorFunc) error {
|
||||
}
|
||||
|
||||
return runInterceptors(func() error {
|
||||
return form.config.TxDao.SaveAdmin(form.admin)
|
||||
return form.config.Dao.SaveAdmin(form.admin)
|
||||
}, interceptors...)
|
||||
}
|
||||
|
||||
+10
-10
@@ -36,8 +36,8 @@ type CollectionUpsert struct {
|
||||
//
|
||||
// NB! App is a required struct member.
|
||||
type CollectionUpsertConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
App core.App
|
||||
Dao *daos.Dao
|
||||
}
|
||||
|
||||
// NewCollectionUpsert creates a new [CollectionUpsert] form with initializer
|
||||
@@ -45,7 +45,7 @@ type CollectionUpsertConfig struct {
|
||||
// (for create you could pass a pointer to an empty Collection - `&models.Collection{}`).
|
||||
//
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewCollectionUpsertWithConfig] with explicitly set TxDao.
|
||||
// [NewCollectionUpsertWithConfig] with explicitly set Dao.
|
||||
func NewCollectionUpsert(app core.App, collection *models.Collection) *CollectionUpsert {
|
||||
return NewCollectionUpsertWithConfig(CollectionUpsertConfig{
|
||||
App: app,
|
||||
@@ -65,8 +65,8 @@ func NewCollectionUpsertWithConfig(config CollectionUpsertConfig, collection *mo
|
||||
panic("Invalid initializer config or nil upsert model.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
// load defaults
|
||||
@@ -130,11 +130,11 @@ func (form *CollectionUpsert) Validate() error {
|
||||
func (form *CollectionUpsert) checkUniqueName(value any) error {
|
||||
v, _ := value.(string)
|
||||
|
||||
if !form.config.TxDao.IsCollectionNameUnique(v, form.collection.Id) {
|
||||
if !form.config.Dao.IsCollectionNameUnique(v, form.collection.Id) {
|
||||
return validation.NewError("validation_collection_name_exists", "Collection name must be unique (case insensitive).")
|
||||
}
|
||||
|
||||
if (form.collection.IsNew() || !strings.EqualFold(v, form.collection.Name)) && form.config.TxDao.HasTable(v) {
|
||||
if (form.collection.IsNew() || !strings.EqualFold(v, form.collection.Name)) && form.config.Dao.HasTable(v) {
|
||||
return validation.NewError("validation_collection_name_table_exists", "The collection name must be also unique table name.")
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ func (form *CollectionUpsert) ensureExistingRelationCollectionId(value any) erro
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := form.config.TxDao.FindCollectionByNameOrId(options.CollectionId); err != nil {
|
||||
if _, err := form.config.Dao.FindCollectionByNameOrId(options.CollectionId); err != nil {
|
||||
return validation.Errors{fmt.Sprint(i): validation.NewError(
|
||||
"validation_field_invalid_relation",
|
||||
"The relation collection doesn't exist.",
|
||||
@@ -228,7 +228,7 @@ func (form *CollectionUpsert) checkRule(value any) error {
|
||||
}
|
||||
|
||||
dummy := &models.Collection{Schema: form.Schema}
|
||||
r := resolvers.NewRecordFieldResolver(form.config.TxDao, dummy, nil)
|
||||
r := resolvers.NewRecordFieldResolver(form.config.Dao, dummy, nil)
|
||||
|
||||
_, err := search.FilterData(*v).BuildExpr(r)
|
||||
if err != nil {
|
||||
@@ -273,6 +273,6 @@ func (form *CollectionUpsert) Submit(interceptors ...InterceptorFunc) error {
|
||||
form.collection.DeleteRule = form.DeleteRule
|
||||
|
||||
return runInterceptors(func() error {
|
||||
return form.config.TxDao.SaveCollection(form.collection)
|
||||
return form.config.Dao.SaveCollection(form.collection)
|
||||
}, interceptors...)
|
||||
}
|
||||
|
||||
@@ -24,15 +24,15 @@ type CollectionsImport struct {
|
||||
//
|
||||
// NB! App is a required struct member.
|
||||
type CollectionsImportConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
App core.App
|
||||
Dao *daos.Dao
|
||||
}
|
||||
|
||||
// NewCollectionsImport creates a new [CollectionsImport] form with
|
||||
// initializer config created from the provided [core.App] instance.
|
||||
//
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewCollectionsImportWithConfig] with explicitly set TxDao.
|
||||
// [NewCollectionsImportWithConfig] with explicitly set Dao.
|
||||
func NewCollectionsImport(app core.App) *CollectionsImport {
|
||||
return NewCollectionsImportWithConfig(CollectionsImportConfig{
|
||||
App: app,
|
||||
@@ -48,8 +48,8 @@ func NewCollectionsImportWithConfig(config CollectionsImportConfig) *Collections
|
||||
panic("Missing required config.App instance.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
return form
|
||||
@@ -79,7 +79,7 @@ func (form *CollectionsImport) Submit(interceptors ...InterceptorFunc) error {
|
||||
}
|
||||
|
||||
return runInterceptors(func() error {
|
||||
return form.config.TxDao.RunInTransaction(func(txDao *daos.Dao) error {
|
||||
return form.config.Dao.RunInTransaction(func(txDao *daos.Dao) error {
|
||||
importErr := txDao.ImportCollections(
|
||||
form.Collections,
|
||||
form.DeleteMissing,
|
||||
@@ -122,8 +122,8 @@ func (form *CollectionsImport) beforeRecordsSync(txDao *daos.Dao, mappedNew, map
|
||||
}
|
||||
|
||||
upsertForm := NewCollectionUpsertWithConfig(CollectionUpsertConfig{
|
||||
App: form.config.App,
|
||||
TxDao: txDao,
|
||||
App: form.config.App,
|
||||
Dao: txDao,
|
||||
}, upsertModel)
|
||||
|
||||
// load form fields with the refreshed collection state
|
||||
|
||||
@@ -37,8 +37,8 @@ type RecordUpsert struct {
|
||||
//
|
||||
// NB! App is required struct member.
|
||||
type RecordUpsertConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
App core.App
|
||||
Dao *daos.Dao
|
||||
}
|
||||
|
||||
// NewRecordUpsert creates a new [RecordUpsert] form with initializer
|
||||
@@ -46,7 +46,7 @@ type RecordUpsertConfig struct {
|
||||
// (for create you could pass a pointer to an empty Record - `models.NewRecord(collection)`).
|
||||
//
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewRecordUpsertWithConfig] with explicitly set TxDao.
|
||||
// [NewRecordUpsertWithConfig] with explicitly set Dao.
|
||||
func NewRecordUpsert(app core.App, record *models.Record) *RecordUpsert {
|
||||
return NewRecordUpsertWithConfig(RecordUpsertConfig{
|
||||
App: app,
|
||||
@@ -68,8 +68,8 @@ func NewRecordUpsertWithConfig(config RecordUpsertConfig, record *models.Record)
|
||||
panic("Invalid initializer config or nil upsert model.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
form.Id = record.Id
|
||||
@@ -298,7 +298,7 @@ func (form *RecordUpsert) Validate() error {
|
||||
|
||||
// record data validator
|
||||
dataValidator := validators.NewRecordDataValidator(
|
||||
form.config.TxDao,
|
||||
form.config.Dao,
|
||||
form.record,
|
||||
form.filesToUpload,
|
||||
)
|
||||
@@ -328,7 +328,7 @@ func (form *RecordUpsert) DrySubmit(callback func(txDao *daos.Dao) error) error
|
||||
return err
|
||||
}
|
||||
|
||||
return form.config.TxDao.RunInTransaction(func(txDao *daos.Dao) error {
|
||||
return form.config.Dao.RunInTransaction(func(txDao *daos.Dao) error {
|
||||
tx, ok := txDao.DB().(*dbx.Tx)
|
||||
if !ok {
|
||||
return errors.New("failed to get transaction db")
|
||||
@@ -378,7 +378,7 @@ func (form *RecordUpsert) Submit(interceptors ...InterceptorFunc) error {
|
||||
}
|
||||
|
||||
return runInterceptors(func() error {
|
||||
return form.config.TxDao.RunInTransaction(func(txDao *daos.Dao) error {
|
||||
return form.config.Dao.RunInTransaction(func(txDao *daos.Dao) error {
|
||||
// persist record model
|
||||
if err := txDao.SaveRecord(form.record); err != nil {
|
||||
return err
|
||||
|
||||
@@ -139,7 +139,7 @@ func TestRecordUpsertLoadDataJson(t *testing.T) {
|
||||
t.Fatalf("Expect only 1 manyfiles to remain, got \n%v", manyfiles)
|
||||
}
|
||||
|
||||
onlyimages, ok := form.Data["onlyimages"]
|
||||
onlyimages := form.Data["onlyimages"]
|
||||
if len(list.ToUniqueStringSlice(onlyimages)) != 0 {
|
||||
t.Fatalf("Expect onlyimages field to be deleted, got \n%v", onlyimages)
|
||||
}
|
||||
|
||||
+10
-10
@@ -20,16 +20,16 @@ type SettingsUpsert struct {
|
||||
//
|
||||
// NB! App is required struct member.
|
||||
type SettingsUpsertConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
TxLogsDao *daos.Dao
|
||||
App core.App
|
||||
Dao *daos.Dao
|
||||
LogsDao *daos.Dao
|
||||
}
|
||||
|
||||
// NewSettingsUpsert creates a new [SettingsUpsert] form with initializer
|
||||
// config created from the provided [core.App] instance.
|
||||
//
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewSettingsUpsertWithConfig] with explicitly set TxDao.
|
||||
// [NewSettingsUpsertWithConfig] with explicitly set Dao.
|
||||
func NewSettingsUpsert(app core.App) *SettingsUpsert {
|
||||
return NewSettingsUpsertWithConfig(SettingsUpsertConfig{
|
||||
App: app,
|
||||
@@ -45,12 +45,12 @@ func NewSettingsUpsertWithConfig(config SettingsUpsertConfig) *SettingsUpsert {
|
||||
panic("Missing required config.App instance.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
if form.config.TxLogsDao == nil {
|
||||
form.config.TxLogsDao = form.config.App.LogsDao()
|
||||
if form.config.LogsDao == nil {
|
||||
form.config.LogsDao = form.config.App.LogsDao()
|
||||
}
|
||||
|
||||
// load the application settings into the form
|
||||
@@ -78,7 +78,7 @@ func (form *SettingsUpsert) Submit(interceptors ...InterceptorFunc) error {
|
||||
encryptionKey := os.Getenv(form.config.App.EncryptionEnv())
|
||||
|
||||
return runInterceptors(func() error {
|
||||
saveErr := form.config.TxDao.SaveParam(
|
||||
saveErr := form.config.Dao.SaveParam(
|
||||
models.ParamAppSettings,
|
||||
form.Settings,
|
||||
encryptionKey,
|
||||
@@ -88,7 +88,7 @@ func (form *SettingsUpsert) Submit(interceptors ...InterceptorFunc) error {
|
||||
}
|
||||
|
||||
// explicitly trigger old logs deletion
|
||||
form.config.TxLogsDao.DeleteOldRequests(
|
||||
form.config.LogsDao.DeleteOldRequests(
|
||||
time.Now().AddDate(0, 0, -1*form.Settings.Logs.MaxDays),
|
||||
)
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ func (form *TestEmailSend) Validate() error {
|
||||
&form.Email,
|
||||
validation.Required,
|
||||
validation.Length(1, 255),
|
||||
is.Email,
|
||||
is.EmailFormat,
|
||||
),
|
||||
validation.Field(
|
||||
&form.Template,
|
||||
|
||||
@@ -20,8 +20,8 @@ type UserEmailChangeConfirm struct {
|
||||
//
|
||||
// NB! App is required struct member.
|
||||
type UserEmailChangeConfirmConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
App core.App
|
||||
Dao *daos.Dao
|
||||
}
|
||||
|
||||
// NewUserEmailChangeConfirm creates a new [UserEmailChangeConfirm]
|
||||
@@ -29,7 +29,7 @@ type UserEmailChangeConfirmConfig struct {
|
||||
//
|
||||
// This factory method is used primarily for convenience (and backward compatibility).
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewUserEmailChangeConfirmWithConfig] with explicitly set TxDao.
|
||||
// [NewUserEmailChangeConfirmWithConfig] with explicitly set Dao.
|
||||
func NewUserEmailChangeConfirm(app core.App) *UserEmailChangeConfirm {
|
||||
return NewUserEmailChangeConfirmWithConfig(UserEmailChangeConfirmConfig{
|
||||
App: app,
|
||||
@@ -45,8 +45,8 @@ func NewUserEmailChangeConfirmWithConfig(config UserEmailChangeConfirmConfig) *U
|
||||
panic("Missing required config.App instance.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
return form
|
||||
@@ -103,12 +103,12 @@ func (form *UserEmailChangeConfirm) parseToken(token string) (*models.User, stri
|
||||
}
|
||||
|
||||
// ensure that there aren't other users with the new email
|
||||
if !form.config.TxDao.IsUserEmailUnique(newEmail, "") {
|
||||
if !form.config.Dao.IsUserEmailUnique(newEmail, "") {
|
||||
return nil, "", validation.NewError("validation_existing_token_email", "The new email address is already registered: "+newEmail)
|
||||
}
|
||||
|
||||
// verify that the token is not expired and its signature is valid
|
||||
user, err := form.config.TxDao.FindUserByToken(
|
||||
user, err := form.config.Dao.FindUserByToken(
|
||||
token,
|
||||
form.config.App.Settings().UserEmailChangeToken.Secret,
|
||||
)
|
||||
@@ -135,7 +135,7 @@ func (form *UserEmailChangeConfirm) Submit() (*models.User, error) {
|
||||
user.Verified = true
|
||||
user.RefreshTokenKey() // invalidate old tokens
|
||||
|
||||
if err := form.config.TxDao.SaveUser(user); err != nil {
|
||||
if err := form.config.Dao.SaveUser(user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -21,15 +21,15 @@ type UserEmailChangeRequest struct {
|
||||
//
|
||||
// NB! App is required struct member.
|
||||
type UserEmailChangeRequestConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
App core.App
|
||||
Dao *daos.Dao
|
||||
}
|
||||
|
||||
// NewUserEmailChangeRequest creates a new [UserEmailChangeRequest]
|
||||
// form with initializer config created from the provided [core.App] instance.
|
||||
//
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewUserEmailChangeConfirmWithConfig] with explicitly set TxDao.
|
||||
// [NewUserEmailChangeConfirmWithConfig] with explicitly set Dao.
|
||||
func NewUserEmailChangeRequest(app core.App, user *models.User) *UserEmailChangeRequest {
|
||||
return NewUserEmailChangeRequestWithConfig(UserEmailChangeRequestConfig{
|
||||
App: app,
|
||||
@@ -48,8 +48,8 @@ func NewUserEmailChangeRequestWithConfig(config UserEmailChangeRequestConfig, us
|
||||
panic("Invalid initializer config or nil user model.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
return form
|
||||
@@ -62,7 +62,7 @@ func (form *UserEmailChangeRequest) Validate() error {
|
||||
&form.NewEmail,
|
||||
validation.Required,
|
||||
validation.Length(1, 255),
|
||||
is.Email,
|
||||
is.EmailFormat,
|
||||
validation.By(form.checkUniqueEmail),
|
||||
),
|
||||
)
|
||||
@@ -71,7 +71,7 @@ func (form *UserEmailChangeRequest) Validate() error {
|
||||
func (form *UserEmailChangeRequest) checkUniqueEmail(value any) error {
|
||||
v, _ := value.(string)
|
||||
|
||||
if !form.config.TxDao.IsUserEmailUnique(v, "") {
|
||||
if !form.config.Dao.IsUserEmailUnique(v, "") {
|
||||
return validation.NewError("validation_user_email_exists", "User email already exists.")
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ type UserEmailLogin struct {
|
||||
//
|
||||
// NB! App is required struct member.
|
||||
type UserEmailLoginConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
App core.App
|
||||
Dao *daos.Dao
|
||||
}
|
||||
|
||||
// NewUserEmailLogin creates a new [UserEmailLogin] form with
|
||||
@@ -29,7 +29,7 @@ type UserEmailLoginConfig struct {
|
||||
//
|
||||
// This factory method is used primarily for convenience (and backward compatibility).
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewUserEmailLoginWithConfig] with explicitly set TxDao.
|
||||
// [NewUserEmailLoginWithConfig] with explicitly set Dao.
|
||||
func NewUserEmailLogin(app core.App) *UserEmailLogin {
|
||||
return NewUserEmailLoginWithConfig(UserEmailLoginConfig{
|
||||
App: app,
|
||||
@@ -45,8 +45,8 @@ func NewUserEmailLoginWithConfig(config UserEmailLoginConfig) *UserEmailLogin {
|
||||
panic("Missing required config.App instance.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
return form
|
||||
@@ -67,7 +67,7 @@ func (form *UserEmailLogin) Submit() (*models.User, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := form.config.TxDao.FindUserByEmail(form.Email)
|
||||
user, err := form.config.Dao.FindUserByEmail(form.Email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+71
-44
@@ -35,15 +35,15 @@ type UserOauth2Login struct {
|
||||
//
|
||||
// NB! App is required struct member.
|
||||
type UserOauth2LoginConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
App core.App
|
||||
Dao *daos.Dao
|
||||
}
|
||||
|
||||
// NewUserOauth2Login creates a new [UserOauth2Login] form with
|
||||
// initializer config created from the provided [core.App] instance.
|
||||
//
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewUserOauth2LoginWithConfig] with explicitly set TxDao.
|
||||
// [NewUserOauth2LoginWithConfig] with explicitly set Dao.
|
||||
func NewUserOauth2Login(app core.App) *UserOauth2Login {
|
||||
return NewUserOauth2LoginWithConfig(UserOauth2LoginConfig{
|
||||
App: app,
|
||||
@@ -59,8 +59,8 @@ func NewUserOauth2LoginWithConfig(config UserOauth2LoginConfig) *UserOauth2Login
|
||||
panic("Missing required config.App instance.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
return form
|
||||
@@ -99,8 +99,11 @@ func (form *UserOauth2Login) Submit() (*models.User, *auth.AuthUser, error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// load provider configuration
|
||||
config := form.config.App.Settings().NamedAuthProviderConfigs()[form.Provider]
|
||||
config.SetupProvider(provider)
|
||||
if err := config.SetupProvider(provider); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
provider.SetRedirectUrl(form.RedirectUrl)
|
||||
|
||||
@@ -113,55 +116,79 @@ func (form *UserOauth2Login) Submit() (*models.User, *auth.AuthUser, error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// fetch auth user
|
||||
// fetch external auth user
|
||||
authData, err := provider.FetchAuthUser(token)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// login/register the auth user
|
||||
user, _ := form.config.TxDao.FindUserByEmail(authData.Email)
|
||||
if user != nil {
|
||||
// update the existing user's verified state
|
||||
if !user.Verified {
|
||||
var user *models.User
|
||||
|
||||
// check for existing relation with the external auth user
|
||||
rel, _ := form.config.Dao.FindExternalAuthByProvider(form.Provider, authData.Id)
|
||||
if rel != nil {
|
||||
user, err = form.config.Dao.FindUserById(rel.UserId)
|
||||
if err != nil {
|
||||
return nil, authData, err
|
||||
}
|
||||
} else if authData.Email != "" {
|
||||
// look for an existing user by the external user's email
|
||||
user, _ = form.config.Dao.FindUserByEmail(authData.Email)
|
||||
}
|
||||
|
||||
if user == nil && !config.AllowRegistrations {
|
||||
return nil, authData, errors.New("New users registration is not allowed for the authorized provider.")
|
||||
}
|
||||
|
||||
saveErr := form.config.Dao.RunInTransaction(func(txDao *daos.Dao) error {
|
||||
if user == nil {
|
||||
user = &models.User{}
|
||||
user.Verified = true
|
||||
if err := form.config.TxDao.SaveUser(user); err != nil {
|
||||
return nil, authData, err
|
||||
user.Email = authData.Email
|
||||
user.SetPassword(security.RandomString(30))
|
||||
|
||||
// create the new user
|
||||
if err := txDao.SaveUser(user); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// update the existing user empty email if the authData has one
|
||||
// (this in case previously the user was created with
|
||||
// an OAuth2 provider that didn't return an email address)
|
||||
if user.Email == "" && authData.Email != "" {
|
||||
user.Email = authData.Email
|
||||
if err := txDao.SaveUser(user); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// update the existing user verified state
|
||||
// (only if the user doesn't have an email or the user email match with the one in authData)
|
||||
if !user.Verified && (user.Email == "" || user.Email == authData.Email) {
|
||||
user.Verified = true
|
||||
if err := txDao.SaveUser(user); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return user, authData, nil
|
||||
}
|
||||
|
||||
if !config.AllowRegistrations {
|
||||
// registration of new users is not allowed via the Oauth2 provider
|
||||
return nil, authData, errors.New("Cannot find user with the authorized email.")
|
||||
}
|
||||
// create ExternalAuth relation if missing
|
||||
if rel == nil {
|
||||
rel = &models.ExternalAuth{
|
||||
UserId: user.Id,
|
||||
Provider: form.Provider,
|
||||
ProviderId: authData.Id,
|
||||
}
|
||||
if err := txDao.SaveExternalAuth(rel); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// create new user
|
||||
user = &models.User{Verified: true}
|
||||
upsertForm := NewUserUpsertWithConfig(UserUpsertConfig{
|
||||
App: form.config.App,
|
||||
TxDao: form.config.TxDao,
|
||||
}, user)
|
||||
upsertForm.Email = authData.Email
|
||||
upsertForm.Password = security.RandomString(30)
|
||||
upsertForm.PasswordConfirm = upsertForm.Password
|
||||
return nil
|
||||
})
|
||||
|
||||
event := &core.UserOauth2RegisterEvent{
|
||||
User: user,
|
||||
AuthData: authData,
|
||||
}
|
||||
|
||||
if err := form.config.App.OnUserBeforeOauth2Register().Trigger(event); err != nil {
|
||||
return nil, authData, err
|
||||
}
|
||||
|
||||
if err := upsertForm.Submit(); err != nil {
|
||||
return nil, authData, err
|
||||
}
|
||||
|
||||
if err := form.config.App.OnUserAfterOauth2Register().Trigger(event); err != nil {
|
||||
return nil, authData, err
|
||||
if saveErr != nil {
|
||||
return nil, authData, saveErr
|
||||
}
|
||||
|
||||
return user, authData, nil
|
||||
|
||||
@@ -22,15 +22,15 @@ type UserPasswordResetConfirm struct {
|
||||
//
|
||||
// NB! App is required struct member.
|
||||
type UserPasswordResetConfirmConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
App core.App
|
||||
Dao *daos.Dao
|
||||
}
|
||||
|
||||
// NewUserPasswordResetConfirm creates a new [UserPasswordResetConfirm]
|
||||
// form with initializer config created from the provided [core.App] instance.
|
||||
//
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewUserPasswordResetConfirmWithConfig] with explicitly set TxDao.
|
||||
// [NewUserPasswordResetConfirmWithConfig] with explicitly set Dao.
|
||||
func NewUserPasswordResetConfirm(app core.App) *UserPasswordResetConfirm {
|
||||
return NewUserPasswordResetConfirmWithConfig(UserPasswordResetConfirmConfig{
|
||||
App: app,
|
||||
@@ -46,8 +46,8 @@ func NewUserPasswordResetConfirmWithConfig(config UserPasswordResetConfirmConfig
|
||||
panic("Missing required config.App instance.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
return form
|
||||
@@ -70,7 +70,7 @@ func (form *UserPasswordResetConfirm) checkToken(value any) error {
|
||||
return nil // nothing to check
|
||||
}
|
||||
|
||||
user, err := form.config.TxDao.FindUserByToken(
|
||||
user, err := form.config.Dao.FindUserByToken(
|
||||
v,
|
||||
form.config.App.Settings().UserPasswordResetToken.Secret,
|
||||
)
|
||||
@@ -88,7 +88,7 @@ func (form *UserPasswordResetConfirm) Submit() (*models.User, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := form.config.TxDao.FindUserByToken(
|
||||
user, err := form.config.Dao.FindUserByToken(
|
||||
form.Token,
|
||||
form.config.App.Settings().UserPasswordResetToken.Secret,
|
||||
)
|
||||
@@ -100,7 +100,7 @@ func (form *UserPasswordResetConfirm) Submit() (*models.User, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := form.config.TxDao.SaveUser(user); err != nil {
|
||||
if err := form.config.Dao.SaveUser(user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ type UserPasswordResetRequest struct {
|
||||
// NB! App is required struct member.
|
||||
type UserPasswordResetRequestConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
Dao *daos.Dao
|
||||
ResendThreshold float64 // in seconds
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ type UserPasswordResetRequestConfig struct {
|
||||
// form with initializer config created from the provided [core.App] instance.
|
||||
//
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewUserPasswordResetRequestWithConfig] with explicitly set TxDao.
|
||||
// [NewUserPasswordResetRequestWithConfig] with explicitly set Dao.
|
||||
func NewUserPasswordResetRequest(app core.App) *UserPasswordResetRequest {
|
||||
return NewUserPasswordResetRequestWithConfig(UserPasswordResetRequestConfig{
|
||||
App: app,
|
||||
@@ -50,8 +50,8 @@ func NewUserPasswordResetRequestWithConfig(config UserPasswordResetRequestConfig
|
||||
panic("Missing required config.App instance.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
return form
|
||||
@@ -66,7 +66,7 @@ func (form *UserPasswordResetRequest) Validate() error {
|
||||
&form.Email,
|
||||
validation.Required,
|
||||
validation.Length(1, 255),
|
||||
is.Email,
|
||||
is.EmailFormat,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -78,7 +78,7 @@ func (form *UserPasswordResetRequest) Submit() error {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := form.config.TxDao.FindUserByEmail(form.Email)
|
||||
user, err := form.config.Dao.FindUserByEmail(form.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -96,5 +96,5 @@ func (form *UserPasswordResetRequest) Submit() error {
|
||||
// update last sent timestamp
|
||||
user.LastResetSentAt = types.NowDateTime()
|
||||
|
||||
return form.config.TxDao.SaveUser(user)
|
||||
return form.config.Dao.SaveUser(user)
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ type UserUpsert struct {
|
||||
//
|
||||
// NB! App is required struct member.
|
||||
type UserUpsertConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
App core.App
|
||||
Dao *daos.Dao
|
||||
}
|
||||
|
||||
// NewUserUpsert creates a new [UserUpsert] form with initializer
|
||||
@@ -37,7 +37,7 @@ type UserUpsertConfig struct {
|
||||
// (for create you could pass a pointer to an empty User - `&models.User{}`).
|
||||
//
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewUserEmailChangeConfirmWithConfig] with explicitly set TxDao.
|
||||
// [NewUserEmailChangeConfirmWithConfig] with explicitly set Dao.
|
||||
func NewUserUpsert(app core.App, user *models.User) *UserUpsert {
|
||||
return NewUserUpsertWithConfig(UserUpsertConfig{
|
||||
App: app,
|
||||
@@ -57,8 +57,8 @@ func NewUserUpsertWithConfig(config UserUpsertConfig, user *models.User) *UserUp
|
||||
panic("Invalid initializer config or nil upsert model.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
// load defaults
|
||||
@@ -83,7 +83,7 @@ func (form *UserUpsert) Validate() error {
|
||||
&form.Email,
|
||||
validation.Required,
|
||||
validation.Length(1, 255),
|
||||
is.Email,
|
||||
is.EmailFormat,
|
||||
validation.By(form.checkEmailDomain),
|
||||
validation.By(form.checkUniqueEmail),
|
||||
),
|
||||
@@ -103,7 +103,7 @@ func (form *UserUpsert) Validate() error {
|
||||
func (form *UserUpsert) checkUniqueEmail(value any) error {
|
||||
v, _ := value.(string)
|
||||
|
||||
if v == "" || form.config.TxDao.IsUserEmailUnique(v, form.user.Id) {
|
||||
if v == "" || form.config.Dao.IsUserEmailUnique(v, form.user.Id) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -160,6 +160,6 @@ func (form *UserUpsert) Submit(interceptors ...InterceptorFunc) error {
|
||||
form.user.Email = form.Email
|
||||
|
||||
return runInterceptors(func() error {
|
||||
return form.config.TxDao.SaveUser(form.user)
|
||||
return form.config.Dao.SaveUser(form.user)
|
||||
}, interceptors...)
|
||||
}
|
||||
|
||||
@@ -19,15 +19,15 @@ type UserVerificationConfirm struct {
|
||||
//
|
||||
// NB! App is required struct member.
|
||||
type UserVerificationConfirmConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
App core.App
|
||||
Dao *daos.Dao
|
||||
}
|
||||
|
||||
// NewUserVerificationConfirm creates a new [UserVerificationConfirm]
|
||||
// form with initializer config created from the provided [core.App] instance.
|
||||
//
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewUserVerificationConfirmWithConfig] with explicitly set TxDao.
|
||||
// [NewUserVerificationConfirmWithConfig] with explicitly set Dao.
|
||||
func NewUserVerificationConfirm(app core.App) *UserVerificationConfirm {
|
||||
return NewUserVerificationConfirmWithConfig(UserVerificationConfirmConfig{
|
||||
App: app,
|
||||
@@ -43,8 +43,8 @@ func NewUserVerificationConfirmWithConfig(config UserVerificationConfirmConfig)
|
||||
panic("Missing required config.App instance.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
return form
|
||||
@@ -63,7 +63,7 @@ func (form *UserVerificationConfirm) checkToken(value any) error {
|
||||
return nil // nothing to check
|
||||
}
|
||||
|
||||
user, err := form.config.TxDao.FindUserByToken(
|
||||
user, err := form.config.Dao.FindUserByToken(
|
||||
v,
|
||||
form.config.App.Settings().UserVerificationToken.Secret,
|
||||
)
|
||||
@@ -81,7 +81,7 @@ func (form *UserVerificationConfirm) Submit() (*models.User, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := form.config.TxDao.FindUserByToken(
|
||||
user, err := form.config.Dao.FindUserByToken(
|
||||
form.Token,
|
||||
form.config.App.Settings().UserVerificationToken.Secret,
|
||||
)
|
||||
@@ -95,7 +95,7 @@ func (form *UserVerificationConfirm) Submit() (*models.User, error) {
|
||||
|
||||
user.Verified = true
|
||||
|
||||
if err := form.config.TxDao.SaveUser(user); err != nil {
|
||||
if err := form.config.Dao.SaveUser(user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ type UserVerificationRequest struct {
|
||||
// NB! App is required struct member.
|
||||
type UserVerificationRequestConfig struct {
|
||||
App core.App
|
||||
TxDao *daos.Dao
|
||||
Dao *daos.Dao
|
||||
ResendThreshold float64 // in seconds
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ type UserVerificationRequestConfig struct {
|
||||
// form with initializer config created from the provided [core.App] instance.
|
||||
//
|
||||
// If you want to submit the form as part of another transaction, use
|
||||
// [NewUserVerificationRequestWithConfig] with explicitly set TxDao.
|
||||
// [NewUserVerificationRequestWithConfig] with explicitly set Dao.
|
||||
func NewUserVerificationRequest(app core.App) *UserVerificationRequest {
|
||||
return NewUserVerificationRequestWithConfig(UserVerificationRequestConfig{
|
||||
App: app,
|
||||
@@ -50,8 +50,8 @@ func NewUserVerificationRequestWithConfig(config UserVerificationRequestConfig)
|
||||
panic("Missing required config.App instance.")
|
||||
}
|
||||
|
||||
if form.config.TxDao == nil {
|
||||
form.config.TxDao = form.config.App.Dao()
|
||||
if form.config.Dao == nil {
|
||||
form.config.Dao = form.config.App.Dao()
|
||||
}
|
||||
|
||||
return form
|
||||
@@ -66,7 +66,7 @@ func (form *UserVerificationRequest) Validate() error {
|
||||
&form.Email,
|
||||
validation.Required,
|
||||
validation.Length(1, 255),
|
||||
is.Email,
|
||||
is.EmailFormat,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -78,7 +78,7 @@ func (form *UserVerificationRequest) Submit() error {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := form.config.TxDao.FindUserByEmail(form.Email)
|
||||
user, err := form.config.Dao.FindUserByEmail(form.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -100,5 +100,5 @@ func (form *UserVerificationRequest) Submit() error {
|
||||
// update last sent timestamp
|
||||
user.LastVerificationSentAt = types.NowDateTime()
|
||||
|
||||
return form.config.TxDao.SaveUser(user)
|
||||
return form.config.Dao.SaveUser(user)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ package validators
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
"github.com/pocketbase/pocketbase/tools/rest"
|
||||
)
|
||||
@@ -47,10 +47,10 @@ func UploadedFileMimeType(validTypes []string) validation.RuleFunc {
|
||||
return validation.NewError("validation_invalid_mime_type", "Unsupported file type.")
|
||||
}
|
||||
|
||||
filetype := http.DetectContentType(v.Bytes())
|
||||
filetype := mimetype.Detect(v.Bytes())
|
||||
|
||||
for _, t := range validTypes {
|
||||
if t == filetype {
|
||||
if filetype.Is(t) {
|
||||
return nil // valid
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ func (validator *RecordDataValidator) checkEmailValue(field *schema.SchemaField,
|
||||
return nil // nothing to check
|
||||
}
|
||||
|
||||
if is.Email.Validate(val) != nil {
|
||||
if is.EmailFormat.Validate(val) != nil {
|
||||
return validation.NewError("validation_invalid_email", "Must be a valid email")
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@ module github.com/pocketbase/pocketbase
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.5
|
||||
github.com/aws/aws-sdk-go v1.44.81
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6
|
||||
github.com/aws/aws-sdk-go v1.44.102
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/domodwyer/mailyak/v3 v3.3.4
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/gabriel-vasile/mimetype v1.4.1
|
||||
github.com/ganigeorgiev/fexpr v0.1.1
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
@@ -17,31 +18,33 @@ require (
|
||||
github.com/spf13/cast v1.5.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
gocloud.dev v0.26.0
|
||||
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8
|
||||
golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7
|
||||
modernc.org/sqlite v1.18.1
|
||||
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0
|
||||
golang.org/x/net v0.0.0-20220921155015-db77216a4ee9
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
|
||||
modernc.org/sqlite v1.19.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.13 // indirect
|
||||
github.com/aws/smithy-go v1.12.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.33 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.19 // indirect
|
||||
github.com/aws/smithy-go v1.13.3 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
@@ -58,27 +61,26 @@ require (
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 // indirect
|
||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
|
||||
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
|
||||
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
|
||||
golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
||||
google.golang.org/api v0.93.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/api v0.96.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220819174105-e9f053255caa // indirect
|
||||
google.golang.org/grpc v1.48.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006 // indirect
|
||||
google.golang.org/grpc v1.49.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.36.1 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.8 // indirect
|
||||
modernc.org/libc v1.16.19 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.39.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.9 // indirect
|
||||
modernc.org/libc v1.19.0 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.4.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.2 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -78,6 +78,8 @@ contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0Wk
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.5 h1:A8cYupsAZkjaUmhtTYv3sSqc7LO5mp1XDfqe5E/9wRQ=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.5/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
||||
github.com/Azure/azure-amqp-common-go/v3 v3.2.1/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI=
|
||||
github.com/Azure/azure-amqp-common-go/v3 v3.2.2/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI=
|
||||
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
|
||||
@@ -122,53 +124,85 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:W
|
||||
github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||
github.com/aws/aws-sdk-go v1.44.81 h1:C8oBZ+a+ka0qk3Q24MohQIFq0tkbO8IAu5tfpAMKVWE=
|
||||
github.com/aws/aws-sdk-go v1.44.81/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||
github.com/aws/aws-sdk-go v1.44.85 h1:JM2rkKY/GtTDCQXW0StkImbLn6n4Q/Dm2bj+u1rm7Kw=
|
||||
github.com/aws/aws-sdk-go v1.44.85/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||
github.com/aws/aws-sdk-go v1.44.102 h1:6tUCTGL2UDbFZae1TLGk8vTgeXuzkb8KbAe2FiAeKHc=
|
||||
github.com/aws/aws-sdk-go v1.44.102/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.11 h1:xM1ZPSvty3xVmdxiGr7ay/wlqv+MWhH0rMlyLdbC0YQ=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.11/go.mod h1:WTACcleLz6VZTp7fak4EO5b9Q4foxbn+8PIz3PmyKlo=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.16 h1:M1fj4FE2lB4NzRb9Y0xdWsn2P0+2UHVxwKyOa4YJNjk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.16/go.mod h1:SwiyXi/1zTUZ6KIAmLK5V5ll8SiURNUYOqTerZPaF9k=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.4 h1:zfT11pa7ifu/VlLDpmc5OY2W4nYmnKkFDGeMVnmqAI0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.4/go.mod h1:ES0I1GBs+YYgcDS1ek47Erbn4TOL811JKqBXtgzqyZ8=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 h1:tcFliCWne+zOuUfKNRn8JdFBuWPDuISDH08wD2ULkhk=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8/go.mod h1:JTnlBSot91steJeti4ryyu/tLd4Sk84O5W22L7O2EQU=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.15.3/go.mod h1:9YL3v07Xc/ohTsxFXzan9ZpFpdTOFl4X65BAKYaz8jg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.1 h1:BWxTjokU/69BZ4DnLrZco6OvBDii6ToEdfBL/y5I1nA=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.1/go.mod h1:uOxDHjBemNTF2Zos+fgG0NNfE86wn1OAHDTGxjMEYi0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.7 h1:odVM52tFHhpqZBKNjVW5h+Zt1tKHbhdTQRb+0WHrNtw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.7/go.mod h1:dN2gja/QXxFF15hQreyrqYhLBaQo1d9ZKe/v/uplQoI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.11.2/go.mod h1:j8YsY9TXTm31k4eFhspiQicfXPLZ0gYXA50i4gxPE8g=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.14 h1:AtVG/amkjbDBfnPr/tuW2IG18HGNznP6L12Dx0rLz+Q=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.14/go.mod h1:opAndTyq+YN7IpVG57z2CeNuXSQMqTYxGGlYH0m0RMY=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.20 h1:9+ZhlDY7N9dPnUmf7CDfW9In4sW5Ff3bh7oy4DzS1IE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.20/go.mod h1:UKY5HyIux08bbNA7Blv4PcXQ8cTkGh7ghHMFklaviR4=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3/go.mod h1:uk1vhHHERfSVCUnqSqz8O48LBYDSC+k6brng09jcMOk=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.12 h1:wgJBHO58Pc1V1QAnzdVM3JK3WbE/6eUF0JxCZ+/izz0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.12/go.mod h1:aZ4vZnyUuxedC7eD4JyEHpGnCz+O2sHQEx3VvAwklSE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17 h1:r08j4sbZu/RVi+BNxkBJwPMUYY3P8mgSDuKkZ/ZN1lE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17/go.mod h1:yIkQcCDYNsZfXpd5UX2Cy+sWA1jPgIhGTw9cOBzfVnQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3/go.mod h1:0dHuD2HZZSiwfJSy1FO5bX1hQ1TxVV1QXXjpn3XUE44=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.27 h1:xFXIMBci0UXStoOHq/8w0XIZPB2hgb9CD7uATJhqt10=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.27/go.mod h1:+tj2cHQkChanggNZn1J2fJ1Cv6RO1TV0AA3472do31I=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.33 h1:fAoVmNGhir6BR+RU0/EI+6+D7abM+MCwWf8v4ip5jNI=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.33/go.mod h1:84XgODVR8uRhmOnUkKGUZKqIMxmjmLOR8Uyp7G/TPwc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9/go.mod h1:AnVH5pvai0pAF4lXRq0bmhbes1u9R8wTE+g+183bZNM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.18 h1:OmiwoVyLKEqqD5GvB683dbSqxiOfvx4U2lDZhG2Esc4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.18/go.mod h1:348MLhzV1GSlZSMusdwQpXKbhD7X2gbI/TxwAPKkYZQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23 h1:s4g/wnzMf+qepSNgTvaQQHNxyMLKSawNhKCPNy++2xY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23/go.mod h1:2DFxAQ9pfIRy0imBCJv+vZ2X6RKxves6fbnEuSry6b4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.12 h1:5mvQDtNWtI6H56+E4LUnLWEmATMB7oEh+Z9RurtIuC0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.12/go.mod h1:ckaCVTEdGAxO6KwTGzgskxR1xM+iJW4lxMyDFVda2Fc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17 h1:/K482T5A3623WJgWT8w1yRAFK4RzGzEl7y39yhtn9eA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17/go.mod h1:pRwaTYCJemADaqCbUAxltMoHKata7hmB5PjEXeu0kfg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10/go.mod h1:8DcYQcz0+ZJaSxANlHIsbbi6S+zMwjwdDqwW3r9AzaE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.19 h1:g5qq9sgtEzt2szMaDqQO6fqKe026T6dHTFJp5NsPzkQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.19/go.mod h1:cVHo8KTuHjShb9V8/VjH3S/8+xPu16qx8fdGwmotJhE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24 h1:wj5Rwc05hvUSvKuOF29IYb9QrCLjU+rHAy/x/o0DK2c=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24/go.mod h1:jULHjqqjDlbyTa7pfM7WICATnOv+iOhjletM3N0Xbu8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.9 h1:agLpf3vtYX1rtKTrOGpevdP3iC2W0hKDmzmhhxJzL+A=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.9/go.mod h1:cv+n1mdyh+0B8tAtlEBzTYFA2Uv15SISEn6kabYhIgE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14 h1:ZSIPAkAsCCjYrhqfw2+lNzWDzxzHXEckFkTePL5RSWQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14/go.mod h1:AyGgqiKv9ECM6IZeNQtdT8NnMvUb3/2wokeq2Fgryto=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.5 h1:g1ITJ9i9ixa+/WVggLNK20KyliAA8ltnuxfZEDfo2hM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.5/go.mod h1:oehQLbMQkppKLXvpx/1Eo0X47Fe+0971DXC9UjGnKcI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 h1:Lh1AShsuIJTwMkoxVCAYPJgNG5H+eN6SmoUn8nOZ5wE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9/go.mod h1:a9j48l6yL5XINLHLcOKInjdvknN+vWqPBxqeIDw7ktw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3/go.mod h1:Seb8KNmD6kVTjwRjVEgOT5hPin6sq+v4C2ycJQDwuH8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.13 h1:3GamN8jcdz/a3nvL/ZVtoH/6xxeshfsiXj5O+6GW4Rg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.13/go.mod h1:89CSPn69UECDLVn0H6FwKNgbtirksl8C8i3aBeeeihw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18 h1:BBYoNQt2kUZUUK4bIPsKrCcjVPUMNsgQpNAwhznK/zo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18/go.mod h1:NS55eQ4YixUJPTC+INxi2/jCqe1y2Uw3rnh9wEOVJxY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3/go.mod h1:wlY6SVjuwvh3TVRpTqdy4I1JpBFLX4UGeKZdWntaocw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.12 h1:7iPTTX4SAI2U2VOogD7/gmHlsgnYSgoNHt7MSQXtG2M=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.12/go.mod h1:1TODGhheLWjpQWSuhYuAUWYTCKwEjx2iblIFKDHjeTc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17 h1:Jrd/oMh0PKQc6+BowB+pLEwLIgaQF29eYbe7E1Av9Ug=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17/go.mod h1:4nYOrY41Lrbk2170/BGkcJKBhws9Pfn8MG3aGqjjeFI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3/go.mod h1:Bm/v2IaN6rZ+Op7zX+bOUMdL4fsrYZiD0dsjLhNKwZc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.12 h1:QFjSOmHSb77qRTv7KI9UFon9X5wLWY5/M+6la3dTcZc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.12/go.mod h1:MADjAN0GHFDuc5lRa5Y5ki+oIO/w7X4qczHy+OUx0IA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17 h1:HfVVR1vItaG6le+Bpw6P4midjBDMKnjMyZnw9MXYUcE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17/go.mod h1:YqMdV+gEKCQ59NrB7rzrJdALeBIsYiVi8Inj3+KcqHI=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.16.3/go.mod h1:QuiHPBqlOFCi4LqdSskYYAWpQlx3PKmohy+rE2F+o5g=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z4KJ7DtFFCx8fJD2a491Ak=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.5 h1:h9qqTedYnA9JcWjKyLV6UYIMSdp91ExLCUbjbpDLH7A=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.5/go.mod h1:J8SS5Tp/zeLxaubB0xGfKnVrvssNBNLwTipreTKLhjQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11 h1:3/gm/JTX9bX8CpzTgIlrtYpB3EVBDxyg/GY/QdcIEZw=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11/go.mod h1:fmgDANqTUCxciViKl9hb/zD5LFbvPINFRgWhDbR+vZo=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4/go.mod h1:PJc8s+lxyU8rrre0/4a0pn2wgwiDvOEzoOjcJUBr67o=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.17.4/go.mod h1:kElt+uCcXxcqFyc+bQqZPFD9DME/eC6oHBXvFzQ9Bcw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.18.3/go.mod h1:skmQo0UPvsjsuYYSYMVmrPc1HWCbHUJyrCEp+ZaLzqM=
|
||||
@@ -176,12 +210,20 @@ github.com/aws/aws-sdk-go-v2/service/ssm v1.24.1/go.mod h1:NR/xoKjdbRJ+qx0pMR4mI
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.3/go.mod h1:7UQ/e69kU7LDPtY40OyoHYgRmgfGM4mgsLYtcObdveU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.17 h1:pXxu9u2z1UqSbjO9YA8kmFJBhFc1EVTDaf7A+S+Ivq8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.17/go.mod h1:mS5xqLZc/6kc06IpXn5vRxdLaED+jEuaSRv5BxtnsiY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.23 h1:pwvCchFUEnlceKIgPUouBJwK81aCkQ8UDMORfeFtW10=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.23/go.mod h1:/w0eg9IhFGjGyyncHIQrXtU8wvNsTJOP0R6PPj0wf80=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.5 h1:GUnZ62TevLqIoDyHeiWj2P7EqaosgakBKVvWriIdLQY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.5/go.mod h1:csZuQY65DAdFBt1oIjO5hhBR49kQqop4+lcuCjf2arA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.3/go.mod h1:bfBj0iVmsUyUg4weDB4NxktD9rDGeKSVWnjTnwbx9b8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.13 h1:dl8T0PJlN92rvEGOEUiD0+YPYdPEaCZK0TqHukvSfII=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.13/go.mod h1:Ru3QVMLygVs/07UQ3YDur1AQZZp2tUNje8wfloFttC0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.19 h1:9pPi0PsFNAGILFfPCk8Y0iyEBGc6lu6OQ97U7hmdesg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.19/go.mod h1:h4J3oPZQbxLhzGnk+j9dfYHi5qIOVJ5kczZd658/ydM=
|
||||
github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
|
||||
github.com/aws/smithy-go v1.12.1 h1:yQRC55aXN/y1W10HgwHle01DRuV9Dpf31iGkotjt3Ag=
|
||||
github.com/aws/smithy-go v1.12.1/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/aws/smithy-go v1.13.3 h1:l7LYxGuzK6/K+NzJ2mC+VvLUbae0sL3bXU//04MkmnA=
|
||||
github.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -238,6 +280,8 @@ github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q=
|
||||
github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=
|
||||
github.com/ganigeorgiev/fexpr v0.1.1 h1:La9kYEgTcIutvOnqNZ8pOUD0O0Q/Gn15sTVEX+IeBE8=
|
||||
github.com/ganigeorgiev/fexpr v0.1.1/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
@@ -577,8 +621,10 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c=
|
||||
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503 h1:vJ2V3lFLg+bBhgroYuRfyN583UzVveQmIXjc8T/y3to=
|
||||
golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 h1:a5Yg6ylndHHYJqIPrdq0AhvR6KTvDTAvgBtaidhEevY=
|
||||
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -594,6 +640,8 @@ golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+o
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 h1:/eM0PCrQI2xd471rI+snWuu251/+/jpBpZqir2mPdnU=
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY=
|
||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -668,8 +716,11 @@ golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c h1:JVAXQ10yGGVbSyoer5VILysz6YKjdNT2bsvlayjqhes=
|
||||
golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20220921155015-db77216a4ee9 h1:SdDGdqRuKrF2R4XGcnPzcvZ63c/55GvhoHUus0o+BNI=
|
||||
golang.org/x/net v0.0.0-20220921155015-db77216a4ee9/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -692,9 +743,10 @@ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
|
||||
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
|
||||
golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7 h1:dtndE8FcEta75/4kHF3AbpuWzV6f1LjnLrM4pe2SZrw=
|
||||
golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8=
|
||||
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA=
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -783,15 +835,20 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U=
|
||||
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w=
|
||||
golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -809,6 +866,8 @@ golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxb
|
||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45 h1:yuLAip3bfURHClMG9VBdzPrQvCWjWiWUTBGV+/fCbUs=
|
||||
golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -879,6 +938,8 @@ golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0=
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
@@ -925,8 +986,10 @@ google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69
|
||||
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
|
||||
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
|
||||
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
|
||||
google.golang.org/api v0.93.0 h1:T2xt9gi0gHdxdnRkVQhT8mIvPaXKNsDNWz+L696M66M=
|
||||
google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
|
||||
google.golang.org/api v0.94.0 h1:KtKM9ru3nzQioV1HLlUf1cR7vMYJIpgls5VhAYQXIwA=
|
||||
google.golang.org/api v0.94.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI=
|
||||
google.golang.org/api v0.96.0 h1:F60cuQPJq7K7FzsxMYHAUJSiXh2oKctHxBMbDygxhfM=
|
||||
google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
|
||||
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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -1029,8 +1092,10 @@ google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP
|
||||
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220819174105-e9f053255caa h1:Ux9yJCyf598uEniFPSyp8g1jtGTt77m+lzYyVgrWQaQ=
|
||||
google.golang.org/genproto v0.0.0-20220819174105-e9f053255caa/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
|
||||
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc h1:Nf+EdcTLHR8qDNN/KfkQL0u0ssxt9OhbaWCl5C0ucEI=
|
||||
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
|
||||
google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006 h1:mmbq5q8M1t7dhkLw320YK4PsOXm6jdnUAkErImaIqOg=
|
||||
google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -1062,8 +1127,8 @@ google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11
|
||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w=
|
||||
google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw=
|
||||
google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
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=
|
||||
@@ -1104,40 +1169,46 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
||||
modernc.org/cc/v3 v3.36.1 h1:CICrjwr/1M4+6OQ4HJZ/AHxjcwe67r5vPUF518MkO8A=
|
||||
modernc.org/cc/v3 v3.36.1/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
||||
modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
|
||||
modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
|
||||
modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
|
||||
modernc.org/ccgo/v3 v3.16.8 h1:G0QNlTqI5uVgczBWfGKs7B++EPwCfXPWGD2MdeKloDs=
|
||||
modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws=
|
||||
modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
||||
modernc.org/cc/v3 v3.36.3 h1:uISP3F66UlixxWEcKuIWERa4TwrZENHSL8tWxZz8bHg=
|
||||
modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
||||
modernc.org/cc/v3 v3.39.0 h1:sd+UyMj63acEV1jaFqxFGPQfllSncJgL+roJjFlo6lI=
|
||||
modernc.org/cc/v3 v3.39.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=
|
||||
modernc.org/ccgo/v3 v3.16.9 h1:AXquSwg7GuMk11pIdw7fmO1Y/ybgazVkMhsZWCV0mHM=
|
||||
modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=
|
||||
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 v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
|
||||
modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=
|
||||
modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU=
|
||||
modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
|
||||
modernc.org/libc v1.16.19 h1:S8flPn5ZeXx6iw/8yNa986hwTQDrY8RXU7tObZuAozo=
|
||||
modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
|
||||
modernc.org/libc v1.17.0 h1:nbL2Lv0I323wLc1GmTh/AqVtI9JeBVc7Nhapdg9EONs=
|
||||
modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=
|
||||
modernc.org/libc v1.19.0 h1:bXyVhGQg6KIClTr8FMVIDPl7jtbcs7aS5WP7vLDaxPs=
|
||||
modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=
|
||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
|
||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.1.1 h1:bDOL0DIDLQv7bWhP3gMvIrnoFw+Eo6F7a2QK9HPDiFU=
|
||||
modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.2.0 h1:zXehBrt9n+Pjn+4RoRCZ0KqRA/0ePFqcecxZ/hXCIVw=
|
||||
modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
|
||||
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
|
||||
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.18.1 h1:ko32eKt3jf7eqIkCgPAeHMBXw3riNSLhl2f3loEF7o8=
|
||||
modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=
|
||||
modernc.org/sqlite v1.19.1 h1:8xmS5oLnZtAK//vnd4aTVj8VOeTAccEFOtUnIzfSw+4=
|
||||
modernc.org/sqlite v1.19.1/go.mod h1:UfQ83woKMaPW/ZBruK0T7YaFCrI+IE0LeWVY6pmnVms=
|
||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||
modernc.org/strutil v1.1.2 h1:iFBDH6j1Z0bN/Q9udJnnFoFpENA4252qe/7/5woE5MI=
|
||||
modernc.org/strutil v1.1.2/go.mod h1:OYajnUAcI/MX+XD/Wx7v1bbdvcQSvxgtb0gC+u3d3eg=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.13.1 h1:npxzTwFTZYM8ghWicVIX1cRWzj7Nd8i6AqqX2p+IYao=
|
||||
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package migrations
|
||||
|
||||
import "github.com/pocketbase/dbx"
|
||||
|
||||
func init() {
|
||||
AppMigrations.Register(func(db dbx.Builder) error {
|
||||
_, createErr := db.NewQuery(`
|
||||
CREATE TABLE {{_externalAuths}} (
|
||||
[[id]] TEXT PRIMARY KEY,
|
||||
[[userId]] TEXT NOT NULL,
|
||||
[[provider]] TEXT NOT NULL,
|
||||
[[providerId]] TEXT NOT NULL,
|
||||
[[created]] TEXT DEFAULT "" NOT NULL,
|
||||
[[updated]] TEXT DEFAULT "" NOT NULL,
|
||||
---
|
||||
FOREIGN KEY ([[userId]]) REFERENCES {{_users}} ([[id]]) ON UPDATE CASCADE ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX _externalAuths_userId_provider_idx on {{_externalAuths}} ([[userId]], [[provider]]);
|
||||
CREATE UNIQUE INDEX _externalAuths_provider_providerId_idx on {{_externalAuths}} ([[provider]], [[providerId]]);
|
||||
`).Execute()
|
||||
if createErr != nil {
|
||||
return createErr
|
||||
}
|
||||
|
||||
// remove the unique email index from the _users table and
|
||||
// replace it with partial index
|
||||
_, alterErr := db.NewQuery(`
|
||||
-- crate new users table
|
||||
CREATE TABLE {{_newUsers}} (
|
||||
[[id]] TEXT PRIMARY KEY,
|
||||
[[verified]] BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
[[email]] TEXT DEFAULT "" NOT NULL,
|
||||
[[tokenKey]] TEXT NOT NULL,
|
||||
[[passwordHash]] TEXT NOT NULL,
|
||||
[[lastResetSentAt]] TEXT DEFAULT "" NOT NULL,
|
||||
[[lastVerificationSentAt]] TEXT DEFAULT "" NOT NULL,
|
||||
[[created]] TEXT DEFAULT "" NOT NULL,
|
||||
[[updated]] TEXT DEFAULT "" NOT NULL
|
||||
);
|
||||
|
||||
-- copy all data from the old users table to the new one
|
||||
INSERT INTO {{_newUsers}} SELECT * FROM {{_users}};
|
||||
|
||||
-- drop old table
|
||||
DROP TABLE {{_users}};
|
||||
|
||||
-- rename new table
|
||||
ALTER TABLE {{_newUsers}} RENAME TO {{_users}};
|
||||
|
||||
-- create named indexes
|
||||
CREATE UNIQUE INDEX _users_email_idx ON {{_users}} ([[email]]) WHERE [[email]] != "";
|
||||
CREATE UNIQUE INDEX _users_tokenKey_idx ON {{_users}} ([[tokenKey]]);
|
||||
`).Execute()
|
||||
if alterErr != nil {
|
||||
return alterErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}, func(db dbx.Builder) error {
|
||||
if _, err := db.DropTable("_externalAuths").Execute(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// drop the partial email unique index and replace it with normal unique index
|
||||
_, indexErr := db.NewQuery(`
|
||||
DROP INDEX IF EXISTS _users_email_idx;
|
||||
CREATE UNIQUE INDEX _users_email_idx on {{_users}} ([[email]]);
|
||||
`).Execute()
|
||||
if indexErr != nil {
|
||||
return indexErr
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
var _ Model = (*ExternalAuth)(nil)
|
||||
|
||||
type ExternalAuth struct {
|
||||
BaseModel
|
||||
|
||||
UserId string `db:"userId" json:"userId"`
|
||||
Provider string `db:"provider" json:"provider"`
|
||||
ProviderId string `db:"providerId" json:"providerId"`
|
||||
}
|
||||
|
||||
func (m *ExternalAuth) TableName() string {
|
||||
return "_externalAuths"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
func TestExternalAuthTableName(t *testing.T) {
|
||||
m := models.ExternalAuth{}
|
||||
if m.TableName() != "_externalAuths" {
|
||||
t.Fatalf("Unexpected table name, got %q", m.TableName())
|
||||
}
|
||||
}
|
||||
+76
-71
@@ -26,50 +26,60 @@ type appWrapper struct {
|
||||
|
||||
// PocketBase defines a PocketBase app launcher.
|
||||
//
|
||||
// It implements [core.App] via embedding and all of interface methods
|
||||
// It implements [core.App] via embedding and all of the app interface methods
|
||||
// could be accessed directly through the instance (eg. PocketBase.DataDir()).
|
||||
type PocketBase struct {
|
||||
*appWrapper
|
||||
|
||||
// RootCmd is the main cli command
|
||||
debugFlag bool
|
||||
dataDirFlag string
|
||||
encryptionEnvFlag string
|
||||
hideStartBanner bool
|
||||
|
||||
// RootCmd is the main console command
|
||||
RootCmd *cobra.Command
|
||||
|
||||
// console flags
|
||||
debugFlag bool
|
||||
dataDirFlag string
|
||||
encryptionEnv string
|
||||
|
||||
// console flag fallback values
|
||||
defaultDebug bool
|
||||
defaultDataDir string
|
||||
defaultEncryptionEnv string
|
||||
|
||||
// serve start banner
|
||||
showStartBanner bool
|
||||
}
|
||||
|
||||
// New creates a new PocketBase instance.
|
||||
// Config is the PocketBase initialization config struct.
|
||||
type Config struct {
|
||||
// optional default values for the console flags
|
||||
DefaultDebug bool
|
||||
DefaultDataDir string // if not set, it will fallback to "./pb_data"
|
||||
DefaultEncryptionEnv string
|
||||
|
||||
// hide the default console server info on app startup
|
||||
HideStartBanner bool
|
||||
}
|
||||
|
||||
// New creates a new PocketBase instance with the default configuration.
|
||||
// Use [NewWithConfig()] if you want to provide a custom configuration.
|
||||
//
|
||||
// Note that the application will not be initialized/bootstrapped yet,
|
||||
// aka. DB connections, migrations, app settings, etc. will not be accessible.
|
||||
// Everything will be initialized when Start() is executed.
|
||||
// If you want to initialize the application before calling Start(),
|
||||
// then you'll have to manually call Bootstrap().
|
||||
// Everything will be initialized when [Start()] is executed.
|
||||
// If you want to initialize the application before calling [Start()],
|
||||
// then you'll have to manually call [Bootstrap()].
|
||||
func New() *PocketBase {
|
||||
// try to find the base executable directory and how it was run
|
||||
var withGoRun bool
|
||||
var baseDir string
|
||||
if strings.HasPrefix(os.Args[0], os.TempDir()) {
|
||||
// probably ran with go run...
|
||||
withGoRun = true
|
||||
baseDir, _ = os.Getwd()
|
||||
} else {
|
||||
// probably ran with go build...
|
||||
withGoRun = false
|
||||
baseDir = filepath.Dir(os.Args[0])
|
||||
}
|
||||
_, isUsingGoRun := inspectRuntime()
|
||||
|
||||
defaultDir := filepath.Join(baseDir, "pb_data")
|
||||
return NewWithConfig(Config{
|
||||
DefaultDebug: isUsingGoRun,
|
||||
})
|
||||
}
|
||||
|
||||
// NewWithConfig creates a new PocketBase instance with the provided config.
|
||||
//
|
||||
// Note that the application will not be initialized/bootstrapped yet,
|
||||
// aka. DB connections, migrations, app settings, etc. will not be accessible.
|
||||
// Everything will be initialized when [Start()] is executed.
|
||||
// If you want to initialize the application before calling [Start()],
|
||||
// then you'll have to manually call [Bootstrap()].
|
||||
func NewWithConfig(config Config) *PocketBase {
|
||||
// initialize a default data directory based on the executable baseDir
|
||||
if config.DefaultDataDir == "" {
|
||||
baseDir, _ := inspectRuntime()
|
||||
config.DefaultDataDir = filepath.Join(baseDir, "pb_data")
|
||||
}
|
||||
|
||||
pb := &PocketBase{
|
||||
RootCmd: &cobra.Command{
|
||||
@@ -84,46 +94,31 @@ func New() *PocketBase {
|
||||
DisableDefaultCmd: true,
|
||||
},
|
||||
},
|
||||
defaultDebug: withGoRun,
|
||||
defaultDataDir: defaultDir,
|
||||
defaultEncryptionEnv: "",
|
||||
showStartBanner: true,
|
||||
debugFlag: config.DefaultDebug,
|
||||
dataDirFlag: config.DefaultDataDir,
|
||||
encryptionEnvFlag: config.DefaultEncryptionEnv,
|
||||
hideStartBanner: config.HideStartBanner,
|
||||
}
|
||||
|
||||
// parse base flags
|
||||
// (errors are ignored, since the full flags parsing happens on Execute())
|
||||
pb.eagerParseFlags()
|
||||
pb.eagerParseFlags(config)
|
||||
|
||||
// initialize the app instance
|
||||
pb.appWrapper = &appWrapper{core.NewBaseApp(
|
||||
pb.dataDirFlag,
|
||||
pb.encryptionEnv,
|
||||
pb.encryptionEnvFlag,
|
||||
pb.debugFlag,
|
||||
)}
|
||||
|
||||
return pb
|
||||
}
|
||||
// hide the default help command (allow only `--help` flag)
|
||||
pb.RootCmd.SetHelpCommand(&cobra.Command{Hidden: true})
|
||||
|
||||
// DefaultDebug sets the default --debug flag value.
|
||||
func (pb *PocketBase) DefaultDebug(val bool) *PocketBase {
|
||||
pb.defaultDebug = val
|
||||
return pb
|
||||
}
|
||||
// hook the bootstrap process
|
||||
pb.RootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
return pb.Bootstrap()
|
||||
}
|
||||
|
||||
// DefaultDataDir sets the default --dir flag value.
|
||||
func (pb *PocketBase) DefaultDataDir(val string) *PocketBase {
|
||||
pb.defaultDataDir = val
|
||||
return pb
|
||||
}
|
||||
|
||||
// DefaultEncryptionEnv sets the default --encryptionEnv flag value.
|
||||
func (pb *PocketBase) DefaultEncryptionEnv(val string) *PocketBase {
|
||||
pb.defaultEncryptionEnv = val
|
||||
return pb
|
||||
}
|
||||
|
||||
// ShowStartBanner shows/hides the web server start banner.
|
||||
func (pb *PocketBase) ShowStartBanner(val bool) *PocketBase {
|
||||
pb.showStartBanner = val
|
||||
return pb
|
||||
}
|
||||
|
||||
@@ -131,7 +126,7 @@ func (pb *PocketBase) ShowStartBanner(val bool) *PocketBase {
|
||||
// commands (serve, migrate, version) and executes pb.RootCmd.
|
||||
func (pb *PocketBase) Start() error {
|
||||
// register system commands
|
||||
pb.RootCmd.AddCommand(cmd.NewServeCommand(pb, pb.showStartBanner))
|
||||
pb.RootCmd.AddCommand(cmd.NewServeCommand(pb, !pb.hideStartBanner))
|
||||
pb.RootCmd.AddCommand(cmd.NewMigrateCommand(pb))
|
||||
|
||||
return pb.Execute()
|
||||
@@ -143,10 +138,6 @@ func (pb *PocketBase) Start() error {
|
||||
// This method differs from pb.Start() by not registering the default
|
||||
// system commands!
|
||||
func (pb *PocketBase) Execute() error {
|
||||
if err := pb.Bootstrap(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
@@ -180,27 +171,41 @@ func (pb *PocketBase) onTerminate() error {
|
||||
|
||||
// eagerParseFlags parses the global app flags before calling pb.RootCmd.Execute().
|
||||
// so we can have all PocketBase flags ready for use on initialization.
|
||||
func (pb *PocketBase) eagerParseFlags() error {
|
||||
func (pb *PocketBase) eagerParseFlags(config Config) error {
|
||||
pb.RootCmd.PersistentFlags().StringVar(
|
||||
&pb.dataDirFlag,
|
||||
"dir",
|
||||
pb.defaultDataDir,
|
||||
config.DefaultDataDir,
|
||||
"the PocketBase data directory",
|
||||
)
|
||||
|
||||
pb.RootCmd.PersistentFlags().StringVar(
|
||||
&pb.encryptionEnv,
|
||||
&pb.encryptionEnvFlag,
|
||||
"encryptionEnv",
|
||||
pb.defaultEncryptionEnv,
|
||||
"the env variable whose value of 32 chars will be used \nas encryption key for the app settings (default none)",
|
||||
config.DefaultEncryptionEnv,
|
||||
"the env variable whose value of 32 characters will be used \nas encryption key for the app settings (default none)",
|
||||
)
|
||||
|
||||
pb.RootCmd.PersistentFlags().BoolVar(
|
||||
&pb.debugFlag,
|
||||
"debug",
|
||||
pb.defaultDebug,
|
||||
config.DefaultDebug,
|
||||
"enable debug mode, aka. showing more detailed logs",
|
||||
)
|
||||
|
||||
return pb.RootCmd.ParseFlags(os.Args[1:])
|
||||
}
|
||||
|
||||
// tries to find the base executable directory and how it was run
|
||||
func inspectRuntime() (baseDir string, withGoRun bool) {
|
||||
if strings.HasPrefix(os.Args[0], os.TempDir()) {
|
||||
// probably ran with go run
|
||||
withGoRun = true
|
||||
baseDir, _ = os.Getwd()
|
||||
} else {
|
||||
// probably ran with go build
|
||||
withGoRun = false
|
||||
baseDir = filepath.Dir(os.Args[0])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
+82
-36
@@ -6,14 +6,19 @@ import (
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
testDir := "./pb_test_data_dir"
|
||||
defer os.RemoveAll(testDir)
|
||||
// copy os.Args
|
||||
originalArgs := []string{}
|
||||
copy(originalArgs, os.Args)
|
||||
defer func() {
|
||||
// restore os.Args
|
||||
copy(os.Args, originalArgs)
|
||||
}()
|
||||
|
||||
// reset os.Args
|
||||
// change os.Args
|
||||
os.Args = os.Args[0:1]
|
||||
os.Args = append(
|
||||
os.Args,
|
||||
"--dir="+testDir,
|
||||
"--dir=test_dir",
|
||||
"--encryptionEnv=test_encryption_env",
|
||||
"--debug=true",
|
||||
)
|
||||
@@ -32,8 +37,8 @@ func TestNew(t *testing.T) {
|
||||
t.Fatal("Expected appWrapper to be initialized, got nil")
|
||||
}
|
||||
|
||||
if app.DataDir() != testDir {
|
||||
t.Fatalf("Expected app.DataDir() %q, got %q", testDir, app.DataDir())
|
||||
if app.DataDir() != "test_dir" {
|
||||
t.Fatalf("Expected app.DataDir() %q, got %q", "test_dir", app.DataDir())
|
||||
}
|
||||
|
||||
if app.EncryptionEnv() != "test_encryption_env" {
|
||||
@@ -45,52 +50,93 @@ func TestNew(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultDebug(t *testing.T) {
|
||||
app := New()
|
||||
func TestNewWithConfig(t *testing.T) {
|
||||
app := NewWithConfig(Config{
|
||||
DefaultDebug: true,
|
||||
DefaultDataDir: "test_dir",
|
||||
DefaultEncryptionEnv: "test_encryption_env",
|
||||
HideStartBanner: true,
|
||||
})
|
||||
|
||||
app.DefaultDebug(true)
|
||||
if app.defaultDebug != true {
|
||||
t.Fatalf("Expected defaultDebug %v, got %v", true, app.defaultDebug)
|
||||
if app == nil {
|
||||
t.Fatal("Expected initialized PocketBase instance, got nil")
|
||||
}
|
||||
|
||||
app.DefaultDebug(false)
|
||||
if app.defaultDebug != false {
|
||||
t.Fatalf("Expected defaultDebug %v, got %v", false, app.defaultDebug)
|
||||
if app.RootCmd == nil {
|
||||
t.Fatal("Expected RootCmd to be initialized, got nil")
|
||||
}
|
||||
|
||||
if app.appWrapper == nil {
|
||||
t.Fatal("Expected appWrapper to be initialized, got nil")
|
||||
}
|
||||
|
||||
if app.hideStartBanner != true {
|
||||
t.Fatal("Expected app.hideStartBanner to be true, got false")
|
||||
}
|
||||
|
||||
if app.DataDir() != "test_dir" {
|
||||
t.Fatalf("Expected app.DataDir() %q, got %q", "test_dir", app.DataDir())
|
||||
}
|
||||
|
||||
if app.EncryptionEnv() != "test_encryption_env" {
|
||||
t.Fatalf("Expected app.DataDir() %q, got %q", "test_encryption_env", app.EncryptionEnv())
|
||||
}
|
||||
|
||||
if app.IsDebug() != true {
|
||||
t.Fatal("Expected app.IsDebug() true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultDataDir(t *testing.T) {
|
||||
app := New()
|
||||
func TestNewWithConfigAndFlags(t *testing.T) {
|
||||
// copy os.Args
|
||||
originalArgs := []string{}
|
||||
copy(originalArgs, os.Args)
|
||||
defer func() {
|
||||
// restore os.Args
|
||||
copy(os.Args, originalArgs)
|
||||
}()
|
||||
|
||||
expected := "test_default"
|
||||
// change os.Args
|
||||
os.Args = os.Args[0:1]
|
||||
os.Args = append(
|
||||
os.Args,
|
||||
"--dir=test_dir_flag",
|
||||
"--encryptionEnv=test_encryption_env_flag",
|
||||
"--debug=false",
|
||||
)
|
||||
|
||||
app.DefaultDataDir(expected)
|
||||
if app.defaultDataDir != expected {
|
||||
t.Fatalf("Expected defaultDataDir %v, got %v", expected, app.defaultDataDir)
|
||||
app := NewWithConfig(Config{
|
||||
DefaultDebug: true,
|
||||
DefaultDataDir: "test_dir",
|
||||
DefaultEncryptionEnv: "test_encryption_env",
|
||||
HideStartBanner: true,
|
||||
})
|
||||
|
||||
if app == nil {
|
||||
t.Fatal("Expected initialized PocketBase instance, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultEncryptionEnv(t *testing.T) {
|
||||
app := New()
|
||||
if app.RootCmd == nil {
|
||||
t.Fatal("Expected RootCmd to be initialized, got nil")
|
||||
}
|
||||
|
||||
expected := "test_env"
|
||||
if app.appWrapper == nil {
|
||||
t.Fatal("Expected appWrapper to be initialized, got nil")
|
||||
}
|
||||
|
||||
app.DefaultEncryptionEnv(expected)
|
||||
if app.defaultEncryptionEnv != expected {
|
||||
t.Fatalf("Expected defaultEncryptionEnv %v, got %v", expected, app.defaultEncryptionEnv)
|
||||
if app.hideStartBanner != true {
|
||||
t.Fatal("Expected app.hideStartBanner to be true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShowStartBanner(t *testing.T) {
|
||||
app := New()
|
||||
if app.DataDir() != "test_dir_flag" {
|
||||
t.Fatalf("Expected app.DataDir() %q, got %q", "test_dir_flag", app.DataDir())
|
||||
}
|
||||
|
||||
app.ShowStartBanner(true)
|
||||
if app.showStartBanner != true {
|
||||
t.Fatalf("Expected showStartBanner %v, got %v", true, app.showStartBanner)
|
||||
if app.EncryptionEnv() != "test_encryption_env_flag" {
|
||||
t.Fatalf("Expected app.DataDir() %q, got %q", "test_encryption_env_flag", app.EncryptionEnv())
|
||||
}
|
||||
|
||||
app.ShowStartBanner(false)
|
||||
if app.showStartBanner != false {
|
||||
t.Fatalf("Expected showStartBanner %v, got %v", false, app.showStartBanner)
|
||||
if app.IsDebug() != false {
|
||||
t.Fatal("Expected app.IsDebug() false, got true")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func (r *RecordFieldResolver) Resolve(fieldName string) (resultName string, plac
|
||||
props := strings.Split(fieldName, ".")
|
||||
|
||||
currentCollectionName := r.baseCollection.Name
|
||||
currentTableAlias := currentCollectionName
|
||||
currentTableAlias := inflector.Columnify(currentCollectionName)
|
||||
|
||||
// check for @collection field (aka. non-relational join)
|
||||
// must be in the format "@collection.COLLECTION_NAME.FIELD[.FIELD2....]"
|
||||
@@ -106,14 +106,14 @@ func (r *RecordFieldResolver) Resolve(fieldName string) (resultName string, plac
|
||||
}
|
||||
|
||||
currentCollectionName = props[1]
|
||||
currentTableAlias = "__collection_" + currentCollectionName
|
||||
currentTableAlias = inflector.Columnify("__collection_" + currentCollectionName)
|
||||
|
||||
collection, err := r.loadCollection(currentCollectionName)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("Failed to load collection %q from field path %q.", currentCollectionName, fieldName)
|
||||
}
|
||||
|
||||
r.addJoin(collection.Name, currentTableAlias, nil)
|
||||
r.addJoin(inflector.Columnify(collection.Name), currentTableAlias, nil)
|
||||
|
||||
props = props[2:] // leave only the collection fields
|
||||
} else if props[0] == "@request" {
|
||||
@@ -123,13 +123,13 @@ func (r *RecordFieldResolver) Resolve(fieldName string) (resultName string, plac
|
||||
}
|
||||
|
||||
// not a profile relational field
|
||||
if len(props) <= 4 || !strings.HasPrefix(fieldName, "@request.user.profile.") {
|
||||
if !strings.HasPrefix(fieldName, "@request.user.profile.") {
|
||||
return r.resolveStaticRequestField(props[1:]...)
|
||||
}
|
||||
|
||||
// resolve the profile collection fields
|
||||
currentCollectionName = models.ProfileCollectionName
|
||||
currentTableAlias = "__user_" + currentCollectionName
|
||||
currentTableAlias = inflector.Columnify("__user_" + currentCollectionName)
|
||||
|
||||
collection, err := r.loadCollection(currentCollectionName)
|
||||
if err != nil {
|
||||
@@ -146,12 +146,16 @@ func (r *RecordFieldResolver) Resolve(fieldName string) (resultName string, plac
|
||||
}
|
||||
|
||||
// join the profile collection
|
||||
r.addJoin(collection.Name, currentTableAlias, dbx.NewExp(fmt.Sprintf(
|
||||
// aka. profiles.id = profileId
|
||||
"[[%s.id]] = %s",
|
||||
inflector.Columnify(currentTableAlias),
|
||||
profileIdPlaceholder,
|
||||
), profileIdPlaceholderParam))
|
||||
r.addJoin(
|
||||
inflector.Columnify(collection.Name),
|
||||
currentTableAlias,
|
||||
dbx.NewExp(fmt.Sprintf(
|
||||
// aka. profiles.id = profileId
|
||||
"[[%s.id]] = %s",
|
||||
currentTableAlias,
|
||||
profileIdPlaceholder,
|
||||
), profileIdPlaceholderParam),
|
||||
)
|
||||
|
||||
props = props[3:] // leave only the profile fields
|
||||
}
|
||||
@@ -168,7 +172,7 @@ func (r *RecordFieldResolver) Resolve(fieldName string) (resultName string, plac
|
||||
|
||||
// base model prop (always available but not part of the collection schema)
|
||||
if list.ExistInSlice(prop, baseModelFields) {
|
||||
return fmt.Sprintf("[[%s.%s]]", inflector.Columnify(currentTableAlias), inflector.Columnify(prop)), nil, nil
|
||||
return fmt.Sprintf("[[%s.%s]]", currentTableAlias, inflector.Columnify(prop)), nil, nil
|
||||
}
|
||||
|
||||
field := collection.Schema.GetFieldByName(prop)
|
||||
@@ -178,7 +182,7 @@ func (r *RecordFieldResolver) Resolve(fieldName string) (resultName string, plac
|
||||
|
||||
// last prop
|
||||
if i == totalProps-1 {
|
||||
return fmt.Sprintf("[[%s.%s]]", inflector.Columnify(currentTableAlias), inflector.Columnify(prop)), nil, nil
|
||||
return fmt.Sprintf("[[%s.%s]]", currentTableAlias, inflector.Columnify(prop)), nil, nil
|
||||
}
|
||||
|
||||
// check if it is a relation field
|
||||
@@ -198,20 +202,27 @@ func (r *RecordFieldResolver) Resolve(fieldName string) (resultName string, plac
|
||||
if relErr != nil {
|
||||
return "", nil, fmt.Errorf("Failed to find field %q collection.", prop)
|
||||
}
|
||||
|
||||
cleanFieldName := inflector.Columnify(field.Name)
|
||||
newCollectionName := relCollection.Name
|
||||
newTableAlias := currentTableAlias + "_" + field.Name
|
||||
newTableAlias := currentTableAlias + "_" + cleanFieldName
|
||||
|
||||
jeTable := currentTableAlias + "_" + cleanFieldName + "_je"
|
||||
jePair := currentTableAlias + "." + cleanFieldName
|
||||
|
||||
r.addJoin(
|
||||
newCollectionName,
|
||||
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)`,
|
||||
jePair, jePair, jePair,
|
||||
),
|
||||
jeTable,
|
||||
nil,
|
||||
)
|
||||
r.addJoin(
|
||||
inflector.Columnify(newCollectionName),
|
||||
newTableAlias,
|
||||
dbx.NewExp(fmt.Sprintf(
|
||||
// 'LIKE' expr is used to handle the case when the reference field supports multiple values (aka. is json array)
|
||||
"[[%s.%s]] LIKE ('%%' || [[%s.%s]] || '%%')",
|
||||
inflector.Columnify(currentTableAlias),
|
||||
inflector.Columnify(field.Name),
|
||||
inflector.Columnify(newTableAlias),
|
||||
inflector.Columnify("id"),
|
||||
)),
|
||||
dbx.NewExp(fmt.Sprintf("[[%s.id]] = [[%s.value]]", newTableAlias, jeTable)),
|
||||
)
|
||||
|
||||
currentCollectionName = newCollectionName
|
||||
@@ -296,11 +307,7 @@ func (r *RecordFieldResolver) loadCollection(collectionNameOrId string) (*models
|
||||
}
|
||||
|
||||
func (r *RecordFieldResolver) addJoin(tableName string, tableAlias string, on dbx.Expression) {
|
||||
tableExpr := fmt.Sprintf(
|
||||
"%s %s",
|
||||
inflector.Columnify(tableName),
|
||||
inflector.Columnify(tableAlias),
|
||||
)
|
||||
tableExpr := fmt.Sprintf("%s %s", tableName, tableAlias)
|
||||
|
||||
join := join{
|
||||
id: tableAlias,
|
||||
|
||||
@@ -52,32 +52,32 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
|
||||
{
|
||||
"single rel",
|
||||
[]string{"onerel.title"},
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `demo4` `demo4_onerel` ON [[demo4.onerel]] LIKE ('%' || [[demo4_onerel.id]] || '%')",
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.onerel]]) THEN [[demo4.onerel]] ELSE json_array([[demo4.onerel]]) END) `demo4_onerel_je` LEFT JOIN `demo4` `demo4_onerel` ON [[demo4_onerel.id]] = [[demo4_onerel_je.value]]",
|
||||
},
|
||||
{
|
||||
"non-relation field + single rel",
|
||||
[]string{"title", "onerel.title"},
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `demo4` `demo4_onerel` ON [[demo4.onerel]] LIKE ('%' || [[demo4_onerel.id]] || '%')",
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.onerel]]) THEN [[demo4.onerel]] ELSE json_array([[demo4.onerel]]) END) `demo4_onerel_je` LEFT JOIN `demo4` `demo4_onerel` ON [[demo4_onerel.id]] = [[demo4_onerel_je.value]]",
|
||||
},
|
||||
{
|
||||
"nested incomplete rels",
|
||||
[]string{"manyrels.onerel"},
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `demo4` `demo4_manyrels` ON [[demo4.manyrels]] LIKE ('%' || [[demo4_manyrels.id]] || '%')",
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.manyrels]]) THEN [[demo4.manyrels]] ELSE json_array([[demo4.manyrels]]) END) `demo4_manyrels_je` LEFT JOIN `demo4` `demo4_manyrels` ON [[demo4_manyrels.id]] = [[demo4_manyrels_je.value]]",
|
||||
},
|
||||
{
|
||||
"nested complete rels",
|
||||
[]string{"manyrels.onerel.title"},
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `demo4` `demo4_manyrels` ON [[demo4.manyrels]] LIKE ('%' || [[demo4_manyrels.id]] || '%') LEFT JOIN `demo4` `demo4_manyrels_onerel` ON [[demo4_manyrels.onerel]] LIKE ('%' || [[demo4_manyrels_onerel.id]] || '%')",
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.manyrels]]) THEN [[demo4.manyrels]] ELSE json_array([[demo4.manyrels]]) END) `demo4_manyrels_je` LEFT JOIN `demo4` `demo4_manyrels` ON [[demo4_manyrels.id]] = [[demo4_manyrels_je.value]] LEFT JOIN json_each(CASE WHEN json_valid([[demo4_manyrels.onerel]]) THEN [[demo4_manyrels.onerel]] ELSE json_array([[demo4_manyrels.onerel]]) END) `demo4_manyrels_onerel_je` LEFT JOIN `demo4` `demo4_manyrels_onerel` ON [[demo4_manyrels_onerel.id]] = [[demo4_manyrels_onerel_je.value]]",
|
||||
},
|
||||
{
|
||||
"repeated nested rels",
|
||||
[]string{"manyrels.onerel.manyrels.onerel.title"},
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `demo4` `demo4_manyrels` ON [[demo4.manyrels]] LIKE ('%' || [[demo4_manyrels.id]] || '%') LEFT JOIN `demo4` `demo4_manyrels_onerel` ON [[demo4_manyrels.onerel]] LIKE ('%' || [[demo4_manyrels_onerel.id]] || '%') LEFT JOIN `demo4` `demo4_manyrels_onerel_manyrels` ON [[demo4_manyrels_onerel.manyrels]] LIKE ('%' || [[demo4_manyrels_onerel_manyrels.id]] || '%') LEFT JOIN `demo4` `demo4_manyrels_onerel_manyrels_onerel` ON [[demo4_manyrels_onerel_manyrels.onerel]] LIKE ('%' || [[demo4_manyrels_onerel_manyrels_onerel.id]] || '%')",
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.manyrels]]) THEN [[demo4.manyrels]] ELSE json_array([[demo4.manyrels]]) END) `demo4_manyrels_je` LEFT JOIN `demo4` `demo4_manyrels` ON [[demo4_manyrels.id]] = [[demo4_manyrels_je.value]] LEFT JOIN json_each(CASE WHEN json_valid([[demo4_manyrels.onerel]]) THEN [[demo4_manyrels.onerel]] ELSE json_array([[demo4_manyrels.onerel]]) END) `demo4_manyrels_onerel_je` LEFT JOIN `demo4` `demo4_manyrels_onerel` ON [[demo4_manyrels_onerel.id]] = [[demo4_manyrels_onerel_je.value]] LEFT JOIN json_each(CASE WHEN json_valid([[demo4_manyrels_onerel.manyrels]]) THEN [[demo4_manyrels_onerel.manyrels]] ELSE json_array([[demo4_manyrels_onerel.manyrels]]) END) `demo4_manyrels_onerel_manyrels_je` LEFT JOIN `demo4` `demo4_manyrels_onerel_manyrels` ON [[demo4_manyrels_onerel_manyrels.id]] = [[demo4_manyrels_onerel_manyrels_je.value]] LEFT JOIN json_each(CASE WHEN json_valid([[demo4_manyrels_onerel_manyrels.onerel]]) THEN [[demo4_manyrels_onerel_manyrels.onerel]] ELSE json_array([[demo4_manyrels_onerel_manyrels.onerel]]) END) `demo4_manyrels_onerel_manyrels_onerel_je` LEFT JOIN `demo4` `demo4_manyrels_onerel_manyrels_onerel` ON [[demo4_manyrels_onerel_manyrels_onerel.id]] = [[demo4_manyrels_onerel_manyrels_onerel_je.value]]",
|
||||
},
|
||||
{
|
||||
"multiple rels",
|
||||
[]string{"manyrels.title", "onerel.onefile"},
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `demo4` `demo4_manyrels` ON [[demo4.manyrels]] LIKE ('%' || [[demo4_manyrels.id]] || '%') LEFT JOIN `demo4` `demo4_onerel` ON [[demo4.onerel]] LIKE ('%' || [[demo4_onerel.id]] || '%')",
|
||||
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN json_each(CASE WHEN json_valid([[demo4.manyrels]]) THEN [[demo4.manyrels]] ELSE json_array([[demo4.manyrels]]) END) `demo4_manyrels_je` LEFT JOIN `demo4` `demo4_manyrels` ON [[demo4_manyrels.id]] = [[demo4_manyrels_je.value]] LEFT JOIN json_each(CASE WHEN json_valid([[demo4.onerel]]) THEN [[demo4.onerel]] ELSE json_array([[demo4.onerel]]) END) `demo4_onerel_je` LEFT JOIN `demo4` `demo4_onerel` ON [[demo4_onerel.id]] = [[demo4_onerel_je.value]]",
|
||||
},
|
||||
{
|
||||
"@collection join",
|
||||
@@ -87,7 +87,9 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
|
||||
{
|
||||
"static @request.user.profile fields",
|
||||
[]string{"@request.user.id", "@request.user.profile.id", "@request.data.demo"},
|
||||
"SELECT `demo4`.* FROM `demo4`",
|
||||
"^" +
|
||||
regexp.QuoteMeta("SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `profiles` `__user_profiles` ON [[__user_profiles.id]] =") +
|
||||
" {:.*}$",
|
||||
},
|
||||
{
|
||||
"relational @request.user.profile fields",
|
||||
@@ -95,12 +97,12 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
|
||||
"^" +
|
||||
regexp.QuoteMeta("SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `profiles` `__user_profiles` ON [[__user_profiles.id]] =") +
|
||||
" {:.*} " +
|
||||
regexp.QuoteMeta("LEFT JOIN `profiles` `__user_profiles_rel` ON [[__user_profiles.rel]] LIKE ('%' || [[__user_profiles_rel.id]] || '%')") +
|
||||
regexp.QuoteMeta("LEFT JOIN json_each(CASE WHEN json_valid([[__user_profiles.rel]]) THEN [[__user_profiles.rel]] ELSE json_array([[__user_profiles.rel]]) END) `__user_profiles_rel_je` LEFT JOIN `profiles` `__user_profiles_rel` ON [[__user_profiles_rel.id]] = [[__user_profiles_rel_je.value]]") +
|
||||
"$",
|
||||
},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
for _, s := range scenarios {
|
||||
query := app.Dao().RecordQuery(collection)
|
||||
|
||||
r := resolvers.NewRecordFieldResolver(app.Dao(), collection, requestData)
|
||||
@@ -109,14 +111,14 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
|
||||
}
|
||||
|
||||
if err := r.UpdateQuery(query); err != nil {
|
||||
t.Errorf("(%d) UpdateQuery failed with error %v", i, err)
|
||||
t.Errorf("(%s) UpdateQuery failed with error %v", s.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
rawQuery := query.Build().SQL()
|
||||
|
||||
if !list.ExistInSliceWithRegex(rawQuery, []string{s.expectQuery}) {
|
||||
t.Errorf("(%d) Expected query\n %v \ngot:\n %v", i, s.expectQuery, rawQuery)
|
||||
t.Errorf("(%s) Expected query\n %v \ngot:\n %v", s.name, s.expectQuery, rawQuery)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,6 +163,7 @@ func TestRecordFieldResolverResolveSchemaFields(t *testing.T) {
|
||||
{"manyrels.title", false, "[[demo4_manyrels.title]]"},
|
||||
{"manyrels.onerel.manyrels.onefile", false, "[[demo4_manyrels_onerel_manyrels.onefile]]"},
|
||||
// @request.user.profile relation join:
|
||||
{"@request.user.profile.rel", false, "[[__user_profiles.rel]]"},
|
||||
{"@request.user.profile.rel.name", false, "[[__user_profiles_rel.name]]"},
|
||||
// @collection fieds:
|
||||
{"@collect", true, ""},
|
||||
@@ -247,7 +250,6 @@ func TestRecordFieldResolverResolveStaticRequestDataFields(t *testing.T) {
|
||||
{"@request.user", true, ""},
|
||||
{"@request.user.id", false, `"4d0197cc-2b4a-3f83-a26b-d77bc8423d3c"`},
|
||||
{"@request.user.profile", false, `"{\"id\":\"d13f60a4-5765-48c7-9e1d-3e782340f833\",\"name\":\"test\"}"`},
|
||||
{"@request.user.profile.name", false, `"test"`},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
|
||||
+17
-7
@@ -36,13 +36,23 @@ type ApiScenario struct {
|
||||
|
||||
// test hooks
|
||||
// ---
|
||||
BeforeFunc func(t *testing.T, app *TestApp, e *echo.Echo)
|
||||
AfterFunc func(t *testing.T, app *TestApp, e *echo.Echo)
|
||||
TestAppFactory func() (*TestApp, error)
|
||||
BeforeTestFunc func(t *testing.T, app *TestApp, e *echo.Echo)
|
||||
AfterTestFunc func(t *testing.T, app *TestApp, e *echo.Echo)
|
||||
}
|
||||
|
||||
// Test executes the test case/scenario.
|
||||
func (scenario *ApiScenario) Test(t *testing.T) {
|
||||
testApp, _ := NewTestApp()
|
||||
var testApp *TestApp
|
||||
var testAppErr error
|
||||
if scenario.TestAppFactory != nil {
|
||||
testApp, testAppErr = scenario.TestAppFactory()
|
||||
} else {
|
||||
testApp, testAppErr = NewTestApp()
|
||||
}
|
||||
if testAppErr != nil {
|
||||
t.Fatalf("Failed to initialize the test app instance: %v", testAppErr)
|
||||
}
|
||||
defer testApp.Cleanup()
|
||||
|
||||
e, err := apis.InitApi(testApp)
|
||||
@@ -50,8 +60,8 @@ func (scenario *ApiScenario) Test(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if scenario.BeforeFunc != nil {
|
||||
scenario.BeforeFunc(t, testApp, e)
|
||||
if scenario.BeforeTestFunc != nil {
|
||||
scenario.BeforeTestFunc(t, testApp, e)
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
@@ -135,7 +145,7 @@ func (scenario *ApiScenario) Test(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if scenario.AfterFunc != nil {
|
||||
scenario.AfterFunc(t, testApp, e)
|
||||
if scenario.AfterTestFunc != nil {
|
||||
scenario.AfterTestFunc(t, testApp, e)
|
||||
}
|
||||
}
|
||||
|
||||
+33
-21
@@ -45,12 +45,21 @@ func (t *TestApp) ResetEventCalls() {
|
||||
t.EventCalls = make(map[string]int)
|
||||
}
|
||||
|
||||
// NewTestApp creates and initializes a full application instance for testing.
|
||||
// NewTestApp creates and initializes a test application instance.
|
||||
//
|
||||
// It is the caller's responsibility to call `app.Cleanup()`
|
||||
// when the app is no longer needed.
|
||||
func NewTestApp() (*TestApp, error) {
|
||||
tempDir, err := NewTempDataDir()
|
||||
func NewTestApp(optTestDataDir ...string) (*TestApp, error) {
|
||||
var testDataDir string
|
||||
if len(optTestDataDir) == 0 || optTestDataDir[0] == "" {
|
||||
// fallback to the default test data directory
|
||||
_, currentFile, _, _ := runtime.Caller(0)
|
||||
testDataDir = filepath.Join(path.Dir(currentFile), "data")
|
||||
} else {
|
||||
testDataDir = optTestDataDir[0]
|
||||
}
|
||||
|
||||
tempDir, err := TempDirClone(testDataDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -188,21 +197,26 @@ func NewTestApp() (*TestApp, error) {
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserBeforeOauth2Register().Add(func(e *core.UserOauth2RegisterEvent) error {
|
||||
t.EventCalls["OnUserBeforeOauth2Register"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserAfterOauth2Register().Add(func(e *core.UserOauth2RegisterEvent) error {
|
||||
t.EventCalls["OnUserAfterOauth2Register"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserAuthRequest().Add(func(e *core.UserAuthEvent) error {
|
||||
t.EventCalls["OnUserAuthRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserListExternalAuths().Add(func(e *core.UserListExternalAuthsEvent) error {
|
||||
t.EventCalls["OnUserListExternalAuths"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserBeforeUnlinkExternalAuthRequest().Add(func(e *core.UserUnlinkExternalAuthEvent) error {
|
||||
t.EventCalls["OnUserBeforeUnlinkExternalAuthRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserAfterUnlinkExternalAuthRequest().Add(func(e *core.UserUnlinkExternalAuthEvent) error {
|
||||
t.EventCalls["OnUserAfterUnlinkExternalAuthRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnMailerBeforeAdminResetPasswordSend().Add(func(e *core.MailerAdminEvent) error {
|
||||
t.EventCalls["OnMailerBeforeAdminResetPasswordSend"]++
|
||||
return nil
|
||||
@@ -376,21 +390,19 @@ func NewTestApp() (*TestApp, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// NewTempDataDir creates a new temporary directory copy of the test data.
|
||||
// TempDirClone creates a new temporary directory copy from the
|
||||
// provided directory path.
|
||||
//
|
||||
// It is the caller's responsibility to call `os.RemoveAll(dir)`
|
||||
// when the directory is no longer needed.
|
||||
func NewTempDataDir() (string, error) {
|
||||
// It is the caller's responsibility to call `os.RemoveAll(tempDir)`
|
||||
// when the directory is no longer needed!
|
||||
func TempDirClone(dirToClone string) (string, error) {
|
||||
tempDir, err := os.MkdirTemp("", "pb_test_*")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, currentFile, _, _ := runtime.Caller(0)
|
||||
testDataDir := filepath.Join(path.Dir(currentFile), "data")
|
||||
|
||||
// copy everything from testDataDir to tempDir
|
||||
if err := copyDir(testDataDir, tempDir); err != nil {
|
||||
if err := copyDir(dirToClone, tempDir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -11,6 +11,7 @@ import (
|
||||
type AuthUser struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
AvatarUrl string `json:"avatarUrl"`
|
||||
}
|
||||
@@ -90,6 +91,10 @@ func NewProviderByName(name string) (Provider, error) {
|
||||
return NewGithubProvider(), nil
|
||||
case NameGitlab:
|
||||
return NewGitlabProvider(), nil
|
||||
case NameDiscord:
|
||||
return NewDiscordProvider(), nil
|
||||
case NameTwitter:
|
||||
return NewTwitterProvider(), nil
|
||||
default:
|
||||
return nil, errors.New("Missing provider " + name)
|
||||
}
|
||||
|
||||
@@ -54,4 +54,13 @@ func TestNewProviderByName(t *testing.T) {
|
||||
if _, ok := p.(*auth.Gitlab); !ok {
|
||||
t.Error("Expected to be instance of *auth.Gitlab")
|
||||
}
|
||||
|
||||
// discord
|
||||
p, err = auth.NewProviderByName(auth.NameDiscord)
|
||||
if err != nil {
|
||||
t.Errorf("Expected nil, got error %v", err)
|
||||
}
|
||||
if _, ok := p.(*auth.Discord); !ok {
|
||||
t.Error("Expected to be instance of *auth.Discord")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var _ Provider = (*Discord)(nil)
|
||||
|
||||
// NameDiscord is the unique name of the Discord provider.
|
||||
const NameDiscord string = "discord"
|
||||
|
||||
// Discord allows authentication via Discord OAuth2.
|
||||
type Discord struct {
|
||||
*baseProvider
|
||||
}
|
||||
|
||||
// NewDiscordProvider creates a new Discord provider instance with some defaults.
|
||||
func NewDiscordProvider() *Discord {
|
||||
// https://discord.com/developers/docs/topics/oauth2
|
||||
// https://discord.com/developers/docs/resources/user#get-current-user
|
||||
return &Discord{&baseProvider{
|
||||
scopes: []string{"identify", "email"},
|
||||
authUrl: "https://discord.com/api/oauth2/authorize",
|
||||
tokenUrl: "https://discord.com/api/oauth2/token",
|
||||
userApiUrl: "https://discord.com/api/users/@me",
|
||||
}}
|
||||
}
|
||||
|
||||
// FetchAuthUser returns an AuthUser instance from Discord's user api.
|
||||
func (p *Discord) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
||||
// https://discord.com/developers/docs/resources/user#user-object
|
||||
rawData := struct {
|
||||
Id string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Discriminator string `json:"discriminator"`
|
||||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar"`
|
||||
}{}
|
||||
|
||||
if err := p.FetchRawUserData(token, &rawData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build a full avatar URL using the avatar hash provided in the API response
|
||||
// https://discord.com/developers/docs/reference#image-formatting
|
||||
avatarUrl := fmt.Sprintf("https://cdn.discordapp.com/avatars/%s/%s.png", rawData.Id, rawData.Avatar)
|
||||
|
||||
// Concatenate the user's username and discriminator into a single username string
|
||||
username := fmt.Sprintf("%s#%s", rawData.Username, rawData.Discriminator)
|
||||
|
||||
user := &AuthUser{
|
||||
Id: rawData.Id,
|
||||
Name: username,
|
||||
Username: rawData.Username,
|
||||
Email: rawData.Email,
|
||||
AvatarUrl: avatarUrl,
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
@@ -32,6 +32,7 @@ func NewGithubProvider() *Github {
|
||||
func (p *Github) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
||||
// https://docs.github.com/en/rest/reference/users#get-the-authenticated-user
|
||||
rawData := struct {
|
||||
Login string `json:"login"`
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
@@ -45,6 +46,7 @@ func (p *Github) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
||||
user := &AuthUser{
|
||||
Id: strconv.Itoa(rawData.Id),
|
||||
Name: rawData.Name,
|
||||
Username: rawData.Login,
|
||||
Email: rawData.Email,
|
||||
AvatarUrl: rawData.AvatarUrl,
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ func (p *Gitlab) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
||||
rawData := struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
AvatarUrl string `json:"avatar_url"`
|
||||
}{}
|
||||
@@ -43,6 +44,7 @@ func (p *Gitlab) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
||||
user := &AuthUser{
|
||||
Id: strconv.Itoa(rawData.Id),
|
||||
Name: rawData.Name,
|
||||
Username: rawData.Username,
|
||||
Email: rawData.Email,
|
||||
AvatarUrl: rawData.AvatarUrl,
|
||||
}
|
||||
|
||||
@@ -29,12 +29,11 @@ func NewGoogleProvider() *Google {
|
||||
|
||||
// FetchAuthUser returns an AuthUser instance based the Google's user api.
|
||||
func (p *Google) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
||||
// https://cloud.google.com/identity-platform/docs/reference/rest/v1/UserInfo
|
||||
rawData := struct {
|
||||
LocalId string `json:"localId"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Email string `json:"email"`
|
||||
PhotoUrl string `json:"photoUrl"`
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Picture string `json:"picture"`
|
||||
}{}
|
||||
|
||||
if err := p.FetchRawUserData(token, &rawData); err != nil {
|
||||
@@ -42,10 +41,10 @@ func (p *Google) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
||||
}
|
||||
|
||||
user := &AuthUser{
|
||||
Id: rawData.LocalId,
|
||||
Name: rawData.DisplayName,
|
||||
Id: rawData.Id,
|
||||
Name: rawData.Name,
|
||||
Email: rawData.Email,
|
||||
AvatarUrl: rawData.PhotoUrl,
|
||||
AvatarUrl: rawData.Picture,
|
||||
}
|
||||
|
||||
return user, nil
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var _ Provider = (*Twitter)(nil)
|
||||
|
||||
// NameTwitter is the unique name of the Twitter provider.
|
||||
const NameTwitter string = "twitter"
|
||||
|
||||
// Twitter allows authentication via Twitter OAuth2.
|
||||
type Twitter struct {
|
||||
*baseProvider
|
||||
}
|
||||
|
||||
// NewTwitterProvider creates new Twitter provider instance with some defaults.
|
||||
func NewTwitterProvider() *Twitter {
|
||||
return &Twitter{&baseProvider{
|
||||
scopes: []string{
|
||||
"users.read",
|
||||
|
||||
// we don't actually use this scope, but for some reason it is required by the `/2/users/me` endpoint
|
||||
// (see https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me)
|
||||
"tweet.read",
|
||||
},
|
||||
authUrl: "https://twitter.com/i/oauth2/authorize",
|
||||
tokenUrl: "https://api.twitter.com/2/oauth2/token",
|
||||
userApiUrl: "https://api.twitter.com/2/users/me?user.fields=id,name,username,profile_image_url",
|
||||
}}
|
||||
}
|
||||
|
||||
// FetchAuthUser returns an AuthUser instance based on the Twitter's user api.
|
||||
func (p *Twitter) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
|
||||
// https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me
|
||||
rawData := struct {
|
||||
Data struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
ProfileImageUrl string `json:"profile_image_url"`
|
||||
|
||||
// NB! At the time of writing, Twitter OAuth2 doesn't support returning the user email address
|
||||
// (see https://twittercommunity.com/t/which-api-to-get-user-after-oauth2-authorization/162417/33)
|
||||
Email string `json:"email"`
|
||||
} `json:"data"`
|
||||
}{}
|
||||
|
||||
if err := p.FetchRawUserData(token, &rawData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := &AuthUser{
|
||||
Id: rawData.Data.Id,
|
||||
Name: rawData.Data.Name,
|
||||
Username: rawData.Data.Username,
|
||||
Email: rawData.Data.Email,
|
||||
AvatarUrl: rawData.Data.ProfileImageUrl,
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
"gocloud.dev/blob"
|
||||
"gocloud.dev/blob/fileblob"
|
||||
@@ -98,7 +99,11 @@ func (s *System) Attributes(fileKey string) (*blob.Attributes, error) {
|
||||
|
||||
// Upload writes content into the fileKey location.
|
||||
func (s *System) Upload(content []byte, fileKey string) error {
|
||||
w, writerErr := s.bucket.NewWriter(s.ctx, fileKey, nil)
|
||||
opts := &blob.WriterOptions{
|
||||
ContentType: mimetype.Detect(content).String(),
|
||||
}
|
||||
|
||||
w, writerErr := s.bucket.NewWriter(s.ctx, fileKey, opts)
|
||||
if writerErr != nil {
|
||||
return writerErr
|
||||
}
|
||||
@@ -185,9 +190,16 @@ var inlineServeContentTypes = []string{
|
||||
// video
|
||||
"video/webm", "video/mp4", "video/3gpp", "video/quicktime", "video/x-ms-wmv",
|
||||
// audio
|
||||
"audio/basic", "audio/aiff", "audio/mpeg", "audio/midi", "audio/wave",
|
||||
"audio/basic", "audio/aiff", "audio/mpeg", "audio/midi", "audio/mp3", "audio/wave",
|
||||
"audio/wav", "audio/x-wav", "audio/x-mpeg", "audio/x-m4a", "audio/aac",
|
||||
// document
|
||||
"application/pdf",
|
||||
"application/pdf", "application/x-pdf",
|
||||
}
|
||||
|
||||
// manualExtensionContentTypes is a map of file extensions to content types.
|
||||
var manualExtensionContentTypes = map[string]string{
|
||||
".svg": "image/svg+xml", // (see https://github.com/whatwg/mimesniff/issues/7)
|
||||
".css": "text/css", // (see https://github.com/gabriel-vasile/mimetype/pull/113)
|
||||
}
|
||||
|
||||
// Serve serves the file at fileKey location to an HTTP response.
|
||||
@@ -204,12 +216,11 @@ func (s *System) Serve(response http.ResponseWriter, fileKey string, name string
|
||||
disposition = "inline"
|
||||
}
|
||||
|
||||
// make an exception for svg and use a custom content type
|
||||
// to send in the response so that it can be loaded in a img tag
|
||||
// (see https://github.com/whatwg/mimesniff/issues/7)
|
||||
// make an exception for specific content types and force a
|
||||
// custom content type to send in the response so that it can be loaded directly.
|
||||
extContentType := realContentType
|
||||
if filepath.Ext(name) == ".svg" {
|
||||
extContentType = "image/svg+xml"
|
||||
if ct, found := manualExtensionContentTypes[filepath.Ext(name)]; found && extContentType != ct {
|
||||
extContentType = ct
|
||||
}
|
||||
|
||||
response.Header().Set("Content-Disposition", disposition+"; filename="+name)
|
||||
@@ -265,8 +276,8 @@ func (s *System) CreateThumb(originalKey string, thumbKey, thumbSize string) err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
// create imaging object from the origial reader
|
||||
img, decodeErr := imaging.Decode(r)
|
||||
// create imaging object from the original reader
|
||||
img, decodeErr := imaging.Decode(r, imaging.AutoOrientation(true))
|
||||
if decodeErr != nil {
|
||||
return decodeErr
|
||||
}
|
||||
@@ -299,8 +310,15 @@ func (s *System) CreateThumb(originalKey string, thumbKey, thumbSize string) err
|
||||
return writerErr
|
||||
}
|
||||
|
||||
// try to detect the thumb format based on the original file name
|
||||
// (fallbacks to png on error)
|
||||
format, err := imaging.FormatFromFilename(thumbKey)
|
||||
if err != nil {
|
||||
format = imaging.PNG
|
||||
}
|
||||
|
||||
// thumb encode (aka. upload)
|
||||
if err := imaging.Encode(w, thumbImg, imaging.PNG); err != nil {
|
||||
if err := imaging.Encode(w, thumbImg, format); err != nil {
|
||||
w.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -207,6 +207,18 @@ func TestFileSystemServe(t *testing.T) {
|
||||
"Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; sandbox",
|
||||
},
|
||||
},
|
||||
{
|
||||
// css exception
|
||||
"style.css",
|
||||
"test_name.css",
|
||||
false,
|
||||
map[string]string{
|
||||
"Content-Disposition": "attachment; filename=test_name.css",
|
||||
"Content-Type": "text/css",
|
||||
"Content-Length": "0",
|
||||
"Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; sandbox",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
@@ -216,7 +228,7 @@ func TestFileSystemServe(t *testing.T) {
|
||||
hasErr := err != nil
|
||||
|
||||
if hasErr != scenario.expectError {
|
||||
t.Errorf("(%s) Expected hasError %v, got %v", scenario.path, scenario.expectError, hasErr)
|
||||
t.Errorf("(%s) Expected hasError %v, got %v (%v)", scenario.path, scenario.expectError, hasErr, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -321,5 +333,11 @@ func createTestDir(t *testing.T) string {
|
||||
}
|
||||
file4.Close()
|
||||
|
||||
file5, err := os.OpenFile(filepath.Join(dir, "style.css"), os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
file5.Close()
|
||||
|
||||
return dir
|
||||
}
|
||||
|
||||
+12
-1
@@ -17,7 +17,18 @@ type Hook[T any] struct {
|
||||
handlers []Handler[T]
|
||||
}
|
||||
|
||||
// Add registers a new handler to the hook.
|
||||
// PreAdd registers a new handler to the hook by prepending it to the existing queue.
|
||||
func (h *Hook[T]) PreAdd(fn Handler[T]) {
|
||||
h.mux.Lock()
|
||||
defer h.mux.Unlock()
|
||||
|
||||
// minimize allocations by shifting the slice
|
||||
h.handlers = append(h.handlers, nil)
|
||||
copy(h.handlers[1:], h.handlers)
|
||||
h.handlers[0] = fn
|
||||
}
|
||||
|
||||
// Add registers a new handler to the hook by appending it to the existing queue.
|
||||
func (h *Hook[T]) Add(fn Handler[T]) {
|
||||
h.mux.Lock()
|
||||
defer h.mux.Unlock()
|
||||
|
||||
+21
-5
@@ -5,18 +5,34 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
func TestAddAndPreAdd(t *testing.T) {
|
||||
h := Hook[int]{}
|
||||
|
||||
if total := len(h.handlers); total != 0 {
|
||||
t.Fatalf("Expected no handlers, found %d", total)
|
||||
}
|
||||
|
||||
h.Add(func(data int) error { return nil })
|
||||
h.Add(func(data int) error { return nil })
|
||||
triggerSequence := ""
|
||||
|
||||
if total := len(h.handlers); total != 2 {
|
||||
t.Fatalf("Expected 2 handlers, found %d", total)
|
||||
f1 := func(data int) error { triggerSequence += "f1"; return nil }
|
||||
f2 := func(data int) error { triggerSequence += "f2"; return nil }
|
||||
f3 := func(data int) error { triggerSequence += "f3"; return nil }
|
||||
f4 := func(data int) error { triggerSequence += "f4"; return nil }
|
||||
|
||||
h.Add(f1)
|
||||
h.Add(f2)
|
||||
h.PreAdd(f3)
|
||||
h.PreAdd(f4)
|
||||
h.Trigger(1)
|
||||
|
||||
if total := len(h.handlers); total != 4 {
|
||||
t.Fatalf("Expected %d handlers, found %d", 4, total)
|
||||
}
|
||||
|
||||
expectedTriggerSequence := "f4f3f1f2"
|
||||
|
||||
if triggerSequence != expectedTriggerSequence {
|
||||
t.Fatalf("Expected trigger sequence %s, got %s", expectedTriggerSequence, triggerSequence)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package mailer
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
var whitespaceRegex = regexp.MustCompile(`\s+`)
|
||||
|
||||
// Very rudimentary auto HTML to Text mail body converter.
|
||||
//
|
||||
// Caveats:
|
||||
// - This method doesn't check for correctness of the HTML document.
|
||||
// - Links will be converted to "[text](url)" format.
|
||||
// - List items (<li>) are prefixed with "- ".
|
||||
// - Indentation is stripped (both tabs and spaces).
|
||||
// - Trailing spaces are preserved.
|
||||
// - Multiple consequence newlines are collapsed as one unless multiple <br> tags are used.
|
||||
func html2Text(htmlDocument string) (string, error) {
|
||||
var builder strings.Builder
|
||||
|
||||
doc, err := html.Parse(strings.NewReader(htmlDocument))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tagsToSkip := []string{
|
||||
"style", "script", "iframe", "applet", "object", "svg", "img",
|
||||
"button", "form", "textarea", "input", "select", "option", "template",
|
||||
}
|
||||
|
||||
inlineTags := []string{
|
||||
"a", "span", "small", "strike", "strong",
|
||||
"sub", "sup", "em", "b", "u", "i",
|
||||
}
|
||||
|
||||
var canAddNewLine bool
|
||||
|
||||
// see https://pkg.go.dev/golang.org/x/net/html#Parse
|
||||
var f func(*html.Node)
|
||||
f = func(n *html.Node) {
|
||||
// start link wrapping for producing "[text](link)" formatted string
|
||||
isLink := n.Type == html.ElementNode && n.Data == "a"
|
||||
if isLink {
|
||||
builder.WriteString("[")
|
||||
}
|
||||
|
||||
switch n.Type {
|
||||
case html.TextNode:
|
||||
txt := whitespaceRegex.ReplaceAllString(n.Data, " ")
|
||||
|
||||
// the prev node has new line so it is safe to trim the indentation
|
||||
if !canAddNewLine {
|
||||
txt = strings.TrimLeft(txt, " ")
|
||||
}
|
||||
|
||||
if txt != "" {
|
||||
builder.WriteString(txt)
|
||||
canAddNewLine = true
|
||||
}
|
||||
case html.ElementNode:
|
||||
if n.Data == "br" {
|
||||
// always write new lines when <br> tag is used
|
||||
builder.WriteString("\r\n")
|
||||
canAddNewLine = false
|
||||
} else if canAddNewLine && !list.ExistInSlice(n.Data, inlineTags) {
|
||||
builder.WriteString("\r\n")
|
||||
canAddNewLine = false
|
||||
}
|
||||
|
||||
// prefix list items with dash
|
||||
if n.Data == "li" {
|
||||
builder.WriteString("- ")
|
||||
}
|
||||
}
|
||||
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
if c.Type != html.ElementNode || !list.ExistInSlice(c.Data, tagsToSkip) {
|
||||
f(c)
|
||||
}
|
||||
}
|
||||
|
||||
// end link wrapping
|
||||
if isLink {
|
||||
builder.WriteString("]")
|
||||
for _, a := range n.Attr {
|
||||
if a.Key == "href" {
|
||||
if a.Val != "" {
|
||||
builder.WriteString("(")
|
||||
builder.WriteString(a.Val)
|
||||
builder.WriteString(")")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f(doc)
|
||||
|
||||
return strings.TrimSpace(builder.String()), nil
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package mailer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHtml2Text(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
html string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"ab c",
|
||||
"ab c",
|
||||
},
|
||||
{
|
||||
"<!-- test html comment -->",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"<!-- test html comment --> a ",
|
||||
"a",
|
||||
},
|
||||
{
|
||||
"<span>a</span>b<span>c</span>",
|
||||
"abc",
|
||||
},
|
||||
{
|
||||
`<a href="a/b/c">test</span>`,
|
||||
"[test](a/b/c)",
|
||||
},
|
||||
{
|
||||
`<a href="">test</span>`,
|
||||
"[test]",
|
||||
},
|
||||
{
|
||||
"<span>a</span> <span>b</span>",
|
||||
"a b",
|
||||
},
|
||||
{
|
||||
"<span>a</span> b <span>c</span>",
|
||||
"a b c",
|
||||
},
|
||||
{
|
||||
"<span>a</span> b <div>c</div>",
|
||||
"a b \r\nc",
|
||||
},
|
||||
{
|
||||
`
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- test html comment -->
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
<div class="wrapper">
|
||||
<div class="content">
|
||||
<p>Lorem ipsum</p>
|
||||
<p>Dolor sit amet</p>
|
||||
<p>
|
||||
<a href="a/b/c">Verify</a>
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
<a href="a/b/c"><strong>Verify2.1</strong> <strong>Verify2.2</strong></a>
|
||||
</p>
|
||||
<br>
|
||||
<br>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<ul>
|
||||
<li>ul.test1</li>
|
||||
<li>ul.test2</li>
|
||||
<li>ul.test3</li>
|
||||
</ul>
|
||||
<ol>
|
||||
<li>ol.test1</li>
|
||||
<li>ol.test2</li>
|
||||
<li>ol.test3</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<select>
|
||||
<option>Option 1</option>
|
||||
<option>Option 2</option>
|
||||
</select>
|
||||
<textarea>test</textarea>
|
||||
<input type="text" value="test" />
|
||||
<button>test</button>
|
||||
<p>
|
||||
Thanks,<br/>
|
||||
PocketBase team
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
"Lorem ipsum \r\nDolor sit amet \r\n[Verify](a/b/c) \r\n[Verify2.1 Verify2.2](a/b/c) \r\n\r\n- ul.test1 \r\n- ul.test2 \r\n- ul.test3 \r\n- ol.test1 \r\n- ol.test2 \r\n- ol.test3 \r\nThanks,\r\nPocketBase team",
|
||||
},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
result, err := html2Text(s.html)
|
||||
if err != nil {
|
||||
t.Errorf("(%d) Unexpected error %v", i, err)
|
||||
}
|
||||
|
||||
if result != s.expected {
|
||||
t.Errorf("(%d) Expected \n(%q)\n%v,\n\ngot:\n\n(%q)\n%v", i, s.expected, s.expected, result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,11 @@ func (m *SmtpClient) Send(
|
||||
yak.Subject(subject)
|
||||
yak.HTML().Set(htmlContent)
|
||||
|
||||
// try to generate a plain text version of the HTML
|
||||
if plain, err := html2Text(htmlContent); err == nil {
|
||||
yak.Plain().Set(plain)
|
||||
}
|
||||
|
||||
for name, data := range attachments {
|
||||
yak.Attach(name, data)
|
||||
}
|
||||
|
||||
@@ -103,16 +103,28 @@ func (f FilterData) resolveTokenizedExpr(expr fexpr.Expr, fieldResolver FieldRes
|
||||
case fexpr.SignNeq:
|
||||
return dbx.NewExp(fmt.Sprintf("COALESCE(%s, '') != COALESCE(%s, '')", lName, rName), params), nil
|
||||
case fexpr.SignLike:
|
||||
// both sides are columns and therefore wrap the right side with "%" for contains like behavior
|
||||
if len(params) == 0 {
|
||||
return dbx.NewExp(fmt.Sprintf("%s LIKE ('%%' || %s || '%%')", lName, rName), params), nil
|
||||
}
|
||||
|
||||
// normalize operands and switch sides if the left operand is a number or text
|
||||
if len(lParams) > 0 {
|
||||
return dbx.NewExp(fmt.Sprintf("%s LIKE %s", rName, lName), f.normalizeLikeParams(params)), nil
|
||||
}
|
||||
|
||||
return dbx.NewExp(fmt.Sprintf("%s LIKE %s", lName, rName), f.normalizeLikeParams(params)), nil
|
||||
case fexpr.SignNlike:
|
||||
// both sides are columns and therefore wrap the right side with "%" for not-contains like behavior
|
||||
if len(params) == 0 {
|
||||
return dbx.NewExp(fmt.Sprintf("%s NOT LIKE ('%%' || %s || '%%')", lName, rName), params), nil
|
||||
}
|
||||
|
||||
// normalize operands and switch sides if the left operand is a number or text
|
||||
if len(lParams) > 0 {
|
||||
return dbx.NewExp(fmt.Sprintf("%s NOT LIKE %s", rName, lName), f.normalizeLikeParams(params)), nil
|
||||
}
|
||||
|
||||
return dbx.NewExp(fmt.Sprintf("%s NOT LIKE %s", lName, rName), f.normalizeLikeParams(params)), nil
|
||||
case fexpr.SignLt:
|
||||
return dbx.NewExp(fmt.Sprintf("%s < %s", lName, rName), params), nil
|
||||
|
||||
@@ -32,6 +32,34 @@ func TestFilterDataBuildExpr(t *testing.T) {
|
||||
regexp.QuoteMeta("}") +
|
||||
"$",
|
||||
},
|
||||
// like with 2 columns
|
||||
{"test1 ~ test2", false,
|
||||
"^" +
|
||||
regexp.QuoteMeta("[[test1]] LIKE ('%' || [[test2]] || '%')") +
|
||||
"$",
|
||||
},
|
||||
// reversed like with text
|
||||
{"'lorem' ~ test1", false,
|
||||
"^" +
|
||||
regexp.QuoteMeta("[[test1]] LIKE {:") +
|
||||
".+" +
|
||||
regexp.QuoteMeta("}") +
|
||||
"$",
|
||||
},
|
||||
// not like with 2 columns
|
||||
{"test1 !~ test2", false,
|
||||
"^" +
|
||||
regexp.QuoteMeta("[[test1]] NOT LIKE ('%' || [[test2]] || '%')") +
|
||||
"$",
|
||||
},
|
||||
// reversed not like with text
|
||||
{"'lorem' !~ test1", false,
|
||||
"^" +
|
||||
regexp.QuoteMeta("[[test1]] NOT LIKE {:") +
|
||||
".+" +
|
||||
regexp.QuoteMeta("}") +
|
||||
"$",
|
||||
},
|
||||
// current datetime constant
|
||||
{"test1 > @now", false,
|
||||
"^" +
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
const DefaultPerPage int = 30
|
||||
|
||||
// MaxPerPage specifies the maximum allowed search result items returned in a single page.
|
||||
const MaxPerPage int = 200
|
||||
const MaxPerPage int = 400
|
||||
|
||||
// url search query params
|
||||
const (
|
||||
@@ -199,7 +199,7 @@ func (s *Provider) Exec(items any) (*Result, error) {
|
||||
// count
|
||||
var totalCount int64
|
||||
countQuery := modelsQuery
|
||||
countQuery.Distinct(false).Select("COUNT(*)")
|
||||
countQuery.Distinct(false).Select("COUNT(*)").OrderBy() // unset ORDER BY statements
|
||||
if s.countColumn != "" {
|
||||
countQuery.Select("COUNT(DISTINCT(" + s.countColumn + "))")
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ func TestProviderExecNonEmptyQuery(t *testing.T) {
|
||||
false,
|
||||
`{"page":1,"perPage":10,"totalItems":2,"totalPages":1,"items":[{"test1":1,"test2":"test2.1","test3":""},{"test1":2,"test2":"test2.2","test3":""}]}`,
|
||||
[]string{
|
||||
"SELECT COUNT(*) FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC",
|
||||
"SELECT COUNT(*) FROM `test` WHERE NOT (`test1` IS NULL)",
|
||||
"SELECT * FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC LIMIT 10",
|
||||
},
|
||||
},
|
||||
@@ -252,7 +252,7 @@ func TestProviderExecNonEmptyQuery(t *testing.T) {
|
||||
false,
|
||||
`{"page":1,"perPage":30,"totalItems":2,"totalPages":1,"items":[{"test1":1,"test2":"test2.1","test3":""},{"test1":2,"test2":"test2.2","test3":""}]}`,
|
||||
[]string{
|
||||
"SELECT COUNT(*) FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC",
|
||||
"SELECT COUNT(*) FROM `test` WHERE NOT (`test1` IS NULL)",
|
||||
"SELECT * FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC LIMIT 30",
|
||||
},
|
||||
},
|
||||
@@ -288,8 +288,8 @@ func TestProviderExecNonEmptyQuery(t *testing.T) {
|
||||
false,
|
||||
`{"page":1,"perPage":` + fmt.Sprint(MaxPerPage) + `,"totalItems":1,"totalPages":1,"items":[{"test1":2,"test2":"test2.2","test3":""}]}`,
|
||||
[]string{
|
||||
"SELECT COUNT(*) FROM `test` WHERE ((NOT (`test1` IS NULL)) AND (COALESCE(test2, '') != COALESCE(null, ''))) AND (test1 >= 2) ORDER BY `test1` ASC, `test2` DESC",
|
||||
"SELECT * FROM `test` WHERE ((NOT (`test1` IS NULL)) AND (COALESCE(test2, '') != COALESCE(null, ''))) AND (test1 >= 2) ORDER BY `test1` ASC, `test2` DESC LIMIT 200",
|
||||
"SELECT COUNT(*) FROM `test` WHERE ((NOT (`test1` IS NULL)) AND (COALESCE(test2, '') != COALESCE(null, ''))) AND (test1 >= 2)",
|
||||
"SELECT * FROM `test` WHERE ((NOT (`test1` IS NULL)) AND (COALESCE(test2, '') != COALESCE(null, ''))) AND (test1 >= 2) ORDER BY `test1` ASC, `test2` DESC LIMIT 400",
|
||||
},
|
||||
},
|
||||
// valid sort and filter fields (zero results)
|
||||
@@ -302,7 +302,7 @@ func TestProviderExecNonEmptyQuery(t *testing.T) {
|
||||
false,
|
||||
`{"page":1,"perPage":10,"totalItems":0,"totalPages":0,"items":[]}`,
|
||||
[]string{
|
||||
"SELECT COUNT(*) FROM `test` WHERE (NOT (`test1` IS NULL)) AND (COALESCE(test3, '') != COALESCE('', '')) ORDER BY `test1` ASC, `test3` ASC",
|
||||
"SELECT COUNT(*) FROM `test` WHERE (NOT (`test1` IS NULL)) AND (COALESCE(test3, '') != COALESCE('', ''))",
|
||||
"SELECT * FROM `test` WHERE (NOT (`test1` IS NULL)) AND (COALESCE(test3, '') != COALESCE('', '')) ORDER BY `test1` ASC, `test3` ASC LIMIT 10",
|
||||
},
|
||||
},
|
||||
@@ -316,7 +316,7 @@ func TestProviderExecNonEmptyQuery(t *testing.T) {
|
||||
false,
|
||||
`{"page":2,"perPage":1,"totalItems":2,"totalPages":2,"items":[{"test1":2,"test2":"test2.2","test3":""}]}`,
|
||||
[]string{
|
||||
"SELECT COUNT(*) FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC",
|
||||
"SELECT COUNT(*) FROM `test` WHERE NOT (`test1` IS NULL)",
|
||||
"SELECT * FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC LIMIT 1 OFFSET 1",
|
||||
},
|
||||
},
|
||||
@@ -330,7 +330,7 @@ func TestProviderExecNonEmptyQuery(t *testing.T) {
|
||||
false,
|
||||
`{"page":2,"perPage":1,"totalItems":2,"totalPages":2,"items":[{"test1":2,"test2":"test2.2","test3":""}]}`,
|
||||
[]string{
|
||||
"SELECT COUNT(DISTINCT(test.test1)) FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC",
|
||||
"SELECT COUNT(DISTINCT(test.test1)) FROM `test` WHERE NOT (`test1` IS NULL)",
|
||||
"SELECT * FROM `test` WHERE NOT (`test1` IS NULL) ORDER BY `test1` ASC LIMIT 1 OFFSET 1",
|
||||
},
|
||||
},
|
||||
@@ -437,9 +437,9 @@ func TestProviderParseAndExec(t *testing.T) {
|
||||
},
|
||||
// valid query params
|
||||
{
|
||||
"page=3&perPage=555&filter=test1>1&sort=-test2,test3",
|
||||
"page=3&perPage=9999&filter=test1>1&sort=-test2,test3",
|
||||
false,
|
||||
`{"page":1,"perPage":200,"totalItems":1,"totalPages":1,"items":[{"test1":2,"test2":"test2.2","test3":""}]}`,
|
||||
`{"page":1,"perPage":400,"totalItems":1,"totalPages":1,"items":[{"test1":2,"test2":"test2.2","test3":""}]}`,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -4,4 +4,4 @@ PB_PROFILE_COLLECTION = "profiles"
|
||||
PB_INSTALLER_PARAM = "installer"
|
||||
PB_RULES_SYNTAX_DOCS = "https://pocketbase.io/docs/manage-collections#rules-filters-syntax"
|
||||
PB_RELEASES = "https://github.com/pocketbase/pocketbase/releases"
|
||||
PB_VERSION = "v0.5.2"
|
||||
PB_VERSION = "v0.7.7"
|
||||
|
||||
+13
File diff suppressed because one or more lines are too long
-13
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
@@ -1,2 +1,2 @@
|
||||
import{S as E,i as G,s as I,F as K,c as A,m as B,t as H,a as N,d as T,C as M,q as J,e as c,w as q,b as k,f as u,r as L,g as b,h as _,u as h,v as O,j as Q,l as U,o as w,A as V,p as W,B as X,D as Y,x as Z,z as S}from"./index.c92519c5.js";function y(f){let e,o,s;return{c(){e=q("for "),o=c("strong"),s=q(f[3]),u(o,"class","txt-nowrap")},m(l,t){b(l,e,t),b(l,o,t),_(o,s)},p(l,t){t&8&&Z(s,l[3])},d(l){l&&w(e),l&&w(o)}}}function x(f){let e,o,s,l,t,r,p,d;return{c(){e=c("label"),o=q("New password"),l=k(),t=c("input"),u(e,"for",s=f[8]),u(t,"type","password"),u(t,"id",r=f[8]),t.required=!0,t.autofocus=!0},m(n,i){b(n,e,i),_(e,o),b(n,l,i),b(n,t,i),S(t,f[0]),t.focus(),p||(d=h(t,"input",f[6]),p=!0)},p(n,i){i&256&&s!==(s=n[8])&&u(e,"for",s),i&256&&r!==(r=n[8])&&u(t,"id",r),i&1&&t.value!==n[0]&&S(t,n[0])},d(n){n&&w(e),n&&w(l),n&&w(t),p=!1,d()}}}function ee(f){let e,o,s,l,t,r,p,d;return{c(){e=c("label"),o=q("New password confirm"),l=k(),t=c("input"),u(e,"for",s=f[8]),u(t,"type","password"),u(t,"id",r=f[8]),t.required=!0},m(n,i){b(n,e,i),_(e,o),b(n,l,i),b(n,t,i),S(t,f[1]),p||(d=h(t,"input",f[7]),p=!0)},p(n,i){i&256&&s!==(s=n[8])&&u(e,"for",s),i&256&&r!==(r=n[8])&&u(t,"id",r),i&2&&t.value!==n[1]&&S(t,n[1])},d(n){n&&w(e),n&&w(l),n&&w(t),p=!1,d()}}}function te(f){let e,o,s,l,t,r,p,d,n,i,g,R,C,v,P,F,j,m=f[3]&&y(f);return r=new J({props:{class:"form-field required",name:"password",$$slots:{default:[x,({uniqueId:a})=>({8:a}),({uniqueId:a})=>a?256:0]},$$scope:{ctx:f}}}),d=new J({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[ee,({uniqueId:a})=>({8:a}),({uniqueId:a})=>a?256:0]},$$scope:{ctx:f}}}),{c(){e=c("form"),o=c("div"),s=c("h4"),l=q(`Reset your admin password
|
||||
`),m&&m.c(),t=k(),A(r.$$.fragment),p=k(),A(d.$$.fragment),n=k(),i=c("button"),g=c("span"),g.textContent="Set new password",R=k(),C=c("div"),v=c("a"),v.textContent="Back to login",u(s,"class","m-b-xs"),u(o,"class","content txt-center m-b-sm"),u(g,"class","txt"),u(i,"type","submit"),u(i,"class","btn btn-lg btn-block"),i.disabled=f[2],L(i,"btn-loading",f[2]),u(e,"class","m-b-base"),u(v,"href","/login"),u(v,"class","link-hint"),u(C,"class","content txt-center")},m(a,$){b(a,e,$),_(e,o),_(o,s),_(s,l),m&&m.m(s,null),_(e,t),B(r,e,null),_(e,p),B(d,e,null),_(e,n),_(e,i),_(i,g),b(a,R,$),b(a,C,$),_(C,v),P=!0,F||(j=[h(e,"submit",O(f[4])),Q(U.call(null,v))],F=!0)},p(a,$){a[3]?m?m.p(a,$):(m=y(a),m.c(),m.m(s,null)):m&&(m.d(1),m=null);const z={};$&769&&(z.$$scope={dirty:$,ctx:a}),r.$set(z);const D={};$&770&&(D.$$scope={dirty:$,ctx:a}),d.$set(D),(!P||$&4)&&(i.disabled=a[2]),$&4&&L(i,"btn-loading",a[2])},i(a){P||(H(r.$$.fragment,a),H(d.$$.fragment,a),P=!0)},o(a){N(r.$$.fragment,a),N(d.$$.fragment,a),P=!1},d(a){a&&w(e),m&&m.d(),T(r),T(d),a&&w(R),a&&w(C),F=!1,V(j)}}}function se(f){let e,o;return e=new K({props:{$$slots:{default:[te]},$$scope:{ctx:f}}}),{c(){A(e.$$.fragment)},m(s,l){B(e,s,l),o=!0},p(s,[l]){const t={};l&527&&(t.$$scope={dirty:l,ctx:s}),e.$set(t)},i(s){o||(H(e.$$.fragment,s),o=!0)},o(s){N(e.$$.fragment,s),o=!1},d(s){T(e,s)}}}function le(f,e,o){let s,{params:l}=e,t="",r="",p=!1;async function d(){if(!p){o(2,p=!0);try{await W.admins.confirmPasswordReset(l==null?void 0:l.token,t,r),X("Successfully set a new admin password."),Y("/")}catch(g){W.errorResponseHandler(g)}o(2,p=!1)}}function n(){t=this.value,o(0,t)}function i(){r=this.value,o(1,r)}return f.$$set=g=>{"params"in g&&o(5,l=g.params)},f.$$.update=()=>{f.$$.dirty&32&&o(3,s=M.getJWTPayload(l==null?void 0:l.token).email||"")},[t,r,p,s,d,l,n,i]}class ae extends E{constructor(e){super(),G(this,e,le,se,I,{params:5})}}export{ae as default};
|
||||
import{S as E,i as G,s as I,F as K,c as A,m as B,t as H,a as N,d as T,C as M,q as J,e as c,w as q,b as C,f as u,r as L,g as b,h as _,u as h,v as O,j as Q,l as U,o as w,A as V,p as W,B as X,D as Y,x as Z,z as S}from"./index.7e58656e.js";function y(f){let e,o,s;return{c(){e=q("for "),o=c("strong"),s=q(f[3]),u(o,"class","txt-nowrap")},m(l,t){b(l,e,t),b(l,o,t),_(o,s)},p(l,t){t&8&&Z(s,l[3])},d(l){l&&w(e),l&&w(o)}}}function x(f){let e,o,s,l,t,r,p,d;return{c(){e=c("label"),o=q("New password"),l=C(),t=c("input"),u(e,"for",s=f[8]),u(t,"type","password"),u(t,"id",r=f[8]),t.required=!0,t.autofocus=!0},m(n,i){b(n,e,i),_(e,o),b(n,l,i),b(n,t,i),S(t,f[0]),t.focus(),p||(d=h(t,"input",f[6]),p=!0)},p(n,i){i&256&&s!==(s=n[8])&&u(e,"for",s),i&256&&r!==(r=n[8])&&u(t,"id",r),i&1&&t.value!==n[0]&&S(t,n[0])},d(n){n&&w(e),n&&w(l),n&&w(t),p=!1,d()}}}function ee(f){let e,o,s,l,t,r,p,d;return{c(){e=c("label"),o=q("New password confirm"),l=C(),t=c("input"),u(e,"for",s=f[8]),u(t,"type","password"),u(t,"id",r=f[8]),t.required=!0},m(n,i){b(n,e,i),_(e,o),b(n,l,i),b(n,t,i),S(t,f[1]),p||(d=h(t,"input",f[7]),p=!0)},p(n,i){i&256&&s!==(s=n[8])&&u(e,"for",s),i&256&&r!==(r=n[8])&&u(t,"id",r),i&2&&t.value!==n[1]&&S(t,n[1])},d(n){n&&w(e),n&&w(l),n&&w(t),p=!1,d()}}}function te(f){let e,o,s,l,t,r,p,d,n,i,g,R,P,v,k,F,j,m=f[3]&&y(f);return r=new J({props:{class:"form-field required",name:"password",$$slots:{default:[x,({uniqueId:a})=>({8:a}),({uniqueId:a})=>a?256:0]},$$scope:{ctx:f}}}),d=new J({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[ee,({uniqueId:a})=>({8:a}),({uniqueId:a})=>a?256:0]},$$scope:{ctx:f}}}),{c(){e=c("form"),o=c("div"),s=c("h4"),l=q(`Reset your admin password
|
||||
`),m&&m.c(),t=C(),A(r.$$.fragment),p=C(),A(d.$$.fragment),n=C(),i=c("button"),g=c("span"),g.textContent="Set new password",R=C(),P=c("div"),v=c("a"),v.textContent="Back to login",u(s,"class","m-b-xs"),u(o,"class","content txt-center m-b-sm"),u(g,"class","txt"),u(i,"type","submit"),u(i,"class","btn btn-lg btn-block"),i.disabled=f[2],L(i,"btn-loading",f[2]),u(e,"class","m-b-base"),u(v,"href","/login"),u(v,"class","link-hint"),u(P,"class","content txt-center")},m(a,$){b(a,e,$),_(e,o),_(o,s),_(s,l),m&&m.m(s,null),_(e,t),B(r,e,null),_(e,p),B(d,e,null),_(e,n),_(e,i),_(i,g),b(a,R,$),b(a,P,$),_(P,v),k=!0,F||(j=[h(e,"submit",O(f[4])),Q(U.call(null,v))],F=!0)},p(a,$){a[3]?m?m.p(a,$):(m=y(a),m.c(),m.m(s,null)):m&&(m.d(1),m=null);const z={};$&769&&(z.$$scope={dirty:$,ctx:a}),r.$set(z);const D={};$&770&&(D.$$scope={dirty:$,ctx:a}),d.$set(D),(!k||$&4)&&(i.disabled=a[2]),(!k||$&4)&&L(i,"btn-loading",a[2])},i(a){k||(H(r.$$.fragment,a),H(d.$$.fragment,a),k=!0)},o(a){N(r.$$.fragment,a),N(d.$$.fragment,a),k=!1},d(a){a&&w(e),m&&m.d(),T(r),T(d),a&&w(R),a&&w(P),F=!1,V(j)}}}function se(f){let e,o;return e=new K({props:{$$slots:{default:[te]},$$scope:{ctx:f}}}),{c(){A(e.$$.fragment)},m(s,l){B(e,s,l),o=!0},p(s,[l]){const t={};l&527&&(t.$$scope={dirty:l,ctx:s}),e.$set(t)},i(s){o||(H(e.$$.fragment,s),o=!0)},o(s){N(e.$$.fragment,s),o=!1},d(s){T(e,s)}}}function le(f,e,o){let s,{params:l}=e,t="",r="",p=!1;async function d(){if(!p){o(2,p=!0);try{await W.admins.confirmPasswordReset(l==null?void 0:l.token,t,r),X("Successfully set a new admin password."),Y("/")}catch(g){W.errorResponseHandler(g)}o(2,p=!1)}}function n(){t=this.value,o(0,t)}function i(){r=this.value,o(1,r)}return f.$$set=g=>{"params"in g&&o(5,l=g.params)},f.$$.update=()=>{f.$$.dirty&32&&o(3,s=M.getJWTPayload(l==null?void 0:l.token).email||"")},[t,r,p,s,d,l,n,i]}class ae extends E{constructor(e){super(),G(this,e,le,se,I,{params:5})}}export{ae as default};
|
||||
@@ -1,2 +0,0 @@
|
||||
import{S as M,i as T,s as j,F as z,c as H,m as L,t as w,a as y,d as S,b as g,e as _,f as p,g as k,h as d,j as A,l as B,k as N,n as D,o as v,p as C,q as G,r as F,u as E,v as I,w as h,x as J,y as P,z as R}from"./index.c92519c5.js";function K(c){let e,s,n,l,t,o,f,m,i,a,b,u;return l=new G({props:{class:"form-field required",name:"email",$$slots:{default:[Q,({uniqueId:r})=>({5:r}),({uniqueId:r})=>r?32:0]},$$scope:{ctx:c}}}),{c(){e=_("form"),s=_("div"),s.innerHTML=`<h4 class="m-b-xs">Forgotten admin password</h4>
|
||||
<p>Enter the email associated with your account and we\u2019ll send you a recovery link:</p>`,n=g(),H(l.$$.fragment),t=g(),o=_("button"),f=_("i"),m=g(),i=_("span"),i.textContent="Send recovery link",p(s,"class","content txt-center m-b-sm"),p(f,"class","ri-mail-send-line"),p(i,"class","txt"),p(o,"type","submit"),p(o,"class","btn btn-lg btn-block"),o.disabled=c[1],F(o,"btn-loading",c[1]),p(e,"class","m-b-base")},m(r,$){k(r,e,$),d(e,s),d(e,n),L(l,e,null),d(e,t),d(e,o),d(o,f),d(o,m),d(o,i),a=!0,b||(u=E(e,"submit",I(c[3])),b=!0)},p(r,$){const q={};$&97&&(q.$$scope={dirty:$,ctx:r}),l.$set(q),(!a||$&2)&&(o.disabled=r[1]),$&2&&F(o,"btn-loading",r[1])},i(r){a||(w(l.$$.fragment,r),a=!0)},o(r){y(l.$$.fragment,r),a=!1},d(r){r&&v(e),S(l),b=!1,u()}}}function O(c){let e,s,n,l,t,o,f,m,i;return{c(){e=_("div"),s=_("div"),s.innerHTML='<i class="ri-checkbox-circle-line"></i>',n=g(),l=_("div"),t=_("p"),o=h("Check "),f=_("strong"),m=h(c[0]),i=h(" for the recovery link."),p(s,"class","icon"),p(f,"class","txt-nowrap"),p(l,"class","content"),p(e,"class","alert alert-success")},m(a,b){k(a,e,b),d(e,s),d(e,n),d(e,l),d(l,t),d(t,o),d(t,f),d(f,m),d(t,i)},p(a,b){b&1&&J(m,a[0])},i:P,o:P,d(a){a&&v(e)}}}function Q(c){let e,s,n,l,t,o,f,m;return{c(){e=_("label"),s=h("Email"),l=g(),t=_("input"),p(e,"for",n=c[5]),p(t,"type","email"),p(t,"id",o=c[5]),t.required=!0,t.autofocus=!0},m(i,a){k(i,e,a),d(e,s),k(i,l,a),k(i,t,a),R(t,c[0]),t.focus(),f||(m=E(t,"input",c[4]),f=!0)},p(i,a){a&32&&n!==(n=i[5])&&p(e,"for",n),a&32&&o!==(o=i[5])&&p(t,"id",o),a&1&&t.value!==i[0]&&R(t,i[0])},d(i){i&&v(e),i&&v(l),i&&v(t),f=!1,m()}}}function U(c){let e,s,n,l,t,o,f,m;const i=[O,K],a=[];function b(u,r){return u[2]?0:1}return e=b(c),s=a[e]=i[e](c),{c(){s.c(),n=g(),l=_("div"),t=_("a"),t.textContent="Back to login",p(t,"href","/login"),p(t,"class","link-hint"),p(l,"class","content txt-center")},m(u,r){a[e].m(u,r),k(u,n,r),k(u,l,r),d(l,t),o=!0,f||(m=A(B.call(null,t)),f=!0)},p(u,r){let $=e;e=b(u),e===$?a[e].p(u,r):(N(),y(a[$],1,1,()=>{a[$]=null}),D(),s=a[e],s?s.p(u,r):(s=a[e]=i[e](u),s.c()),w(s,1),s.m(n.parentNode,n))},i(u){o||(w(s),o=!0)},o(u){y(s),o=!1},d(u){a[e].d(u),u&&v(n),u&&v(l),f=!1,m()}}}function V(c){let e,s;return e=new z({props:{$$slots:{default:[U]},$$scope:{ctx:c}}}),{c(){H(e.$$.fragment)},m(n,l){L(e,n,l),s=!0},p(n,[l]){const t={};l&71&&(t.$$scope={dirty:l,ctx:n}),e.$set(t)},i(n){s||(w(e.$$.fragment,n),s=!0)},o(n){y(e.$$.fragment,n),s=!1},d(n){S(e,n)}}}function W(c,e,s){let n="",l=!1,t=!1;async function o(){if(!l){s(1,l=!0);try{await C.admins.requestPasswordReset(n),s(2,t=!0)}catch(m){C.errorResponseHandler(m)}s(1,l=!1)}}function f(){n=this.value,s(0,n)}return[n,l,t,o,f]}class Y extends M{constructor(e){super(),T(this,e,W,V,j,{})}}export{Y as default};
|
||||
@@ -0,0 +1,2 @@
|
||||
import{S as M,i as T,s as j,F as z,c as H,m as L,t as w,a as y,d as S,b as g,e as _,f as p,g as k,h as d,j as A,l as B,k as N,n as D,o as v,p as C,q as G,r as F,u as E,v as I,w as h,x as J,y as P,z as R}from"./index.7e58656e.js";function K(c){let e,s,n,l,t,o,f,m,i,a,b,u;return l=new G({props:{class:"form-field required",name:"email",$$slots:{default:[Q,({uniqueId:r})=>({5:r}),({uniqueId:r})=>r?32:0]},$$scope:{ctx:c}}}),{c(){e=_("form"),s=_("div"),s.innerHTML=`<h4 class="m-b-xs">Forgotten admin password</h4>
|
||||
<p>Enter the email associated with your account and we\u2019ll send you a recovery link:</p>`,n=g(),H(l.$$.fragment),t=g(),o=_("button"),f=_("i"),m=g(),i=_("span"),i.textContent="Send recovery link",p(s,"class","content txt-center m-b-sm"),p(f,"class","ri-mail-send-line"),p(i,"class","txt"),p(o,"type","submit"),p(o,"class","btn btn-lg btn-block"),o.disabled=c[1],F(o,"btn-loading",c[1]),p(e,"class","m-b-base")},m(r,$){k(r,e,$),d(e,s),d(e,n),L(l,e,null),d(e,t),d(e,o),d(o,f),d(o,m),d(o,i),a=!0,b||(u=E(e,"submit",I(c[3])),b=!0)},p(r,$){const q={};$&97&&(q.$$scope={dirty:$,ctx:r}),l.$set(q),(!a||$&2)&&(o.disabled=r[1]),(!a||$&2)&&F(o,"btn-loading",r[1])},i(r){a||(w(l.$$.fragment,r),a=!0)},o(r){y(l.$$.fragment,r),a=!1},d(r){r&&v(e),S(l),b=!1,u()}}}function O(c){let e,s,n,l,t,o,f,m,i;return{c(){e=_("div"),s=_("div"),s.innerHTML='<i class="ri-checkbox-circle-line"></i>',n=g(),l=_("div"),t=_("p"),o=h("Check "),f=_("strong"),m=h(c[0]),i=h(" for the recovery link."),p(s,"class","icon"),p(f,"class","txt-nowrap"),p(l,"class","content"),p(e,"class","alert alert-success")},m(a,b){k(a,e,b),d(e,s),d(e,n),d(e,l),d(l,t),d(t,o),d(t,f),d(f,m),d(t,i)},p(a,b){b&1&&J(m,a[0])},i:P,o:P,d(a){a&&v(e)}}}function Q(c){let e,s,n,l,t,o,f,m;return{c(){e=_("label"),s=h("Email"),l=g(),t=_("input"),p(e,"for",n=c[5]),p(t,"type","email"),p(t,"id",o=c[5]),t.required=!0,t.autofocus=!0},m(i,a){k(i,e,a),d(e,s),k(i,l,a),k(i,t,a),R(t,c[0]),t.focus(),f||(m=E(t,"input",c[4]),f=!0)},p(i,a){a&32&&n!==(n=i[5])&&p(e,"for",n),a&32&&o!==(o=i[5])&&p(t,"id",o),a&1&&t.value!==i[0]&&R(t,i[0])},d(i){i&&v(e),i&&v(l),i&&v(t),f=!1,m()}}}function U(c){let e,s,n,l,t,o,f,m;const i=[O,K],a=[];function b(u,r){return u[2]?0:1}return e=b(c),s=a[e]=i[e](c),{c(){s.c(),n=g(),l=_("div"),t=_("a"),t.textContent="Back to login",p(t,"href","/login"),p(t,"class","link-hint"),p(l,"class","content txt-center")},m(u,r){a[e].m(u,r),k(u,n,r),k(u,l,r),d(l,t),o=!0,f||(m=A(B.call(null,t)),f=!0)},p(u,r){let $=e;e=b(u),e===$?a[e].p(u,r):(N(),y(a[$],1,1,()=>{a[$]=null}),D(),s=a[e],s?s.p(u,r):(s=a[e]=i[e](u),s.c()),w(s,1),s.m(n.parentNode,n))},i(u){o||(w(s),o=!0)},o(u){y(s),o=!1},d(u){a[e].d(u),u&&v(n),u&&v(l),f=!1,m()}}}function V(c){let e,s;return e=new z({props:{$$slots:{default:[U]},$$scope:{ctx:c}}}),{c(){H(e.$$.fragment)},m(n,l){L(e,n,l),s=!0},p(n,[l]){const t={};l&71&&(t.$$scope={dirty:l,ctx:n}),e.$set(t)},i(n){s||(w(e.$$.fragment,n),s=!0)},o(n){y(e.$$.fragment,n),s=!1},d(n){S(e,n)}}}function W(c,e,s){let n="",l=!1,t=!1;async function o(){if(!l){s(1,l=!0);try{await C.admins.requestPasswordReset(n),s(2,t=!0)}catch(m){C.errorResponseHandler(m)}s(1,l=!1)}}function f(){n=this.value,s(0,n)}return[n,l,t,o,f]}class Y extends M{constructor(e){super(),T(this,e,W,V,j,{})}}export{Y as default};
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
import{S as J,i as M,s as N,F as R,c as T,m as L,t as v,a as y,d as z,C as U,E as W,g as _,k as Y,n as j,o as b,_ as A,p as B,q as D,e as m,w as C,b as h,f as d,r as F,h as k,u as q,v as G,y as E,x as I,z as H}from"./index.c92519c5.js";function K(r){let e,t,s,l,n,o,c,a,i,u,g,$,p=r[3]&&S(r);return o=new D({props:{class:"form-field required",name:"password",$$slots:{default:[Q,({uniqueId:f})=>({8:f}),({uniqueId:f})=>f?256:0]},$$scope:{ctx:r}}}),{c(){e=m("form"),t=m("div"),s=m("h5"),l=C(`Type your password to confirm changing your email address
|
||||
`),p&&p.c(),n=h(),T(o.$$.fragment),c=h(),a=m("button"),i=m("span"),i.textContent="Confirm new email",d(t,"class","content txt-center m-b-base"),d(i,"class","txt"),d(a,"type","submit"),d(a,"class","btn btn-lg btn-block"),a.disabled=r[1],F(a,"btn-loading",r[1])},m(f,w){_(f,e,w),k(e,t),k(t,s),k(s,l),p&&p.m(s,null),k(e,n),L(o,e,null),k(e,c),k(e,a),k(a,i),u=!0,g||($=q(e,"submit",G(r[4])),g=!0)},p(f,w){f[3]?p?p.p(f,w):(p=S(f),p.c(),p.m(s,null)):p&&(p.d(1),p=null);const P={};w&769&&(P.$$scope={dirty:w,ctx:f}),o.$set(P),(!u||w&2)&&(a.disabled=f[1]),w&2&&F(a,"btn-loading",f[1])},i(f){u||(v(o.$$.fragment,f),u=!0)},o(f){y(o.$$.fragment,f),u=!1},d(f){f&&b(e),p&&p.d(),z(o),g=!1,$()}}}function O(r){let e,t,s,l,n;return{c(){e=m("div"),e.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div>
|
||||
import{S as G,i as J,s as M,F as N,c as T,m as L,t as v,a as y,d as z,C as R,E as U,g as _,k as W,n as Y,o as b,G as j,p as A,q as B,e as m,w as C,b as h,f as d,r as F,h as k,u as q,v as D,y as E,x as I,z as H}from"./index.7e58656e.js";function K(r){let e,t,s,l,n,o,c,a,i,u,g,$,p=r[3]&&S(r);return o=new B({props:{class:"form-field required",name:"password",$$slots:{default:[Q,({uniqueId:f})=>({8:f}),({uniqueId:f})=>f?256:0]},$$scope:{ctx:r}}}),{c(){e=m("form"),t=m("div"),s=m("h5"),l=C(`Type your password to confirm changing your email address
|
||||
`),p&&p.c(),n=h(),T(o.$$.fragment),c=h(),a=m("button"),i=m("span"),i.textContent="Confirm new email",d(t,"class","content txt-center m-b-base"),d(i,"class","txt"),d(a,"type","submit"),d(a,"class","btn btn-lg btn-block"),a.disabled=r[1],F(a,"btn-loading",r[1])},m(f,w){_(f,e,w),k(e,t),k(t,s),k(s,l),p&&p.m(s,null),k(e,n),L(o,e,null),k(e,c),k(e,a),k(a,i),u=!0,g||($=q(e,"submit",D(r[4])),g=!0)},p(f,w){f[3]?p?p.p(f,w):(p=S(f),p.c(),p.m(s,null)):p&&(p.d(1),p=null);const P={};w&769&&(P.$$scope={dirty:w,ctx:f}),o.$set(P),(!u||w&2)&&(a.disabled=f[1]),(!u||w&2)&&F(a,"btn-loading",f[1])},i(f){u||(v(o.$$.fragment,f),u=!0)},o(f){y(o.$$.fragment,f),u=!1},d(f){f&&b(e),p&&p.d(),z(o),g=!1,$()}}}function O(r){let e,t,s,l,n;return{c(){e=m("div"),e.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div>
|
||||
<div class="content txt-bold"><p>Successfully changed the user email address.</p>
|
||||
<p>You can now sign in with your new email address.</p></div>`,t=h(),s=m("button"),s.textContent="Close",d(e,"class","alert alert-success"),d(s,"type","button"),d(s,"class","btn btn-secondary btn-block")},m(o,c){_(o,e,c),_(o,t,c),_(o,s,c),l||(n=q(s,"click",r[6]),l=!0)},p:E,i:E,o:E,d(o){o&&b(e),o&&b(t),o&&b(s),l=!1,n()}}}function S(r){let e,t,s;return{c(){e=C("to "),t=m("strong"),s=C(r[3]),d(t,"class","txt-nowrap")},m(l,n){_(l,e,n),_(l,t,n),k(t,s)},p(l,n){n&8&&I(s,l[3])},d(l){l&&b(e),l&&b(t)}}}function Q(r){let e,t,s,l,n,o,c,a;return{c(){e=m("label"),t=C("Password"),l=h(),n=m("input"),d(e,"for",s=r[8]),d(n,"type","password"),d(n,"id",o=r[8]),n.required=!0,n.autofocus=!0},m(i,u){_(i,e,u),k(e,t),_(i,l,u),_(i,n,u),H(n,r[0]),n.focus(),c||(a=q(n,"input",r[7]),c=!0)},p(i,u){u&256&&s!==(s=i[8])&&d(e,"for",s),u&256&&o!==(o=i[8])&&d(n,"id",o),u&1&&n.value!==i[0]&&H(n,i[0])},d(i){i&&b(e),i&&b(l),i&&b(n),c=!1,a()}}}function V(r){let e,t,s,l;const n=[O,K],o=[];function c(a,i){return a[2]?0:1}return e=c(r),t=o[e]=n[e](r),{c(){t.c(),s=W()},m(a,i){o[e].m(a,i),_(a,s,i),l=!0},p(a,i){let u=e;e=c(a),e===u?o[e].p(a,i):(Y(),y(o[u],1,1,()=>{o[u]=null}),j(),t=o[e],t?t.p(a,i):(t=o[e]=n[e](a),t.c()),v(t,1),t.m(s.parentNode,s))},i(a){l||(v(t),l=!0)},o(a){y(t),l=!1},d(a){o[e].d(a),a&&b(s)}}}function X(r){let e,t;return e=new R({props:{nobranding:!0,$$slots:{default:[V]},$$scope:{ctx:r}}}),{c(){T(e.$$.fragment)},m(s,l){L(e,s,l),t=!0},p(s,[l]){const n={};l&527&&(n.$$scope={dirty:l,ctx:s}),e.$set(n)},i(s){t||(v(e.$$.fragment,s),t=!0)},o(s){y(e.$$.fragment,s),t=!1},d(s){z(e,s)}}}function Z(r,e,t){let s,{params:l}=e,n="",o=!1,c=!1;async function a(){if(o)return;t(1,o=!0);const g=new A("../");try{await g.users.confirmEmailChange(l==null?void 0:l.token,n),t(2,c=!0)}catch($){B.errorResponseHandler($)}t(1,o=!1)}const i=()=>window.close();function u(){n=this.value,t(0,n)}return r.$$set=g=>{"params"in g&&t(5,l=g.params)},r.$$.update=()=>{r.$$.dirty&32&&t(3,s=U.getJWTPayload(l==null?void 0:l.token).newEmail||"")},[n,o,c,s,a,l,i,u]}class ee extends J{constructor(e){super(),M(this,e,Z,X,N,{params:5})}}export{ee as default};
|
||||
<p>You can now sign in with your new email address.</p></div>`,t=h(),s=m("button"),s.textContent="Close",d(e,"class","alert alert-success"),d(s,"type","button"),d(s,"class","btn btn-secondary btn-block")},m(o,c){_(o,e,c),_(o,t,c),_(o,s,c),l||(n=q(s,"click",r[6]),l=!0)},p:E,i:E,o:E,d(o){o&&b(e),o&&b(t),o&&b(s),l=!1,n()}}}function S(r){let e,t,s;return{c(){e=C("to "),t=m("strong"),s=C(r[3]),d(t,"class","txt-nowrap")},m(l,n){_(l,e,n),_(l,t,n),k(t,s)},p(l,n){n&8&&I(s,l[3])},d(l){l&&b(e),l&&b(t)}}}function Q(r){let e,t,s,l,n,o,c,a;return{c(){e=m("label"),t=C("Password"),l=h(),n=m("input"),d(e,"for",s=r[8]),d(n,"type","password"),d(n,"id",o=r[8]),n.required=!0,n.autofocus=!0},m(i,u){_(i,e,u),k(e,t),_(i,l,u),_(i,n,u),H(n,r[0]),n.focus(),c||(a=q(n,"input",r[7]),c=!0)},p(i,u){u&256&&s!==(s=i[8])&&d(e,"for",s),u&256&&o!==(o=i[8])&&d(n,"id",o),u&1&&n.value!==i[0]&&H(n,i[0])},d(i){i&&b(e),i&&b(l),i&&b(n),c=!1,a()}}}function V(r){let e,t,s,l;const n=[O,K],o=[];function c(a,i){return a[2]?0:1}return e=c(r),t=o[e]=n[e](r),{c(){t.c(),s=U()},m(a,i){o[e].m(a,i),_(a,s,i),l=!0},p(a,i){let u=e;e=c(a),e===u?o[e].p(a,i):(W(),y(o[u],1,1,()=>{o[u]=null}),Y(),t=o[e],t?t.p(a,i):(t=o[e]=n[e](a),t.c()),v(t,1),t.m(s.parentNode,s))},i(a){l||(v(t),l=!0)},o(a){y(t),l=!1},d(a){o[e].d(a),a&&b(s)}}}function X(r){let e,t;return e=new N({props:{nobranding:!0,$$slots:{default:[V]},$$scope:{ctx:r}}}),{c(){T(e.$$.fragment)},m(s,l){L(e,s,l),t=!0},p(s,[l]){const n={};l&527&&(n.$$scope={dirty:l,ctx:s}),e.$set(n)},i(s){t||(v(e.$$.fragment,s),t=!0)},o(s){y(e.$$.fragment,s),t=!1},d(s){z(e,s)}}}function Z(r,e,t){let s,{params:l}=e,n="",o=!1,c=!1;async function a(){if(o)return;t(1,o=!0);const g=new j("../");try{await g.users.confirmEmailChange(l==null?void 0:l.token,n),t(2,c=!0)}catch($){A.errorResponseHandler($)}t(1,o=!1)}const i=()=>window.close();function u(){n=this.value,t(0,n)}return r.$$set=g=>{"params"in g&&t(5,l=g.params)},r.$$.update=()=>{r.$$.dirty&32&&t(3,s=R.getJWTPayload(l==null?void 0:l.token).newEmail||"")},[n,o,c,s,a,l,i,u]}class ee extends G{constructor(e){super(),J(this,e,Z,X,M,{params:5})}}export{ee as default};
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
import{S as U,i as W,s as Y,F as j,c as H,m as N,t as P,a as q,d as L,C as A,E as B,g as _,k as D,n as G,o as m,_ as I,p as K,q as E,e as b,w as y,b as C,f as c,r as J,h as w,u as S,v as O,y as F,x as Q,z as R}from"./index.c92519c5.js";function V(i){let e,l,s,n,t,o,p,u,r,a,v,g,k,h,d=i[4]&&M(i);return o=new E({props:{class:"form-field required",name:"password",$$slots:{default:[Z,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:i}}}),u=new E({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[x,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:i}}}),{c(){e=b("form"),l=b("div"),s=b("h5"),n=y(`Reset your user password
|
||||
`),d&&d.c(),t=C(),H(o.$$.fragment),p=C(),H(u.$$.fragment),r=C(),a=b("button"),v=b("span"),v.textContent="Set new password",c(l,"class","content txt-center m-b-base"),c(v,"class","txt"),c(a,"type","submit"),c(a,"class","btn btn-lg btn-block"),a.disabled=i[2],J(a,"btn-loading",i[2])},m(f,$){_(f,e,$),w(e,l),w(l,s),w(s,n),d&&d.m(s,null),w(e,t),N(o,e,null),w(e,p),N(u,e,null),w(e,r),w(e,a),w(a,v),g=!0,k||(h=S(e,"submit",O(i[5])),k=!0)},p(f,$){f[4]?d?d.p(f,$):(d=M(f),d.c(),d.m(s,null)):d&&(d.d(1),d=null);const T={};$&3073&&(T.$$scope={dirty:$,ctx:f}),o.$set(T);const z={};$&3074&&(z.$$scope={dirty:$,ctx:f}),u.$set(z),(!g||$&4)&&(a.disabled=f[2]),$&4&&J(a,"btn-loading",f[2])},i(f){g||(P(o.$$.fragment,f),P(u.$$.fragment,f),g=!0)},o(f){q(o.$$.fragment,f),q(u.$$.fragment,f),g=!1},d(f){f&&m(e),d&&d.d(),L(o),L(u),k=!1,h()}}}function X(i){let e,l,s,n,t;return{c(){e=b("div"),e.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div>
|
||||
import{S as M,i as U,s as W,F as Y,c as H,m as N,t as P,a as q,d as L,C as j,E as A,g as _,k as B,n as D,o as m,G as I,p as K,q as E,e as b,w as y,b as C,f as c,r as G,h as w,u as S,v as O,y as F,x as Q,z as R}from"./index.7e58656e.js";function V(i){let e,l,s,n,t,o,p,u,r,a,v,k,g,h,d=i[4]&&J(i);return o=new E({props:{class:"form-field required",name:"password",$$slots:{default:[Z,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:i}}}),u=new E({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[x,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:i}}}),{c(){e=b("form"),l=b("div"),s=b("h5"),n=y(`Reset your user password
|
||||
`),d&&d.c(),t=C(),H(o.$$.fragment),p=C(),H(u.$$.fragment),r=C(),a=b("button"),v=b("span"),v.textContent="Set new password",c(l,"class","content txt-center m-b-base"),c(v,"class","txt"),c(a,"type","submit"),c(a,"class","btn btn-lg btn-block"),a.disabled=i[2],G(a,"btn-loading",i[2])},m(f,$){_(f,e,$),w(e,l),w(l,s),w(s,n),d&&d.m(s,null),w(e,t),N(o,e,null),w(e,p),N(u,e,null),w(e,r),w(e,a),w(a,v),k=!0,g||(h=S(e,"submit",O(i[5])),g=!0)},p(f,$){f[4]?d?d.p(f,$):(d=J(f),d.c(),d.m(s,null)):d&&(d.d(1),d=null);const T={};$&3073&&(T.$$scope={dirty:$,ctx:f}),o.$set(T);const z={};$&3074&&(z.$$scope={dirty:$,ctx:f}),u.$set(z),(!k||$&4)&&(a.disabled=f[2]),(!k||$&4)&&G(a,"btn-loading",f[2])},i(f){k||(P(o.$$.fragment,f),P(u.$$.fragment,f),k=!0)},o(f){q(o.$$.fragment,f),q(u.$$.fragment,f),k=!1},d(f){f&&m(e),d&&d.d(),L(o),L(u),g=!1,h()}}}function X(i){let e,l,s,n,t;return{c(){e=b("div"),e.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div>
|
||||
<div class="content txt-bold"><p>Successfully changed the user password.</p>
|
||||
<p>You can now sign in with your new password.</p></div>`,l=C(),s=b("button"),s.textContent="Close",c(e,"class","alert alert-success"),c(s,"type","button"),c(s,"class","btn btn-secondary btn-block")},m(o,p){_(o,e,p),_(o,l,p),_(o,s,p),n||(t=S(s,"click",i[7]),n=!0)},p:F,i:F,o:F,d(o){o&&m(e),o&&m(l),o&&m(s),n=!1,t()}}}function M(i){let e,l,s;return{c(){e=y("for "),l=b("strong"),s=y(i[4])},m(n,t){_(n,e,t),_(n,l,t),w(l,s)},p(n,t){t&16&&Q(s,n[4])},d(n){n&&m(e),n&&m(l)}}}function Z(i){let e,l,s,n,t,o,p,u;return{c(){e=b("label"),l=y("New password"),n=C(),t=b("input"),c(e,"for",s=i[10]),c(t,"type","password"),c(t,"id",o=i[10]),t.required=!0,t.autofocus=!0},m(r,a){_(r,e,a),w(e,l),_(r,n,a),_(r,t,a),R(t,i[0]),t.focus(),p||(u=S(t,"input",i[8]),p=!0)},p(r,a){a&1024&&s!==(s=r[10])&&c(e,"for",s),a&1024&&o!==(o=r[10])&&c(t,"id",o),a&1&&t.value!==r[0]&&R(t,r[0])},d(r){r&&m(e),r&&m(n),r&&m(t),p=!1,u()}}}function x(i){let e,l,s,n,t,o,p,u;return{c(){e=b("label"),l=y("New password confirm"),n=C(),t=b("input"),c(e,"for",s=i[10]),c(t,"type","password"),c(t,"id",o=i[10]),t.required=!0},m(r,a){_(r,e,a),w(e,l),_(r,n,a),_(r,t,a),R(t,i[1]),p||(u=S(t,"input",i[9]),p=!0)},p(r,a){a&1024&&s!==(s=r[10])&&c(e,"for",s),a&1024&&o!==(o=r[10])&&c(t,"id",o),a&2&&t.value!==r[1]&&R(t,r[1])},d(r){r&&m(e),r&&m(n),r&&m(t),p=!1,u()}}}function ee(i){let e,l,s,n;const t=[X,V],o=[];function p(u,r){return u[3]?0:1}return e=p(i),l=o[e]=t[e](i),{c(){l.c(),s=B()},m(u,r){o[e].m(u,r),_(u,s,r),n=!0},p(u,r){let a=e;e=p(u),e===a?o[e].p(u,r):(D(),q(o[a],1,1,()=>{o[a]=null}),G(),l=o[e],l?l.p(u,r):(l=o[e]=t[e](u),l.c()),P(l,1),l.m(s.parentNode,s))},i(u){n||(P(l),n=!0)},o(u){q(l),n=!1},d(u){o[e].d(u),u&&m(s)}}}function te(i){let e,l;return e=new j({props:{nobranding:!0,$$slots:{default:[ee]},$$scope:{ctx:i}}}),{c(){H(e.$$.fragment)},m(s,n){N(e,s,n),l=!0},p(s,[n]){const t={};n&2079&&(t.$$scope={dirty:n,ctx:s}),e.$set(t)},i(s){l||(P(e.$$.fragment,s),l=!0)},o(s){q(e.$$.fragment,s),l=!1},d(s){L(e,s)}}}function se(i,e,l){let s,{params:n}=e,t="",o="",p=!1,u=!1;async function r(){if(p)return;l(2,p=!0);const k=new I("../");try{await k.users.confirmPasswordReset(n==null?void 0:n.token,t,o),l(3,u=!0)}catch(h){K.errorResponseHandler(h)}l(2,p=!1)}const a=()=>window.close();function v(){t=this.value,l(0,t)}function g(){o=this.value,l(1,o)}return i.$$set=k=>{"params"in k&&l(6,n=k.params)},i.$$.update=()=>{i.$$.dirty&64&&l(4,s=A.getJWTPayload(n==null?void 0:n.token).email||"")},[t,o,p,u,s,r,n,a,v,g]}class ne extends U{constructor(e){super(),W(this,e,se,te,Y,{params:6})}}export{ne as default};
|
||||
<p>You can now sign in with your new password.</p></div>`,l=C(),s=b("button"),s.textContent="Close",c(e,"class","alert alert-success"),c(s,"type","button"),c(s,"class","btn btn-secondary btn-block")},m(o,p){_(o,e,p),_(o,l,p),_(o,s,p),n||(t=S(s,"click",i[7]),n=!0)},p:F,i:F,o:F,d(o){o&&m(e),o&&m(l),o&&m(s),n=!1,t()}}}function J(i){let e,l,s;return{c(){e=y("for "),l=b("strong"),s=y(i[4])},m(n,t){_(n,e,t),_(n,l,t),w(l,s)},p(n,t){t&16&&Q(s,n[4])},d(n){n&&m(e),n&&m(l)}}}function Z(i){let e,l,s,n,t,o,p,u;return{c(){e=b("label"),l=y("New password"),n=C(),t=b("input"),c(e,"for",s=i[10]),c(t,"type","password"),c(t,"id",o=i[10]),t.required=!0,t.autofocus=!0},m(r,a){_(r,e,a),w(e,l),_(r,n,a),_(r,t,a),R(t,i[0]),t.focus(),p||(u=S(t,"input",i[8]),p=!0)},p(r,a){a&1024&&s!==(s=r[10])&&c(e,"for",s),a&1024&&o!==(o=r[10])&&c(t,"id",o),a&1&&t.value!==r[0]&&R(t,r[0])},d(r){r&&m(e),r&&m(n),r&&m(t),p=!1,u()}}}function x(i){let e,l,s,n,t,o,p,u;return{c(){e=b("label"),l=y("New password confirm"),n=C(),t=b("input"),c(e,"for",s=i[10]),c(t,"type","password"),c(t,"id",o=i[10]),t.required=!0},m(r,a){_(r,e,a),w(e,l),_(r,n,a),_(r,t,a),R(t,i[1]),p||(u=S(t,"input",i[9]),p=!0)},p(r,a){a&1024&&s!==(s=r[10])&&c(e,"for",s),a&1024&&o!==(o=r[10])&&c(t,"id",o),a&2&&t.value!==r[1]&&R(t,r[1])},d(r){r&&m(e),r&&m(n),r&&m(t),p=!1,u()}}}function ee(i){let e,l,s,n;const t=[X,V],o=[];function p(u,r){return u[3]?0:1}return e=p(i),l=o[e]=t[e](i),{c(){l.c(),s=A()},m(u,r){o[e].m(u,r),_(u,s,r),n=!0},p(u,r){let a=e;e=p(u),e===a?o[e].p(u,r):(B(),q(o[a],1,1,()=>{o[a]=null}),D(),l=o[e],l?l.p(u,r):(l=o[e]=t[e](u),l.c()),P(l,1),l.m(s.parentNode,s))},i(u){n||(P(l),n=!0)},o(u){q(l),n=!1},d(u){o[e].d(u),u&&m(s)}}}function te(i){let e,l;return e=new Y({props:{nobranding:!0,$$slots:{default:[ee]},$$scope:{ctx:i}}}),{c(){H(e.$$.fragment)},m(s,n){N(e,s,n),l=!0},p(s,[n]){const t={};n&2079&&(t.$$scope={dirty:n,ctx:s}),e.$set(t)},i(s){l||(P(e.$$.fragment,s),l=!0)},o(s){q(e.$$.fragment,s),l=!1},d(s){L(e,s)}}}function se(i,e,l){let s,{params:n}=e,t="",o="",p=!1,u=!1;async function r(){if(p)return;l(2,p=!0);const g=new I("../");try{await g.users.confirmPasswordReset(n==null?void 0:n.token,t,o),l(3,u=!0)}catch(h){K.errorResponseHandler(h)}l(2,p=!1)}const a=()=>window.close();function v(){t=this.value,l(0,t)}function k(){o=this.value,l(1,o)}return i.$$set=g=>{"params"in g&&l(6,n=g.params)},i.$$.update=()=>{i.$$.dirty&64&&l(4,s=j.getJWTPayload(n==null?void 0:n.token).email||"")},[t,o,p,u,s,r,n,a,v,k]}class ne extends M{constructor(e){super(),U(this,e,se,te,W,{params:6})}}export{ne as default};
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
import{S as k,i as v,s as y,F as w,c as x,m as C,t as g,a as $,d as L,_ as H,E as M,g as r,o as a,e as u,b as m,f,u as _,y as p}from"./index.c92519c5.js";function P(c){let t,s,e,n,i;return{c(){t=u("div"),t.innerHTML=`<div class="icon"><i class="ri-error-warning-line"></i></div>
|
||||
import{S as k,i as v,s as y,F as w,c as x,m as C,t as g,a as $,d as L,G as H,E as M,g as r,o as a,e as u,b as m,f,u as _,y as p}from"./index.7e58656e.js";function P(c){let t,s,e,n,i;return{c(){t=u("div"),t.innerHTML=`<div class="icon"><i class="ri-error-warning-line"></i></div>
|
||||
<div class="content txt-bold"><p>Invalid or expired verification token.</p></div>`,s=m(),e=u("button"),e.textContent="Close",f(t,"class","alert alert-danger"),f(e,"type","button"),f(e,"class","btn btn-secondary btn-block")},m(l,o){r(l,t,o),r(l,s,o),r(l,e,o),n||(i=_(e,"click",c[4]),n=!0)},p,d(l){l&&a(t),l&&a(s),l&&a(e),n=!1,i()}}}function S(c){let t,s,e,n,i;return{c(){t=u("div"),t.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div>
|
||||
<div class="content txt-bold"><p>Successfully verified email address.</p></div>`,s=m(),e=u("button"),e.textContent="Close",f(t,"class","alert alert-success"),f(e,"type","button"),f(e,"class","btn btn-secondary btn-block")},m(l,o){r(l,t,o),r(l,s,o),r(l,e,o),n||(i=_(e,"click",c[3]),n=!0)},p,d(l){l&&a(t),l&&a(s),l&&a(e),n=!1,i()}}}function T(c){let t;return{c(){t=u("div"),t.innerHTML='<div class="loader loader-lg"><em>Please wait...</em></div>',f(t,"class","txt-center")},m(s,e){r(s,t,e)},p,d(s){s&&a(t)}}}function F(c){let t;function s(i,l){return i[1]?T:i[0]?S:P}let e=s(c),n=e(c);return{c(){n.c(),t=M()},m(i,l){n.m(i,l),r(i,t,l)},p(i,l){e===(e=s(i))&&n?n.p(i,l):(n.d(1),n=e(i),n&&(n.c(),n.m(t.parentNode,t)))},d(i){n.d(i),i&&a(t)}}}function V(c){let t,s;return t=new w({props:{nobranding:!0,$$slots:{default:[F]},$$scope:{ctx:c}}}),{c(){x(t.$$.fragment)},m(e,n){C(t,e,n),s=!0},p(e,[n]){const i={};n&67&&(i.$$scope={dirty:n,ctx:e}),t.$set(i)},i(e){s||(g(t.$$.fragment,e),s=!0)},o(e){$(t.$$.fragment,e),s=!1},d(e){L(t,e)}}}function q(c,t,s){let{params:e}=t,n=!1,i=!1;l();async function l(){s(1,i=!0);const d=new H("../");try{await d.users.confirmVerification(e==null?void 0:e.token),s(0,n=!0)}catch{s(0,n=!1)}s(1,i=!1)}const o=()=>window.close(),b=()=>window.close();return c.$$set=d=>{"params"in d&&s(2,e=d.params)},[n,i,e,o,b]}class N extends k{constructor(t){super(),v(this,t,q,V,y,{params:2})}}export{N as default};
|
||||
<div class="content txt-bold"><p>Successfully verified email address.</p></div>`,s=m(),e=u("button"),e.textContent="Close",f(t,"class","alert alert-success"),f(e,"type","button"),f(e,"class","btn btn-secondary btn-block")},m(l,o){r(l,t,o),r(l,s,o),r(l,e,o),n||(i=_(e,"click",c[3]),n=!0)},p,d(l){l&&a(t),l&&a(s),l&&a(e),n=!1,i()}}}function T(c){let t;return{c(){t=u("div"),t.innerHTML='<div class="loader loader-lg"><em>Please wait...</em></div>',f(t,"class","txt-center")},m(s,e){r(s,t,e)},p,d(s){s&&a(t)}}}function F(c){let t;function s(i,l){return i[1]?T:i[0]?S:P}let e=s(c),n=e(c);return{c(){n.c(),t=M()},m(i,l){n.m(i,l),r(i,t,l)},p(i,l){e===(e=s(i))&&n?n.p(i,l):(n.d(1),n=e(i),n&&(n.c(),n.m(t.parentNode,t)))},d(i){n.d(i),i&&a(t)}}}function V(c){let t,s;return t=new w({props:{nobranding:!0,$$slots:{default:[F]},$$scope:{ctx:c}}}),{c(){x(t.$$.fragment)},m(e,n){C(t,e,n),s=!0},p(e,[n]){const i={};n&67&&(i.$$scope={dirty:n,ctx:e}),t.$set(i)},i(e){s||(g(t.$$.fragment,e),s=!0)},o(e){$(t.$$.fragment,e),s=!1},d(e){L(t,e)}}}function q(c,t,s){let{params:e}=t,n=!1,i=!1;l();async function l(){s(1,i=!0);const d=new H("../");try{await d.users.confirmVerification(e==null?void 0:e.token),s(0,n=!0)}catch{s(0,n=!1)}s(1,i=!1)}const o=()=>window.close(),b=()=>window.close();return c.$$set=d=>{"params"in d&&s(2,e=d.params)},[n,i,e,o,b]}class I extends k{constructor(t){super(),v(this,t,q,V,y,{params:2})}}export{I as default};
|
||||
File diff suppressed because one or more lines are too long
Vendored
+661
File diff suppressed because one or more lines are too long
Vendored
+12
File diff suppressed because one or more lines are too long
Vendored
-12
File diff suppressed because one or more lines are too long
Vendored
-661
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -24,8 +24,8 @@
|
||||
window.Prism = window.Prism || {};
|
||||
window.Prism.manual = true;
|
||||
</script>
|
||||
<script type="module" crossorigin src="./assets/index.c92519c5.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index.8992dfa3.css">
|
||||
<script type="module" crossorigin src="./assets/index.7e58656e.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index.26987507.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
Generated
+320
-293
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -25,7 +25,7 @@
|
||||
"chart.js": "^3.7.1",
|
||||
"chartjs-adapter-luxon": "^1.1.0",
|
||||
"luxon": "^2.3.2",
|
||||
"pocketbase": "^0.5.0",
|
||||
"pocketbase": "^0.7.0",
|
||||
"prismjs": "^1.28.0",
|
||||
"sass": "^1.45.0",
|
||||
"svelte": "^3.44.0",
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
submitShortcut,
|
||||
...closeBracketsKeymap,
|
||||
...defaultKeymap,
|
||||
...searchKeymap,
|
||||
searchKeymap.find((item) => item.key === "Mod-d"),
|
||||
...historyKeymap,
|
||||
...completionKeymap,
|
||||
]),
|
||||
|
||||
@@ -270,7 +270,6 @@
|
||||
}
|
||||
|
||||
const skipFields = [
|
||||
"@request.user.profile.id",
|
||||
"@request.user.profile.userId",
|
||||
"@request.user.profile.created",
|
||||
"@request.user.profile.updated",
|
||||
@@ -379,7 +378,7 @@
|
||||
submitShortcut,
|
||||
...closeBracketsKeymap,
|
||||
...defaultKeymap,
|
||||
...searchKeymap,
|
||||
searchKeymap.find((item) => item.key === "Mod-d"),
|
||||
...historyKeymap,
|
||||
...completionKeymap,
|
||||
]),
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<script>
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import tooltip from "@/actions/tooltip";
|
||||
|
||||
export let date = "";
|
||||
|
||||
// note: manual trim the ms without converting to DateTime
|
||||
// to help improving the rendering performance in large data sets
|
||||
let shortDate = date.length > 19 ? date.substring(0, 19) : date;
|
||||
</script>
|
||||
|
||||
{#if date}
|
||||
<span class="txt" use:tooltip={CommonHelper.formatToLocalDate(date) + " Local"}>
|
||||
{CommonHelper.formatToUTCDate(date)} UTC
|
||||
</span>
|
||||
<span class="txt">{shortDate} UTC</span>
|
||||
{:else}
|
||||
<span class="txt txt-hint">N/A</span>
|
||||
{/if}
|
||||
|
||||
@@ -25,8 +25,9 @@
|
||||
|
||||
let newSelected = [];
|
||||
|
||||
for (let item of items) {
|
||||
if (CommonHelper.inArray(newKeyOfSelected, item[selectionKey])) {
|
||||
for (let v of newKeyOfSelected) {
|
||||
const item = CommonHelper.findByKey(items, selectionKey, v);
|
||||
if (item) {
|
||||
newSelected.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user