backend-manager 5.9.7 → 5.9.9

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 (216) hide show
  1. package/package.json +5 -3
  2. package/src/cli/commands/setup-tests/bem-config.js +3 -2
  3. package/src/cli/commands/setup-tests/helpers.js +5 -0
  4. package/src/cli/commands/setup-tests/project-id-consistency.js +2 -1
  5. package/src/test/fixtures/firebase-project/.temp/test-mode.json +1 -1
  6. package/src/test/fixtures/firebase-project/database-debug.log +8 -8
  7. package/src/test/fixtures/firebase-project/firestore-debug.log +57 -55
  8. package/src/test/fixtures/firebase-project/pubsub-debug.log +3 -3
  9. package/templates/_.env +42 -0
  10. package/templates/_.gitignore +60 -0
  11. package/templates/backend-manager-config.json +249 -0
  12. package/templates/database.rules.json +83 -0
  13. package/templates/firebase.json +66 -0
  14. package/templates/firestore.indexes.json +4 -0
  15. package/templates/firestore.rules +112 -0
  16. package/templates/public/404.html +26 -0
  17. package/templates/public/index.html +24 -0
  18. package/templates/remoteconfig.template.json +1 -0
  19. package/templates/storage-lifecycle-config-1-day.json +9 -0
  20. package/templates/storage-lifecycle-config-30-days.json +9 -0
  21. package/templates/storage.rules +8 -0
  22. package/test/_init/accounts-validation.js +58 -0
  23. package/test/_legacy/ai/index.js +231 -0
  24. package/test/_legacy/ai/test.jpg +0 -0
  25. package/test/_legacy/ai/test.pdf +0 -0
  26. package/test/_legacy/index.js +31 -0
  27. package/test/_legacy/payment-resolver/chargebee/orders/refunded.json +0 -0
  28. package/test/_legacy/payment-resolver/chargebee/orders/unpaid.json +98 -0
  29. package/test/_legacy/payment-resolver/chargebee/subscriptions/active.json +578 -0
  30. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-in-trial.json +38 -0
  31. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-skipped-to-active.json +135 -0
  32. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active-to-cancelled.json +42 -0
  33. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active.json +44 -0
  34. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-cancelled.json +39 -0
  35. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-refund.json +143 -0
  36. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-suspended.json +48 -0
  37. package/test/_legacy/payment-resolver/coinbase/orders/regular.json +204 -0
  38. package/test/_legacy/payment-resolver/coinbase/subscriptions/cancelled.json +204 -0
  39. package/test/_legacy/payment-resolver/coinbase/subscriptions/paid-2.json +125 -0
  40. package/test/_legacy/payment-resolver/index.js +1558 -0
  41. package/test/_legacy/payment-resolver/paypal/orders/refunded.json +0 -0
  42. package/test/_legacy/payment-resolver/paypal/orders/regular.json +120 -0
  43. package/test/_legacy/payment-resolver/paypal/subscriptions/active-refund-previous-stmnt.json +323 -0
  44. package/test/_legacy/payment-resolver/paypal/subscriptions/active.json +192 -0
  45. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-in-trial.json +125 -0
  46. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-not-complete.json +76 -0
  47. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue-2.json +114 -0
  48. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue.json +114 -0
  49. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active-to-cancelled.json +111 -0
  50. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active.json +132 -0
  51. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-cancelled.json +107 -0
  52. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-expired.json +91 -0
  53. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-refund.json +147 -0
  54. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-suspended.json +120 -0
  55. package/test/_legacy/payment-resolver/stripe/orders/refunded.json +0 -0
  56. package/test/_legacy/payment-resolver/stripe/orders/regular.json +91 -0
  57. package/test/_legacy/payment-resolver/stripe/subscriptions/active.json +421 -0
  58. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-in-trial.json +349 -0
  59. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active-failed-authorization.json +430 -0
  60. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active.json +319 -0
  61. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-cancelled.json +319 -0
  62. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-refund.json +414 -0
  63. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-suspended.json +319 -0
  64. package/test/_legacy/payment-resolver/stripe/subscriptions/unsure.json +339 -0
  65. package/test/_legacy/test-new +1178 -0
  66. package/test/_legacy/test.md +89 -0
  67. package/test/_legacy/usage.js +175 -0
  68. package/test/_legacy/user.js +31 -0
  69. package/test/ai/tools-live.js +170 -0
  70. package/test/boot/emulator-boots.js +37 -0
  71. package/test/content/blog-generate.js +160 -0
  72. package/test/email/campaign-send.js +45 -0
  73. package/test/email/consent-lifecycle.js +257 -0
  74. package/test/email/feedback-and-plain-send.js +52 -0
  75. package/test/email/fixtures/clean.json +30 -0
  76. package/test/email/fixtures/editorial.json +30 -0
  77. package/test/email/fixtures/field-report.json +53 -0
  78. package/test/email/marketing-lifecycle.js +132 -0
  79. package/test/email/newsletter-generate.js +819 -0
  80. package/test/email/newsletter-templates.js +491 -0
  81. package/test/email/templates.js +207 -0
  82. package/test/email/transactional-send.js +34 -0
  83. package/test/email/transactional.js +560 -0
  84. package/test/email/validation.js +710 -0
  85. package/test/events/auth-delete-race.js +201 -0
  86. package/test/events/payments/journey-payments-cancel-endpoint.js +100 -0
  87. package/test/events/payments/journey-payments-cancel.js +182 -0
  88. package/test/events/payments/journey-payments-discount.js +89 -0
  89. package/test/events/payments/journey-payments-failure.js +146 -0
  90. package/test/events/payments/journey-payments-legacy-product.js +149 -0
  91. package/test/events/payments/journey-payments-one-time-failure.js +111 -0
  92. package/test/events/payments/journey-payments-one-time.js +136 -0
  93. package/test/events/payments/journey-payments-plan-change.js +144 -0
  94. package/test/events/payments/journey-payments-refund-webhook.js +180 -0
  95. package/test/events/payments/journey-payments-suspend.js +181 -0
  96. package/test/events/payments/journey-payments-trial-cancel.js +130 -0
  97. package/test/events/payments/journey-payments-trial.js +171 -0
  98. package/test/events/payments/journey-payments-uid-resolution.js +132 -0
  99. package/test/events/payments/journey-payments-upgrade.js +135 -0
  100. package/test/fixtures/chargebee/invoice-one-time.json +27 -0
  101. package/test/fixtures/chargebee/subscription-active.json +44 -0
  102. package/test/fixtures/chargebee/subscription-cancelled.json +42 -0
  103. package/test/fixtures/chargebee/subscription-in-trial.json +41 -0
  104. package/test/fixtures/chargebee/subscription-legacy-plan.json +41 -0
  105. package/test/fixtures/chargebee/subscription-non-renewing.json +41 -0
  106. package/test/fixtures/chargebee/subscription-paused.json +42 -0
  107. package/test/fixtures/chargebee/webhook-payment-failed.json +51 -0
  108. package/test/fixtures/chargebee/webhook-subscription-created.json +47 -0
  109. package/test/fixtures/paypal/order-approved.json +62 -0
  110. package/test/fixtures/paypal/order-completed.json +110 -0
  111. package/test/fixtures/paypal/subscription-active.json +76 -0
  112. package/test/fixtures/paypal/subscription-cancelled.json +50 -0
  113. package/test/fixtures/paypal/subscription-suspended.json +65 -0
  114. package/test/fixtures/stripe/checkout-session-completed.json +130 -0
  115. package/test/fixtures/stripe/invoice-payment-failed.json +148 -0
  116. package/test/fixtures/stripe/invoice-subscription-payment-failed.json +28 -0
  117. package/test/fixtures/stripe/subscription-active.json +161 -0
  118. package/test/fixtures/stripe/subscription-canceled.json +161 -0
  119. package/test/fixtures/stripe/subscription-trialing.json +161 -0
  120. package/test/functions/admin/database-read.js +134 -0
  121. package/test/functions/admin/database-write.js +159 -0
  122. package/test/functions/admin/edit-post.js +366 -0
  123. package/test/functions/admin/firestore-query.js +207 -0
  124. package/test/functions/admin/firestore-read.js +129 -0
  125. package/test/functions/admin/firestore-write.js +105 -0
  126. package/test/functions/admin/get-stats.js +115 -0
  127. package/test/functions/admin/send-email.js +119 -0
  128. package/test/functions/admin/send-notification.js +208 -0
  129. package/test/functions/admin/write-repo-content.js +223 -0
  130. package/test/functions/general/add-marketing-contact.js +335 -0
  131. package/test/functions/general/fetch-post.js +54 -0
  132. package/test/functions/general/generate-uuid.js +114 -0
  133. package/test/functions/test/authenticate.js +64 -0
  134. package/test/functions/user/create-custom-token.js +73 -0
  135. package/test/functions/user/delete.js +135 -0
  136. package/test/functions/user/get-active-sessions.js +111 -0
  137. package/test/functions/user/get-subscription-info.js +99 -0
  138. package/test/functions/user/regenerate-api-keys.js +156 -0
  139. package/test/functions/user/resolve.js +52 -0
  140. package/test/functions/user/sign-out-all-sessions.js +94 -0
  141. package/test/functions/user/sign-up.js +252 -0
  142. package/test/functions/user/submit-feedback.js +104 -0
  143. package/test/functions/user/validate-settings.js +82 -0
  144. package/test/helpers/ai-request-payload.js +300 -0
  145. package/test/helpers/ai-test-provider.js +202 -0
  146. package/test/helpers/ai-tools-format.js +383 -0
  147. package/test/helpers/content/blog-auto-publisher.js +484 -0
  148. package/test/helpers/content/feed-parser.js +528 -0
  149. package/test/helpers/content/ghostii-blocks.js +134 -0
  150. package/test/helpers/content/ghostii-feed-integration.js +404 -0
  151. package/test/helpers/content/ghostii-write-article.js +243 -0
  152. package/test/helpers/environment.js +230 -0
  153. package/test/helpers/infer-contact.js +113 -0
  154. package/test/helpers/merge-line-files.js +271 -0
  155. package/test/helpers/payment/chargebee/parse-webhook.js +413 -0
  156. package/test/helpers/payment/chargebee/to-unified-one-time.js +147 -0
  157. package/test/helpers/payment/chargebee/to-unified-subscription.js +648 -0
  158. package/test/helpers/payment/paypal/parse-webhook.js +539 -0
  159. package/test/helpers/payment/paypal/to-unified-one-time.js +382 -0
  160. package/test/helpers/payment/paypal/to-unified-subscription.js +820 -0
  161. package/test/helpers/payment/stripe/parse-webhook.js +447 -0
  162. package/test/helpers/payment/stripe/to-unified-one-time.js +306 -0
  163. package/test/helpers/payment/stripe/to-unified-subscription.js +708 -0
  164. package/test/helpers/sanitize.js +222 -0
  165. package/test/helpers/slugify.js +394 -0
  166. package/test/helpers/storage.js +194 -0
  167. package/test/helpers/user.js +755 -0
  168. package/test/helpers/webhook-forward.js +418 -0
  169. package/test/mcp/discovery.js +53 -0
  170. package/test/mcp/oauth.js +161 -0
  171. package/test/mcp/protocol.js +268 -0
  172. package/test/mcp/roles.js +188 -0
  173. package/test/mcp/utils.js +245 -0
  174. package/test/routes/admin/create-post.js +364 -0
  175. package/test/routes/admin/database.js +131 -0
  176. package/test/routes/admin/deduplicate-image-alts.js +190 -0
  177. package/test/routes/admin/email.js +114 -0
  178. package/test/routes/admin/firestore-query.js +204 -0
  179. package/test/routes/admin/firestore.js +127 -0
  180. package/test/routes/admin/infer-contact.js +218 -0
  181. package/test/routes/admin/notification.js +198 -0
  182. package/test/routes/admin/post-resize-image.js +184 -0
  183. package/test/routes/admin/post.js +368 -0
  184. package/test/routes/admin/repo-content.js +223 -0
  185. package/test/routes/admin/stats.js +112 -0
  186. package/test/routes/content/post.js +54 -0
  187. package/test/routes/general/uuid.js +115 -0
  188. package/test/routes/marketing/campaign.js +125 -0
  189. package/test/routes/marketing/contact.js +370 -0
  190. package/test/routes/marketing/email-preferences.js +292 -0
  191. package/test/routes/marketing/push-send.js +32 -0
  192. package/test/routes/marketing/webhook-forward.js +61 -0
  193. package/test/routes/marketing/webhook.js +639 -0
  194. package/test/routes/payments/cancel.js +163 -0
  195. package/test/routes/payments/discount.js +80 -0
  196. package/test/routes/payments/dispute-alert.js +320 -0
  197. package/test/routes/payments/intent.js +336 -0
  198. package/test/routes/payments/portal.js +92 -0
  199. package/test/routes/payments/refund.js +179 -0
  200. package/test/routes/payments/trial-eligibility.js +71 -0
  201. package/test/routes/payments/webhook.js +107 -0
  202. package/test/routes/test/authenticate.js +59 -0
  203. package/test/routes/test/schema.js +554 -0
  204. package/test/routes/test/usage.js +342 -0
  205. package/test/routes/user/api-keys.js +156 -0
  206. package/test/routes/user/delete.js +136 -0
  207. package/test/routes/user/feedback.js +104 -0
  208. package/test/routes/user/sessions.js +199 -0
  209. package/test/routes/user/settings-validate.js +82 -0
  210. package/test/routes/user/signup.js +542 -0
  211. package/test/routes/user/subscription.js +99 -0
  212. package/test/routes/user/token.js +73 -0
  213. package/test/routes/user/user.js +157 -0
  214. package/test/rules/notifications.js +410 -0
  215. package/test/rules/payments-carts.js +371 -0
  216. package/test/rules/user.js +396 -0
