@x47base/pocketbase-addon 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/.dockerignore +9 -0
  2. package/Dockerfile +26 -0
  3. package/FEATURES.md +32 -0
  4. package/LICENSE.md +17 -0
  5. package/MIGRATION.md +49 -0
  6. package/NOTICE.md +5 -0
  7. package/README.md +26 -0
  8. package/adapter.go +31 -0
  9. package/admin/register.go +60 -0
  10. package/admin/register_test.go +37 -0
  11. package/backups/backup_encryption_test.go +82 -0
  12. package/backups/encryption.go +138 -0
  13. package/backups/integration_test.go +51 -0
  14. package/backups/register.go +120 -0
  15. package/backups/restore.go +119 -0
  16. package/backups/s3_test.go +52 -0
  17. package/backups/swap.go +53 -0
  18. package/backups/swap_test.go +75 -0
  19. package/backups/upload.go +52 -0
  20. package/bin/pocketbase-extension.mjs +25 -0
  21. package/cmd/edge/main.go +123 -0
  22. package/cmd/import-fork/main.go +37 -0
  23. package/cmd/loadtest/main.go +61 -0
  24. package/cmd/loadtest/sandbox.go +73 -0
  25. package/cmd/loadtest/sandbox_test.go +20 -0
  26. package/cmd/pocketbase/main.go +78 -0
  27. package/deploy/README.md +148 -0
  28. package/deploy/app/hooks/README.md +2 -0
  29. package/deploy/app/migrations/1789000000_notes.js +16 -0
  30. package/deploy/app/public/README.md +2 -0
  31. package/deploy/compose.secrets.yaml +8 -0
  32. package/deploy/compose.yaml +68 -0
  33. package/deploy/edge.json +13 -0
  34. package/edge/gateway.go +295 -0
  35. package/edge/gateway_test.go +296 -0
  36. package/edge/openapi.json +1 -0
  37. package/edge/policy.go +150 -0
  38. package/features/collection_singleton.go +13 -0
  39. package/features/collection_singleton_test.go +46 -0
  40. package/features/dimensions_test.go +65 -0
  41. package/features/duplicate.go +178 -0
  42. package/features/duplicate_test.go +128 -0
  43. package/features/field_color.go +46 -0
  44. package/features/field_date_only.go +39 -0
  45. package/features/field_json_schema.go +92 -0
  46. package/features/field_scalar_extensions_test.go +66 -0
  47. package/features/files.go +36 -0
  48. package/features/filter_has_any_test.go +81 -0
  49. package/features/generate_test.go +72 -0
  50. package/features/has_any_visibility_test.go +62 -0
  51. package/features/json.go +50 -0
  52. package/features/membership.go +64 -0
  53. package/features/register.go +45 -0
  54. package/features/schema_test.go +58 -0
  55. package/features/ui/main.js +133 -0
  56. package/features/ui/settings.js +31 -0
  57. package/go.mod +54 -0
  58. package/go.sum +159 -0
  59. package/internal/archive/create.go +91 -0
  60. package/internal/archive/create_test.go +125 -0
  61. package/internal/archive/extract.go +99 -0
  62. package/internal/archive/extract_test.go +88 -0
  63. package/jsvm/binds.go +1273 -0
  64. package/jsvm/binds_app_reset_test.go +314 -0
  65. package/jsvm/binds_test.go +1870 -0
  66. package/jsvm/form_data.go +149 -0
  67. package/jsvm/form_data_test.go +225 -0
  68. package/jsvm/internal/types/generated/embed.go +6 -0
  69. package/jsvm/internal/types/generated/types.d.ts +24820 -0
  70. package/jsvm/internal/types/types.go +1408 -0
  71. package/jsvm/jsvm.go +587 -0
  72. package/jsvm/mapper.go +67 -0
  73. package/jsvm/mapper_test.go +42 -0
  74. package/jsvm/pool.go +73 -0
  75. package/jsvm/program_source_test.go +24 -0
  76. package/loadtest/loadtest.go +202 -0
  77. package/loadtest/loadtest_test.go +84 -0
  78. package/localization/README.md +23 -0
  79. package/localization/catalogue.json +483 -0
  80. package/localization/localization.go +94 -0
  81. package/localization/localization_test.go +21 -0
  82. package/mail/register.go +80 -0
  83. package/mail/register_test.go +49 -0
  84. package/mail/resolve.go +91 -0
  85. package/migration/import.go +96 -0
  86. package/migration/import_test.go +58 -0
  87. package/otp/otp.go +56 -0
  88. package/otp/otp_test.go +56 -0
  89. package/package.json +51 -0
  90. package/scripts/check-edge.py +42 -0
  91. package/scripts/check.sh +11 -0
  92. package/scripts/sync-jsvm-types.sh +10 -0
  93. package/security/README.md +94 -0
  94. package/security/assurance_test.go +149 -0
  95. package/security/compatibility_test.go +128 -0
  96. package/security/config.go +78 -0
  97. package/security/dashboard_test.go +103 -0
  98. package/security/management.go +169 -0
  99. package/security/openapi.json +508 -0
  100. package/security/review.go +34 -0
  101. package/security/security.go +503 -0
  102. package/security/security_test.go +146 -0
  103. package/security/state.go +116 -0
  104. package/security/ui/dashboard.css +4 -0
  105. package/security/ui/dashboard.js +83 -0
  106. package/security/ui/main.js +15 -0
  107. package/security/ui/model.js +32 -0
  108. package/security/ui/model.test.mjs +25 -0
  109. package/security/ui/registration.test.mjs +10 -0
  110. package/settings/env_test.go +41 -0
  111. package/settings/openapi.json +193 -0
  112. package/settings/settings.go +155 -0
  113. package/settings/settings_test.go +31 -0
  114. package/watcher/watcher.go +192 -0
  115. package/watcher/watcher_test.go +200 -0
  116. package/web/static.go +99 -0
  117. package/web/static_test.go +48 -0
