Prepare server deployment
This commit is contained in:
32
README.md
32
README.md
@ -25,3 +25,35 @@ cp .env.example .env
|
|||||||
npm install
|
npm install
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Deploy test server
|
||||||
|
Target deploy:
|
||||||
|
- domain `wa.iptek.co`
|
||||||
|
- app port `127.0.0.1:3007`
|
||||||
|
- reverse proxy `nginx`
|
||||||
|
|
||||||
|
Contoh langkah di server:
|
||||||
|
```bash
|
||||||
|
cd /var/www
|
||||||
|
git clone https://git.iptek.co/wirabasalamah/webhook-test.git wa-test-nextjs
|
||||||
|
cd wa-test-nextjs
|
||||||
|
cp .env.example .env
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
npx pm2 start ecosystem.config.cjs
|
||||||
|
npx pm2 save
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `.env` minimal:
|
||||||
|
```bash
|
||||||
|
NEXT_PUBLIC_APP_URL=https://wa.iptek.co
|
||||||
|
WHATSAPP_VERIFY_TOKEN=...
|
||||||
|
WHATSAPP_ACCESS_TOKEN=...
|
||||||
|
WHATSAPP_PHONE_NUMBER_ID=...
|
||||||
|
WA_TEST_NUMBER=...
|
||||||
|
WA_TEST_LOGIN_USERNAME=...
|
||||||
|
WA_TEST_LOGIN_PASSWORD=...
|
||||||
|
SESSION_SECRET=...
|
||||||
|
```
|
||||||
|
|
||||||
|
Contoh config nginx ada di `deploy/nginx/wa.iptek.co.conf`.
|
||||||
|
|||||||
19
deploy/nginx/wa.iptek.co.conf
Normal file
19
deploy/nginx/wa.iptek.co.conf
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name wa.iptek.co;
|
||||||
|
|
||||||
|
client_max_body_size 10m;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:3007;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
ecosystem.config.cjs
Normal file
13
ecosystem.config.cjs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: 'wa-test-nextjs',
|
||||||
|
script: 'npm',
|
||||||
|
args: 'run start:3007',
|
||||||
|
cwd: '/var/www/wa-test-nextjs',
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
4
next-env.d.ts
vendored
4
next-env.d.ts
vendored
@ -1,4 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
|
import "./.next/types/routes.d.ts";
|
||||||
|
|
||||||
// This file is auto-generated by Next.js
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
11
package.json
11
package.json
@ -6,12 +6,13 @@
|
|||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"start:3007": "next start -p 3007 -H 127.0.0.1",
|
||||||
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "16.0.0",
|
"next": "16.2.6",
|
||||||
"react": "19.2.0",
|
"react": "19.2.4",
|
||||||
"react-dom": "19.2.0"
|
"react-dom": "19.2.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
@ -19,6 +20,6 @@
|
|||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.0.0"
|
"eslint-config-next": "16.2.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export default async function TestPage({ searchParams }: { searchParams: Promise
|
|||||||
redirect('/login')
|
redirect('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
const logs = readJsonFile<any[]>('webhook-logs.json', [])
|
const logs = readJsonFile<unknown[]>('webhook-logs.json', [])
|
||||||
const state = readJsonFile<Record<string, unknown>>('flow-state.json', {})
|
const state = readJsonFile<Record<string, unknown>>('flow-state.json', {})
|
||||||
const params = await searchParams
|
const params = await searchParams
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export default async function WebhookLogsPage() {
|
|||||||
redirect('/login')
|
redirect('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
const logs = readJsonFile<any[]>('webhook-logs.json', [])
|
const logs = readJsonFile<unknown[]>('webhook-logs.json', [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="wrap">
|
<div className="wrap">
|
||||||
|
|||||||
@ -1,6 +1,25 @@
|
|||||||
import { appendLog, readJsonFile, writeJsonFile } from './storage'
|
import { appendLog, readJsonFile, writeJsonFile } from './storage'
|
||||||
|
|
||||||
type FlowState = Record<string, { step: string; name?: string }>
|
type FlowState = Record<string, { step: string; name?: string }>
|
||||||
|
type WebhookValue = {
|
||||||
|
messages?: IncomingMessage[]
|
||||||
|
statuses?: unknown[]
|
||||||
|
}
|
||||||
|
type WebhookChange = {
|
||||||
|
value?: WebhookValue
|
||||||
|
}
|
||||||
|
type WebhookEntry = {
|
||||||
|
changes?: WebhookChange[]
|
||||||
|
}
|
||||||
|
type WebhookPayload = {
|
||||||
|
entry?: WebhookEntry[]
|
||||||
|
}
|
||||||
|
type IncomingMessage = {
|
||||||
|
from?: string
|
||||||
|
text?: {
|
||||||
|
body?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeNumber(number?: string | null) {
|
function normalizeNumber(number?: string | null) {
|
||||||
return (number || '').replace(/\D+/g, '')
|
return (number || '').replace(/\D+/g, '')
|
||||||
@ -37,7 +56,7 @@ export async function sendTextMessage(to: string, message: string) {
|
|||||||
return payload
|
return payload
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleWebhook(payload: any) {
|
export async function handleWebhook(payload: WebhookPayload) {
|
||||||
appendLog({ type: 'webhook_received', payload, created_at: new Date().toISOString() })
|
appendLog({ type: 'webhook_received', payload, created_at: new Date().toISOString() })
|
||||||
|
|
||||||
for (const entry of payload.entry || []) {
|
for (const entry of payload.entry || []) {
|
||||||
@ -55,7 +74,7 @@ export async function handleWebhook(payload: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleIncomingMessage(message: any) {
|
async function handleIncomingMessage(message: IncomingMessage) {
|
||||||
const from = message.from || ''
|
const from = message.from || ''
|
||||||
appendLog({ type: 'incoming_message', from, message, created_at: new Date().toISOString() })
|
appendLog({ type: 'incoming_message', from, message, created_at: new Date().toISOString() })
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2017",
|
"target": "ES2017",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
"allowJs": false,
|
"allowJs": false,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
@ -11,13 +15,27 @@
|
|||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "react-jsx",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"plugins": [{ "name": "next" }],
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
"exclude": ["node_modules"]
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user