backend-manager 5.0.202 → 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.
- package/CHANGELOG.md +61 -0
- package/CLAUDE.md +43 -1501
- package/docs/admin-post-route.md +24 -0
- package/docs/ai-library.md +23 -0
- package/docs/architecture.md +31 -0
- package/docs/auth-hooks.md +74 -0
- package/docs/cli-firestore-auth.md +59 -0
- package/docs/cli-logs.md +67 -0
- package/docs/code-patterns.md +67 -0
- package/docs/common-operations.md +64 -0
- package/docs/directory-structure.md +119 -0
- package/docs/environment-detection.md +7 -0
- package/docs/file-naming.md +11 -0
- package/docs/marketing-campaigns.md +244 -0
- package/docs/marketing-fields.md +25 -0
- package/docs/mcp.md +95 -0
- package/docs/payment-system.md +325 -0
- package/docs/response-headers.md +7 -0
- package/docs/routes.md +126 -0
- package/docs/sanitization.md +61 -0
- package/docs/schemas.md +39 -0
- package/docs/stripe-webhook-forwarding.md +18 -0
- package/docs/testing.md +129 -0
- package/docs/usage-rate-limiting.md +67 -0
- package/package.json +8 -4
- package/src/defaults/CHANGELOG.md +15 -0
- package/src/defaults/CLAUDE.md +8 -4
- package/src/defaults/docs/README.md +17 -0
- package/src/defaults/test/README.md +33 -0
- package/src/manager/events/cron/daily/marketing-newsletter-generate.js +48 -8
- package/src/manager/functions/core/actions/api/admin/create-post.js +3 -27
- package/src/manager/helpers/settings.js +26 -7
- package/src/manager/helpers/utilities.js +21 -0
- package/src/manager/index.js +1 -1
- package/src/manager/libraries/ai/index.js +162 -0
- package/src/manager/libraries/ai/providers/anthropic.js +193 -0
- package/src/manager/libraries/ai/providers/claude-code.js +206 -0
- package/src/manager/libraries/ai/providers/openai.js +934 -0
- package/src/manager/libraries/disposable-domains.json +2 -0
- package/src/manager/libraries/email/generators/lib/filter.js +179 -0
- package/src/manager/libraries/email/generators/lib/image-host.js +231 -0
- package/src/manager/libraries/email/generators/lib/mjml-template.js +83 -0
- package/src/manager/libraries/email/generators/lib/structure.js +278 -0
- package/src/manager/libraries/email/generators/lib/svg-illustrator.js +184 -0
- package/src/manager/libraries/email/generators/lib/templates/classic-schema.js +63 -0
- package/src/manager/libraries/email/generators/lib/templates/clean.js +82 -0
- package/src/manager/libraries/email/generators/lib/templates/editorial-helpers.js +100 -0
- package/src/manager/libraries/email/generators/lib/templates/editorial.js +317 -0
- package/src/manager/libraries/email/generators/lib/templates/field-report-helpers.js +138 -0
- package/src/manager/libraries/email/generators/lib/templates/field-report.js +497 -0
- package/src/manager/libraries/email/generators/lib/templates/index.js +28 -0
- package/src/manager/libraries/email/generators/lib/templates/shared.js +534 -0
- package/src/manager/libraries/email/generators/newsletter.js +377 -95
- package/src/manager/libraries/email/marketing/index.js +5 -2
- package/src/manager/libraries/email/providers/beehiiv.js +7 -3
- package/src/manager/libraries/openai.js +13 -932
- package/src/manager/routes/admin/post/deduplicate-image-alts.js +52 -0
- package/src/manager/routes/admin/post/post.js +10 -17
- package/templates/_.env +4 -0
- package/templates/_.gitignore +1 -0
- package/templates/backend-manager-config.json +48 -4
- package/test/helpers/slugify.js +394 -0
- package/test/marketing/fixtures/clean.json +31 -0
- package/test/marketing/fixtures/editorial.json +31 -0
- package/test/marketing/fixtures/field-report.json +54 -0
- package/test/marketing/newsletter-generate.js +731 -0
- package/test/marketing/newsletter-templates.js +512 -0
- package/test/routes/admin/deduplicate-image-alts.js +190 -0
|
@@ -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., ``)
|
|
8
|
+
2. Extracts all `` 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
|
+
```
|
package/docs/cli-logs.md
ADDED
|
@@ -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` |
|