Skip to content

Commit cd2979d

Browse files
committed
Fix Render deployment: Docker server/ copy, same-origin frontend, config diagnostics
- Dockerfile runtime stage was missing the server/ directory, causing a module-not-found crash on boot (server.js requires ./server/*). - Frontend no longer hard-throws when VITE_API_URL is unset; empty base URL now correctly means same-origin requests for single-service deployments. - Add startup [Config] log summary so Stripe/email live-vs-mock state is visible in logs. - Add render.yaml blueprint and RENDER.md with the full env-var reference.
1 parent 7e12281 commit cd2979d

5 files changed

Lines changed: 183 additions & 5 deletions

File tree

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ RUN npm ci --omit=dev \
3030
&& npm cache clean --force
3131

3232
COPY server.js ./
33+
COPY server ./server
3334
COPY --from=builder /app/dist ./dist
3435

3536
# Runtime directories (uploads ephemeral; /data intended for SQLite volume mounts)

RENDER.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Deploying SpectraCleanse AI on Render
2+
3+
This is the complete environment-variable reference and setup guide for running
4+
SpectraCleanse AI on [Render](https://render.com). It explains why Stripe and
5+
email verification may appear "not working" and exactly how to fix it.
6+
7+
## Why Stripe / email verification aren't working
8+
9+
The server changes behavior based on `NODE_ENV` and which secrets are present:
10+
11+
- **If `NODE_ENV` is NOT `production`** the server enables **mock checkout**
12+
(Stripe is bypassed and returns a fake success URL — no real charge) and
13+
**dev-fallback email** (verification/reset emails are only logged, never
14+
sent). This is almost always the cause of "Stripe and email aren't working."
15+
- **If `NODE_ENV` is `production` but Stripe vars are missing**, the server
16+
exits on boot with `FATAL: Stripe is not fully configured in production.`
17+
- **If SMTP vars are missing in production**, account creation still works but
18+
verification/reset emails fail to send.
19+
20+
After deploying, open your Render service **Logs** and look for the
21+
`[Config]` summary printed at startup — it tells you whether Stripe and Email
22+
are actually live or running in mock/fallback mode.
23+
24+
## Required environment variables
25+
26+
Set these in **Render → your service → Environment**. (If you deploy via the
27+
included `render.yaml` blueprint, the keys are pre-created and you just fill in
28+
the secret values.)
29+
30+
### Core
31+
32+
| Variable | Required | Value / Notes |
33+
|---|---|---|
34+
| `NODE_ENV` || `production`**this is the single most important one.** Turns off mock checkout and dev-email fallback. |
35+
| `JWT_SECRET` || A long random string. Generate: `node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"` |
36+
| `FRONTEND_URL` || Your public URL, e.g. `https://spectracleanseai.onrender.com`. Used for CORS, Stripe redirect URLs, and email links. |
37+
| `DB_PATH` || `/data/spectra.db` — must point at a **persistent disk** or data is wiped on every deploy. |
38+
| `PORT` | Auto | Render injects this automatically; the server reads it. Do not hard-code. |
39+
| `APP_BASE_URL` | Optional | Base URL for email links. Falls back to `FRONTEND_URL`, so usually unnecessary. |
40+
| `ALLOWED_ORIGINS` | Optional | Comma-separated extra CORS origins. Only needed if the frontend is on a different domain than the API. |
41+
42+
### Stripe — all four required for live checkout
43+
44+
| Variable | Where to find it |
45+
|---|---|
46+
| `STRIPE_SECRET_KEY` | Stripe Dashboard → Developers → API keys → Secret key (`sk_live_…`) |
47+
| `STRIPE_WEBHOOK_SECRET` | Stripe Dashboard → Developers → Webhooks → your endpoint → Signing secret (`whsec_…`) |
48+
| `STRIPE_CREATOR_PRICE_ID` | Stripe → Products → Creator plan → Price ID (`price_…`) |
49+
| `STRIPE_STUDIO_PRICE_ID` | Stripe → Products → Studio plan → Price ID (`price_…`) |
50+
51+
If **any** of these four is missing, the server treats Stripe as unconfigured.
52+
53+
### Email / SMTP — all five required to send mail
54+
55+
| Variable | Notes |
56+
|---|---|
57+
| `SMTP_HOST` | e.g. `smtp.sendgrid.net`, `smtp.resend.com`, `smtp.gmail.com` |
58+
| `SMTP_PORT` | `587` (STARTTLS) or `465` (implicit TLS) |
59+
| `SMTP_USER` | SMTP username (for SendGrid the literal string `apikey`) |
60+
| `SMTP_PASS` | SMTP password / API key |
61+
| `SMTP_FROM` | From address, e.g. `SpectraCleanse <no-reply@spectracleanse.com>` |
62+
63+
If **any** of these five is missing, verification/reset emails won't send in production.
64+
65+
### Optional
66+
67+
| Variable | Notes |
68+
|---|---|
69+
| `GEMINI_API_KEY` | Only needed for the AI SEO-generation feature. |
70+
| `VITE_API_URL` | Build-time only. Leave **unset** for the default same-origin deployment. Set it only if you host the frontend separately from the API. |
71+
72+
## Setup steps
73+
74+
1. **Create the service.** Use the included `render.yaml` (New → Blueprint) or
75+
create a Web Service with **Runtime: Docker** pointing at this repo's
76+
`Dockerfile`.
77+
2. **Add a persistent disk** mounted at `/data` (≥1 GB) so the SQLite database
78+
survives deploys. Set `DB_PATH=/data/spectra.db`.
79+
3. **Fill in all environment variables** from the tables above.
80+
4. **Deploy**, then check `https://<your-service>.onrender.com/api/health`
81+
`{"status":"ok"}`.
82+
5. **Configure the Stripe webhook.** In Stripe → Developers → Webhooks, add an
83+
endpoint at `https://<your-service>.onrender.com/api/stripe-webhook` and
84+
subscribe to `checkout.session.completed` and `customer.subscription.deleted`.
85+
Copy its signing secret into `STRIPE_WEBHOOK_SECRET` and redeploy.
86+
6. **Verify the logs.** The startup `[Config]` lines should show Stripe and
87+
Email as `configured`.
88+
89+
## Quick checklist
90+
91+
- [ ] `NODE_ENV=production`
92+
- [ ] `JWT_SECRET` set to a strong random value
93+
- [ ] `FRONTEND_URL` = your Render URL
94+
- [ ] Persistent disk at `/data` + `DB_PATH=/data/spectra.db`
95+
- [ ] All 4 `STRIPE_*` vars set
96+
- [ ] Stripe webhook endpoint created and `STRIPE_WEBHOOK_SECRET` set
97+
- [ ] All 5 `SMTP_*` vars set
98+
- [ ] `/api/health` returns ok and logs show Stripe + Email `configured`

app.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ import {
1919
type SavedReleaseDefaults,
2020
} from './src/utils/releaseDefaults';
2121

22+
// When the frontend and API are served from the same origin (the default
23+
// single-service Render/Docker deployment), an empty base URL means requests
24+
// are made relative to the current origin. Only set VITE_API_URL when the API
25+
// is hosted on a different origin than the frontend.
2226
const API_BASE_URL =
2327
import.meta.env.VITE_API_URL ||
2428
(import.meta.env.DEV ? 'http://localhost:3001' : '');
25-
26-
if (!API_BASE_URL) {
27-
throw new Error('Missing VITE_API_URL in production build');
28-
}
2929
const PLATFORMS = ['General', 'YouTube', 'Spotify', 'Apple Music', 'TikTok'] as const;
3030
type Platform = typeof PLATFORMS[number];
3131
type ItemStatus = 'pending' | 'analyzing' | 'processing' | 'done' | 'error';

render.yaml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Render Blueprint for SpectraCleanse AI
2+
# Deploy: Render Dashboard → New → Blueprint → connect this repo.
3+
# Secrets (sync: false) must be filled in the Render dashboard after the first deploy.
4+
services:
5+
- type: web
6+
name: spectracleanseai
7+
runtime: docker
8+
dockerfilePath: ./Dockerfile
9+
plan: starter
10+
healthCheckPath: /api/health
11+
# Persistent disk for the SQLite database. Without this, ALL user accounts
12+
# and data are wiped on every deploy/restart.
13+
disk:
14+
name: spectra-data
15+
mountPath: /data
16+
sizeGB: 1
17+
envVars:
18+
# ── Core ────────────────────────────────────────────────────────────────
19+
- key: NODE_ENV
20+
value: production
21+
- key: DB_PATH
22+
value: /data/spectra.db
23+
- key: JWT_SECRET
24+
generateValue: true # Render generates a strong random value
25+
# Your public Render URL, e.g. https://spectracleanseai.onrender.com
26+
# Used for CORS, Stripe success/cancel URLs, and email verification links.
27+
- key: FRONTEND_URL
28+
sync: false
29+
- key: APP_BASE_URL
30+
sync: false # Optional; falls back to FRONTEND_URL
31+
- key: ALLOWED_ORIGINS
32+
sync: false # Optional; only if a separate frontend domain
33+
34+
# ── Stripe (ALL FOUR required for live checkout) ────────────────────────
35+
- key: STRIPE_SECRET_KEY
36+
sync: false
37+
- key: STRIPE_WEBHOOK_SECRET
38+
sync: false
39+
- key: STRIPE_CREATOR_PRICE_ID
40+
sync: false
41+
- key: STRIPE_STUDIO_PRICE_ID
42+
sync: false
43+
44+
# ── Email / SMTP (ALL FIVE required to send verification/reset mail) ─────
45+
- key: SMTP_HOST
46+
sync: false
47+
- key: SMTP_PORT
48+
value: "587"
49+
- key: SMTP_USER
50+
sync: false
51+
- key: SMTP_PASS
52+
sync: false
53+
- key: SMTP_FROM
54+
sync: false
55+
56+
# ── Optional ────────────────────────────────────────────────────────────
57+
- key: GEMINI_API_KEY
58+
sync: false # Only needed for AI SEO generation

server.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,28 @@ if (fs.existsSync(distPath)) {
764764
}
765765

766766
const PORT = process.env.PORT || 3001;
767-
app.listen(PORT, () => console.log(`SpectraCleanse backend on :${PORT}`));
767+
app.listen(PORT, () => {
768+
console.log(`SpectraCleanse backend on :${PORT}`);
769+
// Startup config summary – check these lines in your host's logs to confirm
770+
// Stripe and email are actually live (not running in mock / dev-fallback mode).
771+
console.log(`[Config] NODE_ENV=${process.env.NODE_ENV || '(unset → non-production defaults)'}`);
772+
console.log(
773+
`[Config] Stripe: ${
774+
STRIPE_CONFIGURED
775+
? 'configured (live checkout)'
776+
: `NOT configured${ENABLE_MOCK_CHECKOUT ? ' (mock checkout enabled – no real charges)' : ''}`
777+
}`
778+
);
779+
console.log(
780+
`[Config] Email/SMTP: ${
781+
isEmailDeliveryConfigured()
782+
? 'configured (verification & reset emails will send)'
783+
: 'NOT configured (verification/reset emails will NOT send)'
784+
}`
785+
);
786+
console.log(`[Config] Gemini SEO: ${process.env.GEMINI_API_KEY ? 'configured' : 'NOT configured'}`);
787+
console.log(`[Config] CORS origins: ${[...allowedOrigins].join(', ') || '(none)'}`);
788+
});
768789

769790
process.on('exit', () => { exiftool.end(); db.close(); });
770791
process.on('SIGTERM', () => { exiftool.end(); db.close(); process.exit(0); });

0 commit comments

Comments
 (0)