Skip to main content

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.

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.
1

Create a Supabase project

  1. Go to 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.
2

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.
-- ========================================
-- 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');
Change ADMIN123 to a unique, hard-to-guess value before running this SQL. Anyone with this code can register an admin account.
After the query succeeds you should see all four tables — messages, page_configs, users, and registration_codes — in the Table Editor.
3

Retrieve your API credentials

Your Next.js app needs three values from Supabase. Navigate to Project Settings → API in the Supabase dashboard:
CredentialWhere to find itUsed in
Project URL”Project URL” fieldNEXT_PUBLIC_SUPABASE_URL
anon / public key”Project API keys” → anon publicNEXT_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.
4

Add credentials as environment variables

Add the three Supabase values to your environment. See the environment variables page for the full list of required variables and a ready-to-paste .env.local template.

Row Level Security

The migration SQL automatically enables Row Level Security (RLS) policies on all three tables. Here is what each policy enforces:
TablePolicyEffect
messagesAllow public readAnyone can read wishes displayed on the home page
messagesService role full accessOnly server-side API routes can insert or delete wishes
page_configsAllow public readAnyone can read page visibility settings
page_configsService role full accessOnly server-side API routes can toggle page visibility
usersService role full accessOnly server-side API routes can read or write user records
registration_codesAllow public readClient-side code can verify a registration code before submitting
registration_codesService role full accessOnly server-side API routes can create or mark codes as used
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.
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:
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.