@@ -0,0 +1,149 @@
1
+ package jsvm
2
+
3
+ import (
4
+ "bytes"
5
+ "io"
6
+ "mime/multipart"
7
+
8
+ "github.com/pocketbase/pocketbase/tools/filesystem"
9
+ "github.com/spf13/cast"
10
+ )
11
+
12
+ // FormData represents an interface similar to the browser's [FormData].
13
+ //
14
+ // The value of each FormData entry must be a string or [*filesystem.File] instance.
15
+ //
16
+ // It is intended to be used together with the JSVM `$http.send` when
17
+ // sending multipart/form-data requests.
18
+ //
19
+ // [FormData]: https://developer.mozilla.org/en-US/docs/Web/API/FormData.
20
+ type FormData map[string][]any
21
+
22
+ // Append appends a new value onto an existing key inside the current FormData,
23
+ // or adds the key if it does not already exist.
24
+ func (data FormData) Append(key string, value any) {
25
+ data[key] = append(data[key], value)
26
+ }
27
+
28
+ // Set sets a new value for an existing key inside the current FormData,
29
+ // or adds the key/value if it does not already exist.
30
+ func (data FormData) Set(key string, value any) {
31
+ data[key] = []any{value}
32
+ }
33
+
34
+ // Delete deletes a key and its value(s) from the current FormData.
35
+ func (data FormData) Delete(key string) {
36
+ delete(data, key)
37
+ }
38
+
39
+ // Get returns the first value associated with a given key from
40
+ // within the current FormData.
41
+ //
42
+ // If you expect multiple values and want all of them,
43
+ // use the [FormData.GetAll] method instead.
44
+ func (data FormData) Get(key string) any {
45
+ values, ok := data[key]
46
+ if !ok || len(values) == 0 {
47
+ return nil
48
+ }
49
+
50
+ return values[0]
51
+ }
52
+
53
+ // GetAll returns all the values associated with a given key
54
+ // from within the current FormData.
55
+ func (data FormData) GetAll(key string) []any {
56
+ values, ok := data[key]
57
+ if !ok {
58
+ return nil
59
+ }
60
+
61
+ return values
62
+ }
63
+
64
+ // Has returns whether a FormData object contains a certain key.
65
+ func (data FormData) Has(key string) bool {
66
+ values, ok := data[key]
67
+
68
+ return ok && len(values) > 0
69
+ }
70
+
71
+ // Keys returns all keys contained in the current FormData.
72
+ func (data FormData) Keys() []string {
73
+ result := make([]string, 0, len(data))
74
+
75
+ for k := range data {
76
+ result = append(result, k)
77
+ }
78
+
79
+ return result
80
+ }
81
+
82
+ // Keys returns all values contained in the current FormData.
83
+ func (data FormData) Values() []any {
84
+ result := make([]any, 0, len(data))
85
+
86
+ for _, values := range data {
87
+ result = append(result, values...)
88
+ }
89
+
90
+ return result
91
+ }
92
+
93
+ // Entries returns a [key, value] slice pair for each FormData entry.
94
+ func (data FormData) Entries() [][]any {
95
+ result := make([][]any, 0, len(data))
96
+
97
+ for k, values := range data {
98
+ for _, v := range values {
99
+ result = append(result, []any{k, v})
100
+ }
101
+ }
102
+
103
+ return result
104
+ }
105
+
106
+ // toMultipart converts the current FormData entries into multipart encoded data.
107
+ func (data FormData) toMultipart() (*bytes.Buffer, *multipart.Writer, error) {
108
+ body := new(bytes.Buffer)
109
+
110
+ mp := multipart.NewWriter(body)
111
+ defer mp.Close()
112
+
113
+ for k, values := range data {
114
+ for _, rawValue := range values {
115
+ switch v := rawValue.(type) {
116
+ case *filesystem.File:
117
+ err := func() error {
118
+ mpw, err := mp.CreateFormFile(k, v.OriginalName)
119
+ if err != nil {
120
+ return err
121
+ }
122
+
123
+ file, err := v.Reader.Open()
124
+ if err != nil {
125
+ return err
126
+ }
127
+ defer file.Close()
128
+
129
+ _, err = io.Copy(mpw, file)
130
+ if err != nil {
131
+ return err
132
+ }
133
+
134
+ return nil
135
+ }()
136
+ if err != nil {
137
+ return nil, nil, err
138
+ }
139
+ default:
140
+ err := mp.WriteField(k, cast.ToString(v))
141
+ if err != nil {
142
+ return nil, nil, err
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ return body, mp, nil
149
+ }
@@ -0,0 +1,225 @@
1
+ package jsvm
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json/v2"
6
+ "strings"
7
+ "testing"
8
+
9
+ "github.com/pocketbase/pocketbase/tools/filesystem"
10
+ "github.com/pocketbase/pocketbase/tools/list"
11
+ )
12
+
13
+ func TestFormDataAppendAndSet(t *testing.T) {
14
+ t.Parallel()
15
+
16
+ data := FormData{}
17
+
18
+ data.Append("a", 1)
19
+ data.Append("a", 2)
20
+
21
+ data.Append("b", 3)
22
+ data.Append("b", 4)
23
+ data.Set("b", 5) // should overwrite the previous 2 calls
24
+
25
+ data.Set("c", 6)
26
+ data.Set("c", 7)
27
+
28
+ if len(data["a"]) != 2 {
29
+ t.Fatalf("Expected 2 'a' values, got %v", data["a"])
30
+ }
31
+ if data["a"][0] != 1 || data["a"][1] != 2 {
32
+ t.Fatalf("Expected 1 and 2 'a' key values, got %v", data["a"])
33
+ }
34
+
35
+ if len(data["b"]) != 1 {
36
+ t.Fatalf("Expected 1 'b' values, got %v", data["b"])
37
+ }
38
+ if data["b"][0] != 5 {
39
+ t.Fatalf("Expected 5 as 'b' key value, got %v", data["b"])
40
+ }
41
+
42
+ if len(data["c"]) != 1 {
43
+ t.Fatalf("Expected 1 'c' values, got %v", data["c"])
44
+ }
45
+ if data["c"][0] != 7 {
46
+ t.Fatalf("Expected 7 as 'c' key value, got %v", data["c"])
47
+ }
48
+ }
49
+
50
+ func TestFormDataDelete(t *testing.T) {
51
+ t.Parallel()
52
+
53
+ data := FormData{}
54
+ data.Append("a", 1)
55
+ data.Append("a", 2)
56
+ data.Append("b", 3)
57
+
58
+ data.Delete("missing") // should do nothing
59
+ data.Delete("a")
60
+
61
+ if len(data) != 1 {
62
+ t.Fatalf("Expected exactly 1 data remaining key, got %v", data)
63
+ }
64
+
65
+ if data["b"][0] != 3 {
66
+ t.Fatalf("Expected 3 as 'b' key value, got %v", data["b"])
67
+ }
68
+ }
69
+
70
+ func TestFormDataGet(t *testing.T) {
71
+ t.Parallel()
72
+
73
+ data := FormData{}
74
+ data.Append("a", 1)
75
+ data.Append("a", 2)
76
+
77
+ if v := data.Get("missing"); v != nil {
78
+ t.Fatalf("Expected %v for key 'missing', got %v", nil, v)
79
+ }
80
+
81
+ if v := data.Get("a"); v != 1 {
82
+ t.Fatalf("Expected %v for key 'a', got %v", 1, v)
83
+ }
84
+ }
85
+
86
+ func TestFormDataGetAll(t *testing.T) {
87
+ t.Parallel()
88
+
89
+ data := FormData{}
90
+ data.Append("a", 1)
91
+ data.Append("a", 2)
92
+
93
+ if v := data.GetAll("missing"); v != nil {
94
+ t.Fatalf("Expected %v for key 'a', got %v", nil, v)
95
+ }
96
+
97
+ values := data.GetAll("a")
98
+ if len(values) != 2 || values[0] != 1 || values[1] != 2 {
99
+ t.Fatalf("Expected 1 and 2 values, got %v", values)
100
+ }
101
+ }
102
+
103
+ func TestFormDataHas(t *testing.T) {
104
+ t.Parallel()
105
+
106
+ data := FormData{}
107
+ data.Append("a", 1)
108
+
109
+ if v := data.Has("missing"); v {
110
+ t.Fatalf("Expected key 'missing' to not exist: %v", v)
111
+ }
112
+
113
+ if v := data.Has("a"); !v {
114
+ t.Fatalf("Expected key 'a' to exist: %v", v)
115
+ }
116
+ }
117
+
118
+ func TestFormDataKeys(t *testing.T) {
119
+ t.Parallel()
120
+
121
+ data := FormData{}
122
+ data.Append("a", 1)
123
+ data.Append("b", 1)
124
+ data.Append("c", 1)
125
+ data.Append("a", 1)
126
+
127
+ keys := data.Keys()
128
+
129
+ expectedKeys := []string{"a", "b", "c"}
130
+
131
+ for _, expected := range expectedKeys {
132
+ if !list.ExistInSlice(expected, keys) {
133
+ t.Fatalf("Expected key %s to exists in %v", expected, keys)
134
+ }
135
+ }
136
+ }
137
+
138
+ func TestFormDataValues(t *testing.T) {
139
+ t.Parallel()
140
+
141
+ data := FormData{}
142
+ data.Append("a", 1)
143
+ data.Append("b", 2)
144
+ data.Append("c", 3)
145
+ data.Append("a", 4)
146
+
147
+ values := data.Values()
148
+
149
+ expectedKeys := []any{1, 2, 3, 4}
150
+
151
+ for _, expected := range expectedKeys {
152
+ if !list.ExistInSlice(expected, values) {
153
+ t.Fatalf("Expected key %s to exists in %v", expected, values)
154
+ }
155
+ }
156
+ }
157
+
158
+ func TestFormDataEntries(t *testing.T) {
159
+ t.Parallel()
160
+
161
+ data := FormData{}
162
+ data.Append("a", 1)
163
+ data.Append("b", 2)
164
+ data.Append("c", 3)
165
+ data.Append("a", 4)
166
+
167
+ entries := data.Entries()
168
+
169
+ rawEntries, err := json.Marshal(entries, json.Deterministic(true))
170
+ if err != nil {
171
+ t.Fatal(err)
172
+ }
173
+
174
+ if len(entries) != 4 {
175
+ t.Fatalf("Expected 4 entries")
176
+ }
177
+
178
+ expectedEntries := []string{`["a",1]`, `["a",4]`, `["b",2]`, `["c",3]`}
179
+ for _, expected := range expectedEntries {
180
+ if !bytes.Contains(rawEntries, []byte(expected)) {
181
+ t.Fatalf("Expected entry %s to exists in %s", expected, rawEntries)
182
+ }
183
+ }
184
+ }
185
+
186
+ func TestFormDataToMultipart(t *testing.T) {
187
+ t.Parallel()
188
+
189
+ f, err := filesystem.NewFileFromBytes([]byte("abc"), "test")
190
+ if err != nil {
191
+ t.Fatal(err)
192
+ }
193
+
194
+ data := FormData{}
195
+ data.Append("a", 1) // should be casted
196
+ data.Append("b", "test1")
197
+ data.Append("b", "test2")
198
+ data.Append("c", f)
199
+
200
+ body, mp, err := data.toMultipart()
201
+ if err != nil {
202
+ t.Fatal(err)
203
+ }
204
+ bodyStr := body.String()
205
+
206
+ // content type checks
207
+ contentType := mp.FormDataContentType()
208
+ expectedContentType := "multipart/form-data; boundary="
209
+ if !strings.Contains(contentType, expectedContentType) {
210
+ t.Fatalf("Expected to find content-type %s in %s", expectedContentType, contentType)
211
+ }
212
+
213
+ // body checks
214
+ expectedBodyParts := []string{
215
+ "Content-Disposition: form-data; name=\"a\"\r\n\r\n1",
216
+ "Content-Disposition: form-data; name=\"b\"\r\n\r\ntest1",
217
+ "Content-Disposition: form-data; name=\"b\"\r\n\r\ntest2",
218
+ "Content-Disposition: form-data; name=\"c\"; filename=\"test\"\r\nContent-Type: application/octet-stream\r\n\r\nabc",
219
+ }
220
+ for _, part := range expectedBodyParts {
221
+ if !strings.Contains(bodyStr, part) {
222
+ t.Fatalf("Expected to find %s in body\n%s", part, bodyStr)
223
+ }
224
+ }
225
+ }
@@ -0,0 +1,6 @@
1
+ package generated
2
+
3
+ import "embed"
4
+
5
+ //go:embed types.d.ts
6
+ var Types embed.FS