backend-manager 5.0.203 → 5.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 (69) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/CLAUDE.md +43 -1501
  3. package/TODO-CHARGEBLAST.md +32 -0
  4. package/TODO-email-auth.md +14 -0
  5. package/docs/admin-post-route.md +24 -0
  6. package/docs/ai-library.md +23 -0
  7. package/docs/architecture.md +31 -0
  8. package/docs/auth-hooks.md +74 -0
  9. package/docs/cli-firestore-auth.md +59 -0
  10. package/docs/cli-logs.md +67 -0
  11. package/docs/code-patterns.md +67 -0
  12. package/docs/common-operations.md +64 -0
  13. package/docs/directory-structure.md +119 -0
  14. package/docs/environment-detection.md +7 -0
  15. package/docs/file-naming.md +11 -0
  16. package/docs/marketing-campaigns.md +244 -0
  17. package/docs/marketing-fields.md +25 -0
  18. package/docs/mcp.md +95 -0
  19. package/docs/payment-system.md +325 -0
  20. package/docs/response-headers.md +7 -0
  21. package/docs/routes.md +126 -0
  22. package/docs/sanitization.md +61 -0
  23. package/docs/schemas.md +39 -0
  24. package/docs/stripe-webhook-forwarding.md +18 -0
  25. package/docs/testing.md +129 -0
  26. package/docs/usage-rate-limiting.md +67 -0
  27. package/package.json +8 -4
  28. package/src/defaults/CHANGELOG.md +15 -0
  29. package/src/defaults/CLAUDE.md +8 -4
  30. package/src/defaults/docs/README.md +17 -0
  31. package/src/defaults/test/README.md +33 -0
  32. package/src/manager/events/cron/daily/marketing-newsletter-generate.js +48 -8
  33. package/src/manager/functions/core/actions/api/admin/create-post.js +3 -27
  34. package/src/manager/helpers/utilities.js +21 -0
  35. package/src/manager/index.js +1 -1
  36. package/src/manager/libraries/ai/index.js +162 -0
  37. package/src/manager/libraries/ai/providers/anthropic.js +193 -0
  38. package/src/manager/libraries/ai/providers/claude-code.js +206 -0
  39. package/src/manager/libraries/ai/providers/openai.js +934 -0
  40. package/src/manager/libraries/disposable-domains.json +2 -0
  41. package/src/manager/libraries/email/generators/lib/filter.js +179 -0
  42. package/src/manager/libraries/email/generators/lib/image-host.js +231 -0
  43. package/src/manager/libraries/email/generators/lib/mjml-template.js +83 -0
  44. package/src/manager/libraries/email/generators/lib/structure.js +278 -0
  45. package/src/manager/libraries/email/generators/lib/svg-illustrator.js +184 -0
  46. package/src/manager/libraries/email/generators/lib/templates/classic-schema.js +63 -0
  47. package/src/manager/libraries/email/generators/lib/templates/clean.js +82 -0
  48. package/src/manager/libraries/email/generators/lib/templates/editorial-helpers.js +100 -0
  49. package/src/manager/libraries/email/generators/lib/templates/editorial.js +317 -0
  50. package/src/manager/libraries/email/generators/lib/templates/field-report-helpers.js +138 -0
  51. package/src/manager/libraries/email/generators/lib/templates/field-report.js +497 -0
  52. package/src/manager/libraries/email/generators/lib/templates/index.js +28 -0
  53. package/src/manager/libraries/email/generators/lib/templates/shared.js +534 -0
  54. package/src/manager/libraries/email/generators/newsletter.js +377 -95
  55. package/src/manager/libraries/email/marketing/index.js +5 -2
  56. package/src/manager/libraries/email/providers/beehiiv.js +7 -3
  57. package/src/manager/libraries/openai.js +13 -932
  58. package/src/manager/routes/admin/post/deduplicate-image-alts.js +52 -0
  59. package/src/manager/routes/admin/post/post.js +10 -17
  60. package/templates/_.env +4 -0
  61. package/templates/_.gitignore +1 -0
  62. package/templates/backend-manager-config.json +48 -4
  63. package/test/helpers/slugify.js +394 -0
  64. package/test/marketing/fixtures/clean.json +31 -0
  65. package/test/marketing/fixtures/editorial.json +31 -0
  66. package/test/marketing/fixtures/field-report.json +54 -0
  67. package/test/marketing/newsletter-generate.js +731 -0
  68. package/test/marketing/newsletter-templates.js +512 -0
  69. package/test/routes/admin/deduplicate-image-alts.js +190 -0
