> ## Documentation Index
> Fetch the complete documentation index at: https://help.helloazhenweb.top/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up Supabase for the Birthday Wall wish wall

> Create a Supabase project, run the database migrations to create all required tables, and retrieve the API keys your deployment needs.

Birthday Wall stores wish wall messages, page visibility settings, admin users, and registration codes in a Supabase Postgres database. This page walks you through creating a project, running the migration SQL, and locating the credentials you will add to your environment variables.

<Steps>
  <Step title="Create a Supabase project">
    1. Go to [supabase.com](https://supabase.com) and sign in (or create a free account).
    2. Click **New project** from your organization dashboard.
    3. Give the project a name (e.g. `birthday-wall`), choose a database password, and select the region closest to your users.
    4. Click **Create new project** and wait for provisioning to finish — this typically takes about 30 seconds.
  </Step>

  <Step title="Run the database migration">
    All three required tables are created by a single SQL script. In your Supabase project:

    1. Click **SQL Editor** in the left sidebar.
    2. Click **New query**.
    3. Paste the entire migration below and click **Run**.

    ```sql theme={null}
    -- ========================================
    -- Table: messages
    -- Stores birthday wish wall submissions
    -- ========================================

    create table messages (
      id uuid default gen_random_uuid() primary key,
      content text not null,
      ip_hash text not null,
      color text not null,
      created_at timestamp with time zone default timezone('utc'::text, now()) not null
    );

    -- Allow anyone to read messages (displayed on the home page)
    alter table messages enable row level security;
    create policy "Allow public read messages" on messages
      for select using (true);

    -- Only the service role can insert or delete messages
    create policy "Service role full access messages" on messages
      for all using (auth.role() = 'service_role');


    -- ========================================
    -- Table: page_configs
    -- Controls which pages appear in the bottom navigation bar
    -- ========================================

    create table page_configs (
      id text primary key,
      name text not null,
      path text not null,
      icon text,
      visible boolean not null default true,
      "order" integer not null default 0
    );

    -- Insert default page configuration
    insert into page_configs (id, name, path, icon, visible, "order") values
      ('home', '首页', '/', '🏠', true, 1),
      ('write', '写祝福', '/write', '✏️', true, 2),
      ('report', '报告', '/report', '📊', true, 3),
      ('games', '游戏室', '/games', '🎮', true, 4);

    -- Allow anyone to read page configs
    create policy "Allow public read page_configs" on page_configs
      for select using (true);

    -- Only the service role can modify page configs
    create policy "Service role full access page_configs" on page_configs
      for all using (auth.role() = 'service_role');


    -- ========================================
    -- Table: users
    -- Stores admin user records linked to Supabase Auth
    -- ========================================

    create table users (
      id uuid primary key references auth.users(id) on delete cascade,
      email text,
      role text not null default 'user' check (role in ('admin', 'user')),
      created_at timestamp with time zone default timezone('utc'::text, now()) not null
    );

    -- Only the service role can access user records
    create policy "Service role full access users" on users
      for all using (auth.role() = 'service_role');


    -- ========================================
    -- Table: registration_codes
    -- Used for invitation-code-gated admin registration
    -- ========================================

    create table registration_codes (
      id uuid default gen_random_uuid() primary key,
      code text not null unique,
      used boolean not null default false,
      created_at timestamp with time zone default timezone('utc'::text, now()) not null
    );

    -- Allow anyone to read codes (needed for client-side verification)
    create policy "Allow public read registration_codes" on registration_codes
      for select using (true);

    -- Only the service role can modify registration codes
    create policy "Service role full access registration_codes" on registration_codes
      for all using (auth.role() = 'service_role');


    -- ========================================
    -- Optional: seed an initial admin registration code
    -- Replace 'ADMIN123' with a secret value before running
    -- ========================================

    insert into registration_codes (code) values ('ADMIN123');
    ```

    <Warning>
      Change `ADMIN123` to a unique, hard-to-guess value before running this SQL. Anyone with this code can register an admin account.
    </Warning>

    After the query succeeds you should see all four tables — `messages`, `page_configs`, `users`, and `registration_codes` — in the **Table Editor**.
  </Step>

  <Step title="Retrieve your API credentials">
    Your Next.js app needs three values from Supabase. Navigate to **Project Settings → API** in the Supabase dashboard:

    | Credential            | Where to find it                                      | Used in                         |
    | --------------------- | ----------------------------------------------------- | ------------------------------- |
    | **Project URL**       | "Project URL" field                                   | `NEXT_PUBLIC_SUPABASE_URL`      |
    | **anon / public key** | "Project API keys" → `anon public`                    | `NEXT_PUBLIC_SUPABASE_ANON_KEY` |
    | **service\_role key** | "Project API keys" → `service_role` (click to reveal) | `SUPABASE_SERVICE_ROLE_KEY`     |

    Copy all three values — you will need them in the next step.
  </Step>

  <Step title="Add credentials as environment variables">
    Add the three Supabase values to your environment. See the [environment variables page](/self-hosting/environment) for the full list of required variables and a ready-to-paste `.env.local` template.
  </Step>
</Steps>

## Row Level Security

The migration SQL automatically enables Row Level Security (RLS) policies on all three tables. Here is what each policy enforces:

| Table                | Policy                     | Effect                                                            |
| -------------------- | -------------------------- | ----------------------------------------------------------------- |
| `messages`           | `Allow public read`        | Anyone can read wishes displayed on the home page                 |
| `messages`           | `Service role full access` | Only server-side API routes can insert or delete wishes           |
| `page_configs`       | `Allow public read`        | Anyone can read page visibility settings                          |
| `page_configs`       | `Service role full access` | Only server-side API routes can toggle page visibility            |
| `users`              | `Service role full access` | Only server-side API routes can read or write user records        |
| `registration_codes` | `Allow public read`        | Client-side code can verify a registration code before submitting |
| `registration_codes` | `Service role full access` | Only server-side API routes can create or mark codes as used      |

<Warning>
  The `service_role` key bypasses all RLS policies and has unrestricted access to your entire database. Never expose it in browser code or commit it to your repository. Birthday Wall only uses this key inside server-side API routes — it is never sent to the browser.
</Warning>

The `anon` key, by contrast, is intentionally public. It is embedded in your frontend bundle via the `NEXT_PUBLIC_` prefix and is safe to expose — RLS policies enforce what it can and cannot access.

## Creating the first admin user

Once your tables exist you need a seed admin account to access `/admin`:

1. In the Supabase dashboard, go to **Authentication → Users → Add user**.
2. Enter an email and a strong password, then click **Create user**.
3. Copy the UUID shown in the users list.
4. Open the **SQL Editor** and run:

```sql theme={null}
INSERT INTO users (id, email, role)
VALUES ('<paste-uuid-here>', 'your@email.com', 'admin');
```

Alternatively, you can register through the `/admin/login` UI using the registration code you seeded in the SQL above — any account created with a valid registration code is automatically assigned the `admin` role.
