Skip to content

Commit 6e02a43

Browse files
committed
migrate to supabase with unified setup
1 parent 151eade commit 6e02a43

File tree

12 files changed

+688
-85
lines changed

12 files changed

+688
-85
lines changed

CLAUDE.md

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@ pnpm run dev # Start dev server on localhost:3000
1414
pnpm run build # Build for production
1515
pnpm run preview # Preview production build
1616

17-
# Database (requires .env file in project root)
18-
pnpm run db:start # Start PostgreSQL + PostGIS with Docker
19-
pnpm run db:stop # Stop database
20-
pnpm run db:restart # Restart database (useful for reseeding)
17+
# Database (uses Supabase remote)
18+
pnpm run db:setup # Run migrations and seeds on remote Supabase
2119
pnpm run db:generate # Generate Drizzle migrations from schema changes
20+
pnpm run db:push # Push migrations to remote Supabase via CLI
2221

2322
# Code Quality
2423
pnpm run lint # Run ESLint with cache
@@ -55,20 +54,29 @@ The app uses **PostgreSQL with PostGIS** and a normalized relational schema with
5554
2. Insert corresponding rows into `location_categories` junction table
5655
3. PostGIS automatically handles the geometry conversion
5756

58-
### Database Seeding
57+
### Database Setup and Seeding
5958

60-
- **Automatic seeding** via Docker Compose when starting the PostgreSQL database
59+
The project uses **Supabase** for remote database management:
60+
61+
- **Remote**: Connected to Supabase project (wwiwmsgedqeepcdtnhon) via connection pooler
6162
- Migrations are stored in `database/migrations/` (generated by Drizzle)
6263
- Seed files are stored in `database/seeds/`:
64+
- `rls-policies.sql` - Row Level Security policies
6365
- `categories.sql` - All Google Maps category types with icon mappings
64-
- `sources/dummy.sql` - Dummy location data for testing
65-
- The seeding process (executed by Docker):
66-
1. `init.sh` - Creates PostGIS extensions, roles, and permissions
67-
2. `run-migrations.sh` - Applies Drizzle migrations to create tables
68-
3. `rls-policies.sql` - Applies Row Level Security policies
69-
4. Seeds categories and dummy location data
70-
- To start/restart database: `pnpm run db:start`
71-
- To regenerate migrations after schema changes: `pnpm run db:generate`
66+
- `sources/*.sql` - Location data from various sources
67+
68+
**Database workflow**:
69+
70+
1. Generate migrations from schema changes: `pnpm run db:generate`
71+
2. Apply migrations and seeds to remote: `pnpm run db:setup`
72+
3. (Optional) Push via Supabase CLI: `pnpm run db:push`
73+
74+
The `db:setup` script (`scripts/db-setup.ts`):
75+
76+
- Enables PostGIS extension automatically
77+
- Runs all migrations in order, tracking applied migrations to avoid duplicates
78+
- Applies RLS policies and seeds data from SQL files
79+
- Uses connection pooler with `prepare: false` for transaction pooling mode
7280

7381
### API Endpoints
7482

@@ -118,7 +126,8 @@ The app uses **UnoCSS with Nimiq presets**:
118126
- Import schema as `tables` from `server/utils/drizzle.ts`
119127
- Schema is defined in `database/schema.ts`
120128
- Type exports: `Location`, `Category`, `LocationCategory`
121-
- Connection uses individual env vars (POSTGRES_HOST, POSTGRES_PORT, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB)
129+
- Connection uses `DATABASE_URL` environment variable
130+
- Must use `prepare: false` option for Supabase transaction pooler
122131
- PostgreSQL-specific: Use `sql` tagged templates for PostGIS queries
123132
- PostGIS functions: `ST_X()`, `ST_Y()`, `ST_Distance()`, `ST_Within()`, etc.
124133

@@ -130,7 +139,7 @@ The app uses **UnoCSS with Nimiq presets**:
130139
2. Categories must be valid Google Maps types (snake_case strings) from `database/seeds/categories.sql`
131140
3. Use PostGIS `ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)` for location geometry
132141
4. Insert corresponding rows into `location_categories` junction table
133-
5. Restart Docker Compose to apply changes: `pnpm run db:restart`
142+
5. Apply changes: `pnpm run db:setup` (runs migrations and seeds)
134143

135144
### Category Filtering
136145

