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.
- package/package.json +5 -3
- package/src/cli/commands/setup-tests/bem-config.js +3 -2
- package/src/cli/commands/setup-tests/helpers.js +5 -0
- package/src/cli/commands/setup-tests/project-id-consistency.js +2 -1
- package/src/test/fixtures/firebase-project/.temp/test-mode.json +1 -1
- package/src/test/fixtures/firebase-project/database-debug.log +8 -8
- package/src/test/fixtures/firebase-project/firestore-debug.log +57 -55
- package/src/test/fixtures/firebase-project/pubsub-debug.log +3 -3
- package/templates/_.env +42 -0
- package/templates/_.gitignore +60 -0
- package/templates/backend-manager-config.json +249 -0
- package/templates/database.rules.json +83 -0
- package/templates/firebase.json +66 -0
- package/templates/firestore.indexes.json +4 -0
- package/templates/firestore.rules +112 -0
- package/templates/public/404.html +26 -0
- package/templates/public/index.html +24 -0
- package/templates/remoteconfig.template.json +1 -0
- package/templates/storage-lifecycle-config-1-day.json +9 -0
- package/templates/storage-lifecycle-config-30-days.json +9 -0
- package/templates/storage.rules +8 -0
- package/test/_init/accounts-validation.js +58 -0
- package/test/_legacy/ai/index.js +231 -0
- package/test/_legacy/ai/test.jpg +0 -0
- package/test/_legacy/ai/test.pdf +0 -0
- package/test/_legacy/index.js +31 -0
- package/test/_legacy/payment-resolver/chargebee/orders/refunded.json +0 -0
- package/test/_legacy/payment-resolver/chargebee/orders/unpaid.json +98 -0
- package/test/_legacy/payment-resolver/chargebee/subscriptions/active.json +578 -0
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-in-trial.json +38 -0
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-skipped-to-active.json +135 -0
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active-to-cancelled.json +42 -0
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active.json +44 -0
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-cancelled.json +39 -0
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-refund.json +143 -0
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-suspended.json +48 -0
- package/test/_legacy/payment-resolver/coinbase/orders/regular.json +204 -0
- package/test/_legacy/payment-resolver/coinbase/subscriptions/cancelled.json +204 -0
- package/test/_legacy/payment-resolver/coinbase/subscriptions/paid-2.json +125 -0
- package/test/_legacy/payment-resolver/index.js +1558 -0
- package/test/_legacy/payment-resolver/paypal/orders/refunded.json +0 -0
- package/test/_legacy/payment-resolver/paypal/orders/regular.json +120 -0
- package/test/_legacy/payment-resolver/paypal/subscriptions/active-refund-previous-stmnt.json +323 -0
- package/test/_legacy/payment-resolver/paypal/subscriptions/active.json +192 -0
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-in-trial.json +125 -0
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-not-complete.json +76 -0
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue-2.json +114 -0
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue.json +114 -0
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active-to-cancelled.json +111 -0
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active.json +132 -0
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-cancelled.json +107 -0
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-expired.json +91 -0
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-refund.json +147 -0
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-suspended.json +120 -0
- package/test/_legacy/payment-resolver/stripe/orders/refunded.json +0 -0
- package/test/_legacy/payment-resolver/stripe/orders/regular.json +91 -0
- package/test/_legacy/payment-resolver/stripe/subscriptions/active.json +421 -0
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-in-trial.json +349 -0
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active-failed-authorization.json +430 -0
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active.json +319 -0
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-cancelled.json +319 -0
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-refund.json +414 -0
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-suspended.json +319 -0
- package/test/_legacy/payment-resolver/stripe/subscriptions/unsure.json +339 -0
- package/test/_legacy/test-new +1178 -0
- package/test/_legacy/test.md +89 -0
- package/test/_legacy/usage.js +175 -0
- package/test/_legacy/user.js +31 -0
- package/test/ai/tools-live.js +170 -0
- package/test/boot/emulator-boots.js +37 -0
- package/test/content/blog-generate.js +160 -0
- package/test/email/campaign-send.js +45 -0
- package/test/email/consent-lifecycle.js +257 -0
- package/test/email/feedback-and-plain-send.js +52 -0
- package/test/email/fixtures/clean.json +30 -0
- package/test/email/fixtures/editorial.json +30 -0
- package/test/email/fixtures/field-report.json +53 -0
- package/test/email/marketing-lifecycle.js +132 -0
- package/test/email/newsletter-generate.js +819 -0
- package/test/email/newsletter-templates.js +491 -0
- package/test/email/templates.js +207 -0
- package/test/email/transactional-send.js +34 -0
- package/test/email/transactional.js +560 -0
- package/test/email/validation.js +710 -0
- package/test/events/auth-delete-race.js +201 -0
- package/test/events/payments/journey-payments-cancel-endpoint.js +100 -0
- package/test/events/payments/journey-payments-cancel.js +182 -0
- package/test/events/payments/journey-payments-discount.js +89 -0
- package/test/events/payments/journey-payments-failure.js +146 -0
- package/test/events/payments/journey-payments-legacy-product.js +149 -0
- package/test/events/payments/journey-payments-one-time-failure.js +111 -0
- package/test/events/payments/journey-payments-one-time.js +136 -0
- package/test/events/payments/journey-payments-plan-change.js +144 -0
- package/test/events/payments/journey-payments-refund-webhook.js +180 -0
- package/test/events/payments/journey-payments-suspend.js +181 -0
- package/test/events/payments/journey-payments-trial-cancel.js +130 -0
- package/test/events/payments/journey-payments-trial.js +171 -0
- package/test/events/payments/journey-payments-uid-resolution.js +132 -0
- package/test/events/payments/journey-payments-upgrade.js +135 -0
- package/test/fixtures/chargebee/invoice-one-time.json +27 -0
- package/test/fixtures/chargebee/subscription-active.json +44 -0
- package/test/fixtures/chargebee/subscription-cancelled.json +42 -0
- package/test/fixtures/chargebee/subscription-in-trial.json +41 -0
- package/test/fixtures/chargebee/subscription-legacy-plan.json +41 -0
- package/test/fixtures/chargebee/subscription-non-renewing.json +41 -0
- package/test/fixtures/chargebee/subscription-paused.json +42 -0
- package/test/fixtures/chargebee/webhook-payment-failed.json +51 -0
- package/test/fixtures/chargebee/webhook-subscription-created.json +47 -0
- package/test/fixtures/paypal/order-approved.json +62 -0
- package/test/fixtures/paypal/order-completed.json +110 -0
- package/test/fixtures/paypal/subscription-active.json +76 -0
- package/test/fixtures/paypal/subscription-cancelled.json +50 -0
- package/test/fixtures/paypal/subscription-suspended.json +65 -0
- package/test/fixtures/stripe/checkout-session-completed.json +130 -0
- package/test/fixtures/stripe/invoice-payment-failed.json +148 -0
- package/test/fixtures/stripe/invoice-subscription-payment-failed.json +28 -0
- package/test/fixtures/stripe/subscription-active.json +161 -0
- package/test/fixtures/stripe/subscription-canceled.json +161 -0
- package/test/fixtures/stripe/subscription-trialing.json +161 -0
- package/test/functions/admin/database-read.js +134 -0
- package/test/functions/admin/database-write.js +159 -0
- package/test/functions/admin/edit-post.js +366 -0
- package/test/functions/admin/firestore-query.js +207 -0
- package/test/functions/admin/firestore-read.js +129 -0
- package/test/functions/admin/firestore-write.js +105 -0
- package/test/functions/admin/get-stats.js +115 -0
- package/test/functions/admin/send-email.js +119 -0
- package/test/functions/admin/send-notification.js +208 -0
- package/test/functions/admin/write-repo-content.js +223 -0
- package/test/functions/general/add-marketing-contact.js +335 -0
- package/test/functions/general/fetch-post.js +54 -0
- package/test/functions/general/generate-uuid.js +114 -0
- package/test/functions/test/authenticate.js +64 -0
- package/test/functions/user/create-custom-token.js +73 -0
- package/test/functions/user/delete.js +135 -0
- package/test/functions/user/get-active-sessions.js +111 -0
- package/test/functions/user/get-subscription-info.js +99 -0
- package/test/functions/user/regenerate-api-keys.js +156 -0
- package/test/functions/user/resolve.js +52 -0
- package/test/functions/user/sign-out-all-sessions.js +94 -0
- package/test/functions/user/sign-up.js +252 -0
- package/test/functions/user/submit-feedback.js +104 -0
- package/test/functions/user/validate-settings.js +82 -0
- package/test/helpers/ai-request-payload.js +300 -0
- package/test/helpers/ai-test-provider.js +202 -0
- package/test/helpers/ai-tools-format.js +383 -0
- package/test/helpers/content/blog-auto-publisher.js +484 -0
- package/test/helpers/content/feed-parser.js +528 -0
- package/test/helpers/content/ghostii-blocks.js +134 -0
- package/test/helpers/content/ghostii-feed-integration.js +404 -0
- package/test/helpers/content/ghostii-write-article.js +243 -0
- package/test/helpers/environment.js +230 -0
- package/test/helpers/infer-contact.js +113 -0
- package/test/helpers/merge-line-files.js +271 -0
- package/test/helpers/payment/chargebee/parse-webhook.js +413 -0
- package/test/helpers/payment/chargebee/to-unified-one-time.js +147 -0
- package/test/helpers/payment/chargebee/to-unified-subscription.js +648 -0
- package/test/helpers/payment/paypal/parse-webhook.js +539 -0
- package/test/helpers/payment/paypal/to-unified-one-time.js +382 -0
- package/test/helpers/payment/paypal/to-unified-subscription.js +820 -0
- package/test/helpers/payment/stripe/parse-webhook.js +447 -0
- package/test/helpers/payment/stripe/to-unified-one-time.js +306 -0
- package/test/helpers/payment/stripe/to-unified-subscription.js +708 -0
- package/test/helpers/sanitize.js +222 -0
- package/test/helpers/slugify.js +394 -0
- package/test/helpers/storage.js +194 -0
- package/test/helpers/user.js +755 -0
- package/test/helpers/webhook-forward.js +418 -0
- package/test/mcp/discovery.js +53 -0
- package/test/mcp/oauth.js +161 -0
- package/test/mcp/protocol.js +268 -0
- package/test/mcp/roles.js +188 -0
- package/test/mcp/utils.js +245 -0
- package/test/routes/admin/create-post.js +364 -0
- package/test/routes/admin/database.js +131 -0
- package/test/routes/admin/deduplicate-image-alts.js +190 -0
- package/test/routes/admin/email.js +114 -0
- package/test/routes/admin/firestore-query.js +204 -0
- package/test/routes/admin/firestore.js +127 -0
- package/test/routes/admin/infer-contact.js +218 -0
- package/test/routes/admin/notification.js +198 -0
- package/test/routes/admin/post-resize-image.js +184 -0
- package/test/routes/admin/post.js +368 -0
- package/test/routes/admin/repo-content.js +223 -0
- package/test/routes/admin/stats.js +112 -0
- package/test/routes/content/post.js +54 -0
- package/test/routes/general/uuid.js +115 -0
- package/test/routes/marketing/campaign.js +125 -0
- package/test/routes/marketing/contact.js +370 -0
- package/test/routes/marketing/email-preferences.js +292 -0
- package/test/routes/marketing/push-send.js +32 -0
- package/test/routes/marketing/webhook-forward.js +61 -0
- package/test/routes/marketing/webhook.js +639 -0
- package/test/routes/payments/cancel.js +163 -0
- package/test/routes/payments/discount.js +80 -0
- package/test/routes/payments/dispute-alert.js +320 -0
- package/test/routes/payments/intent.js +336 -0
- package/test/routes/payments/portal.js +92 -0
- package/test/routes/payments/refund.js +179 -0
- package/test/routes/payments/trial-eligibility.js +71 -0
- package/test/routes/payments/webhook.js +107 -0
- package/test/routes/test/authenticate.js +59 -0
- package/test/routes/test/schema.js +554 -0
- package/test/routes/test/usage.js +342 -0
- package/test/routes/user/api-keys.js +156 -0
- package/test/routes/user/delete.js +136 -0
- package/test/routes/user/feedback.js +104 -0
- package/test/routes/user/sessions.js +199 -0
- package/test/routes/user/settings-validate.js +82 -0
- package/test/routes/user/signup.js +542 -0
- package/test/routes/user/subscription.js +99 -0
- package/test/routes/user/token.js +73 -0
- package/test/routes/user/user.js +157 -0
- package/test/rules/notifications.js +410 -0
- package/test/rules/payments-carts.js +371 -0
- 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,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,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
|
+
})
|
|
File without changes
|