Browse Source

fix: replace hardcoded URLs with environment variables #3

- Added BACKEND_URL and FRONTEND_URL environment variables to backend
- Created .env.example file for backend with all required configuration
- Updated api/index.js to use env vars for all OAuth callbacks and redirects
- Updated CLAUDE.md with new environment variable documentation
- Updated DEPLOYMENT_GUIDE.md with Vercel backend configuration
- Updated SHOPRENTER.md with note about configurable URLs

All hardcoded production URLs now use environment variables with sensible
defaults for both development and production environments.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude 5 months ago
parent
commit
56051947af
5 changed files with 110 additions and 24 deletions
  1. 30 8
      CLAUDE.md
  2. 29 0
      DEPLOYMENT_GUIDE.md
  3. 6 0
      SHOPRENTER.md
  4. 28 0
      shopcall.ai-backend-main/.env.example
  5. 17 16
      shopcall.ai-backend-main/api/index.js

+ 30 - 8
CLAUDE.md

@@ -166,19 +166,38 @@ npm start                # Start server (node api/index)
 
 ### Backend `.env` (shopcall.ai-backend-main)
 ```bash
+# Supabase Configuration
 SUPABASE_URL=https://ztklqodcdjeqpsvhlpud.supabase.co
 SUPABASE_ANON_KEY=<anon_key>
+
+# URL Configuration
+BACKEND_URL=https://shopcall-ai-backend.vercel.app  # Backend API base URL
+FRONTEND_URL=https://shopcall.ai                     # Frontend application URL
+
+# OAuth Configuration
 SHOPIFY_API_KEY=<shopify_api_key>
 SHOPIFY_API_SECRET=<shopify_api_secret>
+SHOPRENTER_CLIENT_ID=<shoprenter_client_id>
+SHOPRENTER_CLIENT_SECRET=<shoprenter_client_secret>
+
+# Email Configuration
 EMAIL_USER=<gmail_address>
 EMAIL_PASSWORD=<gmail_app_password>
+
+# Environment
 NODE_ENV=production|development
 ```
 
-### Frontend Environment
-- No `.env` file in shopcall.ai-main
-- Backend API URL hardcoded: `https://shopcall-ai-backend.vercel.app`
-- Frontend URL hardcoded: `https://shopcall.ai`
+### Frontend `.env` (shopcall.ai-main)
+```bash
+# Backend API Base URL
+VITE_API_URL=https://ztklqodcdjeqpsvhlpud.supabase.co/functions/v1
+
+# Frontend URL (for OAuth callbacks)
+VITE_FRONTEND_URL=https://shopcall.ai
+```
+
+**Note**: See `.env.example` files in both repositories for complete configuration templates.
 
 ## Deployment
 
@@ -192,7 +211,8 @@ Both applications deploy to Vercel:
 }
 ```
 - All routes proxy to `/api/index.js`
-- Deployed at: `https://shopcall-ai-backend.vercel.app`
+- Deployed at: `https://shopcall-ai-backend.vercel.app` (configure via `BACKEND_URL` env var)
+- **Important**: Set `BACKEND_URL` and `FRONTEND_URL` environment variables in Vercel project settings
 
 **Frontend** (`vercel.json`):
 ```json
@@ -201,7 +221,8 @@ Both applications deploy to Vercel:
 }
 ```
 - SPA routing handled by React Router
-- Deployed at: `https://shopcall.ai`
+- Deployed at: `https://shopcall.ai` (configure via `VITE_FRONTEND_URL` env var)
+- **Important**: Set `VITE_API_URL` and `VITE_FRONTEND_URL` environment variables in Vercel project settings
 
 ## Development Workflow
 
@@ -217,10 +238,11 @@ When making changes across both repositories:
 
 - All backend logic is in a single file (`api/index.js`)
 - In-memory stores (`pendingSignups`, `nonceStore`) will reset on serverless function cold starts
-- Frontend expects backend at `https://shopcall-ai-backend.vercel.app` (hardcoded)
+- **URLs are configurable via environment variables** (`BACKEND_URL`, `FRONTEND_URL`, `VITE_API_URL`, `VITE_FRONTEND_URL`)
+- Backend and frontend URLs have sensible defaults for development and production
 - Supabase handles user authentication and data storage
 - Email verification required for signup (OTP sent via Gmail)