@@ -151,27 +160,28 @@ The app uses **UnoCSS with Nimiq presets**:
151160
- **`drizzle.config.ts`** - PostgreSQL dialect, schema at `database/schema.ts`, migrations output to `database/migrations/`
152161
- **`uno.config.ts`** - UnoCSS with Nimiq presets
153162
- **`eslint.config.mjs`** - Antfu's ESLint config with Nuxt integration
154-
- **`database/docker-compose.yml`** - Docker Compose setup for local PostgreSQL development
155-
- **`database/init.sh`** - Initializes PostGIS extensions and permissions
156-
- **`database/run-migrations.sh`** - Runs Drizzle migrations on container start
157-
- **`database/rls-policies.sql`** - Row Level Security policies
163+
- **`supabase/config.toml`** - Supabase local development configuration
164+
- **`scripts/db-setup.ts`** - Unified migration and seeding script
165+
- **`database/migrations/`** - Drizzle-generated SQL migrations
166+
- **`database/seeds/rls-policies.sql`** - Row Level Security policies
158167
- **`database/seeds/categories.sql`** - All Google Maps categories with icons
159-
- **`database/seeds/sources/dummy.sql`** - Dummy location data
168+
- **`database/seeds/sources/*.sql`** - Location data from various sources
160169

161170
## Environment Variables
162171

163172
Required in `.env` (project root):
164173

165174
```env
166-
# PostgreSQL Configuration
167-
POSTGRES_HOST=localhost
168-
POSTGRES_PORT=5432
169-
POSTGRES_USER=postgres
170-
POSTGRES_PASSWORD=your_password
171-
POSTGRES_DB=postgres
175+
# PostgreSQL Configuration (Supabase Remote)
176+
DATABASE_URL=postgresql://postgres.wwiwmsgedqeepcdtnhon:[YOUR-PASSWORD]@aws-1-eu-central-1.pooler.supabase.com:6543/postgres
172177
173178
# API Keys
174179
NUXT_GOOGLE_API_KEY=your_api_key
175180
```
176181

177-
All variables are validated via `safeRuntimeConfig` using Valibot schema.
182+
**Important**:
183+
184+
- Uses Supabase connection pooler (port 6543) for remote database access
185+
- The pooler provides IPv4 connectivity (required for some WSL/network setups)
186+
- Connection uses transaction pooling mode (requires `prepare: false` in postgres client)
187+
- All variables are validated via `safeRuntimeConfig` using Valibot schema

README.md

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,51 @@ pnpm install
3232

3333
# Set up environment variables
3434
cp .env.example .env
35-
# Edit .env with your PostgreSQL credentials
35+
# Edit .env with your database connection string
36+
```
37+
38+
## Database Setup
39+
40+
This project supports both **remote Supabase** and **local development** with Supabase CLI.
41+
42+
### Option 1: Remote Supabase (Recommended for Production)
43+
44+
The project is configured to use a remote Supabase database by default.
45+
46+
```bash
47+
# 1. Add your Supabase connection string to .env
48+
# DATABASE_URL=postgresql://postgres.PROJECT_REF:PASSWORD@aws-1-eu-central-1.pooler.supabase.com:6543/postgres
3649

37-
# Start PostgreSQL + PostGIS with Docker
38-
pnpm run db:start
39-
# Database is automatically seeded on first start
50+
# 2. Run migrations and seeds
51+
pnpm run db:setup
4052
```
4153

54+
**Note:** The connection string uses the Supabase pooler (port 6543) which provides transaction pooling and IPv4 connectivity.
55+
56+
### Option 2: Local Development with Supabase CLI
57+
58+
For local development, you can run a local Supabase instance:
59+
60+
```bash
61+
# 1. Start local Supabase (includes PostgreSQL + PostGIS)
62+
pnpm run db:local
63+
64+
# 2. Update .env to use local connection
65+
# DATABASE_URL=postgresql://postgres:postgres@localhost:54322/postgres
66+
67+
# 3. Run migrations and seeds
68+
pnpm run db:setup
69+
70+
# 4. Stop local Supabase when done
71+
pnpm run db:local:stop
72+
```
73+
74+
**Local Supabase includes:**
75+
76+
- PostgreSQL with PostGIS on `localhost:54322`
77+
- Supabase Studio on `localhost:54323`
78+
- REST API on `localhost:54321`
79+
4280
## Development
4381

4482
```bash
@@ -179,10 +217,11 @@ pnpm run build # Build for production
179217
pnpm run preview # Preview production build
180218

