feat: add PostgreSQL database adapter support
This commit is contained in:
@@ -85,6 +85,29 @@ If users are redirected incorrectly or permissions aren't working:
|
|||||||
3. Run `npx prisma generate`
|
3. Run `npx prisma generate`
|
||||||
4. Update affected API routes and components
|
4. Update affected API routes and components
|
||||||
|
|
||||||
|
### Database Provider Switching
|
||||||
|
The application supports both SQLite (default) and PostgreSQL databases.
|
||||||
|
|
||||||
|
**To switch between databases:**
|
||||||
|
```bash
|
||||||
|
# Switch to SQLite
|
||||||
|
npm run db:switch sqlite
|
||||||
|
|
||||||
|
# Switch to PostgreSQL
|
||||||
|
npm run db:switch postgresql
|
||||||
|
```
|
||||||
|
|
||||||
|
**To set up PostgreSQL:**
|
||||||
|
```bash
|
||||||
|
npm run db:setup-postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
**Database Configuration:**
|
||||||
|
- Edit `.env` file to set `DATABASE_PROVIDER` and `DATABASE_URL`
|
||||||
|
- For PostgreSQL, also set `DATABASE_SHADOW_URL` for migrations
|
||||||
|
|
||||||
|
**Note:** The database provider is automatically detected by Better Auth and Prisma.
|
||||||
|
|
||||||
### Running Tests
|
### Running Tests
|
||||||
- **Unit tests**: `npm run test`
|
- **Unit tests**: `npm run test`
|
||||||
- **Acceptance tests**: `npm run test:acceptance`
|
- **Acceptance tests**: `npm run test:acceptance`
|
||||||
|
|||||||
@@ -88,7 +88,14 @@ euchre_camp/
|
|||||||
|
|
||||||
- Node.js 20+
|
- Node.js 20+
|
||||||
- npm or yarn
|
- npm or yarn
|
||||||
- SQLite3
|
- **SQLite3** (default) or **PostgreSQL** (optional)
|
||||||
|
|
||||||
|
**For SQLite (default):**
|
||||||
|
- No additional setup required
|
||||||
|
|
||||||
|
**For PostgreSQL:**
|
||||||
|
- PostgreSQL 12+ installed and running
|
||||||
|
- Create a database for EuchreCamp
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
@@ -103,13 +110,31 @@ euchre_camp/
|
|||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Set up the database**
|
3. **Set up environment variables**
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env to configure your database
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Set up the database**
|
||||||
|
|
||||||
|
**For SQLite (default):**
|
||||||
```bash
|
```bash
|
||||||
npx prisma migrate deploy
|
npx prisma migrate deploy
|
||||||
npx prisma generate
|
npx prisma generate
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Start the development server**
|
**For PostgreSQL:**
|
||||||
|
```bash
|
||||||
|
# Set up PostgreSQL database
|
||||||
|
npm run db:setup-postgres
|
||||||
|
|
||||||
|
# Or manually:
|
||||||
|
npx prisma migrate deploy
|
||||||
|
npx prisma generate
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Start the development server**
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
@@ -119,11 +144,23 @@ euchre_camp/
|
|||||||
Create a `.env` file:
|
Create a `.env` file:
|
||||||
|
|
||||||
```env
|
```env
|
||||||
|
# Database Configuration
|
||||||
|
DATABASE_PROVIDER=sqlite # or "postgresql"
|
||||||
DATABASE_URL="file:./dev.db"
|
DATABASE_URL="file:./dev.db"
|
||||||
|
|
||||||
|
# Better Auth
|
||||||
BETTER_AUTH_SECRET="your-secret-key-here"
|
BETTER_AUTH_SECRET="your-secret-key-here"
|
||||||
BETTER_AUTH_URL="http://localhost:3000"
|
BETTER_AUTH_URL="http://localhost:3000"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Using PostgreSQL:**
|
||||||
|
|
||||||
|
```env
|
||||||
|
DATABASE_PROVIDER=postgresql
|
||||||
|
DATABASE_URL="postgresql://username:password@localhost:5432/euchre_camp"
|
||||||
|
DATABASE_SHADOW_URL="postgresql://username:password@localhost:5432/euchre_camp_shadow"
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Creating a Tournament
|
### Creating a Tournament
|
||||||
@@ -180,6 +217,20 @@ npm run test
|
|||||||
npm run test:acceptance
|
npm run test:acceptance
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Database Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Switch between SQLite and PostgreSQL
|
||||||
|
npm run db:switch sqlite
|
||||||
|
npm run db:switch postgresql
|
||||||
|
|
||||||
|
# Set up PostgreSQL database
|
||||||
|
npm run db:setup-postgres
|
||||||
|
|
||||||
|
# Seed database with sample data
|
||||||
|
npm run db:seed
|
||||||
|
```
|
||||||
|
|
||||||
## Admin User Creation
|
## Admin User Creation
|
||||||
|
|
||||||
To create an admin user, use the provided scripts:
|
To create an admin user, use the provided scripts:
|
||||||
|
|||||||
+4
-1
@@ -10,7 +10,10 @@
|
|||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:run": "vitest run",
|
"test:run": "vitest run",
|
||||||
"test:acceptance": "playwright test src/__tests__/e2e/",
|
"test:acceptance": "playwright test src/__tests__/e2e/",
|
||||||
"test:acceptance:headed": "playwright test src/__tests__/e2e/ --headed"
|
"test:acceptance:headed": "playwright test src/__tests__/e2e/ --headed",
|
||||||
|
"db:switch": "node scripts/switch-database.js",
|
||||||
|
"db:setup-postgres": "node scripts/setup-postgres.js",
|
||||||
|
"db:seed": "node scripts/seed.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ generator client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "sqlite"
|
provider = env("DATABASE_PROVIDER", "sqlite")
|
||||||
url = env("DATABASE_URL")
|
url = env("DATABASE_URL")
|
||||||
|
shadowDatabaseUrl = env("DATABASE_SHADOW_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Player {
|
model Player {
|
||||||
|
|||||||
Executable
+113
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database seed script for EuchreCamp
|
||||||
|
* Creates sample data for testing and development
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { PrismaClient } = require('@prisma/client');
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('🌱 Seeding database...\n');
|
||||||
|
|
||||||
|
// Create admin user if it doesn't exist
|
||||||
|
const adminEmail = 'david@dhg.lol';
|
||||||
|
const existingAdmin = await prisma.user.findUnique({
|
||||||
|
where: { email: adminEmail },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingAdmin) {
|
||||||
|
console.log('Creating admin user...');
|
||||||
|
const passwordHash = await bcrypt.hash('adminadmin', 12);
|
||||||
|
|
||||||
|
const adminUser = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
email: adminEmail,
|
||||||
|
emailVerified: true,
|
||||||
|
name: 'David Admin',
|
||||||
|
role: 'club_admin',
|
||||||
|
accounts: {
|
||||||
|
create: {
|
||||||
|
id: `admin_${Date.now()}`,
|
||||||
|
accountId: `admin_${Date.now()}`,
|
||||||
|
providerId: 'credential',
|
||||||
|
password: passwordHash,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Created admin user: ${adminUser.email}`);
|
||||||
|
} else {
|
||||||
|
console.log('✅ Admin user already exists');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create sample players if none exist
|
||||||
|
const playerCount = await prisma.player.count();
|
||||||
|
if (playerCount === 0) {
|
||||||
|
console.log('\nCreating sample players...');
|
||||||
|
const samplePlayers = [
|
||||||
|
'John Smith',
|
||||||
|
'Jane Doe',
|
||||||
|
'Mike Johnson',
|
||||||
|
'Sarah Brown',
|
||||||
|
'Charlie Wilson',
|
||||||
|
'Diana Davis',
|
||||||
|
'Robert Taylor',
|
||||||
|
'Emily Anderson',
|
||||||
|
'David Martinez',
|
||||||
|
'Lisa Thompson',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const name of samplePlayers) {
|
||||||
|
await prisma.player.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
currentElo: 1000 + Math.floor(Math.random() * 200) - 100,
|
||||||
|
gamesPlayed: Math.floor(Math.random() * 50),
|
||||||
|
wins: Math.floor(Math.random() * 30),
|
||||||
|
losses: Math.floor(Math.random() * 30),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log(`✅ Created ${samplePlayers.length} sample players`);
|
||||||
|
} else {
|
||||||
|
console.log(`✅ ${playerCount} players already exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create sample tournament
|
||||||
|
const tournamentCount = await prisma.event.count({
|
||||||
|
where: { eventType: 'tournament' },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tournamentCount === 0) {
|
||||||
|
console.log('\nCreating sample tournament...');
|
||||||
|
const tournament = await prisma.event.create({
|
||||||
|
data: {
|
||||||
|
name: 'Sample Tournament',
|
||||||
|
eventType: 'tournament',
|
||||||
|
format: 'round_robin',
|
||||||
|
eventDate: new Date(),
|
||||||
|
status: 'planned',
|
||||||
|
ownerId: existingAdmin?.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(`✅ Created sample tournament: ${tournament.name}`);
|
||||||
|
} else {
|
||||||
|
console.log('✅ Tournaments already exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n🌱 Seeding completed successfully!');
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((e) => {
|
||||||
|
console.error('❌ Seeding failed:', e);
|
||||||
|
process.exit(1);
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
});
|
||||||
Executable
+113
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Script to set up PostgreSQL database for EuchreCamp
|
||||||
|
* This script creates the database and runs migrations
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function checkPostgresConnection() {
|
||||||
|
try {
|
||||||
|
const dbUrl = process.env.DATABASE_URL;
|
||||||
|
if (!dbUrl || !dbUrl.startsWith('postgresql://')) {
|
||||||
|
console.error('❌ DATABASE_URL is not set for PostgreSQL');
|
||||||
|
console.error('Please update your .env file with:');
|
||||||
|
console.error('DATABASE_PROVIDER=postgresql');
|
||||||
|
console.error('DATABASE_URL="postgresql://username:password@localhost:5432/euchre_camp"');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to connect using psql
|
||||||
|
execSync(`psql "${dbUrl}" -c "SELECT 1"`, { stdio: 'pipe' });
|
||||||
|
console.log('✅ PostgreSQL connection successful');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ PostgreSQL connection failed:', error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDatabase() {
|
||||||
|
try {
|
||||||
|
const dbUrl = process.env.DATABASE_URL;
|
||||||
|
const dbName = dbUrl.split('/').pop();
|
||||||
|
|
||||||
|
// Create database if it doesn't exist
|
||||||
|
execSync(`psql "${dbUrl}" -c "CREATE DATABASE ${dbName};" 2>/dev/null || true`, { stdio: 'pipe' });
|
||||||
|
console.log(`✅ Database "${dbName}" ready`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to create database:', error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runMigrations() {
|
||||||
|
try {
|
||||||
|
console.log('🔄 Running migrations...');
|
||||||
|
execSync('npx prisma migrate deploy', { stdio: 'inherit' });
|
||||||
|
console.log('✅ Migrations completed successfully');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Migration failed:', error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePrismaClient() {
|
||||||
|
try {
|
||||||
|
console.log('🔄 Generating Prisma client...');
|
||||||
|
execSync('npx prisma generate', { stdio: 'inherit' });
|
||||||
|
console.log('✅ Prisma client generated successfully');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Prisma client generation failed:', error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
console.log('=== PostgreSQL Setup for EuchreCamp ===\n');
|
||||||
|
|
||||||
|
// Check if DATABASE_URL is set
|
||||||
|
if (!process.env.DATABASE_URL) {
|
||||||
|
console.error('❌ DATABASE_URL environment variable is not set');
|
||||||
|
console.error('Please add it to your .env file');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check PostgreSQL connection
|
||||||
|
if (!checkPostgresConnection()) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create database
|
||||||
|
if (!createDatabase()) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Prisma client
|
||||||
|
if (!generatePrismaClient()) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run migrations
|
||||||
|
if (!runMigrations()) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ PostgreSQL setup completed successfully!');
|
||||||
|
console.log('\nNext steps:');
|
||||||
|
console.log('1. Start your development server: npm run dev');
|
||||||
|
console.log('2. Test the connection by visiting http://localhost:3000');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run if called directly
|
||||||
|
if (require.main === module) {
|
||||||
|
main();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { checkPostgresConnection, createDatabase, runMigrations, generatePrismaClient };
|
||||||
Executable
+80
@@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Script to switch between SQLite and PostgreSQL databases
|
||||||
|
* Usage: node scripts/switch-database.js [sqlite|postgres]
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const envFile = '.env';
|
||||||
|
const envExampleFile = '.env.example';
|
||||||
|
|
||||||
|
function updateEnvFile(provider) {
|
||||||
|
const envPath = path.join(process.cwd(), envFile);
|
||||||
|
|
||||||
|
// Read current .env file or create from example
|
||||||
|
let envContent = '';
|
||||||
|
if (fs.existsSync(envPath)) {
|
||||||
|
envContent = fs.readFileSync(envPath, 'utf8');
|
||||||
|
} else if (fs.existsSync(envExampleFile)) {
|
||||||
|
envContent = fs.readFileSync(envExampleFile, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update DATABASE_PROVIDER
|
||||||
|
const providerRegex = /^DATABASE_PROVIDER=.*$/m;
|
||||||
|
if (providerRegex.test(envContent)) {
|
||||||
|
envContent = envContent.replace(providerRegex, `DATABASE_PROVIDER=${provider}`);
|
||||||
|
} else {
|
||||||
|
envContent += `\nDATABASE_PROVIDER=${provider}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update DATABASE_URL based on provider
|
||||||
|
if (provider === 'sqlite') {
|
||||||
|
const sqliteUrl = 'DATABASE_URL="file:./prisma/dev.db"';
|
||||||
|
const urlRegex = /^DATABASE_URL=.*$/m;
|
||||||
|
if (urlRegex.test(envContent)) {
|
||||||
|
envContent = envContent.replace(urlRegex, sqliteUrl);
|
||||||
|
} else {
|
||||||
|
envContent += `${sqliteUrl}\n`;
|
||||||
|
}
|
||||||
|
} else if (provider === 'postgresql') {
|
||||||
|
const pgUrl = 'DATABASE_URL="postgresql://username:password@localhost:5432/euchre_camp"';
|
||||||
|
const urlRegex = /^DATABASE_URL=.*$/m;
|
||||||
|
if (urlRegex.test(envContent)) {
|
||||||
|
envContent = envContent.replace(urlRegex, pgUrl);
|
||||||
|
} else {
|
||||||
|
envContent += `${pgUrl}\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write updated content
|
||||||
|
fs.writeFileSync(envPath, envContent);
|
||||||
|
console.log(`✅ Updated ${envFile} to use ${provider} database`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const provider = args[0];
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
console.error('❌ Usage: node scripts/switch-database.js [sqlite|postgres]');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!['sqlite', 'postgres', 'postgresql'].includes(provider)) {
|
||||||
|
console.error(`❌ Invalid provider: ${provider}. Must be 'sqlite' or 'postgres'`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedProvider = provider === 'postgres' ? 'postgresql' : provider;
|
||||||
|
updateEnvFile(normalizedProvider);
|
||||||
|
|
||||||
|
console.log('\nNext steps:');
|
||||||
|
console.log('1. Run: npx prisma generate');
|
||||||
|
console.log('2. Run: npx prisma migrate deploy');
|
||||||
|
console.log('3. Restart your development server');
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
+4
-1
@@ -3,9 +3,12 @@ import { prismaAdapter } from "better-auth/adapters/prisma";
|
|||||||
import { prisma } from "./prisma";
|
import { prisma } from "./prisma";
|
||||||
import { testUtils } from "better-auth/plugins";
|
import { testUtils } from "better-auth/plugins";
|
||||||
|
|
||||||
|
// Detect database provider from environment
|
||||||
|
const databaseProvider = process.env.DATABASE_PROVIDER || "sqlite";
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
database: prismaAdapter(prisma, {
|
database: prismaAdapter(prisma, {
|
||||||
provider: "sqlite",
|
provider: databaseProvider as "sqlite" | "postgresql",
|
||||||
}),
|
}),
|
||||||
emailAndPassword: {
|
emailAndPassword: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user