Skip to content

Commit 9a68adb

Browse files
authored
deploy (#34)
* try to deploy next * bind webhook almost working * lint * health check endpoint * sanitize rsa key * add kv test * remove chache from health * health log * health log
1 parent e484a9f commit 9a68adb

File tree

16 files changed

+324
-51
lines changed

16 files changed

+324
-51
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ yarn-error.log*
2828
.env.development.local
2929
.env.test.local
3030
.env.production.local
31+
.env.deploy*
3132

3233
# turbo
3334
.turbo

fly.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# fly.toml app configuration file generated for ws-bot on 2023-10-16T12:54:39+01:00
2+
#
3+
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
4+
#
5+
6+
app = "ws-bot"
7+
primary_region = "lhr"
8+
9+
[build]
10+
dockerfile = "multichannel-app/ws-bot-backend/Dockerfile"
11+
build-target = "runner"
12+
13+
[http_service]
14+
internal_port = 8080
15+
force_https = true
16+
auto_stop_machines = true
17+
auto_start_machines = true
18+
min_machines_running = 0
19+
processes = ["app"]
20+
21+
[env]
22+
WS_BOT_PORT=8080

multichannel-app/README.md

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,26 @@ VONAGE_API_KEY="<your vonage api key>"
1919
VONAGE_API_SECRET="<your vonage account secret>"
2020
VONAGE_APPLICATION_ID="<your vonage application id>"
2121
VONAGE_PRIVATE_KEY="<your vonage api key>"
22-
NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
23-
NEXT_PUBLIC_SUPABASE_ANON_KEY=<random string>
24-
DEVICE_REFRESH_TOKEN_SECRET=<random string>
25-
DEVICE_CODE_SALT=<random string>
2622
LOCALTUNNEL=true
2723
VONAGE_LVN=<your lvn>
24+
NEXT_PUBLIC_VONAGE_LVN=<your lvn (same as above)>
2825
ENABLE_FACEBOOK=false
2926
VONAGE_ENDPOINT=https://api-us-3.vonage.com/v1
3027
WEBHOOK_PATH=api/
3128
TUNNEL_PORT=3000
29+
30+
KV_REST_API_READ_ONLY_TOKEN="AnoCASQgZGMxNGQwN2EtNWFjZS00ZDMwLWIxNTYtNmM2N2VhMjhmZWZjyGaGY9p6iFYqlhfeJtEfa91OI88tHO6QJGg9avRxNs0="
31+
KV_REST_API_TOKEN="AXoCASQgZGMxNGQwN2EtNWFjZS00ZDMwLWIxNTYtNmM2N2VhMjhmZWZjNDU2ZDA1ODhiZWFhNDYzOGE2Y2I4NDg5ODcxNmUzOTY="
32+
KV_REST_API_URL="https://glowing-gar-31234.kv.vercel-storage.com"
33+
KV_URL="redis://default:456d0588beaa4638a6cb84898716e396@glowing-gar-31234.kv.vercel-storage.com:31234"
34+
35+
#ws bot
3236
WS_BOT_PORT=3001
33-
GOOGLE_APPLICATION_CREDENTIALS="<your project path>/reference-client_sdk-ios-android-js-node-deno-usecases/gapp-creds.json"
34-
OPENAI_API_KEY="<openai org id>"
35-
OPENAI_ORG_ID="<openai org id>"
37+
OPENAI_API_KEY=
38+
OPENAI_ORG_ID=
39+
GOOGLE_PRIVATE_KEY=
40+
GOOGLE_CLIENT_EMAIL=
41+
GOOGLE_CLIENT_ID=
3642
3743
```
3844

@@ -50,5 +56,30 @@ then run
5056
npm run dev:multichannel-app
5157
```
5258

59+
### how to deploy
60+
61+
#### ws bot on fly.io
62+
63+
first install flyctr and loging
64+
65+
then set the secrets:
66+
67+
```
68+
cat .env | grep 'GOOGLE\|OPENAI' > .env.deploy.flyio
69+
fly secrets import < .env.deploy.flyio
70+
71+
```
72+
73+
then deploy with:
74+
75+
```
76+
fly deploy
77+
```
78+
79+
#### frontedn on next
80+
81+
82+
83+
5384

5485
<!-- TODO: Add instructions -->

multichannel-app/frontend/app/@dash/(dash)/chat/@conversation/[id]/LinkPreview.tsx

Whitespace-only changes.

multichannel-app/frontend/app/@dash/(dash)/layout.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { VonageClientProvider } from '@vonage/client-sdk-react';
55

66

77
import { UserProfileWidget } from './UserProfileWidget.server';
8-
import { getUserProfile } from './utils';
98

109
import '@vonage/client-sdk-react/dist/vonage-client-sdk-react.css';
1110
import { Database } from 'supabase-helpers';

multichannel-app/frontend/app/@dash/(dash)/utils.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

multichannel-app/frontend/app/@login/Login.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use client';
2+
// @ts-ignore
23
import { experimental_useFormState as useFormState } from 'react-dom';
4+
// @ts-ignore
35
import { experimental_useFormStatus as useFormStatus } from 'react-dom';
46
import { handleLogin } from './loginActions';
57
import { defaultLoginState } from './types';
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { getToken } from '../../token';
2+
export const revalidate = 0;
3+
const VONAGE_API_KEY = process.env.VONAGE_API_KEY;
4+
const VONAGE_API_SECRET = process.env.VONAGE_API_SECRET;
5+
const VONAGE_LVN = process.env.VONAGE_LVN;
6+
const VONAGE_APPLICATION_ID = process.env.VONAGE_APPLICATION_ID;
7+
8+
9+
export const GET = async (_req: Request) => {
10+
try {
11+
const phone_number = VONAGE_LVN;
12+
const application_id = VONAGE_APPLICATION_ID;
13+
const endpoint = process.env.ENDPOINT;
14+
const webhooks_host = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}`:endpoint;
15+
const WEBHOOKS_URL = `${webhooks_host}/api`;
16+
const api_key = VONAGE_API_KEY;
17+
const api_secret = VONAGE_API_SECRET;
18+
const dev_api_token = Buffer.from((`${api_key}:${api_secret}`)).toString('base64');
19+
const capabilities = {
20+
voice: {
21+
webhooks: {
22+
answer_url: {
23+
address: `${WEBHOOKS_URL}/webhook-voice-answer`,
24+
http_method: 'POST'
25+
},
26+
event_url: {
27+
address: `${WEBHOOKS_URL}/webhook-voice-event`,
28+
http_method: 'POST'
29+
}
30+
},
31+
},
32+
rtc: {
33+
webhooks: {
34+
event_url: {
35+
address: `${WEBHOOKS_URL}/webhook-rtc-event`,
36+
http_method: 'POST'
37+
}
38+
},
39+
},
40+
messages: {
41+
webhooks: {
42+
inbound_url: {
43+
address: `${WEBHOOKS_URL}/webhook-message-inbound`,
44+
http_method: 'POST'
45+
},
46+
status_url: {
47+
address: `${WEBHOOKS_URL}/webhook-status-event`,
48+
http_method: 'POST'
49+
}
50+
},
51+
},
52+
53+
};
54+
55+
56+
57+
const newAppRes = await fetch(`https://api.nexmo.com/v2/applications/${application_id}`, {
58+
body: JSON.stringify({
59+
'name': 'Multichannel app',
60+
capabilities
61+
}),
62+
method: 'PUT',
63+
headers: {
64+
'Authorization': `basic ${dev_api_token}`,
65+
'Content-Type': 'application/json'
66+
}
67+
}).then(res => {
68+
if(!res.ok){
69+
console.error('update webhooks', res);
70+
throw Error(res.statusText);
71+
}else {
72+
return res.json();
73+
}
74+
});
75+
76+
const requestLVN ={
77+
'country':'GB',
78+
'msisdn': phone_number as string,
79+
app_id: application_id as string,
80+
moHttpUrl: '',
81+
voiceCallbackType: 'app',
82+
voiceCallbackValue: application_id as string,
83+
};
84+
85+
const bindLvnRes = fetch('https://rest.nexmo.com/number/update', {
86+
method: 'PUT',
87+
body: new URLSearchParams(requestLVN),
88+
headers: {
89+
'Content-Type': 'application/x-www-form-urlencoded' ,
90+
'Authorization': `basic ${dev_api_token}`,
91+
92+
}
93+
}).then(res => {
94+
if(!res.ok){
95+
console.error(res);
96+
}else {
97+
return res.json();
98+
}
99+
});
100+
101+
102+
return new Response(JSON.stringify({
103+
env: {
104+
VONAGE_API_KEY,
105+
VONAGE_API_SECRET,
106+
VONAGE_LVN,
107+
WEBHOOKS_URL,
108+
dev_api_token
109+
},
110+
newAppRes,
111+
bindLvnRes
112+
}), {
113+
status: 200,
114+
headers: { 'Content-Type': 'application/json' },
115+
});
116+
} catch(err){
117+
return new Response(JSON.stringify({
118+
err
119+
}), {
120+
status: 500,
121+
headers: { 'Content-Type': 'application/json' },
122+
});
123+
}
124+
};