-- Shopify/WooCommerce OAuth flows store credentials in Supabase `stores` table
+- Shopify/WooCommerce/ShopRenter OAuth flows store credentials in Supabase `stores` table
 
 ## Path Aliases
 

+ 29 - 0
DEPLOYMENT_GUIDE.md

@@ -230,6 +230,35 @@ curl -X POST https://YOUR_PROJECT.supabase.co/functions/v1/auth/login \
    - `delete_expired_pending_signups()`
    - `delete_expired_oauth_nonces()`
 
+## Vercel Backend Deployment (Alternative)
+
+If using the Vercel backend (`shopcall.ai-backend-main/`), configure these environment variables:
+
+```bash
+# Supabase Configuration
+SUPABASE_URL=https://YOUR_PROJECT.supabase.co
+SUPABASE_ANON_KEY=your_anon_key
+
+# URL Configuration
+BACKEND_URL=https://shopcall-ai-backend.vercel.app  # Your backend deployment URL
+FRONTEND_URL=https://shopcall.ai                     # Your frontend URL
+
+# OAuth Configuration
+SHOPIFY_API_KEY=your_shopify_api_key
+SHOPIFY_API_SECRET=your_shopify_api_secret
+SHOPRENTER_CLIENT_ID=your_shoprenter_client_id
+SHOPRENTER_CLIENT_SECRET=your_shoprenter_client_secret
+
+# Email Configuration
+EMAIL_USER=your_gmail_address@gmail.com
+EMAIL_PASSWORD=your_gmail_app_password
+
+# Environment
+NODE_ENV=production
+```
+
+**Important**: Set `BACKEND_URL` and `FRONTEND_URL` environment variables to match your actual deployment URLs. This allows for flexible deployment across different environments.
+
 ## Rollback Plan
 
 If you need to rollback to the old Vercel deployment:

+ 6 - 0
SHOPRENTER.md

@@ -78,6 +78,12 @@ Redirected (user redirected to our platform, not iframe)
 
 #### 2. Technical Endpoints
 
