PageSourceSearch

https://www.joannaalbrecht.com/supabase-client.js?v=captcha-20261002-1

js joannaalbrecht.com collected 2026-10-08 01:20:19 UTC 25,298 bytes, 911 lines download raw bytes

1import { supabaseConfig } from "./supabase-config.js";
2
3const baseHeaders = {
4  apikey: supabaseConfig.publishableKey,
5  Authorization: `Bearer ${supabaseConfig.publishableKey}`,
6  "Content-Type": "application/json"
7};
8
9function endpoint(path, params = {}) {
10  const url = new URL(`${supabaseConfig.url}/rest/v1/${path}`);
11  Object.entries(params).forEach(([key, value]) => url.searchParams.set(key, value));
12  return url.toString();
13}
14
15function functionEndpoint(name) {
16  return `${supabaseConfig.url}/functions/v1/${name}`;
17}
18
19function storageObjectUrl(path) {
20  return `${supabaseConfig.url}/storage/v1/object/platform-media/${path.split("/").map(encodeURIComponent).join("/")}`;
21}
22
23function publicStorageObjectUrl(path) {
24  return `${supabaseConfig.url}/storage/v1/object/platform-public/${path.split("/").map(encodeURIComponent).join("/")}`;
25}
26
27function publicStorageUrl(path) {
28  return `${supabaseConfig.url}/storage/v1/object/public/platform-public/${path.split("/").map(encodeURIComponent).join("/")}`;
29}
30
31function storageSignUrl(path) {
32  return `${supabaseConfig.url}/storage/v1/object/sign/platform-media/${path.split("/").map(encodeURIComponent).join("/")}`;
33}
34
35export function storageReference(path) {
36  return `storage://platform-media/${path}`;
37}
38
39export function storagePathFromReference(reference) {
40  if (!reference?.startsWith("storage://platform-media/")) return null;
41  return reference.replace("storage://platform-media/", "");
42}
43
44export async function createSignedPlaybackUrl(accessToken, reference, expiresIn = 3600) {
45  const path = storagePathFromReference(reference);
46  if (!path) return reference;
47
48  const response = await fetch(storageSignUrl(path), {
49    method: "POST",
50    headers: {
51      apikey: supabaseConfig.publishableKey,
52      Authorization: `Bearer ${accessToken}`,
53      "Content-Type": "application/json"
54    },
55    body: JSON.stringify({ expiresIn })
56  });
57
58  const payload = await response.json();
59  if (!response.ok) {
60    throw new Error(payload.message || payload.error || `Playback link failed: ${response.status}`);
61  }
62
63  const signedUrl = payload.signedURL || payload.signedUrl;
64  if (!signedUrl) throw new Error("Supabase did not return a playback link.");
65  if (signedUrl.startsWith("http")) return signedUrl;
66  if (signedUrl.startsWith("/storage/v1/")) return `${supabaseConfig.url}${signedUrl}`;
67  if (signedUrl.startsWith("/object/")) return `${supabaseConfig.url}/storage/v1${signedUrl}`;
68  return `${supabaseConfig.url}/storage/v1/${signedUrl.replace(/^\/+/, "")}`;
69}
70
71export async function uploadStorageFile(accessToken, file, path) {
72  const response = await fetch(storageObjectUrl(path), {
73    method: "POST",
74    headers: {
75      apikey: supabaseConfig.publishableKey,
76      Authorization: `Bearer ${accessToken}`,
77      "Content-Type": file.type || "application/octet-stream",
78      "x-upsert": "true"
79    },
80    body: file
81  });
82
83  let payload = null;
84  try {
85    payload = await response.json();
86  } catch {
87    payload = null;
88  }
89
90  if (!response.ok) {
91    throw new Error(payload?.message || payload?.error || `Upload failed: ${response.status}`);
92  }
93
94  return {
95    path,
96    reference: storageReference(path),
97    payload
98  };
99}
100
101export async function uploadPublicStorageFile(accessToken, file, path) {
102  const response = await fetch(publicStorageObjectUrl(path), {
103    method: "POST",
104    headers: {
105      apikey: supabaseConfig.publishableKey,
106      Authorization: `Bearer ${accessToken}`,
107      "Content-Type": file.type || "application/octet-stream",
108      "x-upsert": "true"
109    },
110    body: file
111  });
112
113  let payload = null;
114  try {
115    payload = await response.json();
116  } catch {
117    payload = null;
118  }
119
120  if (!response.ok) {
121    throw new Error(payload?.message || payload?.error || `Cover image upload failed: ${response.status}`);
122  }
123
124  return {
125    path,
126    url: publicStorageUrl(path),
127    payload
128  };
129}
130
131export async function fetchPublishedOfferings() {
132  const response = await fetch(endpoint("offerings", {
133    select: "*",
134    status: "eq.published",
135    order: "sort_order.asc"
136  }), {
137    headers: baseHeaders
138  });
139
140  if (!response.ok) {
141    throw new Error(`Supabase offerings request failed: ${response.status}`);
142  }
143
144  return response.json();
145}
146
147export async function signUpWithPassword(email, password, fullName, options = {}) {
148  const response = await fetch(`${supabaseConfig.url}/auth/v1/signup`, {
149    method: "POST",
150    headers: baseHeaders,
151    body: JSON.stringify({
152      email,
153      password,
154      gotrue_meta_security: { captcha_token: options.captchaToken },
155      data: {
156        full_name: fullName
157      }
158    })
159  });
160
161  const payload = await response.json();
162  if (!response.ok) {
163    throw new Error(payload.msg || payload.message || "Signup failed");
164  }
165  return payload;
166}
167
168export async function signInWithPassword(email, password, options = {}) {
169  const response = await fetch(`${supabaseConfig.url}/auth/v1/token?grant_type=password`, {
170    method: "POST",
171    headers: baseHeaders,
172    body: JSON.stringify({ email, password, gotrue_meta_security: { captcha_token: options.captchaToken } }),
173    signal: options.signal
174  });
175
176  const payload = await response.json();
177  if (!response.ok) {
178    throw new Error(payload.error_description || payload.msg || payload.message || "Login failed");
179  }
180  return payload;
181}
182
183export async function refreshSession(refreshToken) {
184  const response = await fetch(`${supabaseConfig.url}/auth/v1/token?grant_type=refresh_token`, {
185    method: "POST",
186    headers: baseHeaders,
187    body: JSON.stringify({ refresh_token: refreshToken })
188  });
189
190  const payload = await response.json();
191  if (!response.ok) {
192    throw new Error(payload.error_description || payload.msg || payload.message || "Login refresh failed");
193  }
194  return payload;
195}
196
197export async function sendPasswordRecovery(email, redirectTo, options = {}) {
198  const response = await fetch(`${supabaseConfig.url}/auth/v1/recover`, {
199    method: "POST",
200    headers: baseHeaders,
201    body: JSON.stringify({
202      email,
203      redirect_to: redirectTo,
204      gotrue_meta_security: { captcha_token: options.captchaToken }
205    })
206  });
207
208  const text = await response.text();
209  let payload = {};
210  try {
211    payload = text ? JSON.parse(text) || {} : {};
212  } catch {
213    payload = {};
214  }
215  if (!response.ok) {
216    throw new Error(payload.msg || payload.message || "Password recovery failed");
217  }
218  return payload;
219}
220
221export async function updatePassword(accessToken, password) {
222  const response = await fetch(`${supabaseConfig.url}/auth/v1/user`, {
223    method: "PUT",
224    headers: {
225      ...baseHeaders,
226      Authorization: `Bearer ${accessToken}`
227    },
228    body: JSON.stringify({ password })
229  });
230
231  const payload = await response.json();
232  if (!response.ok) {
233    throw new Error(payload.msg || payload.message || "Password update failed");
234  }
235  return payload;
236}
237
238export async function fetchProfile(accessToken, userId) {
239  const response = await fetch(endpoint("profiles", {
240    select: "*",
241    id: `eq.${userId}`,
242    limit: "1"
243  }), {
244    headers: {
245      ...baseHeaders,
246      Authorization: `Bearer ${accessToken}`
247    }
248  });
249
250  if (!response.ok) {
251    throw new Error(`Profile request failed: ${response.status}`);
252  }
253
254  const rows = await response.json();
255  return rows[0] || null;
256}
257
258export async function fetchProfiles(accessToken) {
259  const response = await fetch(endpoint("profiles", {
260    select: "*",
261    order: "created_at.desc"
262  }), {
263    headers: {
264      ...baseHeaders,
265      Authorization: `Bearer ${accessToken}`
266    }
267  });
268
269  if (!response.ok) {
270    throw new Error(`Profiles request failed: ${response.status}`);
271  }
272
273  return response.json();
274}
275
276export async function findProfileByEmail(accessToken, email) {
277  const response = await fetch(endpoint("profiles", {
278    select: "*",
279    email: `eq.${email}`,
280    limit: "1"
281  }), {
282    headers: {
283      ...baseHeaders,
284      Authorization: `Bearer ${accessToken}`
285    }
286  });
287
288  if (!response.ok) {
289    throw new Error(`Profile lookup failed: ${response.status}`);
290  }
291
292  const rows = await response.json();
293  return rows[0] || null;
294}
295
296export async function fetchEnrollments(accessToken) {
297  const response = await fetch(endpoint("enrollments", {
298    select: "*",
299    order: "created_at.desc"
300  }), {
301    headers: {
302      ...baseHeaders,
303      Authorization: `Bearer ${accessToken}`
304    }
305  });
306
307  if (!response.ok) {
308    throw new Error(`Enrollments request failed: ${response.status}`);
309  }
310
311  return response.json();
312}
313
314export async function fetchCourseProgress(accessToken, courseSlug, userId = "") {
315  const query = {
316    select: "*",
317    course_slug: `eq.${courseSlug}`,
318    limit: "1"
319  };
320  if (userId) query.user_id = `eq.${userId}`;
321
322  const response = await fetch(endpoint("course_progress", query), {
323    headers: {
324      ...baseHeaders,
325      Authorization: `Bearer ${accessToken}`
326    }
327  });
328
329  const payload = await response.json();
330  if (!response.ok) {
331    throw new Error(payload.message || `Course progress request failed: ${response.status}`);
332  }
333
334  return payload[0] || null;
335}
336
337export async function fetchAllCourseProgress(accessToken) {
338  const response = await fetch(endpoint("course_progress", {
339    select: "*",
340    order: "updated_at.desc"
341  }), {
342    headers: {
343      ...baseHeaders,
344      Authorization: `Bearer ${accessToken}`
345    }
346  });
347
348  const payload = await response.json();
349  if (!response.ok) {
350    throw new Error(payload.message || `Course progress request failed: ${response.status}`);
351  }
352
353  return payload;
354}
355
356export async function upsertCourseProgress(accessToken, progress) {
357  const response = await fetch(endpoint("course_progress", {
358    on_conflict: "user_id,course_slug"
359  }), {
360    method: "POST",
361    headers: {
362      ...baseHeaders,
363      Authorization: `Bearer ${accessToken}`,
364      Prefer: "resolution=merge-duplicates,return=representation"
365    },
366    body: JSON.stringify({
367      user_id: progress.userId,
368      course_slug: progress.courseSlug,
369      completed_days: progress.completedDays,
370      selected_path: progress.selectedPath || null,
371      last_route: progress.lastRoute,
372      updated_at: new Date().toISOString()
373    })
374  });
375
376  const payload = await response.json();
377  if (!response.ok) {
378    throw new Error(payload.message || `Course progress save failed: ${response.status}`);
379  }
380
381  return payload[0] || null;
382}
383
384export async function fetchPayments(accessToken) {
385  const response = await fetch(endpoint("payments", {
386    select: "*",
387    order: "created_at.desc"
388  }), {
389    headers: {
390      ...baseHeaders,
391      Authorization: `Bearer ${accessToken}`
392    }
393  });
394
395  if (!response.ok) {
396    throw new Error(`Payments request failed: ${response.status}`);
397  }
398
399  return response.json();
400}
401
402export async function createStripeCheckoutSession(accessToken, offeringId) {
403  const response = await fetch(functionEndpoint("create-checkout-session"), {
404    method: "POST",
405    headers: {
406      ...baseHeaders,
407      Authorization: `Bearer ${accessToken}`
408    },
409    body: JSON.stringify({ offeringId })
410  });
411
412  const text = await response.text();
413  let payload = {};
414  try {
415    payload = text ? JSON.parse(text) || {} : {};
416  } catch {
417    payload = { error: text };
418  }
419  if (!response.ok) {
420    throw new Error(payload.error || payload.message || `Stripe checkout failed: ${response.status}`);
421  }
422  return payload;
423}
424
425async function parseFunctionResponse(response, fallbackMessage) {
426  const text = await response.text();
427  const contentType = response.headers.get("content-type") || "";
428  const isHtml = /text\/html/i.test(contentType) || /^\s*<!doctype html/i.test(text);
429  let payload = {};
430  try {
431    payload = text ? JSON.parse(text) || {} : {};
432  } catch {
433    payload = {};
434  }
435  if (!response.ok) {
436    if (isHtml) {
437      throw new Error("We couldn’t complete that request just now. Please refresh the page and try again.");
438    }
439    throw new Error(payload.error || payload.message || `${fallbackMessage}: ${response.status}`);
440  }
441  return payload;
442}
443
444export async function createPayPalOrder(accessToken, offeringId) {
445  const response = await fetch(functionEndpoint("create-paypal-order"), {
446    method: "POST",
447    headers: {
448      ...baseHeaders,
449      Authorization: `Bearer ${accessToken}`
450    },
451    body: JSON.stringify({ offeringId })
452  });
453
454  return parseFunctionResponse(response, "PayPal checkout failed");
455}
456
457export async function requestGiftAccess(accessToken, offeringId) {
458  const response = await fetch("/api/free-course-access", {
459    method: "POST",
460    headers: {
461      "Content-Type": "application/json",
462      Authorization: `Bearer ${accessToken}`
463    },
464    body: JSON.stringify({ offeringId })
465  });
466
467  return parseFunctionResponse(response, "Free access request failed");
468}
469
470export async function redeemCoupon(accessToken, offeringId, code) {
471  const response = await fetch("/api/redeem-coupon", {
472    method: "POST",
473    headers: {
474      "Content-Type": "application/json",
475      Authorization: `Bearer ${accessToken}`
476    },
477    body: JSON.stringify({ offeringId, code })
478  });
479
480  return parseFunctionResponse(response, "Coupon redemption failed");
481}
482
483export async function capturePayPalOrder(accessToken, orderId, offeringId) {
484  const response = await fetch(functionEndpoint("capture-paypal-order"), {
485    method: "POST",
486    headers: {
487      ...baseHeaders,
488      Authorization: `Bearer ${accessToken}`
489    },
490    body: JSON.stringify({ orderId, offeringId })
491  });
492
493  return parseFunctionResponse(response, "PayPal confirmation failed");
494}
495
496export async function importWordPressAudio(accessToken, items) {
497  const response = await fetch(functionEndpoint("import-wordpress-audio"), {
498    method: "POST",
499    headers: {
500      ...baseHeaders,
501      Authorization: `Bearer ${accessToken}`
502    },
503    body: JSON.stringify({ items })
504  });
505
506  return parseFunctionResponse(response, "WordPress audio import failed");
507}
508
509export async function fetchLessonsForCourse(courseId) {
510  const response = await fetch(endpoint("lessons", {
511    select: "*",
512    course_id: `eq.${courseId}`,
513    status: "eq.published",
514    order: "order_index.asc"
515  }), {
516    headers: baseHeaders
517  });
518
519  if (!response.ok) {
520    throw new Error(`Lessons request failed: ${response.status}`);
521  }
522
523  return response.json();
524}
525
526export async function fetchMeditationDetails() {
527  const response = await fetch(endpoint("meditation_details", {
528    select: "*"
529  }), {
530    headers: baseHeaders
531  });
532
533  if (!response.ok) {
534    throw new Error(`Meditation details request failed: ${response.status}`);
535  }
536
537  return response.json();
538}
539
540export async function fetchMediaAssets(accessToken) {
541  const response = await fetch(endpoint("media_assets", {
542    select: "*",
543    order: "created_at.desc"
544  }), {
545    headers: {
546      ...baseHeaders,
547      Authorization: `Bearer ${accessToken}`
548    }
549  });
550
551  const payload = await response.json();
552  if (!response.ok) {
553    throw new Error(payload.message || `Media library request failed: ${response.status}`);
554  }
555
556  return payload;
557}
558
559export async function createOffering(accessToken, offering) {
560  const response = await fetch(endpoint("offerings"), {
561    method: "POST",
562    headers: {
563      ...baseHeaders,
564      Authorization: `Bearer ${accessToken}`,
565      Prefer: "return=representation"
566    },
567    body: JSON.stringify({
568      type: offering.type,
569      title: offering.title,
570      slug: offering.slug,
571      short_description: offering.shortDescription,
572      description: offering.description,
573      price_cents: offering.priceCents,
574      currency: "usd",
575      access_rule: offering.accessRule,
576      status: offering.status || "published",
577      featured_image_url: offering.image,
578      is_featured: false,
579      sort_order: offering.sortOrder || 999
580    })
581  });
582
583  const payload = await response.json();
584  if (!response.ok) {
585    throw new Error(payload.message || `Create offering failed: ${response.status}`);
586  }
587  return payload[0];
588}
589
590export async function updateOffering(accessToken, offeringId, offering) {
591  const response = await fetch(endpoint("offerings", {
592    id: `eq.${offeringId}`
593  }), {
594    method: "PATCH",
595    headers: {
596      ...baseHeaders,
597      Authorization: `Bearer ${accessToken}`,
598      Prefer: "return=representation"
599    },
600    body: JSON.stringify({
601      type: offering.type,
602      title: offering.title,
603      slug: offering.slug,
604      short_description: offering.shortDescription,
605      description: offering.description,
606      price_cents: offering.priceCents,
607      access_rule: offering.accessRule,
608      status: offering.status,
609      featured_image_url: offering.image,
610      updated_at: new Date().toISOString()
611    })
612  });
613
614  const payload = await response.json();
615  if (!response.ok) {
616    throw new Error(payload.message || `Update offering failed: ${response.status}`);
617  }
618  return payload[0];
619}
620
621export async function deleteOffering(accessToken, offeringId) {
622  const response = await fetch(endpoint("offerings", {
623    id: `eq.${offeringId}`
624  }), {
625    method: "DELETE",
626    headers: {
627      ...baseHeaders,
628      Authorization: `Bearer ${accessToken}`,
629      Prefer: "return=representation"
630    }
631  });
632
633  const payload = await response.json();
634  if (!response.ok) {
635    throw new Error(payload.message || `Delete offering failed: ${response.status}`);
636  }
637  return payload[0] || null;
638}
639
640export async function createEnrollment(accessToken, enrollment) {
641  const response = await fetch(endpoint("enrollments"), {
642    method: "POST",
643    headers: {
644      ...baseHeaders,
645      Authorization: `Bearer ${accessToken}`,
646      Prefer: "return=representation"
647    },
648    body: JSON.stringify({
649      user_id: enrollment.userId,
650      offering_id: enrollment.offeringId,
651      source: enrollment.source,
652      payment_id: enrollment.paymentId || null,
653      granted_by_admin_id: enrollment.grantedByAdminId || null,
654      admin_note: enrollment.note || null
655    })
656  });
657
658  const payload = await response.json();
659  if (!response.ok) {
660    if (payload.code === "23505" || /duplicate key|enrollments_user_id_offering_id_key/i.test(payload.message || "")) {
661      const error = new Error("This student already has access to this offering.");
662      error.code = "DUPLICATE_ENROLLMENT";
663      throw error;
664    }
665    throw new Error(payload.message || `Grant access failed: ${response.status}`);
666  }
667  return payload[0];
668}
669
670export async function restoreEnrollment(accessToken, enrollmentId, enrollment) {
671  const response = await fetch(endpoint("enrollments", { id: `eq.${enrollmentId}` }), {
672    method: "PATCH",
673    headers: {
674      ...baseHeaders,
675      Authorization: `Bearer ${accessToken}`,
676      Prefer: "return=representation"
677    },
678    body: JSON.stringify({
679      source: enrollment.source,
680      payment_id: enrollment.paymentId || null,
681      granted_by_admin_id: enrollment.grantedByAdminId || null,
682      admin_note: enrollment.note || null,
683      revoked_at: null
684    })
685  });
686
687  const payload = await response.json();
688  if (!response.ok) {
689    throw new Error(payload.message || `Restore access failed: ${response.status}`);
690  }
691  return payload[0];
692}
693
694export async function revokeEnrollment(accessToken, enrollmentId) {
695  const response = await fetch(endpoint("enrollments", { id: `eq.${enrollmentId}` }), {
696    method: "PATCH",
697    headers: {
698      ...baseHeaders,
699      Authorization: `Bearer ${accessToken}`,
700      Prefer: "return=representation"
701    },
702    body: JSON.stringify({
703      revoked_at: new Date().toISOString()
704    })
705  });
706
707  const payload = await response.json();
708  if (!response.ok) {
709    throw new Error(payload.message || `Revoke access failed: ${response.status}`);
710  }
711  return payload[0];
712}
713
714export async function createLesson(accessToken, lesson) {
715  const response = await fetch(endpoint("lessons"), {
716    method: "POST",
717    headers: {
718      ...baseHeaders,
719      Authorization: `Bearer ${accessToken}`,
720      Prefer: "return=representation"
721    },
722    body: JSON.stringify({
723      course_id: lesson.courseId,
724      title: lesson.title,
725      slug: lesson.slug,
726      order_index: lesson.orderIndex,
727      content: lesson.content,
728      audio_url: lesson.audioUrl || null,
729      video_url: lesson.videoUrl || null,
730      status: "published"
731    })
732  });
733
734  const payload = await response.json();
735  if (!response.ok) {
736    throw new Error(payload.message || `Create lesson failed: ${response.status}`);
737  }
738  return payload[0];
739}
740
741export async function updateLesson(accessToken, lessonId, lesson) {
742  const response = await fetch(endpoint("lessons", {
743    id: `eq.${lessonId}`
744  }), {
745    method: "PATCH",
746    headers: {
747      ...baseHeaders,
748      Authorization: `Bearer ${accessToken}`,
749      Prefer: "return=representation"
750    },
751    body: JSON.stringify({
752      title: lesson.title,
753      slug: lesson.slug,
754      order_index: lesson.orderIndex,
755      content: lesson.content,
756      audio_url: lesson.audioUrl || null,
757      video_url: lesson.videoUrl || null,
758      status: lesson.status || "published",
759      updated_at: new Date().toISOString()
760    })
761  });
762
763  const payload = await response.json();
764  if (!response.ok) {
765    throw new Error(payload.message || `Update lesson failed: ${response.status}`);
766  }
767  return payload[0];
768}
769
770export async function deleteLesson(accessToken, lessonId) {
771  const response = await fetch(endpoint("lessons", {
772    id: `eq.${lessonId}`
773  }), {
774    method: "DELETE",
775    headers: {
776      ...baseHeaders,
777      Authorization: `Bearer ${accessToken}`,
778      Prefer: "return=representation"
779    }
780  });
781
782  const payload = await response.json();
783  if (!response.ok) {
784    throw new Error(payload.message || `Delete lesson failed: ${response.status}`);
785  }
786  return payload[0] || null;
787}
788
789export async function createMediaAsset(accessToken, asset) {
790  const response = await fetch(endpoint("media_assets"), {
791    method: "POST",
792    headers: {
793      ...baseHeaders,
794      Authorization: `Bearer ${accessToken}`,
795      Prefer: "return=representation"
796    },
797    body: JSON.stringify({
798      title: asset.title,
799      kind: asset.kind,
800      provider: asset.provider,
801      file_url: asset.fileUrl,
802      preview_url: asset.previewUrl || null,
803      duration_seconds: asset.durationSeconds || null,
804      transcript: asset.transcript || null,
805      notes: asset.notes || null,
806      created_by_admin_id: asset.createdByAdminId || null
807    })
808  });
809
810  const payload = await response.json();
811  if (!response.ok) {
812    throw new Error(payload.message || `Create media asset failed: ${response.status}`);
813  }
814  return payload[0];
815}
816
817export async function deleteMediaAsset(accessToken, assetId) {
818  const response = await fetch(endpoint("media_assets", {
819    id: `eq.${assetId}`
820  }), {
821    method: "DELETE",
822    headers: {
823      ...baseHeaders,
824      Authorization: `Bearer ${accessToken}`,
825      Prefer: "return=representation"
826    }
827  });
828
829  const payload = await response.json();
830  if (!response.ok) {
831    throw new Error(payload.message || `Delete media asset failed: ${response.status}`);
832  }
833  return payload[0] || null;
834}
835
836export async function upsertMeditationDetail(accessToken, detail) {
837  const response = await fetch(endpoint("meditation_details", {
838    on_conflict: "offering_id"
839  }), {
840    method: "POST",
841    headers: {
842      ...baseHeaders,
843      Authorization: `Bearer ${accessToken}`,
844      Prefer: "resolution=merge-duplicates,return=representation"
845    },
846    body: JSON.stringify({
847      offering_id: detail.offeringId,
848      audio_url: detail.audioUrl || null,
849      preview_audio_url: detail.previewAudioUrl || null,
850      duration_seconds: detail.durationSeconds || null,
851      practice_notes: detail.practiceNotes || null
852    })
853  });
854
855  const payload = await response.json();
856  if (!response.ok) {
857    throw new Error(payload.message || `Save meditation detail failed: ${response.status}`);
858  }
859  return payload[0];
860}
861
862export async function deleteMeditationDetail(accessToken, offeringId) {
863  const response = await fetch(endpoint("meditation_details", {
864    offering_id: `eq.${offeringId}`
865  }), {
866    method: "DELETE",
867    headers: {
868      ...baseHeaders,
869      Authorization: `Bearer ${accessToken}`,
870      Prefer: "return=representation"
871    }
872  });
873
874  const payload = await response.json();
875  if (!response.ok) {
876    throw new Error(payload.message || `Delete meditation detail failed: ${response.status}`);
877  }
878  return payload[0] || null;
879}
880
881export async function fetchAuthUser(accessToken) {
882  const response = await fetch(`${supabaseConfig.url}/auth/v1/user`, {
883    headers: {
884      ...baseHeaders,
885      Authorization: `Bearer ${accessToken}`
886    }
887  });
888
889  const payload = await response.json();
890  if (!response.ok) {
891    throw new Error(payload.msg || payload.message || "Could not load confirmed user");
892  }
893  return payload;
894}
895
896export function mapSupabaseOffering(row) {
897  return {
898    id: row.id,
899    type: row.type,
900    title: row.title,
901    slug: row.slug,
902    priceCents: row.price_cents,
903    accessRule: row.access_rule,
904    status: row.status,
905    image: row.featured_image_url,
906    category: row.type === "course" ? "Course" : "Meditation",
907    duration: row.type === "course" ? "Lessons" : "Audio",
908    lessonCount: undefined,
909    description: row.description || row.short_description || ""
910  };
911}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.