claude-all-config 3.2.0 → 3.3.1
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/VERSION +1 -1
- package/mcp.json +5 -5
- package/package.json +2 -2
- package/skills/whatsapp-business-platform/SKILL.md +430 -0
- package/skills/whatsapp-business-platform/templates/.env.template +93 -0
- package/skills/whatsapp-business-platform/templates/backend-main.go.template +105 -0
- package/skills/whatsapp-business-platform/templates/docker-compose.yml.template +142 -0
- package/skills/whatsapp-business-platform/templates/hooks.server.ts.template +116 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.3.0
|
package/mcp.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"command": "npx",
|
|
5
5
|
"args": ["-y", "@upstash/context7-mcp"],
|
|
6
6
|
"env": {
|
|
7
|
-
"CONTEXT7_API_KEY": "
|
|
7
|
+
"CONTEXT7_API_KEY": "ctx7sk-dfdd3d92-65fd-4e1d-bd1c-1bee51cbacf0"
|
|
8
8
|
}
|
|
9
9
|
},
|
|
10
10
|
"sequential-thinking": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"command": "npx",
|
|
16
16
|
"args": ["-y", "exa-mcp-server"],
|
|
17
17
|
"env": {
|
|
18
|
-
"EXA_API_KEY": "
|
|
18
|
+
"EXA_API_KEY": "8bab0085-90d5-4767-911d-6fd2f5caf6eb"
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"memory": {
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"type": "http",
|
|
35
35
|
"url": "https://api.z.ai/api/mcp/zread/mcp",
|
|
36
36
|
"headers": {
|
|
37
|
-
"Authorization": "Bearer
|
|
37
|
+
"Authorization": "Bearer 7b1a5a0d145545ae8f2baa2957691ac4.lOH07gIuomdiNa7E"
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"vision": {
|
|
41
41
|
"command": "npx",
|
|
42
42
|
"args": ["-y", "@z_ai/mcp-server"],
|
|
43
43
|
"env": {
|
|
44
|
-
"Z_AI_API_KEY": "
|
|
44
|
+
"Z_AI_API_KEY": "7b1a5a0d145545ae8f2baa2957691ac4.lOH07gIuomdiNa7E",
|
|
45
45
|
"Z_AI_MODE": "ZAI"
|
|
46
46
|
}
|
|
47
47
|
},
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"type": "http",
|
|
50
50
|
"url": "https://api.z.ai/api/mcp/web_search_prime/mcp",
|
|
51
51
|
"headers": {
|
|
52
|
-
"Authorization": "Bearer
|
|
52
|
+
"Authorization": "Bearer 7b1a5a0d145545ae8f2baa2957691ac4.lOH07gIuomdiNa7E"
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
55
|
"minimax": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-all-config",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.1",
|
|
4
4
|
"description": "🤖 Universal AI CLI Config with Advanced Skills System - Quality Scoring, Scaffolding, Testing, Hooks & Multi-Agent Support (Claude Code, Cursor, Copilot, Gemini & 20+ More)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"codex",
|
|
121
121
|
"trae"
|
|
122
122
|
],
|
|
123
|
-
"skillsCount":
|
|
123
|
+
"skillsCount": 62,
|
|
124
124
|
"agentsCount": 14,
|
|
125
125
|
"commandsCount": 3
|
|
126
126
|
},
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: whatsapp-business-platform
|
|
3
|
+
description: Build WhatsApp Business API platforms with SvelteKit frontend, Go/Fiber backend, OAuth integration, Docker deployment, and Cloudflare tunneling. Use when creating messaging platforms, WhatsApp Business integrations, multi-tenant SaaS messaging solutions, or customer communication platforms.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# WhatsApp Business Platform
|
|
7
|
+
|
|
8
|
+
Build production-ready WhatsApp Business API platforms using proven architecture patterns.
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
- Building WhatsApp Business API integrations
|
|
13
|
+
- Creating messaging platforms or CRM systems
|
|
14
|
+
- Multi-tenant SaaS messaging solutions
|
|
15
|
+
- Customer communication dashboards
|
|
16
|
+
- E-commerce messaging automation
|
|
17
|
+
|
|
18
|
+
## Architecture Pattern
|
|
19
|
+
|
|
20
|
+
**Frontend:** SvelteKit (TypeScript)
|
|
21
|
+
**Backend:** Go with Fiber framework
|
|
22
|
+
**Database:** PostgreSQL + Redis
|
|
23
|
+
**Deployment:** Docker + Cloudflare Tunnel
|
|
24
|
+
**Authentication:** OAuth (Google/Facebook) + JWT
|
|
25
|
+
**Domain Routing:** Landing page + app subdomain
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
1. **Initialize Project Structure**
|
|
30
|
+
```bash
|
|
31
|
+
mkdir my-messaging-platform && cd my-messaging-platform
|
|
32
|
+
mkdir -p {backend/{cmd/api,internal/{config,database,handlers,middleware,services,storage,queue}},frontend/src/{lib,routes,hooks},docs,assets}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
2. **Backend Setup (Go/Fiber)**
|
|
36
|
+
|
|
37
|
+
Create `backend/cmd/api/main.go`:
|
|
38
|
+
```go
|
|
39
|
+
package main
|
|
40
|
+
|
|
41
|
+
import (
|
|
42
|
+
"log"
|
|
43
|
+
"os"
|
|
44
|
+
"os/signal"
|
|
45
|
+
"syscall"
|
|
46
|
+
|
|
47
|
+
"github.com/gofiber/fiber/v2"
|
|
48
|
+
"github.com/gofiber/fiber/v2/middleware/cors"
|
|
49
|
+
"github.com/gofiber/fiber/v2/middleware/helmet"
|
|
50
|
+
"github.com/gofiber/fiber/v2/middleware/logger"
|
|
51
|
+
"github.com/gofiber/fiber/v2/middleware/recover"
|
|
52
|
+
"github.com/joho/godotenv"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
func main() {
|
|
56
|
+
if err := godotenv.Load(); err != nil {
|
|
57
|
+
log.Println("No .env file found")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
app := fiber.New(fiber.Config{
|
|
61
|
+
ServerHeader: "WhatsApp Platform API",
|
|
62
|
+
AppName: "WhatsApp Business Platform v1.0",
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// Middleware
|
|
66
|
+
app.Use(cors.New(cors.Config{
|
|
67
|
+
AllowOrigins: os.Getenv("ALLOWED_ORIGINS"),
|
|
68
|
+
AllowHeaders: "Origin, Content-Type, Accept, Authorization",
|
|
69
|
+
}))
|
|
70
|
+
app.Use(helmet.New())
|
|
71
|
+
app.Use(logger.New())
|
|
72
|
+
app.Use(recover.New())
|
|
73
|
+
|
|
74
|
+
// Routes
|
|
75
|
+
app.Get("/health", func(c *fiber.Ctx) error {
|
|
76
|
+
return c.JSON(fiber.Map{"status": "ok"})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// API routes
|
|
80
|
+
api := app.Group("/api/v1")
|
|
81
|
+
api.Post("/webhooks/whatsapp", handleWhatsAppWebhook)
|
|
82
|
+
api.Post("/auth/google/callback", handleGoogleCallback)
|
|
83
|
+
api.Post("/auth/facebook/callback", handleFacebookCallback)
|
|
84
|
+
|
|
85
|
+
port := os.Getenv("PORT")
|
|
86
|
+
if port == "" {
|
|
87
|
+
port = "8080"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
log.Printf("Server starting on port %s", port)
|
|
91
|
+
if err := app.Listen(":" + port); err != nil {
|
|
92
|
+
log.Fatalf("Failed to start server: %v", err)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
3. **Frontend Setup (SvelteKit)**
|
|
98
|
+
|
|
99
|
+
Create `frontend/src/hooks.server.ts` for domain routing:
|
|
100
|
+
```typescript
|
|
101
|
+
import type { Handle } from '@sveltejs/kit';
|
|
102
|
+
import { redirect } from '@sveltejs/kit';
|
|
103
|
+
|
|
104
|
+
const LANDING_PATHS = ['/', '/docs', '/pricing', '/contact'];
|
|
105
|
+
const APP_PATHS = ['/login', '/register', '/dashboard', '/inbox', '/contacts', '/settings'];
|
|
106
|
+
|
|
107
|
+
export const handle: Handle = async ({ event, resolve }) => {
|
|
108
|
+
const hostname = event.url.hostname;
|
|
109
|
+
const pathname = event.url.pathname;
|
|
110
|
+
|
|
111
|
+
const isLandingDomain = hostname === 'yourdomain.com' || hostname === 'www.yourdomain.com';
|
|
112
|
+
const isAppDomain = hostname === 'app.yourdomain.com';
|
|
113
|
+
|
|
114
|
+
if (isLandingDomain) {
|
|
115
|
+
const isAppPath = APP_PATHS.some(p => pathname === p || pathname.startsWith(p + '/'));
|
|
116
|
+
if (isAppPath) {
|
|
117
|
+
throw redirect(302, 'https://app.yourdomain.com' + pathname + event.url.search);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (isAppDomain) {
|
|
122
|
+
const isLandingPath = LANDING_PATHS.some(p => pathname === p || pathname.startsWith(p + '/'));
|
|
123
|
+
if (isLandingPath && pathname !== '/') {
|
|
124
|
+
throw redirect(302, 'https://yourdomain.com' + pathname + event.url.search);
|
|
125
|
+
}
|
|
126
|
+
if (pathname === '/') {
|
|
127
|
+
throw redirect(302, 'https://app.yourdomain.com/login');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const response = await resolve(event);
|
|
132
|
+
response.headers.set('X-Frame-Options', 'SAMEORIGIN');
|
|
133
|
+
response.headers.set('X-Content-Type-Options', 'nosniff');
|
|
134
|
+
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
135
|
+
return response;
|
|
136
|
+
};
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
4. **Environment Configuration**
|
|
140
|
+
|
|
141
|
+
Create `backend/.env.example`:
|
|
142
|
+
```bash
|
|
143
|
+
# Server
|
|
144
|
+
ENVIRONMENT=development
|
|
145
|
+
PORT=8080
|
|
146
|
+
|
|
147
|
+
# Database
|
|
148
|
+
DATABASE_URL=postgres://postgres:postgres@localhost:5432/messaging_platform?sslmode=disable
|
|
149
|
+
|
|
150
|
+
# JWT
|
|
151
|
+
JWT_SECRET=your-super-secret-jwt-key-min-32-chars-here
|
|
152
|
+
JWT_EXPIRY=15m
|
|
153
|
+
JWT_REFRESH_EXPIRY=168h
|
|
154
|
+
|
|
155
|
+
# OAuth - Google
|
|
156
|
+
GOOGLE_CLIENT_ID=your-google-client-id
|
|
157
|
+
GOOGLE_CLIENT_SECRET=your-google-client-secret
|
|
158
|
+
GOOGLE_REDIRECT_URI=https://app.yourdomain.com/auth/google/callback
|
|
159
|
+
|
|
160
|
+
# OAuth - Facebook
|
|
161
|
+
FACEBOOK_APP_ID=your-facebook-app-id
|
|
162
|
+
FACEBOOK_APP_SECRET=your-facebook-app-secret
|
|
163
|
+
FACEBOOK_REDIRECT_URI=https://app.yourdomain.com/auth/facebook/callback
|
|
164
|
+
|
|
165
|
+
# WhatsApp Business API
|
|
166
|
+
META_ACCESS_TOKEN=your-meta-access-token
|
|
167
|
+
WEBHOOK_VERIFY_TOKEN=your-webhook-verify-token
|
|
168
|
+
WHATSAPP_PHONE_NUMBER_ID=your-phone-number-id
|
|
169
|
+
|
|
170
|
+
# CORS
|
|
171
|
+
ALLOWED_ORIGINS=https://yourdomain.com,https://app.yourdomain.com
|
|
172
|
+
|
|
173
|
+
# Redis
|
|
174
|
+
REDIS_ADDR=localhost:6379
|
|
175
|
+
REDIS_PASSWORD=
|
|
176
|
+
REDIS_DB=0
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
5. **Docker Configuration**
|
|
180
|
+
|
|
181
|
+
Create `docker-compose.yml`:
|
|
182
|
+
```yaml
|
|
183
|
+
version: '3.8'
|
|
184
|
+
|
|
185
|
+
services:
|
|
186
|
+
backend:
|
|
187
|
+
build: ./backend
|
|
188
|
+
container_name: messaging-backend
|
|
189
|
+
restart: unless-stopped
|
|
190
|
+
ports:
|
|
191
|
+
- "127.0.0.1:8084:8080"
|
|
192
|
+
environment:
|
|
193
|
+
- PORT=8080
|
|
194
|
+
- TZ=Asia/Jakarta
|
|
195
|
+
env_file:
|
|
196
|
+
- backend/.env
|
|
197
|
+
networks:
|
|
198
|
+
- messaging-network
|
|
199
|
+
depends_on:
|
|
200
|
+
- db
|
|
201
|
+
- redis
|
|
202
|
+
healthcheck:
|
|
203
|
+
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
|
204
|
+
interval: 30s
|
|
205
|
+
timeout: 10s
|
|
206
|
+
retries: 3
|
|
207
|
+
|
|
208
|
+
frontend:
|
|
209
|
+
build: ./frontend
|
|
210
|
+
container_name: messaging-frontend
|
|
211
|
+
restart: unless-stopped
|
|
212
|
+
ports:
|
|
213
|
+
- "127.0.0.1:3002:3000"
|
|
214
|
+
environment:
|
|
215
|
+
- NODE_ENV=production
|
|
216
|
+
networks:
|
|
217
|
+
- messaging-network
|
|
218
|
+
|
|
219
|
+
db:
|
|
220
|
+
image: postgres:15
|
|
221
|
+
container_name: messaging-db
|
|
222
|
+
restart: unless-stopped
|
|
223
|
+
ports:
|
|
224
|
+
- "127.0.0.1:5434:5432"
|
|
225
|
+
environment:
|
|
226
|
+
- POSTGRES_DB=messaging_platform
|
|
227
|
+
- POSTGRES_USER=postgres
|
|
228
|
+
- POSTGRES_PASSWORD=postgres
|
|
229
|
+
volumes:
|
|
230
|
+
- messaging_db_data:/var/lib/postgresql/data
|
|
231
|
+
networks:
|
|
232
|
+
- messaging-network
|
|
233
|
+
|
|
234
|
+
redis:
|
|
235
|
+
image: redis:7-alpine
|
|
236
|
+
container_name: messaging-redis
|
|
237
|
+
restart: unless-stopped
|
|
238
|
+
ports:
|
|
239
|
+
- "127.0.0.1:6379:6379"
|
|
240
|
+
volumes:
|
|
241
|
+
- messaging_redis_data:/data
|
|
242
|
+
networks:
|
|
243
|
+
- messaging-network
|
|
244
|
+
|
|
245
|
+
networks:
|
|
246
|
+
messaging-network:
|
|
247
|
+
driver: bridge
|
|
248
|
+
|
|
249
|
+
volumes:
|
|
250
|
+
messaging_db_data:
|
|
251
|
+
messaging_redis_data:
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Meta WhatsApp Business API Setup
|
|
255
|
+
|
|
256
|
+
1. **Create Meta App**
|
|
257
|
+
- Go to Meta for Developers
|
|
258
|
+
- Create new app → Business
|
|
259
|
+
- Add WhatsApp product
|
|
260
|
+
|
|
261
|
+
2. **Configure Webhooks**
|
|
262
|
+
```go
|
|
263
|
+
func handleWhatsAppWebhook(c *fiber.Ctx) error {
|
|
264
|
+
// Webhook verification
|
|
265
|
+
if c.Query("hub.verify_token") == os.Getenv("WEBHOOK_VERIFY_TOKEN") {
|
|
266
|
+
return c.SendString(c.Query("hub.challenge"))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Handle incoming messages
|
|
270
|
+
var payload WebhookPayload
|
|
271
|
+
if err := c.BodyParser(&payload); err != nil {
|
|
272
|
+
return c.Status(400).JSON(fiber.Map{"error": "Invalid payload"})
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Process messages asynchronously
|
|
276
|
+
go processWhatsAppMessage(payload)
|
|
277
|
+
|
|
278
|
+
return c.JSON(fiber.Map{"status": "received"})
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
3. **Send Messages**
|
|
283
|
+
```go
|
|
284
|
+
func sendWhatsAppMessage(to, message string) error {
|
|
285
|
+
url := "https://graph.facebook.com/v18.0/{PHONE_NUMBER_ID}/messages"
|
|
286
|
+
|
|
287
|
+
payload := map[string]interface{}{
|
|
288
|
+
"messaging_product": "whatsapp",
|
|
289
|
+
"to": to,
|
|
290
|
+
"text": map[string]string{"body": message},
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// HTTP request with Meta Access Token
|
|
294
|
+
// Implementation details...
|
|
295
|
+
|
|
296
|
+
return nil
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## OAuth Integration
|
|
301
|
+
|
|
302
|
+
**Google OAuth Setup:**
|
|
303
|
+
```go
|
|
304
|
+
func handleGoogleCallback(c *fiber.Ctx) error {
|
|
305
|
+
code := c.Query("code")
|
|
306
|
+
|
|
307
|
+
// Exchange code for tokens
|
|
308
|
+
tokenResp, err := exchangeGoogleCode(code)
|
|
309
|
+
if err != nil {
|
|
310
|
+
return c.Status(400).JSON(fiber.Map{"error": "Invalid code"})
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Get user info
|
|
314
|
+
userInfo, err := getGoogleUserInfo(tokenResp.AccessToken)
|
|
315
|
+
if err != nil {
|
|
316
|
+
return c.Status(500).JSON(fiber.Map{"error": "Failed to get user info"})
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Create/update user in database
|
|
320
|
+
user, err := createOrUpdateUser(userInfo)
|
|
321
|
+
if err != nil {
|
|
322
|
+
return c.Status(500).JSON(fiber.Map{"error": "Database error"})
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Generate JWT
|
|
326
|
+
token, err := generateJWT(user)
|
|
327
|
+
if err != nil {
|
|
328
|
+
return c.Status(500).JSON(fiber.Map{"error": "Token generation failed"})
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return c.JSON(fiber.Map{
|
|
332
|
+
"success": true,
|
|
333
|
+
"token": token,
|
|
334
|
+
"user": user,
|
|
335
|
+
})
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Domain Routing Strategy
|
|
340
|
+
|
|
341
|
+
**Landing Domain** (`yourdomain.com`):
|
|
342
|
+
- `/` - Homepage
|
|
343
|
+
- `/pricing` - Pricing page
|
|
344
|
+
- `/docs` - Documentation
|
|
345
|
+
- `/contact` - Contact form
|
|
346
|
+
|
|
347
|
+
**App Domain** (`app.yourdomain.com`):
|
|
348
|
+
- `/login` - Login page
|
|
349
|
+
- `/register` - Registration
|
|
350
|
+
- `/dashboard` - Main dashboard
|
|
351
|
+
- `/inbox` - Message inbox
|
|
352
|
+
- `/contacts` - Contact management
|
|
353
|
+
- `/settings` - User settings
|
|
354
|
+
|
|
355
|
+
All app routes on landing domain redirect to app domain.
|
|
356
|
+
All landing routes on app domain redirect to landing domain.
|
|
357
|
+
|
|
358
|
+
## Security Best Practices
|
|
359
|
+
|
|
360
|
+
1. **Environment Variables**
|
|
361
|
+
- Never commit `.env` files
|
|
362
|
+
- Use strong JWT secrets (32+ chars)
|
|
363
|
+
- Rotate tokens regularly
|
|
364
|
+
|
|
365
|
+
2. **CORS Configuration**
|
|
366
|
+
- Specific origins only
|
|
367
|
+
- No wildcards in production
|
|
368
|
+
|
|
369
|
+
3. **Webhook Security**
|
|
370
|
+
- Verify webhook tokens
|
|
371
|
+
- Validate payloads
|
|
372
|
+
- Rate limiting
|
|
373
|
+
|
|
374
|
+
4. **Database Security**
|
|
375
|
+
- Localhost-only binding (127.0.0.1)
|
|
376
|
+
- Strong passwords
|
|
377
|
+
- Connection pooling
|
|
378
|
+
|
|
379
|
+
## Deployment
|
|
380
|
+
|
|
381
|
+
1. **Standard Architecture**
|
|
382
|
+
- Use `claude` standard-architecture skill for Nginx + Unix Socket + CF Tunnel
|
|
383
|
+
- Localhost-only port binding (127.0.0.1)
|
|
384
|
+
- SSL termination at Cloudflare
|
|
385
|
+
|
|
386
|
+
2. **Domain Setup**
|
|
387
|
+
- Configure DNS for yourdomain.com and app.yourdomain.com
|
|
388
|
+
- Both pointing to Cloudflare Tunnel
|
|
389
|
+
|
|
390
|
+
3. **Environment Setup**
|
|
391
|
+
- Copy `.env.example` to `.env`
|
|
392
|
+
- Configure all variables
|
|
393
|
+
- Test OAuth callbacks
|
|
394
|
+
|
|
395
|
+
## Testing
|
|
396
|
+
|
|
397
|
+
1. **Backend Health Check**
|
|
398
|
+
```bash
|
|
399
|
+
curl http://localhost:8080/health
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
2. **Webhook Testing**
|
|
403
|
+
```bash
|
|
404
|
+
curl -X POST http://localhost:8080/api/v1/webhooks/whatsapp \
|
|
405
|
+
-H "Content-Type: application/json" \
|
|
406
|
+
-d '{"test": "payload"}'
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
3. **Frontend Routing**
|
|
410
|
+
- Test domain redirects
|
|
411
|
+
- Verify OAuth flows
|
|
412
|
+
- Check security headers
|
|
413
|
+
|
|
414
|
+
## Scaling Considerations
|
|
415
|
+
|
|
416
|
+
- **Message Queue**: Use Redis/Asynq for background processing
|
|
417
|
+
- **Database**: PostgreSQL with read replicas
|
|
418
|
+
- **Cache**: Redis for session storage
|
|
419
|
+
- **Monitoring**: Health checks and logs
|
|
420
|
+
- **Rate Limiting**: Per-user and global limits
|
|
421
|
+
|
|
422
|
+
## Revenue Features
|
|
423
|
+
|
|
424
|
+
- **Multi-tenancy**: Workspace isolation
|
|
425
|
+
- **Usage tracking**: Message counts, API calls
|
|
426
|
+
- **Billing integration**: Stripe/payment gateway
|
|
427
|
+
- **Analytics**: User engagement, conversion
|
|
428
|
+
- **API limits**: Tiered pricing model
|
|
429
|
+
|
|
430
|
+
This pattern provides a solid foundation for building scalable WhatsApp Business platforms with modern web technologies.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# {{PROJECT_NAME}} Configuration
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Server Configuration
|
|
6
|
+
ENVIRONMENT=production
|
|
7
|
+
PORT=8080
|
|
8
|
+
HOST=0.0.0.0
|
|
9
|
+
|
|
10
|
+
# Database Configuration
|
|
11
|
+
DATABASE_URL=postgres://{{DB_USER}}:{{DB_PASSWORD}}@{{DB_HOST}}:{{DB_PORT}}/{{DB_NAME}}?sslmode=disable
|
|
12
|
+
DB_MAX_CONNECTIONS=25
|
|
13
|
+
DB_MAX_IDLE_CONNECTIONS=5
|
|
14
|
+
|
|
15
|
+
# Redis Configuration
|
|
16
|
+
REDIS_ADDR={{REDIS_HOST}}:{{REDIS_PORT}}
|
|
17
|
+
REDIS_PASSWORD={{REDIS_PASSWORD}}
|
|
18
|
+
REDIS_DB=0
|
|
19
|
+
REDIS_MAX_RETRIES=3
|
|
20
|
+
|
|
21
|
+
# JWT Configuration
|
|
22
|
+
JWT_SECRET={{JWT_SECRET}}
|
|
23
|
+
JWT_EXPIRY=15m
|
|
24
|
+
JWT_REFRESH_EXPIRY=168h
|
|
25
|
+
JWT_ISSUER={{PROJECT_NAME}}
|
|
26
|
+
|
|
27
|
+
# Encryption (AES-256, must be exactly 32 bytes)
|
|
28
|
+
ENCRYPTION_KEY={{ENCRYPTION_KEY}}
|
|
29
|
+
|
|
30
|
+
# CORS Configuration
|
|
31
|
+
ALLOWED_ORIGINS=https://{{MAIN_DOMAIN}},https://app.{{MAIN_DOMAIN}}
|
|
32
|
+
|
|
33
|
+
# Rate Limiting
|
|
34
|
+
RATE_LIMIT_REQUESTS=100
|
|
35
|
+
RATE_LIMIT_WINDOW=60s
|
|
36
|
+
|
|
37
|
+
# Google OAuth Configuration
|
|
38
|
+
GOOGLE_CLIENT_ID={{GOOGLE_CLIENT_ID}}
|
|
39
|
+
GOOGLE_CLIENT_SECRET={{GOOGLE_CLIENT_SECRET}}
|
|
40
|
+
GOOGLE_REDIRECT_URI=https://app.{{MAIN_DOMAIN}}/auth/google/callback
|
|
41
|
+
|
|
42
|
+
# Facebook OAuth Configuration
|
|
43
|
+
FACEBOOK_APP_ID={{FACEBOOK_APP_ID}}
|
|
44
|
+
FACEBOOK_APP_SECRET={{FACEBOOK_APP_SECRET}}
|
|
45
|
+
FACEBOOK_REDIRECT_URI=https://app.{{MAIN_DOMAIN}}/auth/facebook/callback
|
|
46
|
+
|
|
47
|
+
# WhatsApp Business API Configuration
|
|
48
|
+
META_ACCESS_TOKEN={{META_ACCESS_TOKEN}}
|
|
49
|
+
META_APP_SECRET={{META_APP_SECRET}}
|
|
50
|
+
WEBHOOK_VERIFY_TOKEN={{WEBHOOK_VERIFY_TOKEN}}
|
|
51
|
+
WHATSAPP_PHONE_NUMBER_ID={{WHATSAPP_PHONE_NUMBER_ID}}
|
|
52
|
+
WHATSAPP_BUSINESS_ACCOUNT_ID={{WHATSAPP_BUSINESS_ACCOUNT_ID}}
|
|
53
|
+
|
|
54
|
+
# Email Configuration (Optional)
|
|
55
|
+
SMTP_HOST={{SMTP_HOST}}
|
|
56
|
+
SMTP_PORT={{SMTP_PORT}}
|
|
57
|
+
SMTP_USERNAME={{SMTP_USERNAME}}
|
|
58
|
+
SMTP_PASSWORD={{SMTP_PASSWORD}}
|
|
59
|
+
FROM_EMAIL={{FROM_EMAIL}}
|
|
60
|
+
|
|
61
|
+
# File Upload Configuration
|
|
62
|
+
MAX_FILE_SIZE=10MB
|
|
63
|
+
ALLOWED_FILE_TYPES=image/jpeg,image/png,image/gif,application/pdf
|
|
64
|
+
UPLOAD_DIR=./uploads
|
|
65
|
+
|
|
66
|
+
# Logging Configuration
|
|
67
|
+
LOG_LEVEL=info
|
|
68
|
+
LOG_FORMAT=json
|
|
69
|
+
|
|
70
|
+
# Monitoring Configuration
|
|
71
|
+
SENTRY_DSN={{SENTRY_DSN}}
|
|
72
|
+
HEALTH_CHECK_INTERVAL=30s
|
|
73
|
+
|
|
74
|
+
# Business Configuration
|
|
75
|
+
COMPANY_NAME={{COMPANY_NAME}}
|
|
76
|
+
SUPPORT_EMAIL={{SUPPORT_EMAIL}}
|
|
77
|
+
COMPANY_WEBSITE=https://{{MAIN_DOMAIN}}
|
|
78
|
+
|
|
79
|
+
# Feature Flags
|
|
80
|
+
ENABLE_REGISTRATION=true
|
|
81
|
+
ENABLE_GOOGLE_AUTH=true
|
|
82
|
+
ENABLE_FACEBOOK_AUTH=true
|
|
83
|
+
ENABLE_EMAIL_VERIFICATION=true
|
|
84
|
+
ENABLE_RATE_LIMITING=true
|
|
85
|
+
|
|
86
|
+
# Timezone
|
|
87
|
+
TZ=Asia/Jakarta
|
|
88
|
+
|
|
89
|
+
# =============================================================================
|
|
90
|
+
# Development Only (Remove in Production)
|
|
91
|
+
# =============================================================================
|
|
92
|
+
# DATABASE_URL=postgres://postgres:postgres@localhost:5432/{{DB_NAME}}_dev?sslmode=disable
|
|
93
|
+
# REDIS_ADDR=localhost:6379
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"log"
|
|
5
|
+
"os"
|
|
6
|
+
"os/signal"
|
|
7
|
+
"syscall"
|
|
8
|
+
|
|
9
|
+
"github.com/gofiber/fiber/v2"
|
|
10
|
+
"github.com/gofiber/fiber/v2/middleware/cors"
|
|
11
|
+
"github.com/gofiber/fiber/v2/middleware/helmet"
|
|
12
|
+
"github.com/gofiber/fiber/v2/middleware/logger"
|
|
13
|
+
"github.com/gofiber/fiber/v2/middleware/recover"
|
|
14
|
+
"github.com/joho/godotenv"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
func main() {
|
|
18
|
+
// Load environment variables
|
|
19
|
+
if err := godotenv.Load(); err != nil {
|
|
20
|
+
log.Println("No .env file found, using system environment variables")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Initialize Fiber app
|
|
24
|
+
app := fiber.New(fiber.Config{
|
|
25
|
+
ServerHeader: "{{PROJECT_NAME}} API",
|
|
26
|
+
AppName: "{{PROJECT_NAME}} v1.0",
|
|
27
|
+
ErrorHandler: errorHandler,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Middleware
|
|
31
|
+
app.Use(cors.New(cors.Config{
|
|
32
|
+
AllowOrigins: os.Getenv("ALLOWED_ORIGINS"),
|
|
33
|
+
AllowHeaders: "Origin, Content-Type, Accept, Authorization",
|
|
34
|
+
AllowMethods: "GET, POST, PUT, DELETE, OPTIONS",
|
|
35
|
+
AllowCredentials: true,
|
|
36
|
+
}))
|
|
37
|
+
app.Use(helmet.New())
|
|
38
|
+
app.Use(logger.New())
|
|
39
|
+
app.Use(recover.New())
|
|
40
|
+
|
|
41
|
+
// Health check
|
|
42
|
+
app.Get("/health", func(c *fiber.Ctx) error {
|
|
43
|
+
return c.JSON(fiber.Map{
|
|
44
|
+
"status": "ok",
|
|
45
|
+
"timestamp": time.Now().Unix(),
|
|
46
|
+
"service": "{{PROJECT_NAME}}",
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// API routes
|
|
51
|
+
api := app.Group("/api/v1")
|
|
52
|
+
|
|
53
|
+
// Auth routes
|
|
54
|
+
auth := api.Group("/auth")
|
|
55
|
+
auth.Post("/google/callback", handleGoogleCallback)
|
|
56
|
+
auth.Post("/facebook/callback", handleFacebookCallback)
|
|
57
|
+
auth.Post("/refresh", handleTokenRefresh)
|
|
58
|
+
auth.Post("/logout", middleware.RequireAuth(), handleLogout)
|
|
59
|
+
|
|
60
|
+
// WhatsApp webhook
|
|
61
|
+
api.Post("/webhooks/whatsapp", handleWhatsAppWebhook)
|
|
62
|
+
api.Get("/webhooks/whatsapp", handleWhatsAppVerification)
|
|
63
|
+
|
|
64
|
+
// Protected routes
|
|
65
|
+
protected := api.Group("/", middleware.RequireAuth())
|
|
66
|
+
protected.Get("/me", getUserProfile)
|
|
67
|
+
protected.Post("/messages", sendMessage)
|
|
68
|
+
protected.Get("/conversations", getConversations)
|
|
69
|
+
protected.Get("/contacts", getContacts)
|
|
70
|
+
|
|
71
|
+
// Graceful shutdown
|
|
72
|
+
c := make(chan os.Signal, 1)
|
|
73
|
+
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
|
74
|
+
go func() {
|
|
75
|
+
<-c
|
|
76
|
+
log.Println("Gracefully shutting down...")
|
|
77
|
+
_ = app.Shutdown()
|
|
78
|
+
}()
|
|
79
|
+
|
|
80
|
+
// Start server
|
|
81
|
+
port := os.Getenv("PORT")
|
|
82
|
+
if port == "" {
|
|
83
|
+
port = "8080"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
log.Printf("🚀 Server starting on port %s", port)
|
|
87
|
+
log.Printf("🔗 Health check: http://localhost:%s/health", port)
|
|
88
|
+
|
|
89
|
+
if err := app.Listen(":" + port); err != nil {
|
|
90
|
+
log.Fatalf("❌ Failed to start server: %v", err)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
func errorHandler(c *fiber.Ctx, err error) error {
|
|
95
|
+
code := fiber.StatusInternalServerError
|
|
96
|
+
if e, ok := err.(*fiber.Error); ok {
|
|
97
|
+
code = e.Code
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return c.Status(code).JSON(fiber.Map{
|
|
101
|
+
"error": true,
|
|
102
|
+
"message": err.Error(),
|
|
103
|
+
"code": code,
|
|
104
|
+
})
|
|
105
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
# Backend API Service
|
|
5
|
+
backend:
|
|
6
|
+
build:
|
|
7
|
+
context: ./backend
|
|
8
|
+
dockerfile: Dockerfile
|
|
9
|
+
container_name: {{PROJECT_NAME}}-backend
|
|
10
|
+
restart: unless-stopped
|
|
11
|
+
ports:
|
|
12
|
+
- "127.0.0.1:{{BACKEND_PORT}}:8080"
|
|
13
|
+
environment:
|
|
14
|
+
- PORT=8080
|
|
15
|
+
- TZ=Asia/Jakarta
|
|
16
|
+
- DATABASE_URL=postgres://{{DB_USER}}:{{DB_PASSWORD}}@{{PROJECT_NAME}}-db:5432/{{DB_NAME}}?sslmode=disable
|
|
17
|
+
- REDIS_ADDR={{PROJECT_NAME}}-redis:6379
|
|
18
|
+
env_file:
|
|
19
|
+
- backend/.env
|
|
20
|
+
networks:
|
|
21
|
+
- {{PROJECT_NAME}}-network
|
|
22
|
+
depends_on:
|
|
23
|
+
- db
|
|
24
|
+
- redis
|
|
25
|
+
volumes:
|
|
26
|
+
- ./backend/uploads:/app/uploads
|
|
27
|
+
- ./backend/logs:/app/logs
|
|
28
|
+
healthcheck:
|
|
29
|
+
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
|
30
|
+
interval: 30s
|
|
31
|
+
timeout: 10s
|
|
32
|
+
retries: 3
|
|
33
|
+
start_period: 30s
|
|
34
|
+
|
|
35
|
+
# Frontend Service
|
|
36
|
+
frontend:
|
|
37
|
+
build:
|
|
38
|
+
context: ./frontend
|
|
39
|
+
dockerfile: Dockerfile
|
|
40
|
+
container_name: {{PROJECT_NAME}}-frontend
|
|
41
|
+
restart: unless-stopped
|
|
42
|
+
ports:
|
|
43
|
+
- "127.0.0.1:{{FRONTEND_PORT}}:3000"
|
|
44
|
+
environment:
|
|
45
|
+
- NODE_ENV=production
|
|
46
|
+
- API_URL=http://{{PROJECT_NAME}}-backend:8080
|
|
47
|
+
- DOMAIN={{MAIN_DOMAIN}}
|
|
48
|
+
networks:
|
|
49
|
+
- {{PROJECT_NAME}}-network
|
|
50
|
+
depends_on:
|
|
51
|
+
- backend
|
|
52
|
+
|
|
53
|
+
# PostgreSQL Database
|
|
54
|
+
db:
|
|
55
|
+
image: postgres:15-alpine
|
|
56
|
+
container_name: {{PROJECT_NAME}}-db
|
|
57
|
+
restart: unless-stopped
|
|
58
|
+
ports:
|
|
59
|
+
- "127.0.0.1:{{DB_PORT}}:5432"
|
|
60
|
+
environment:
|
|
61
|
+
- POSTGRES_DB={{DB_NAME}}
|
|
62
|
+
- POSTGRES_USER={{DB_USER}}
|
|
63
|
+
- POSTGRES_PASSWORD={{DB_PASSWORD}}
|
|
64
|
+
- POSTGRES_INITDB_ARGS=--auth-host=scram-sha-256
|
|
65
|
+
volumes:
|
|
66
|
+
- {{PROJECT_NAME}}_db_data:/var/lib/postgresql/data
|
|
67
|
+
- ./backend/migrations:/docker-entrypoint-initdb.d
|
|
68
|
+
networks:
|
|
69
|
+
- {{PROJECT_NAME}}-network
|
|
70
|
+
healthcheck:
|
|
71
|
+
test: ["CMD-SHELL", "pg_isready -U {{DB_USER}} -d {{DB_NAME}}"]
|
|
72
|
+
interval: 30s
|
|
73
|
+
timeout: 10s
|
|
74
|
+
retries: 3
|
|
75
|
+
|
|
76
|
+
# Redis Cache & Queue
|
|
77
|
+
redis:
|
|
78
|
+
image: redis:7-alpine
|
|
79
|
+
container_name: {{PROJECT_NAME}}-redis
|
|
80
|
+
restart: unless-stopped
|
|
81
|
+
ports:
|
|
82
|
+
- "127.0.0.1:{{REDIS_PORT}}:6379"
|
|
83
|
+
command: redis-server --requirepass {{REDIS_PASSWORD}}
|
|
84
|
+
volumes:
|
|
85
|
+
- {{PROJECT_NAME}}_redis_data:/data
|
|
86
|
+
- ./backend/redis.conf:/usr/local/etc/redis/redis.conf
|
|
87
|
+
networks:
|
|
88
|
+
- {{PROJECT_NAME}}-network
|
|
89
|
+
healthcheck:
|
|
90
|
+
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
|
|
91
|
+
interval: 30s
|
|
92
|
+
timeout: 3s
|
|
93
|
+
retries: 5
|
|
94
|
+
|
|
95
|
+
# Background Job Worker (Optional)
|
|
96
|
+
worker:
|
|
97
|
+
build:
|
|
98
|
+
context: ./backend
|
|
99
|
+
dockerfile: Dockerfile
|
|
100
|
+
container_name: {{PROJECT_NAME}}-worker
|
|
101
|
+
restart: unless-stopped
|
|
102
|
+
command: ["./worker"]
|
|
103
|
+
environment:
|
|
104
|
+
- TZ=Asia/Jakarta
|
|
105
|
+
- DATABASE_URL=postgres://{{DB_USER}}:{{DB_PASSWORD}}@{{PROJECT_NAME}}-db:5432/{{DB_NAME}}?sslmode=disable
|
|
106
|
+
- REDIS_ADDR={{PROJECT_NAME}}-redis:6379
|
|
107
|
+
env_file:
|
|
108
|
+
- backend/.env
|
|
109
|
+
networks:
|
|
110
|
+
- {{PROJECT_NAME}}-network
|
|
111
|
+
depends_on:
|
|
112
|
+
- db
|
|
113
|
+
- redis
|
|
114
|
+
volumes:
|
|
115
|
+
- ./backend/uploads:/app/uploads
|
|
116
|
+
- ./backend/logs:/app/logs
|
|
117
|
+
|
|
118
|
+
# Networks
|
|
119
|
+
networks:
|
|
120
|
+
{{PROJECT_NAME}}-network:
|
|
121
|
+
driver: bridge
|
|
122
|
+
ipam:
|
|
123
|
+
config:
|
|
124
|
+
- subnet: 172.{{SUBNET}}.0.0/16
|
|
125
|
+
|
|
126
|
+
# Volumes
|
|
127
|
+
volumes:
|
|
128
|
+
{{PROJECT_NAME}}_db_data:
|
|
129
|
+
driver: local
|
|
130
|
+
{{PROJECT_NAME}}_redis_data:
|
|
131
|
+
driver: local
|
|
132
|
+
|
|
133
|
+
# =============================================================================
|
|
134
|
+
# Production Considerations:
|
|
135
|
+
# =============================================================================
|
|
136
|
+
# 1. Use Docker secrets for sensitive data
|
|
137
|
+
# 2. Set up log rotation for container logs
|
|
138
|
+
# 3. Configure backup strategy for volumes
|
|
139
|
+
# 4. Monitor container resource usage
|
|
140
|
+
# 5. Use health checks for all services
|
|
141
|
+
# 6. Set up monitoring with Prometheus/Grafana
|
|
142
|
+
# =============================================================================
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { Handle } from '@sveltejs/kit';
|
|
2
|
+
import { redirect } from '@sveltejs/kit';
|
|
3
|
+
|
|
4
|
+
// Landing page paths (served on main domain)
|
|
5
|
+
const LANDING_PATHS = [
|
|
6
|
+
'/',
|
|
7
|
+
'/docs',
|
|
8
|
+
'/pricing',
|
|
9
|
+
'/contact',
|
|
10
|
+
'/about',
|
|
11
|
+
'/features',
|
|
12
|
+
'/blog',
|
|
13
|
+
'/legal/privacy',
|
|
14
|
+
'/legal/terms'
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
// App paths (served on app subdomain)
|
|
18
|
+
const APP_PATHS = [
|
|
19
|
+
'/login',
|
|
20
|
+
'/register',
|
|
21
|
+
'/forgot-password',
|
|
22
|
+
'/reset-password',
|
|
23
|
+
'/verify-email',
|
|
24
|
+
'/resend-verification',
|
|
25
|
+
'/dashboard',
|
|
26
|
+
'/inbox',
|
|
27
|
+
'/conversations',
|
|
28
|
+
'/contacts',
|
|
29
|
+
'/templates',
|
|
30
|
+
'/webhooks',
|
|
31
|
+
'/auto-reply',
|
|
32
|
+
'/broadcasts',
|
|
33
|
+
'/analytics',
|
|
34
|
+
'/billing',
|
|
35
|
+
'/settings',
|
|
36
|
+
'/team',
|
|
37
|
+
'/integrations',
|
|
38
|
+
'/api-keys',
|
|
39
|
+
'/profile'
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
export const handle: Handle = async ({ event, resolve }) => {
|
|
43
|
+
const hostname = event.url.hostname;
|
|
44
|
+
const pathname = event.url.pathname;
|
|
45
|
+
|
|
46
|
+
// Define domain patterns
|
|
47
|
+
const isLandingDomain = hostname === '{{MAIN_DOMAIN}}' || hostname === 'www.{{MAIN_DOMAIN}}';
|
|
48
|
+
const isAppDomain = hostname === 'app.{{MAIN_DOMAIN}}';
|
|
49
|
+
|
|
50
|
+
// Landing domain logic
|
|
51
|
+
if (isLandingDomain) {
|
|
52
|
+
// If user accesses app path on landing domain, redirect to app domain
|
|
53
|
+
const isAppPath = APP_PATHS.some(p => pathname === p || pathname.startsWith(p + '/'));
|
|
54
|
+
if (isAppPath) {
|
|
55
|
+
throw redirect(302, 'https://app.{{MAIN_DOMAIN}}' + pathname + event.url.search);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// App domain logic
|
|
60
|
+
if (isAppDomain) {
|
|
61
|
+
// If user accesses landing path on app domain, redirect to landing domain
|
|
62
|
+
const isLandingPath = LANDING_PATHS.some(p =>
|
|
63
|
+
pathname === p || (p !== '/' && pathname.startsWith(p + '/'))
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (isLandingPath && pathname !== '/') {
|
|
67
|
+
throw redirect(302, 'https://{{MAIN_DOMAIN}}' + pathname + event.url.search);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Root path on app domain should redirect to login/dashboard
|
|
71
|
+
if (pathname === '/') {
|
|
72
|
+
// Check if user is authenticated (you can implement JWT verification here)
|
|
73
|
+
const token = event.cookies.get('auth_token');
|
|
74
|
+
|
|
75
|
+
if (token && await verifyToken(token)) {
|
|
76
|
+
throw redirect(302, 'https://app.{{MAIN_DOMAIN}}/dashboard');
|
|
77
|
+
} else {
|
|
78
|
+
throw redirect(302, 'https://app.{{MAIN_DOMAIN}}/login');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Process the request
|
|
84
|
+
const response = await resolve(event);
|
|
85
|
+
|
|
86
|
+
// Add security headers
|
|
87
|
+
response.headers.set('X-Frame-Options', 'SAMEORIGIN');
|
|
88
|
+
response.headers.set('X-Content-Type-Options', 'nosniff');
|
|
89
|
+
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
90
|
+
response.headers.set('X-XSS-Protection', '1; mode=block');
|
|
91
|
+
|
|
92
|
+
// HSTS for production
|
|
93
|
+
if (hostname.includes('{{MAIN_DOMAIN}}')) {
|
|
94
|
+
response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return response;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Helper function to verify JWT token
|
|
101
|
+
async function verifyToken(token: string): Promise<boolean> {
|
|
102
|
+
try {
|
|
103
|
+
// Implement JWT verification logic here
|
|
104
|
+
// This is a placeholder - replace with actual JWT verification
|
|
105
|
+
const response = await fetch('http://localhost:8080/api/v1/auth/verify', {
|
|
106
|
+
headers: {
|
|
107
|
+
'Authorization': `Bearer ${token}`
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return response.ok;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('Token verification failed:', error);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|