@@ -0,0 +1,32 @@
1
+ As for the AMEX enrollment, AMEX alerts are not supported on Stripe/Shopify. To enable them, you’ll need a dedicated AMEX MID and a payment orchestrator to route all AMEX transactions through that dedicated MID. Using a dedicated AMEX MID allows for a dispute rate of up to 5%, so routing all AMEX traffic through it will significantly reduce your dispute rate on Stripe.
2
+
3
+ 06:44 PM
4
+ For Discover,
5
+
6
+ In order to enroll in Discover coverage, you will need to do the following:
7
+
8
+ First, provide us with details about your business below:
9
+ - Merchant Legal Name:
10
+ - Merchant DBA Name:
11
+ - Merchant Registered Street Address:
12
+ - City:
13
+ - Country:
14
+ - State/Province:
15
+ - ZIP/Postal Code:
16
+
17
+ Second, you must request from your payment processor’s support for a few pieces of information. To do so, please follow the steps below and send them the email template below.
18
+
19
+ ""I am enrolling for Discover Ethoca Alerts via my third-party vendor, Chargeblast. I need my 15-Digit Discover SE number. Can you please provide these pieces of information to me ASAP? Let me know if you need any additional info from me. Please feel free to provide me with these pieces of information piecemeal.”
20
+
21
+ 06:44 PM
22
+ For AMEX If you’d like to proceed with obtaining a dedicated AMEX MID, we’d be happy to arrange an introduction for you.
23
+
24
+ 06:45 PM
25
+ Avatar of Zander
26
+ Zander
27
+ Are we still connected?
28
+
29
+ 06:56 PM
30
+ Avatar of Zander
31
+ Zander
32
+ Since this chat has been inactive, I’ll close it for now. If you have more questions, feel free to contact us at any time. Have a great day ahead!
@@ -0,0 +1,14 @@
1
+ https://github.com/disposable-email-domains/disposable-email-domains?tab=readme-ov-file
2
+ https://www.npmjs.com/package/disposable-domains
3
+ https://github.com/tompec/disposable-email-domains
4
+
5
+ Two repos:
6
+
7
+ Repo Domains Approach
8
+ disposable-email-domains/disposable-email-domains 5,359 Curated, conservative, high confidence
9
+ ivolo/disposable-email-domains 121,569 Aggressive, aggregated from many sources, more false positives
10
+ Our current list has 854 — so even the smaller curated list is 6x larger.
11
+
12
+ For our use case (currently only used to skip marketing sync, not blocking signups), I'd recommend the 5,359 curated list — it's comprehensive enough without being overly aggressive. And if we ever do use it for blocking signups, the false positive risk is much lower.
13
+
14
+ Want to swap to that one?
@@ -0,0 +1,24 @@
1
+ # Admin Post Route
2
+
3
+ The `POST /admin/post` route creates blog posts via GitHub's API. It handles image extraction, upload, and body rewriting.
4
+
5
+ ## Image Processing Flow
6
+
7
+ 1. Receives markdown body with external image URLs (e.g., `![alt](https://images.unsplash.com/...)`)
8
+ 2. Extracts all `![alt](url)` patterns from the body using regex
9
+ 3. Downloads each image and uploads it to `src/assets/images/blog/post-{id}/` on GitHub
10
+ 4. **Rewrites the body** to replace external URLs with `@post/{filename}` format
11
+ 5. The `@post/` prefix is resolved at Jekyll build time by `jekyll-uj-powertools` to the full path
12
+
13
+ ## Key Details
14
+
15
+ - Image filenames are derived from `hyphenate(alt_text)` + downloaded extension
16
+ - Header image (`headerImageURL`) is uploaded but NOT rewritten in the body (it's in frontmatter)
17
+ - Failed image downloads are skipped — the original external URL stays in the body
18
+ - The `extractImages()` function returns a URL mapping used for body rewriting
19
+
20
+ ## Files
21
+
22
+ - `src/manager/routes/admin/post/post.js` — POST handler (create)
23
+ - `src/manager/routes/admin/post/put.js` — PUT handler (edit)
24
+ - `src/manager/routes/admin/post/templates/post.html` — Post template
@@ -0,0 +1,23 @@
1
+ # AI Library
2
+
3
+ `Manager.AI(assistant).request({ provider, model, messages, ... })` is the unified entry for all AI calls. Provider-agnostic surface — same options shape, same return shape.
4
+
5
+ | Provider | Default model | Notes |
6
+ |---|---|---|
7
+ | `openai` | `gpt-5-mini` | Better at structured JSON via JSON schema |
8
+ | `anthropic` | `claude-sonnet-4-6` | Better at SVG illustrations and creative output |
9
+
10
+ Return shape (same for all providers): `{ content, output, tokens, raw }`.
11
+
12
+ `options.response: 'json'` triggers JSON parsing — both providers strip fences and parse with JSON5 for robustness. `options.schema` enforces structure on OpenAI (real JSON schema) and is injected into the system prompt on Anthropic.
13
+
14
+ API keys: `BACKEND_MANAGER_OPENAI_API_KEY`, `BACKEND_MANAGER_ANTHROPIC_API_KEY` (process.env or config).
15
+
16
+ The legacy `src/manager/libraries/openai.js` is a thin compatibility shim that re-exports the OpenAI provider class — existing callers using `new OpenAI(assistant, key)` still work unchanged.
17
+
18
+ | File | Purpose |
19
+ |---|---|
20
+ | `src/manager/libraries/ai/index.js` | Unified `AI` class (dispatches by provider) |
21
+ | `src/manager/libraries/ai/providers/openai.js` | OpenAI provider (original `openai.js` content) |
22
+ | `src/manager/libraries/ai/providers/anthropic.js` | Anthropic provider (Claude Messages API) |
23
+ | `src/manager/libraries/openai.js` | Back-compat shim → providers/openai.js |
@@ -0,0 +1,31 @@
1
+ # Architecture
2
+
3
+ ## Manager Class
4
+
5
+ The core `Manager` class (in `src/manager/index.js`) extends EventEmitter and orchestrates all functionality:
6
+ - Initializes Firebase Admin SDK
7
+ - Sets up built-in Cloud Functions (`bm_api`, auth events, cron)
8
+ - Provides factory methods for helper classes
9
+ - Manages configuration from multiple sources
10
+
11
+ ## Dual-Mode Support
12
+
13
+ BEM supports two deployment modes:
14
+ - **Firebase Functions** (`projectType: 'firebase'`): Cloud Functions with Firebase triggers
15
+ - **Custom Server** (`projectType: 'custom'`): Express server for non-Firebase deployments
16
+
17
+ ## Helper Factory Pattern
18
+
19
+ All helpers are accessed via factory methods on the Manager instance:
20
+
21
+ ```javascript
22
+ Manager.Assistant({ req, res }) // Request handler
23
+ Manager.User(data) // User properties
24
+ Manager.Analytics({ assistant }) // GA4 events
25
+ Manager.Usage() // Rate limiting
26
+ Manager.Middleware(req, res) // Request pipeline
27
+ Manager.Settings() // Schema validation
28
+ Manager.Utilities() // Batch operations
29
+ Manager.Metadata(doc) // Timestamps/tags
30
+ Manager.storage({ name }) // Local JSON storage (lowdb)
31
+ ```
@@ -0,0 +1,74 @@
1
+ # Auth Hooks (Consumer Project)
2
+
3
+ Auth hooks let consumer projects inject custom logic into BEM's auth event lifecycle. BEM runs its core handler first, then looks for a matching hook at `hooks/auth/{event-name}.js`.
4
+
5
+ | Hook | File | Behavior |
6
+ |------|------|----------|
7
+ | `before-create` | `hooks/auth/before-create.js` | Runs after BEM's disposable email + rate limit checks. **Can throw `HttpsError` to block signup.** |
8
+ | `before-signin` | `hooks/auth/before-signin.js` | Runs after BEM's activity update. **Can throw `HttpsError` to block sign-in.** |
9
+ | `on-create` | `hooks/auth/on-create.js` | Runs after BEM creates the user doc. **Non-blocking** — errors are caught and logged. |
10
+ | `on-delete` | `hooks/auth/on-delete.js` | Runs after BEM deletes the user doc. **Non-blocking** — errors are caught and logged. |
11
+
12
+ Hook signature (same as BEM's internal handlers):
13
+
14
+ ```javascript
15
+ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
16
+ // user: AuthUserRecord (uid, email, providerData, etc.)
17
+ // context: AuthEventContext for blocking functions (ipAddress, userAgent, additionalUserInfo)
18
+ // EventContext for triggers (eventId, eventType, timestamp — no IP/userAgent)
19
+ // libraries: { admin, functions, ... }
20
+ };
21
+ ```
22
+
23
+ ## Blocking hook example (before-create)
24
+
25
+ ```javascript
26
+ // hooks/auth/before-create.js — Only allow Google OAuth signups
27
+ const ENFORCE = true;
28
+
29
+ const ALLOWED_PROVIDERS = ['google.com'];
30
+
31
+ module.exports = async ({ assistant, user, context, libraries }) => {
32
+ if (!ENFORCE) { return; }
33
+
34
+ const { functions } = libraries;
35
+ const provider = context.additionalUserInfo?.providerId;
36
+
37
+ if (!ALLOWED_PROVIDERS.includes(provider)) {
38
+ assistant.error(`hook/before-create: Blocked provider '${provider}' for ${user.email}`);
39
+ throw new functions.auth.HttpsError('permission-denied', 'Please sign up with Google.');
40
+ }
41
+ };
42
+ ```
43
+
44
+ ## Non-blocking hook example (on-create)
45
+
46
+ ```javascript
47
+ // hooks/auth/on-create.js — Auto-delete spam referrals
48
+ const powertools = require('node-powertools');
49
+
50
+ const ENFORCE = true;
51
+ const BLOCKED_AFFILIATE_CODES = ['iLvQjmvm'];
52
+
53
+ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
54
+ if (!ENFORCE) { return; }
55
+
56
+ const { admin } = libraries;
57
+ const uid = user.uid;
58
+
59
+ // Poll until signup route attaches attribution.affiliate.code
60
+ let referredBy = null;
61
+
62
+ await powertools.poll(async () => {
63
+ const userDoc = await admin.firestore().doc(`users/${uid}`).get().catch(() => null);
64
+ if (!userDoc?.exists) { return true; }
65
+ referredBy = userDoc.data()?.attribution?.affiliate?.code;
66
+ return !!referredBy;
67
+ }, { interval: 10000, timeout: 60000 }).catch(() => {});
68
+
69
+ if (!referredBy || !BLOCKED_AFFILIATE_CODES.includes(referredBy)) { return; }
70
+
71
+ // Delete spam account (triggers on-delete for cleanup)
72
+ await admin.auth().deleteUser(uid).catch(e => assistant.error('Delete failed:', e));
73
+ };
74
+ ```
@@ -0,0 +1,59 @@
1
+ # CLI: Firestore & Auth Commands
2
+
3
+ Quick commands for reading/writing Firestore and managing Auth users directly from the terminal. Works in any BEM consumer project (requires `functions/service-account.json` for production, or `--emulator` for local).
4
+
5
+ **IMPORTANT: All CLI commands (`npx mgr ...`) MUST be run from the consumer project's `functions/` subdirectory** (e.g., `cd /path/to/my-project/functions && npx mgr ...`). The `mgr` binary lives in `functions/node_modules/.bin/` — running from the project root or any other directory will fail.
6
+
7
+ For log commands, see [docs/cli-logs.md](cli-logs.md).
8
+
9
+ ## Firestore Commands
10
+
11
+ ```bash
12
+ npx mgr firestore:get <path> # Read a document
13
+ npx mgr firestore:set <path> '<json>' # Write/merge a document
14
+ npx mgr firestore:set <path> '<json>' --no-merge # Overwrite a document entirely
15
+ npx mgr firestore:query <collection> # Query a collection (default limit 25)
16
+ --where "field==value" # Filter (repeatable for AND)
17
+ --orderBy "field:desc" # Sort
18
+ --limit N # Limit results
19
+ npx mgr firestore:delete <path> # Delete a document (prompts for confirmation)
20
+ ```
21
+
22
+ ## Auth Commands
23
+
24
+ ```bash
25
+ npx mgr auth:get <uid-or-email> # Get user by UID or email (auto-detected via @)
26
+ npx mgr auth:list [--limit N] [--page-token T] # List users (default 100)
27
+ npx mgr auth:delete <uid-or-email> # Delete user (prompts for confirmation)
28
+ npx mgr auth:set-claims <uid-or-email> '<json>' # Set custom claims
29
+ ```
30
+
31
+ ## Shared Flags
32
+
33
+ | Flag | Description |
34
+ |------|-------------|
35
+ | `--emulator` | Target local emulator instead of production |
36
+ | `--force` | Skip confirmation on destructive operations |
37
+ | `--raw` | Compact JSON output (for piping to `jq` etc.) |
38
+
39
+ ## Examples
40
+
41
+ ```bash
42
+ # Read a user document from production
43
+ npx mgr firestore:get users/abc123
44
+
45
+ # Write to emulator
46
+ npx mgr firestore:set users/test123 '{"name":"Test User"}' --emulator
47
+
48
+ # Query with filters
49
+ npx mgr firestore:query users --where "subscription.status==active" --limit 10
50
+
51
+ # Look up auth user by email
52
+ npx mgr auth:get user@example.com
53
+
54
+ # Set admin claims
55
+ npx mgr auth:set-claims user@example.com '{"admin":true}'
56
+
57
+ # Delete from emulator (no confirmation needed)
58
+ npx mgr firestore:delete users/test123 --emulator
59
+ ```
@@ -0,0 +1,67 @@
1
+ # CLI: Logs Commands
2
+
3
+ Fetch or stream Cloud Function logs from Google Cloud Logging. Requires `gcloud` CLI installed and authenticated. Auto-resolves the project ID from `service-account.json`, `.firebaserc`, or `GCLOUD_PROJECT`.
4
+
5
+ > All `npx mgr ...` commands must be run from the consumer project's `functions/` subdirectory. See [docs/cli-firestore-auth.md](cli-firestore-auth.md) for the explanation.
6
+
7
+ ## Commands
8
+
9
+ ```bash
10
+ npx mgr logs:read # Read last 1h of logs (default: 300 entries, newest first)
11
+ npx mgr logs:read --fn bm_api # Filter by function name
12
+ npx mgr logs:read --fn bm_api --severity ERROR # Filter by severity (DEBUG, INFO, WARNING, ERROR, CRITICAL)
13
+ npx mgr logs:read --since 2d --limit 100 # Custom time range and limit
14
+ npx mgr logs:read --search "72.134.242.25" # Search textPayload for a string (IP, email, error, etc.)
15
+ npx mgr logs:read --fn bm_authBeforeCreate --search "ian@example.com" --since 7d # Combined filters
16
+ npx mgr logs:read --order asc # Oldest first (default: desc/newest first)
17
+ npx mgr logs:read --filter 'jsonPayload.level="error"' # Raw gcloud filter passthrough
18
+ npx mgr logs:tail # Stream live logs
19
+ npx mgr logs:tail --fn bm_paymentsWebhookOnWrite # Stream filtered live logs
20
+ ```
21
+
22
+ Both commands save output to `functions/logs.log` (overwritten on each run). `logs:read` saves raw JSON; `logs:tail` streams text.
23
+
24
+ **Cloud Logs vs Local Logs:** These commands query **production** Google Cloud Logging. For **local/dev** logs, read `functions/serve.log` (from `npx mgr serve`) or `functions/emulator.log` (from `npx mgr test`) directly — they are plain text files, not gcloud.
25
+
26
+ ## Flags
27
+
28
+ | Flag | Description | Default | Commands |
29
+ |------|-------------|---------|----------|
30
+ | `--fn <name>` | Filter by Cloud Function name (see table below) | all | both |
31
+ | `--severity <level>` | Minimum severity: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` | all | both |
32
+ | `--search <text>` | Search textPayload for a substring (IP, email, uid, error message) | none | both |
33
+ | `--filter <expr>` | Raw gcloud logging filter expression (appended to built-in filters) | none | both |
34
+ | `--since <duration>` | Time range (`30m`, `1h`, `2d`, `1w`) | `1h` | read only |
35
+ | `--limit <n>` | Max entries | `300` | read only |
36
+ | `--order <dir>` | Sort order: `asc` (oldest first) or `desc` (newest first) | `desc` | read only |
37
+ | `--interval <sec>` | Polling interval in seconds | `5` | tail only |
38
+ | `--raw` | Output raw JSON | false | both |
39
+
40
+ ## `--fn` Function Name Reference
41
+
42
+ The `--fn` flag uses the **deployed Cloud Function name**, not the route path.
43
+
44
+ **BEM built-in functions (always deployed):**
45
+
46
+ | Function name | Type | Description |
47
+ |---------------|------|-------------|
48
+ | `bm_api` | HTTPS | Main API router — all consumer routes (GET/POST/PUT/DELETE) go through this |
49
+ | `bm_authBeforeCreate` | Auth blocking | Before user creation: disposable email blocking, IP rate limiting, consumer hooks |
50
+ | `bm_authBeforeSignIn` | Auth blocking | Before sign-in: consumer hooks |
51
+ | `bm_authOnCreate` | Auth event | After user creation: user doc setup |
52
+ | `bm_authOnDelete` | Auth event | After user deletion |
53
+ | `bm_paymentsWebhookOnWrite` | Firestore trigger | Processes payment webhooks |
54
+ | `bm_paymentsDisputeOnWrite` | Firestore trigger | Processes payment disputes |
55
+ | `bm_notificationsOnWrite` | Firestore trigger | Sends push notifications |
56
+ | `bm_cronDaily` | Scheduled | Daily cron (midnight UTC) |
57
+ | `bm_cronFrequent` | Scheduled | Frequent cron (every 10 min) |
58
+
59
+ **Consumer-defined functions** use the export name from `functions/index.js` (e.g., `exports.items = ...` → `--fn items`).
60
+
61
+ **Quick lookup — which function to query:**
62
+ - API route errors → `--fn bm_api`
63
+ - Signup/auth blocked → `--fn bm_authBeforeCreate`
64
+ - Sign-in issues → `--fn bm_authBeforeSignIn`
65
+ - User doc not created → `--fn bm_authOnCreate`
66
+ - Payment not processing → `--fn bm_paymentsWebhookOnWrite`
67
+ - Cron job issues → `--fn bm_cronDaily` or `--fn bm_cronFrequent`
@@ -0,0 +1,67 @@
1
+ # Code Patterns
2
+
3
+ ## Short-Circuit Returns
4
+
5
+ Use early returns instead of nested conditionals:
6
+
7
+ ```javascript
8
+ // CORRECT
9
+ function handler(data) {
10
+ if (!data) {
11
+ return assistant.errorify('Missing data', { code: 400 });
12
+ }
13
+
14
+ // Main logic here
15
+ return assistant.respond({ success: true });
16
+ }
17
+
18
+ // INCORRECT
19
+ function handler(data) {
20
+ if (data) {
21
+ // Main logic here
22
+ return assistant.respond({ success: true });
23
+ }
24
+ }
25
+ ```
26
+
27
+ ## Logical Operators on New Lines
28
+
29
+ Place operators at the start of continuation lines:
30
+
31
+ ```javascript
32
+ // CORRECT
33
+ const isValid = hasPermission
34
+ || isAdmin
35
+ || isOwner;
36
+
37
+ // INCORRECT
38
+ const isValid = hasPermission ||
39
+ isAdmin ||
40
+ isOwner;
41
+ ```
42
+
43
+ ## Firestore Document Access
44
+
45
+ Use shorthand `.doc()` path:
46
+
47
+ ```javascript
48
+ // CORRECT
49
+ admin.firestore().doc('users/abc123')
50
+
51
+ // INCORRECT
52
+ admin.firestore().collection('users').doc('abc123')
53
+ ```
54
+
55
+ ## Template Strings for Requires
56
+
57
+ ```javascript
58
+ // CORRECT
59
+ require(`${functionsDir}/node_modules/backend-manager`)
60
+
61
+ // INCORRECT
62
+ require(functionsDir + '/node_modules/backend-manager')
63
+ ```
64
+
65
+ ## Prefer fs-jetpack
66
+
67
+ Use `fs-jetpack` over `fs` or `fs-extra` for file operations.
@@ -0,0 +1,64 @@
1
+ # Common Operations
2
+
3
+ Inside-the-handler patterns for the most frequent operations. See [docs/routes.md](routes.md) for the route file structure itself.
4
+
5
+ ## Authenticate User
6
+
7
+ ```javascript
8
+ const user = await assistant.authenticate();
9
+ if (!user.authenticated) {
10
+ return assistant.errorify('Authentication required', { code: 401 });
11
+ }
12
+ ```
13
+
14
+ ## Read/Write Firestore
15
+
16
+ ```javascript
17
+ const { admin } = Manager.libraries;
18
+
19
+ // Read
20
+ const doc = await admin.firestore().doc('users/abc123').get();
21
+ const data = doc.data();
22
+
23
+ // Write
24
+ await admin.firestore().doc('users/abc123').set({ field: 'value' }, { merge: true });
25
+ ```
26
+
27
+ ## Handle Errors
28
+
29
+ ```javascript
30
+ // Send error response
31
+ assistant.errorify('Something went wrong', { code: 500, sentry: true });
32
+
33
+ // Or throw to reject
34
+ return reject(assistant.errorify('Bad request', { code: 400 }));
35
+ ```
36
+
37
+ ## Send Response
38
+
39
+ ```javascript
40
+ // Success
41
+ assistant.respond({ success: true, data: result });
42
+
43
+ // With custom status
44
+ assistant.respond({ created: true }, { code: 201 });
45
+
46
+ // Redirect
47
+ assistant.respond('https://example.com', { code: 302 });
48
+ ```
49
+
50
+ ## Use Hooks (Consumer Project)
51
+
52
+ ```javascript
53
+ Manager.handlers.bm_api = function (mod, position) {
54
+ const assistant = mod.assistant;
55
+ const command = assistant.request.data.command;
56
+
57
+ return new Promise(async function(resolve, reject) {
58
+ if (position === 'pre' && command === 'user:sign-up') {
59
+ // Before sign-up logic
60
+ }
61
+ return resolve();
62
+ });
63
+ };
64
+ ```
@@ -0,0 +1,119 @@
1
+ # Directory Structure
2
+
3
+ ## BEM Library (this repo)
4
+
5
+ ```
6
+ src/
7
+ manager/
8
+ index.js # Main Manager class
9
+ helpers/ # Helper classes
10
+ assistant.js # Request/response handling
11
+ user.js # User property structure + schema
12
+ analytics.js # GA4 integration
13
+ usage.js # Rate limiting
14
+ middleware.js # Request pipeline
15
+ settings.js # Schema validation
16
+ utilities.js # Batch operations
17
+ metadata.js # Timestamps/tags
18
+ libraries/
19
+ payment/ # Shared payment utilities
20
+ order-id.js # Order ID generation (XXXX-XXXX-XXXX)
21
+ processors/ # Payment processor libraries
22
+ stripe.js # Stripe SDK init, fetchResource, toUnified*, resolvePriceId
23
+ paypal.js # PayPal fetchResource, toUnified* (custom_id parsing)
24
+ test.js # Test processor (delegates to Stripe shapes)
25
+ events/ # All event-driven code
26
+ auth/ # Auth event handlers (hookable)
27
+ before-create.js # Disposable email blocking + IP rate limiting
28
+ before-signin.js # Activity update + sign-in analytics
29
+ on-create.js # User doc creation
30
+ on-delete.js # User doc deletion + marketing cleanup
31
+ utils.js # Shared utilities (retryWrite, runAuthHook)
32
+ cron/ # Cron job runners
33
+ runner.js # Shared cron job runner (BEM + consumer hooks)
34
+ daily.js # Daily cron entry point
35
+ daily/{job}.js # Individual daily cron jobs
36
+ frequent.js # Frequent cron entry point
37
+ frequent/{job}.js # Individual frequent cron jobs
38
+ firestore/ # Firestore triggers
39
+ payments-webhooks/ # Webhook processing pipeline
40
+ on-write.js # Orchestrator: fetch→transform→transition→write
41
+ analytics.js # Payment analytics tracking (GA4, Meta, TikTok)
42
+ transitions/ # State transition detection + handlers
43
+ index.js # Transition detection logic
44
+ send-email.js # Shared email helper for handlers
45
+ subscription/ # Subscription transition handlers
46
+ one-time/ # One-time payment transition handlers
47
+ functions/core/ # Built-in functions
48
+ actions/
49
+ api.js # Main bm_api handler
50
+ api/{category}/{action}.js # API command handlers
51
+ routes/ # Built-in routes
52
+ admin/
53
+ post/ # POST /admin/post - Create blog posts via GitHub
54
+ post.js # Extracts images, uploads to GitHub, rewrites body to @post/ format
55
+ put.js # PUT /admin/post - Edit existing posts
56
+ templates/
57
+ post.html # Post frontmatter template
58
+ payments/
59
+ intent/ # POST /payments/intent
60
+ post.js # Intent creation orchestrator
61
+ processors/ # Per-processor intent creators
62
+ stripe.js # Stripe Checkout Session creation
63
+ paypal.js # PayPal subscription + one-time order creation
64
+ test.js # Test processor (auto-fires webhooks)
65
+ webhook/ # POST /payments/webhook
66
+ post.js # Webhook ingestion + Firestore write
67
+ processors/ # Per-processor webhook parsers
68
+ stripe.js # Stripe event parsing + categorization
69
+ paypal.js # PayPal event parsing + categorization
70
+ test.js # Test processor (delegates to Stripe)
71
+ cancel/ # POST /payments/cancel
72
+ processors/
73
+ stripe.js # Stripe cancel_at_period_end
74
+ paypal.js # PayPal subscription cancel
75
+ test.js # Test cancel (writes webhook doc)
76
+ refund/ # POST /payments/refund
77
+ processors/
78
+ stripe.js # Stripe refund + immediate cancel
79
+ paypal.js # PayPal refund + cancel
80
+ test.js # Test refund (writes webhook doc)
81
+ portal/ # POST /payments/portal
82
+ processors/
83
+ stripe.js # Stripe billing portal URL
84
+ paypal.js # PayPal management URL
85
+ schemas/ # Built-in schemas
86
+ cli/
87
+ index.js # CLI entry point
88
+ commands/ # CLI commands
89
+ test/
90
+ test-accounts.js # Test account definitions (static + journey)
91
+ templates/
92
+ backend-manager-config.json # Config template
93
+ ```
94
+
95
+ ## Consumer Project Structure
96
+
97
+ ```
98
+ functions/
99
+ index.js # Manager.init() + custom functions
100
+ backend-manager-config.json # App configuration
101
+ service-account.json # Firebase credentials
102
+ routes/
103
+ {endpoint}/
104
+ index.js # All methods handler
105
+ get.js # GET handler
106
+ post.js # POST handler
107
+ schemas/
108
+ {endpoint}/
109
+ index.js # Schema definition
110
+ hooks/
111
+ auth/
112
+ before-create.js # Custom pre-signup checks (can block)
113
+ before-signin.js # Custom pre-signin checks (can block)
114
+ on-create.js # Post-signup side effects (non-blocking)
115
+ on-delete.js # Post-deletion side effects (non-blocking)
116
+ cron/
117
+ daily/
118
+ {job}.js # Custom daily jobs
119
+ ```
@@ -0,0 +1,7 @@
1
+ # Environment Detection
2
+
3
+ ```javascript
4
+ assistant.isDevelopment() // true when ENVIRONMENT !== 'production' or in emulator
5
+ assistant.isProduction() // true when ENVIRONMENT === 'production'
6
+ assistant.isTesting() // true when running tests (via npx mgr test)
7
+ ```
@@ -0,0 +1,11 @@
1
+ # File Naming Conventions
2
+
3
+ | Type | Location | Naming |
4
+ |------|----------|--------|
5
+ | Routes | `routes/{name}/` | `index.js` or `{method}.js` |
6
+ | Schemas | `schemas/{name}/` | `index.js` or `{method}.js` |
7
+ | API Commands | `actions/api/{category}/` | `{action}.js` |
8
+ | Auth Events | `events/auth/` | `{event}.js` |
9
+ | Auth Hooks (consumer) | `hooks/auth/` | `{event}.js` |
10
+ | Cron Jobs (BEM) | `events/cron/daily/` | `{job}.js` |
11
+ | Cron Jobs (consumer) | `hooks/cron/daily/` | `{job}.js` |