multichannel-app/frontend/app/api/admin/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getToken } from '../token';
2+
export const revalidate = 0;
23

34
export const GET = async (_req: Request) => {
45
return new Response(JSON.stringify({ token: await getToken() }), {
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { kv } from '@vercel/kv';
2+
import { getToken } from '../token';
3+
4+
type HealthResponseKeys = 'ping'| 'ws_bot_ping' | 'application' | 'env_vars' | 'token' | 'kv';
5+
type HealthResponse = {
6+
healthy: {
7+
[key in HealthResponseKeys]?: boolean
8+
},
9+
error_message: {
10+
[key in HealthResponseKeys]?: string
11+
}
12+
}
13+
type TestResult = { type: HealthResponseKeys, healty: boolean, err?: string};
14+
15+
function assignResult(resp: HealthResponse, res: TestResult): HealthResponse {
16+
if(res.healty){
17+
resp.healthy[res.type] = true;
18+
}else{
19+
resp.healthy[res.type] = false;
20+
resp.error_message[res.type] = res.err as string;
21+
}
22+
return resp;
23+
}
24+
25+
function networkTestGet(url: string, type: HealthResponseKeys): Promise<TestResult> {
26+
return fetch(url,{
27+
cache: 'no-cache',
28+
headers : {
29+
'Content-Type': 'application/json',
30+
'Accept': 'application/json'
31+
}
32+
}).then((res) => {
33+
if(!res.ok){
34+
return res.text().then(text => ({
35+
healty: false,
36+
err: text as string
37+
}));
38+
}
39+
return res.json().then(res => {
40+
return { healty: true };
41+
});
42+
}).catch(err => ({
43+
healty: false,
44+
err: `error getting this url: ${url}. ${err.toString()}`
45+
})).then(res => ({
46+
...res,
47+
type
48+
}));
49+
50+
}
51+
52+
export const GET = async (_req: Request) => {
53+
54+
55+
const endpoint = process.env.ENDPOINT;
56+
const webhooks_host = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}`:endpoint;
57+
58+
const ws_bot_url = process.env.WS_BOT_URL;
59+
const pingRes: Promise<TestResult> = networkTestGet(`${webhooks_host}/api/ping`, 'ping');
60+
const wsBotPingRes: Promise<TestResult> = networkTestGet(`${ws_bot_url}/ping`, 'ws_bot_ping');
61+
62+
const tokenRes:Promise<TestResult> = getToken()
63+
.then(token => ({
64+
healty: true
65+
})).catch(err => ({
66+
healty: false,
67+
err: err as string
68+
})).then((res ) => ({
69+
...res,
70+
type: 'token'
71+
}));
72+
73+
const kvTestRes: Promise<TestResult> = kv.set('test', 'test').then(val => kv.del('test'))
74+
.then(res => ({healty: true }))
75+
.catch(err => ({
76+
healty: false,
77+
err: err as string
78+
}))
79+
.then((res ) => ({
80+
...res,
81+
type: 'kv'
82+
}));
83+
84+
const resp:HealthResponse = {
85+
healthy: {},
86+
error_message: {}
87+
};
88+
const responses: TestResult[] = await Promise.all<TestResult>([
89+
pingRes,
90+
wsBotPingRes,
91+
tokenRes,
92+
kvTestRes]);
93+
94+
responses.forEach(result =>{
95+
assignResult(resp, result);
96+
})
97+
;
98+
99+
return new Response(JSON.stringify(resp), {
100+
status: 200,
101+
headers: { 'Content-Type': 'application/json' },
102+
});
103+
};

0 commit comments

Comments
 (0)