Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ bun.lockb
bunfig.toml
fly*
node_modules
**/node_modules
package.json
README.md
supabase
target
yarn.lock
.git
**/dist
**/.next
**/.turbo
10 changes: 0 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,6 @@ jobs:
- name: Client checks OK
run: exit 0

typos:
needs: changed-files
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: crate-ci/[email protected]
with:
config: apps/freedit/.typos.toml
files: ${{ needs.changed-files.outputs.all_changed_files }}

_server:
needs: changed-files
if: needs.changed-files.outputs.server_any_changed == 'true'
Expand Down
5 changes: 5 additions & 0 deletions .yarnrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
--ignore-optional true
--install.ignore-optional true
--install.prefer-offline true
--install.no-bin-links true
network-timeout 1000000
73 changes: 72 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,75 @@
# PSE Forum
# PSE Forums

## Supabase Self-Hosted Setup

This project includes a self-hosted Supabase setup that provides:
- PostgreSQL database with custom extensions
- REST API via PostgREST
- Storage API
- Authentication
- Supabase Studio UI

## Configuration

All configuration is managed through a single `.env` file in the project root. This file contains all the environment variables needed for both the main application and the Supabase services.

### Environment Variables

The key environment variables include:
- `SUPABASE_ANON_KEY` - Anonymous API key for client-side authentication
- `SUPABASE_SERVICE_KEY` - Service role API key for server-side operations
- `JWT_SECRET` - Secret used for JWT authentication
- `DASHBOARD_USERNAME` and `DASHBOARD_PASSWORD` - Credentials for accessing Supabase Studio

## Running the Application

To start the entire application with Supabase services:

```bash
docker compose -f docker-compose.combined.yml --env-file .env up -d
```

This will start:
1. The PostgreSQL database
2. Supabase services (REST API, Storage, Meta, Studio)
3. The application API
4. The application client

### Troubleshooting

If you encounter a network error like:
```
network pse-forum-network was found but has incorrect label com.docker.compose.network
```

Run the following commands to fix it:
```bash
# Stop all containers
docker compose down --remove-orphans

# Remove the existing network
docker network rm pse-forum-network

# Start again
docker compose -f docker-compose.combined.yml --env-file .env up -d
```

## Accessing Supabase

### Supabase Studio
- URL: http://localhost:8000
- Username: `supabase` (or the value of `DASHBOARD_USERNAME` in .env)
- Password: `this_password_is_insecure_and_should_be_updated` (or the value of `DASHBOARD_PASSWORD` in .env)

### REST API
- Base URL: http://localhost:8000/rest/v1
- Authentication: Add header `apikey: [SUPABASE_ANON_KEY]`

## Development

When developing, you can access:
- Client application: http://localhost:5173
- API: http://localhost:3001

## Client

Expand Down
34 changes: 34 additions & 0 deletions apps/api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Dependencies
node_modules/
yarn.lock
package-lock.json

# Build output
dist/
build/

# Environment variables
.env
.env.local
.env.*.local

# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# IDE and editor files
.idea/
.vscode/
*.swp
*.swo
.DS_Store
Thumbs.db

# TypeScript cache
*.tsbuildinfo

# Test coverage
coverage/
25 changes: 25 additions & 0 deletions apps/api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM node:20-alpine

WORKDIR /workspace/apps/api

# Install dependencies
RUN apk add --no-cache bash postgresql-client

# Copy package files
COPY package*.json ./

# Make entrypoint executable
COPY entrypoint.sh ./
RUN chmod +x ./entrypoint.sh

# Install dependencies with increased timeout
RUN npm install --network-timeout 600000

# Use entrypoint script
ENTRYPOINT ["./entrypoint.sh"]

COPY . .

EXPOSE 3001

CMD ["npm", "run", "dev"]
53 changes: 53 additions & 0 deletions apps/api/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/bin/sh
set -e

echo "Starting API server setup..."

# Install dependencies for shared package
echo "Setting up shared package..."
cd ../shared
npm install --no-fund --no-audit
npm run build || echo "Build for shared package failed, but continuing..."

# Go back to API directory and install dependencies
echo "Setting up API package..."
cd ../api
npm install --no-fund --no-audit

# Display environment info
echo "Environment variables:"
echo "NODE_ENV: $NODE_ENV"
echo "DATABASE_URL: ${DATABASE_URL}"
echo "PORT: $PORT"

