@gonzih/meet-the-one-ai 1.0.0
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/.env.example +41 -0
- package/.node-version +1 -0
- package/basis/BERNAYS.md +233 -0
- package/basis/FOUNDING_TRANSCRIPT.md +218 -0
- package/basis/TECH_SPEC.md +303 -0
- package/basis/VALS.md +255 -0
- package/basis/layers/L1_IDENTITY_AUTH.md +78 -0
- package/basis/layers/L2_CONVERSATION.md +159 -0
- package/basis/layers/L3_RECORDING_STORE.md +104 -0
- package/basis/layers/L4_ANALYSIS_PIPELINE.md +257 -0
- package/basis/layers/L5_MATCHING_ENGINE.md +164 -0
- package/basis/layers/L6_CONSENT_INTRODUCTION.md +143 -0
- package/basis/layers/L7_PORTABLE_IDENTITY.md +139 -0
- package/basis/layers/STACK.md +64 -0
- package/basis/schema.sql +203 -0
- package/dist/agent.d.ts +2 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +114 -0
- package/dist/agent.js.map +1 -0
- package/dist/api/routes/auth.d.ts +2 -0
- package/dist/api/routes/auth.d.ts.map +1 -0
- package/dist/api/routes/auth.js +79 -0
- package/dist/api/routes/auth.js.map +1 -0
- package/dist/api/routes/identity.d.ts +2 -0
- package/dist/api/routes/identity.d.ts.map +1 -0
- package/dist/api/routes/identity.js +92 -0
- package/dist/api/routes/identity.js.map +1 -0
- package/dist/api/routes/text-submission.d.ts +2 -0
- package/dist/api/routes/text-submission.d.ts.map +1 -0
- package/dist/api/routes/text-submission.js +56 -0
- package/dist/api/routes/text-submission.js.map +1 -0
- package/dist/api/webhooks/twilio.d.ts +2 -0
- package/dist/api/webhooks/twilio.d.ts.map +1 -0
- package/dist/api/webhooks/twilio.js +144 -0
- package/dist/api/webhooks/twilio.js.map +1 -0
- package/dist/api/webhooks/vapi.d.ts +2 -0
- package/dist/api/webhooks/vapi.d.ts.map +1 -0
- package/dist/api/webhooks/vapi.js +177 -0
- package/dist/api/webhooks/vapi.js.map +1 -0
- package/dist/bot.d.ts +3 -0
- package/dist/bot.d.ts.map +1 -0
- package/dist/bot.js +39 -0
- package/dist/bot.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs/compact-identity.d.ts +2 -0
- package/dist/jobs/compact-identity.d.ts.map +1 -0
- package/dist/jobs/compact-identity.js +159 -0
- package/dist/jobs/compact-identity.js.map +1 -0
- package/dist/jobs/consent-call.d.ts +2 -0
- package/dist/jobs/consent-call.d.ts.map +1 -0
- package/dist/jobs/consent-call.js +70 -0
- package/dist/jobs/consent-call.js.map +1 -0
- package/dist/jobs/export-identity.d.ts +2 -0
- package/dist/jobs/export-identity.d.ts.map +1 -0
- package/dist/jobs/export-identity.js +129 -0
- package/dist/jobs/export-identity.js.map +1 -0
- package/dist/jobs/introduction-call.d.ts +2 -0
- package/dist/jobs/introduction-call.d.ts.map +1 -0
- package/dist/jobs/introduction-call.js +86 -0
- package/dist/jobs/introduction-call.js.map +1 -0
- package/dist/jobs/reanalyze-identity.d.ts +2 -0
- package/dist/jobs/reanalyze-identity.d.ts.map +1 -0
- package/dist/jobs/reanalyze-identity.js +56 -0
- package/dist/jobs/reanalyze-identity.js.map +1 -0
- package/dist/jobs/run-matching.d.ts +2 -0
- package/dist/jobs/run-matching.d.ts.map +1 -0
- package/dist/jobs/run-matching.js +200 -0
- package/dist/jobs/run-matching.js.map +1 -0
- package/dist/jobs/scheduled-matching.d.ts +2 -0
- package/dist/jobs/scheduled-matching.d.ts.map +1 -0
- package/dist/jobs/scheduled-matching.js +44 -0
- package/dist/jobs/scheduled-matching.js.map +1 -0
- package/dist/jobs/transcribe-session.d.ts +2 -0
- package/dist/jobs/transcribe-session.d.ts.map +1 -0
- package/dist/jobs/transcribe-session.js +66 -0
- package/dist/jobs/transcribe-session.js.map +1 -0
- package/dist/lib/anthropic.d.ts +4 -0
- package/dist/lib/anthropic.d.ts.map +1 -0
- package/dist/lib/anthropic.js +32 -0
- package/dist/lib/anthropic.js.map +1 -0
- package/dist/lib/config.d.ts +57 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +73 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/deepgram.d.ts +15 -0
- package/dist/lib/deepgram.d.ts.map +1 -0
- package/dist/lib/deepgram.js +37 -0
- package/dist/lib/deepgram.js.map +1 -0
- package/dist/lib/inngest.d.ts +42 -0
- package/dist/lib/inngest.d.ts.map +1 -0
- package/dist/lib/inngest.js +7 -0
- package/dist/lib/inngest.js.map +1 -0
- package/dist/lib/openai.d.ts +3 -0
- package/dist/lib/openai.d.ts.map +1 -0
- package/dist/lib/openai.js +13 -0
- package/dist/lib/openai.js.map +1 -0
- package/dist/lib/prompts.d.ts +8 -0
- package/dist/lib/prompts.d.ts.map +1 -0
- package/dist/lib/prompts.js +258 -0
- package/dist/lib/prompts.js.map +1 -0
- package/dist/lib/r2.d.ts +7 -0
- package/dist/lib/r2.d.ts.map +1 -0
- package/dist/lib/r2.js +49 -0
- package/dist/lib/r2.js.map +1 -0
- package/dist/lib/session-helpers.d.ts +8 -0
- package/dist/lib/session-helpers.d.ts.map +1 -0
- package/dist/lib/session-helpers.js +31 -0
- package/dist/lib/session-helpers.js.map +1 -0
- package/dist/lib/supabase.d.ts +2 -0
- package/dist/lib/supabase.d.ts.map +1 -0
- package/dist/lib/supabase.js +11 -0
- package/dist/lib/supabase.js.map +1 -0
- package/dist/lib/twilio.d.ts +7 -0
- package/dist/lib/twilio.d.ts.map +1 -0
- package/dist/lib/twilio.js +34 -0
- package/dist/lib/twilio.js.map +1 -0
- package/dist/lib/vapi.d.ts +4 -0
- package/dist/lib/vapi.d.ts.map +1 -0
- package/dist/lib/vapi.js +59 -0
- package/dist/lib/vapi.js.map +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +177 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/types/index.d.ts +104 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +28 -0
- package/railway.json +14 -0
- package/src/agent.ts +123 -0
- package/src/api/routes/auth.ts +95 -0
- package/src/api/routes/identity.ts +112 -0
- package/src/api/routes/text-submission.ts +64 -0
- package/src/api/webhooks/twilio.ts +181 -0
- package/src/api/webhooks/vapi.ts +219 -0
- package/src/bot.ts +44 -0
- package/src/index.ts +11 -0
- package/src/jobs/compact-identity.ts +211 -0
- package/src/jobs/consent-call.ts +87 -0
- package/src/jobs/export-identity.ts +166 -0
- package/src/jobs/introduction-call.ts +101 -0
- package/src/jobs/reanalyze-identity.ts +65 -0
- package/src/jobs/run-matching.ts +243 -0
- package/src/jobs/scheduled-matching.ts +59 -0
- package/src/jobs/transcribe-session.ts +77 -0
- package/src/lib/anthropic.ts +37 -0
- package/src/lib/config.ts +81 -0
- package/src/lib/deepgram.ts +57 -0
- package/src/lib/inngest.ts +33 -0
- package/src/lib/openai.ts +14 -0
- package/src/lib/prompts.ts +266 -0
- package/src/lib/r2.ts +79 -0
- package/src/lib/session-helpers.ts +37 -0
- package/src/lib/supabase.ts +15 -0
- package/src/lib/twilio.ts +49 -0
- package/src/lib/vapi.ts +80 -0
- package/src/mcp-server.ts +195 -0
- package/src/types/index.ts +146 -0
- package/supabase/.branches/_current_branch +1 -0
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/.temp/gotrue-version +1 -0
- package/supabase/.temp/pooler-url +1 -0
- package/supabase/.temp/postgres-version +1 -0
- package/supabase/.temp/project-ref +1 -0
- package/supabase/.temp/rest-version +1 -0
- package/supabase/.temp/storage-migration +1 -0
- package/supabase/.temp/storage-version +1 -0
- package/supabase/config.toml +384 -0
- package/supabase/migrations/20260303000000_initial_schema.sql +203 -0
- package/supabase/migrations/20260304000000_brand_consents.sql +13 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/api/routes/auth.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,WAAW,KAAW,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { send_otp, verify_otp, normalize_phone } from "../../lib/twilio.js";
|
|
4
|
+
import { supabase } from "../../lib/supabase.js";
|
|
5
|
+
import { trigger_intake_call } from "../../lib/vapi.js";
|
|
6
|
+
export const auth_router = Router();
|
|
7
|
+
const RequestOtpBody = z.object({
|
|
8
|
+
phone: z.string().min(7),
|
|
9
|
+
brand: z.string().optional().default("flagship"),
|
|
10
|
+
});
|
|
11
|
+
const VerifyOtpBody = z.object({
|
|
12
|
+
phone: z.string().min(7),
|
|
13
|
+
code: z.string().length(6),
|
|
14
|
+
brand: z.string().optional().default("flagship"),
|
|
15
|
+
});
|
|
16
|
+
// POST /api/auth/request-otp
|
|
17
|
+
auth_router.post("/request-otp", async (req, res) => {
|
|
18
|
+
const parsed = RequestOtpBody.safeParse(req.body);
|
|
19
|
+
if (!parsed.success) {
|
|
20
|
+
res.status(400).json({ error: parsed.error.flatten() });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
let phone;
|
|
24
|
+
try {
|
|
25
|
+
phone = normalize_phone(parsed.data.phone);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
res.status(400).json({ error: "Invalid phone number" });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
await send_otp(phone);
|
|
33
|
+
res.json({ ok: true });
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
console.error("send_otp error", err);
|
|
37
|
+
res.status(500).json({ error: "Failed to send OTP" });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
// POST /api/auth/verify-otp
|
|
41
|
+
auth_router.post("/verify-otp", async (req, res) => {
|
|
42
|
+
const parsed = VerifyOtpBody.safeParse(req.body);
|
|
43
|
+
if (!parsed.success) {
|
|
44
|
+
res.status(400).json({ error: parsed.error.flatten() });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
let phone;
|
|
48
|
+
try {
|
|
49
|
+
phone = normalize_phone(parsed.data.phone);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
res.status(400).json({ error: "Invalid phone number" });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const ok = await verify_otp(phone, parsed.data.code);
|
|
56
|
+
if (!ok) {
|
|
57
|
+
res.status(401).json({ error: "Invalid or expired code" });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Upsert user
|
|
61
|
+
const { data: user, error } = await supabase
|
|
62
|
+
.from("users")
|
|
63
|
+
.upsert({ phone, brand: parsed.data.brand, last_active: new Date().toISOString() }, { onConflict: "phone" })
|
|
64
|
+
.select("id, phone, brand, created_at")
|
|
65
|
+
.single();
|
|
66
|
+
if (error || !user) {
|
|
67
|
+
console.error("upsert user error", error);
|
|
68
|
+
res.status(500).json({ error: "Failed to create user" });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// New user (created_at === last_active within a few ms) → trigger intake call
|
|
72
|
+
const is_new_user = new Date(user.created_at).getTime() > Date.now() - 5000;
|
|
73
|
+
if (is_new_user) {
|
|
74
|
+
// Fire-and-forget — don't block the auth response
|
|
75
|
+
trigger_intake_call(phone, user.id).catch((err) => console.error("trigger_intake_call error", err));
|
|
76
|
+
}
|
|
77
|
+
res.json({ ok: true, user, is_new_user });
|
|
78
|
+
});
|
|
79
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/api/routes/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../../src/api/routes/identity.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,eAAe,KAAW,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { supabase } from "../../lib/supabase.js";
|
|
4
|
+
import { inngest } from "../../lib/inngest.js";
|
|
5
|
+
export const identity_router = Router();
|
|
6
|
+
const UserParams = z.object({ user_id: z.string().uuid() });
|
|
7
|
+
// GET /api/identity/:user_id/export
|
|
8
|
+
// Triggers async export — user gets SMS when ready
|
|
9
|
+
identity_router.get("/:user_id/export", async (req, res) => {
|
|
10
|
+
const parsed = UserParams.safeParse(req.params);
|
|
11
|
+
if (!parsed.success) {
|
|
12
|
+
res.status(400).json({ error: "Invalid user_id" });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const { user_id } = parsed.data;
|
|
16
|
+
// Verify user exists and has an identity
|
|
17
|
+
const { data: identity } = await supabase
|
|
18
|
+
.from("identities")
|
|
19
|
+
.select("signal_completeness_score, session_count")
|
|
20
|
+
.eq("user_id", user_id)
|
|
21
|
+
.single();
|
|
22
|
+
if (!identity) {
|
|
23
|
+
res.status(404).json({ error: "No identity found — complete at least one session first" });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
await inngest.send({ name: "identity/export", data: { user_id } });
|
|
27
|
+
res.json({
|
|
28
|
+
ok: true,
|
|
29
|
+
message: "Export started — you'll receive a download link via SMS shortly",
|
|
30
|
+
completeness: identity.signal_completeness_score,
|
|
31
|
+
sessions: identity.session_count,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
// GET /api/identity/:user_id
|
|
35
|
+
// Returns current identity profile (for web portal)
|
|
36
|
+
identity_router.get("/:user_id", async (req, res) => {
|
|
37
|
+
const parsed = UserParams.safeParse(req.params);
|
|
38
|
+
if (!parsed.success) {
|
|
39
|
+
res.status(400).json({ error: "Invalid user_id" });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const { data, error } = await supabase
|
|
43
|
+
.from("identities")
|
|
44
|
+
.select("base_profile, modality_weights, signal_completeness_score, session_count, ready_for_matching, last_updated")
|
|
45
|
+
.eq("user_id", parsed.data.user_id)
|
|
46
|
+
.single();
|
|
47
|
+
if (error || !data) {
|
|
48
|
+
res.status(404).json({ error: "Identity not found" });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
res.json(data);
|
|
52
|
+
});
|
|
53
|
+
// DELETE /api/identity/:user_id
|
|
54
|
+
// GDPR hard delete — removes all user data
|
|
55
|
+
identity_router.delete("/:user_id", async (req, res) => {
|
|
56
|
+
const parsed = UserParams.safeParse(req.params);
|
|
57
|
+
if (!parsed.success) {
|
|
58
|
+
res.status(400).json({ error: "Invalid user_id" });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const { user_id } = parsed.data;
|
|
62
|
+
// Verify user exists
|
|
63
|
+
const { data: user } = await supabase
|
|
64
|
+
.from("users")
|
|
65
|
+
.select("id")
|
|
66
|
+
.eq("id", user_id)
|
|
67
|
+
.single();
|
|
68
|
+
if (!user) {
|
|
69
|
+
res.status(404).json({ error: "User not found" });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Cascade delete — all child records deleted via ON DELETE CASCADE:
|
|
73
|
+
// sessions, identities, identity_modality_embeddings, matches (partial — see note)
|
|
74
|
+
// matches where user is user_b won't cascade — delete explicitly
|
|
75
|
+
await Promise.all([
|
|
76
|
+
supabase.from("matches").delete().eq("user_a_id", user_id),
|
|
77
|
+
supabase.from("matches").delete().eq("user_b_id", user_id),
|
|
78
|
+
supabase.from("declined_pairs").delete().eq("user_a_id", user_id),
|
|
79
|
+
supabase.from("declined_pairs").delete().eq("user_b_id", user_id),
|
|
80
|
+
]);
|
|
81
|
+
// Delete user — cascades to sessions, identities, embeddings
|
|
82
|
+
const { error } = await supabase.from("users").delete().eq("id", user_id);
|
|
83
|
+
if (error) {
|
|
84
|
+
console.error("GDPR delete error", error);
|
|
85
|
+
res.status(500).json({ error: "Deletion failed — contact support" });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Note: R2 audio/transcript files are NOT deleted here — add async cleanup job
|
|
89
|
+
// for production. R2 lifecycle rules can auto-expire after 30 days.
|
|
90
|
+
res.json({ ok: true, message: "All data deleted" });
|
|
91
|
+
});
|
|
92
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../../src/api/routes/identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-submission.d.ts","sourceRoot":"","sources":["../../../src/api/routes/text-submission.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,sBAAsB,KAAW,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { supabase } from "../../lib/supabase.js";
|
|
4
|
+
import { inngest } from "../../lib/inngest.js";
|
|
5
|
+
import { create_session } from "../../lib/session-helpers.js";
|
|
6
|
+
export const text_submission_router = Router();
|
|
7
|
+
const TextBody = z.object({
|
|
8
|
+
user_id: z.string().uuid(),
|
|
9
|
+
text: z.string().min(10).max(10000),
|
|
10
|
+
});
|
|
11
|
+
// POST /api/session/text
|
|
12
|
+
// Accepts a free-text submission (alternative to voice intake)
|
|
13
|
+
text_submission_router.post("/text", async (req, res) => {
|
|
14
|
+
const parsed = TextBody.safeParse(req.body);
|
|
15
|
+
if (!parsed.success) {
|
|
16
|
+
res.status(400).json({ error: parsed.error.flatten() });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const { user_id, text } = parsed.data;
|
|
20
|
+
// Verify user exists
|
|
21
|
+
const { data: user, error: user_error } = await supabase
|
|
22
|
+
.from("users")
|
|
23
|
+
.select("id")
|
|
24
|
+
.eq("id", user_id)
|
|
25
|
+
.single();
|
|
26
|
+
if (user_error || !user) {
|
|
27
|
+
res.status(404).json({ error: "User not found" });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
let session_id;
|
|
31
|
+
try {
|
|
32
|
+
session_id = await create_session(user_id, "text", { raw_text: text });
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
console.error("create_session error", err);
|
|
36
|
+
res.status(500).json({ error: "Failed to create session" });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Fire Inngest event — triggers compact-identity pipeline
|
|
40
|
+
// Text sessions skip transcription and go straight to analysis
|
|
41
|
+
await inngest.send({
|
|
42
|
+
name: "session/transcribed",
|
|
43
|
+
data: {
|
|
44
|
+
user_id,
|
|
45
|
+
session_id,
|
|
46
|
+
transcript_r2_key: "", // empty — compact-identity reads raw_text instead
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
// Update last_active
|
|
50
|
+
await supabase
|
|
51
|
+
.from("users")
|
|
52
|
+
.update({ last_active: new Date().toISOString() })
|
|
53
|
+
.eq("id", user_id);
|
|
54
|
+
res.json({ ok: true, session_id });
|
|
55
|
+
});
|
|
56
|
+
//# sourceMappingURL=text-submission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-submission.js","sourceRoot":"","sources":["../../../src/api/routes/text-submission.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,EAAE,CAAC;AAE/C,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC;IACxB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE;IAC1B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;CACpC,CAAC,CAAC;AAEH,yBAAyB;AACzB,+DAA+D;AAC/D,sBAAsB,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;IACtD,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACxD,OAAO;IACT,CAAC;IAED,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC;IAEtC,qBAAqB;IACrB,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,QAAQ;SACrD,IAAI,CAAC,OAAO,CAAC;SACb,MAAM,CAAC,IAAI,CAAC;SACZ,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC;SACjB,MAAM,EAAE,CAAC;IAEZ,IAAI,UAAU,IAAI,CAAC,IAAI,EAAE,CAAC;QACxB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;QAClD,OAAO;IACT,CAAC;IAED,IAAI,UAAkB,CAAC;IACvB,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACzE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC;QAC3C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;QAC5D,OAAO;IACT,CAAC;IAED,0DAA0D;IAC1D,+DAA+D;IAC/D,MAAM,OAAO,CAAC,IAAI,CAAC;QACjB,IAAI,EAAE,qBAAqB;QAC3B,IAAI,EAAE;YACJ,OAAO;YACP,UAAU;YACV,iBAAiB,EAAE,EAAE,EAAE,kDAAkD;SAC1E;KACF,CAAC,CAAC;IAEH,qBAAqB;IACrB,MAAM,QAAQ;SACX,IAAI,CAAC,OAAO,CAAC;SACb,MAAM,CAAC,EAAE,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;SACjD,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAErB,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twilio.d.ts","sourceRoot":"","sources":["../../../src/api/webhooks/twilio.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,qBAAqB,KAAW,CAAC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { supabase } from "../../lib/supabase.js";
|
|
3
|
+
import { inngest } from "../../lib/inngest.js";
|
|
4
|
+
import { normalize_phone, send_sms } from "../../lib/twilio.js";
|
|
5
|
+
export const twilio_webhook_router = Router();
|
|
6
|
+
// POST /api/webhooks/twilio/sms
|
|
7
|
+
// Handles inbound SMS — consent responses and export triggers
|
|
8
|
+
twilio_webhook_router.post("/sms", async (req, res) => {
|
|
9
|
+
// Twilio sends form-encoded body
|
|
10
|
+
const from = req.body.From ?? "";
|
|
11
|
+
const body = (req.body.Body ?? "").trim();
|
|
12
|
+
// Always respond with empty TwiML to suppress Twilio error
|
|
13
|
+
res.set("Content-Type", "text/xml");
|
|
14
|
+
res.send(`<?xml version="1.0" encoding="UTF-8"?><Response></Response>`);
|
|
15
|
+
if (!from || !body)
|
|
16
|
+
return;
|
|
17
|
+
const phone = normalize_phone(from);
|
|
18
|
+
const text = body.toUpperCase();
|
|
19
|
+
try {
|
|
20
|
+
await handle_sms(phone, text, body);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
console.error("twilio sms webhook error", err);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
// POST /api/webhooks/twilio/conference
|
|
27
|
+
// Status callback when conference ends — no action needed yet
|
|
28
|
+
twilio_webhook_router.post("/conference", (_req, res) => {
|
|
29
|
+
res.sendStatus(204);
|
|
30
|
+
});
|
|
31
|
+
async function handle_sms(phone, text, raw_body) {
|
|
32
|
+
// Look up user by phone
|
|
33
|
+
const { data: user } = await supabase
|
|
34
|
+
.from("users")
|
|
35
|
+
.select("id, phone, brand")
|
|
36
|
+
.eq("phone", phone)
|
|
37
|
+
.single();
|
|
38
|
+
if (!user) {
|
|
39
|
+
console.warn(`Inbound SMS from unknown phone: ${phone}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// ── EXPORT trigger ────────────────────────────────────────────────────────
|
|
43
|
+
if (text === "EXPORT" || text === "EXPORT IDENTITY" || text === "SEND ME MY IDENTITY") {
|
|
44
|
+
await inngest.send({
|
|
45
|
+
name: "identity/export",
|
|
46
|
+
data: { user_id: user.id },
|
|
47
|
+
});
|
|
48
|
+
await send_sms(phone, "Preparing your identity export — you'll receive a download link shortly.", user.brand);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// ── DELETE trigger ────────────────────────────────────────────────────────
|
|
52
|
+
if (text === "DELETE MY DATA" || text === "DELETE") {
|
|
53
|
+
// Fire deletion via API — use internal fetch to keep logic in one place
|
|
54
|
+
// In production replace with direct service call
|
|
55
|
+
await supabase.from("users").delete().eq("id", user.id);
|
|
56
|
+
// Note: cascades handle sessions, identities, embeddings
|
|
57
|
+
// matches deleted separately
|
|
58
|
+
await Promise.all([
|
|
59
|
+
supabase.from("matches").delete().eq("user_a_id", user.id),
|
|
60
|
+
supabase.from("matches").delete().eq("user_b_id", user.id),
|
|
61
|
+
supabase.from("declined_pairs").delete().eq("user_a_id", user.id),
|
|
62
|
+
supabase.from("declined_pairs").delete().eq("user_b_id", user.id),
|
|
63
|
+
]);
|
|
64
|
+
// Can't send SMS after deletion since user no longer exists — no reply needed
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// ── Consent responses (ACCEPT / DECLINE with match reference) ────────────
|
|
68
|
+
// Format: "ACCEPT [match:uuid]" or just "ACCEPT" (applies to oldest pending)
|
|
69
|
+
const match_ref = raw_body.match(/\[match:([a-f0-9-]{36})\]/i);
|
|
70
|
+
const match_id_from_msg = match_ref?.[1];
|
|
71
|
+
if (text.startsWith("ACCEPT") || text.startsWith("YES")) {
|
|
72
|
+
await handle_consent_sms(user.id, "accepted", match_id_from_msg ?? null);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (text.startsWith("DECLINE") || text.startsWith("NO") || text.startsWith("PASS")) {
|
|
76
|
+
await handle_consent_sms(user.id, "declined", match_id_from_msg ?? null);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Unknown — send help
|
|
80
|
+
await send_sms(phone, "Reply ACCEPT or DECLINE to respond to a match. Reply EXPORT to get your identity file.", user.brand);
|
|
81
|
+
}
|
|
82
|
+
async function handle_consent_sms(user_id, decision, explicit_match_id) {
|
|
83
|
+
// Find the relevant pending match
|
|
84
|
+
let match_id = explicit_match_id;
|
|
85
|
+
if (!match_id) {
|
|
86
|
+
// Find oldest pending_consent match for this user
|
|
87
|
+
const { data } = await supabase
|
|
88
|
+
.from("matches")
|
|
89
|
+
.select("id, user_a_id, user_b_id, status")
|
|
90
|
+
.or(`user_a_id.eq.${user_id},user_b_id.eq.${user_id}`)
|
|
91
|
+
.eq("status", "pending_consent")
|
|
92
|
+
.order("created_at", { ascending: true })
|
|
93
|
+
.limit(1)
|
|
94
|
+
.single();
|
|
95
|
+
if (!data)
|
|
96
|
+
return; // no pending match
|
|
97
|
+
match_id = data.id;
|
|
98
|
+
}
|
|
99
|
+
const { data: match } = await supabase
|
|
100
|
+
.from("matches")
|
|
101
|
+
.select("id, user_a_id, user_b_id, status")
|
|
102
|
+
.eq("id", match_id)
|
|
103
|
+
.single();
|
|
104
|
+
if (!match)
|
|
105
|
+
return;
|
|
106
|
+
const is_user_a = user_id === match.user_a_id;
|
|
107
|
+
if (decision === "declined") {
|
|
108
|
+
await supabase.from("declined_pairs").upsert({
|
|
109
|
+
user_a_id: match.user_a_id,
|
|
110
|
+
user_b_id: match.user_b_id,
|
|
111
|
+
declined_by: user_id,
|
|
112
|
+
reason: "sms_declined",
|
|
113
|
+
created_at: new Date().toISOString(),
|
|
114
|
+
}, { onConflict: "user_a_id,user_b_id" });
|
|
115
|
+
await supabase
|
|
116
|
+
.from("matches")
|
|
117
|
+
.update({
|
|
118
|
+
status: is_user_a ? "a_declined" : "b_declined",
|
|
119
|
+
updated_at: new Date().toISOString(),
|
|
120
|
+
})
|
|
121
|
+
.eq("id", match_id);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// accepted
|
|
125
|
+
const current_status = match.status;
|
|
126
|
+
let new_status;
|
|
127
|
+
if (is_user_a) {
|
|
128
|
+
new_status = current_status === "b_accepted" ? "both_accepted" : "a_accepted";
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
new_status = current_status === "a_accepted" ? "both_accepted" : "b_accepted";
|
|
132
|
+
}
|
|
133
|
+
await supabase
|
|
134
|
+
.from("matches")
|
|
135
|
+
.update({ status: new_status, updated_at: new Date().toISOString() })
|
|
136
|
+
.eq("id", match_id);
|
|
137
|
+
if (new_status === "both_accepted") {
|
|
138
|
+
await inngest.send({
|
|
139
|
+
name: "match/both-accepted",
|
|
140
|
+
data: { match_id },
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=twilio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twilio.js","sourceRoot":"","sources":["../../../src/api/webhooks/twilio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEhE,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,EAAE,CAAC;AAE9C,gCAAgC;AAChC,8DAA8D;AAC9D,qBAAqB,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;IACpD,iCAAiC;IACjC,MAAM,IAAI,GAAW,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;IACzC,MAAM,IAAI,GAAW,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAElD,2DAA2D;IAC3D,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;IACpC,GAAG,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;IAExE,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI;QAAE,OAAO;IAE3B,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAEhC,IAAI,CAAC;QACH,MAAM,UAAU,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,GAAG,CAAC,CAAC;IACjD,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,uCAAuC;AACvC,8DAA8D;AAC9D,qBAAqB,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IACtD,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;AACtB,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,UAAU,CAAC,KAAa,EAAE,IAAY,EAAE,QAAgB;IACrE,wBAAwB;IACxB,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,QAAQ;SAClC,IAAI,CAAC,OAAO,CAAC;SACb,MAAM,CAAC,kBAAkB,CAAC;SAC1B,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC;SAClB,MAAM,EAAE,CAAC;IAEZ,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,IAAI,CAAC,mCAAmC,KAAK,EAAE,CAAC,CAAC;QACzD,OAAO;IACT,CAAC;IAED,6EAA6E;IAC7E,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,iBAAiB,IAAI,IAAI,KAAK,qBAAqB,EAAE,CAAC;QACtF,MAAM,OAAO,CAAC,IAAI,CAAC;YACjB,IAAI,EAAE,iBAAiB;YACvB,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE;SAC3B,CAAC,CAAC;QACH,MAAM,QAAQ,CACZ,KAAK,EACL,0EAA0E,EAC1E,IAAI,CAAC,KAAuC,CAC7C,CAAC;QACF,OAAO;IACT,CAAC;IAED,6EAA6E;IAC7E,IAAI,IAAI,KAAK,gBAAgB,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACnD,wEAAwE;QACxE,iDAAiD;QACjD,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QACxD,yDAAyD;QACzD,6BAA6B;QAC7B,MAAM,OAAO,CAAC,GAAG,CAAC;YAChB,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC;YAC1D,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC;YAC1D,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC;YACjE,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC;SAClE,CAAC,CAAC;QACH,8EAA8E;QAC9E,OAAO;IACT,CAAC;IAED,4EAA4E;IAC5E,6EAA6E;IAC7E,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAC/D,MAAM,iBAAiB,GAAG,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC;IAEzC,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,kBAAkB,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,iBAAiB,IAAI,IAAI,CAAC,CAAC;QACzE,OAAO;IACT,CAAC;IAED,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACnF,MAAM,kBAAkB,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,iBAAiB,IAAI,IAAI,CAAC,CAAC;QACzE,OAAO;IACT,CAAC;IAED,sBAAsB;IACtB,MAAM,QAAQ,CACZ,KAAK,EACL,wFAAwF,EACxF,IAAI,CAAC,KAAuC,CAC7C,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,kBAAkB,CAC/B,OAAe,EACf,QAAiC,EACjC,iBAAgC;IAEhC,kCAAkC;IAClC,IAAI,QAAQ,GAAG,iBAAiB,CAAC;IAEjC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,kDAAkD;QAClD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,QAAQ;aAC5B,IAAI,CAAC,SAAS,CAAC;aACf,MAAM,CAAC,kCAAkC,CAAC;aAC1C,EAAE,CAAC,gBAAgB,OAAO,iBAAiB,OAAO,EAAE,CAAC;aACrD,EAAE,CAAC,QAAQ,EAAE,iBAAiB,CAAC;aAC/B,KAAK,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;aACxC,KAAK,CAAC,CAAC,CAAC;aACR,MAAM,EAAE,CAAC;QAEZ,IAAI,CAAC,IAAI;YAAE,OAAO,CAAC,mBAAmB;QACtC,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC;IACrB,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ;SACnC,IAAI,CAAC,SAAS,CAAC;SACf,MAAM,CAAC,kCAAkC,CAAC;SAC1C,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC;SAClB,MAAM,EAAE,CAAC;IAEZ,IAAI,CAAC,KAAK;QAAE,OAAO;IAEnB,MAAM,SAAS,GAAG,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IAE9C,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;QAC5B,MAAM,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAC1C;YACE,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,WAAW,EAAE,OAAO;YACpB,MAAM,EAAE,cAAc;YACtB,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACrC,EACD,EAAE,UAAU,EAAE,qBAAqB,EAAE,CACtC,CAAC;QAEF,MAAM,QAAQ;aACX,IAAI,CAAC,SAAS,CAAC;aACf,MAAM,CAAC;YACN,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY;YAC/C,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACrC,CAAC;aACD,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAEtB,OAAO;IACT,CAAC;IAED,WAAW;IACX,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,CAAC;IACpC,IAAI,UAAkB,CAAC;IAEvB,IAAI,SAAS,EAAE,CAAC;QACd,UAAU,GAAG,cAAc,KAAK,YAAY,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,YAAY,CAAC;IAChF,CAAC;SAAM,CAAC;QACN,UAAU,GAAG,cAAc,KAAK,YAAY,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,YAAY,CAAC;IAChF,CAAC;IAED,MAAM,QAAQ;SACX,IAAI,CAAC,SAAS,CAAC;SACf,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;SACpE,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAEtB,IAAI,UAAU,KAAK,eAAe,EAAE,CAAC;QACnC,MAAM,OAAO,CAAC,IAAI,CAAC;YACjB,IAAI,EAAE,qBAAqB;YAC3B,IAAI,EAAE,EAAE,QAAQ,EAAE;SACnB,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vapi.d.ts","sourceRoot":"","sources":["../../../src/api/webhooks/vapi.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,mBAAmB,KAAW,CAAC"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { supabase } from "../../lib/supabase.js";
|
|
3
|
+
import { inngest } from "../../lib/inngest.js";
|
|
4
|
+
import { upload_buffer } from "../../lib/r2.js";
|
|
5
|
+
export const vapi_webhook_router = Router();
|
|
6
|
+
// POST /api/webhooks/vapi
|
|
7
|
+
vapi_webhook_router.post("/", async (req, res) => {
|
|
8
|
+
const event = req.body;
|
|
9
|
+
res.json({ ok: true });
|
|
10
|
+
try {
|
|
11
|
+
await handle_vapi_event(event);
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
console.error("vapi webhook handler error", err);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
async function handle_vapi_event(event) {
|
|
18
|
+
switch (event.message?.type) {
|
|
19
|
+
case "end-of-call-report":
|
|
20
|
+
await handle_call_ended(event);
|
|
21
|
+
break;
|
|
22
|
+
default:
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function handle_call_ended(event) {
|
|
27
|
+
const { call, recordingUrl, durationSeconds } = event.message ?? {};
|
|
28
|
+
if (!call?.id)
|
|
29
|
+
return;
|
|
30
|
+
const vars = call.assistantOverrides?.variableValues ?? {};
|
|
31
|
+
const user_id = vars.user_id;
|
|
32
|
+
const match_id = vars.match_id;
|
|
33
|
+
// ── Consent call outcome ──────────────────────────────────────────────────
|
|
34
|
+
if (match_id) {
|
|
35
|
+
await handle_consent_outcome(call, match_id);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// ── Intake call — create session ─────────────────────────────────────────
|
|
39
|
+
if (!user_id) {
|
|
40
|
+
console.warn("vapi call ended but no user_id or match_id in variableValues", call.id);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const { data: session, error } = await supabase
|
|
44
|
+
.from("sessions")
|
|
45
|
+
.insert({
|
|
46
|
+
user_id,
|
|
47
|
+
source: "vapi",
|
|
48
|
+
duration_seconds: durationSeconds ? Math.round(durationSeconds) : null,
|
|
49
|
+
analysis_status: "pending",
|
|
50
|
+
})
|
|
51
|
+
.select("id")
|
|
52
|
+
.single();
|
|
53
|
+
if (error || !session) {
|
|
54
|
+
console.error("Failed to create session for vapi call", error);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const session_id = session.id;
|
|
58
|
+
if (recordingUrl) {
|
|
59
|
+
try {
|
|
60
|
+
const audio_res = await fetch(recordingUrl);
|
|
61
|
+
const audio_buf = Buffer.from(await audio_res.arrayBuffer());
|
|
62
|
+
const audio_r2_key = `audio/${user_id}/${session_id}.wav`;
|
|
63
|
+
await upload_buffer(audio_r2_key, audio_buf, "audio/wav");
|
|
64
|
+
await supabase.from("sessions").update({ audio_r2_key }).eq("id", session_id);
|
|
65
|
+
await inngest.send({
|
|
66
|
+
name: "session/completed",
|
|
67
|
+
data: { user_id, session_id },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
console.error("Failed to store audio for session", session_id, err);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const transcript_text = call.transcript ?? "";
|
|
76
|
+
if (transcript_text) {
|
|
77
|
+
await supabase.from("sessions").update({ raw_text: transcript_text }).eq("id", session_id);
|
|
78
|
+
await inngest.send({
|
|
79
|
+
name: "session/transcribed",
|
|
80
|
+
data: { user_id, session_id, transcript_r2_key: "" },
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
await supabase
|
|
85
|
+
.from("users")
|
|
86
|
+
.update({ last_active: new Date().toISOString() })
|
|
87
|
+
.eq("id", user_id);
|
|
88
|
+
}
|
|
89
|
+
async function handle_consent_outcome(call, match_id) {
|
|
90
|
+
if (!call)
|
|
91
|
+
return;
|
|
92
|
+
// Vapi assistant sets call.summary or transcript to indicate decision
|
|
93
|
+
// Convention: assistant ends call with structured summary JSON
|
|
94
|
+
// e.g. {"decision": "accepted"} or {"decision": "declined"}
|
|
95
|
+
const transcript = call.transcript ?? "";
|
|
96
|
+
const summary = call.summary ?? "";
|
|
97
|
+
const decision = extract_decision(summary || transcript);
|
|
98
|
+
if (!decision) {
|
|
99
|
+
console.warn(`No consent decision found for match ${match_id}, call ${call.id}`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Load match to determine which user this call was for
|
|
103
|
+
const { data: match, error } = await supabase
|
|
104
|
+
.from("matches")
|
|
105
|
+
.select("id, user_a_id, user_b_id, status")
|
|
106
|
+
.eq("id", match_id)
|
|
107
|
+
.single();
|
|
108
|
+
if (error || !match) {
|
|
109
|
+
console.error(`Match not found for consent outcome: ${match_id}`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Identify which user answered (match call.id to stored call reference — simplified:
|
|
113
|
+
// use variableValues.user_id if present, else infer from status)
|
|
114
|
+
const vars = call.assistantOverrides?.variableValues ?? {};
|
|
115
|
+
const answering_user_id = vars.user_id;
|
|
116
|
+
if (!answering_user_id) {
|
|
117
|
+
console.warn(`Cannot identify consenting user for match ${match_id}`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const is_user_a = answering_user_id === match.user_a_id;
|
|
121
|
+
if (decision === "declined") {
|
|
122
|
+
// Record decline — insert to declined_pairs and update match
|
|
123
|
+
await supabase.from("declined_pairs").upsert({
|
|
124
|
+
user_a_id: match.user_a_id,
|
|
125
|
+
user_b_id: match.user_b_id,
|
|
126
|
+
declined_by: answering_user_id,
|
|
127
|
+
reason: "consent_call_declined",
|
|
128
|
+
created_at: new Date().toISOString(),
|
|
129
|
+
}, { onConflict: "user_a_id,user_b_id" });
|
|
130
|
+
const new_status = is_user_a ? "a_declined" : "b_declined";
|
|
131
|
+
await supabase
|
|
132
|
+
.from("matches")
|
|
133
|
+
.update({ status: new_status, updated_at: new Date().toISOString() })
|
|
134
|
+
.eq("id", match_id);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// decision === "accepted"
|
|
138
|
+
const current_status = match.status;
|
|
139
|
+
let new_status;
|
|
140
|
+
if (is_user_a) {
|
|
141
|
+
new_status = current_status === "b_accepted" ? "both_accepted" : "a_accepted";
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
new_status = current_status === "a_accepted" ? "both_accepted" : "b_accepted";
|
|
145
|
+
}
|
|
146
|
+
await supabase
|
|
147
|
+
.from("matches")
|
|
148
|
+
.update({ status: new_status, updated_at: new Date().toISOString() })
|
|
149
|
+
.eq("id", match_id);
|
|
150
|
+
if (new_status === "both_accepted") {
|
|
151
|
+
await inngest.send({
|
|
152
|
+
name: "match/both-accepted",
|
|
153
|
+
data: { match_id },
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Extract accept/decline from Vapi call summary or transcript
|
|
158
|
+
function extract_decision(text) {
|
|
159
|
+
// Try structured JSON first (Vapi assistant summary)
|
|
160
|
+
try {
|
|
161
|
+
const parsed = JSON.parse(text);
|
|
162
|
+
if (parsed.decision === "accepted")
|
|
163
|
+
return "accepted";
|
|
164
|
+
if (parsed.decision === "declined")
|
|
165
|
+
return "declined";
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// not JSON — fall through to keyword search
|
|
169
|
+
}
|
|
170
|
+
const lower = text.toLowerCase();
|
|
171
|
+
if (/\b(accept|yes|interested|connect)\b/.test(lower))
|
|
172
|
+
return "accepted";
|
|
173
|
+
if (/\b(decline|no|pass|not interested)\b/.test(lower))
|
|
174
|
+
return "declined";
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=vapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vapi.js","sourceRoot":"","sources":["../../../src/api/webhooks/vapi.ts"],"names":[],"mappings":"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"}
|
package/dist/bot.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAKpC,wBAAgB,SAAS,IAAI,QAAQ,CAsCpC"}
|