realtimex-crm 0.8.1 → 0.9.2
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/bin/realtimex-crm.js +56 -32
- package/dist/assets/{DealList-B3alafQv.js → DealList-DbwJCRGl.js} +2 -2
- package/dist/assets/{DealList-B3alafQv.js.map → DealList-DbwJCRGl.js.map} +1 -1
- package/dist/assets/index-C__S90Gb.css +1 -0
- package/dist/assets/index-mE-upBfc.js +166 -0
- package/dist/assets/index-mE-upBfc.js.map +1 -0
- package/dist/index.html +1 -1
- package/dist/stats.html +1 -1
- package/package.json +3 -1
- package/src/components/atomic-crm/activities/ActivitiesPage.tsx +16 -0
- package/src/components/atomic-crm/activities/ActivityFeed.tsx +212 -0
- package/src/components/atomic-crm/activities/FileUpload.tsx +359 -0
- package/src/components/atomic-crm/contacts/ContactShow.tsx +28 -10
- package/src/components/atomic-crm/integrations/ApiKeysTab.tsx +184 -0
- package/src/components/atomic-crm/integrations/CreateApiKeyDialog.tsx +217 -0
- package/src/components/atomic-crm/integrations/CreateChannelDialog.tsx +139 -0
- package/src/components/atomic-crm/integrations/IngestionChannelsTab.tsx +188 -0
- package/src/components/atomic-crm/integrations/IntegrationsPage.tsx +46 -0
- package/src/components/atomic-crm/integrations/WebhooksTab.tsx +402 -0
- package/src/components/atomic-crm/layout/Header.tsx +14 -1
- package/src/components/atomic-crm/root/CRM.tsx +2 -0
- package/src/components/ui/alert-dialog.tsx +155 -0
- package/src/lib/api-key-utils.ts +22 -0
- package/supabase/fix_webhook_hardcoded.sql +34 -0
- package/supabase/functions/_shared/apiKeyAuth.ts +171 -0
- package/supabase/functions/_shared/ingestionGuard.ts +128 -0
- package/supabase/functions/_shared/utils.ts +1 -1
- package/supabase/functions/_shared/webhookSignature.ts +23 -0
- package/supabase/functions/api-v1-activities/index.ts +137 -0
- package/supabase/functions/api-v1-companies/index.ts +166 -0
- package/supabase/functions/api-v1-contacts/index.ts +171 -0
- package/supabase/functions/api-v1-deals/index.ts +166 -0
- package/supabase/functions/ingest-activity/.well-known/supabase/config.toml +4 -0
- package/supabase/functions/ingest-activity/index.ts +261 -0
- package/supabase/functions/webhook-dispatcher/index.ts +133 -0
- package/supabase/migrations/20251219120000_api_integrations.sql +133 -0
- package/supabase/migrations/20251219120100_webhook_triggers.sql +176 -0
- package/supabase/migrations/20251219120200_webhook_cron.sql +26 -0
- package/supabase/migrations/20251220120000_realtime_ingestion.sql +154 -0
- package/supabase/migrations/20251221000000_contact_matching.sql +94 -0
- package/supabase/migrations/20251221000001_fix_ingestion_providers_rls.sql +23 -0
- package/supabase/migrations/20251221000002_fix_contact_matching_jsonb.sql +67 -0
- package/supabase/migrations/20251221000003_fix_email_matching.sql +70 -0
- package/supabase/migrations/20251221000004_time_based_work_stealing.sql +99 -0
- package/supabase/migrations/20251221000005_realtime_functions.sql +73 -0
- package/supabase/migrations/20251221000006_enable_pg_net.sql +3 -0
- package/supabase/migrations/20251222075019_enable_extensions_and_configure_cron.sql +86 -0
- package/supabase/migrations/20251222094036_large_payload_storage.sql +213 -0
- package/supabase/migrations/20251222094247_large_payload_cron.sql +28 -0
- package/supabase/migrations/20251222220000_fix_large_payload_types.sql +72 -0
- package/supabase/migrations/20251223000000_enable_realtime_all_crm_tables.sql +50 -0
- package/supabase/migrations/20251223185638_remove_large_payload_storage.sql +54 -0
- package/dist/assets/index-CHk72bAf.css +0 -1
- package/dist/assets/index-mhAjQ1_w.js +0 -153
- package/dist/assets/index-mhAjQ1_w.js.map +0 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
|
2
|
+
import { supabaseAdmin } from "../_shared/supabaseAdmin.ts";
|
|
3
|
+
import { corsHeaders, createErrorResponse } from "../_shared/utils.ts";
|
|
4
|
+
import {
|
|
5
|
+
validateApiKey,
|
|
6
|
+
checkRateLimit,
|
|
7
|
+
hasScope,
|
|
8
|
+
logApiRequest,
|
|
9
|
+
} from "../_shared/apiKeyAuth.ts";
|
|
10
|
+
|
|
11
|
+
Deno.serve(async (req: Request) => {
|
|
12
|
+
const startTime = Date.now();
|
|
13
|
+
|
|
14
|
+
if (req.method === "OPTIONS") {
|
|
15
|
+
return new Response(null, { status: 204, headers: corsHeaders });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const authResult = await validateApiKey(req);
|
|
19
|
+
if ("status" in authResult) {
|
|
20
|
+
return authResult;
|
|
21
|
+
}
|
|
22
|
+
const { apiKey } = authResult;
|
|
23
|
+
|
|
24
|
+
const rateLimitError = checkRateLimit(apiKey.id);
|
|
25
|
+
if (rateLimitError) {
|
|
26
|
+
await logApiRequest(
|
|
27
|
+
apiKey.id,
|
|
28
|
+
"/v1/companies",
|
|
29
|
+
req.method,
|
|
30
|
+
429,
|
|
31
|
+
Date.now() - startTime,
|
|
32
|
+
req
|
|
33
|
+
);
|
|
34
|
+
return rateLimitError;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const url = new URL(req.url);
|
|
39
|
+
const pathParts = url.pathname.split("/").filter(Boolean);
|
|
40
|
+
const companyId = pathParts[1];
|
|
41
|
+
|
|
42
|
+
let response: Response;
|
|
43
|
+
|
|
44
|
+
if (req.method === "GET" && companyId) {
|
|
45
|
+
response = await getCompany(apiKey, companyId);
|
|
46
|
+
} else if (req.method === "POST") {
|
|
47
|
+
response = await createCompany(apiKey, req);
|
|
48
|
+
} else if (req.method === "PATCH" && companyId) {
|
|
49
|
+
response = await updateCompany(apiKey, companyId, req);
|
|
50
|
+
} else if (req.method === "DELETE" && companyId) {
|
|
51
|
+
response = await deleteCompany(apiKey, companyId);
|
|
52
|
+
} else {
|
|
53
|
+
response = createErrorResponse(404, "Not found");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const responseTime = Date.now() - startTime;
|
|
57
|
+
await logApiRequest(
|
|
58
|
+
apiKey.id,
|
|
59
|
+
url.pathname,
|
|
60
|
+
req.method,
|
|
61
|
+
response.status,
|
|
62
|
+
responseTime,
|
|
63
|
+
req
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return response;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const responseTime = Date.now() - startTime;
|
|
69
|
+
await logApiRequest(
|
|
70
|
+
apiKey.id,
|
|
71
|
+
new URL(req.url).pathname,
|
|
72
|
+
req.method,
|
|
73
|
+
500,
|
|
74
|
+
responseTime,
|
|
75
|
+
req,
|
|
76
|
+
error.message
|
|
77
|
+
);
|
|
78
|
+
return createErrorResponse(500, "Internal server error");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
async function getCompany(apiKey: any, companyId: string) {
|
|
83
|
+
if (!hasScope(apiKey, "companies:read")) {
|
|
84
|
+
return createErrorResponse(403, "Insufficient permissions");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const { data, error } = await supabaseAdmin
|
|
88
|
+
.from("companies")
|
|
89
|
+
.select("*")
|
|
90
|
+
.eq("id", companyId)
|
|
91
|
+
.single();
|
|
92
|
+
|
|
93
|
+
if (error || !data) {
|
|
94
|
+
return createErrorResponse(404, "Company not found");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return new Response(JSON.stringify({ data }), {
|
|
98
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function createCompany(apiKey: any, req: Request) {
|
|
103
|
+
if (!hasScope(apiKey, "companies:write")) {
|
|
104
|
+
return createErrorResponse(403, "Insufficient permissions");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const body = await req.json();
|
|
108
|
+
|
|
109
|
+
const { data, error } = await supabaseAdmin
|
|
110
|
+
.from("companies")
|
|
111
|
+
.insert({
|
|
112
|
+
...body,
|
|
113
|
+
sales_id: apiKey.sales_id,
|
|
114
|
+
})
|
|
115
|
+
.select()
|
|
116
|
+
.single();
|
|
117
|
+
|
|
118
|
+
if (error) {
|
|
119
|
+
return createErrorResponse(400, error.message);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return new Response(JSON.stringify({ data }), {
|
|
123
|
+
status: 201,
|
|
124
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function updateCompany(apiKey: any, companyId: string, req: Request) {
|
|
129
|
+
if (!hasScope(apiKey, "companies:write")) {
|
|
130
|
+
return createErrorResponse(403, "Insufficient permissions");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const body = await req.json();
|
|
134
|
+
|
|
135
|
+
const { data, error } = await supabaseAdmin
|
|
136
|
+
.from("companies")
|
|
137
|
+
.update(body)
|
|
138
|
+
.eq("id", companyId)
|
|
139
|
+
.select()
|
|
140
|
+
.single();
|
|
141
|
+
|
|
142
|
+
if (error || !data) {
|
|
143
|
+
return createErrorResponse(404, "Company not found");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return new Response(JSON.stringify({ data }), {
|
|
147
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function deleteCompany(apiKey: any, companyId: string) {
|
|
152
|
+
if (!hasScope(apiKey, "companies:write")) {
|
|
153
|
+
return createErrorResponse(403, "Insufficient permissions");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const { error } = await supabaseAdmin
|
|
157
|
+
.from("companies")
|
|
158
|
+
.delete()
|
|
159
|
+
.eq("id", companyId);
|
|
160
|
+
|
|
161
|
+
if (error) {
|
|
162
|
+
return createErrorResponse(404, "Company not found");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return new Response(null, { status: 204, headers: corsHeaders });
|
|
166
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
|
2
|
+
import { supabaseAdmin } from "../_shared/supabaseAdmin.ts";
|
|
3
|
+
import { corsHeaders, createErrorResponse } from "../_shared/utils.ts";
|
|
4
|
+
import {
|
|
5
|
+
validateApiKey,
|
|
6
|
+
checkRateLimit,
|
|
7
|
+
hasScope,
|
|
8
|
+
logApiRequest,
|
|
9
|
+
} from "../_shared/apiKeyAuth.ts";
|
|
10
|
+
|
|
11
|
+
Deno.serve(async (req: Request) => {
|
|
12
|
+
const startTime = Date.now();
|
|
13
|
+
|
|
14
|
+
// Handle CORS
|
|
15
|
+
if (req.method === "OPTIONS") {
|
|
16
|
+
return new Response(null, { status: 204, headers: corsHeaders });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Validate API key
|
|
20
|
+
const authResult = await validateApiKey(req);
|
|
21
|
+
if ("status" in authResult) {
|
|
22
|
+
return authResult; // Error response
|
|
23
|
+
}
|
|
24
|
+
const { apiKey } = authResult;
|
|
25
|
+
|
|
26
|
+
// Check rate limit
|
|
27
|
+
const rateLimitError = checkRateLimit(apiKey.id);
|
|
28
|
+
if (rateLimitError) {
|
|
29
|
+
await logApiRequest(
|
|
30
|
+
apiKey.id,
|
|
31
|
+
"/v1/contacts",
|
|
32
|
+
req.method,
|
|
33
|
+
429,
|
|
34
|
+
Date.now() - startTime,
|
|
35
|
+
req
|
|
36
|
+
);
|
|
37
|
+
return rateLimitError;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const url = new URL(req.url);
|
|
42
|
+
const pathParts = url.pathname.split("/").filter(Boolean);
|
|
43
|
+
// pathParts: ["api-v1-contacts", "{id}"]
|
|
44
|
+
const contactId = pathParts[1];
|
|
45
|
+
|
|
46
|
+
let response: Response;
|
|
47
|
+
|
|
48
|
+
if (req.method === "GET" && contactId) {
|
|
49
|
+
response = await getContact(apiKey, contactId);
|
|
50
|
+
} else if (req.method === "POST") {
|
|
51
|
+
response = await createContact(apiKey, req);
|
|
52
|
+
} else if (req.method === "PATCH" && contactId) {
|
|
53
|
+
response = await updateContact(apiKey, contactId, req);
|
|
54
|
+
} else if (req.method === "DELETE" && contactId) {
|
|
55
|
+
response = await deleteContact(apiKey, contactId);
|
|
56
|
+
} else {
|
|
57
|
+
response = createErrorResponse(404, "Not found");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const responseTime = Date.now() - startTime;
|
|
61
|
+
await logApiRequest(
|
|
62
|
+
apiKey.id,
|
|
63
|
+
url.pathname,
|
|
64
|
+
req.method,
|
|
65
|
+
response.status,
|
|
66
|
+
responseTime,
|
|
67
|
+
req
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return response;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
const responseTime = Date.now() - startTime;
|
|
73
|
+
await logApiRequest(
|
|
74
|
+
apiKey.id,
|
|
75
|
+
new URL(req.url).pathname,
|
|
76
|
+
req.method,
|
|
77
|
+
500,
|
|
78
|
+
responseTime,
|
|
79
|
+
req,
|
|
80
|
+
error.message
|
|
81
|
+
);
|
|
82
|
+
return createErrorResponse(500, "Internal server error");
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
async function getContact(apiKey: any, contactId: string) {
|
|
87
|
+
if (!hasScope(apiKey, "contacts:read")) {
|
|
88
|
+
return createErrorResponse(403, "Insufficient permissions");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const id = parseInt(contactId, 10);
|
|
92
|
+
const { data, error } = await supabaseAdmin
|
|
93
|
+
.from("contacts")
|
|
94
|
+
.select("*")
|
|
95
|
+
.eq("id", id)
|
|
96
|
+
.single();
|
|
97
|
+
|
|
98
|
+
if (error || !data) {
|
|
99
|
+
return createErrorResponse(404, "Contact not found");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return new Response(JSON.stringify({ data }), {
|
|
103
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function createContact(apiKey: any, req: Request) {
|
|
108
|
+
if (!hasScope(apiKey, "contacts:write")) {
|
|
109
|
+
return createErrorResponse(403, "Insufficient permissions");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const body = await req.json();
|
|
113
|
+
|
|
114
|
+
const { data, error } = await supabaseAdmin
|
|
115
|
+
.from("contacts")
|
|
116
|
+
.insert({
|
|
117
|
+
...body,
|
|
118
|
+
sales_id: apiKey.sales_id, // Associate with API key owner
|
|
119
|
+
})
|
|
120
|
+
.select()
|
|
121
|
+
.single();
|
|
122
|
+
|
|
123
|
+
if (error) {
|
|
124
|
+
return createErrorResponse(400, error.message);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return new Response(JSON.stringify({ data }), {
|
|
128
|
+
status: 201,
|
|
129
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function updateContact(apiKey: any, contactId: string, req: Request) {
|
|
134
|
+
if (!hasScope(apiKey, "contacts:write")) {
|
|
135
|
+
return createErrorResponse(403, "Insufficient permissions");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const body = await req.json();
|
|
139
|
+
|
|
140
|
+
const { data, error } = await supabaseAdmin
|
|
141
|
+
.from("contacts")
|
|
142
|
+
.update(body)
|
|
143
|
+
.eq("id", parseInt(contactId, 10))
|
|
144
|
+
.select()
|
|
145
|
+
.single();
|
|
146
|
+
|
|
147
|
+
if (error || !data) {
|
|
148
|
+
return createErrorResponse(404, "Contact not found");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return new Response(JSON.stringify({ data }), {
|
|
152
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function deleteContact(apiKey: any, contactId: string) {
|
|
157
|
+
if (!hasScope(apiKey, "contacts:write")) {
|
|
158
|
+
return createErrorResponse(403, "Insufficient permissions");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const { error } = await supabaseAdmin
|
|
162
|
+
.from("contacts")
|
|
163
|
+
.delete()
|
|
164
|
+
.eq("id", parseInt(contactId, 10));
|
|
165
|
+
|
|
166
|
+
if (error) {
|
|
167
|
+
return createErrorResponse(404, "Contact not found");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return new Response(null, { status: 204, headers: corsHeaders });
|
|
171
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
|
2
|
+
import { supabaseAdmin } from "../_shared/supabaseAdmin.ts";
|
|
3
|
+
import { corsHeaders, createErrorResponse } from "../_shared/utils.ts";
|
|
4
|
+
import {
|
|
5
|
+
validateApiKey,
|
|
6
|
+
checkRateLimit,
|
|
7
|
+
hasScope,
|
|
8
|
+
logApiRequest,
|
|
9
|
+
} from "../_shared/apiKeyAuth.ts";
|
|
10
|
+
|
|
11
|
+
Deno.serve(async (req: Request) => {
|
|
12
|
+
const startTime = Date.now();
|
|
13
|
+
|
|
14
|
+
if (req.method === "OPTIONS") {
|
|
15
|
+
return new Response(null, { status: 204, headers: corsHeaders });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const authResult = await validateApiKey(req);
|
|
19
|
+
if ("status" in authResult) {
|
|
20
|
+
return authResult;
|
|
21
|
+
}
|
|
22
|
+
const { apiKey } = authResult;
|
|
23
|
+
|
|
24
|
+
const rateLimitError = checkRateLimit(apiKey.id);
|
|
25
|
+
if (rateLimitError) {
|
|
26
|
+
await logApiRequest(
|
|
27
|
+
apiKey.id,
|
|
28
|
+
"/v1/deals",
|
|
29
|
+
req.method,
|
|
30
|
+
429,
|
|
31
|
+
Date.now() - startTime,
|
|
32
|
+
req
|
|
33
|
+
);
|
|
34
|
+
return rateLimitError;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const url = new URL(req.url);
|
|
39
|
+
const pathParts = url.pathname.split("/").filter(Boolean);
|
|
40
|
+
const dealId = pathParts[1];
|
|
41
|
+
|
|
42
|
+
let response: Response;
|
|
43
|
+
|
|
44
|
+
if (req.method === "GET" && dealId) {
|
|
45
|
+
response = await getDeal(apiKey, dealId);
|
|
46
|
+
} else if (req.method === "POST") {
|
|
47
|
+
response = await createDeal(apiKey, req);
|
|
48
|
+
} else if (req.method === "PATCH" && dealId) {
|
|
49
|
+
response = await updateDeal(apiKey, dealId, req);
|
|
50
|
+
} else if (req.method === "DELETE" && dealId) {
|
|
51
|
+
response = await deleteDeal(apiKey, dealId);
|
|
52
|
+
} else {
|
|
53
|
+
response = createErrorResponse(404, "Not found");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const responseTime = Date.now() - startTime;
|
|
57
|
+
await logApiRequest(
|
|
58
|
+
apiKey.id,
|
|
59
|
+
url.pathname,
|
|
60
|
+
req.method,
|
|
61
|
+
response.status,
|
|
62
|
+
responseTime,
|
|
63
|
+
req
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return response;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const responseTime = Date.now() - startTime;
|
|
69
|
+
await logApiRequest(
|
|
70
|
+
apiKey.id,
|
|
71
|
+
new URL(req.url).pathname,
|
|
72
|
+
req.method,
|
|
73
|
+
500,
|
|
74
|
+
responseTime,
|
|
75
|
+
req,
|
|
76
|
+
error.message
|
|
77
|
+
);
|
|
78
|
+
return createErrorResponse(500, "Internal server error");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
async function getDeal(apiKey: any, dealId: string) {
|
|
83
|
+
if (!hasScope(apiKey, "deals:read")) {
|
|
84
|
+
return createErrorResponse(403, "Insufficient permissions");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const { data, error } = await supabaseAdmin
|
|
88
|
+
.from("deals")
|
|
89
|
+
.select("*")
|
|
90
|
+
.eq("id", dealId)
|
|
91
|
+
.single();
|
|
92
|
+
|
|
93
|
+
if (error || !data) {
|
|
94
|
+
return createErrorResponse(404, "Deal not found");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return new Response(JSON.stringify({ data }), {
|
|
98
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function createDeal(apiKey: any, req: Request) {
|
|
103
|
+
if (!hasScope(apiKey, "deals:write")) {
|
|
104
|
+
return createErrorResponse(403, "Insufficient permissions");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const body = await req.json();
|
|
108
|
+
|
|
109
|
+
const { data, error } = await supabaseAdmin
|
|
110
|
+
.from("deals")
|
|
111
|
+
.insert({
|
|
112
|
+
...body,
|
|
113
|
+
sales_id: apiKey.sales_id,
|
|
114
|
+
})
|
|
115
|
+
.select()
|
|
116
|
+
.single();
|
|
117
|
+
|
|
118
|
+
if (error) {
|
|
119
|
+
return createErrorResponse(400, error.message);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return new Response(JSON.stringify({ data }), {
|
|
123
|
+
status: 201,
|
|
124
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function updateDeal(apiKey: any, dealId: string, req: Request) {
|
|
129
|
+
if (!hasScope(apiKey, "deals:write")) {
|
|
130
|
+
return createErrorResponse(403, "Insufficient permissions");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const body = await req.json();
|
|
134
|
+
|
|
135
|
+
const { data, error } = await supabaseAdmin
|
|
136
|
+
.from("deals")
|
|
137
|
+
.update(body)
|
|
138
|
+
.eq("id", dealId)
|
|
139
|
+
.select()
|
|
140
|
+
.single();
|
|
141
|
+
|
|
142
|
+
if (error || !data) {
|
|
143
|
+
return createErrorResponse(404, "Deal not found");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return new Response(JSON.stringify({ data }), {
|
|
147
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function deleteDeal(apiKey: any, dealId: string) {
|
|
152
|
+
if (!hasScope(apiKey, "deals:write")) {
|
|
153
|
+
return createErrorResponse(403, "Insufficient permissions");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const { error } = await supabaseAdmin
|
|
157
|
+
.from("deals")
|
|
158
|
+
.delete()
|
|
159
|
+
.eq("id", dealId);
|
|
160
|
+
|
|
161
|
+
if (error) {
|
|
162
|
+
return createErrorResponse(404, "Deal not found");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return new Response(null, { status: 204, headers: corsHeaders });
|
|
166
|
+
}
|