@@ -0,0 +1,83 @@
1
+ {
2
+ "rules": {
3
+ ///---backend-manager---///
4
+ ///---version=0.0.0---///
5
+
6
+ // Sessions rules
7
+ "sessions": {
8
+ ".read": false,
9
+ ".write": false,
10
+ "$room": {
11
+ ".read": "
12
+ (auth.uid != null && query.equalTo == auth.uid)
13
+ ",
14
+ ".write": false,
15
+ ".indexOn": ["uid"],
16
+ "$id": {
17
+ ".read": "
18
+ // Allowed if user is authenticated AND is the owner of the doc
19
+ (auth != null && auth.uid == data.child('uid').val())
20
+ // Allowed if uid is equal to the doc id [LEGACY FOR SOMIIBO]
21
+ || (auth != null && auth.uid == $id)
22
+ // Allowed if user is not authenticated AND is the doc has no owner
23
+ || (auth == null && (data.child('uid').val() == ''))
24
+ ",
25
+ ".write": "
26
+ // Allowed if the user is authenticated AND is the owner of the existing doc
27
+ (auth != null && auth.uid == data.child('uid').val())
28
+ // Allowed if the user is authenticated AND is the owner of the new doc
29
+ || (auth != null && auth.uid == newData.child('uid').val())
30
+ // Allowed if the user is authenticated AND is the owner of the existing doc
31
+ || (auth != null && auth.uid == data.child('uid').val())
32
+ // Allowed if uid is equal to the doc id [LEGACY FOR SOMIIBO]
33
+ || (auth != null && auth.uid == $id)
34
+ // Allowed if the existing doc has no owner
35
+ || (data.child('uid').val() == '')
36
+ // Allowed if the new doc has no owner
37
+ || (newData.child('uid').val() == '')
38
+ // Allowed if it's a delete
39
+ || (!newData.exists())
40
+ ",
41
+ }
42
+ }
43
+ },
44
+ "gatherings": {
45
+ ".read": false,
46
+ ".write": false,
47
+ "$room": {
48
+ ".read": "
49
+ (auth.uid != null && query.equalTo == auth.uid)
50
+ ",
51
+ ".write": false,
52
+ ".indexOn": ["uid"],
53
+ "$id": {
54
+ ".read": "
55
+ // Allowed if user is authenticated AND is the owner of the doc
56
+ (auth != null && auth.uid == data.child('uid').val())
57
+ // Allowed if uid is equal to the doc id [LEGACY FOR SOMIIBO]
58
+ || (auth != null && auth.uid == $id)
59
+ // Allowed if user is not authenticated AND is the doc has no owner
60
+ || (auth == null && (data.child('uid').val() == ''))
61
+ ",
62
+ ".write": "
63
+ // Allowed if the user is authenticated AND is the owner of the existing doc
64
+ (auth != null && auth.uid == data.child('uid').val())
65
+ // Allowed if the user is authenticated AND is the owner of the new doc
66
+ || (auth != null && auth.uid == newData.child('uid').val())
67
+ // Allowed if the user is authenticated AND is the owner of the existing doc
68
+ || (auth != null && auth.uid == data.child('uid').val())
69
+ // Allowed if uid is equal to the doc id [LEGACY FOR SOMIIBO]
70
+ || (auth != null && auth.uid == $id)
71
+ // Allowed if the existing doc has no owner
72
+ || (data.child('uid').val() == '')
73
+ // Allowed if the new doc has no owner
74
+ || (newData.child('uid').val() == '')
75
+ // Allowed if it's a delete
76
+ || (!newData.exists())
77
+ ",
78
+ }
79
+ }
80
+ },
81
+ ///---------end---------///
82
+ }
83
+ }
@@ -0,0 +1,66 @@
1
+ {
2
+ "hosting": {
3
+ "public": "public",
4
+ "ignore": [
5
+ "firebase.json",
6
+ "**/.*",
7
+ "**/node_modules/**"
8
+ ],
9
+ "rewrites": [
10
+ {
11
+ "source": "{/backend-manager,/backend-manager/**,/mcp,/mcp/**,/.well-known/oauth-protected-resource,/.well-known/oauth-authorization-server,/authorize,/token,/register}",
12
+ "function": "bm_api"
13
+ }
14
+ ]
15
+ },
16
+ "functions": [
17
+ {
18
+ "source": "functions",
19
+ "codebase": "default",
20
+ "ignore": [
21
+ "node_modules",
22
+ ".git",
23
+ "firebase-debug.log",
24
+ "firebase-debug.*.log"
25
+ ]
26
+ }
27
+ ],
28
+ "firestore": {
29
+ "rules": "firestore.rules",
30
+ "indexes": "firestore.indexes.json"
31
+ },
32
+ "database": {
33
+ "rules": "database.rules.json"
34
+ },
35
+ "storage": {
36
+ "rules": "storage.rules"
37
+ },
38
+ "emulators": {
39
+ "auth": {
40
+ "port": 9099
41
+ },
42
+ "functions": {
43
+ "port": 5001
44
+ },
45
+ "firestore": {
46
+ "port": 8080
47
+ },
48
+ "database": {
49
+ "port": 9000
50
+ },
51
+ "hosting": {
52
+ "port": 5002
53
+ },
54
+ "storage": {
55
+ "port": 9199
56
+ },
57
+ "ui": {
58
+ "enabled": true,
59
+ "port": 4050
60
+ },
61
+ "pubsub": {
62
+ "port": 8085
63
+ },
64
+ "singleProjectMode": true
65
+ }
66
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "indexes": [],
3
+ "fieldOverrides": []
4
+ }
@@ -0,0 +1,112 @@
1
+ rules_version = '2';
2
+ service cloud.firestore {
3
+ match /databases/{database}/documents {
4
+ // Custom rules
5
+ // ...
6
+
7
+ ///---backend-manager---///
8
+ ///---version=0.0.0---///
9
+ // Lock by default
10
+ match /{document=**} {
11
+ allow read, write: if isAdmin();
12
+ }
13
+
14
+ // Protect user account data
15
+ match /users/{uid} {
16
+ allow read: if belongsTo(uid);
17
+ allow write: if belongsTo(uid) && !isWritingProtectedUserField();
18
+ }
19
+
20
+ // Protect notification data
21
+ match /notifications/{token} {
22
+ allow read: if resource == null || existingData().token == token || belongsTo(existingData().owner);
23
+ allow update: if existingData().token == token;
24
+ allow create: if true;
25
+ }
26
+
27
+ // Protect abandoned cart data
28
+ match /payments-carts/{uid} {
29
+ allow create, update: if belongsTo(uid)
30
+ && incomingData().owner == uid
31
+ && incomingData().status == 'pending';
32
+ allow read: if belongsTo(uid);
33
+ }
34
+
35
+ // Auth functions
36
+ function authEmail() {
37
+ return request.auth.token.email;
38
+ }
39
+ function authUid() {
40
+ return request.auth.uid;
41
+ }
42
+ function isAuthenticated() {
43
+ return request.auth != null;
44
+ }
45
+ function belongsTo(identity) {
46
+ return isAuthenticated() && (authUid() == identity || authEmail() == identity);
47
+ // eventually include a check for (existingData().owner == identity)...(in case its a doc owned by a user that's not actually user doc)
48
+ }
49
+
50
+ function getRoles() {
51
+ return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles;
52
+ }
53
+ function getVerifications() {
54
+ return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.verifications;
55
+ }
56
+ function isAdmin() {
57
+ return getRoles().admin == true;
58
+ }
59
+ function emailVerified() {
60
+ return getVerifications().email == true;
61
+ }
62
+
63
+ function isWritingProtectedUserField() {
64
+ return isWritingField('auth')
65
+ || isWritingField('roles')
66
+ || isWritingField('flags')
67
+ || isWritingField('plan') // TODO: REMOVE THIS WHEN ALL PROJECTS UPDATED
68
+ || isWritingField('subscription')
69
+ || isWritingField('affiliate')
70
+ || isWritingField('api')
71
+ || isWritingField('metadata')
72
+ || isWritingField('usage')
73
+ || isWritingField('consent');
74
+ }
75
+
76
+ function isCreatingField(field) {
77
+ return !(field in resource.data) && field in request.resource.data;
78
+ }
79
+
80
+ function isUpdatingField(field) {
81
+ return field in resource.data && field in request.resource.data && resource.data[field] != request.resource.data[field];
82
+ }
83
+
84
+ function isWritingField(field) {
85
+ return isCreatingField(field) || isUpdatingField(field);
86
+ }
87
+
88
+ // function userEmail(userId) {
89
+ // return get(/databases/$(database)/documents/users/$(userId)).data.email;
90
+ // }
91
+ // Helper functions
92
+ // [READ] Data that exists on the Firestore document
93
+ function existingData() {
94
+ return resource.data;
95
+ }
96
+ // [WRITE] Data that is sent to a Firestore document
97
+ function incomingData() {
98
+ return request.resource.data;
99
+ }
100
+ ///--------tests--------///
101
+ // match /test-cases/write/admin {
102
+ // allow write: if isAdmin();
103
+ // }
104
+ ///------resources------///
105
+ // https://fireship.io/snippets/firestore-rules-recipes/
106
+ // https://github.com/jaysquared/atom-firestore-grammar
107
+ // https://fireship.io/courses/firestore-data-modeling/models-roles/
108
+ // https://firebase.google.com/docs/firestore/security/test-rules-emulator
109
+ ///---------end---------///
110
+
111
+ }
112
+ }
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>404 Error Page</title>
8
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
9
+ </head>
10
+
11
+ <body>
12
+ <div class="d-flex align-items-center justify-content-center vh-100">
13
+ <div class="text-center">
14
+ <h1 class="display-1 fw-bold">404</h1>
15
+
16
+ <p class="fs-3"> <span class="text-danger">Opps!</span> Page not found.</p>
17
+
18
+ <p class="lead">
19
+ The page you're looking for doesn't exist.
20
+ </p>
21
+
22
+ <a href="{url}" class="btn btn-primary">Go Home</a>
23
+ </div>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,24 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>404 Error Page</title>
8
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
9
+ </head>
10
+
11
+ <body>
12
+ <div class="d-flex align-items-center justify-content-center vh-100">
13
+ <div class="text-center">
14
+ <h1 class="display-1 fw-bold">Welcome!</h1>
15
+
16
+ <p class="lead">
17
+ This is an internal page, please return to our main website
18
+ </p>
19
+
20
+ <a href="{url}" class="btn btn-primary">Go Home</a>
21
+ </div>
22
+ </div>
23
+ </body>
24
+ </html>
@@ -0,0 +1 @@
1
+ {}
@@ -0,0 +1,9 @@
1
+ {
2
+ "rule":
3
+ [
4
+ {
5
+ "action": {"type": "Delete"},
6
+ "condition": {"age": 1}
7
+ }
8
+ ]
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "rule":
3
+ [
4
+ {
5
+ "action": {"type": "Delete"},
6
+ "condition": {"age": 30}
7
+ }
8
+ ]
9
+ }
@@ -0,0 +1,8 @@
1
+ rules_version = '2';
2
+ service firebase.storage {
3
+ match /b/{bucket}/o {
4
+ match /{allPaths=**} {
5
+ allow read, write: if request.auth!=null;
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,58 @@
1
+ const { TEST_ACCOUNTS } = require('../../src/test/test-accounts.js');
2
+
3
+ /**
4
+ * Test: Account Structure Validation
5
+ * Verifies that all test accounts were created with the expected User helper fields
6
+ * This catches failures in auth:on-create before other tests run
7
+ */
8
+ module.exports = {
9
+ description: 'Test accounts have required User structure',
10
+ auth: 'none',
11
+ timeout: 10000,
12
+
13
+ async run({ accounts, assert, firestore }) {
14
+ const accountIds = Object.keys(TEST_ACCOUNTS);
15
+
16
+ for (const accountId of accountIds) {
17
+ const account = accounts[accountId];
18
+
19
+ // Check account was fetched
20
+ assert.ok(account, `Account '${accountId}' should exist in accounts object`);
21
+ assert.ok(account.uid, `Account '${accountId}' should have uid`);
22
+
23
+ // Fetch full user doc from Firestore
24
+ const userDoc = await firestore.get(`users/${account.uid}`);
25
+
26
+ assert.ok(userDoc, `User doc for '${accountId}' should exist in Firestore`);
27
+
28
+ // Validate auth fields (set by on-create)
29
+ assert.hasProperty(userDoc, 'auth.uid', `Account '${accountId}' should have auth.uid`);
30
+ assert.hasProperty(userDoc, 'auth.email', `Account '${accountId}' should have auth.email`);
31
+
32
+ // Validate API fields (generated by on-create via User helper)
33
+ assert.hasProperty(userDoc, 'api.clientId', `Account '${accountId}' should have api.clientId`);
34
+ assert.hasProperty(userDoc, 'api.privateKey', `Account '${accountId}' should have api.privateKey`);
35
+
36
+ // Validate privateKey was fetched correctly
37
+ assert.ok(account.privateKey, `Account '${accountId}' privateKey should be fetched`);
38
+ assert.equal(
39
+ account.privateKey,
40
+ userDoc.api.privateKey,
41
+ `Account '${accountId}' fetched privateKey should match Firestore`
42
+ );
43
+
44
+ // Validate affiliate code (generated by on-create via User helper)
45
+ assert.hasProperty(userDoc, 'affiliate.code', `Account '${accountId}' should have affiliate.code`);
46
+
47
+ // Validate metadata.created (set by on-create via User helper)
48
+ assert.hasProperty(userDoc, 'metadata.created.timestamp', `Account '${accountId}' should have metadata.created.timestamp`);
49
+ assert.hasProperty(userDoc, 'metadata.created.timestampUNIX', `Account '${accountId}' should have metadata.created.timestampUNIX`);
50
+
51
+ // Validate subscription fields (merged from test account properties)
52
+ assert.hasProperty(userDoc, 'subscription.product.id', `Account '${accountId}' should have subscription.product.id`);
53
+ assert.hasProperty(userDoc, 'subscription.status', `Account '${accountId}' should have subscription.status`);
54
+ }
55
+
56
+ return { success: true };
57
+ },
58
+ };
@@ -0,0 +1,231 @@
1
+ const assert = require('assert');
2
+ const Manager = require('../../src/manager/index.js');
3
+
4
+ describe('AI Library Tests', function() {
5
+ let manager;
6
+ let assistant;
7
+ let ai;
8
+
9
+ before(function() {
10
+ manager = new Manager();
11
+ manager.init(exports, {
12
+ projectType: 'custom',
13
+ setupServer: false,
14
+ initialize: false,
15
+ backendManagerConfigPath: 'templates/backend-manager-config.json',
16
+ });
17
+ assistant = manager.assistant;
18
+ ai = new (require('../../src/manager/libraries/openai'))(assistant, process.env.OPENAI_API_KEY);
19
+ });
20
+
21
+ if (!process.env.OPENAI_API_KEY) {
22
+ throw new Error('OPENAI_API_KEY is not set. Please set it in your environment variables.');
23
+ }
24
+
25
+ describe('Simple AI Message Test', function() {
26
+ it('should respond with "Hi!" when asked to say hi', async function() {
27
+ this.timeout(30000);
28
+
29
+ const response = await ai.request({
30
+ // log: true,
31
+ prompt: { content: 'You are helping test an implementation. Please respond with "Hi!" and nothing else.' },
32
+ message: { content: 'Hi there friend!' },
33
+ });
34
+
35
+ assert(response);
36
+ assert(response.content);
37
+ assert.strictEqual(response.content.trim(), 'Hi!');
38
+ });
39
+
40
+ it('should analyze an image and respond with "cat"', async function() {
41
+ this.timeout(30000);
42
+
43
+ const response = await ai.request({
44
+ // log: true,
45
+ prompt: { content: 'You are a helpful assistant that describes images. Please answer in ONE WORD.' },
46
+ message: {
47
+ content: 'What animal is this?',
48
+ attachments: [
49
+ {
50
+ type: 'image',
51
+ content: `${__dirname}/test.jpg`,
52
+ }
53
+ ]
54
+ },
55
+ });
56
+
57
+ assert(response);
58
+ assert(response.content);
59
+ assert.strictEqual(response.content.trim().toLowerCase(), 'cat');
60
+ });
61
+
62
+ it('should analyze a PDF file and respond with "42"', async function() {
63
+ this.timeout(30000);
64
+
65
+ const response = await ai.request({
66
+ // log: true,
67
+ prompt: { content: 'You are a helpful assistant that analyzes documents. Please answer in ONE WORD.' },
68
+ message: {
69
+ content: 'What is the answer (numerical form)?',
70
+ attachments: [
71
+ {
72
+ type: 'file',
73
+ content: `${__dirname}/test.pdf`,
74
+ filename: 'test.pdf'
75
+ }
76
+ ]
77
+ },
78
+ });
79
+
80
+ assert(response);
81
+ assert.strictEqual(response.content.trim().toLowerCase(), '42');
82
+ });
83
+
84
+ it('should analyze a remote image URL and respond with "cat"', async function() {
85
+ this.timeout(30000);
86
+
87
+ const response = await ai.request({
88
+ // log: true,
89
+ prompt: { content: 'You are a helpful assistant that describes images. Please answer in ONE WORD.' },
90
+ message: {
91
+ content: 'What animal is this?',
92
+ attachments: [
93
+ {
94
+ type: 'image',
95
+ content: 'https://raw.githubusercontent.com/ITW-Creative-Works/backend-manager/master/test/ai/test.jpg',
96
+ }
97
+ ]
98
+ },
99
+ });
100
+
101
+ assert(response);
102
+ assert(response.content);
103
+ assert.strictEqual(response.content.trim().toLowerCase(), 'cat');
104
+ });
105
+
106
+ it('should analyze a remote PDF URL and respond with "42"', async function() {
107
+ this.timeout(30000);
108
+
109
+ const response = await ai.request({
110
+ // log: true,
111
+ prompt: { content: 'You are a helpful assistant that analyzes documents. Please answer in ONE WORD.' },
112
+ message: {
113
+ content: 'What is the answer (numerical form)?',
114
+ attachments: [
115
+ {
116
+ type: 'file',
117
+ content: 'https://raw.githubusercontent.com/ITW-Creative-Works/backend-manager/master/test/ai/test.pdf',
118
+ filename: 'test.pdf'
119
+ }
120
+ ]
121
+ },
122
+ });
123
+
124
+ assert(response);
125
+ assert.strictEqual(response.content.trim().toLowerCase(), '42');
126
+ });
127
+
128
+ it('should return JSON when response format is set to json', async function() {
129
+ this.timeout(30000);
130
+
131
+ const response = await ai.request({
132
+ // log: true,
133
+ response: 'json',
134
+ prompt: { content: 'You are a helpful assistant that returns structured data in JSON format.' },
135
+ message: { content: 'Give me information about a cat in JSON format with fields: name, age, color.' },
136
+ });
137
+
138
+ assert(response);
139
+ assert(response.content);
140
+ assert(typeof response.content === 'object');
141
+ assert(response.content.name);
142
+ assert(response.content.age);
143
+ assert(response.content.color);
144
+ });
145
+
146
+ it('should return data matching schema when schema is provided', async function() {
147
+ this.timeout(30000);
148
+
149
+ const schema = {
150
+ type: 'object',
151
+ properties: {
152
+ animal: {
153
+ type: 'string',
154
+ description: 'The type of animal'
155
+ },
156
+ characteristics: {
157
+ type: 'array',
158
+ items: {
159
+ type: 'string'
160
+ },
161
+ description: 'List of characteristics'
162
+ },
163
+ habitat: {
164
+ type: 'string',
165
+ description: 'Where the animal lives'
166
+ }
167
+ },
168
+ required: ['animal', 'characteristics', 'habitat'],
169
+ additionalProperties: false
170
+ };
171
+
172
+ const response = await ai.request({
173
+ // log: true,
174
+ response: 'json',
175
+ schema: schema,
176
+ prompt: { content: 'You are a helpful assistant that provides animal information.' },
177
+ message: { content: 'Tell me about a lion.' },
178
+ });
179
+
180
+ assert(response);
181
+ assert(response.content);
182
+ assert(typeof response.content === 'object');
183
+ assert.strictEqual(typeof response.content.animal, 'string');
184
+ assert(Array.isArray(response.content.characteristics));
185
+ assert.strictEqual(typeof response.content.habitat, 'string');
186
+ assert(response.content.animal.toLowerCase().includes('lion'));
187
+ });
188
+
189
+ it('should moderate content and return moderation results', async function() {
190
+ this.timeout(30000);
191
+
192
+ const response = await ai.request({
193
+ // log: true,
194
+ moderate: true,
195
+ prompt: { content: 'You are a helpful assistant.' },
196
+ message: { content: 'This is a completely normal and appropriate message about cats.' },
197
+ });
198
+
199
+ assert(response);
200
+ assert(response.content);
201
+ assert(response.moderation);
202
+ assert(typeof response.moderation.flagged === 'boolean');
203
+ assert.strictEqual(response.moderation.flagged, false);
204
+ });
205
+
206
+ it('should moderate content with image attachment', async function() {
207
+ this.timeout(30000);
208
+
209
+ const response = await ai.request({
210
+ // log: true,
211
+ moderate: true,
212
+ prompt: { content: 'You are a helpful assistant.' },
213
+ message: {
214
+ content: 'Here is an image of a cat.',
215
+ attachments: [
216
+ {
217
+ type: 'image',
218
+ content: `${__dirname}/test.jpg`,
219
+ }
220
+ ]
221
+ },
222
+ });
223
+
224
+ assert(response);
225
+ assert(response.content);
226
+ assert(response.moderation);
227
+ assert(typeof response.moderation.flagged === 'boolean');
228
+ assert.strictEqual(response.moderation.flagged, false);
229
+ });
230
+ });
231
+ });
Binary file
Binary file
@@ -0,0 +1,31 @@
1
+ const package = require('../package.json');
2
+ const assert = require('assert');
3
+
4
+ beforeEach(() => {
5
+ });
6
+
7
+ before(() => {
8
+ });
9
+
10
+ after(() => {
11
+ });
12
+
13
+ /*
14
+ * ============
15
+ * Test Cases
16
+ * ============
17
+ */
18
+ describe(`${package.name}`, () => {
19
+ describe('.dependencies()', () => {
20
+ Object.keys(package.dependencies).forEach((dependency) => {
21
+ it(`should load ${dependency}`, () => {
22
+ try {
23
+ const dep = require(dependency);
24
+ assert.ok(dep);
25
+ } catch (e) {
26
+ assert.fail(`Failed to load ${dependency}: ${e.message}`);
27
+ }
28
+ });
29
+ });
30
+ });
31
+ })