@amogads/ui 1.0.2 → 1.1.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,2662 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/server/chat.handler.ts
9
+ import { createOpenRouter } from "@openrouter/ai-sdk-provider";
10
+ import { generateText } from "ai";
11
+ import { NextResponse } from "next/server";
12
+ var UI_RENDER_SYSTEM_PROMPT = `
13
+ You are a UI Schema Generator. Your task is to generate a valid UI schema in JSON format based on the user's request.
14
+ You MUST output ONLY valid JSON. Do not write any explanations, do not wrap it in markdown code blocks, do not write anything else.
15
+
16
+ If the user provides an OCR text extraction payload (containing fields like invoice, date, total, business details, customer name, lines, etc.), you MUST analyze the text, extract the key values, and construct an editable Form containing corresponding inputs (e.g. Input with defaultValue, Textarea) so the user can verify, edit, and submit the extracted details.
17
+
18
+ The schema MUST follow this exact TypeScript interface:
19
+ interface UiSchema {
20
+ root: string; // The ID of the root element (usually "root")
21
+ elements: {
22
+ [elementId: string]: {
23
+ type: 'Stack' | 'Card' | 'Form' | 'Input' | 'Textarea' | 'Button' | 'Checkbox' | 'Badge' | 'Alert' | 'Separator' | 'Progress' | 'Heading' | 'Text' | 'Price' | 'FeatureList' | 'Tabs' | 'Calendar' | 'Switch' | 'RadioGroup' | 'PremiumStats';
24
+ props?: Record<string, any>;
25
+ children?: string[]; // Array of element IDs that are children of this element
26
+ }
27
+ }
28
+ }
29
+
30
+ Common Components & Props:
31
+ 1. Stack: props: { direction: 'vertical' | 'horizontal', gap: 'xs' | 'sm' | 'md' | 'lg' | 'xl', align: 'start' | 'center' | 'end' }
32
+ 2. Form: props: { onSubmit?: string }
33
+ 3. Card: props: { title?: string, description?: string, className?: string, maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | 'full', centered?: boolean }
34
+ 4. Input: props: { label?: string, name?: string, placeholder?: string, required?: boolean, type?: string, defaultValue?: any }
35
+ 5. Textarea: props: { label?: string, name?: string, placeholder?: string, required?: boolean, defaultValue?: any }
36
+ 6. Button: props: { label: string, type?: 'button' | 'submit', variant?: 'default' | 'outline' | 'destructive' | 'ghost', className?: string }
37
+ 7. Heading: props: { level: '1' | '2' | '3' | '4' | '5' | '6', children: string }
38
+ 8. Text: props: { children: string, size?: 'sm' | 'base' | 'lg' | 'xl', className?: string }
39
+ `;
40
+ async function handleChatPost(request) {
41
+ try {
42
+ const { message, model, tool } = await request.json();
43
+ if (!message) {
44
+ return NextResponse.json({ error: "Message is required" }, { status: 400 });
45
+ }
46
+ const openRouterApiKey = process.env.OPENROUTER_API_KEY;
47
+ if (!openRouterApiKey) {
48
+ return NextResponse.json(
49
+ { error: "OpenRouter API key is not configured" },
50
+ { status: 500 }
51
+ );
52
+ }
53
+ const openrouter = createOpenRouter({
54
+ apiKey: openRouterApiKey
55
+ });
56
+ const isUiRender = tool === "ui-render";
57
+ const { text } = await generateText({
58
+ model: openrouter.chat(model || "google/gemini-2.5-flash"),
59
+ system: isUiRender ? UI_RENDER_SYSTEM_PROMPT : void 0,
60
+ prompt: message
61
+ });
62
+ return NextResponse.json({ text });
63
+ } catch (error) {
64
+ console.error("Error in handleChatPost:", error);
65
+ return NextResponse.json(
66
+ { error: error?.message || "Failed to generate response" },
67
+ { status: 500 }
68
+ );
69
+ }
70
+ }
71
+
72
+ // src/server/geocode.handler.ts
73
+ import { NextResponse as NextResponse2 } from "next/server";
74
+ async function handleGeocodeRequest(req) {
75
+ const { searchParams } = new URL(req.url);
76
+ const lat = searchParams.get("lat");
77
+ const lon = searchParams.get("lon");
78
+ if (!lat || !lon) {
79
+ return NextResponse2.json({ error: "Latitude and Longitude are required" }, { status: 400 });
80
+ }
81
+ try {
82
+ const response = await fetch(
83
+ `https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${lat}&lon=${lon}`,
84
+ {
85
+ headers: {
86
+ "User-Agent": "AmogaDS/1.0 (contact@amoga.io)",
87
+ "Accept-Language": "en"
88
+ },
89
+ next: { revalidate: 3600 }
90
+ }
91
+ );
92
+ if (!response.ok) {
93
+ throw new Error(`Nominatim returned status ${response.status}`);
94
+ }
95
+ const data = await response.json();
96
+ return NextResponse2.json(data);
97
+ } catch (error) {
98
+ console.error("Server-side geocoding failed:", error);
99
+ return NextResponse2.json({ error: error.message || "Geocoding failed" }, { status: 500 });
100
+ }
101
+ }
102
+
103
+ // src/server/shorten.handler.ts
104
+ import { NextResponse as NextResponse3 } from "next/server";
105
+
106
+ // src/lib/short-url-store.ts
107
+ import { promises as fs } from "fs";
108
+ import path from "path";
109
+ import { createClient } from "@supabase/supabase-js";
110
+ var supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
111
+ var supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY;
112
+ var supabase = supabaseUrl && supabaseAnonKey ? createClient(supabaseUrl, supabaseAnonKey) : null;
113
+ var URLS_FILE = path.join(process.cwd(), "src/features/link-builder/data/urls.json");
114
+ var ID_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
115
+ function getMemoryStore() {
116
+ const g = globalThis;
117
+ if (!g.__shortUrlStore) {
118
+ g.__shortUrlStore = /* @__PURE__ */ new Map();
119
+ }
120
+ return g.__shortUrlStore;
121
+ }
122
+ function generateShortId(length = 6) {
123
+ let result = "";
124
+ for (let i = 0; i < length; i++) {
125
+ result += ID_CHARS.charAt(Math.floor(Math.random() * ID_CHARS.length));
126
+ }
127
+ return result;
128
+ }
129
+ function isKvConfigured() {
130
+ return Boolean(process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN);
131
+ }
132
+ async function kvSet(key, entry, ttlSeconds) {
133
+ if (!isKvConfigured()) return false;
134
+ try {
135
+ const res = await fetch(process.env.KV_REST_API_URL, {
136
+ method: "POST",
137
+ headers: {
138
+ Authorization: `Bearer ${process.env.KV_REST_API_TOKEN}`,
139
+ "Content-Type": "application/json"
140
+ },
141
+ body: JSON.stringify(["SET", key, JSON.stringify(entry), "EX", ttlSeconds]),
142
+ cache: "no-store"
143
+ });
144
+ return res.ok;
145
+ } catch {
146
+ return false;
147
+ }
148
+ }
149
+ async function readFileStore() {
150
+ try {
151
+ const raw = await fs.readFile(URLS_FILE, "utf-8");
152
+ return JSON.parse(raw);
153
+ } catch {
154
+ return [];
155
+ }
156
+ }
157
+ async function writeFileStore(entries) {
158
+ try {
159
+ await fs.writeFile(URLS_FILE, JSON.stringify(entries, null, 2), "utf-8");
160
+ return true;
161
+ } catch {
162
+ return false;
163
+ }
164
+ }
165
+ async function saveShortUrl(targetUrl, expiresAtMs) {
166
+ const id = generateShortId();
167
+ const entry = {
168
+ id,
169
+ targetUrl,
170
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
171
+ expiresAt: new Date(expiresAtMs).toISOString()
172
+ };
173
+ const memory = getMemoryStore();
174
+ memory.set(id, entry);
175
+ const ttlSeconds = Math.max(60, Math.ceil((expiresAtMs - Date.now()) / 1e3));
176
+ const kvSaved = await kvSet(`short:${id}`, entry, ttlSeconds);
177
+ const existing = await readFileStore();
178
+ existing.push(entry);
179
+ const fileSaved = await writeFileStore(existing);
180
+ let supabaseSaved = false;
181
+ if (supabase) {
182
+ try {
183
+ const { error } = await supabase.from("short_urls").insert({
184
+ id,
185
+ target_url: targetUrl,
186
+ expires_at: new Date(expiresAtMs).toISOString()
187
+ });
188
+ if (error) {
189
+ console.error("Supabase saveShortUrl error:", error);
190
+ } else {
191
+ supabaseSaved = true;
192
+ console.log("Supabase saveShortUrl success:", id);
193
+ }
194
+ } catch (e) {
195
+ console.error("Supabase saveShortUrl exception:", e);
196
+ }
197
+ }
198
+ const needsFallback = !kvSaved && !fileSaved && !supabaseSaved;
199
+ const shortUrlSuffix = needsFallback ? `${id}?r=${Buffer.from(targetUrl).toString("base64url")}` : id;
200
+ return { id, entry, shortUrlSuffix };
201
+ }
202
+
203
+ // src/server/shorten.handler.ts
204
+ function getOrigin(request) {
205
+ const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host");
206
+ const proto = request.headers.get("x-forwarded-proto") ?? "https";
207
+ if (host) return `${proto}://${host}`;
208
+ if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
209
+ return request.nextUrl.origin;
210
+ }
211
+ async function handleShortenPost(request) {
212
+ try {
213
+ const body = await request.json();
214
+ const { url, durationHours = 1 } = body;
215
+ if (!url || typeof url !== "string") {
216
+ return NextResponse3.json({ error: "URL is required" }, { status: 400 });
217
+ }
218
+ const expiresAtMs = Date.now() + durationHours * 60 * 60 * 1e3;
219
+ const expiresAt = new Date(expiresAtMs).toISOString();
220
+ let urlWithExpiration = url;
221
+ if (url.includes("/l/")) {
222
+ const parts = url.split("/l/");
223
+ const domain = parts[0];
224
+ const config = parts[1];
225
+ urlWithExpiration = `${domain}/l?c=${config}&exp=${expiresAtMs}`;
226
+ } else if (url.includes("/l?")) {
227
+ const parsed = new URL(url);
228
+ parsed.searchParams.set("exp", String(expiresAtMs));
229
+ urlWithExpiration = parsed.toString();
230
+ } else {
231
+ const separator = url.includes("?") ? "&" : "?";
232
+ urlWithExpiration = `${url}${separator}exp=${expiresAtMs}`;
233
+ }
234
+ const origin = getOrigin(request);
235
+ const { shortUrlSuffix } = await saveShortUrl(urlWithExpiration, expiresAtMs);
236
+ const shortUrl = `${origin}/go/${shortUrlSuffix}`;
237
+ return NextResponse3.json(
238
+ { shortUrl, expiresAt },
239
+ {
240
+ status: 200,
241
+ headers: {
242
+ "Access-Control-Allow-Origin": "*",
243
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
244
+ "Access-Control-Allow-Headers": "Content-Type"
245
+ }
246
+ }
247
+ );
248
+ } catch (err) {
249
+ console.error("URL shortening API exception:", err);
250
+ return NextResponse3.json(
251
+ { error: err instanceof Error ? err.message : "Internal Server Error" },
252
+ { status: 500 }
253
+ );
254
+ }
255
+ }
256
+ async function handleShortenOptions() {
257
+ return new NextResponse3(null, {
258
+ status: 204,
259
+ headers: {
260
+ "Access-Control-Allow-Origin": "*",
261
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
262
+ "Access-Control-Allow-Headers": "Content-Type"
263
+ }
264
+ });
265
+ }
266
+
267
+ // src/server/vouchers.handler.ts
268
+ import { NextResponse as NextResponse4 } from "next/server";
269
+
270
+ // src/lib/supabase/server.ts
271
+ import { createServerClient } from "@supabase/ssr";
272
+ import { cookies } from "next/headers";
273
+ async function createClient2() {
274
+ const cookieStore = await cookies();
275
+ return createServerClient(
276
+ process.env.NEXT_PUBLIC_SUPABASE_URL,
277
+ process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY,
278
+ {
279
+ cookies: {
280
+ getAll() {
281
+ return cookieStore.getAll();
282
+ },
283
+ setAll(cookiesToSet) {
284
+ try {
285
+ cookiesToSet.forEach(
286
+ ({ name, value, options }) => cookieStore.set(name, value, options)
287
+ );
288
+ } catch {
289
+ }
290
+ }
291
+ }
292
+ }
293
+ );
294
+ }
295
+
296
+ // src/server/vouchers.handler.ts
297
+ import { getServerSession } from "next-auth";
298
+
299
+ // src/lib/auth.ts
300
+ import GoogleProvider from "next-auth/providers/google";
301
+ import { createClient as createSupabaseClient } from "@supabase/supabase-js";
302
+ import crypto2 from "crypto";
303
+ var SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL || "";
304
+ var SUPABASE_KEY = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY || "";
305
+ function stringToUuid(str) {
306
+ if (!str) return crypto2.randomUUID();
307
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
308
+ if (uuidRegex.test(str)) {
309
+ return str;
310
+ }
311
+ const hash = crypto2.createHash("md5").update(str).digest("hex");
312
+ return `${hash.substring(0, 8)}-${hash.substring(8, 12)}-4${hash.substring(13, 16)}-a${hash.substring(17, 20)}-${hash.substring(20, 32)}`;
313
+ }
314
+ var authOptions = {
315
+ providers: [
316
+ GoogleProvider({
317
+ clientId: process.env.GOOGLE_CLIENT_ID || process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || "",
318
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET || process.env.NEXT_PUBLIC_GOOGLE_SECRET_ID || "",
319
+ authorization: {
320
+ params: {
321
+ prompt: "select_account",
322
+ access_type: "offline",
323
+ response_type: "code"
324
+ }
325
+ },
326
+ checks: ["none"]
327
+ })
328
+ ],
329
+ secret: process.env.NEXTAUTH_SECRET || "secret_next_auth_shadcn_admin_key_2026_super_secure",
330
+ session: {
331
+ strategy: "jwt",
332
+ maxAge: 30 * 24 * 60 * 60
333
+ // 30 days
334
+ },
335
+ callbacks: {
336
+ async redirect({ url, baseUrl }) {
337
+ try {
338
+ const { cookies: cookies2 } = await import("next/headers");
339
+ const cookieStore = await cookies2();
340
+ const isMobileAuth = cookieStore.get("mobile_auth")?.value === "true" || url.includes("is_mobile=true");
341
+ if (isMobileAuth) {
342
+ console.log("\u{1F4F1} [NextAuth Redirect Callback] Mobile auth detected. Redirecting to /auth/callback?is_mobile=true");
343
+ return `${baseUrl}/auth/callback?is_mobile=true&next=/`;
344
+ }
345
+ } catch (err) {
346
+ console.error("\u274C [NextAuth Redirect Callback] Error inspecting cookies:", err);
347
+ }
348
+ if (url.includes("/auth/callback")) return url;
349
+ if (url.startsWith("/")) return `${baseUrl}${url}`;
350
+ else if (new URL(url).origin === baseUrl) return url;
351
+ return baseUrl;
352
+ },
353
+ async signIn({ user }) {
354
+ if (!user.email) return false;
355
+ try {
356
+ if (SUPABASE_URL && SUPABASE_KEY) {
357
+ const supabase2 = createSupabaseClient(SUPABASE_URL, SUPABASE_KEY);
358
+ const fallbackUuid = stringToUuid(user.id || user.email);
359
+ const { data: existingProfile } = await supabase2.from("profiles").select("id, auth_user_id").eq("email", user.email.toLowerCase()).maybeSingle();
360
+ const profileId = existingProfile?.id || fallbackUuid;
361
+ const profileData = {
362
+ id: profileId,
363
+ name: user.name || user.email.split("@")[0],
364
+ email: user.email.toLowerCase(),
365
+ avatar: user.image || null,
366
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
367
+ };
368
+ profileData.auth_user_id = existingProfile?.auth_user_id || profileId;
369
+ if (existingProfile) {
370
+ await supabase2.from("profiles").update(profileData).eq("id", existingProfile.id);
371
+ } else {
372
+ await supabase2.from("profiles").insert(profileData);
373
+ }
374
+ user.id = profileId;
375
+ }
376
+ } catch (err) {
377
+ console.error("[NextAuth] Error syncing user to profiles table:", err);
378
+ }
379
+ return true;
380
+ },
381
+ async jwt({ token, user }) {
382
+ if (user) {
383
+ token.id = stringToUuid(user.id || user.email);
384
+ token.email = user.email;
385
+ token.name = user.name;
386
+ token.picture = user.image;
387
+ } else if (token.sub && !stringToUuid(token.sub)) {
388
+ token.id = stringToUuid(token.sub);
389
+ }
390
+ return token;
391
+ },
392
+ async session({ session, token }) {
393
+ if (session.user) {
394
+ const canonicalId = stringToUuid(token.id || token.sub || session.user.email);
395
+ session.user.id = canonicalId;
396
+ session.user.picture = token.picture || token.image;
397
+ }
398
+ return session;
399
+ }
400
+ },
401
+ pages: {
402
+ signIn: "/sign-in",
403
+ error: "/sign-in"
404
+ }
405
+ };
406
+
407
+ // src/server/vouchers.handler.ts
408
+ async function handleVouchersGet() {
409
+ try {
410
+ const session = await getServerSession(authOptions);
411
+ const supabase2 = await createClient2();
412
+ let userId = null;
413
+ let userEmail = null;
414
+ if (session?.user) {
415
+ const user = session.user;
416
+ userEmail = user.email ? user.email.toLowerCase() : null;
417
+ userId = stringToUuid(user.id || user.email);
418
+ } else {
419
+ const { data: { user } } = await supabase2.auth.getUser();
420
+ if (user?.id) {
421
+ userId = user.id;
422
+ userEmail = user.email ? user.email.toLowerCase() : null;
423
+ }
424
+ }
425
+ let voucherRows = [];
426
+ try {
427
+ if (userId) {
428
+ const { data: vData } = await supabase2.from("vouchers").select("*").eq("user_id", userId).order("created_at", { ascending: false }).limit(100);
429
+ if (vData && vData.length > 0) {
430
+ voucherRows = vData;
431
+ } else if (userEmail) {
432
+ const { data: profileRow } = await supabase2.from("profiles").select("id").eq("email", userEmail).maybeSingle();
433
+ if (profileRow?.id) {
434
+ const { data: vByProfile } = await supabase2.from("vouchers").select("*").eq("user_id", profileRow.id).order("created_at", { ascending: false }).limit(100);
435
+ if (vByProfile && vByProfile.length > 0) {
436
+ voucherRows = vByProfile;
437
+ }
438
+ }
439
+ }
440
+ }
441
+ if (voucherRows.length === 0) {
442
+ const { data: fallbackVData } = await supabase2.from("vouchers").select("*").order("created_at", { ascending: false }).limit(100);
443
+ if (fallbackVData) voucherRows = fallbackVData;
444
+ }
445
+ } catch (e) {
446
+ console.warn("[GET /api/vouchers] Vouchers table fetch warning:", e);
447
+ }
448
+ let chatFileRows = [];
449
+ try {
450
+ if (userId) {
451
+ const [rOwner, rSender] = await Promise.all([
452
+ supabase2.from("chat_messages").select("*").eq("owner_user_id", userId).not("file_url", "is", null).order("created_at", { ascending: false }).limit(100),
453
+ supabase2.from("chat_messages").select("*").eq("sender_user_id", userId).not("file_url", "is", null).order("created_at", { ascending: false }).limit(100)
454
+ ]);
455
+ const userMsgs = [...rOwner.data ?? [], ...rSender.data ?? []];
456
+ if (userMsgs.length > 0) {
457
+ chatFileRows = userMsgs.map((msg) => ({
458
+ id: `chat-file-${msg.id}`,
459
+ voucher_no: msg.id ? String(msg.id).slice(0, 8) : "file",
460
+ file_name: msg.file_name || "Attached File",
461
+ original_file_url: msg.file_url ?? void 0,
462
+ edited_file_url: msg.file_url ?? void 0,
463
+ vendor_name: msg.sender_name || "Uploaded Document",
464
+ customer_name: userEmail ? userEmail.split("@")[0] : "User",
465
+ user_name: userEmail ? userEmail.split("@")[0] : "User",
466
+ created_at: msg.created_at || (/* @__PURE__ */ new Date()).toISOString(),
467
+ status: msg.processing_status || "Active",
468
+ edited_json: msg.file_content_json || null
469
+ }));
470
+ }
471
+ }
472
+ if (chatFileRows.length === 0) {
473
+ const { data: fallbackMsgs } = await supabase2.from("chat_messages").select("*").not("file_url", "is", null).order("created_at", { ascending: false }).limit(100);
474
+ if (fallbackMsgs && fallbackMsgs.length > 0) {
475
+ chatFileRows = fallbackMsgs.map((msg) => ({
476
+ id: `chat-file-${msg.id}`,
477
+ voucher_no: msg.id ? String(msg.id).slice(0, 8) : "file",
478
+ file_name: msg.file_name || "Attached File",
479
+ original_file_url: msg.file_url ?? void 0,
480
+ edited_file_url: msg.file_url ?? void 0,
481
+ vendor_name: msg.sender_name || "Uploaded Document",
482
+ customer_name: userEmail ? userEmail.split("@")[0] : "User",
483
+ user_name: userEmail ? userEmail.split("@")[0] : "User",
484
+ created_at: msg.created_at || (/* @__PURE__ */ new Date()).toISOString(),
485
+ status: msg.processing_status || "Active",
486
+ edited_json: msg.file_content_json || null
487
+ }));
488
+ }
489
+ }
490
+ } catch (e) {
491
+ console.warn("[GET /api/vouchers] Chat files fetch warning:", e);
492
+ }
493
+ const allFiles = [...voucherRows, ...chatFileRows];
494
+ allFiles.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
495
+ const seen = /* @__PURE__ */ new Set();
496
+ const uniqueFiles = [];
497
+ for (const f of allFiles) {
498
+ const key = `${f.id}|${f.file_name ?? ""}|${f.original_file_url ?? ""}`;
499
+ if (!seen.has(key)) {
500
+ seen.add(key);
501
+ uniqueFiles.push(f);
502
+ }
503
+ }
504
+ return NextResponse4.json({ success: true, data: uniqueFiles });
505
+ } catch (err) {
506
+ console.error("[GET /api/vouchers] Internal error:", err);
507
+ return NextResponse4.json({ error: err.message || "Internal error" }, { status: 500 });
508
+ }
509
+ }
510
+ async function handleVouchersPost(request) {
511
+ try {
512
+ const session = await getServerSession(authOptions);
513
+ const supabase2 = await createClient2();
514
+ let userId = null;
515
+ let userEmail = null;
516
+ if (session?.user) {
517
+ const user = session.user;
518
+ userEmail = user.email ? user.email.toLowerCase() : null;
519
+ userId = stringToUuid(user.id || user.email);
520
+ } else {
521
+ const { data: { user } } = await supabase2.auth.getUser();
522
+ if (user?.id) {
523
+ userId = user.id;
524
+ userEmail = user.email?.toLowerCase() ?? null;
525
+ }
526
+ }
527
+ if (!userId) {
528
+ return NextResponse4.json({ error: "Unauthorized" }, { status: 401 });
529
+ }
530
+ if (userEmail) {
531
+ const { data: profileRow } = await supabase2.from("profiles").select("id").eq("email", userEmail).maybeSingle();
532
+ if (profileRow?.id) userId = profileRow.id;
533
+ }
534
+ const body = await request.json();
535
+ const {
536
+ voucher_no,
537
+ file_name,
538
+ original_file_url,
539
+ edited_file_url,
540
+ edited_json,
541
+ vendor_name,
542
+ customer_name,
543
+ invoice_date,
544
+ total,
545
+ currency
546
+ } = body;
547
+ if (!voucher_no || !file_name) {
548
+ return NextResponse4.json({ error: "voucher_no and file_name are required" }, { status: 400 });
549
+ }
550
+ const { data: row, error } = await supabase2.from("vouchers").insert({
551
+ user_id: userId,
552
+ voucher_no,
553
+ file_name,
554
+ original_file_url: original_file_url || null,
555
+ edited_file_url: edited_file_url || null,
556
+ edited_json: edited_json || null,
557
+ vendor_name: vendor_name || null,
558
+ customer_name: customer_name || null,
559
+ invoice_date: invoice_date || null,
560
+ total: total || null,
561
+ currency: currency || "USD",
562
+ status: "Active"
563
+ }).select("*").single();
564
+ if (error) {
565
+ return NextResponse4.json({ error: error.message }, { status: 500 });
566
+ }
567
+ return NextResponse4.json({ success: true, data: row }, { status: 201 });
568
+ } catch (err) {
569
+ console.error("[POST /api/vouchers] Internal error:", err);
570
+ return NextResponse4.json({ error: err.message || "Internal error" }, { status: 500 });
571
+ }
572
+ }
573
+
574
+ // src/server/search.handler.ts
575
+ import { NextResponse as NextResponse5 } from "next/server";
576
+
577
+ // src/services/ai-search.service.ts
578
+ import axios from "axios";
579
+ var AiSearchService = class {
580
+ /**
581
+ * Executes web search through Tavily API.
582
+ */
583
+ static async searchWeb(query, apiKey) {
584
+ if (!apiKey) {
585
+ throw new Error("Tavily API key is not configured.");
586
+ }
587
+ const res = await axios.post(
588
+ "https://api.tavily.com/search",
589
+ {
590
+ api_key: apiKey,
591
+ query,
592
+ search_depth: "advanced",
593
+ max_results: 10,
594
+ include_images: true
595
+ },
596
+ { timeout: 15e3 }
597
+ );
598
+ return {
599
+ results: res.data?.results || [],
600
+ images: res.data?.images || []
601
+ };
602
+ }
603
+ /**
604
+ * Generates answer analysis using Gemini API.
605
+ */
606
+ static async generateAnswer(prompt, apiKey) {
607
+ if (!apiKey) {
608
+ throw new Error("Gemini API key is not configured.");
609
+ }
610
+ const res = await axios.post(
611
+ `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${apiKey}`,
612
+ {
613
+ contents: [
614
+ {
615
+ parts: [{ text: prompt }]
616
+ }
617
+ ]
618
+ },
619
+ { timeout: 3e4 }
620
+ );
621
+ return res.data?.candidates?.[0]?.content?.parts?.[0]?.text || "";
622
+ }
623
+ };
624
+
625
+ // src/server/search.handler.ts
626
+ async function handleSearchPost(request) {
627
+ try {
628
+ const body = await request.json();
629
+ const { query, toolPrompt, toolId } = body;
630
+ if (!query || typeof query !== "string") {
631
+ return NextResponse5.json({ error: "Query is required" }, { status: 400 });
632
+ }
633
+ const tavilyKey = process.env.TAVILY_API_KEY || process.env.NEXT_PUBLIC_TAVILY_API_KEY || "";
634
+ const geminiKey = process.env.GEMINI_API_KEY || process.env.NEXT_PUBLIC_GEMINI_API_KEY || "";
635
+ if (!tavilyKey) {
636
+ return NextResponse5.json({ error: "Tavily API key is not configured" }, { status: 500 });
637
+ }
638
+ const { results, images } = await AiSearchService.searchWeb(query, tavilyKey);
639
+ if (results.length === 0) {
640
+ return NextResponse5.json({
641
+ answer: "No search results found for your query. Please try a different search term.",
642
+ sources: [],
643
+ images: []
644
+ });
645
+ }
646
+ const context = results.map((item) => `Title: ${item.title}
647
+ Content: ${item.content}
648
+ URL: ${item.url}`).join("\n\n");
649
+ const systemPrompt = toolPrompt || "You are an AI Search Assistant. Give comprehensive answers using the search results provided. Use headings and bullet points when useful, and always cite your sources.";
650
+ const prompt = `
651
+ ${systemPrompt}
652
+
653
+ Question:
654
+ ${query}
655
+
656
+ Search Results:
657
+ ${context}
658
+
659
+ Instructions:
660
+ - Give a comprehensive answer based on search results.
661
+ - Use headings and bullet points for readability.
662
+ - Cite sources accurately.
663
+ `;
664
+ let answer = "";
665
+ if (geminiKey) {
666
+ answer = await AiSearchService.generateAnswer(prompt, geminiKey);
667
+ }
668
+ return NextResponse5.json({
669
+ answer,
670
+ sources: results,
671
+ images
672
+ });
673
+ } catch (err) {
674
+ console.error("Error in handleSearchPost:", err);
675
+ return NextResponse5.json(
676
+ { error: err.message || "Failed to process AI search request" },
677
+ { status: 500 }
678
+ );
679
+ }
680
+ }
681
+
682
+ // src/server/messages.handler.ts
683
+ import { NextResponse as NextResponse6 } from "next/server";
684
+
685
+ // src/lib/supabase/client.ts
686
+ import { createBrowserClient } from "@supabase/ssr";
687
+ var clientSingleton = null;
688
+ function createClient3() {
689
+ if (typeof window === "undefined") {
690
+ return createBrowserClient(
691
+ process.env.NEXT_PUBLIC_SUPABASE_URL,
692
+ process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY
693
+ );
694
+ }
695
+ if (!clientSingleton) {
696
+ clientSingleton = createBrowserClient(
697
+ process.env.NEXT_PUBLIC_SUPABASE_URL,
698
+ process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY
699
+ );
700
+ }
701
+ return clientSingleton;
702
+ }
703
+
704
+ // src/stores/auth-store.ts
705
+ import { create } from "zustand";
706
+
707
+ // src/lib/cookies.ts
708
+ var DEFAULT_MAX_AGE = 60 * 60 * 24 * 7;
709
+ function getCookie(name) {
710
+ if (typeof document === "undefined") return void 0;
711
+ const value = `; ${document.cookie}`;
712
+ const parts = value.split(`; ${name}=`);
713
+ if (parts.length === 2) {
714
+ const cookieValue = parts.pop()?.split(";").shift();
715
+ return cookieValue;
716
+ }
717
+ return void 0;
718
+ }
719
+ function setCookie(name, value, maxAge = DEFAULT_MAX_AGE) {
720
+ if (typeof document === "undefined") return;
721
+ document.cookie = `${name}=${value}; path=/; max-age=${maxAge}`;
722
+ }
723
+ function removeCookie(name) {
724
+ if (typeof document === "undefined") return;
725
+ document.cookie = `${name}=; path=/; max-age=0`;
726
+ }
727
+
728
+ // src/stores/auth-store.ts
729
+ var ACCESS_TOKEN = "thisisjustarandomstring";
730
+ var USER_DATA = "auth_user_data";
731
+ var useAuthStore = create()((set) => {
732
+ const cookieState = getCookie(ACCESS_TOKEN);
733
+ let initToken = "";
734
+ if (cookieState) {
735
+ try {
736
+ initToken = JSON.parse(cookieState);
737
+ } catch {
738
+ removeCookie(ACCESS_TOKEN);
739
+ }
740
+ }
741
+ const userCookie = getCookie(USER_DATA);
742
+ let initUser = null;
743
+ if (userCookie) {
744
+ try {
745
+ const parsed = JSON.parse(decodeURIComponent(userCookie));
746
+ if (parsed.exp && parsed.exp > Date.now()) {
747
+ initUser = parsed;
748
+ } else {
749
+ removeCookie(ACCESS_TOKEN);
750
+ removeCookie(USER_DATA);
751
+ }
752
+ } catch {
753
+ initUser = null;
754
+ removeCookie(USER_DATA);
755
+ }
756
+ }
757
+ return {
758
+ auth: {
759
+ user: initUser,
760
+ setUser: (user) => set((state) => {
761
+ if (user) {
762
+ setCookie(USER_DATA, encodeURIComponent(JSON.stringify(user)));
763
+ } else {
764
+ removeCookie(USER_DATA);
765
+ }
766
+ return { ...state, auth: { ...state.auth, user } };
767
+ }),
768
+ accessToken: initUser ? initToken : "",
769
+ setAccessToken: (accessToken) => set((state) => {
770
+ setCookie(ACCESS_TOKEN, JSON.stringify(accessToken));
771
+ return { ...state, auth: { ...state.auth, accessToken } };
772
+ }),
773
+ resetAccessToken: () => set((state) => {
774
+ removeCookie(ACCESS_TOKEN);
775
+ return { ...state, auth: { ...state.auth, accessToken: "" } };
776
+ }),
777
+ reset: () => set((state) => {
778
+ removeCookie(ACCESS_TOKEN);
779
+ removeCookie(USER_DATA);
780
+ return {
781
+ ...state,
782
+ auth: { ...state.auth, user: null, accessToken: "" }
783
+ };
784
+ })
785
+ }
786
+ };
787
+ });
788
+
789
+ // src/features/chattemplate/shared/api/auth.ts
790
+ async function getAccessToken() {
791
+ const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY || "";
792
+ try {
793
+ const storeToken = useAuthStore.getState().auth.accessToken;
794
+ if (storeToken && typeof storeToken === "string" && storeToken.split(".").length === 3) {
795
+ return storeToken;
796
+ }
797
+ const supabase2 = createClient3();
798
+ const { data: { session } } = await supabase2.auth.getSession();
799
+ if (session?.access_token && session.access_token.split(".").length === 3) {
800
+ return session.access_token;
801
+ }
802
+ return supabaseKey;
803
+ } catch (error) {
804
+ return supabaseKey;
805
+ }
806
+ }
807
+
808
+ // src/features/chattemplate/shared/api/headers.ts
809
+ async function getHeaders(customHeaders = {}) {
810
+ const token = await getAccessToken();
811
+ const apiKey = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY || "";
812
+ const is3PartJwt = typeof token === "string" && token.split(".").length === 3;
813
+ const validBearerToken = is3PartJwt ? token : apiKey;
814
+ return {
815
+ "apikey": apiKey,
816
+ "Authorization": `Bearer ${validBearerToken}`,
817
+ "Content-Type": "application/json",
818
+ ...customHeaders
819
+ };
820
+ }
821
+
822
+ // src/features/chattemplate/shared/api/errorHandler.ts
823
+ var ApiError = class extends Error {
824
+ constructor(message, code, details, hint) {
825
+ super(message);
826
+ this.name = "ApiError";
827
+ this.code = code;
828
+ this.details = details;
829
+ this.hint = hint;
830
+ }
831
+ };
832
+ async function handleError(response) {
833
+ let errorData;
834
+ try {
835
+ errorData = await response.json();
836
+ } catch {
837
+ throw new ApiError(response.statusText || "An unknown network error occurred");
838
+ }
839
+ const message = errorData?.message || errorData?.error_description || "API request failed";
840
+ const code = errorData?.code;
841
+ const details = errorData?.details;
842
+ const hint = errorData?.hint;
843
+ throw new ApiError(message, code, details, hint);
844
+ }
845
+
846
+ // src/features/chattemplate/shared/api/apiClient.ts
847
+ var SUPABASE_URL2 = process.env.NEXT_PUBLIC_SUPABASE_URL;
848
+ if (!SUPABASE_URL2) {
849
+ throw new Error("Missing environment variable: NEXT_PUBLIC_SUPABASE_URL");
850
+ }
851
+ var BASE_URL = SUPABASE_URL2.endsWith("/") ? SUPABASE_URL2.slice(0, -1) : SUPABASE_URL2;
852
+ async function parseResponse(response) {
853
+ const text = await response.text();
854
+ return text ? JSON.parse(text) : {};
855
+ }
856
+ var apiClient = {
857
+ async get(path3, options) {
858
+ const headers = await getHeaders(options?.headers);
859
+ const response = await fetch(`${BASE_URL}${path3}`, {
860
+ method: "GET",
861
+ headers
862
+ });
863
+ if (!response.ok) {
864
+ await handleError(response);
865
+ }
866
+ return parseResponse(response);
867
+ },
868
+ async post(path3, body, options) {
869
+ const defaultHeaders = {
870
+ "Prefer": "return=representation"
871
+ };
872
+ const headers = await getHeaders({ ...defaultHeaders, ...options?.headers });
873
+ const response = await fetch(`${BASE_URL}${path3}`, {
874
+ method: "POST",
875
+ headers,
876
+ body: JSON.stringify(body)
877
+ });
878
+ if (!response.ok) {
879
+ await handleError(response);
880
+ }
881
+ return parseResponse(response);
882
+ },
883
+ async patch(path3, body, options) {
884
+ const defaultHeaders = {
885
+ "Prefer": "return=representation"
886
+ };
887
+ const headers = await getHeaders({ ...defaultHeaders, ...options?.headers });
888
+ const response = await fetch(`${BASE_URL}${path3}`, {
889
+ method: "PATCH",
890
+ headers,
891
+ body: JSON.stringify(body)
892
+ });
893
+ if (!response.ok) {
894
+ await handleError(response);
895
+ }
896
+ return parseResponse(response);
897
+ },
898
+ async delete(path3, options) {
899
+ const defaultHeaders = {
900
+ "Prefer": "return=representation"
901
+ };
902
+ const headers = await getHeaders({ ...defaultHeaders, ...options?.headers });
903
+ const response = await fetch(`${BASE_URL}${path3}`, {
904
+ method: "DELETE",
905
+ headers
906
+ });
907
+ if (!response.ok) {
908
+ await handleError(response);
909
+ }
910
+ return parseResponse(response);
911
+ }
912
+ };
913
+
914
+ // src/features/chattemplate/shared/api/queryBuilder.ts
915
+ var QueryBuilder = class {
916
+ constructor() {
917
+ this.params = new URLSearchParams();
918
+ }
919
+ /**
920
+ * Embeds resource relationships or filters output columns.
921
+ */
922
+ select(fields) {
923
+ const cleaned = fields.replace(/\s+/g, "");
924
+ this.params.set("select", cleaned);
925
+ return this;
926
+ }
927
+ /**
928
+ * Equality filter.
929
+ */
930
+ eq(column, value) {
931
+ this.params.append(column, `eq.${value}`);
932
+ return this;
933
+ }
934
+ lt(column, value) {
935
+ this.params.append(column, `lt.${value}`);
936
+ return this;
937
+ }
938
+ /**
939
+ * Case-insensitive pattern matching filter.
940
+ */
941
+ ilike(column, value) {
942
+ this.params.append(column, `ilike.${value}`);
943
+ return this;
944
+ }
945
+ /**
946
+ * IN filter matching array of values.
947
+ */
948
+ in(column, values) {
949
+ const formatted = values.map((v) => `${v}`).join(",");
950
+ this.params.append(column, `in.(${formatted})`);
951
+ return this;
952
+ }
953
+ /**
954
+ * Logical OR filter matching multiple column criteria.
955
+ * Example string: "email.ilike.test@gmail.com,contact_user_id.eq.some-uuid"
956
+ */
957
+ or(filterString) {
958
+ this.params.set("or", `(${filterString})`);
959
+ return this;
960
+ }
961
+ /**
962
+ * Sorting filter.
963
+ */
964
+ order(column, options) {
965
+ const dir = options?.ascending !== false ? "asc" : "desc";
966
+ this.params.set("order", `${column}.${dir}`);
967
+ return this;
968
+ }
969
+ /**
970
+ * Limit result size.
971
+ */
972
+ limit(n) {
973
+ this.params.set("limit", n.toString());
974
+ return this;
975
+ }
976
+ /**
977
+ * Offset result index.
978
+ */
979
+ offset(n) {
980
+ this.params.set("offset", n.toString());
981
+ return this;
982
+ }
983
+ /**
984
+ * Generates the final query parameter string (e.g. "?owner_id=eq.123").
985
+ */
986
+ toString() {
987
+ const q = this.params.toString();
988
+ return q ? `?${q}` : "";
989
+ }
990
+ };
991
+ function createQuery() {
992
+ return new QueryBuilder();
993
+ }
994
+
995
+ // src/features/chattemplate/chat/api/messages.api.ts
996
+ async function getConversationMessages(conversationId, userId, options) {
997
+ try {
998
+ const query = createQuery().select(
999
+ `
1000
+ *,
1001
+ sender:profiles!sender_user_id (
1002
+ id,
1003
+ name,
1004
+ email,
1005
+ avatar
1006
+ )
1007
+ `
1008
+ ).eq("conversation_id", conversationId).eq("owner_user_id", userId).or("deleted.eq.false,deleted_by.not.is.null");
1009
+ if (options?.before) query.lt("created_at", options.before);
1010
+ query.order("created_at", { ascending: !options?.limit });
1011
+ if (options?.limit) query.limit(options.limit);
1012
+ const response = await apiClient.get(
1013
+ `/rest/v1/chat_messages${query.toString()}`
1014
+ );
1015
+ const data = options?.limit ? [...response].reverse() : response;
1016
+ if (!data) return [];
1017
+ const messages = data.map((d) => ({
1018
+ id: d.id,
1019
+ conversation_id: d.conversation_id,
1020
+ owner_user_id: d.owner_user_id,
1021
+ sender_user_id: d.sender_user_id,
1022
+ message: d.message,
1023
+ message_type: d.message_type,
1024
+ direction: d.direction,
1025
+ sent: d.sent,
1026
+ received: d.received,
1027
+ created_at: d.created_at,
1028
+ message_status: d.message_status || void 0,
1029
+ client_message_id: d.client_message_id || void 0,
1030
+ queued_at: d.queued_at || void 0,
1031
+ delivered_at: d.delivered_at || void 0,
1032
+ read_at: d.read_at || void 0,
1033
+ retry_count: d.retry_count ? Number(d.retry_count) : void 0,
1034
+ file_url: d.file_url || void 0,
1035
+ file_name: d.file_name || void 0,
1036
+ file_size: d.file_size ? Number(d.file_size) : void 0,
1037
+ mime_type: d.mime_type || void 0,
1038
+ duration: d.duration ? Number(d.duration) : void 0,
1039
+ thumbnail: d.thumbnail || void 0,
1040
+ file_content_text: d.file_content_text || void 0,
1041
+ file_content_json: d.file_content_json || void 0,
1042
+ processing_status: d.processing_status || void 0,
1043
+ thumb: !!d.thumb,
1044
+ favorite: !!d.favorite,
1045
+ flag: !!d.flag,
1046
+ star: !!d.star,
1047
+ pin: !!d.pin,
1048
+ archive: !!d.archive,
1049
+ deleted: !!d.deleted,
1050
+ action_this: !!d.action_this,
1051
+ reply: !!d.reply,
1052
+ forward: !!d.forward,
1053
+ deleted_at: d.deleted_at || void 0,
1054
+ deleted_by: d.deleted_by || void 0,
1055
+ replyemoji: d.replyemoji || void 0,
1056
+ replyto_message_id: d.replyto_message_id || void 0,
1057
+ replyto_user_id: d.replyto_user_id || void 0,
1058
+ parent_message_id: d.parent_message_id || void 0,
1059
+ forwardemoji: d.forwardemoji || void 0,
1060
+ forwardto_message_id: d.forwardto_message_id || void 0,
1061
+ forwardto_user_id: d.forwardto_user_id || void 0,
1062
+ sender_message_id: d.sender_message_id || void 0,
1063
+ sender: d.sender ? {
1064
+ id: d.sender.id,
1065
+ name: d.sender.name,
1066
+ email: d.sender.email,
1067
+ avatar_url: d.sender.avatar || void 0
1068
+ } : void 0,
1069
+ location_data: d.location_data || void 0,
1070
+ location_type: d.location_type || void 0
1071
+ }));
1072
+ for (const msg of messages) {
1073
+ if (msg.reply && msg.replyto_message_id) {
1074
+ const localReply = messages.find(
1075
+ (candidate) => candidate.id === msg.replyto_message_id || candidate.sender_message_id === msg.replyto_message_id
1076
+ );
1077
+ if (localReply) {
1078
+ msg.replyto_message = localReply;
1079
+ msg.replyMetadata = {
1080
+ replyemoji: msg.replyemoji || null,
1081
+ replyto_message_id: msg.replyto_message_id,
1082
+ replyto_user_id: msg.replyto_user_id || null,
1083
+ parent_message_id: msg.parent_message_id || null,
1084
+ replyMessageText: localReply.deleted ? "Original message unavailable" : localReply.message_type === "text" ? localReply.message || "" : `Attachment: ${localReply.file_name || "File"}`,
1085
+ replySenderName: localReply.sender?.name || "User"
1086
+ };
1087
+ continue;
1088
+ }
1089
+ const replyQuery = createQuery().select(
1090
+ `
1091
+ id,
1092
+ sender_user_id,
1093
+ created_at,
1094
+ message,
1095
+ message_type,
1096
+ file_name,
1097
+ deleted,
1098
+ sender:profiles!sender_user_id(name)
1099
+ `
1100
+ ).eq("id", msg.replyto_message_id).limit(1);
1101
+ try {
1102
+ const replyMsgs = await apiClient.get(
1103
+ `/rest/v1/chat_messages${replyQuery.toString()}`
1104
+ );
1105
+ const replyMsg = replyMsgs[0] || null;
1106
+ if (replyMsg) {
1107
+ msg.replyto_message = {
1108
+ id: replyMsg.id,
1109
+ conversation_id: msg.conversation_id,
1110
+ owner_user_id: msg.owner_user_id,
1111
+ sender_user_id: replyMsg.sender_user_id,
1112
+ message: replyMsg.deleted ? "Original message unavailable" : replyMsg.message,
1113
+ message_type: replyMsg.message_type || "text",
1114
+ direction: "Received",
1115
+ sent: true,
1116
+ received: true,
1117
+ created_at: replyMsg.created_at || msg.created_at,
1118
+ file_name: replyMsg.file_name || void 0,
1119
+ thumb: false,
1120
+ favorite: false,
1121
+ flag: false,
1122
+ star: false,
1123
+ pin: false,
1124
+ archive: false,
1125
+ deleted: !!replyMsg.deleted,
1126
+ action_this: false,
1127
+ reply: false,
1128
+ forward: false,
1129
+ sender: replyMsg.sender ? {
1130
+ id: replyMsg.sender_user_id,
1131
+ name: replyMsg.sender.name || "User",
1132
+ email: ""
1133
+ } : void 0
1134
+ };
1135
+ msg.replyMetadata = {
1136
+ replyemoji: msg.replyemoji || null,
1137
+ replyto_message_id: msg.replyto_message_id,
1138
+ replyto_user_id: msg.replyto_user_id || null,
1139
+ parent_message_id: msg.parent_message_id || null,
1140
+ replyMessageText: replyMsg.deleted ? "Original message unavailable" : replyMsg.message_type === "text" ? replyMsg.message : `Attachment: ${replyMsg.file_name || "File"}`,
1141
+ replySenderName: replyMsg.sender?.name || "User"
1142
+ };
1143
+ } else {
1144
+ msg.replyMetadata = {
1145
+ replyemoji: msg.replyemoji || null,
1146
+ replyto_message_id: msg.replyto_message_id,
1147
+ replyto_user_id: msg.replyto_user_id || null,
1148
+ parent_message_id: msg.parent_message_id || null,
1149
+ replyMessageText: "Original message unavailable",
1150
+ replySenderName: "User"
1151
+ };
1152
+ }
1153
+ } catch (e) {
1154
+ console.warn(
1155
+ "[Messages API] Failed to fetch reply message details:",
1156
+ e
1157
+ );
1158
+ }
1159
+ }
1160
+ }
1161
+ return messages;
1162
+ } catch (e) {
1163
+ console.error("[Messages API] Failed to get conversation messages:", e);
1164
+ return [];
1165
+ }
1166
+ }
1167
+ async function createMessage(msg) {
1168
+ try {
1169
+ const memberQuery = createQuery().select("user_id").eq("conversation_id", msg.conversationId);
1170
+ const members = await apiClient.get(
1171
+ `/rest/v1/conversation_members${memberQuery.toString()}`
1172
+ );
1173
+ if (!members || members.length === 0) {
1174
+ throw new Error("No members found in conversation");
1175
+ }
1176
+ const senderMsgId = msg.id || crypto.randomUUID();
1177
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1178
+ let resolvedReplyToId = null;
1179
+ if (msg.replyMetadata?.replyto_message_id) {
1180
+ const parentMsgQuery = createQuery().select("id, sender_message_id").eq("id", msg.replyMetadata.replyto_message_id).limit(1);
1181
+ const replyMsgs = await apiClient.get(
1182
+ `/rest/v1/chat_messages${parentMsgQuery.toString()}`
1183
+ );
1184
+ const replyMsg = replyMsgs[0] || null;
1185
+ resolvedReplyToId = replyMsg ? replyMsg.sender_message_id || replyMsg.id : msg.replyMetadata.replyto_message_id;
1186
+ }
1187
+ const records = [];
1188
+ for (const member of members) {
1189
+ const isSender = member.user_id === msg.senderId;
1190
+ const msgId = isSender ? senderMsgId : crypto.randomUUID();
1191
+ let finalMessage = msg.message;
1192
+ if (msg.messageType === "system" && msg.systemMetadata) {
1193
+ const { type: sysType, groupName, creatorName } = msg.systemMetadata;
1194
+ if (sysType === "group_created") {
1195
+ finalMessage = isSender ? `You created group "${groupName}"` : `${creatorName} created group "${groupName}"`;
1196
+ } else if (sysType === "members_added") {
1197
+ if (isSender) {
1198
+ continue;
1199
+ }
1200
+ finalMessage = `${creatorName} added you`;
1201
+ }
1202
+ }
1203
+ const hasPreparsedData = !!(msg.fileContentText || msg.fileContentJson);
1204
+ records.push({
1205
+ id: msgId,
1206
+ conversation_id: msg.conversationId,
1207
+ owner_user_id: member.user_id,
1208
+ sender_user_id: msg.senderId,
1209
+ message: finalMessage,
1210
+ message_type: msg.messageType,
1211
+ direction: isSender ? "Sent" : "Received",
1212
+ sent: true,
1213
+ received: isSender,
1214
+ created_at: now,
1215
+ file_url: msg.fileUrl || null,
1216
+ file_name: msg.fileName || null,
1217
+ file_size: msg.fileSize || null,
1218
+ mime_type: msg.mimeType || null,
1219
+ duration: msg.duration || null,
1220
+ thumbnail: msg.thumbnail || null,
1221
+ thumb: false,
1222
+ favorite: false,
1223
+ flag: false,
1224
+ star: false,
1225
+ pin: false,
1226
+ archive: false,
1227
+ deleted: false,
1228
+ action_this: false,
1229
+ reply: !!msg.replyMetadata,
1230
+ forward: false,
1231
+ replyemoji: msg.replyMetadata?.replyemoji || null,
1232
+ replyto_message_id: resolvedReplyToId,
1233
+ replyto_user_id: msg.replyMetadata?.replyto_user_id || null,
1234
+ parent_message_id: msg.replyMetadata?.parent_message_id || null,
1235
+ sender_message_id: isSender ? null : senderMsgId,
1236
+ client_message_id: msg.clientMessageId || null,
1237
+ message_status: "sent",
1238
+ location_data: msg.locationData || null,
1239
+ location_type: msg.locationType || null,
1240
+ file_content_text: msg.fileContentText || null,
1241
+ file_content_json: msg.fileContentJson || null,
1242
+ processing_status: hasPreparsedData ? "completed" : msg.messageType === "document" && (msg.fileName?.toLowerCase().endsWith(".pdf") || msg.mimeType === "application/pdf") ? "pending" : null
1243
+ });
1244
+ }
1245
+ if (records.length > 0) {
1246
+ await apiClient.post("/rest/v1/chat_messages", records);
1247
+ const isPdf = msg.messageType === "document" && (msg.fileName?.toLowerCase().endsWith(".pdf") || msg.mimeType === "application/pdf");
1248
+ if (isPdf && typeof window !== "undefined") {
1249
+ fetch("/api/process-pdf", {
1250
+ method: "POST",
1251
+ headers: { "Content-Type": "application/json" },
1252
+ body: JSON.stringify({
1253
+ messageId: senderMsgId
1254
+ })
1255
+ }).catch((err) => console.warn("[Messages API] Asynchronous PDF processing dispatch error:", err));
1256
+ }
1257
+ if (typeof window !== "undefined") {
1258
+ fetch("/api/notifications/push", {
1259
+ method: "POST",
1260
+ headers: { "Content-Type": "application/json" },
1261
+ body: JSON.stringify({
1262
+ senderId: msg.senderId,
1263
+ conversationId: msg.conversationId,
1264
+ message: msg.message,
1265
+ messageType: msg.messageType,
1266
+ fileName: msg.fileName
1267
+ })
1268
+ }).catch((err) => console.warn("[Messages API] Asynchronous FCM push dispatch error:", err));
1269
+ }
1270
+ }
1271
+ const senderRecord = records.find(
1272
+ (r) => r.owner_user_id === msg.senderId
1273
+ );
1274
+ if (!senderRecord) return null;
1275
+ const profileQuery = createQuery().select("id, name, email, avatar").eq("id", msg.senderId).limit(1);
1276
+ const profiles = await apiClient.get(
1277
+ `/rest/v1/profiles${profileQuery.toString()}`
1278
+ );
1279
+ const profile = profiles[0] || null;
1280
+ return {
1281
+ id: senderRecord.id,
1282
+ conversation_id: senderRecord.conversation_id,
1283
+ owner_user_id: senderRecord.owner_user_id,
1284
+ sender_user_id: senderRecord.sender_user_id,
1285
+ message: senderRecord.message,
1286
+ message_type: senderRecord.message_type,
1287
+ direction: senderRecord.direction,
1288
+ sent: senderRecord.sent,
1289
+ received: senderRecord.received,
1290
+ created_at: senderRecord.created_at,
1291
+ file_url: senderRecord.file_url || void 0,
1292
+ file_name: senderRecord.file_name || void 0,
1293
+ file_size: senderRecord.file_size ? Number(senderRecord.file_size) : void 0,
1294
+ mime_type: senderRecord.mime_type || void 0,
1295
+ duration: senderRecord.duration ? Number(senderRecord.duration) : void 0,
1296
+ thumbnail: senderRecord.thumbnail || void 0,
1297
+ thumb: senderRecord.thumb,
1298
+ favorite: senderRecord.favorite,
1299
+ flag: senderRecord.flag,
1300
+ star: senderRecord.star,
1301
+ pin: senderRecord.pin,
1302
+ archive: senderRecord.archive,
1303
+ deleted: senderRecord.deleted,
1304
+ action_this: senderRecord.action_this,
1305
+ reply: senderRecord.reply,
1306
+ forward: senderRecord.forward,
1307
+ replyemoji: senderRecord.replyemoji || void 0,
1308
+ replyto_message_id: senderRecord.replyto_message_id || void 0,
1309
+ replyto_user_id: senderRecord.replyto_user_id || void 0,
1310
+ parent_message_id: senderRecord.parent_message_id || void 0,
1311
+ message_status: senderRecord.message_status,
1312
+ client_message_id: senderRecord.client_message_id || void 0,
1313
+ location_data: senderRecord.location_data || void 0,
1314
+ location_type: senderRecord.location_type || void 0,
1315
+ file_content_text: senderRecord.file_content_text || void 0,
1316
+ file_content_json: senderRecord.file_content_json || void 0,
1317
+ processing_status: senderRecord.processing_status || void 0,
1318
+ sender: profile ? {
1319
+ id: profile.id,
1320
+ name: profile.name || profile.email.split("@")[0],
1321
+ email: profile.email,
1322
+ avatar_url: profile.avatar || void 0
1323
+ } : void 0
1324
+ };
1325
+ } catch (err) {
1326
+ console.error("[Messages API] Failed to create message copies:", err);
1327
+ return null;
1328
+ }
1329
+ }
1330
+
1331
+ // src/features/chattemplate/chat/repositories/message-repository.ts
1332
+ async function getConversationMessages2(conversationId, userId, options) {
1333
+ return getConversationMessages(conversationId, userId, options);
1334
+ }
1335
+ async function createMessage2(msg) {
1336
+ return createMessage(msg);
1337
+ }
1338
+
1339
+ // src/features/chattemplate/chat/api/conversations.api.ts
1340
+ async function getUserConversations(userId) {
1341
+ try {
1342
+ const businessUserId = stringToUuid(userId);
1343
+ const memberQuery = createQuery().select("conversation_id").eq("user_id", businessUserId);
1344
+ const memberOf = await apiClient.get(`/rest/v1/conversation_members${memberQuery.toString()}`);
1345
+ if (!memberOf || memberOf.length === 0) return [];
1346
+ const convoIds = memberOf.map((m) => m.conversation_id);
1347
+ const convosQuery = createQuery().select(`
1348
+ *,
1349
+ conversation_members (
1350
+ role,
1351
+ joined_at,
1352
+ user_id,
1353
+ unread_count,
1354
+ profiles (
1355
+ id,
1356
+ name,
1357
+ email,
1358
+ avatar,
1359
+ last_seen
1360
+ )
1361
+ ),
1362
+ chat_messages (
1363
+ id,
1364
+ conversation_id,
1365
+ sender_user_id,
1366
+ message,
1367
+ message_type,
1368
+ file_url,
1369
+ file_name,
1370
+ file_size,
1371
+ mime_type,
1372
+ duration,
1373
+ created_at,
1374
+ deleted,
1375
+ deleted_by,
1376
+ message_status
1377
+ )
1378
+ `).in("id", convoIds).eq("chat_messages.owner_user_id", businessUserId);
1379
+ const convos = await apiClient.get(`/rest/v1/conversations${convosQuery.toString()}`);
1380
+ if (!convos) return [];
1381
+ const mapped = convos.map((c) => {
1382
+ const members = (c.conversation_members || []).map((cm) => {
1383
+ if (!cm.profiles) return null;
1384
+ return {
1385
+ id: cm.profiles.id,
1386
+ name: cm.profiles.name,
1387
+ email: cm.profiles.email,
1388
+ avatar_url: cm.profiles.avatar || void 0,
1389
+ last_seen: cm.profiles.last_seen || void 0
1390
+ };
1391
+ }).filter(Boolean);
1392
+ const messageCopies = [...c.chat_messages || []].filter((m) => !m.deleted || m.deleted_by !== null).sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
1393
+ const lastMessage = messageCopies[0] || void 0;
1394
+ let displayName = c.name || "";
1395
+ let displayImage = c.image || "";
1396
+ if (c.type === "direct") {
1397
+ const otherMember = members.find((m) => m.id !== businessUserId);
1398
+ if (otherMember) {
1399
+ displayName = otherMember.name;
1400
+ displayImage = otherMember.avatar_url || "";
1401
+ } else {
1402
+ displayName = "Chat Note (You)";
1403
+ const selfMember = members.find((m) => m.id === businessUserId);
1404
+ displayImage = selfMember?.avatar_url || "";
1405
+ }
1406
+ }
1407
+ const selfMemberRecord = (c.conversation_members || []).find((cm) => cm.user_id === businessUserId);
1408
+ const unreadCount = selfMemberRecord?.unread_count || 0;
1409
+ return {
1410
+ id: c.id,
1411
+ type: c.type,
1412
+ name: displayName,
1413
+ image: displayImage,
1414
+ created_by: c.created_by || void 0,
1415
+ created_at: c.created_at,
1416
+ lastMessage: lastMessage ? {
1417
+ id: lastMessage.id,
1418
+ conversation_id: lastMessage.conversation_id,
1419
+ sender_user_id: lastMessage.sender_user_id || "system",
1420
+ message: lastMessage.message || "",
1421
+ message_type: lastMessage.message_type,
1422
+ file_url: lastMessage.file_url || void 0,
1423
+ file_name: lastMessage.file_name || void 0,
1424
+ file_size: lastMessage.file_size || void 0,
1425
+ mime_type: lastMessage.mime_type || void 0,
1426
+ duration: lastMessage.duration || void 0,
1427
+ created_at: lastMessage.created_at,
1428
+ deleted: !!lastMessage.deleted
1429
+ } : void 0,
1430
+ unreadCount,
1431
+ members
1432
+ };
1433
+ });
1434
+ const seenDirectRecipients = /* @__PURE__ */ new Set();
1435
+ const deduplicated = [];
1436
+ const sorted = [...mapped].sort((a, b) => {
1437
+ const timeA = a.lastMessage ? new Date(a.lastMessage.created_at).getTime() : new Date(a.created_at).getTime();
1438
+ const timeB = b.lastMessage ? new Date(b.lastMessage.created_at).getTime() : new Date(b.created_at).getTime();
1439
+ return timeB - timeA;
1440
+ });
1441
+ for (const convo of sorted) {
1442
+ if (convo.type === "direct") {
1443
+ const otherMember = convo.members.find((m) => m.id !== businessUserId);
1444
+ const recipientId = otherMember ? otherMember.id : businessUserId;
1445
+ if (seenDirectRecipients.has(recipientId)) {
1446
+ continue;
1447
+ }
1448
+ seenDirectRecipients.add(recipientId);
1449
+ }
1450
+ deduplicated.push(convo);
1451
+ }
1452
+ return deduplicated;
1453
+ } catch (err) {
1454
+ console.error("[Conversations API] Failed to get user conversations:", err);
1455
+ return [];
1456
+ }
1457
+ }
1458
+ async function getOrCreateDirectConversation(userAId, userBId) {
1459
+ try {
1460
+ const businessUserAId = userAId;
1461
+ const businessUserBId = userBId;
1462
+ const queryA = createQuery().select("conversation_id, conversations!inner(*)").eq("user_id", businessUserAId).eq("conversations.type", "direct");
1463
+ const membersA = await apiClient.get(`/rest/v1/conversation_members${queryA.toString()}`);
1464
+ if (membersA && membersA.length > 0) {
1465
+ const convoIds = membersA.map((m) => m.conversation_id);
1466
+ const queryB = createQuery().select("conversation_id").in("conversation_id", convoIds).eq("user_id", businessUserBId);
1467
+ const membersB = await apiClient.get(`/rest/v1/conversation_members${queryB.toString()}`);
1468
+ if (membersB && membersB.length > 0) {
1469
+ return membersB[0].conversation_id;
1470
+ }
1471
+ }
1472
+ const insertedConvos = await apiClient.post("/rest/v1/conversations", {
1473
+ type: "direct",
1474
+ created_by: businessUserAId
1475
+ });
1476
+ const newConvo = insertedConvos[0] || null;
1477
+ if (!newConvo) {
1478
+ throw new Error("Failed to create conversation.");
1479
+ }
1480
+ const membersToInsert = [
1481
+ { conversation_id: newConvo.id, user_id: businessUserAId }
1482
+ ];
1483
+ if (businessUserAId !== businessUserBId) {
1484
+ membersToInsert.push({ conversation_id: newConvo.id, user_id: businessUserBId });
1485
+ }
1486
+ await apiClient.post("/rest/v1/conversation_members", membersToInsert);
1487
+ return newConvo.id;
1488
+ } catch (err) {
1489
+ console.error("[Conversations API] Failed to get or create direct conversation:", err);
1490
+ return null;
1491
+ }
1492
+ }
1493
+
1494
+ // src/features/chattemplate/chat/repositories/conversation-repository.ts
1495
+ async function getUserConversations2(userId) {
1496
+ return getUserConversations(userId);
1497
+ }
1498
+ async function getOrCreateDirectConversation2(userAId, userBId) {
1499
+ return getOrCreateDirectConversation(userAId, userBId);
1500
+ }
1501
+
1502
+ // src/server/messages.handler.ts
1503
+ async function handleMessagesGet(request) {
1504
+ try {
1505
+ const { searchParams } = new URL(request.url);
1506
+ const conversationId = searchParams.get("conversationId");
1507
+ const recipientId = searchParams.get("recipientId");
1508
+ const senderId = searchParams.get("senderId");
1509
+ if (!conversationId && !recipientId) {
1510
+ return NextResponse6.json({ error: "conversationId or recipientId is required" }, { status: 400 });
1511
+ }
1512
+ if (!senderId) {
1513
+ return NextResponse6.json({ error: "senderId (owner_user_id) is required" }, { status: 400 });
1514
+ }
1515
+ let targetConvoId = conversationId;
1516
+ if (!targetConvoId && recipientId) {
1517
+ targetConvoId = await getOrCreateDirectConversation2(senderId, recipientId);
1518
+ }
1519
+ if (!targetConvoId) {
1520
+ return NextResponse6.json([]);
1521
+ }
1522
+ const messages = await getConversationMessages2(targetConvoId, senderId);
1523
+ return NextResponse6.json(messages);
1524
+ } catch (err) {
1525
+ console.error("GET messages error:", err);
1526
+ return NextResponse6.json(
1527
+ { error: err instanceof Error ? err.message : "Failed to fetch messages" },
1528
+ { status: 500 }
1529
+ );
1530
+ }
1531
+ }
1532
+ async function handleMessagesPost(request) {
1533
+ try {
1534
+ const body = await request.json();
1535
+ const isAttachment = body.messageType && body.messageType !== "text";
1536
+ const hasContent = body.message?.trim() || isAttachment && body.fileUrl;
1537
+ if (!hasContent) {
1538
+ return NextResponse6.json(
1539
+ { error: "message (or fileUrl for attachments) is required" },
1540
+ { status: 400 }
1541
+ );
1542
+ }
1543
+ let targetConvoId = body.conversationId;
1544
+ if (!targetConvoId && body.recipientId) {
1545
+ const senderId = body.senderId;
1546
+ if (!senderId) {
1547
+ return NextResponse6.json(
1548
+ { error: "senderId is required to resolve conversation" },
1549
+ { status: 400 }
1550
+ );
1551
+ }
1552
+ targetConvoId = await getOrCreateDirectConversation2(senderId, body.recipientId);
1553
+ }
1554
+ if (!targetConvoId) {
1555
+ return NextResponse6.json(
1556
+ { error: "Could not resolve or create conversation" },
1557
+ { status: 400 }
1558
+ );
1559
+ }
1560
+ const msg = await createMessage2({
1561
+ conversationId: targetConvoId,
1562
+ senderId: body.senderId,
1563
+ message: body.message,
1564
+ messageType: body.messageType || "text",
1565
+ fileUrl: body.fileUrl,
1566
+ fileName: body.fileName,
1567
+ replyToMessageId: body.replyToMessageId,
1568
+ forwardedFromMessageId: body.forwardedFromMessageId,
1569
+ locationData: body.location
1570
+ });
1571
+ if (!msg) {
1572
+ return NextResponse6.json(
1573
+ { error: "Failed to create message" },
1574
+ { status: 500 }
1575
+ );
1576
+ }
1577
+ return NextResponse6.json(msg, { status: 201 });
1578
+ } catch (err) {
1579
+ console.error("POST message error:", err);
1580
+ return NextResponse6.json(
1581
+ { error: err instanceof Error ? err.message : "Failed to create message" },
1582
+ { status: 500 }
1583
+ );
1584
+ }
1585
+ }
1586
+
1587
+ // src/server/contacts.handler.ts
1588
+ import { NextResponse as NextResponse7 } from "next/server";
1589
+
1590
+ // src/features/chattemplate/contacts/api/contacts.mapper.ts
1591
+ function mapToContact(dbRecord) {
1592
+ const u = dbRecord.contact_user || {};
1593
+ const contactEmail = dbRecord.email || u.email || "";
1594
+ return {
1595
+ id: dbRecord.id,
1596
+ ownerId: dbRecord.owner_id,
1597
+ contactUserId: dbRecord.contact_user_id,
1598
+ fullName: dbRecord.nickname || u.name || contactEmail.split("@")[0] || "Unknown",
1599
+ email: contactEmail,
1600
+ avatarUrl: u.avatar || void 0,
1601
+ company: u.company || void 0,
1602
+ mobile: u.mobile || void 0,
1603
+ status: "Active",
1604
+ nickname: dbRecord.nickname || void 0,
1605
+ createdAt: dbRecord.created_at
1606
+ };
1607
+ }
1608
+
1609
+ // src/lib/db-alerts/types/db-alert.ts
1610
+ var DB_ALERTS_CONFIG = {
1611
+ groupName: "DB Alerts",
1612
+ groupImage: "https://images.unsplash.com/photo-1598257006458-087169a1f08d?w=128&h=128&fit=crop&auto=format&q=80",
1613
+ adminEmails: [
1614
+ "itsaman00786@gmail.com",
1615
+ "amanmicropay@gmail.com",
1616
+ "n.rajukrishna@gmail.com"
1617
+ ]
1618
+ };
1619
+
1620
+ // src/services/db-alert.service.ts
1621
+ function formatAlertTime(date) {
1622
+ const day = date.getDate();
1623
+ const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1624
+ const month = months[date.getMonth()];
1625
+ const year = date.getFullYear();
1626
+ let hours = date.getHours();
1627
+ const minutes = date.getMinutes().toString().padStart(2, "0");
1628
+ const ampm = hours >= 12 ? "AM" : "PM";
1629
+ hours = hours % 12;
1630
+ hours = hours ? hours : 12;
1631
+ return `${day} ${month} ${year} ${hours}:${minutes} ${ampm}`;
1632
+ }
1633
+ async function getOrCreateAlertConversation() {
1634
+ const supabase2 = createClient3();
1635
+ try {
1636
+ const { data: existing, error } = await supabase2.from("conversations").select("id").eq("name", DB_ALERTS_CONFIG.groupName).eq("type", "group").maybeSingle();
1637
+ if (error) throw error;
1638
+ if (existing) return existing.id;
1639
+ const { data: newConvo, error: createError } = await supabase2.from("conversations").insert({
1640
+ type: "group",
1641
+ name: DB_ALERTS_CONFIG.groupName,
1642
+ image: DB_ALERTS_CONFIG.groupImage || null
1643
+ }).select("id").single();
1644
+ if (createError) throw createError;
1645
+ if (!newConvo) return null;
1646
+ const { data: adminProfiles, error: profilesError } = await supabase2.from("profiles").select("id, email").in("email", DB_ALERTS_CONFIG.adminEmails);
1647
+ if (profilesError) throw profilesError;
1648
+ if (adminProfiles && adminProfiles.length > 0) {
1649
+ const membersToInsert = adminProfiles.map((p) => ({
1650
+ conversation_id: newConvo.id,
1651
+ user_id: p.id,
1652
+ role: "member"
1653
+ }));
1654
+ const { error: insertError } = await supabase2.from("conversation_members").insert(membersToInsert);
1655
+ if (insertError) {
1656
+ console.error("[DB Alerts] Failed to insert initial admin members:", insertError);
1657
+ } else {
1658
+ console.log(`[DB Alerts] Created group with ${adminProfiles.length} configured admin members`);
1659
+ }
1660
+ }
1661
+ return newConvo.id;
1662
+ } catch (err) {
1663
+ console.error("[DB Alerts] Error in getOrCreateAlertConversation:", err);
1664
+ return null;
1665
+ }
1666
+ }
1667
+ async function triggerContactAlert(action, ownerId, contactUserId) {
1668
+ const supabase2 = createClient3();
1669
+ try {
1670
+ const convoId = await getOrCreateAlertConversation();
1671
+ if (!convoId) return;
1672
+ const { data: ownerProfile } = await supabase2.from("profiles").select("name, email").eq("id", ownerId).maybeSingle();
1673
+ const ownerName = ownerProfile?.name || ownerProfile?.email?.split("@")[0] || "System";
1674
+ const { data: contactProfile } = await supabase2.from("profiles").select("name, email").eq("id", contactUserId).maybeSingle();
1675
+ const contactName = contactProfile?.name || contactProfile?.email?.split("@")[0] || "System";
1676
+ const timeStr = formatAlertTime(/* @__PURE__ */ new Date());
1677
+ let formattedMessage = "";
1678
+ if (action === "create") {
1679
+ formattedMessage = `Contact Created
1680
+ \u{1F7E2} Contact Added
1681
+ By: ${ownerName}
1682
+ Contact: ${contactName}
1683
+ Time: ${timeStr}`;
1684
+ } else if (action === "delete") {
1685
+ formattedMessage = `Contact Deleted
1686
+ \u{1F534} Contact Deleted
1687
+ By: ${ownerName}
1688
+ Contact: ${contactName}`;
1689
+ } else if (action === "update") {
1690
+ formattedMessage = `Contact Updated
1691
+ \u{1F7E1} Contact Updated
1692
+ By: ${ownerName}
1693
+ Contact: ${contactName}
1694
+ Time: ${timeStr}`;
1695
+ }
1696
+ await createMessage2({
1697
+ conversationId: convoId,
1698
+ senderId: ownerId,
1699
+ message: formattedMessage,
1700
+ messageType: "system"
1701
+ });
1702
+ } catch (err) {
1703
+ console.error("[DB Alerts] Failed to trigger contact alert:", err);
1704
+ }
1705
+ }
1706
+ async function triggerGroupAlert(action, actorId, groupName) {
1707
+ const supabase2 = createClient3();
1708
+ try {
1709
+ const convoId = await getOrCreateAlertConversation();
1710
+ if (!convoId) return;
1711
+ let actorName = "System";
1712
+ if (actorId) {
1713
+ const { data: actorProfile } = await supabase2.from("profiles").select("name, email").eq("id", actorId).maybeSingle();
1714
+ actorName = actorProfile?.name || actorProfile?.email?.split("@")[0] || "System";
1715
+ }
1716
+ let formattedMessage = "";
1717
+ if (action === "create") {
1718
+ formattedMessage = `Group Created
1719
+ \u{1F7E2} Group Created
1720
+ By: ${actorName}
1721
+ Group: ${groupName}`;
1722
+ } else if (action === "delete") {
1723
+ formattedMessage = `Group Deleted
1724
+ \u{1F534} Group Deleted
1725
+ By: ${actorName}
1726
+ Group: ${groupName}`;
1727
+ } else if (action === "update") {
1728
+ formattedMessage = `Group Updated
1729
+ \u{1F7E1} Group Updated
1730
+ By: ${actorName}
1731
+ Group: ${groupName}`;
1732
+ }
1733
+ let senderId = actorId;
1734
+ if (!senderId) {
1735
+ const { data: firstMember } = await supabase2.from("conversation_members").select("user_id").eq("conversation_id", convoId).limit(1).maybeSingle();
1736
+ senderId = firstMember?.user_id || "";
1737
+ }
1738
+ if (!senderId) {
1739
+ console.warn("[DB Alerts] Cannot send alert because no valid sender ID is available.");
1740
+ return;
1741
+ }
1742
+ await createMessage2({
1743
+ conversationId: convoId,
1744
+ senderId,
1745
+ message: formattedMessage,
1746
+ messageType: "system"
1747
+ });
1748
+ } catch (err) {
1749
+ console.error("[DB Alerts] Failed to trigger group alert:", err);
1750
+ }
1751
+ }
1752
+
1753
+ // src/features/chattemplate/chat/services/chat-storage.service.ts
1754
+ async function initializeContactStorage(contactEmail) {
1755
+ return Promise.resolve();
1756
+ }
1757
+
1758
+ // src/features/chattemplate/contacts/api/contacts.api.ts
1759
+ async function getContacts(userId) {
1760
+ try {
1761
+ const validUserId = stringToUuid(userId);
1762
+ const query = createQuery().select(`
1763
+ id,
1764
+ owner_id,
1765
+ contact_user_id,
1766
+ nickname,
1767
+ email,
1768
+ created_at,
1769
+ contact_user:profiles!contacts_contact_user_id_fkey (
1770
+ id,
1771
+ name,
1772
+ email,
1773
+ avatar,
1774
+ company,
1775
+ mobile
1776
+ )
1777
+ `).eq("owner_id", validUserId).order("created_at", { ascending: false });
1778
+ const data = await apiClient.get(`/rest/v1/contacts${query.toString()}`);
1779
+ if (!data) return [];
1780
+ return data.map(mapToContact);
1781
+ } catch (error) {
1782
+ console.error("[Contacts API] Failed to get user contacts:", error);
1783
+ return [];
1784
+ }
1785
+ }
1786
+ async function createContact(ownerId, contactEmail, nickname, ownerEmail) {
1787
+ try {
1788
+ const validOwnerId = stringToUuid(ownerId);
1789
+ const emailLower = contactEmail.trim().toLowerCase();
1790
+ const profileQuery = createQuery().select("id, name, email, avatar, company, mobile").eq("email", emailLower).limit(1);
1791
+ const profiles = await apiClient.get(`/rest/v1/profiles${profileQuery.toString()}`);
1792
+ const profile = profiles[0] || null;
1793
+ if (profile && profile.id === validOwnerId) {
1794
+ return { success: false, error: "You cannot add yourself as a contact." };
1795
+ }
1796
+ if (profile) {
1797
+ const existingQuery = createQuery().select("id").eq("owner_id", validOwnerId).or(`email.ilike.${emailLower},contact_user_id.eq.${profile.id}`).limit(1);
1798
+ const existing = await apiClient.get(`/rest/v1/contacts${existingQuery.toString()}`);
1799
+ if (existing.length > 0) {
1800
+ return { success: false, error: "This contact is already in your list." };
1801
+ }
1802
+ } else {
1803
+ const existingQuery = createQuery().select("id").eq("owner_id", validOwnerId).ilike("email", emailLower).limit(1);
1804
+ const existing = await apiClient.get(`/rest/v1/contacts${existingQuery.toString()}`);
1805
+ if (existing.length > 0) {
1806
+ return { success: false, error: "This contact is already in your list." };
1807
+ }
1808
+ }
1809
+ let contactUserId;
1810
+ if (profile) {
1811
+ contactUserId = profile.id;
1812
+ } else {
1813
+ const displayName = nickname || emailLower.split("@")[0];
1814
+ const newUserId = crypto.randomUUID ? crypto.randomUUID() : "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
1815
+ const r = Math.random() * 16 | 0;
1816
+ const v = c === "x" ? r : r & 3 | 8;
1817
+ return v.toString(16);
1818
+ });
1819
+ const newProfileData = {
1820
+ id: newUserId,
1821
+ email: emailLower,
1822
+ name: displayName,
1823
+ avatar: null,
1824
+ company: null,
1825
+ mobile: null,
1826
+ status: "offline",
1827
+ online: false,
1828
+ offline: true,
1829
+ last_seen: (/* @__PURE__ */ new Date()).toISOString(),
1830
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1831
+ };
1832
+ const insertedProfiles = await apiClient.post("/rest/v1/profiles", newProfileData);
1833
+ const newProfile = insertedProfiles[0] || null;
1834
+ if (!newProfile) {
1835
+ return { success: false, error: "Failed to create contact profile." };
1836
+ }
1837
+ contactUserId = newProfile.id;
1838
+ }
1839
+ const contactData = {
1840
+ owner_id: validOwnerId,
1841
+ contact_user_id: contactUserId,
1842
+ nickname: nickname?.trim() || null,
1843
+ email: emailLower,
1844
+ user_uuid: validOwnerId
1845
+ };
1846
+ await apiClient.post("/rest/v1/contacts", contactData);
1847
+ triggerContactAlert("create", ownerId, contactUserId).catch(
1848
+ (err) => console.error("[DB Alerts] Error sending contact created alert:", err)
1849
+ );
1850
+ if (emailLower) {
1851
+ void initializeContactStorage(emailLower).catch(
1852
+ (err) => console.error("[Contact Storage] Error initializing contact file space:", err)
1853
+ );
1854
+ }
1855
+ if (ownerEmail) {
1856
+ void initializeContactStorage(ownerEmail).catch(
1857
+ (err) => console.error("[Contact Storage] Error initializing owner file space:", err)
1858
+ );
1859
+ }
1860
+ return { success: true };
1861
+ } catch (error) {
1862
+ console.error("[Contacts API] Failed to create contact:", error);
1863
+ return { success: false, error: error instanceof Error ? error.message : "Failed to create contact" };
1864
+ }
1865
+ }
1866
+
1867
+ // src/features/chattemplate/contacts/repositories/contact-repository.ts
1868
+ async function getUserContacts(userId) {
1869
+ return getContacts(userId);
1870
+ }
1871
+ async function createContact2(ownerId, contactEmail, nickname, ownerEmail) {
1872
+ return createContact(ownerId, contactEmail, nickname, ownerEmail);
1873
+ }
1874
+
1875
+ // src/server/contacts.handler.ts
1876
+ async function handleContactsGet(request) {
1877
+ try {
1878
+ const { searchParams } = new URL(request.url);
1879
+ const userId = searchParams.get("userId");
1880
+ if (!userId) {
1881
+ return NextResponse7.json({ error: "userId query parameter is required" }, { status: 400 });
1882
+ }
1883
+ const contacts = await getUserContacts(userId);
1884
+ return NextResponse7.json(contacts);
1885
+ } catch (err) {
1886
+ console.error("GET contacts error:", err);
1887
+ return NextResponse7.json(
1888
+ { error: err instanceof Error ? err.message : "Failed to fetch contacts" },
1889
+ { status: 500 }
1890
+ );
1891
+ }
1892
+ }
1893
+ async function handleContactsPost(request) {
1894
+ try {
1895
+ const body = await request.json();
1896
+ if (!body.ownerId) {
1897
+ return NextResponse7.json({ error: "ownerId is required" }, { status: 400 });
1898
+ }
1899
+ if (!body.email) {
1900
+ return NextResponse7.json({ error: "email is required" }, { status: 400 });
1901
+ }
1902
+ const result = await createContact2(body.ownerId, body.email, body.nickname);
1903
+ if (!result.success) {
1904
+ return NextResponse7.json({ error: result.error || "Failed to create contact" }, { status: 409 });
1905
+ }
1906
+ return NextResponse7.json({ success: true, message: "Contact added successfully" }, { status: 201 });
1907
+ } catch (err) {
1908
+ console.error("POST contact error:", err);
1909
+ return NextResponse7.json(
1910
+ { error: err instanceof Error ? err.message : "Failed to create contact" },
1911
+ { status: 500 }
1912
+ );
1913
+ }
1914
+ }
1915
+
1916
+ // src/server/conversations.handler.ts
1917
+ import { NextResponse as NextResponse8 } from "next/server";
1918
+ async function handleConversationsGet(request) {
1919
+ try {
1920
+ const { searchParams } = new URL(request.url);
1921
+ const userId = searchParams.get("userId");
1922
+ if (!userId) {
1923
+ return NextResponse8.json({ error: "userId query parameter is required" }, { status: 400 });
1924
+ }
1925
+ const conversations = await getUserConversations2(userId);
1926
+ return NextResponse8.json(conversations);
1927
+ } catch (err) {
1928
+ console.error("GET conversations error:", err);
1929
+ return NextResponse8.json(
1930
+ { error: err instanceof Error ? err.message : "Failed to fetch conversations" },
1931
+ { status: 500 }
1932
+ );
1933
+ }
1934
+ }
1935
+
1936
+ // src/server/groups.handler.ts
1937
+ import { NextResponse as NextResponse9 } from "next/server";
1938
+
1939
+ // src/features/chattemplate/groups/api/groups.mapper.ts
1940
+ function mapToGroup(dbRecord) {
1941
+ return {
1942
+ id: dbRecord.id,
1943
+ groupName: dbRecord.name,
1944
+ description: dbRecord.description || "",
1945
+ groupImage: dbRecord.image_url || "",
1946
+ users: Array.isArray(dbRecord.users) ? dbRecord.users : [],
1947
+ status: dbRecord.status,
1948
+ email: dbRecord.email || void 0,
1949
+ userUuid: dbRecord.user_uuid || void 0,
1950
+ createdAt: dbRecord.created_at,
1951
+ updatedAt: dbRecord.updated_at
1952
+ };
1953
+ }
1954
+
1955
+ // src/features/chattemplate/groups/api/groups.api.ts
1956
+ async function saveGroup(group) {
1957
+ const id = group.id || crypto.randomUUID();
1958
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1959
+ let userUuid = null;
1960
+ const groupEmail = group.email?.trim().toLowerCase();
1961
+ if (group.userUuid) {
1962
+ userUuid = group.userUuid;
1963
+ } else if (groupEmail) {
1964
+ try {
1965
+ const profileQuery = createQuery().select("id").eq("email", groupEmail).limit(1);
1966
+ const profiles = await apiClient.get(`/rest/v1/profiles${profileQuery.toString()}`);
1967
+ const existing = profiles[0] || null;
1968
+ if (existing) {
1969
+ userUuid = existing.id;
1970
+ } else {
1971
+ const newUserId = crypto.randomUUID ? crypto.randomUUID() : "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
1972
+ const r = Math.random() * 16 | 0;
1973
+ const v = c === "x" ? r : r & 3 | 8;
1974
+ return v.toString(16);
1975
+ });
1976
+ const newProfile = {
1977
+ id: newUserId,
1978
+ email: groupEmail,
1979
+ name: group.groupName || groupEmail.split("@")[0],
1980
+ avatar: group.groupImage || null,
1981
+ status: "offline",
1982
+ online: false,
1983
+ offline: true,
1984
+ last_seen: now,
1985
+ updated_at: now
1986
+ };
1987
+ const inserted = await apiClient.post("/rest/v1/profiles", newProfile);
1988
+ if (inserted && inserted[0]) {
1989
+ userUuid = inserted[0].id;
1990
+ }
1991
+ }
1992
+ } catch (err) {
1993
+ console.error("[Groups API] Failed to get or create group user:", err);
1994
+ }
1995
+ }
1996
+ console.log("[Groups API] Saving group with userUuid:", userUuid);
1997
+ const record = {
1998
+ id,
1999
+ name: group.groupName,
2000
+ description: group.description || null,
2001
+ image_url: group.groupImage || null,
2002
+ users: group.users || [],
2003
+ status: group.status || "Active",
2004
+ email: groupEmail || null,
2005
+ user_uuid: userUuid,
2006
+ created_at: group.createdAt || now,
2007
+ updated_at: now
2008
+ };
2009
+ let isNew = true;
2010
+ try {
2011
+ const checkQuery = createQuery().select("id").eq("id", id).limit(1);
2012
+ const existing = await apiClient.get(`/rest/v1/chat_group${checkQuery.toString()}`);
2013
+ if (existing && existing.length > 0) {
2014
+ isNew = false;
2015
+ }
2016
+ } catch (err) {
2017
+ }
2018
+ try {
2019
+ const data = await apiClient.post("/rest/v1/chat_group", record, {
2020
+ headers: {
2021
+ "Prefer": "resolution=merge-duplicates,return=representation"
2022
+ }
2023
+ });
2024
+ const savedRecord = data && data[0] ? data[0] : null;
2025
+ if (!savedRecord) {
2026
+ return null;
2027
+ }
2028
+ triggerGroupAlert(isNew ? "create" : "update", userUuid || "", group.groupName).catch(
2029
+ (err) => console.error("[DB Alerts] Error sending group saved alert:", err)
2030
+ );
2031
+ return mapToGroup(savedRecord);
2032
+ } catch (error) {
2033
+ console.error("[Groups API] Supabase saveGroup error:", error);
2034
+ return null;
2035
+ }
2036
+ }
2037
+
2038
+ // src/features/chattemplate/groups/repositories/group-repository.ts
2039
+ async function saveGroup2(group) {
2040
+ return saveGroup(group);
2041
+ }
2042
+
2043
+ // src/server/groups.handler.ts
2044
+ import { createClient as createClient4 } from "@supabase/supabase-js";
2045
+ async function handleGroupsGet(request) {
2046
+ try {
2047
+ const { searchParams } = new URL(request.url);
2048
+ const email = searchParams.get("email");
2049
+ if (!email) {
2050
+ return NextResponse9.json([]);
2051
+ }
2052
+ const supabaseUrl2 = process.env.NEXT_PUBLIC_SUPABASE_URL;
2053
+ const supabaseAnonKey2 = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY;
2054
+ if (!supabaseUrl2 || !supabaseAnonKey2) {
2055
+ return NextResponse9.json([]);
2056
+ }
2057
+ const supabase2 = createClient4(supabaseUrl2, supabaseAnonKey2);
2058
+ const { data, error } = await supabase2.from("chat_group").select("*").contains("users", JSON.stringify([email])).order("created_at", { ascending: false });
2059
+ if (error) throw error;
2060
+ const mapped = (data || []).map((d) => ({
2061
+ id: d.id,
2062
+ groupName: d.name,
2063
+ description: d.description || "",
2064
+ groupImage: d.image_url || "",
2065
+ users: Array.isArray(d.users) ? d.users : [],
2066
+ status: d.status,
2067
+ email: d.email || void 0,
2068
+ userUuid: d.user_uuid || void 0,
2069
+ createdAt: d.created_at,
2070
+ updatedAt: d.updated_at
2071
+ }));
2072
+ return NextResponse9.json(mapped);
2073
+ } catch (err) {
2074
+ console.error("GET groups error:", err);
2075
+ return NextResponse9.json(
2076
+ { error: err instanceof Error ? err.message : "Failed to fetch groups" },
2077
+ { status: 500 }
2078
+ );
2079
+ }
2080
+ }
2081
+ async function handleGroupsPost(request) {
2082
+ try {
2083
+ const body = await request.json();
2084
+ if (!body.groupName || !body.users || !body.status || !body.email) {
2085
+ return NextResponse9.json(
2086
+ { error: "groupName, users, status, and email are required fields" },
2087
+ { status: 400 }
2088
+ );
2089
+ }
2090
+ const saved = await saveGroup2({
2091
+ groupName: body.groupName,
2092
+ description: body.description,
2093
+ groupImage: body.groupImage,
2094
+ users: body.users,
2095
+ status: body.status,
2096
+ email: body.email,
2097
+ userUuid: body.userUuid
2098
+ });
2099
+ if (!saved) {
2100
+ return NextResponse9.json({ error: "Failed to create group in database" }, { status: 500 });
2101
+ }
2102
+ return NextResponse9.json(saved, { status: 201 });
2103
+ } catch (err) {
2104
+ console.error("POST group error:", err);
2105
+ return NextResponse9.json(
2106
+ { error: err instanceof Error ? err.message : "Internal server error while creating group" },
2107
+ { status: 500 }
2108
+ );
2109
+ }
2110
+ }
2111
+
2112
+ // src/server/profiles.handler.ts
2113
+ import { NextResponse as NextResponse10 } from "next/server";
2114
+
2115
+ // src/features/chattemplate/chat/api/profiles.api.ts
2116
+ async function getProfileByEmail(email) {
2117
+ try {
2118
+ const query = createQuery().select("*").eq("email", email).limit(1);
2119
+ const profiles = await apiClient.get(`/rest/v1/profiles${query.toString()}`);
2120
+ const data = profiles[0] || null;
2121
+ if (!data) return null;
2122
+ return {
2123
+ id: data.id,
2124
+ name: data.name,
2125
+ email: data.email,
2126
+ avatar_url: data.avatar || void 0
2127
+ };
2128
+ } catch (err) {
2129
+ console.error("[profiles.api] Failed to get profile by email:", err);
2130
+ return null;
2131
+ }
2132
+ }
2133
+ async function getAllProfiles() {
2134
+ try {
2135
+ const query = createQuery().select("*").order("name", { ascending: true });
2136
+ const data = await apiClient.get(`/rest/v1/profiles${query.toString()}`);
2137
+ if (!data) return [];
2138
+ return data.map((d) => ({
2139
+ id: d.id,
2140
+ name: d.name,
2141
+ email: d.email,
2142
+ avatar_url: d.avatar || void 0
2143
+ }));
2144
+ } catch (err) {
2145
+ console.error("[profiles.api] Failed to get all profiles:", err);
2146
+ return [];
2147
+ }
2148
+ }
2149
+
2150
+ // src/features/chattemplate/chat/repositories/profile-repository.ts
2151
+ async function getProfileByEmail2(email) {
2152
+ return getProfileByEmail(email);
2153
+ }
2154
+ async function getAllProfiles2() {
2155
+ return getAllProfiles();
2156
+ }
2157
+
2158
+ // src/server/profiles.handler.ts
2159
+ async function handleProfilesGet(request) {
2160
+ try {
2161
+ const { searchParams } = new URL(request.url);
2162
+ const email = searchParams.get("email");
2163
+ if (email) {
2164
+ const profile = await getProfileByEmail2(email);
2165
+ if (!profile) {
2166
+ return NextResponse10.json({ error: "Profile not found" }, { status: 404 });
2167
+ }
2168
+ return NextResponse10.json(profile);
2169
+ }
2170
+ const profiles = await getAllProfiles2();
2171
+ return NextResponse10.json(profiles);
2172
+ } catch (err) {
2173
+ console.error("GET profiles error:", err);
2174
+ return NextResponse10.json(
2175
+ { error: err instanceof Error ? err.message : "Failed to fetch profiles" },
2176
+ { status: 500 }
2177
+ );
2178
+ }
2179
+ }
2180
+
2181
+ // src/server/notifications.handler.ts
2182
+ import { NextResponse as NextResponse11 } from "next/server";
2183
+ async function handleNotificationsGet(request) {
2184
+ try {
2185
+ const { searchParams } = new URL(request.url);
2186
+ const userId = searchParams.get("userId");
2187
+ const readFilter = searchParams.get("read");
2188
+ const limit = searchParams.get("limit");
2189
+ if (!userId) {
2190
+ return NextResponse11.json({ error: "userId query parameter is required" }, { status: 400 });
2191
+ }
2192
+ const query = createQuery().select("*").eq("user_id", userId).order("created_at", { ascending: false });
2193
+ if (readFilter !== null) {
2194
+ query.eq("read", readFilter);
2195
+ }
2196
+ if (limit) {
2197
+ query.limit(parseInt(limit, 10));
2198
+ }
2199
+ const notifications = await apiClient.get(`/rest/v1/notifications${query.toString()}`);
2200
+ return NextResponse11.json(notifications);
2201
+ } catch (err) {
2202
+ console.error("GET notifications error:", err);
2203
+ return NextResponse11.json(
2204
+ { error: err instanceof Error ? err.message : "Failed to fetch notifications" },
2205
+ { status: 500 }
2206
+ );
2207
+ }
2208
+ }
2209
+ async function handleNotificationsPost(request) {
2210
+ try {
2211
+ const body = await request.json();
2212
+ if (!body.userId || !body.messageText) {
2213
+ return NextResponse11.json(
2214
+ { error: "userId and messageText are required" },
2215
+ { status: 400 }
2216
+ );
2217
+ }
2218
+ const newNotification = await apiClient.post("/rest/v1/notifications", {
2219
+ user_id: body.userId,
2220
+ sender_id: body.senderId || null,
2221
+ message_id: body.messageId || null,
2222
+ message_text: body.messageText,
2223
+ read: false
2224
+ });
2225
+ return NextResponse11.json(newNotification, { status: 201 });
2226
+ } catch (err) {
2227
+ console.error("POST notification error:", err);
2228
+ return NextResponse11.json(
2229
+ { error: err instanceof Error ? err.message : "Failed to create notification" },
2230
+ { status: 500 }
2231
+ );
2232
+ }
2233
+ }
2234
+ async function handleNotificationsPatch(request) {
2235
+ try {
2236
+ const body = await request.json();
2237
+ if (!body.id) {
2238
+ return NextResponse11.json({ error: "id is required" }, { status: 400 });
2239
+ }
2240
+ const query = createQuery().eq("id", body.id);
2241
+ const updated = await apiClient.patch(`/rest/v1/notifications${query.toString()}`, {
2242
+ read: body.read !== void 0 ? body.read : true
2243
+ });
2244
+ return NextResponse11.json(updated);
2245
+ } catch (err) {
2246
+ console.error("PATCH notification error:", err);
2247
+ return NextResponse11.json(
2248
+ { error: err instanceof Error ? err.message : "Failed to update notification" },
2249
+ { status: 500 }
2250
+ );
2251
+ }
2252
+ }
2253
+
2254
+ // src/server/mail.handler.ts
2255
+ import { NextResponse as NextResponse12 } from "next/server";
2256
+
2257
+ // src/lib/email/imap.ts
2258
+ import { ImapFlow } from "imapflow";
2259
+
2260
+ // config/mail.json
2261
+ var mail_default = {
2262
+ email: "ask@morrai.com",
2263
+ password: "0un:ZX3JOs&E",
2264
+ smtp: {
2265
+ host: "smtp.hostinger.com",
2266
+ port: 587,
2267
+ secure: false,
2268
+ requireTLS: true
2269
+ },
2270
+ imap: {
2271
+ host: "imap.hostinger.com",
2272
+ port: 993,
2273
+ secure: true
2274
+ }
2275
+ };
2276
+
2277
+ // src/lib/email/imap.ts
2278
+ function createImapClient() {
2279
+ return new ImapFlow({
2280
+ host: mail_default.imap.host,
2281
+ port: mail_default.imap.port,
2282
+ secure: mail_default.imap.secure,
2283
+ auth: {
2284
+ user: mail_default.email,
2285
+ pass: mail_default.password
2286
+ },
2287
+ // Hostinger and some other providers benefit from lower concurrency/logger configurations
2288
+ logger: false
2289
+ });
2290
+ }
2291
+
2292
+ // src/lib/email/email-parser.ts
2293
+ import { simpleParser } from "mailparser";
2294
+ async function parseEmail(source, seq, isRead) {
2295
+ const parsed = await simpleParser(source);
2296
+ let fromAddress = "";
2297
+ const fromObj = parsed.from;
2298
+ if (fromObj && fromObj.value && fromObj.value.length > 0) {
2299
+ const fromVal = fromObj.value[0];
2300
+ fromAddress = fromVal.address || fromVal.name || "";
2301
+ }
2302
+ let toAddress = "";
2303
+ const toObj = parsed.to;
2304
+ if (toObj && toObj.value && toObj.value.length > 0) {
2305
+ const toVal = toObj.value[0];
2306
+ toAddress = toVal.address || toVal.name || "";
2307
+ }
2308
+ const formatSize2 = (bytes) => {
2309
+ if (!bytes || bytes === 0) return "0 B";
2310
+ const k = 1024;
2311
+ const sizes = ["B", "KB", "MB", "GB"];
2312
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
2313
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
2314
+ };
2315
+ const attachments = (parsed.attachments || []).map((att, idx) => {
2316
+ const mimeType = att.contentType || "application/octet-stream";
2317
+ let fileUrl = "";
2318
+ let formattedSize = formatSize2(att.size || (att.content ? att.content.length : 0));
2319
+ if (att.content) {
2320
+ try {
2321
+ const buf = Buffer.isBuffer(att.content) ? att.content : Buffer.from(att.content);
2322
+ fileUrl = `data:${mimeType};base64,${buf.toString("base64")}`;
2323
+ } catch (err) {
2324
+ console.error("Error processing attachment:", err);
2325
+ }
2326
+ }
2327
+ return {
2328
+ id: `att-${seq}-${idx}`,
2329
+ name: att.filename || `attachment-${idx + 1}`,
2330
+ type: mimeType,
2331
+ size: formattedSize,
2332
+ url: fileUrl
2333
+ };
2334
+ });
2335
+ return {
2336
+ id: String(seq),
2337
+ from: fromAddress,
2338
+ fromName: parsed.from?.text || fromAddress,
2339
+ to: toAddress,
2340
+ subject: parsed.subject || "(No Subject)",
2341
+ date: parsed.date ? parsed.date.toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
2342
+ text: parsed.text || "",
2343
+ html: parsed.html || parsed.textAsHtml || "",
2344
+ isRead,
2345
+ attachments
2346
+ };
2347
+ }
2348
+
2349
+ // src/lib/email/mailer.ts
2350
+ import nodemailer from "nodemailer";
2351
+ var transporter = nodemailer.createTransport({
2352
+ host: mail_default.smtp.host,
2353
+ port: mail_default.smtp.port,
2354
+ secure: mail_default.smtp.secure,
2355
+ requireTLS: mail_default.smtp.requireTLS,
2356
+ auth: {
2357
+ user: mail_default.email,
2358
+ pass: mail_default.password
2359
+ }
2360
+ });
2361
+
2362
+ // src/lib/email/attachment-storage.ts
2363
+ import fs2 from "fs";
2364
+ import path2 from "path";
2365
+ var UPLOAD_DIR = path2.join(process.cwd(), "public", "uploads", "mail-attachments");
2366
+ function ensureDirectoryExists() {
2367
+ if (!fs2.existsSync(UPLOAD_DIR)) {
2368
+ fs2.mkdirSync(UPLOAD_DIR, { recursive: true });
2369
+ }
2370
+ }
2371
+ function formatSize(bytes) {
2372
+ if (!bytes || bytes === 0) return "0 B";
2373
+ const k = 1024;
2374
+ const sizes = ["B", "KB", "MB", "GB"];
2375
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
2376
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
2377
+ }
2378
+ function saveAttachmentLocally(originalFilename, content) {
2379
+ try {
2380
+ ensureDirectoryExists();
2381
+ const sanitizeName = (originalFilename || "attachment").replace(/[^a-zA-Z0-9._-]/g, "_").substring(0, 100);
2382
+ const timestamp = Date.now();
2383
+ const safeFilename = `${timestamp}_${sanitizeName}`;
2384
+ const filePath = path2.join(UPLOAD_DIR, safeFilename);
2385
+ let buffer;
2386
+ if (Buffer.isBuffer(content)) {
2387
+ buffer = content;
2388
+ } else if (typeof content === "string") {
2389
+ let raw = content;
2390
+ if (raw.includes(";base64,")) {
2391
+ raw = raw.split(";base64,").pop() || "";
2392
+ }
2393
+ buffer = Buffer.from(raw, "base64");
2394
+ } else {
2395
+ buffer = Buffer.from(content);
2396
+ }
2397
+ fs2.writeFileSync(filePath, buffer);
2398
+ const publicUrl = `/uploads/mail-attachments/${safeFilename}`;
2399
+ const formattedSize = formatSize(buffer.length);
2400
+ return {
2401
+ url: publicUrl,
2402
+ size: formattedSize
2403
+ };
2404
+ } catch (err) {
2405
+ console.error("Failed to save attachment locally:", err);
2406
+ return {
2407
+ url: "",
2408
+ size: "0 B"
2409
+ };
2410
+ }
2411
+ }
2412
+
2413
+ // src/server/mail.handler.ts
2414
+ async function handleMailInboxGet(request) {
2415
+ const url = new URL(request.url);
2416
+ const page = Math.max(1, parseInt(url.searchParams.get("page") || "1", 10));
2417
+ const limit = Math.max(1, parseInt(url.searchParams.get("limit") || "20", 10));
2418
+ const client = createImapClient();
2419
+ let hasMore = false;
2420
+ let totalMessages = 0;
2421
+ try {
2422
+ await client.connect();
2423
+ const lock = await client.getMailboxLock("INBOX");
2424
+ const emailsList = [];
2425
+ try {
2426
+ const status = await client.status("INBOX", { messages: true });
2427
+ totalMessages = status.messages || 0;
2428
+ if (totalMessages > 0) {
2429
+ const offset = (page - 1) * limit;
2430
+ const endSeq = Math.max(0, totalMessages - offset);
2431
+ const startSeq = Math.max(1, endSeq - limit + 1);
2432
+ if (endSeq >= 1) {
2433
+ const range = `${startSeq}:${endSeq}`;
2434
+ hasMore = startSeq > 1;
2435
+ for await (const message of client.fetch(range, { source: true, flags: true })) {
2436
+ const isRead = message.flags && message.flags.has("\\Seen");
2437
+ try {
2438
+ const parsed = await parseEmail(message.source, message.seq, !!isRead);
2439
+ emailsList.push(parsed);
2440
+ } catch (parseErr) {
2441
+ console.error(`Failed to parse email sequence ${message.seq}:`, parseErr);
2442
+ }
2443
+ }
2444
+ emailsList.reverse();
2445
+ }
2446
+ }
2447
+ } finally {
2448
+ lock.release();
2449
+ }
2450
+ await client.logout();
2451
+ return NextResponse12.json({
2452
+ success: true,
2453
+ emails: emailsList,
2454
+ hasMore,
2455
+ total: totalMessages,
2456
+ page,
2457
+ limit
2458
+ });
2459
+ } catch (error) {
2460
+ console.error("Error reading mailbox via IMAP:", error);
2461
+ try {
2462
+ await client.logout();
2463
+ } catch (_) {
2464
+ }
2465
+ return NextResponse12.json(
2466
+ {
2467
+ success: false,
2468
+ message: `Failed to load inbox emails: ${error.message || error}`
2469
+ },
2470
+ { status: 500 }
2471
+ );
2472
+ }
2473
+ }
2474
+ async function handleMailSentGet(request) {
2475
+ const url = new URL(request.url);
2476
+ const page = Math.max(1, parseInt(url.searchParams.get("page") || "1", 10));
2477
+ const limit = Math.max(1, parseInt(url.searchParams.get("limit") || "20", 10));
2478
+ const client = createImapClient();
2479
+ let hasMore = false;
2480
+ let totalMessages = 0;
2481
+ try {
2482
+ await client.connect();
2483
+ let mailboxName = "INBOX.Sent";
2484
+ let lock;
2485
+ try {
2486
+ lock = await client.getMailboxLock(mailboxName);
2487
+ } catch (_) {
2488
+ mailboxName = "Sent";
2489
+ lock = await client.getMailboxLock(mailboxName);
2490
+ }
2491
+ const emailsList = [];
2492
+ try {
2493
+ const status = await client.status(mailboxName, { messages: true });
2494
+ totalMessages = status.messages || 0;
2495
+ if (totalMessages > 0) {
2496
+ const offset = (page - 1) * limit;
2497
+ const endSeq = Math.max(0, totalMessages - offset);
2498
+ const startSeq = Math.max(1, endSeq - limit + 1);
2499
+ if (endSeq >= 1) {
2500
+ const range = `${startSeq}:${endSeq}`;
2501
+ hasMore = startSeq > 1;
2502
+ for await (const message of client.fetch(range, { source: true, flags: true })) {
2503
+ try {
2504
+ const parsed = await parseEmail(message.source, message.seq, true);
2505
+ emailsList.push({
2506
+ ...parsed,
2507
+ isSent: true
2508
+ });
2509
+ } catch (parseErr) {
2510
+ console.error(`Failed to parse sent email sequence ${message.seq}:`, parseErr);
2511
+ }
2512
+ }
2513
+ emailsList.reverse();
2514
+ }
2515
+ }
2516
+ } finally {
2517
+ if (lock) {
2518
+ lock.release();
2519
+ }
2520
+ }
2521
+ await client.logout();
2522
+ return NextResponse12.json({
2523
+ success: true,
2524
+ emails: emailsList,
2525
+ hasMore,
2526
+ total: totalMessages,
2527
+ page,
2528
+ limit
2529
+ });
2530
+ } catch (error) {
2531
+ console.error("Error reading sent mailbox via IMAP:", error);
2532
+ try {
2533
+ await client.logout();
2534
+ } catch (_) {
2535
+ }
2536
+ return NextResponse12.json(
2537
+ {
2538
+ success: false,
2539
+ message: `Failed to load sent emails: ${error.message || error}`
2540
+ },
2541
+ { status: 500 }
2542
+ );
2543
+ }
2544
+ }
2545
+ async function handleMailSendPost(request) {
2546
+ try {
2547
+ const body = await request.json();
2548
+ const { to, subject, html } = body;
2549
+ if (!to || !subject || !html) {
2550
+ return NextResponse12.json(
2551
+ {
2552
+ success: false,
2553
+ message: "Validation failed: 'to', 'subject', and 'html' are required fields."
2554
+ },
2555
+ { status: 400 }
2556
+ );
2557
+ }
2558
+ const attachments = (body.attachments || []).map((att) => {
2559
+ let rawContent = att.content || att.url || "";
2560
+ const filename = att.filename || att.name || "attachment";
2561
+ const contentType = att.contentType || att.type || "application/octet-stream";
2562
+ const saved = saveAttachmentLocally(filename, rawContent);
2563
+ if (rawContent.includes(";base64,")) {
2564
+ rawContent = rawContent.split(";base64,").pop() || "";
2565
+ }
2566
+ return {
2567
+ filename,
2568
+ contentType,
2569
+ content: Buffer.from(rawContent, "base64"),
2570
+ url: saved.url,
2571
+ size: saved.size
2572
+ };
2573
+ });
2574
+ const mailOptions = {
2575
+ from: body.from || `"${mail_default.email.split("@")[0]}" <${mail_default.email}>`,
2576
+ to: Array.isArray(to) ? to.join(", ") : to,
2577
+ subject,
2578
+ html
2579
+ };
2580
+ if (attachments.length > 0) {
2581
+ mailOptions.attachments = attachments;
2582
+ }
2583
+ const info = await transporter.sendMail(mailOptions);
2584
+ try {
2585
+ const MailComposer = __require("nodemailer/lib/mail-composer");
2586
+ const composer = new MailComposer(mailOptions);
2587
+ const rawMimeBuffer = await composer.compile().build();
2588
+ const client = createImapClient();
2589
+ await client.connect();
2590
+ await client.append("INBOX.Sent", rawMimeBuffer, ["\\Seen"]);
2591
+ await client.logout();
2592
+ } catch (imapErr) {
2593
+ console.warn("Could not save to IMAP INBOX.Sent:", imapErr);
2594
+ }
2595
+ const returnedAttachments = attachments.map((att, idx) => ({
2596
+ id: `sent-att-${Date.now()}-${idx}`,
2597
+ name: att.filename,
2598
+ type: att.contentType,
2599
+ size: att.size,
2600
+ url: att.url
2601
+ }));
2602
+ return NextResponse12.json({
2603
+ success: true,
2604
+ message: "Email sent successfully",
2605
+ messageId: info.messageId,
2606
+ attachments: returnedAttachments
2607
+ });
2608
+ } catch (error) {
2609
+ console.error("Error sending email via Nodemailer:", error);
2610
+ return NextResponse12.json(
2611
+ {
2612
+ success: false,
2613
+ message: `Failed to send email: ${error.message || error}`
2614
+ },
2615
+ { status: 500 }
2616
+ );
2617
+ }
2618
+ }
2619
+ async function handleMailTestGet() {
2620
+ try {
2621
+ await transporter.verify();
2622
+ return NextResponse12.json({
2623
+ success: true,
2624
+ message: "SMTP connection successful"
2625
+ });
2626
+ } catch (error) {
2627
+ console.error("SMTP verification error:", error);
2628
+ return NextResponse12.json(
2629
+ {
2630
+ success: false,
2631
+ message: `SMTP connection failed: ${error.message || error}`
2632
+ },
2633
+ { status: 500 }
2634
+ );
2635
+ }
2636
+ }
2637
+ export {
2638
+ UI_RENDER_SYSTEM_PROMPT,
2639
+ handleChatPost,
2640
+ handleContactsGet,
2641
+ handleContactsPost,
2642
+ handleConversationsGet,
2643
+ handleGeocodeRequest,
2644
+ handleGroupsGet,
2645
+ handleGroupsPost,
2646
+ handleMailInboxGet,
2647
+ handleMailSendPost,
2648
+ handleMailSentGet,
2649
+ handleMailTestGet,
2650
+ handleMessagesGet,
2651
+ handleMessagesPost,
2652
+ handleNotificationsGet,
2653
+ handleNotificationsPatch,
2654
+ handleNotificationsPost,
2655
+ handleProfilesGet,
2656
+ handleSearchPost,
2657
+ handleShortenOptions,
2658
+ handleShortenPost,
2659
+ handleVouchersGet,
2660
+ handleVouchersPost
2661
+ };
2662
+ //# sourceMappingURL=server.mjs.map