181219
# Database
182-
pnpm run db:start # Start PostgreSQL + PostGIS with Docker
183-
pnpm run db:stop # Stop database
184-
pnpm run db:restart # Restart database (useful for reseeding)
185-
pnpm run db:generate # Generate migrations (stored in database/migrations/)
220+
pnpm run db:setup # Run migrations and seeds (local or remote)
221+
pnpm run db:generate # Generate migrations from schema changes
222+
pnpm run db:push # Push migrations to remote Supabase via CLI
223+
pnpm run db:local # Start local Supabase (PostgreSQL + PostGIS)
224+
pnpm run db:local:stop # Stop local Supabase
186225

187226
# Code Quality
188227
pnpm run lint # Run ESLint
@@ -192,47 +231,47 @@ pnpm run typecheck # Run TypeScript checks
192231

193232
## Environment Variables
194233

195-
Create a `.env` file in the project root (see `.env.example`):
234+
Create a `.env` file in the project root:
196235

197236
```env
198-
# PostgreSQL Configuration
199-
POSTGRES_HOST=localhost
200-
POSTGRES_PORT=5432
201-
POSTGRES_USER=postgres
202-
POSTGRES_PASSWORD=your_password
203-
POSTGRES_DB=postgres
237+
# Database Connection String
238+
# Remote Supabase (default)
239+
DATABASE_URL=postgresql://postgres.PROJECT_REF:PASSWORD@aws-1-eu-central-1.pooler.supabase.com:6543/postgres
204240
205-
# JWT Configuration (if using Supabase Studio)
206-
JWT_SECRET=your_jwt_secret
241+
# Or Local Supabase (when using pnpm run db:local)
242+
# DATABASE_URL=postgresql://postgres:postgres@localhost:54322/postgres
207243
208244
# API Keys
209-
ANON_KEY=your_anon_key
210-
SERVICE_ROLE_KEY=your_service_role_key
211245
NUXT_GOOGLE_API_KEY=your_google_api_key
212-
213-
# Kong & Studio (optional)
214-
KONG_HTTP_PORT=8100
215-
STUDIO_PORT=4000
216-
SUPABASE_PUBLIC_URL=http://localhost:8100
217246
```
218247

219248
## Database Development
220249

221-
This repository includes a PostgreSQL + PostGIS setup in the `database/` directory.
250+
The database uses **Supabase** for PostgreSQL + PostGIS hosting.
251+
252+
### Remote Database
253+
254+
The project is connected to a remote Supabase instance. All migrations and seeds are tracked in the `database/` folder and can be applied via:
255+
256+
```bash
257+
pnpm run db:setup
258+
```
259+
260+
### Local Database
222261

223-
**Quick Start:**
262+
For local development, you can spin up a local Supabase instance:
224263

225264
```bash
226-
pnpm run db:start # Start services
265+
pnpm run db:local
227266
```
228267

229-
**Access:**
268+
**Local Access:**
230269

231-
- **Supabase Studio**: http://localhost:4000
232-
- **PostgreSQL**: `localhost:5432`
233-
- **REST API**: http://localhost:8100
270+
- **Supabase Studio**: http://localhost:54323
271+
- **PostgreSQL**: `localhost:54322`
272+
- **REST API**: http://localhost:54321
234273

235-
See [`database/README.md`](database/README.md) for PostGIS examples and REST API usage.
274+
See [`database/README.md`](database/README.md) for PostGIS examples and database structure.
236275

237276
## Learn More
238277