+**⚠️ Note:** The URLs below are examples. These are now configurable via environment variables:
+- `FRONTEND_URL` - for EntryPoint
+- `BACKEND_URL` - for RedirectUri and UninstallUri
+
+See `.env.example` files for configuration details.
+
 **EntryPoint (HTTPS required):**
 ```
 https://shopcall.ai/integrations/shoprenter/entry

+ 28 - 0
shopcall.ai-backend-main/.env.example

@@ -0,0 +1,28 @@
+# Supabase Configuration
+SUPABASE_URL=https://YOUR_PROJECT.supabase.co
+SUPABASE_ANON_KEY=your_supabase_anon_key_here
+
+# Backend URL (this backend's public URL)
+# For production: https://your-backend.vercel.app or https://api.yourdomain.com
+# For local dev: http://localhost:3000
+BACKEND_URL=https://shopcall-ai-backend.vercel.app
+
+# Frontend URL (where users access the application)
+# For production: https://yourdomain.com
+# For local dev: http://localhost:8080
+FRONTEND_URL=https://shopcall.ai
+
+# Shopify OAuth Configuration
+SHOPIFY_API_KEY=your_shopify_api_key
+SHOPIFY_API_SECRET=your_shopify_api_secret
+
+# ShopRenter OAuth Configuration
+SHOPRENTER_CLIENT_ID=your_shoprenter_client_id
+SHOPRENTER_CLIENT_SECRET=your_shoprenter_client_secret
+
+# Email Configuration (for OTP emails)
+EMAIL_USER=your_gmail_address@gmail.com
+EMAIL_PASSWORD=your_gmail_app_password
+
+# Environment
+NODE_ENV=production

+ 17 - 16
shopcall.ai-backend-main/api/index.js

@@ -87,14 +87,21 @@ async function sendOTPEmail(email, otp, userName) {
   }
 }
 
+// Environment URLs
+const BACKEND_URL = process.env.BACKEND_URL || (process.env.NODE_ENV === 'production'
+  ? 'https://shopcall-ai-backend.vercel.app'
+  : 'http://localhost:3000');
+
+const FRONTEND_URL = process.env.FRONTEND_URL || (process.env.NODE_ENV === 'production'
+  ? 'https://shopcall.ai'
+  : 'http://localhost:8080');
+
 // Shopify Configuration
 const config = {
   apiKey: process.env.SHOPIFY_API_KEY,
   apiSecret: process.env.SHOPIFY_API_SECRET,
   scopes: 'read_products,write_products,read_orders,write_orders,read_customers,write_customers',
-  redirectUri: process.env.NODE_ENV === 'production'
-    ? 'https://shopcall-ai-backend.vercel.app/auth/shopify/callback'
-    : 'https://shopcall-ai-backend.vercel.app/auth/shopify/callback'
+  redirectUri: `${BACKEND_URL}/auth/shopify/callback`
 };
 
 // ShopRenter Configuration
@@ -102,15 +109,9 @@ const shoprenterConfig = {
   clientId: process.env.SHOPRENTER_CLIENT_ID,
   clientSecret: process.env.SHOPRENTER_CLIENT_SECRET,
   scopes: 'product:read product:write customer:read customer:write order:read order:write webhook:read webhook:write',
-  redirectUri: process.env.NODE_ENV === 'production'
-    ? 'https://shopcall-ai-backend.vercel.app/auth/shoprenter/callback'
-    : 'http://localhost:3000/auth/shoprenter/callback',
-  entryPoint: process.env.NODE_ENV === 'production'
-    ? 'https://shopcall-ai-backend.vercel.app/auth/shoprenter'
-    : 'http://localhost:3000/auth/shoprenter',
-  uninstallUri: process.env.NODE_ENV === 'production'
-    ? 'https://shopcall-ai-backend.vercel.app/auth/shoprenter/uninstall'
-    : 'http://localhost:3000/auth/shoprenter/uninstall'
+  redirectUri: `${BACKEND_URL}/auth/shoprenter/callback`,
+  entryPoint: `${BACKEND_URL}/auth/shoprenter`,
+  uninstallUri: `${BACKEND_URL}/auth/shoprenter/uninstall`
 };
 
 // Store nonces temporarily (use Redis or database in production)
@@ -537,8 +538,8 @@ app.get('/auth/woocommerce', securedSession, (req, res) => {
     app_name: 'ShopCall.ai',
     scope: 'read_write',
     user_id: req.user.id,
-    return_url: 'https://shopcall.ai/dashboard',
-    callback_url: 'https://shopcall-ai-backend.vercel.app/auth/woocommerce/callback?platform=' + platform + '&shop_url=' + shop_url + '&phone_number=' + phone_number + '&package=' + package
+    return_url: `${FRONTEND_URL}/dashboard`,
+    callback_url: `${BACKEND_URL}/auth/woocommerce/callback?platform=${platform}&shop_url=${shop_url}&phone_number=${phone_number}&package=${package}`
   };
   const query_string = querystring.stringify(params).replace(/%20/g, '+');
   const redirect_url = store_url + endpoint + '?' + query_string;
@@ -766,7 +767,7 @@ app.get('/auth/shoprenter/callback', async (req, res) => {
     console.log(`Successfully stored ShopRenter connection for: ${shopname}`);
 
     // Redirect to dashboard
-    res.redirect('https://shopcall.ai/dashboard?connection=success&platform=shoprenter');
+    res.redirect(`${FRONTEND_URL}/dashboard?connection=success&platform=shoprenter`);
 
   } catch (error) {
     console.error('ShopRenter OAuth callback error:', error);
@@ -887,7 +888,7 @@ app.get('/auth/shopify/callback', async (req, res) => {
     // TODO: Save tokenJson.access_token securely
     console.log(`Successfully authenticated shop: ${normalizedShop}`);
 
-    res.redirect(`https://shopcall.ai/`);
+    res.redirect(`${FRONTEND_URL}/`);
   } catch (error) {
     console.error('OAuth callback error:', error);
     res.status(500).json({