realtimex-crm 0.4.1 → 0.5.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.
@@ -0,0 +1,240 @@
1
+ // RealTimeX CRM - Users Edge Function
2
+ // This function handles creating and updating CRM users (sales team members)
3
+ //
4
+ // To deploy:
5
+ // 1. Go to Supabase Dashboard → Edge Functions
6
+ // 2. Click "Deploy a new function"
7
+ // 3. Name it: users
8
+ // 4. Copy and paste this entire file
9
+ // 5. Click "Deploy"
10
+
11
+ import { createClient } from "jsr:@supabase/supabase-js@2";
12
+
13
+ // CORS headers for API access
14
+ const corsHeaders = {
15
+ "Access-Control-Allow-Origin": "*",
16
+ "Access-Control-Allow-Headers":
17
+ "authorization, x-client-info, apikey, content-type",
18
+ "Access-Control-Allow-Methods": "POST, PATCH, DELETE",
19
+ };
20
+
21
+ function createErrorResponse(status: number, message: string) {
22
+ return new Response(JSON.stringify({ status, message }), {
23
+ headers: { "Content-Type": "application/json", ...corsHeaders },
24
+ status,
25
+ });
26
+ }
27
+
28
+ // Initialize Supabase Admin client (uses service_role key automatically)
29
+ const supabaseAdmin = createClient(
30
+ Deno.env.get("SUPABASE_URL") ?? "",
31
+ Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
32
+ {
33
+ auth: {
34
+ autoRefreshToken: false,
35
+ persistSession: false,
36
+ },
37
+ }
38
+ );
39
+
40
+ async function updateSaleDisabled(user_id: string, disabled: boolean) {
41
+ return await supabaseAdmin
42
+ .from("sales")
43
+ .update({ disabled: disabled ?? false })
44
+ .eq("user_id", user_id);
45
+ }
46
+
47
+ async function updateSaleAdministrator(
48
+ user_id: string,
49
+ administrator: boolean
50
+ ) {
51
+ const { data: sales, error: salesError } = await supabaseAdmin
52
+ .from("sales")
53
+ .update({ administrator })
54
+ .eq("user_id", user_id)
55
+ .select("*");
56
+
57
+ if (!sales?.length || salesError) {
58
+ console.error("Error updating user:", salesError);
59
+ throw salesError ?? new Error("Failed to update sale");
60
+ }
61
+ return sales.at(0);
62
+ }
63
+
64
+ async function updateSaleAvatar(user_id: string, avatar: string) {
65
+ const { data: sales, error: salesError } = await supabaseAdmin
66
+ .from("sales")
67
+ .update({ avatar })
68
+ .eq("user_id", user_id)
69
+ .select("*");
70
+
71
+ if (!sales?.length || salesError) {
72
+ console.error("Error updating user:", salesError);
73
+ throw salesError ?? new Error("Failed to update sale");
74
+ }
75
+ return sales.at(0);
76
+ }
77
+
78
+ async function inviteUser(req: Request, currentUserSale: any) {
79
+ const { email, password, first_name, last_name, disabled, administrator } =
80
+ await req.json();
81
+
82
+ if (!currentUserSale.administrator) {
83
+ return createErrorResponse(401, "Not Authorized");
84
+ }
85
+
86
+ const { data, error: userError } = await supabaseAdmin.auth.admin.createUser({
87
+ email,
88
+ password,
89
+ user_metadata: { first_name, last_name },
90
+ });
91
+
92
+ const { error: emailError } =
93
+ await supabaseAdmin.auth.admin.inviteUserByEmail(email);
94
+
95
+ if (!data?.user || userError) {
96
+ console.error(`Error inviting user: user_error=${userError}`);
97
+ return createErrorResponse(500, "Internal Server Error");
98
+ }
99
+
100
+ if (!data?.user || userError || emailError) {
101
+ console.error(`Error inviting user, email_error=${emailError}`);
102
+ return createErrorResponse(500, "Failed to send invitation mail");
103
+ }
104
+
105
+ try {
106
+ await updateSaleDisabled(data.user.id, disabled);
107
+ const sale = await updateSaleAdministrator(data.user.id, administrator);
108
+
109
+ return new Response(
110
+ JSON.stringify({
111
+ data: sale,
112
+ }),
113
+ {
114
+ headers: { "Content-Type": "application/json", ...corsHeaders },
115
+ }
116
+ );
117
+ } catch (e) {
118
+ console.error("Error patching sale:", e);
119
+ return createErrorResponse(500, "Internal Server Error");
120
+ }
121
+ }
122
+
123
+ async function patchUser(req: Request, currentUserSale: any) {
124
+ const {
125
+ sales_id,
126
+ email,
127
+ first_name,
128
+ last_name,
129
+ avatar,
130
+ administrator,
131
+ disabled,
132
+ } = await req.json();
133
+ const { data: sale } = await supabaseAdmin
134
+ .from("sales")
135
+ .select("*")
136
+ .eq("id", sales_id)
137
+ .single();
138
+
139
+ if (!sale) {
140
+ return createErrorResponse(404, "Not Found");
141
+ }
142
+
143
+ // Users can only update their own profile unless they are an administrator
144
+ if (!currentUserSale.administrator && currentUserSale.id !== sale.id) {
145
+ return createErrorResponse(401, "Not Authorized");
146
+ }
147
+
148
+ const { data, error: userError } =
149
+ await supabaseAdmin.auth.admin.updateUserById(sale.user_id, {
150
+ email,
151
+ ban_duration: disabled ? "87600h" : "none",
152
+ user_metadata: { first_name, last_name },
153
+ });
154
+
155
+ if (!data?.user || userError) {
156
+ console.error("Error patching user:", userError);
157
+ return createErrorResponse(500, "Internal Server Error");
158
+ }
159
+
160
+ if (avatar) {
161
+ await updateSaleAvatar(data.user.id, avatar);
162
+ }
163
+
164
+ // Only administrators can update the administrator and disabled status
165
+ if (!currentUserSale.administrator) {
166
+ const { data: new_sale } = await supabaseAdmin
167
+ .from("sales")
168
+ .select("*")
169
+ .eq("id", sales_id)
170
+ .single();
171
+ return new Response(
172
+ JSON.stringify({
173
+ data: new_sale,
174
+ }),
175
+ {
176
+ headers: {
177
+ "Content-Type": "application/json",
178
+ ...corsHeaders,
179
+ },
180
+ }
181
+ );
182
+ }
183
+
184
+ try {
185
+ await updateSaleDisabled(data.user.id, disabled);
186
+ const sale = await updateSaleAdministrator(data.user.id, administrator);
187
+ return new Response(
188
+ JSON.stringify({
189
+ data: sale,
190
+ }),
191
+ {
192
+ headers: {
193
+ "Content-Type": "application/json",
194
+ ...corsHeaders,
195
+ },
196
+ }
197
+ );
198
+ } catch (e) {
199
+ console.error("Error patching sale:", e);
200
+ return createErrorResponse(500, "Internal Server Error");
201
+ }
202
+ }
203
+
204
+ Deno.serve(async (req: Request) => {
205
+ if (req.method === "OPTIONS") {
206
+ return new Response(null, {
207
+ status: 204,
208
+ headers: corsHeaders,
209
+ });
210
+ }
211
+
212
+ const authHeader = req.headers.get("Authorization")!;
213
+ const localClient = createClient(
214
+ Deno.env.get("SUPABASE_URL") ?? "",
215
+ Deno.env.get("SUPABASE_ANON_KEY") ?? "",
216
+ { global: { headers: { Authorization: authHeader } } }
217
+ );
218
+ const { data } = await localClient.auth.getUser();
219
+ if (!data?.user) {
220
+ return createErrorResponse(401, "Unauthorized");
221
+ }
222
+ const currentUserSale = await supabaseAdmin
223
+ .from("sales")
224
+ .select("*")
225
+ .eq("user_id", data.user.id)
226
+ .single();
227
+
228
+ if (!currentUserSale?.data) {
229
+ return createErrorResponse(401, "Unauthorized");
230
+ }
231
+ if (req.method === "POST") {
232
+ return inviteUser(req, currentUserSale.data);
233
+ }
234
+
235
+ if (req.method === "PATCH") {
236
+ return patchUser(req, currentUserSale.data);
237
+ }
238
+
239
+ return createErrorResponse(405, "Method Not Allowed");
240
+ });
package/dist/index.html CHANGED
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="minimum-scale=1,initial-scale=1,width=device-width,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="./manifest.json"><link rel="icon shortcut" href="./favicon.ico"><title>CRM</title><style>body{margin:0;padding:0;font-family:sans-serif}.loader-container{background-color:#fafafa;flex-direction:column;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.loader,.loader:before,.loader:after{border-radius:50%}.loader{color:#283593;text-indent:-99999em;width:10em;height:10em;margin:55px auto;font-size:11px;position:relative;-webkit-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0);box-shadow:inset 0 0 0 1em}.loader:before,.loader:after{content:"";position:absolute}.loader:before{-webkit-transform-origin:5.2em 5.1em;transform-origin:5.2em 5.1em;background:#fafafa;border-radius:10.2em 0 0 10.2em;width:5.2em;height:10.2em;-webkit-animation:2s 1.5s infinite load2;animation:2s 1.5s infinite load2;top:-.1em;left:-.1em}.loader:after{-webkit-transform-origin:0 5.1em;transform-origin:0 5.1em;background:#fafafa;border-radius:0 10.2em 10.2em 0;width:5.2em;height:10.2em;-webkit-animation:2s infinite load2;animation:2s infinite load2;top:-.1em;left:5.1em}@-webkit-keyframes load2{0%{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes load2{0%{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}</style><script type="module" crossorigin src="./assets/index-IQ-gFBbx.js"></script><link rel="stylesheet" crossorigin href="./assets/index-C0zU8xwx.css"></head><body><div id="root"><div class="loader-container"><div class="loader">Loading...</div></div></div><noscript>You need to enable JavaScript to run this app.</noscript></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="minimum-scale=1,initial-scale=1,width=device-width,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="./manifest.json"><link rel="icon shortcut" href="./favicon.ico"><title>CRM</title><style>body{margin:0;padding:0;font-family:sans-serif}.loader-container{background-color:#fafafa;flex-direction:column;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.loader,.loader:before,.loader:after{border-radius:50%}.loader{color:#283593;text-indent:-99999em;width:10em;height:10em;margin:55px auto;font-size:11px;position:relative;-webkit-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0);box-shadow:inset 0 0 0 1em}.loader:before,.loader:after{content:"";position:absolute}.loader:before{-webkit-transform-origin:5.2em 5.1em;transform-origin:5.2em 5.1em;background:#fafafa;border-radius:10.2em 0 0 10.2em;width:5.2em;height:10.2em;-webkit-animation:2s 1.5s infinite load2;animation:2s 1.5s infinite load2;top:-.1em;left:-.1em}.loader:after{-webkit-transform-origin:0 5.1em;transform-origin:0 5.1em;background:#fafafa;border-radius:0 10.2em 10.2em 0;width:5.2em;height:10.2em;-webkit-animation:2s infinite load2;animation:2s infinite load2;top:-.1em;left:5.1em}@-webkit-keyframes load2{0%{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes load2{0%{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}</style><script type="module" crossorigin src="./assets/index-CTPFYcZP.js"></script><link rel="stylesheet" crossorigin href="./assets/index-DbCBhMSJ.css"></head><body><div id="root"><div class="loader-container"><div class="loader">Loading...</div></div></div><noscript>You need to enable JavaScript to run this app.</noscript></body></html>