Next.js / Supabase өгөгдлийн сан

Supabase өгөгдлийн сан

Supabase-н үндэс нь PostgreSQL өгөгдлийн сан. SQL бичихгүйгээр JavaScript объект шиг өгөгдөл унших, нэмэх, шинэчлэх, устгах боломжтой. Энэ хичээлд Supabase client ашиглан бүрэн CRUD хэрэгжүүлнэ.

Өгөгдөл унших — select

Server Component дотор өгөгдөл унших нь маш цэгцтэй:

typescript
// app/profile/page.tsx
import { createClient } from '@/lib/supabase/server';
import { redirect } from 'next/navigation';

export default async function ProfilePage() {
  const supabase = await createClient();

  const { data: { user } } = await supabase.auth.getUser();
  if (!user) redirect('/login');

  // profiles хүснэгтээс унших
  const { data: profile, error } = await supabase
    .from('profiles')
    .select('username, xp, streak')
    .eq('id', user.id)
    .single();

  if (error || !profile) {
    return <div>Профайл олдсонгүй</div>;
  }

  return (
    <main>
      <h1>{profile.username}</h1>
      <p>XP: {profile.xp}</p>
      <p>Streak: {profile.streak} өдөр</p>
    </main>
  );
}

.eq('id', user.id) нь SQL-н WHERE id = user.id гэсэнтэй адил. .single() нь нэг мөр буцаана — олдохгүй бол error гарна.

Олон мөр унших ба шүүх

typescript
// lib/courses.ts
import { createClient } from "@/lib/supabase/server";

export async function getUserProgress(userId: string) {
  const supabase = await createClient();

  const { data, error } = await supabase
    .from("lesson_progress")
    .select("course_slug, lesson_slug, completed_at")
    .eq("user_id", userId)
    .order("completed_at", { ascending: false }); // шинэ эхлээд

  if (error) return [];
  return data;
}
typescript
// Олон баганыг нэг дор сонгох
const { data } = await supabase
  .from("profiles")
  .select("id, username, xp")
  .gte("xp", 100) // xp >= 100
  .order("xp", { ascending: false })
  .limit(10); // top 10

Өгөгдөл нэмэх ба шинэчлэх — insert / upsert

typescript
// Server Action — хичээл дуусгах
"use server";

import { createClient } from "@/lib/supabase/server";
import { revalidatePath } from "next/cache";

export async function completeLesson(courseSlug: string, lessonSlug: string) {
  const supabase = await createClient();
  const {
    data: { user },
  } = await supabase.auth.getUser();
  if (!user) throw new Error("Нэвтрээгүй байна");

  // upsert: байвал шинэчлэх, байхгүй бол нэмэх
  const { error } = await supabase.from("lesson_progress").upsert({
    user_id: user.id,
    course_slug: courseSlug,
    lesson_slug: lessonSlug,
  });

  if (error) throw new Error("Хадгалахад алдаа гарлаа");

  // XP нэмэх
  await supabase.rpc("increment_xp", { user_id: user.id, amount: 10 });

  revalidatePath("/profile");
  return { success: true };
}

upsert нь (user_id, course_slug, lesson_slug) давхардал байвал шинэчлэх, байхгүй бол шинэ мөр нэмэх — нэг дуудлагаар хоёуланг хийдэг.

Өгөгдөл шинэчлэх ба устгах — update / delete

typescript
// Профайл шинэчлэх
const { error } = await supabase
  .from("profiles")
  .update({ username: "Шинэ нэр", last_active: new Date().toISOString() })
  .eq("id", user.id);

// Өгөгдөл устгах
const { error } = await supabase
  .from("project_submissions")
  .delete()
  .eq("id", submissionId)
  .eq("user_id", user.id); // заавал өөрийн бичлэгийг л устгана

delete хийхдээ eq filter заавал нэмнэ — filter байхгүй бол хүснэгтийн бүх мөрийг устгаж болох тул маш болгоомжтой байх хэрэгтэй.

Дараагийн хичээлд:

Row Level Security (RLS) гэж юу болохыг судална. Бусад хэрэглэгч өөрийн өгөгдлийг харах, өөрчлөх боломжгүй болгох аюулгүй байдлын тохиргоог ойлгоно.