# Wait for PostgreSQL to be ready
echo "Waiting for PostgreSQL to be ready..."
MAX_RETRIES=120 # Increased max retries
RETRY_COUNT=0
until pg_isready -h postgres -p 5432 -U postgres; do
echo "PostgreSQL is unavailable - sleeping (retry $RETRY_COUNT/$MAX_RETRIES)"
RETRY_COUNT=$((RETRY_COUNT+1))
if [ $RETRY_COUNT -ge $MAX_RETRIES ]; then
echo "Failed to connect to PostgreSQL after $MAX_RETRIES attempts"
exit 1
fi
sleep 2 # Increased sleep time
done
echo "PostgreSQL is up - executing migrations"

# Ensure the database exists
echo "Ensuring database exists..."
echo "select 'database exists' from pg_database where datname = 'pse_forum'" | psql "$DATABASE_URL" || createdb -h postgres -U postgres pse_forum || echo "Database might already exist, continuing..."

# Run database migrations and seed
echo "Running migrations..."
npm run db:migrate || { echo "Migration failed, but continuing..."; }
echo "Migrations completed"

echo "Seeding database..."
npm run db:seed || { echo "Seeding failed, but continuing..."; }
echo "Database seeding completed"

# Start the API server
echo "Starting API server..."
exec npm run dev
33 changes: 33 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "api",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "ts-node-dev --respawn --transpile-only -r tsconfig-paths/register src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"db:migrate": "ts-node -r tsconfig-paths/register src/db/migrate.ts",
"db:seed": "ts-node -r tsconfig-paths/register src/db/seed.ts",
"db:reset": "ts-node -r tsconfig-paths/register src/db/migrate.ts && ts-node -r tsconfig-paths/register src/db/seed.ts",
"db:setup": "cd ../server && npm run migrate && cd ../api && npm run seed"
},
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.18.2",
"module-alias": "^2.2.3",
"pg": "^8.11.3",
"uuid": "^11.1.0",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/node": "^20.11.19",
"@types/pg": "^8.11.2",
"@types/uuid": "^10.0.0",
"ts-node-dev": "^2.0.0",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.3.3"
}
}
93 changes: 93 additions & 0 deletions apps/api/src/config/database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Pool } from 'pg';
import dotenv from 'dotenv';
import path from 'path';
import fs from 'fs';

// Load environment variables from appropriate files
const loadEnv = () => {
// Check if we're in development
const NODE_ENV = process.env.NODE_ENV || 'development';

// Try to load .env.${NODE_ENV}.local first
const localEnvPath = path.resolve(process.cwd(), `.env.${NODE_ENV}.local`);
if (fs.existsSync(localEnvPath)) {
console.log(`Loading environment from ${localEnvPath}`);
dotenv.config({ path: localEnvPath });
} else {
// Fall back to .env
const defaultEnvPath = path.resolve(process.cwd(), '.env');
if (fs.existsSync(defaultEnvPath)) {
console.log(`Loading environment from ${defaultEnvPath}`);
dotenv.config({ path: defaultEnvPath });
} else {
console.warn('No .env file found');
}
}
};

// Load environment variables
loadEnv();

// Default to specific values if environment variables aren't set
const dbConfig = {
user: 'postgres',
password: 'postgres',
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5433', 10),
database: process.env.DB_NAME || 'pse_forum',
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
};

// Print database configuration for debugging (with password redacted)
console.log('Database config (redacted password):', {
...dbConfig,
password: '******',
});

// Create a PostgreSQL connection pool
export const pool = new Pool(
process.env.DATABASE_URL
? {
connectionString: process.env.DATABASE_URL,
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
}
: dbConfig
);

// Query helper function
export async function query(text: string, params?: any[]) {
const start = Date.now();
let client;

try {
client = await pool.connect();
const result = await client.query(text, params);
const duration = Date.now() - start;

if (duration > 100) {
// Only log relevant information to avoid circular references
console.log('Long query:', {
text,
duration,
rowCount: result.rowCount,
params: params ? '[provided]' : '[none]'
});
}

return result;
} catch (error) {
const duration = Date.now() - start;
// Safely log error without possible circular references
console.error('Query error:', {
text,
duration,
error: error instanceof Error ? error.message : 'Unknown error',
params: params ? '[provided]' : '[none]'
});
throw error;
} finally {
if (client) {
client.release();
}
}
}
Loading
Loading