database/seeds/rls-policies.sql

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,31 @@ CREATE POLICY "Only service_role can delete location_categories"
8484
ON location_categories FOR DELETE
8585
TO service_role
8686
USING (true);
87+
88+
-- Enable RLS on __container_migrations table
89+
ALTER TABLE __container_migrations ENABLE ROW LEVEL SECURITY;
90+
91+
-- __container_migrations: Read for all, write only for service_role
92+
DROP POLICY IF EXISTS "Allow read access for all users" ON __container_migrations;
93+
CREATE POLICY "Allow read access for all users"
94+
ON __container_migrations FOR SELECT
95+
TO anon, authenticated, service_role
96+
USING (true);
97+
98+
DROP POLICY IF EXISTS "Only service_role can insert migrations" ON __container_migrations;
99+
CREATE POLICY "Only service_role can insert migrations"
100+
ON __container_migrations FOR INSERT
101+
TO service_role
102+
WITH CHECK (true);
103+
104+
DROP POLICY IF EXISTS "Block updates on migrations" ON __container_migrations;
105+
CREATE POLICY "Block updates on migrations"
106+
ON __container_migrations FOR UPDATE
107+
TO anon, authenticated
108+
USING (false);
109+
110+
DROP POLICY IF EXISTS "Block deletes on migrations" ON __container_migrations;
111+
CREATE POLICY "Block deletes on migrations"
112+
ON __container_migrations FOR DELETE
113+
TO anon, authenticated
114+
USING (false);

drizzle.config.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
import process from 'node:process'
22
import { defineConfig } from 'drizzle-kit'
33

4-
const { POSTGRES_HOST, POSTGRES_PORT, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB } = process.env
5-
const url = `postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}`
6-
74
export default defineConfig({
85
dialect: 'postgresql',
96
schema: './database/schema.ts',
107
out: './database/migrations',
118
dbCredentials: {
12-
url,
9+
url: process.env.DATABASE_URL!,
1310
},
1411
})

nuxt.config.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,12 @@ export default defineNuxtConfig({
2727
},
2828
runtimeConfig: {
2929
googleApiKey: process.env.NUXT_GOOGLE_API_KEY || '',
30-
postgres: {
31-
host: process.env.POSTGRES_HOST || '',
32-
port: process.env.POSTGRES_PORT || '',
33-
user: process.env.POSTGRES_USER || '',
34-
password: process.env.POSTGRES_PASSWORD || '',
35-
db: process.env.POSTGRES_DB || '',
36-
},
30+
databaseUrl: process.env.DATABASE_URL || '',
3731
},
3832
safeRuntimeConfig: {
3933
$schema: v.object({
4034
googleApiKey: v.pipe(v.string(), v.minLength(1, 'Google API key is required')),
41-
postgres: v.object({
42-
host: v.pipe(v.string(), v.minLength(1, 'PostgreSQL host is required')),
43-
port: v.pipe(v.string(), v.minLength(1, 'PostgreSQL port is required')),
44-
user: v.pipe(v.string(), v.minLength(1, 'PostgreSQL user is required')),
45-
password: v.pipe(v.string(), v.minLength(1, 'PostgreSQL password is required')),
46-
db: v.pipe(v.string(), v.minLength(1, 'PostgreSQL database is required')),
47-
}),
35+
databaseUrl: v.pipe(v.string(), v.minLength(1, 'Database URL is required')),
4836
}),
4937
},
5038
icon: {

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
"preview": "nuxt preview",
1111
"prepare": "nuxt prepare",
1212
"postinstall": "nuxt prepare",
13-
"db:start": "cd database && docker compose --env-file ../.env up -d && cd ..",
14-
"db:stop": "cd database && docker compose down && cd ..",
15-
"db:restart": "cd database && docker compose --env-file ../.env down && docker compose --env-file ../.env up -d && cd ..",
13+
"db:setup": "NODE_OPTIONS='--dns-result-order=ipv4first' tsx scripts/db-setup.ts",
1614
"db:generate": "drizzle-kit generate",
15+
"db:push": "npx supabase db push",
16+
"db:local": "npx supabase start",
17+
"db:local:stop": "npx supabase stop",
1718
"lint": "eslint . --cache",
1819
"lint:fix": "eslint . --fix --cache",
1920
"test": "vitest run",
@@ -46,12 +47,14 @@
4647
"@unocss/nuxt": "catalog:ui",
4748
"@unocss/preset-attributify": "catalog:ui",
4849
"@vue/test-utils": "catalog:test",
50+
"dotenv": "catalog:",
4951
"drizzle-kit": "catalog:database",
5052
"eslint": "catalog:dev",
5153
"eslint-plugin-format": "catalog:dev",
5254
"happy-dom": "catalog:test",
5355
"nimiq-css": "catalog:ui",
5456
"playwright-core": "catalog:test",
57+
"tsx": "catalog:",
5558
"typescript": "catalog:dev",
5659
"unocss-preset-onmax": "catalog:ui",
5760
"vitest": "catalog:test",

0 commit comments

Comments
 (0)