From 225b9d41f5b0fdf43b464973d29ba1d93786283f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Wed, 4 Mar 2026 16:36:49 +0100 Subject: [PATCH] chore: clean up repo and fix docker compose configuration - Remove outdated docs (COMPOSE.md, DOCKER.md, QUICKSTART.md, REBUILD_GUIDE.md) - Remove build.sh, compose.production.yml, gamification-schema.sql, directus.yaml - Simplify compose.yml for local dev (remove env var indirection) - Add directus.yml schema snapshot and schema.sql from VPS - Add schema:export and schema:import scripts to package.json - Ignore .env files (vars set via compose environment) Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 3 + COMPOSE.md | 424 ------ DOCKER.md | 374 ----- QUICKSTART.md | 330 ---- REBUILD_GUIDE.md | 262 ---- build.sh | 130 -- compose.production.yml | 128 -- compose.yml | 173 +-- directus.yaml => directus.yml | 705 +-------- gamification-schema.sql | 177 --- package.json | 13 +- packages/frontend/.env | 4 - schema.sql | 2667 +++++++++++++++++++++++++++++++++ 13 files changed, 2717 insertions(+), 2673 deletions(-) delete mode 100644 COMPOSE.md delete mode 100644 DOCKER.md delete mode 100644 QUICKSTART.md delete mode 100644 REBUILD_GUIDE.md delete mode 100755 build.sh delete mode 100644 compose.production.yml rename directus.yaml => directus.yml (75%) delete mode 100644 gamification-schema.sql delete mode 100644 packages/frontend/.env create mode 100644 schema.sql diff --git a/.gitignore b/.gitignore index 51161a3..ff5cf20 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,8 @@ dist/ target/ pkg/ +.env .env.* + +.claude/ diff --git a/COMPOSE.md b/COMPOSE.md deleted file mode 100644 index e221852..0000000 --- a/COMPOSE.md +++ /dev/null @@ -1,424 +0,0 @@ -# Docker Compose Guide - -This guide explains the Docker Compose setup for sexy.pivoine.art with local development and production configurations. - -## Architecture Overview - -The application uses a **multi-file compose setup** with two configurations: - -1. **`compose.yml`** - Base configuration for local development -2. **`compose.production.yml`** - Production overrides with Traefik integration - -### Service Architecture - -``` -┌─────────────────────────────────────────────────────────────┐ -│ 🌐 Traefik Reverse Proxy (Production Only) │ -│ ├─ HTTPS Termination │ -│ ├─ Automatic Let's Encrypt │ -│ └─ Routes traffic to frontend & Directus API │ -├─────────────────────────────────────────────────────────────┤ -│ 💄 Frontend (SvelteKit) │ -│ ├─ Port 3000 (internal) │ -│ ├─ Serves on https://sexy.pivoine.art │ -│ └─ Proxies /api to Directus │ -├─────────────────────────────────────────────────────────────┤ -│ 🎭 Directus CMS │ -│ ├─ Port 8055 (internal) │ -│ ├─ Serves on https://sexy.pivoine.art/api │ -│ ├─ Custom bundle extensions mounted │ -│ └─ Uploads volume │ -├─────────────────────────────────────────────────────────────┤ -│ 🗄️ PostgreSQL (Local) / External (Production) │ -│ └─ Database for Directus │ -├─────────────────────────────────────────────────────────────┤ -│ 💾 Redis (Local) / External (Production) │ -│ └─ Cache & session storage │ -└─────────────────────────────────────────────────────────────┘ -``` - -## Local Development Setup - -### Prerequisites - -- Docker 20.10+ -- Docker Compose 2.0+ - -### Quick Start - -1. **Create environment file:** - -```bash -cp .env.example .env -# Edit .env with your local settings (defaults work fine) -``` - -2. **Start all services:** - -```bash -docker-compose up -d -``` - -3. **Access services:** - - Frontend: http://localhost:3000 (if enabled) - - Directus: http://localhost:8055 - - Directus Admin: http://localhost:8055/admin - -4. **View logs:** - -```bash -docker-compose logs -f -``` - -5. **Stop services:** - -```bash -docker-compose down -``` - -### Local Services - -#### PostgreSQL -- **Image:** `postgres:16-alpine` -- **Port:** 5432 (internal only) -- **Volume:** `postgres-data` -- **Database:** `sexy` - -#### Redis -- **Image:** `redis:7-alpine` -- **Port:** 6379 (internal only) -- **Volume:** `redis-data` -- **Persistence:** AOF enabled - -#### Directus -- **Image:** `directus/directus:11` -- **Port:** 8055 (exposed) -- **Volumes:** - - `directus-uploads` - File uploads - - `./packages/bundle/dist` - Custom extensions -- **Features:** - - Auto-reload extensions - - WebSockets enabled - - CORS enabled for localhost - -### Local Development Workflow - -```bash -# Start infrastructure (Postgres, Redis, Directus) -docker-compose up -d - -# Develop frontend locally with hot reload -cd packages/frontend -pnpm dev - -# Build Directus bundle -pnpm --filter @sexy.pivoine.art/bundle build - -# Restart Directus to load new bundle -docker-compose restart directus -``` - -## Production Deployment - -### Prerequisites - -- External PostgreSQL database -- External Redis instance -- Traefik reverse proxy configured -- External network: `compose_network` - -### Setup - -The production compose file now uses the `include` directive to automatically extend `compose.yml`, making deployment simpler. - -1. **Create production environment file:** - -```bash -cp .env.production.example .env.production -``` - -2. **Edit `.env.production` with your values:** - -```bash -# Database (external) -CORE_DB_HOST=your-postgres-host -SEXY_DB_NAME=sexy_production -DB_USER=sexy -DB_PASSWORD=your-secure-password - -# Redis (external) -CORE_REDIS_HOST=your-redis-host - -# Directus -SEXY_DIRECTUS_SECRET=your-32-char-random-secret -ADMIN_PASSWORD=your-secure-admin-password - -# Traefik -SEXY_TRAEFIK_HOST=sexy.pivoine.art - -# Frontend -PUBLIC_API_URL=https://sexy.pivoine.art/api -PUBLIC_URL=https://sexy.pivoine.art - -# Email (SMTP) -EMAIL_SMTP_HOST=smtp.your-provider.com -EMAIL_SMTP_USER=your-email@domain.com -EMAIL_SMTP_PASSWORD=your-smtp-password -``` - -3. **Deploy:** - -```bash -# Simple deployment - compose.production.yml includes compose.yml automatically -docker-compose -f compose.production.yml --env-file .env.production up -d - -# Or use the traditional multi-file approach (same result) -docker-compose -f compose.yml -f compose.production.yml --env-file .env.production up -d -``` - -### Production Services - -#### Directus -- **Image:** `directus/directus:11` (configurable) -- **Network:** `compose_network` (external) -- **Volumes:** - - `/var/www/sexy.pivoine.art/uploads` - Persistent uploads - - `/var/www/sexy.pivoine.art/packages/bundle/dist` - Extensions -- **Traefik routing:** - - Domain: `sexy.pivoine.art/api` - - Strips `/api` prefix before forwarding - - HTTPS with auto-certificates - -#### Frontend -- **Image:** `ghcr.io/valknarxxx/sexy:latest` (from GHCR) -- **Network:** `compose_network` (external) -- **Volume:** `/var/www/sexy.pivoine.art` - Application code -- **Traefik routing:** - - Domain: `sexy.pivoine.art` - - HTTPS with auto-certificates - -### Traefik Integration - -Both services are configured with Traefik labels for automatic routing: - -**Frontend:** -- HTTP → HTTPS redirect -- Routes `sexy.pivoine.art` to port 3000 -- Gzip compression enabled - -**Directus API:** -- HTTP → HTTPS redirect -- Routes `sexy.pivoine.art/api` to port 8055 -- Strips `/api` prefix -- Gzip compression enabled - -### Production Commands - -```bash -# Deploy/update (simplified - uses include) -docker-compose -f compose.production.yml --env-file .env.production up -d - -# View logs -docker-compose -f compose.production.yml logs -f - -# Restart specific service -docker-compose -f compose.production.yml restart frontend - -# Stop all services -docker-compose -f compose.production.yml down - -# Update images -docker-compose -f compose.production.yml pull -docker-compose -f compose.production.yml up -d -``` - -## Environment Variables - -### Local Development (`.env`) - -| Variable | Default | Description | -|----------|---------|-------------| -| `DB_DATABASE` | `sexy` | Database name | -| `DB_USER` | `sexy` | Database user | -| `DB_PASSWORD` | `sexy` | Database password | -| `DIRECTUS_SECRET` | - | Secret for Directus (min 32 chars) | -| `ADMIN_EMAIL` | `admin@sexy.pivoine.art` | Admin email | -| `ADMIN_PASSWORD` | `admin` | Admin password | -| `CORS_ORIGIN` | `http://localhost:3000` | CORS allowed origins | - -See `.env.example` for full list. - -### Production (`.env.production`) - -| Variable | Description | Required | -|----------|-------------|----------| -| `CORE_DB_HOST` | External PostgreSQL host | ✅ | -| `SEXY_DB_NAME` | Database name | ✅ | -| `DB_PASSWORD` | Database password | ✅ | -| `CORE_REDIS_HOST` | External Redis host | ✅ | -| `SEXY_DIRECTUS_SECRET` | Directus secret key | ✅ | -| `SEXY_TRAEFIK_HOST` | Domain name | ✅ | -| `EMAIL_SMTP_HOST` | SMTP server | ✅ | -| `EMAIL_SMTP_PASSWORD` | SMTP password | ✅ | -| `SEXY_FRONTEND_PUBLIC_API_URL` | Frontend API URL | ✅ | -| `SEXY_FRONTEND_PUBLIC_URL` | Frontend public URL | ✅ | - -See `.env.production.example` for full list. - -**Note:** All frontend-specific variables are prefixed with `SEXY_FRONTEND_` for clarity. - -## Volumes - -### Local Development - -- `postgres-data` - PostgreSQL database -- `redis-data` - Redis persistence -- `directus-uploads` - Uploaded files - -### Production - -- `/var/www/sexy.pivoine.art/uploads` - Directus uploads -- `/var/www/sexy.pivoine.art` - Application code (frontend) - -## Networks - -### Local: `sexy-network` -- Bridge network -- Internal communication only -- Directus exposed on 8055 - -### Production: `compose_network` -- External network (pre-existing) -- Connects to Traefik -- No exposed ports (Traefik handles routing) - -## Health Checks - -All services include health checks: - -**PostgreSQL:** -- Command: `pg_isready` -- Interval: 10s - -**Redis:** -- Command: `redis-cli ping` -- Interval: 10s - -**Directus:** -- Endpoint: `/server/health` -- Interval: 30s - -**Frontend:** -- HTTP GET: `localhost:3000` -- Interval: 30s - -## Troubleshooting - -### Local Development - -**Problem:** Directus won't start - -```bash -# Check logs -docker-compose logs directus - -# Common issues: -# 1. Database not ready - wait for postgres to be healthy -# 2. Wrong secret - check DIRECTUS_SECRET is at least 32 chars -``` - -**Problem:** Can't connect to database - -```bash -# Check if postgres is running -docker-compose ps postgres - -# Verify health -docker-compose exec postgres pg_isready -U sexy -``` - -**Problem:** Extensions not loading - -```bash -# Rebuild bundle -pnpm --filter @sexy.pivoine.art/bundle build - -# Verify volume mount -docker-compose exec directus ls -la /directus/extensions/ - -# Restart Directus -docker-compose restart directus -``` - -### Production - -**Problem:** Services not accessible via domain - -```bash -# Check Traefik labels -docker inspect sexy_frontend | grep traefik - -# Verify compose_network exists -docker network ls | grep compose_network - -# Check Traefik is running -docker ps | grep traefik -``` - -**Problem:** Can't connect to external database - -```bash -# Test connection from Directus container -docker-compose exec directus sh -apk add postgresql-client -psql -h $CORE_DB_HOST -U $DB_USER -d $SEXY_DB_NAME -``` - -**Problem:** Frontend can't reach Directus API - -```bash -# Check Directus is accessible -curl https://sexy.pivoine.art/api/server/health - -# Verify CORS settings -# PUBLIC_API_URL should match the public Directus URL -``` - -## Migration from Old Setup - -If migrating from `docker-compose.production.yml`: - -1. **Rename environment variables** according to `.env.production.example` -2. **Update command** to use both compose files -3. **Verify Traefik labels** match your setup -4. **Test** with `docker-compose config` to see merged configuration - -```bash -# Validate configuration -docker-compose -f compose.yml -f compose.production.yml --env-file .env.production config - -# Deploy -docker-compose -f compose.yml -f compose.production.yml --env-file .env.production up -d -``` - -## Best Practices - -### Local Development -1. Use default credentials (they're fine for local) -2. Keep `EXTENSIONS_AUTO_RELOAD=true` for quick iteration -3. Run frontend via `pnpm dev` for hot reload -4. Restart Directus after bundle changes - -### Production -1. Use strong passwords for database and admin -2. Set `EXTENSIONS_AUTO_RELOAD=false` for stability -3. Use GHCR images for frontend -4. Enable Gzip compression via Traefik -5. Monitor logs regularly -6. Keep backups of uploads and database - -## See Also - -- [DOCKER.md](DOCKER.md) - Docker image documentation -- [QUICKSTART.md](QUICKSTART.md) - Quick start guide -- [CLAUDE.md](CLAUDE.md) - Development guide diff --git a/DOCKER.md b/DOCKER.md deleted file mode 100644 index 13563d9..0000000 --- a/DOCKER.md +++ /dev/null @@ -1,374 +0,0 @@ -# Docker Deployment Guide - -This guide covers building and deploying sexy.pivoine.art using Docker. - -## Overview - -The Dockerfile uses a multi-stage build process: - -1. **Base stage**: Sets up Node.js and pnpm -2. **Builder stage**: Installs Rust, compiles WASM, builds all packages -3. **Runner stage**: Minimal production image with only runtime dependencies - -## Prerequisites - -- Docker 20.10+ with BuildKit support -- Docker Compose 2.0+ (optional, for orchestration) - -## Building the Image - -### Basic Build - -```bash -docker build -t sexy.pivoine.art:latest . -``` - -### Build with Build Arguments - -```bash -docker build \ - --build-arg NODE_ENV=production \ - -t sexy.pivoine.art:latest \ - . -``` - -### Multi-platform Build (for ARM64 and AMD64) - -```bash -docker buildx build \ - --platform linux/amd64,linux/arm64 \ - -t sexy.pivoine.art:latest \ - --push \ - . -``` - -## Running the Container - -### Run with Environment Variables - -```bash -docker run -d \ - --name sexy-pivoine-frontend \ - -p 3000:3000 \ - -e PUBLIC_API_URL=https://api.pivoine.art \ - -e PUBLIC_URL=https://sexy.pivoine.art \ - -e PUBLIC_UMAMI_ID=your-umami-id \ - -e PUBLIC_UMAMI_SCRIPT=https://umami.pivoine.art/script.js \ - sexy.pivoine.art:latest -``` - -### Run with Environment File - -```bash -# Create .env.production from template -cp .env.production.example .env.production - -# Edit .env.production with your values -nano .env.production - -# Run container -docker run -d \ - --name sexy-pivoine-frontend \ - -p 3000:3000 \ - --env-file .env.production \ - sexy.pivoine.art:latest -``` - -## Docker Compose Deployment - -### Using docker-compose.production.yml - -```bash -# 1. Create environment file -cp .env.production.example .env.production - -# 2. Edit environment variables -nano .env.production - -# 3. Build and start -docker-compose -f docker-compose.production.yml up -d --build - -# 4. View logs -docker-compose -f docker-compose.production.yml logs -f frontend - -# 5. Stop services -docker-compose -f docker-compose.production.yml down -``` - -### Scale the Application - -```bash -docker-compose -f docker-compose.production.yml up -d --scale frontend=3 -``` - -## Environment Variables Reference - -### Required Variables - -| Variable | Description | Example | -|----------|-------------|---------| -| `PUBLIC_API_URL` | Directus API backend URL | `https://api.pivoine.art` | -| `PUBLIC_URL` | Frontend application URL | `https://sexy.pivoine.art` | - -### Optional Variables - -| Variable | Description | Example | -|----------|-------------|---------| -| `PUBLIC_UMAMI_ID` | Umami analytics tracking ID | `abc123def-456` | -| `PUBLIC_UMAMI_SCRIPT` | Umami script URL | `https://umami.pivoine.art/script.js` | -| `PORT` | Application port (inside container) | `3000` | -| `HOST` | Host binding | `0.0.0.0` | -| `NODE_ENV` | Node environment | `production` | - -## Health Checks - -The container includes a built-in health check that pings the HTTP server every 30 seconds: - -```bash -# Check container health -docker inspect --format='{{.State.Health.Status}}' sexy-pivoine-frontend - -# View health check logs -docker inspect --format='{{json .State.Health}}' sexy-pivoine-frontend | jq -``` - -## Logs and Debugging - -### View Container Logs - -```bash -# Follow logs -docker logs -f sexy-pivoine-frontend - -# Last 100 lines -docker logs --tail 100 sexy-pivoine-frontend - -# With timestamps -docker logs -f --timestamps sexy-pivoine-frontend -``` - -### Execute Commands in Running Container - -```bash -# Open shell -docker exec -it sexy-pivoine-frontend sh - -# Check Node.js version -docker exec sexy-pivoine-frontend node --version - -# Check environment variables -docker exec sexy-pivoine-frontend env -``` - -### Debug Build Issues - -```bash -# Build with no cache -docker build --no-cache -t sexy.pivoine.art:latest . - -# Build specific stage for debugging -docker build --target builder -t sexy.pivoine.art:builder . - -# Inspect builder stage -docker run -it --rm sexy.pivoine.art:builder sh -``` - -## Production Best Practices - -### 1. Use Specific Tags - -```bash -# Tag with version -docker build -t sexy.pivoine.art:1.0.0 . -docker tag sexy.pivoine.art:1.0.0 sexy.pivoine.art:latest -``` - -### 2. Image Scanning - -```bash -# Scan for vulnerabilities (requires Docker Scout or Trivy) -docker scout cves sexy.pivoine.art:latest - -# Or with Trivy -trivy image sexy.pivoine.art:latest -``` - -### 3. Resource Limits - -```bash -docker run -d \ - --name sexy-pivoine-frontend \ - -p 3000:3000 \ - --memory="2g" \ - --cpus="2" \ - --env-file .env.production \ - sexy.pivoine.art:latest -``` - -### 4. Restart Policies - -```bash -docker run -d \ - --name sexy-pivoine-frontend \ - --restart=unless-stopped \ - -p 3000:3000 \ - --env-file .env.production \ - sexy.pivoine.art:latest -``` - -### 5. Use Docker Secrets (Docker Swarm) - -```bash -# Create secrets -echo "your-db-password" | docker secret create db_password - - -# Deploy with secrets -docker service create \ - --name sexy-pivoine-frontend \ - --secret db_password \ - -p 3000:3000 \ - sexy.pivoine.art:latest -``` - -## Optimization Tips - -### Reduce Build Time - -1. **Use BuildKit cache mounts** (already enabled in Dockerfile) -2. **Leverage layer caching** - structure Dockerfile to cache dependencies -3. **Use `.dockerignore`** - exclude unnecessary files from build context - -### Reduce Image Size - -Current optimizations: -- Multi-stage build (builder artifacts not in final image) -- Production-only dependencies (`pnpm install --prod`) -- Minimal base image (`node:20.19.1-slim`) -- Only necessary build artifacts copied to runner - -Image size breakdown: -```bash -docker images sexy.pivoine.art:latest -``` - -## CI/CD Integration - -### GitHub Actions (Automated) - -This repository includes automated GitHub Actions workflows for building, scanning, and managing Docker images. - -**Pre-configured workflows:** -- **Build & Push** (`.github/workflows/docker-build-push.yml`) - - Automatically builds and pushes to `ghcr.io/valknarxxx/sexy` - - Triggers on push to main/develop, version tags, and PRs - - Multi-platform builds (AMD64 + ARM64) - - Smart tagging: latest, branch names, semver, commit SHAs - -- **Security Scan** (`.github/workflows/docker-scan.yml`) - - Daily vulnerability scans with Trivy - - Reports to GitHub Security tab - - Scans on every release - -- **Cleanup** (`.github/workflows/cleanup-images.yml`) - - Weekly cleanup of old untagged images - - Keeps last 10 versions - -**Using pre-built images:** - -```bash -# Pull latest from GitHub Container Registry -docker pull ghcr.io/valknarxxx/sexy:latest - -# Pull specific version -docker pull ghcr.io/valknarxxx/sexy:v1.0.0 - -# Run the image -docker run -d -p 3000:3000 --env-file .env.production ghcr.io/valknarxxx/sexy:latest -``` - -**Triggering builds:** - -```bash -# Push to main → builds 'latest' tag -git push origin main - -# Create version tag → builds semver tags -git tag v1.0.0 && git push origin v1.0.0 - -# Pull request → builds but doesn't push -``` - -See `.github/workflows/README.md` for detailed workflow documentation. - -## Troubleshooting - -### Build Fails at Rust Installation - -**Problem**: Rust installation fails or times out - -**Solution**: -- Check internet connectivity -- Use a Rust mirror if in restricted network -- Increase build timeout - -### WASM Build Fails - -**Problem**: `wasm-bindgen-cli` version mismatch - -**Solution**: -```dockerfile -# In Dockerfile, pin wasm-bindgen-cli version -RUN cargo install wasm-bindgen-cli --version 0.2.103 -``` - -### Container Exits Immediately - -**Problem**: Container starts then exits - -**Solution**: Check logs and verify: -```bash -docker logs sexy-pivoine-frontend - -# Verify build output exists -docker run -it --rm sexy.pivoine.art:latest ls -la packages/frontend/build -``` - -### Port Already in Use - -**Problem**: Port 3000 already bound - -**Solution**: -```bash -# Use different host port -docker run -d -p 8080:3000 sexy.pivoine.art:latest -``` - -## Maintenance - -### Clean Up - -```bash -# Remove stopped containers -docker container prune - -# Remove unused images -docker image prune -a - -# Remove build cache -docker builder prune - -# Complete cleanup (use with caution) -docker system prune -a --volumes -``` - -### Update Base Image - -Regularly update the base Node.js image: - -```bash -# Pull latest Node 20 LTS -docker pull node:20.19.1-slim - -# Rebuild -docker build --pull -t sexy.pivoine.art:latest . -``` diff --git a/QUICKSTART.md b/QUICKSTART.md deleted file mode 100644 index 9d3f657..0000000 --- a/QUICKSTART.md +++ /dev/null @@ -1,330 +0,0 @@ -# Quick Start Guide - -Get sexy.pivoine.art running in under 5 minutes using pre-built Docker images. - -## Prerequisites - -- Docker 20.10+ -- Docker Compose 2.0+ (optional) - -## Option 1: Docker Run (Fastest) - -### Step 1: Pull the Image - -```bash -docker pull ghcr.io/valknarxxx/sexy:latest -``` - -### Step 2: Create Environment File - -```bash -cat > .env.production << EOF -PUBLIC_API_URL=https://api.your-domain.com -PUBLIC_URL=https://your-domain.com -PUBLIC_UMAMI_ID= -PUBLIC_UMAMI_SCRIPT= -EOF -``` - -### Step 3: Run the Container - -```bash -docker run -d \ - --name sexy-pivoine \ - -p 3000:3000 \ - --env-file .env.production \ - --restart unless-stopped \ - ghcr.io/valknarxxx/sexy:latest -``` - -### Step 4: Verify - -```bash -# Check if running -docker ps | grep sexy-pivoine - -# Check logs -docker logs -f sexy-pivoine - -# Test the application -curl http://localhost:3000 -``` - -Your application is now running at `http://localhost:3000` 🎉 - -## Option 2: Docker Compose (Recommended) - -### Step 1: Download docker-compose.production.yml - -```bash -curl -O https://raw.githubusercontent.com/valknarxxx/sexy/main/docker-compose.production.yml -``` - -Or if you have the repository: - -```bash -cd /path/to/sexy.pivoine.art -``` - -### Step 2: Create Environment File - -```bash -cp .env.production.example .env.production -nano .env.production # Edit with your values -``` - -### Step 3: Start Services - -```bash -docker-compose -f docker-compose.production.yml up -d -``` - -### Step 4: Monitor - -```bash -# View logs -docker-compose -f docker-compose.production.yml logs -f - -# Check status -docker-compose -f docker-compose.production.yml ps -``` - -Your application is now running at `http://localhost:3000` 🎉 - -## Accessing Private Images - -If the image is in a private registry: - -### Step 1: Create GitHub Personal Access Token - -1. Go to https://github.com/settings/tokens -2. Click "Generate new token (classic)" -3. Select scope: `read:packages` -4. Generate and copy the token - -### Step 2: Login to GitHub Container Registry - -```bash -echo YOUR_GITHUB_TOKEN | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin -``` - -### Step 3: Pull and Run - -Now you can pull private images: - -```bash -docker pull ghcr.io/valknarxxx/sexy:latest -``` - -## Environment Variables - -### Required - -| Variable | Description | Example | -|----------|-------------|---------| -| `PUBLIC_API_URL` | Directus API endpoint | `https://api.pivoine.art` | -| `PUBLIC_URL` | Frontend URL | `https://sexy.pivoine.art` | - -### Optional - -| Variable | Description | Example | -|----------|-------------|---------| -| `PUBLIC_UMAMI_ID` | Analytics tracking ID | `abc-123-def` | -| `PUBLIC_UMAMI_SCRIPT` | Umami script URL | `https://umami.example.com/script.js` | - -## Common Commands - -### View Logs - -```bash -# Follow logs (Docker Run) -docker logs -f sexy-pivoine - -# Follow logs (Docker Compose) -docker-compose -f docker-compose.production.yml logs -f -``` - -### Restart Container - -```bash -# Docker Run -docker restart sexy-pivoine - -# Docker Compose -docker-compose -f docker-compose.production.yml restart -``` - -### Stop Container - -```bash -# Docker Run -docker stop sexy-pivoine - -# Docker Compose -docker-compose -f docker-compose.production.yml down -``` - -### Update to Latest Version - -```bash -# Docker Run -docker pull ghcr.io/valknarxxx/sexy:latest -docker stop sexy-pivoine -docker rm sexy-pivoine -# Then re-run the docker run command from Step 3 - -# Docker Compose -docker-compose -f docker-compose.production.yml pull -docker-compose -f docker-compose.production.yml up -d -``` - -### Shell Access - -```bash -# Docker Run -docker exec -it sexy-pivoine sh - -# Docker Compose -docker-compose -f docker-compose.production.yml exec frontend sh -``` - -## Available Image Tags - -| Tag | Description | Use Case | -|-----|-------------|----------| -| `latest` | Latest stable build from main | Production | -| `v1.0.0` | Specific version | Production (pinned) | -| `develop` | Latest from develop branch | Staging | -| `main-abc123` | Specific commit | Testing | - -**Best Practice:** Use version tags in production for predictable deployments. - -## Production Deployment - -### 1. Use Version Tags - -```bash -# Instead of :latest -docker pull ghcr.io/valknarxxx/sexy:v1.0.0 -``` - -### 2. Add Resource Limits - -```bash -docker run -d \ - --name sexy-pivoine \ - -p 3000:3000 \ - --env-file .env.production \ - --memory="2g" \ - --cpus="2" \ - --restart unless-stopped \ - ghcr.io/valknarxxx/sexy:v1.0.0 -``` - -### 3. Use a Reverse Proxy - -Example with nginx: - -```nginx -server { - listen 80; - server_name sexy.pivoine.art; - - location / { - proxy_pass http://localhost:3000; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - } -} -``` - -### 4. Enable HTTPS - -Use Certbot or similar: - -```bash -certbot --nginx -d sexy.pivoine.art -``` - -## Health Check - -The container includes a built-in health check: - -```bash -# Check container health -docker inspect --format='{{.State.Health.Status}}' sexy-pivoine -``` - -Possible statuses: -- `starting` - Container just started -- `healthy` - Application is responding -- `unhealthy` - Application is not responding - -## Troubleshooting - -### Container Exits Immediately - -```bash -# Check logs -docker logs sexy-pivoine - -# Common issues: -# - Missing environment variables -# - Port 3000 already in use -# - Invalid environment variable values -``` - -### Cannot Pull Image - -```bash -# For private images, ensure you're logged in -docker login ghcr.io - -# Check if image exists -docker pull ghcr.io/valknarxxx/sexy:latest -``` - -### Port Already in Use - -```bash -# Use a different port -docker run -d -p 8080:3000 ghcr.io/valknarxxx/sexy:latest - -# Or find what's using port 3000 -lsof -i :3000 -``` - -### Application Not Accessible - -```bash -# Check if container is running -docker ps | grep sexy-pivoine - -# Check logs -docker logs sexy-pivoine - -# Verify port mapping -docker port sexy-pivoine - -# Test from inside container -docker exec sexy-pivoine wget -O- http://localhost:3000 -``` - -## Next Steps - -- **Production setup:** See [DOCKER.md](DOCKER.md) -- **Development:** See [CLAUDE.md](CLAUDE.md) -- **CI/CD:** See [.github/workflows/README.md](.github/workflows/README.md) - -## Support - -- **Issues:** https://github.com/valknarxxx/sexy/issues -- **Discussions:** https://github.com/valknarxxx/sexy/discussions -- **Security:** Report privately via GitHub Security tab - -## License - -See [LICENSE](LICENSE) file for details. diff --git a/REBUILD_GUIDE.md b/REBUILD_GUIDE.md deleted file mode 100644 index a017c53..0000000 --- a/REBUILD_GUIDE.md +++ /dev/null @@ -1,262 +0,0 @@ -# 🔄 Rebuild Guide - When You Need to Rebuild the Image - -## Why Rebuild? - -SvelteKit's `PUBLIC_*` environment variables are **baked into the JavaScript** at build time. You need to rebuild when: - -1. ✅ Changing `PUBLIC_API_URL` -2. ✅ Changing `PUBLIC_URL` -3. ❌ NOT needed for `PUBLIC_UMAMI_ID` or `PUBLIC_UMAMI_SCRIPT` (those are runtime) -4. ❌ NOT needed for Directus env vars (those are runtime) - -## Quick Rebuild Process - -### 1. Update Frontend Environment Variables - -Edit the frontend `.env` file: - -```bash -nano packages/frontend/.env -``` - -Set your production values: -```bash -PUBLIC_API_URL=https://sexy.pivoine.art/api -PUBLIC_URL=https://sexy.pivoine.art -# Note: PUBLIC_UMAMI_* can also be set at runtime in docker-compose -PUBLIC_UMAMI_ID=your-umami-id -PUBLIC_UMAMI_SCRIPT=https://umami.pivoine.art/script.js -``` - -### 2. Rebuild the Image - -```bash -# From the project root -docker build -t ghcr.io/valknarxxx/sexy:latest -t sexy.pivoine.art:latest . -``` - -**Expected Time:** 30-45 minutes (first build), 10-15 minutes (cached rebuild) - -### 3. Restart Services - -```bash -# If using docker-compose -cd /home/valknar/Projects/docker-compose/sexy -docker compose down -docker compose up -d - -# Or directly -docker stop sexy_frontend -docker rm sexy_frontend -docker compose up -d frontend -``` - -## Monitoring the Build - -### Check Build Progress - -```bash -# Watch build output -docker build -t ghcr.io/valknarxxx/sexy:latest . - -# Build stages: -# 1. Base (~30s) - Node.js setup -# 2. Builder (~25-40min) - Rust + WASM + packages -# - Rust installation: ~2-3 min -# - wasm-bindgen-cli: ~10-15 min -# - WASM build: ~5-10 min -# - Package builds: ~5-10 min -# 3. Runner (~2min) - Final image assembly -``` - -### Verify Environment Variables in Built Image - -```bash -# Check what PUBLIC_API_URL is baked in -docker run --rm ghcr.io/valknarxxx/sexy:latest sh -c \ - "grep -r 'PUBLIC_API_URL' /home/node/app/packages/frontend/build/ | head -3" - -# Should show: https://sexy.pivoine.art/api -``` - -## Push to GitHub Container Registry - -After successful build: - -```bash -# Login to GHCR (first time only) -echo $GITHUB_TOKEN | docker login ghcr.io -u valknarxxx --password-stdin - -# Push the image -docker push ghcr.io/valknarxxx/sexy:latest -``` - -## Alternative: Build Arguments (Future Enhancement) - -To avoid rebuilding for every env change, consider adding build arguments: - -```dockerfile -# In Dockerfile, before building frontend: -ARG PUBLIC_API_URL=https://sexy.pivoine.art/api -ARG PUBLIC_URL=https://sexy.pivoine.art -ARG PUBLIC_UMAMI_ID= - -# Create .env.production dynamically -RUN echo "PUBLIC_API_URL=${PUBLIC_API_URL}" > packages/frontend/.env.production && \ - echo "PUBLIC_URL=${PUBLIC_URL}" >> packages/frontend/.env.production && \ - echo "PUBLIC_UMAMI_ID=${PUBLIC_UMAMI_ID}" >> packages/frontend/.env.production -``` - -Then build with: -```bash -docker build \ - --build-arg PUBLIC_API_URL=https://sexy.pivoine.art/api \ - --build-arg PUBLIC_URL=https://sexy.pivoine.art \ - -t ghcr.io/valknarxxx/sexy:latest . -``` - -## Troubleshooting - -### Build Fails at Rust Installation - -```bash -# Check network connectivity -ping -c 3 sh.rustup.rs - -# Build with verbose output -docker build --progress=plain -t ghcr.io/valknarxxx/sexy:latest . -``` - -### Build Fails at WASM - -```bash -# Check if wasm-bindgen-cli matches package.json version -docker run --rm rust:latest cargo install wasm-bindgen-cli --version 0.2.103 -``` - -### Frontend Still Shows Wrong URL - -```bash -# Verify .env file is correct -cat packages/frontend/.env - -# Check if old image is cached -docker images | grep sexy -docker rmi ghcr.io/valknarxxx/sexy:old-tag - -# Force rebuild without cache -docker build --no-cache -t ghcr.io/valknarxxx/sexy:latest . -``` - -### Container Starts But Can't Connect to API - -1. Check Traefik routing: -```bash -docker logs traefik | grep sexy -``` - -2. Check if Directus is accessible: -```bash -curl -I https://sexy.pivoine.art/api/server/health -``` - -3. Check frontend logs: -```bash -docker logs sexy_frontend -``` - -## Development vs Production - -### Development (Local) -- Use `pnpm dev` for hot reload -- No rebuild needed for code changes -- Env vars from `.env` or shell - -### Production (Docker) -- Rebuild required for PUBLIC_* changes -- Changes baked into JavaScript -- Env vars from `packages/frontend/.env` - -## Optimization Tips - -### Speed Up Rebuilds - -1. **Use BuildKit cache:** -```bash -export DOCKER_BUILDKIT=1 -docker build --build-arg BUILDKIT_INLINE_CACHE=1 -t ghcr.io/valknarxxx/sexy:latest . -``` - -2. **Multi-stage caching:** -- Dockerfile already optimized with multi-stage build -- Dependencies cached separately from code - -3. **Parallel builds:** -```bash -# Build with more CPU cores -docker build --cpus 4 -t ghcr.io/valknarxxx/sexy:latest . -``` - -### Reduce Image Size - -Current optimizations: -- ✅ Multi-stage build -- ✅ Production dependencies only -- ✅ Minimal base image -- ✅ No dev tools in final image - -Expected sizes: -- Base: ~100MB -- Builder: ~2-3GB (not shipped) -- Runner: ~300-500MB (final) - -## Automation - -### GitHub Actions (Already Set Up) - -The `.github/workflows/docker-build-push.yml` automatically: -1. Builds on push to main -2. Creates version tags -3. Pushes to GHCR -4. Caches layers for faster builds - -**Trigger a rebuild:** -```bash -git tag v1.0.1 -git push origin v1.0.1 -``` - -### Local Build Script - -Use the provided `build.sh`: -```bash -./build.sh -t v1.0.0 -p -``` - -## When NOT to Rebuild - -You DON'T need to rebuild for: -- ❌ Directus configuration changes -- ❌ Database credentials -- ❌ Redis settings -- ❌ SMTP settings -- ❌ Session cookie settings -- ❌ Traefik labels - -These are runtime environment variables and can be changed in docker-compose. - -## Summary - -| Change | Rebuild Needed | How to Apply | -|--------|----------------|--------------| -| `PUBLIC_API_URL` | ✅ Yes | Rebuild image | -| `PUBLIC_URL` | ✅ Yes | Rebuild image | -| `PUBLIC_UMAMI_*` | ❌ No | Restart container | -| `SEXY_DIRECTUS_*` | ❌ No | Restart container | -| `DB_*` | ❌ No | Restart container | -| `EMAIL_*` | ❌ No | Restart container | -| Traefik labels | ❌ No | Restart container | - ---- - -**Remember:** The key difference is **build-time** (compiled into JS) vs **runtime** (read from environment). diff --git a/build.sh b/build.sh deleted file mode 100755 index 58aebff..0000000 --- a/build.sh +++ /dev/null @@ -1,130 +0,0 @@ -#!/bin/bash -# Build script for sexy.pivoine.art Docker image - -set -e # Exit on error - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -# Default values -IMAGE_NAME="sexy.pivoine.art" -TAG="latest" -PUSH=false -PLATFORM="" - -# Parse arguments -while [[ $# -gt 0 ]]; do - case $1 in - -t|--tag) - TAG="$2" - shift 2 - ;; - -n|--name) - IMAGE_NAME="$2" - shift 2 - ;; - -p|--push) - PUSH=true - shift - ;; - --platform) - PLATFORM="$2" - shift 2 - ;; - -h|--help) - echo "Usage: $0 [OPTIONS]" - echo "" - echo "Options:" - echo " -t, --tag TAG Set image tag (default: latest)" - echo " -n, --name NAME Set image name (default: sexy.pivoine.art)" - echo " -p, --push Push image after build" - echo " --platform PLATFORM Build for specific platform (e.g., linux/amd64,linux/arm64)" - echo " -h, --help Show this help message" - echo "" - echo "Examples:" - echo " $0 # Build with defaults" - echo " $0 -t v1.0.0 # Build with version tag" - echo " $0 --platform linux/amd64,linux/arm64 -p # Multi-platform build and push" - exit 0 - ;; - *) - echo -e "${RED}Unknown option: $1${NC}" - exit 1 - ;; - esac -done - -FULL_IMAGE="${IMAGE_NAME}:${TAG}" - -echo -e "${GREEN}=== Building Docker Image ===${NC}" -echo "Image: ${FULL_IMAGE}" -echo "Platform: ${PLATFORM:-default}" -echo "" - -# Check if Docker is running -if ! docker info > /dev/null 2>&1; then - echo -e "${RED}Error: Docker is not running${NC}" - exit 1 -fi - -# Build command -BUILD_CMD="docker build" - -if [ -n "$PLATFORM" ]; then - # Multi-platform build requires buildx - echo -e "${YELLOW}Using buildx for multi-platform build${NC}" - BUILD_CMD="docker buildx build --platform ${PLATFORM}" - - if [ "$PUSH" = true ]; then - BUILD_CMD="${BUILD_CMD} --push" - fi -else - # Regular build - if [ "$PUSH" = true ]; then - echo -e "${YELLOW}Note: --push only works with multi-platform builds. Use 'docker push' after build.${NC}" - fi -fi - -# Execute build -echo -e "${GREEN}Building...${NC}" -$BUILD_CMD -t "${FULL_IMAGE}" . - -if [ $? -eq 0 ]; then - echo -e "${GREEN}✓ Build successful!${NC}" - echo "Image: ${FULL_IMAGE}" - - # Show image size - if [ -z "$PLATFORM" ]; then - SIZE=$(docker images "${FULL_IMAGE}" --format "{{.Size}}") - echo "Size: ${SIZE}" - fi - - # Push if requested and not multi-platform - if [ "$PUSH" = true ] && [ -z "$PLATFORM" ]; then - echo -e "${GREEN}Pushing image...${NC}" - docker push "${FULL_IMAGE}" - if [ $? -eq 0 ]; then - echo -e "${GREEN}✓ Push successful!${NC}" - else - echo -e "${RED}✗ Push failed${NC}" - exit 1 - fi - fi - - echo "" - echo -e "${GREEN}Next steps:${NC}" - echo "1. Run locally:" - echo " docker run -d -p 3000:3000 --env-file .env.production ${FULL_IMAGE}" - echo "" - echo "2. Run with docker-compose:" - echo " docker-compose -f docker-compose.production.yml up -d" - echo "" - echo "3. View logs:" - echo " docker logs -f " -else - echo -e "${RED}✗ Build failed${NC}" - exit 1 -fi diff --git a/compose.production.yml b/compose.production.yml deleted file mode 100644 index 7a393ba..0000000 --- a/compose.production.yml +++ /dev/null @@ -1,128 +0,0 @@ -include: - - compose.yml - -# Production compose file - extends base compose.yml -# Usage: docker-compose -f compose.production.yml up -d - -networks: - compose_network: - external: true - name: compose_network - -services: - # Disable local postgres for production (use external DB) - postgres: - deploy: - replicas: 0 - - # Disable local redis for production (use external Redis) - redis: - deploy: - replicas: 0 - - # Override Directus for production - directus: - networks: - - compose_network - ports: [] # Remove exposed ports, use Traefik instead - - # Override volumes for production paths - volumes: - - ${SEXY_DIRECTUS_UPLOADS:-./uploads}:/directus/uploads - - ${SEXY_DIRECTUS_BUNDLE:-./packages/bundle/dist}:/directus/extensions/sexy.pivoine.art - - # Override environment for production settings - environment: - # Database (external) - DB_HOST: ${CORE_DB_HOST} - DB_PORT: ${CORE_DB_PORT:-5432} - DB_DATABASE: ${SEXY_DB_NAME} - DB_USER: ${DB_USER} - DB_PASSWORD: ${DB_PASSWORD} - - # General - SECRET: ${SEXY_DIRECTUS_SECRET} - ADMIN_EMAIL: ${ADMIN_EMAIL} - ADMIN_PASSWORD: ${ADMIN_PASSWORD} - PUBLIC_URL: ${SEXY_PUBLIC_URL} - - # Cache (external Redis) - REDIS: redis://${CORE_REDIS_HOST}:${CORE_REDIS_PORT:-6379} - - # CORS - CORS_ORIGIN: ${SEXY_CORS_ORIGIN} - - # Security (production settings) - SESSION_COOKIE_SECURE: ${SEXY_SESSION_COOKIE_SECURE:-true} - SESSION_COOKIE_SAME_SITE: ${SEXY_SESSION_COOKIE_SAME_SITE:-strict} - SESSION_COOKIE_DOMAIN: ${SEXY_SESSION_COOKIE_DOMAIN} - - # Extensions - EXTENSIONS_AUTO_RELOAD: ${SEXY_EXTENSIONS_AUTO_RELOAD:-false} - - # Email (production SMTP) - EMAIL_TRANSPORT: ${EMAIL_TRANSPORT:-smtp} - EMAIL_FROM: ${EMAIL_FROM} - EMAIL_SMTP_HOST: ${EMAIL_SMTP_HOST} - EMAIL_SMTP_PORT: ${EMAIL_SMTP_PORT:-587} - EMAIL_SMTP_USER: ${EMAIL_SMTP_USER} - EMAIL_SMTP_PASSWORD: ${EMAIL_SMTP_PASSWORD} - - # User URLs - USER_REGISTER_URL_ALLOW_LIST: ${SEXY_USER_REGISTER_URL_ALLOW_LIST} - PASSWORD_RESET_URL_ALLOW_LIST: ${SEXY_PASSWORD_RESET_URL_ALLOW_LIST} - - # Remove local dependencies - depends_on: [] - - labels: - # Traefik labels for reverse proxy - - 'traefik.enable=${SEXY_TRAEFIK_ENABLED:-true}' - - 'traefik.http.middlewares.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-api-redirect-web-secure.redirectscheme.scheme=https' - - 'traefik.http.routers.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-api-web.middlewares=${SEXY_COMPOSE_PROJECT_NAME:-sexy}-api-redirect-web-secure' - - 'traefik.http.routers.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-api-web.rule=Host(`${SEXY_TRAEFIK_HOST}`) && PathPrefix(`/api`)' - - 'traefik.http.routers.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-api-web.entrypoints=web' - - 'traefik.http.routers.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-api-web-secure.rule=Host(`${SEXY_TRAEFIK_HOST}`) && PathPrefix(`/api`)' - - 'traefik.http.routers.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-api-web-secure.tls.certresolver=resolver' - - 'traefik.http.routers.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-api-web-secure.entrypoints=web-secure' - - 'traefik.http.middlewares.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-api-web-secure-compress.compress=true' - - 'traefik.http.middlewares.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-api-strip.stripprefix.prefixes=/api' - - 'traefik.http.routers.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-api-web-secure.middlewares=${SEXY_COMPOSE_PROJECT_NAME:-sexy}-api-strip,${SEXY_COMPOSE_PROJECT_NAME:-sexy}-api-web-secure-compress' - - 'traefik.http.services.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-api-web-secure.loadbalancer.server.port=8055' - - 'traefik.docker.network=compose_network' - - # Override Frontend for production - frontend: - networks: - - compose_network - ports: [] # Remove exposed ports, use Traefik instead - - # Override environment for production - environment: - NODE_ENV: production - PUBLIC_API_URL: ${SEXY_FRONTEND_PUBLIC_API_URL} - PUBLIC_URL: ${SEXY_FRONTEND_PUBLIC_URL} - PUBLIC_UMAMI_ID: ${SEXY_FRONTEND_PUBLIC_UMAMI_ID:-} - PUBLIC_UMAMI_SCRIPT: ${SEXY_FRONTEND_PUBLIC_UMAMI_SCRIPT:-} - - # Override volume for production path - volumes: - - ${SEXY_FRONTEND_PATH:-/var/www/sexy.pivoine.art}:/home/node/app - - # Remove local dependency - depends_on: [] - - labels: - # Traefik labels for reverse proxy - - 'traefik.enable=${SEXY_TRAEFIK_ENABLED:-true}' - - 'traefik.http.middlewares.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-frontend-redirect-web-secure.redirectscheme.scheme=https' - - 'traefik.http.routers.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-frontend-web.middlewares=${SEXY_COMPOSE_PROJECT_NAME:-sexy}-frontend-redirect-web-secure' - - 'traefik.http.routers.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-frontend-web.rule=Host(`${SEXY_TRAEFIK_HOST}`)' - - 'traefik.http.routers.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-frontend-web.entrypoints=web' - - 'traefik.http.routers.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-frontend-web-secure.rule=Host(`${SEXY_TRAEFIK_HOST}`)' - - 'traefik.http.routers.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-frontend-web-secure.tls.certresolver=resolver' - - 'traefik.http.routers.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-frontend-web-secure.entrypoints=web-secure' - - 'traefik.http.middlewares.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-frontend-web-secure-compress.compress=true' - - 'traefik.http.routers.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-frontend-web-secure.middlewares=${SEXY_COMPOSE_PROJECT_NAME:-sexy}-frontend-web-secure-compress' - - 'traefik.http.services.${SEXY_COMPOSE_PROJECT_NAME:-sexy}-frontend-web-secure.loadbalancer.server.port=3000' - - 'traefik.docker.network=compose_network' diff --git a/compose.yml b/compose.yml index c57d383..35206da 100644 --- a/compose.yml +++ b/compose.yml @@ -1,112 +1,71 @@ +name: sexy services: - # PostgreSQL Database (local only) postgres: image: postgres:16-alpine - container_name: ${SEXY_COMPOSE_PROJECT_NAME:-sexy}_postgres + container_name: sexy_postgres restart: unless-stopped - networks: - - sexy-network volumes: - - postgres-data:/var/lib/postgresql/data + - postgres_data:/var/lib/postgresql/data environment: - POSTGRES_DB: ${DB_DATABASE:-sexy} - POSTGRES_USER: ${DB_USER:-sexy} - POSTGRES_PASSWORD: ${DB_PASSWORD:-sexy} + POSTGRES_DB: sexy + POSTGRES_USER: sexy + POSTGRES_PASSWORD: sexy healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-sexy}"] + test: ["CMD-SHELL", "pg_isready -U sexy"] interval: 10s timeout: 5s retries: 5 - - # Redis Cache (local only) redis: image: redis:7-alpine - container_name: ${SEXY_COMPOSE_PROJECT_NAME:-sexy}_redis + container_name: sexy_redis restart: unless-stopped - networks: - - sexy-network volumes: - - redis-data:/data + - redis_data:/data command: redis-server --appendonly yes healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 - - # Directus CMS directus: - image: ${SEXY_DIRECTUS_IMAGE:-directus/directus:11} - container_name: ${SEXY_COMPOSE_PROJECT_NAME:-sexy}_api + image: directus/directus:11 + container_name: sexy_directus restart: unless-stopped - networks: - - sexy-network ports: - "8055:8055" volumes: - - directus-uploads:/directus/uploads - - ${SEXY_DIRECTUS_BUNDLE:-./packages/bundle}:/directus/extensions/sexy.pivoine.art + - directus_uploads:/directus/uploads + - ./packages/bundle:/directus/extensions/sexy.pivoine.art environment: - # Database DB_CLIENT: pg - DB_HOST: ${CORE_DB_HOST:-postgres} - DB_PORT: ${CORE_DB_PORT:-5432} - DB_DATABASE: ${SEXY_DB_NAME:-sexy} - DB_USER: ${DB_USER:-sexy} - DB_PASSWORD: ${DB_PASSWORD:-sexy} - - # General - SECRET: ${SEXY_DIRECTUS_SECRET:-replace-with-random-secret-min-32-chars} - ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@sexy.pivoine.art} - ADMIN_PASSWORD: ${ADMIN_PASSWORD:-admin} - PUBLIC_URL: ${SEXY_PUBLIC_URL:-http://localhost:8055} - - # Cache - CACHE_ENABLED: ${SEXY_CACHE_ENABLED:-true} - CACHE_AUTO_PURGE: ${SEXY_CACHE_AUTO_PURGE:-true} + DB_HOST: sexy_postgres + DB_PORT: 5432 + DB_DATABASE: sexy + DB_USER: sexy + DB_PASSWORD: sexy + ADMIN_EMAIL: admin@sexy + ADMIN_PASSWORD: admin + PUBLIC_URL: http://localhost:3000/api + CACHE_ENABLED: true + CACHE_AUTO_PURGE: true CACHE_STORE: redis - REDIS: redis://${CORE_REDIS_HOST:-redis}:${CORE_REDIS_PORT:-6379} - - # CORS - CORS_ENABLED: ${SEXY_CORS_ENABLED:-true} - CORS_ORIGIN: ${SEXY_CORS_ORIGIN:-http://localhost:3000} - - # Security - SESSION_COOKIE_SECURE: ${SEXY_SESSION_COOKIE_SECURE:-false} - SESSION_COOKIE_SAME_SITE: ${SEXY_SESSION_COOKIE_SAME_SITE:-lax} - SESSION_COOKIE_DOMAIN: ${SEXY_SESSION_COOKIE_DOMAIN:-localhost} - - # Extensions - EXTENSIONS_PATH: ${SEXY_EXTENSIONS_PATH:-/directus/extensions} - EXTENSIONS_AUTO_RELOAD: ${SEXY_EXTENSIONS_AUTO_RELOAD:-true} - - # WebSockets - WEBSOCKETS_ENABLED: ${SEXY_WEBSOCKETS_ENABLED:-true} - - # Email (optional for local dev) - EMAIL_TRANSPORT: ${EMAIL_TRANSPORT:-sendmail} - EMAIL_FROM: ${EMAIL_FROM:-noreply@sexy.pivoine.art} - EMAIL_SMTP_HOST: ${EMAIL_SMTP_HOST:-} - EMAIL_SMTP_PORT: ${EMAIL_SMTP_PORT:-587} - EMAIL_SMTP_USER: ${EMAIL_SMTP_USER:-} - EMAIL_SMTP_PASSWORD: ${EMAIL_SMTP_PASSWORD:-} - - # User Registration & Password Reset URLs - USER_REGISTER_URL_ALLOW_LIST: ${SEXY_USER_REGISTER_URL_ALLOW_LIST:-http://localhost:3000} - PASSWORD_RESET_URL_ALLOW_LIST: ${SEXY_PASSWORD_RESET_URL_ALLOW_LIST:-http://localhost:3000} - - # Content Security Policy - CONTENT_SECURITY_POLICY_DIRECTIVES__FRAME_SRC: ${SEXY_CONTENT_SECURITY_POLICY_DIRECTIVES__FRAME_SRC:-} - - # Timezone - TZ: ${TIMEZONE:-Europe/Amsterdam} - + REDIS: redis://sexy_redis:6379 + CORS_ENABLED: true + CORS_ORIGIN: http://localhost:3000 + SESSION_COOKIE_SECURE: false + SESSION_COOKIE_SAME_SITE: lax + SESSION_COOKIE_DOMAIN: localhost + EXTENSIONS_PATH: /directus/extensions + EXTENSIONS_AUTO_RELOAD: true + WEBSOCKETS_ENABLED: true + USER_REGISTER_URL_ALLOW_LIST: http://localhost:3000 + PASSWORD_RESET_URL_ALLOW_LIST: http://localhost:3000 + TZ: Europe/Amsterdam depends_on: postgres: condition: service_healthy redis: condition: service_healthy - healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8055/server/health"] interval: 30s @@ -114,66 +73,10 @@ services: retries: 3 start_period: 40s - # Frontend (local development - optional, usually run via pnpm dev) - frontend: - image: ${SEXY_FRONTEND_IMAGE:-ghcr.io/valknarxxx/sexy:latest} - container_name: ${SEXY_COMPOSE_PROJECT_NAME:-sexy}_frontend - restart: unless-stopped - user: node - working_dir: /home/node/app/packages/frontend - networks: - - sexy-network - ports: - - "3000:3000" - environment: - # Node - NODE_ENV: ${NODE_ENV:-development} - PORT: 3000 - HOST: 0.0.0.0 - - # Public environment variables - PUBLIC_API_URL: ${SEXY_FRONTEND_PUBLIC_API_URL:-http://localhost:8055} - PUBLIC_URL: ${SEXY_FRONTEND_PUBLIC_URL:-http://localhost:3000} - PUBLIC_UMAMI_ID: ${SEXY_FRONTEND_PUBLIC_UMAMI_ID:-} - PUBLIC_UMAMI_SCRIPT: ${SEXY_FRONTEND_PUBLIC_UMAMI_SCRIPT:-} - - # Timezone - TZ: ${TIMEZONE:-Europe/Amsterdam} - - volumes: - - ${SEXY_FRONTEND_PATH:-./}:/home/node/app - - command: ["node", "build/index.js"] - - depends_on: - - directus - - healthcheck: - test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"] - interval: 30s - timeout: 3s - retries: 3 - start_period: 40s - - # Uncomment to run frontend in development mode with live reload - # build: - # context: . - # dockerfile: Dockerfile - # volumes: - # - ./packages/frontend:/home/node/app/packages/frontend - # - /home/node/app/packages/frontend/node_modules - # environment: - # NODE_ENV: development - -networks: - sexy-network: - driver: bridge - name: ${SEXY_COMPOSE_PROJECT_NAME:-sexy}_network - volumes: - directus-uploads: + directus_uploads: driver: local - postgres-data: + postgres_data: driver: local - redis-data: + redis_data: driver: local diff --git a/directus.yaml b/directus.yml similarity index 75% rename from directus.yaml rename to directus.yml index fc84b2e..0a6afbe 100644 --- a/directus.yaml +++ b/directus.yml @@ -102,34 +102,6 @@ collections: versioning: false schema: name: sexy_videos_directus_users - - collection: sexy_recordings - meta: - accountability: all - archive_app_filter: true - archive_field: status - archive_value: archived - collapse: open - collection: sexy_recordings - color: null - display_template: null - group: null - hidden: false - icon: fiber_manual_record - item_duplication_fields: null - note: null - preview_url: null - singleton: false - sort: null - sort_field: null - translations: - - language: en-US - plural: Recordings - singular: Recording - translation: Sexy Recordings - unarchive_value: draft - versioning: false - schema: - name: sexy_recordings fields: - collection: directus_users field: website @@ -206,7 +178,7 @@ fields: max_length: 255 numeric_precision: null numeric_scale: null - is_nullable: true + is_nullable: false is_unique: true is_indexed: true is_primary_key: false @@ -1908,639 +1880,6 @@ fields: has_auto_increment: false foreign_key_table: directus_users foreign_key_column: id - - collection: sexy_recordings - field: id - type: uuid - meta: - collection: sexy_recordings - conditions: null - display: null - display_options: null - field: id - group: null - hidden: true - interface: input - note: null - options: null - readonly: true - required: false - sort: 1 - special: - - uuid - translations: null - validation: null - validation_message: null - width: full - schema: - name: id - table: sexy_recordings - data_type: uuid - default_value: null - max_length: null - numeric_precision: null - numeric_scale: null - is_nullable: false - is_unique: true - is_indexed: false - is_primary_key: true - is_generated: false - generation_expression: null - has_auto_increment: false - foreign_key_table: null - foreign_key_column: null - - collection: sexy_recordings - field: status - type: string - meta: - collection: sexy_recordings - conditions: null - display: labels - display_options: - choices: - - background: var(--theme--primary-background) - color: var(--theme--primary) - foreground: var(--theme--primary) - text: $t:published - value: published - - background: var(--theme--background-normal) - color: var(--theme--foreground) - foreground: var(--theme--foreground) - text: $t:draft - value: draft - - background: var(--theme--warning-background) - color: var(--theme--warning) - foreground: var(--theme--warning) - text: $t:archived - value: archived - showAsDot: true - field: status - group: null - hidden: false - interface: select-dropdown - note: null - options: - choices: - - color: var(--theme--primary) - text: $t:published - value: published - - color: var(--theme--foreground) - text: $t:draft - value: draft - - color: var(--theme--warning) - text: $t:archived - value: archived - readonly: false - required: false - sort: 2 - special: null - translations: null - validation: null - validation_message: null - width: full - schema: - name: status - table: sexy_recordings - data_type: character varying - default_value: draft - max_length: 255 - numeric_precision: null - numeric_scale: null - is_nullable: false - is_unique: false - is_indexed: false - is_primary_key: false - is_generated: false - generation_expression: null - has_auto_increment: false - foreign_key_table: null - foreign_key_column: null - - collection: sexy_recordings - field: user_created - type: uuid - meta: - collection: sexy_recordings - conditions: null - display: user - display_options: null - field: user_created - group: null - hidden: true - interface: select-dropdown-m2o - note: null - options: - template: '{{avatar}} {{first_name}} {{last_name}}' - readonly: true - required: false - sort: 3 - special: - - user-created - translations: null - validation: null - validation_message: null - width: half - schema: - name: user_created - table: sexy_recordings - data_type: uuid - default_value: null - max_length: null - numeric_precision: null - numeric_scale: null - is_nullable: true - is_unique: false - is_indexed: false - is_primary_key: false - is_generated: false - generation_expression: null - has_auto_increment: false - foreign_key_table: directus_users - foreign_key_column: id - - collection: sexy_recordings - field: date_created - type: timestamp - meta: - collection: sexy_recordings - conditions: null - display: datetime - display_options: - relative: true - field: date_created - group: null - hidden: true - interface: datetime - note: null - options: null - readonly: true - required: false - sort: 4 - special: - - date-created - translations: null - validation: null - validation_message: null - width: half - schema: - name: date_created - table: sexy_recordings - data_type: timestamp with time zone - default_value: null - max_length: null - numeric_precision: null - numeric_scale: null - is_nullable: true - is_unique: false - is_indexed: false - is_primary_key: false - is_generated: false - generation_expression: null - has_auto_increment: false - foreign_key_table: null - foreign_key_column: null - - collection: sexy_recordings - field: date_updated - type: timestamp - meta: - collection: sexy_recordings - conditions: null - display: datetime - display_options: - relative: true - field: date_updated - group: null - hidden: true - interface: datetime - note: null - options: null - readonly: true - required: false - sort: 5 - special: - - date-updated - translations: null - validation: null - validation_message: null - width: half - schema: - name: date_updated - table: sexy_recordings - data_type: timestamp with time zone - default_value: null - max_length: null - numeric_precision: null - numeric_scale: null - is_nullable: true - is_unique: false - is_indexed: false - is_primary_key: false - is_generated: false - generation_expression: null - has_auto_increment: false - foreign_key_table: null - foreign_key_column: null - - collection: sexy_recordings - field: title - type: string - meta: - collection: sexy_recordings - conditions: null - display: null - display_options: null - field: title - group: null - hidden: false - interface: input - note: null - options: null - readonly: false - required: true - sort: 6 - special: null - translations: null - validation: null - validation_message: null - width: full - schema: - name: title - table: sexy_recordings - data_type: character varying - default_value: null - max_length: 255 - numeric_precision: null - numeric_scale: null - is_nullable: true - is_unique: false - is_indexed: false - is_primary_key: false - is_generated: false - generation_expression: null - has_auto_increment: false - foreign_key_table: null - foreign_key_column: null - - collection: sexy_recordings - field: description - type: text - meta: - collection: sexy_recordings - conditions: null - display: null - display_options: null - field: description - group: null - hidden: false - interface: input-multiline - note: null - options: - trim: true - readonly: false - required: false - sort: 7 - special: null - translations: null - validation: null - validation_message: null - width: full - schema: - name: description - table: sexy_recordings - data_type: text - default_value: null - max_length: null - numeric_precision: null - numeric_scale: null - is_nullable: true - is_unique: false - is_indexed: false - is_primary_key: false - is_generated: false - generation_expression: null - has_auto_increment: false - foreign_key_table: null - foreign_key_column: null - - collection: sexy_recordings - field: slug - type: string - meta: - collection: sexy_recordings - conditions: null - display: null - display_options: null - field: slug - group: null - hidden: false - interface: input - note: null - options: - slug: true - trim: true - readonly: false - required: true - sort: 8 - special: null - translations: null - validation: null - validation_message: null - width: full - schema: - name: slug - table: sexy_recordings - data_type: character varying - default_value: null - max_length: 255 - numeric_precision: null - numeric_scale: null - is_nullable: false - is_unique: true - is_indexed: true - is_primary_key: false - is_generated: false - generation_expression: null - has_auto_increment: false - foreign_key_table: null - foreign_key_column: null - - collection: sexy_recordings - field: duration - type: float - meta: - collection: sexy_recordings - conditions: null - display: null - display_options: null - field: duration - group: null - hidden: false - interface: input - note: Duration in milliseconds - options: null - readonly: false - required: true - sort: 9 - special: null - translations: null - validation: null - validation_message: null - width: full - schema: - name: duration - table: sexy_recordings - data_type: double precision - default_value: null - max_length: null - numeric_precision: 53 - numeric_scale: null - is_nullable: false - is_unique: false - is_indexed: false - is_primary_key: false - is_generated: false - generation_expression: null - has_auto_increment: false - foreign_key_table: null - foreign_key_column: null - - collection: sexy_recordings - field: events - type: json - meta: - collection: sexy_recordings - conditions: null - display: null - display_options: null - field: events - group: null - hidden: false - interface: input-code - note: Array of recorded events with timestamps - options: - language: json - readonly: false - required: true - sort: 10 - special: - - cast-json - translations: null - validation: null - validation_message: null - width: full - schema: - name: events - table: sexy_recordings - data_type: json - default_value: null - max_length: null - numeric_precision: null - numeric_scale: null - is_nullable: false - is_unique: false - is_indexed: false - is_primary_key: false - is_generated: false - generation_expression: null - has_auto_increment: false - foreign_key_table: null - foreign_key_column: null - - collection: sexy_recordings - field: device_info - type: json - meta: - collection: sexy_recordings - conditions: null - display: null - display_options: null - field: device_info - group: null - hidden: false - interface: input-code - note: Array of device metadata - options: - language: json - readonly: false - required: true - sort: 11 - special: - - cast-json - translations: null - validation: null - validation_message: null - width: full - schema: - name: device_info - table: sexy_recordings - data_type: json - default_value: null - max_length: null - numeric_precision: null - numeric_scale: null - is_nullable: false - is_unique: false - is_indexed: false - is_primary_key: false - is_generated: false - generation_expression: null - has_auto_increment: false - foreign_key_table: null - foreign_key_column: null - - collection: sexy_recordings - field: tags - type: json - meta: - collection: sexy_recordings - conditions: null - display: null - display_options: null - field: tags - group: null - hidden: false - interface: tags - note: null - options: null - readonly: false - required: false - sort: 12 - special: - - cast-json - translations: null - validation: null - validation_message: null - width: full - schema: - name: tags - table: sexy_recordings - data_type: json - default_value: null - max_length: null - numeric_precision: null - numeric_scale: null - is_nullable: true - is_unique: false - is_indexed: false - is_primary_key: false - is_generated: false - generation_expression: null - has_auto_increment: false - foreign_key_table: null - foreign_key_column: null - - collection: sexy_recordings - field: linked_video - type: uuid - meta: - collection: sexy_recordings - conditions: null - display: null - display_options: null - field: linked_video - group: null - hidden: false - interface: select-dropdown-m2o - note: null - options: - enableLink: true - readonly: false - required: false - sort: 13 - special: - - m2o - translations: null - validation: null - validation_message: null - width: full - schema: - name: linked_video - table: sexy_recordings - data_type: uuid - default_value: null - max_length: null - numeric_precision: null - numeric_scale: null - is_nullable: true - is_unique: false - is_indexed: false - is_primary_key: false - is_generated: false - generation_expression: null - has_auto_increment: false - foreign_key_table: sexy_videos - foreign_key_column: id - - collection: sexy_recordings - field: featured - type: boolean - meta: - collection: sexy_recordings - conditions: null - display: null - display_options: null - field: featured - group: null - hidden: false - interface: boolean - note: null - options: - label: Featured - readonly: false - required: false - sort: 14 - special: - - cast-boolean - translations: null - validation: null - validation_message: null - width: full - schema: - name: featured - table: sexy_recordings - data_type: boolean - default_value: false - max_length: null - numeric_precision: null - numeric_scale: null - is_nullable: true - is_unique: false - is_indexed: false - is_primary_key: false - is_generated: false - generation_expression: null - has_auto_increment: false - foreign_key_table: null - foreign_key_column: null - - collection: sexy_recordings - field: public - type: boolean - meta: - collection: sexy_recordings - conditions: null - display: null - display_options: null - field: public - group: null - hidden: false - interface: boolean - note: null - options: - label: Public - readonly: false - required: false - sort: 15 - special: - - cast-boolean - translations: null - validation: null - validation_message: null - width: full - schema: - name: public - table: sexy_recordings - data_type: boolean - default_value: false - max_length: null - numeric_precision: null - numeric_scale: null - is_nullable: true - is_unique: false - is_indexed: false - is_primary_key: false - is_generated: false - generation_expression: null - has_auto_increment: false - foreign_key_table: null - foreign_key_column: null relations: - collection: directus_users field: banner @@ -2773,45 +2112,3 @@ relations: constraint_name: sexy_videos_directus_users_sexy_videos_id_foreign on_update: NO ACTION on_delete: SET NULL - - collection: sexy_recordings - field: user_created - related_collection: directus_users - meta: - junction_field: null - many_collection: sexy_recordings - many_field: user_created - one_allowed_collections: null - one_collection: directus_users - one_collection_field: null - one_deselect_action: nullify - one_field: null - sort_field: null - schema: - table: sexy_recordings - column: user_created - foreign_key_table: directus_users - foreign_key_column: id - constraint_name: sexy_recordings_user_created_foreign - on_update: NO ACTION - on_delete: NO ACTION - - collection: sexy_recordings - field: linked_video - related_collection: sexy_videos - meta: - junction_field: null - many_collection: sexy_recordings - many_field: linked_video - one_allowed_collections: null - one_collection: sexy_videos - one_collection_field: null - one_deselect_action: nullify - one_field: null - sort_field: null - schema: - table: sexy_recordings - column: linked_video - foreign_key_table: sexy_videos - foreign_key_column: id - constraint_name: sexy_recordings_linked_video_foreign - on_update: NO ACTION - on_delete: SET NULL diff --git a/gamification-schema.sql b/gamification-schema.sql deleted file mode 100644 index f6cb79e..0000000 --- a/gamification-schema.sql +++ /dev/null @@ -1,177 +0,0 @@ --- Gamification System Schema for Sexy Recordings Platform --- Created: 2025-10-28 --- Description: Recording-focused gamification with time-weighted scoring - --- ==================== --- Table: sexy_recording_plays --- ==================== --- Tracks when users play recordings (similar to video plays) -CREATE TABLE IF NOT EXISTS sexy_recording_plays ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES directus_users(id) ON DELETE CASCADE, - recording_id UUID NOT NULL REFERENCES sexy_recordings(id) ON DELETE CASCADE, - duration_played INTEGER, -- Duration played in milliseconds - completed BOOLEAN DEFAULT FALSE, -- True if >= 90% watched - date_created TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - date_updated TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - -CREATE INDEX IF NOT EXISTS idx_recording_plays_user ON sexy_recording_plays(user_id); -CREATE INDEX IF NOT EXISTS idx_recording_plays_recording ON sexy_recording_plays(recording_id); -CREATE INDEX IF NOT EXISTS idx_recording_plays_date ON sexy_recording_plays(date_created); - -COMMENT ON TABLE sexy_recording_plays IS 'Tracks user playback of recordings for analytics and gamification'; -COMMENT ON COLUMN sexy_recording_plays.completed IS 'True if user watched at least 90% of the recording'; - --- ==================== --- Table: sexy_user_points --- ==================== --- Tracks individual point-earning actions with timestamps for time-weighted scoring -CREATE TABLE IF NOT EXISTS sexy_user_points ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES directus_users(id) ON DELETE CASCADE, - action VARCHAR(50) NOT NULL, -- e.g., "RECORDING_CREATE", "RECORDING_PLAY", "COMMENT_CREATE" - points INTEGER NOT NULL, -- Raw points earned - recording_id UUID REFERENCES sexy_recordings(id) ON DELETE SET NULL, -- Optional reference - date_created TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - -CREATE INDEX IF NOT EXISTS idx_user_points_user ON sexy_user_points(user_id); -CREATE INDEX IF NOT EXISTS idx_user_points_date ON sexy_user_points(date_created); -CREATE INDEX IF NOT EXISTS idx_user_points_action ON sexy_user_points(action); - -COMMENT ON TABLE sexy_user_points IS 'Individual point-earning actions for gamification system'; -COMMENT ON COLUMN sexy_user_points.action IS 'Type of action: RECORDING_CREATE, RECORDING_PLAY, RECORDING_COMPLETE, COMMENT_CREATE, RECORDING_FEATURED'; -COMMENT ON COLUMN sexy_user_points.points IS 'Raw points before time-weighted decay calculation'; - --- ==================== --- Table: sexy_achievements --- ==================== --- Predefined achievement definitions -CREATE TABLE IF NOT EXISTS sexy_achievements ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - code VARCHAR(50) UNIQUE NOT NULL, -- Unique identifier (e.g., "first_recording", "recording_100") - name VARCHAR(255) NOT NULL, -- Display name - description TEXT, -- Achievement description - icon VARCHAR(255), -- Icon identifier or emoji - category VARCHAR(50) NOT NULL, -- e.g., "recordings", "playback", "social", "special" - required_count INTEGER, -- Number of actions needed to unlock - points_reward INTEGER DEFAULT 0, -- Bonus points awarded upon unlock - sort INTEGER DEFAULT 0, -- Display order - status VARCHAR(20) DEFAULT 'published' -- published, draft, archived -); - -CREATE INDEX IF NOT EXISTS idx_achievements_category ON sexy_achievements(category); -CREATE INDEX IF NOT EXISTS idx_achievements_code ON sexy_achievements(code); - -COMMENT ON TABLE sexy_achievements IS 'Predefined achievement definitions for gamification'; -COMMENT ON COLUMN sexy_achievements.code IS 'Unique code used in backend logic (e.g., first_recording, play_100)'; -COMMENT ON COLUMN sexy_achievements.category IS 'Achievement category: recordings, playback, social, special'; - --- ==================== --- Table: sexy_user_achievements --- ==================== --- Junction table tracking unlocked achievements per user -CREATE TABLE IF NOT EXISTS sexy_user_achievements ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES directus_users(id) ON DELETE CASCADE, - achievement_id UUID NOT NULL REFERENCES sexy_achievements(id) ON DELETE CASCADE, - progress INTEGER DEFAULT 0, -- Current progress toward unlocking - date_unlocked TIMESTAMP WITH TIME ZONE, -- NULL if not yet unlocked - UNIQUE(user_id, achievement_id) -); - -CREATE INDEX IF NOT EXISTS idx_user_achievements_user ON sexy_user_achievements(user_id); -CREATE INDEX IF NOT EXISTS idx_user_achievements_achievement ON sexy_user_achievements(achievement_id); -CREATE INDEX IF NOT EXISTS idx_user_achievements_unlocked ON sexy_user_achievements(date_unlocked) WHERE date_unlocked IS NOT NULL; - -COMMENT ON TABLE sexy_user_achievements IS 'Tracks which achievements users have unlocked'; -COMMENT ON COLUMN sexy_user_achievements.progress IS 'Current progress (e.g., 7/10 recordings created)'; -COMMENT ON COLUMN sexy_user_achievements.date_unlocked IS 'NULL if achievement not yet unlocked'; - --- ==================== --- Table: sexy_user_stats --- ==================== --- Cached aggregate statistics for efficient leaderboard queries -CREATE TABLE IF NOT EXISTS sexy_user_stats ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID UNIQUE NOT NULL REFERENCES directus_users(id) ON DELETE CASCADE, - total_raw_points INTEGER DEFAULT 0, -- Sum of all points (no decay) - total_weighted_points NUMERIC(10,2) DEFAULT 0, -- Time-weighted score for rankings - recordings_count INTEGER DEFAULT 0, -- Number of published recordings - playbacks_count INTEGER DEFAULT 0, -- Number of recordings played - comments_count INTEGER DEFAULT 0, -- Number of comments on recordings - achievements_count INTEGER DEFAULT 0, -- Number of unlocked achievements - last_updated TIMESTAMP WITH TIME ZONE DEFAULT NOW() -- Cache timestamp -); - -CREATE INDEX IF NOT EXISTS idx_user_stats_weighted ON sexy_user_stats(total_weighted_points DESC); -CREATE INDEX IF NOT EXISTS idx_user_stats_user ON sexy_user_stats(user_id); - -COMMENT ON TABLE sexy_user_stats IS 'Cached user statistics for fast leaderboard queries'; -COMMENT ON COLUMN sexy_user_stats.total_raw_points IS 'Sum of all points without time decay'; -COMMENT ON COLUMN sexy_user_stats.total_weighted_points IS 'Time-weighted score using exponential decay (λ=0.005)'; -COMMENT ON COLUMN sexy_user_stats.last_updated IS 'Timestamp for cache invalidation'; - --- ==================== --- Insert Initial Achievements --- ==================== - --- 🎬 Recordings (Creation) -INSERT INTO sexy_achievements (code, name, description, icon, category, required_count, points_reward, sort) VALUES -('first_recording', 'First Recording', 'Create your first recording', '🎬', 'recordings', 1, 50, 1), -('recording_10', 'Recording Enthusiast', 'Create 10 recordings', '📹', 'recordings', 10, 100, 2), -('recording_50', 'Prolific Creator', 'Create 50 recordings', '🎥', 'recordings', 50, 500, 3), -('recording_100', 'Recording Master', 'Create 100 recordings', '🏆', 'recordings', 100, 1000, 4), -('featured_recording', 'Featured Creator', 'Get a recording featured', '⭐', 'recordings', 1, 200, 5) -ON CONFLICT (code) DO NOTHING; - --- ▶️ Playback (Consumption) -INSERT INTO sexy_achievements (code, name, description, icon, category, required_count, points_reward, sort) VALUES -('first_play', 'First Play', 'Play your first recording', '▶️', 'playback', 1, 25, 10), -('play_100', 'Active Player', 'Play 100 recordings', '🎮', 'playback', 100, 250, 11), -('play_500', 'Playback Enthusiast', 'Play 500 recordings', '🔥', 'playback', 500, 1000, 12), -('completionist_10', 'Completionist', 'Complete 10 recordings to 90%+', '✅', 'playback', 10, 100, 13), -('completionist_100', 'Super Completionist', 'Complete 100 recordings', '💯', 'playback', 100, 500, 14) -ON CONFLICT (code) DO NOTHING; - --- 💬 Social (Community) -INSERT INTO sexy_achievements (code, name, description, icon, category, required_count, points_reward, sort) VALUES -('first_comment', 'First Comment', 'Leave your first comment', '💬', 'social', 1, 25, 20), -('comment_50', 'Conversationalist', 'Leave 50 comments', '💭', 'social', 50, 200, 21), -('comment_250', 'Community Voice', 'Leave 250 comments', '📣', 'social', 250, 750, 22) -ON CONFLICT (code) DO NOTHING; - --- ⭐ Special (Milestones) -INSERT INTO sexy_achievements (code, name, description, icon, category, required_count, points_reward, sort) VALUES -('early_adopter', 'Early Adopter', 'Join in the first month', '🚀', 'special', 1, 500, 30), -('one_year', 'One Year Anniversary', 'Be a member for 1 year', '🎂', 'special', 1, 1000, 31), -('balanced_creator', 'Balanced Creator', '50 recordings + 100 plays', '⚖️', 'special', 1, 500, 32), -('top_10_rank', 'Top 10 Leaderboard', 'Reach top 10 on leaderboard', '🏅', 'special', 1, 2000, 33) -ON CONFLICT (code) DO NOTHING; - --- ==================== --- Verification Queries --- ==================== - --- Count tables created -SELECT - 'sexy_recording_plays' as table_name, - COUNT(*) as row_count -FROM sexy_recording_plays -UNION ALL -SELECT 'sexy_user_points', COUNT(*) FROM sexy_user_points -UNION ALL -SELECT 'sexy_achievements', COUNT(*) FROM sexy_achievements -UNION ALL -SELECT 'sexy_user_achievements', COUNT(*) FROM sexy_user_achievements -UNION ALL -SELECT 'sexy_user_stats', COUNT(*) FROM sexy_user_stats; - --- Show created achievements -SELECT - category, - COUNT(*) as achievement_count -FROM sexy_achievements -GROUP BY category -ORDER BY category; diff --git a/package.json b/package.json index 559c14b..d7472d9 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,16 @@ "test": "echo \"Error: no test specified\" && exit 1", "build:bundle": "git pull && pnpm install && pnpm --filter @sexy.pivoine.art/bundle build", "build:frontend": "git pull && pnpm install && pnpm --filter @sexy.pivoine.art/frontend build", - "dev:data": "cd ../compose/data && docker compose up -d", - "dev:directus": "cd ../compose/sexy && docker compose --env-file=.env.local up -d directus", - "dev": "pnpm dev:data && pnpm dev:directus && pnpm --filter @sexy.pivoine.art/frontend dev" + "dev": "pnpm build:bundle && docker compose up -d && pnpm --filter @sexy.pivoine.art/frontend dev", + "schema:export": "docker compose exec directus node /directus/cli.js schema snapshot --yes /tmp/snapshot.yml && docker compose cp directus:/tmp/snapshot.yml ./directus.yml && docker compose exec db pg_dump -U sexy --schema-only sexy > schema.sql", + "schema:import": "docker compose exec -T postgres psql -U sexy sexy < schema.sql && docker compose cp ./directus.yml directus:/tmp/snapshot.yml && docker compose exec directus node /directus/cli.js schema apply --yes /tmp/snapshot.yml" }, "keywords": [], - "author": "", - "license": "ISC", + "author": { + "name": "Valknar", + "email": "valknar@pivoine.art" + }, + "license": "MIT", "packageManager": "pnpm@10.19.0", "pnpm": { "onlyBuiltDependencies": [ diff --git a/packages/frontend/.env b/packages/frontend/.env deleted file mode 100644 index f426a29..0000000 --- a/packages/frontend/.env +++ /dev/null @@ -1,4 +0,0 @@ -PUBLIC_API_URL=https://sexy.pivoine.art/api -PUBLIC_URL=https://sexy.pivoine.art -PUBLIC_UMAMI_ID= -PUBLIC_UMAMI_SCRIPT= diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..cf35ef8 --- /dev/null +++ b/schema.sql @@ -0,0 +1,2667 @@ +-- +-- PostgreSQL database dump +-- + +\restrict dH8fx3AdK64TG7IecPmrgrOWxeiXn3AKobbF7dTmH75S3dVHMMUX0JW7Gkw8UDz + +-- Dumped from database version 16.12 +-- Dumped by pg_dump version 16.12 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: pg_database_owner +-- + +COMMENT ON SCHEMA public IS ''; + + +-- +-- Name: topology; Type: SCHEMA; Schema: -; Owner: directus +-- + +CREATE SCHEMA topology; + + +ALTER SCHEMA topology OWNER TO directus; + +-- +-- Name: SCHEMA topology; Type: COMMENT; Schema: -; Owner: directus +-- + +COMMENT ON SCHEMA topology IS 'PostGIS Topology schema'; + + +-- +-- Name: hstore; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS hstore WITH SCHEMA public; + + +-- +-- Name: EXTENSION hstore; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION hstore IS 'data type for storing sets of (key, value) pairs'; + + +-- +-- Name: st_asbinary(text); Type: FUNCTION; Schema: public; Owner: directus +-- + +CREATE FUNCTION public.st_asbinary(text) RETURNS bytea + LANGUAGE sql IMMUTABLE STRICT + AS $_$ SELECT ST_AsBinary($1::geometry);$_$; + + +ALTER FUNCTION public.st_asbinary(text) OWNER TO directus; + +-- +-- Name: st_astext(bytea); Type: FUNCTION; Schema: public; Owner: directus +-- + +CREATE FUNCTION public.st_astext(bytea) RETURNS text + LANGUAGE sql IMMUTABLE STRICT + AS $_$ SELECT ST_AsText($1::geometry);$_$; + + +ALTER FUNCTION public.st_astext(bytea) OWNER TO directus; + +-- +-- Name: gist_geometry_ops; Type: OPERATOR FAMILY; Schema: public; Owner: directus +-- + +CREATE OPERATOR FAMILY public.gist_geometry_ops USING gist; + + +ALTER OPERATOR FAMILY public.gist_geometry_ops USING gist OWNER TO directus; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: directus_access; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_access ( + id uuid NOT NULL, + role uuid, + "user" uuid, + policy uuid NOT NULL, + sort integer +); + + +ALTER TABLE public.directus_access OWNER TO directus; + +-- +-- Name: directus_activity; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_activity ( + id integer NOT NULL, + action character varying(45) NOT NULL, + "user" uuid, + "timestamp" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + ip character varying(50), + user_agent text, + collection character varying(64) NOT NULL, + item character varying(255) NOT NULL, + origin character varying(255) +); + + +ALTER TABLE public.directus_activity OWNER TO directus; + +-- +-- Name: directus_activity_id_seq; Type: SEQUENCE; Schema: public; Owner: directus +-- + +CREATE SEQUENCE public.directus_activity_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.directus_activity_id_seq OWNER TO directus; + +-- +-- Name: directus_activity_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: directus +-- + +ALTER SEQUENCE public.directus_activity_id_seq OWNED BY public.directus_activity.id; + + +-- +-- Name: directus_collections; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_collections ( + collection character varying(64) NOT NULL, + icon character varying(64), + note text, + display_template character varying(255), + hidden boolean DEFAULT false NOT NULL, + singleton boolean DEFAULT false NOT NULL, + translations json, + archive_field character varying(64), + archive_app_filter boolean DEFAULT true NOT NULL, + archive_value character varying(255), + unarchive_value character varying(255), + sort_field character varying(64), + accountability character varying(255) DEFAULT 'all'::character varying, + color character varying(255), + item_duplication_fields json, + sort integer, + "group" character varying(64), + collapse character varying(255) DEFAULT 'open'::character varying NOT NULL, + preview_url character varying(255), + versioning boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.directus_collections OWNER TO directus; + +-- +-- Name: directus_comments; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_comments ( + id uuid NOT NULL, + collection character varying(64) NOT NULL, + item character varying(255) NOT NULL, + comment text NOT NULL, + date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + date_updated timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + user_created uuid, + user_updated uuid +); + + +ALTER TABLE public.directus_comments OWNER TO directus; + +-- +-- Name: directus_dashboards; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_dashboards ( + id uuid NOT NULL, + name character varying(255) NOT NULL, + icon character varying(64) DEFAULT 'dashboard'::character varying NOT NULL, + note text, + date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + user_created uuid, + color character varying(255) +); + + +ALTER TABLE public.directus_dashboards OWNER TO directus; + +-- +-- Name: directus_extensions; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_extensions ( + enabled boolean DEFAULT true NOT NULL, + id uuid NOT NULL, + folder character varying(255) NOT NULL, + source character varying(255) NOT NULL, + bundle uuid +); + + +ALTER TABLE public.directus_extensions OWNER TO directus; + +-- +-- Name: directus_fields; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_fields ( + id integer NOT NULL, + collection character varying(64) NOT NULL, + field character varying(64) NOT NULL, + special character varying(64), + interface character varying(64), + options json, + display character varying(64), + display_options json, + readonly boolean DEFAULT false NOT NULL, + hidden boolean DEFAULT false NOT NULL, + sort integer, + width character varying(30) DEFAULT 'full'::character varying, + translations json, + note text, + conditions json, + required boolean DEFAULT false, + "group" character varying(64), + validation json, + validation_message text +); + + +ALTER TABLE public.directus_fields OWNER TO directus; + +-- +-- Name: directus_fields_id_seq; Type: SEQUENCE; Schema: public; Owner: directus +-- + +CREATE SEQUENCE public.directus_fields_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.directus_fields_id_seq OWNER TO directus; + +-- +-- Name: directus_fields_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: directus +-- + +ALTER SEQUENCE public.directus_fields_id_seq OWNED BY public.directus_fields.id; + + +-- +-- Name: directus_files; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_files ( + id uuid NOT NULL, + storage character varying(255) NOT NULL, + filename_disk character varying(255), + filename_download character varying(255) NOT NULL, + title character varying(255), + type character varying(255), + folder uuid, + uploaded_by uuid, + created_on timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + modified_by uuid, + modified_on timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + charset character varying(50), + filesize bigint, + width integer, + height integer, + duration integer, + embed character varying(200), + description text, + location text, + tags text, + metadata json, + focal_point_x integer, + focal_point_y integer, + tus_id character varying(64), + tus_data json, + uploaded_on timestamp with time zone +); + + +ALTER TABLE public.directus_files OWNER TO directus; + +-- +-- Name: directus_flows; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_flows ( + id uuid NOT NULL, + name character varying(255) NOT NULL, + icon character varying(64), + color character varying(255), + description text, + status character varying(255) DEFAULT 'active'::character varying NOT NULL, + trigger character varying(255), + accountability character varying(255) DEFAULT 'all'::character varying, + options json, + operation uuid, + date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + user_created uuid +); + + +ALTER TABLE public.directus_flows OWNER TO directus; + +-- +-- Name: directus_folders; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_folders ( + id uuid NOT NULL, + name character varying(255) NOT NULL, + parent uuid +); + + +ALTER TABLE public.directus_folders OWNER TO directus; + +-- +-- Name: directus_migrations; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_migrations ( + version character varying(255) NOT NULL, + name character varying(255) NOT NULL, + "timestamp" timestamp with time zone DEFAULT CURRENT_TIMESTAMP +); + + +ALTER TABLE public.directus_migrations OWNER TO directus; + +-- +-- Name: directus_notifications; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_notifications ( + id integer NOT NULL, + "timestamp" timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + status character varying(255) DEFAULT 'inbox'::character varying, + recipient uuid NOT NULL, + sender uuid, + subject character varying(255) NOT NULL, + message text, + collection character varying(64), + item character varying(255) +); + + +ALTER TABLE public.directus_notifications OWNER TO directus; + +-- +-- Name: directus_notifications_id_seq; Type: SEQUENCE; Schema: public; Owner: directus +-- + +CREATE SEQUENCE public.directus_notifications_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.directus_notifications_id_seq OWNER TO directus; + +-- +-- Name: directus_notifications_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: directus +-- + +ALTER SEQUENCE public.directus_notifications_id_seq OWNED BY public.directus_notifications.id; + + +-- +-- Name: directus_operations; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_operations ( + id uuid NOT NULL, + name character varying(255), + key character varying(255) NOT NULL, + type character varying(255) NOT NULL, + position_x integer NOT NULL, + position_y integer NOT NULL, + options json, + resolve uuid, + reject uuid, + flow uuid NOT NULL, + date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + user_created uuid +); + + +ALTER TABLE public.directus_operations OWNER TO directus; + +-- +-- Name: directus_panels; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_panels ( + id uuid NOT NULL, + dashboard uuid NOT NULL, + name character varying(255), + icon character varying(64) DEFAULT NULL::character varying, + color character varying(10), + show_header boolean DEFAULT false NOT NULL, + note text, + type character varying(255) NOT NULL, + position_x integer NOT NULL, + position_y integer NOT NULL, + width integer NOT NULL, + height integer NOT NULL, + options json, + date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + user_created uuid +); + + +ALTER TABLE public.directus_panels OWNER TO directus; + +-- +-- Name: directus_permissions; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_permissions ( + id integer NOT NULL, + collection character varying(64) NOT NULL, + action character varying(10) NOT NULL, + permissions json, + validation json, + presets json, + fields text, + policy uuid NOT NULL +); + + +ALTER TABLE public.directus_permissions OWNER TO directus; + +-- +-- Name: directus_permissions_id_seq; Type: SEQUENCE; Schema: public; Owner: directus +-- + +CREATE SEQUENCE public.directus_permissions_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.directus_permissions_id_seq OWNER TO directus; + +-- +-- Name: directus_permissions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: directus +-- + +ALTER SEQUENCE public.directus_permissions_id_seq OWNED BY public.directus_permissions.id; + + +-- +-- Name: directus_policies; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_policies ( + id uuid NOT NULL, + name character varying(100) NOT NULL, + icon character varying(64) DEFAULT 'badge'::character varying NOT NULL, + description text, + ip_access text, + enforce_tfa boolean DEFAULT false NOT NULL, + admin_access boolean DEFAULT false NOT NULL, + app_access boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.directus_policies OWNER TO directus; + +-- +-- Name: directus_presets; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_presets ( + id integer NOT NULL, + bookmark character varying(255), + "user" uuid, + role uuid, + collection character varying(64), + search character varying(100), + layout character varying(100) DEFAULT 'tabular'::character varying, + layout_query json, + layout_options json, + refresh_interval integer, + filter json, + icon character varying(64) DEFAULT 'bookmark'::character varying, + color character varying(255) +); + + +ALTER TABLE public.directus_presets OWNER TO directus; + +-- +-- Name: directus_presets_id_seq; Type: SEQUENCE; Schema: public; Owner: directus +-- + +CREATE SEQUENCE public.directus_presets_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.directus_presets_id_seq OWNER TO directus; + +-- +-- Name: directus_presets_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: directus +-- + +ALTER SEQUENCE public.directus_presets_id_seq OWNED BY public.directus_presets.id; + + +-- +-- Name: directus_relations; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_relations ( + id integer NOT NULL, + many_collection character varying(64) NOT NULL, + many_field character varying(64) NOT NULL, + one_collection character varying(64), + one_field character varying(64), + one_collection_field character varying(64), + one_allowed_collections text, + junction_field character varying(64), + sort_field character varying(64), + one_deselect_action character varying(255) DEFAULT 'nullify'::character varying NOT NULL +); + + +ALTER TABLE public.directus_relations OWNER TO directus; + +-- +-- Name: directus_relations_id_seq; Type: SEQUENCE; Schema: public; Owner: directus +-- + +CREATE SEQUENCE public.directus_relations_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.directus_relations_id_seq OWNER TO directus; + +-- +-- Name: directus_relations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: directus +-- + +ALTER SEQUENCE public.directus_relations_id_seq OWNED BY public.directus_relations.id; + + +-- +-- Name: directus_revisions; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_revisions ( + id integer NOT NULL, + activity integer NOT NULL, + collection character varying(64) NOT NULL, + item character varying(255) NOT NULL, + data json, + delta json, + parent integer, + version uuid +); + + +ALTER TABLE public.directus_revisions OWNER TO directus; + +-- +-- Name: directus_revisions_id_seq; Type: SEQUENCE; Schema: public; Owner: directus +-- + +CREATE SEQUENCE public.directus_revisions_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.directus_revisions_id_seq OWNER TO directus; + +-- +-- Name: directus_revisions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: directus +-- + +ALTER SEQUENCE public.directus_revisions_id_seq OWNED BY public.directus_revisions.id; + + +-- +-- Name: directus_roles; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_roles ( + id uuid NOT NULL, + name character varying(100) NOT NULL, + icon character varying(64) DEFAULT 'supervised_user_circle'::character varying NOT NULL, + description text, + parent uuid +); + + +ALTER TABLE public.directus_roles OWNER TO directus; + +-- +-- Name: directus_sessions; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_sessions ( + token character varying(64) NOT NULL, + "user" uuid, + expires timestamp with time zone NOT NULL, + ip character varying(255), + user_agent text, + share uuid, + origin character varying(255), + next_token character varying(64) +); + + +ALTER TABLE public.directus_sessions OWNER TO directus; + +-- +-- Name: directus_settings; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_settings ( + id integer NOT NULL, + project_name character varying(100) DEFAULT 'Directus'::character varying NOT NULL, + project_url character varying(255), + project_color character varying(255) DEFAULT '#6644FF'::character varying NOT NULL, + project_logo uuid, + public_foreground uuid, + public_background uuid, + public_note text, + auth_login_attempts integer DEFAULT 25, + auth_password_policy character varying(100), + storage_asset_transform character varying(7) DEFAULT 'all'::character varying, + storage_asset_presets json, + custom_css text, + storage_default_folder uuid, + basemaps json, + mapbox_key character varying(255), + module_bar json, + project_descriptor character varying(100), + default_language character varying(255) DEFAULT 'en-US'::character varying NOT NULL, + custom_aspect_ratios json, + public_favicon uuid, + default_appearance character varying(255) DEFAULT 'auto'::character varying NOT NULL, + default_theme_light character varying(255), + theme_light_overrides json, + default_theme_dark character varying(255), + theme_dark_overrides json, + report_error_url character varying(255), + report_bug_url character varying(255), + report_feature_url character varying(255), + public_registration boolean DEFAULT false NOT NULL, + public_registration_verify_email boolean DEFAULT true NOT NULL, + public_registration_role uuid, + public_registration_email_filter json, + visual_editor_urls json, + accepted_terms boolean DEFAULT false, + project_id uuid, + mcp_enabled boolean DEFAULT false NOT NULL, + mcp_allow_deletes boolean DEFAULT false NOT NULL, + mcp_prompts_collection character varying(255) DEFAULT NULL::character varying, + mcp_system_prompt_enabled boolean DEFAULT true NOT NULL, + mcp_system_prompt text +); + + +ALTER TABLE public.directus_settings OWNER TO directus; + +-- +-- Name: directus_settings_id_seq; Type: SEQUENCE; Schema: public; Owner: directus +-- + +CREATE SEQUENCE public.directus_settings_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.directus_settings_id_seq OWNER TO directus; + +-- +-- Name: directus_settings_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: directus +-- + +ALTER SEQUENCE public.directus_settings_id_seq OWNED BY public.directus_settings.id; + + +-- +-- Name: directus_shares; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_shares ( + id uuid NOT NULL, + name character varying(255), + collection character varying(64) NOT NULL, + item character varying(255) NOT NULL, + role uuid, + password character varying(255), + user_created uuid, + date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + date_start timestamp with time zone, + date_end timestamp with time zone, + times_used integer DEFAULT 0, + max_uses integer +); + + +ALTER TABLE public.directus_shares OWNER TO directus; + +-- +-- Name: directus_translations; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_translations ( + id uuid NOT NULL, + language character varying(255) NOT NULL, + key character varying(255) NOT NULL, + value text NOT NULL +); + + +ALTER TABLE public.directus_translations OWNER TO directus; + +-- +-- Name: directus_users; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_users ( + id uuid NOT NULL, + first_name character varying(50), + last_name character varying(50), + email character varying(128), + password character varying(255), + location character varying(255), + title character varying(50), + description text, + tags json, + avatar uuid, + language character varying(255) DEFAULT NULL::character varying, + tfa_secret character varying(255), + status character varying(16) DEFAULT 'active'::character varying NOT NULL, + role uuid, + token character varying(255), + last_access timestamp with time zone, + last_page character varying(255), + provider character varying(128) DEFAULT 'default'::character varying NOT NULL, + external_identifier character varying(255), + auth_data json, + email_notifications boolean DEFAULT true, + appearance character varying(255), + theme_dark character varying(255), + theme_light character varying(255), + theme_light_overrides json, + theme_dark_overrides json, + text_direction character varying(255) DEFAULT 'auto'::character varying NOT NULL, + website character varying(255), + slug character varying(255) DEFAULT NULL::character varying NOT NULL, + join_date timestamp without time zone NOT NULL, + featured boolean DEFAULT false, + artist_name character varying(255), + banner uuid +); + + +ALTER TABLE public.directus_users OWNER TO directus; + +-- +-- Name: directus_versions; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_versions ( + id uuid NOT NULL, + key character varying(64) NOT NULL, + name character varying(255), + collection character varying(64) NOT NULL, + item character varying(255) NOT NULL, + hash character varying(255), + date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + date_updated timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + user_created uuid, + user_updated uuid, + delta json +); + + +ALTER TABLE public.directus_versions OWNER TO directus; + +-- +-- Name: directus_webhooks; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.directus_webhooks ( + id integer NOT NULL, + name character varying(255) NOT NULL, + method character varying(10) DEFAULT 'POST'::character varying NOT NULL, + url character varying(255) NOT NULL, + status character varying(10) DEFAULT 'active'::character varying NOT NULL, + data boolean DEFAULT true NOT NULL, + actions character varying(100) NOT NULL, + collections character varying(255) NOT NULL, + headers json, + was_active_before_deprecation boolean DEFAULT false NOT NULL, + migrated_flow uuid +); + + +ALTER TABLE public.directus_webhooks OWNER TO directus; + +-- +-- Name: directus_webhooks_id_seq; Type: SEQUENCE; Schema: public; Owner: directus +-- + +CREATE SEQUENCE public.directus_webhooks_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.directus_webhooks_id_seq OWNER TO directus; + +-- +-- Name: directus_webhooks_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: directus +-- + +ALTER SEQUENCE public.directus_webhooks_id_seq OWNED BY public.directus_webhooks.id; + + +-- +-- Name: junction_directus_users_files; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.junction_directus_users_files ( + id integer NOT NULL, + directus_users_id uuid, + directus_files_id uuid +); + + +ALTER TABLE public.junction_directus_users_files OWNER TO directus; + +-- +-- Name: junction_directus_users_files_id_seq; Type: SEQUENCE; Schema: public; Owner: directus +-- + +CREATE SEQUENCE public.junction_directus_users_files_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.junction_directus_users_files_id_seq OWNER TO directus; + +-- +-- Name: junction_directus_users_files_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: directus +-- + +ALTER SEQUENCE public.junction_directus_users_files_id_seq OWNED BY public.junction_directus_users_files.id; + + +-- +-- Name: sexy_achievements; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.sexy_achievements ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + code character varying(50) NOT NULL, + name character varying(255) NOT NULL, + description text, + icon character varying(255), + category character varying(50) NOT NULL, + required_count integer, + points_reward integer DEFAULT 0, + sort integer DEFAULT 0, + status character varying(20) DEFAULT 'published'::character varying +); + + +ALTER TABLE public.sexy_achievements OWNER TO directus; + +-- +-- Name: TABLE sexy_achievements; Type: COMMENT; Schema: public; Owner: directus +-- + +COMMENT ON TABLE public.sexy_achievements IS 'Predefined achievement definitions for gamification'; + + +-- +-- Name: COLUMN sexy_achievements.code; Type: COMMENT; Schema: public; Owner: directus +-- + +COMMENT ON COLUMN public.sexy_achievements.code IS 'Unique code used in backend logic (e.g., first_recording, play_100)'; + + +-- +-- Name: COLUMN sexy_achievements.category; Type: COMMENT; Schema: public; Owner: directus +-- + +COMMENT ON COLUMN public.sexy_achievements.category IS 'Achievement category: recordings, playback, social, special'; + + +-- +-- Name: sexy_articles; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.sexy_articles ( + id uuid NOT NULL, + status character varying(255) DEFAULT 'draft'::character varying NOT NULL, + user_created uuid, + date_created timestamp with time zone, + date_updated timestamp with time zone, + slug character varying(255) DEFAULT NULL::character varying, + title character varying(255), + excerpt text, + content text, + image uuid NOT NULL, + tags json, + publish_date timestamp without time zone, + category character varying(255), + featured boolean DEFAULT false, + author uuid +); + + +ALTER TABLE public.sexy_articles OWNER TO directus; + +-- +-- Name: sexy_model_photos; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.sexy_model_photos ( + id integer NOT NULL, + directus_users_id uuid NOT NULL, + directus_files_id uuid NOT NULL, + date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + date_updated timestamp with time zone DEFAULT CURRENT_TIMESTAMP +); + + +ALTER TABLE public.sexy_model_photos OWNER TO directus; + +-- +-- Name: sexy_model_photos_id_seq; Type: SEQUENCE; Schema: public; Owner: directus +-- + +CREATE SEQUENCE public.sexy_model_photos_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.sexy_model_photos_id_seq OWNER TO directus; + +-- +-- Name: sexy_model_photos_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: directus +-- + +ALTER SEQUENCE public.sexy_model_photos_id_seq OWNED BY public.sexy_model_photos.id; + + +-- +-- Name: sexy_recording_plays; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.sexy_recording_plays ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + user_id uuid NOT NULL, + recording_id uuid NOT NULL, + duration_played integer, + completed boolean DEFAULT false, + date_created timestamp with time zone DEFAULT now(), + date_updated timestamp with time zone DEFAULT now() +); + + +ALTER TABLE public.sexy_recording_plays OWNER TO directus; + +-- +-- Name: TABLE sexy_recording_plays; Type: COMMENT; Schema: public; Owner: directus +-- + +COMMENT ON TABLE public.sexy_recording_plays IS 'Tracks user playback of recordings for analytics and gamification'; + + +-- +-- Name: COLUMN sexy_recording_plays.completed; Type: COMMENT; Schema: public; Owner: directus +-- + +COMMENT ON COLUMN public.sexy_recording_plays.completed IS 'True if user watched at least 90% of the recording'; + + +-- +-- Name: sexy_recordings; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.sexy_recordings ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + title character varying(255) NOT NULL, + description text, + slug character varying(255) NOT NULL, + duration numeric(10,2) NOT NULL, + events jsonb NOT NULL, + device_info jsonb NOT NULL, + tags json DEFAULT '[]'::json, + linked_video uuid, + status character varying(50) DEFAULT 'draft'::character varying, + public boolean DEFAULT false, + user_created uuid, + user_updated uuid, + date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + date_updated timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + original_recording_id uuid +); + + +ALTER TABLE public.sexy_recordings OWNER TO directus; + +-- +-- Name: sexy_user_achievements; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.sexy_user_achievements ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + user_id uuid NOT NULL, + achievement_id uuid NOT NULL, + progress integer DEFAULT 0, + date_unlocked timestamp with time zone +); + + +ALTER TABLE public.sexy_user_achievements OWNER TO directus; + +-- +-- Name: TABLE sexy_user_achievements; Type: COMMENT; Schema: public; Owner: directus +-- + +COMMENT ON TABLE public.sexy_user_achievements IS 'Tracks which achievements users have unlocked'; + + +-- +-- Name: COLUMN sexy_user_achievements.progress; Type: COMMENT; Schema: public; Owner: directus +-- + +COMMENT ON COLUMN public.sexy_user_achievements.progress IS 'Current progress (e.g., 7/10 recordings created)'; + + +-- +-- Name: COLUMN sexy_user_achievements.date_unlocked; Type: COMMENT; Schema: public; Owner: directus +-- + +COMMENT ON COLUMN public.sexy_user_achievements.date_unlocked IS 'NULL if achievement not yet unlocked'; + + +-- +-- Name: sexy_user_points; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.sexy_user_points ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + user_id uuid NOT NULL, + action character varying(50) NOT NULL, + points integer NOT NULL, + recording_id uuid, + date_created timestamp with time zone DEFAULT now() +); + + +ALTER TABLE public.sexy_user_points OWNER TO directus; + +-- +-- Name: TABLE sexy_user_points; Type: COMMENT; Schema: public; Owner: directus +-- + +COMMENT ON TABLE public.sexy_user_points IS 'Individual point-earning actions for gamification system'; + + +-- +-- Name: COLUMN sexy_user_points.action; Type: COMMENT; Schema: public; Owner: directus +-- + +COMMENT ON COLUMN public.sexy_user_points.action IS 'Type of action: RECORDING_CREATE, RECORDING_PLAY, RECORDING_COMPLETE, COMMENT_CREATE, RECORDING_FEATURED'; + + +-- +-- Name: COLUMN sexy_user_points.points; Type: COMMENT; Schema: public; Owner: directus +-- + +COMMENT ON COLUMN public.sexy_user_points.points IS 'Raw points before time-weighted decay calculation'; + + +-- +-- Name: sexy_user_stats; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.sexy_user_stats ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + user_id uuid NOT NULL, + total_raw_points integer DEFAULT 0, + total_weighted_points numeric(10,2) DEFAULT 0, + recordings_count integer DEFAULT 0, + playbacks_count integer DEFAULT 0, + comments_count integer DEFAULT 0, + achievements_count integer DEFAULT 0, + last_updated timestamp with time zone DEFAULT now() +); + + +ALTER TABLE public.sexy_user_stats OWNER TO directus; + +-- +-- Name: TABLE sexy_user_stats; Type: COMMENT; Schema: public; Owner: directus +-- + +COMMENT ON TABLE public.sexy_user_stats IS 'Cached user statistics for fast leaderboard queries'; + + +-- +-- Name: COLUMN sexy_user_stats.total_raw_points; Type: COMMENT; Schema: public; Owner: directus +-- + +COMMENT ON COLUMN public.sexy_user_stats.total_raw_points IS 'Sum of all points without time decay'; + + +-- +-- Name: COLUMN sexy_user_stats.total_weighted_points; Type: COMMENT; Schema: public; Owner: directus +-- + +COMMENT ON COLUMN public.sexy_user_stats.total_weighted_points IS 'Time-weighted score using exponential decay (λ=0.005)'; + + +-- +-- Name: COLUMN sexy_user_stats.last_updated; Type: COMMENT; Schema: public; Owner: directus +-- + +COMMENT ON COLUMN public.sexy_user_stats.last_updated IS 'Timestamp for cache invalidation'; + + +-- +-- Name: sexy_video_likes; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.sexy_video_likes ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + video_id uuid NOT NULL, + user_id uuid NOT NULL, + date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP +); + + +ALTER TABLE public.sexy_video_likes OWNER TO directus; + +-- +-- Name: sexy_video_plays; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.sexy_video_plays ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + video_id uuid NOT NULL, + user_id uuid, + session_id character varying(255), + duration_watched integer DEFAULT 0, + completed boolean DEFAULT false, + date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + date_updated timestamp with time zone DEFAULT CURRENT_TIMESTAMP +); + + +ALTER TABLE public.sexy_video_plays OWNER TO directus; + +-- +-- Name: sexy_videos; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.sexy_videos ( + id uuid NOT NULL, + status character varying(255) DEFAULT 'draft'::character varying NOT NULL, + user_created uuid, + date_created timestamp with time zone, + date_updated timestamp with time zone, + slug character varying(255), + title character varying(255), + image uuid, + upload_date timestamp without time zone, + premium boolean, + featured boolean, + tags json, + movie uuid, + description text +); + + +ALTER TABLE public.sexy_videos OWNER TO directus; + +-- +-- Name: sexy_videos_directus_users; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.sexy_videos_directus_users ( + id integer NOT NULL, + sexy_videos_id uuid, + directus_users_id uuid +); + + +ALTER TABLE public.sexy_videos_directus_users OWNER TO directus; + +-- +-- Name: sexy_videos_directus_users_id_seq; Type: SEQUENCE; Schema: public; Owner: directus +-- + +CREATE SEQUENCE public.sexy_videos_directus_users_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.sexy_videos_directus_users_id_seq OWNER TO directus; + +-- +-- Name: sexy_videos_directus_users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: directus +-- + +ALTER SEQUENCE public.sexy_videos_directus_users_id_seq OWNED BY public.sexy_videos_directus_users.id; + + +-- +-- Name: sexy_videos_models; Type: TABLE; Schema: public; Owner: directus +-- + +CREATE TABLE public.sexy_videos_models ( + id integer NOT NULL, + sexy_videos_id uuid NOT NULL, + directus_users_id uuid NOT NULL, + date_created timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + date_updated timestamp with time zone DEFAULT CURRENT_TIMESTAMP +); + + +ALTER TABLE public.sexy_videos_models OWNER TO directus; + +-- +-- Name: sexy_videos_models_id_seq; Type: SEQUENCE; Schema: public; Owner: directus +-- + +CREATE SEQUENCE public.sexy_videos_models_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.sexy_videos_models_id_seq OWNER TO directus; + +-- +-- Name: sexy_videos_models_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: directus +-- + +ALTER SEQUENCE public.sexy_videos_models_id_seq OWNED BY public.sexy_videos_models.id; + + +-- +-- Name: directus_activity id; Type: DEFAULT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_activity ALTER COLUMN id SET DEFAULT nextval('public.directus_activity_id_seq'::regclass); + + +-- +-- Name: directus_fields id; Type: DEFAULT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_fields ALTER COLUMN id SET DEFAULT nextval('public.directus_fields_id_seq'::regclass); + + +-- +-- Name: directus_notifications id; Type: DEFAULT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_notifications ALTER COLUMN id SET DEFAULT nextval('public.directus_notifications_id_seq'::regclass); + + +-- +-- Name: directus_permissions id; Type: DEFAULT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_permissions ALTER COLUMN id SET DEFAULT nextval('public.directus_permissions_id_seq'::regclass); + + +-- +-- Name: directus_presets id; Type: DEFAULT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_presets ALTER COLUMN id SET DEFAULT nextval('public.directus_presets_id_seq'::regclass); + + +-- +-- Name: directus_relations id; Type: DEFAULT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_relations ALTER COLUMN id SET DEFAULT nextval('public.directus_relations_id_seq'::regclass); + + +-- +-- Name: directus_revisions id; Type: DEFAULT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_revisions ALTER COLUMN id SET DEFAULT nextval('public.directus_revisions_id_seq'::regclass); + + +-- +-- Name: directus_settings id; Type: DEFAULT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_settings ALTER COLUMN id SET DEFAULT nextval('public.directus_settings_id_seq'::regclass); + + +-- +-- Name: directus_webhooks id; Type: DEFAULT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_webhooks ALTER COLUMN id SET DEFAULT nextval('public.directus_webhooks_id_seq'::regclass); + + +-- +-- Name: junction_directus_users_files id; Type: DEFAULT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.junction_directus_users_files ALTER COLUMN id SET DEFAULT nextval('public.junction_directus_users_files_id_seq'::regclass); + + +-- +-- Name: sexy_model_photos id; Type: DEFAULT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_model_photos ALTER COLUMN id SET DEFAULT nextval('public.sexy_model_photos_id_seq'::regclass); + + +-- +-- Name: sexy_videos_directus_users id; Type: DEFAULT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_videos_directus_users ALTER COLUMN id SET DEFAULT nextval('public.sexy_videos_directus_users_id_seq'::regclass); + + +-- +-- Name: sexy_videos_models id; Type: DEFAULT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_videos_models ALTER COLUMN id SET DEFAULT nextval('public.sexy_videos_models_id_seq'::regclass); + + +-- +-- Name: directus_access directus_access_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_access + ADD CONSTRAINT directus_access_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_activity directus_activity_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_activity + ADD CONSTRAINT directus_activity_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_collections directus_collections_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_collections + ADD CONSTRAINT directus_collections_pkey PRIMARY KEY (collection); + + +-- +-- Name: directus_comments directus_comments_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_comments + ADD CONSTRAINT directus_comments_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_dashboards directus_dashboards_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_dashboards + ADD CONSTRAINT directus_dashboards_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_extensions directus_extensions_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_extensions + ADD CONSTRAINT directus_extensions_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_fields directus_fields_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_fields + ADD CONSTRAINT directus_fields_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_files directus_files_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_files + ADD CONSTRAINT directus_files_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_flows directus_flows_operation_unique; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_flows + ADD CONSTRAINT directus_flows_operation_unique UNIQUE (operation); + + +-- +-- Name: directus_flows directus_flows_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_flows + ADD CONSTRAINT directus_flows_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_folders directus_folders_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_folders + ADD CONSTRAINT directus_folders_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_migrations directus_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_migrations + ADD CONSTRAINT directus_migrations_pkey PRIMARY KEY (version); + + +-- +-- Name: directus_notifications directus_notifications_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_notifications + ADD CONSTRAINT directus_notifications_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_operations directus_operations_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_operations + ADD CONSTRAINT directus_operations_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_operations directus_operations_reject_unique; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_operations + ADD CONSTRAINT directus_operations_reject_unique UNIQUE (reject); + + +-- +-- Name: directus_operations directus_operations_resolve_unique; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_operations + ADD CONSTRAINT directus_operations_resolve_unique UNIQUE (resolve); + + +-- +-- Name: directus_panels directus_panels_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_panels + ADD CONSTRAINT directus_panels_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_permissions directus_permissions_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_permissions + ADD CONSTRAINT directus_permissions_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_policies directus_policies_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_policies + ADD CONSTRAINT directus_policies_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_presets directus_presets_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_presets + ADD CONSTRAINT directus_presets_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_relations directus_relations_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_relations + ADD CONSTRAINT directus_relations_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_revisions directus_revisions_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_revisions + ADD CONSTRAINT directus_revisions_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_roles directus_roles_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_roles + ADD CONSTRAINT directus_roles_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_sessions directus_sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_sessions + ADD CONSTRAINT directus_sessions_pkey PRIMARY KEY (token); + + +-- +-- Name: directus_settings directus_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_settings + ADD CONSTRAINT directus_settings_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_shares directus_shares_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_shares + ADD CONSTRAINT directus_shares_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_translations directus_translations_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_translations + ADD CONSTRAINT directus_translations_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_users directus_users_email_unique; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_users + ADD CONSTRAINT directus_users_email_unique UNIQUE (email); + + +-- +-- Name: directus_users directus_users_external_identifier_unique; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_users + ADD CONSTRAINT directus_users_external_identifier_unique UNIQUE (external_identifier); + + +-- +-- Name: directus_users directus_users_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_users + ADD CONSTRAINT directus_users_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_users directus_users_slug_unique; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_users + ADD CONSTRAINT directus_users_slug_unique UNIQUE (slug); + + +-- +-- Name: directus_users directus_users_token_unique; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_users + ADD CONSTRAINT directus_users_token_unique UNIQUE (token); + + +-- +-- Name: directus_versions directus_versions_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_versions + ADD CONSTRAINT directus_versions_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_webhooks directus_webhooks_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_webhooks + ADD CONSTRAINT directus_webhooks_pkey PRIMARY KEY (id); + + +-- +-- Name: junction_directus_users_files junction_directus_users_files_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.junction_directus_users_files + ADD CONSTRAINT junction_directus_users_files_pkey PRIMARY KEY (id); + + +-- +-- Name: sexy_achievements sexy_achievements_code_key; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_achievements + ADD CONSTRAINT sexy_achievements_code_key UNIQUE (code); + + +-- +-- Name: sexy_achievements sexy_achievements_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_achievements + ADD CONSTRAINT sexy_achievements_pkey PRIMARY KEY (id); + + +-- +-- Name: sexy_articles sexy_articles_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_articles + ADD CONSTRAINT sexy_articles_pkey PRIMARY KEY (id); + + +-- +-- Name: sexy_articles sexy_articles_slug_unique; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_articles + ADD CONSTRAINT sexy_articles_slug_unique UNIQUE (slug); + + +-- +-- Name: sexy_model_photos sexy_model_photos_directus_users_id_directus_files_id_key; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_model_photos + ADD CONSTRAINT sexy_model_photos_directus_users_id_directus_files_id_key UNIQUE (directus_users_id, directus_files_id); + + +-- +-- Name: sexy_model_photos sexy_model_photos_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_model_photos + ADD CONSTRAINT sexy_model_photos_pkey PRIMARY KEY (id); + + +-- +-- Name: sexy_recording_plays sexy_recording_plays_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_recording_plays + ADD CONSTRAINT sexy_recording_plays_pkey PRIMARY KEY (id); + + +-- +-- Name: sexy_recordings sexy_recordings_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_recordings + ADD CONSTRAINT sexy_recordings_pkey PRIMARY KEY (id); + + +-- +-- Name: sexy_recordings sexy_recordings_slug_key; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_recordings + ADD CONSTRAINT sexy_recordings_slug_key UNIQUE (slug); + + +-- +-- Name: sexy_user_achievements sexy_user_achievements_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_user_achievements + ADD CONSTRAINT sexy_user_achievements_pkey PRIMARY KEY (id); + + +-- +-- Name: sexy_user_achievements sexy_user_achievements_user_id_achievement_id_key; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_user_achievements + ADD CONSTRAINT sexy_user_achievements_user_id_achievement_id_key UNIQUE (user_id, achievement_id); + + +-- +-- Name: sexy_user_points sexy_user_points_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_user_points + ADD CONSTRAINT sexy_user_points_pkey PRIMARY KEY (id); + + +-- +-- Name: sexy_user_stats sexy_user_stats_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_user_stats + ADD CONSTRAINT sexy_user_stats_pkey PRIMARY KEY (id); + + +-- +-- Name: sexy_user_stats sexy_user_stats_user_id_key; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_user_stats + ADD CONSTRAINT sexy_user_stats_user_id_key UNIQUE (user_id); + + +-- +-- Name: sexy_video_likes sexy_video_likes_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_video_likes + ADD CONSTRAINT sexy_video_likes_pkey PRIMARY KEY (id); + + +-- +-- Name: sexy_video_likes sexy_video_likes_video_id_user_id_key; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_video_likes + ADD CONSTRAINT sexy_video_likes_video_id_user_id_key UNIQUE (video_id, user_id); + + +-- +-- Name: sexy_video_plays sexy_video_plays_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_video_plays + ADD CONSTRAINT sexy_video_plays_pkey PRIMARY KEY (id); + + +-- +-- Name: sexy_videos_directus_users sexy_videos_directus_users_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_videos_directus_users + ADD CONSTRAINT sexy_videos_directus_users_pkey PRIMARY KEY (id); + + +-- +-- Name: sexy_videos_models sexy_videos_models_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_videos_models + ADD CONSTRAINT sexy_videos_models_pkey PRIMARY KEY (id); + + +-- +-- Name: sexy_videos_models sexy_videos_models_sexy_videos_id_directus_users_id_key; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_videos_models + ADD CONSTRAINT sexy_videos_models_sexy_videos_id_directus_users_id_key UNIQUE (sexy_videos_id, directus_users_id); + + +-- +-- Name: sexy_videos sexy_videos_pkey; Type: CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_videos + ADD CONSTRAINT sexy_videos_pkey PRIMARY KEY (id); + + +-- +-- Name: directus_users_slug_index; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX directus_users_slug_index ON public.directus_users USING btree (slug); + + +-- +-- Name: idx_achievements_category; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX idx_achievements_category ON public.sexy_achievements USING btree (category); + + +-- +-- Name: idx_achievements_code; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX idx_achievements_code ON public.sexy_achievements USING btree (code); + + +-- +-- Name: idx_recording_plays_date; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX idx_recording_plays_date ON public.sexy_recording_plays USING btree (date_created); + + +-- +-- Name: idx_recording_plays_recording; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX idx_recording_plays_recording ON public.sexy_recording_plays USING btree (recording_id); + + +-- +-- Name: idx_recording_plays_user; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX idx_recording_plays_user ON public.sexy_recording_plays USING btree (user_id); + + +-- +-- Name: idx_user_achievements_achievement; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX idx_user_achievements_achievement ON public.sexy_user_achievements USING btree (achievement_id); + + +-- +-- Name: idx_user_achievements_unlocked; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX idx_user_achievements_unlocked ON public.sexy_user_achievements USING btree (date_unlocked) WHERE (date_unlocked IS NOT NULL); + + +-- +-- Name: idx_user_achievements_user; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX idx_user_achievements_user ON public.sexy_user_achievements USING btree (user_id); + + +-- +-- Name: idx_user_points_action; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX idx_user_points_action ON public.sexy_user_points USING btree (action); + + +-- +-- Name: idx_user_points_date; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX idx_user_points_date ON public.sexy_user_points USING btree (date_created); + + +-- +-- Name: idx_user_points_user; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX idx_user_points_user ON public.sexy_user_points USING btree (user_id); + + +-- +-- Name: idx_user_stats_user; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX idx_user_stats_user ON public.sexy_user_stats USING btree (user_id); + + +-- +-- Name: idx_user_stats_weighted; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX idx_user_stats_weighted ON public.sexy_user_stats USING btree (total_weighted_points DESC); + + +-- +-- Name: sexy_articles_slug_index; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_articles_slug_index ON public.sexy_articles USING btree (slug); + + +-- +-- Name: sexy_model_photos_files_id_idx; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_model_photos_files_id_idx ON public.sexy_model_photos USING btree (directus_files_id); + + +-- +-- Name: sexy_model_photos_users_id_idx; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_model_photos_users_id_idx ON public.sexy_model_photos USING btree (directus_users_id); + + +-- +-- Name: sexy_recordings_linked_video_idx; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_recordings_linked_video_idx ON public.sexy_recordings USING btree (linked_video); + + +-- +-- Name: sexy_recordings_original_recording_idx; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_recordings_original_recording_idx ON public.sexy_recordings USING btree (original_recording_id); + + +-- +-- Name: sexy_recordings_public_idx; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_recordings_public_idx ON public.sexy_recordings USING btree (public); + + +-- +-- Name: sexy_recordings_slug_idx; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_recordings_slug_idx ON public.sexy_recordings USING btree (slug); + + +-- +-- Name: sexy_recordings_status_idx; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_recordings_status_idx ON public.sexy_recordings USING btree (status); + + +-- +-- Name: sexy_recordings_user_created_idx; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_recordings_user_created_idx ON public.sexy_recordings USING btree (user_created); + + +-- +-- Name: sexy_video_likes_user_id_idx; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_video_likes_user_id_idx ON public.sexy_video_likes USING btree (user_id); + + +-- +-- Name: sexy_video_likes_video_id_idx; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_video_likes_video_id_idx ON public.sexy_video_likes USING btree (video_id); + + +-- +-- Name: sexy_video_plays_session_id_idx; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_video_plays_session_id_idx ON public.sexy_video_plays USING btree (session_id); + + +-- +-- Name: sexy_video_plays_user_id_idx; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_video_plays_user_id_idx ON public.sexy_video_plays USING btree (user_id); + + +-- +-- Name: sexy_video_plays_video_id_idx; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_video_plays_video_id_idx ON public.sexy_video_plays USING btree (video_id); + + +-- +-- Name: sexy_videos_models_users_id_idx; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_videos_models_users_id_idx ON public.sexy_videos_models USING btree (directus_users_id); + + +-- +-- Name: sexy_videos_models_videos_id_idx; Type: INDEX; Schema: public; Owner: directus +-- + +CREATE INDEX sexy_videos_models_videos_id_idx ON public.sexy_videos_models USING btree (sexy_videos_id); + + +-- +-- Name: directus_access directus_access_policy_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_access + ADD CONSTRAINT directus_access_policy_foreign FOREIGN KEY (policy) REFERENCES public.directus_policies(id) ON DELETE CASCADE; + + +-- +-- Name: directus_access directus_access_role_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_access + ADD CONSTRAINT directus_access_role_foreign FOREIGN KEY (role) REFERENCES public.directus_roles(id) ON DELETE CASCADE; + + +-- +-- Name: directus_access directus_access_user_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_access + ADD CONSTRAINT directus_access_user_foreign FOREIGN KEY ("user") REFERENCES public.directus_users(id) ON DELETE CASCADE; + + +-- +-- Name: directus_collections directus_collections_group_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_collections + ADD CONSTRAINT directus_collections_group_foreign FOREIGN KEY ("group") REFERENCES public.directus_collections(collection); + + +-- +-- Name: directus_comments directus_comments_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_comments + ADD CONSTRAINT directus_comments_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id) ON DELETE SET NULL; + + +-- +-- Name: directus_comments directus_comments_user_updated_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_comments + ADD CONSTRAINT directus_comments_user_updated_foreign FOREIGN KEY (user_updated) REFERENCES public.directus_users(id); + + +-- +-- Name: directus_dashboards directus_dashboards_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_dashboards + ADD CONSTRAINT directus_dashboards_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id) ON DELETE SET NULL; + + +-- +-- Name: directus_files directus_files_folder_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_files + ADD CONSTRAINT directus_files_folder_foreign FOREIGN KEY (folder) REFERENCES public.directus_folders(id) ON DELETE SET NULL; + + +-- +-- Name: directus_files directus_files_modified_by_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_files + ADD CONSTRAINT directus_files_modified_by_foreign FOREIGN KEY (modified_by) REFERENCES public.directus_users(id); + + +-- +-- Name: directus_files directus_files_uploaded_by_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_files + ADD CONSTRAINT directus_files_uploaded_by_foreign FOREIGN KEY (uploaded_by) REFERENCES public.directus_users(id); + + +-- +-- Name: directus_flows directus_flows_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_flows + ADD CONSTRAINT directus_flows_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id) ON DELETE SET NULL; + + +-- +-- Name: directus_folders directus_folders_parent_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_folders + ADD CONSTRAINT directus_folders_parent_foreign FOREIGN KEY (parent) REFERENCES public.directus_folders(id); + + +-- +-- Name: directus_notifications directus_notifications_recipient_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_notifications + ADD CONSTRAINT directus_notifications_recipient_foreign FOREIGN KEY (recipient) REFERENCES public.directus_users(id) ON DELETE CASCADE; + + +-- +-- Name: directus_notifications directus_notifications_sender_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_notifications + ADD CONSTRAINT directus_notifications_sender_foreign FOREIGN KEY (sender) REFERENCES public.directus_users(id); + + +-- +-- Name: directus_operations directus_operations_flow_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_operations + ADD CONSTRAINT directus_operations_flow_foreign FOREIGN KEY (flow) REFERENCES public.directus_flows(id) ON DELETE CASCADE; + + +-- +-- Name: directus_operations directus_operations_reject_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_operations + ADD CONSTRAINT directus_operations_reject_foreign FOREIGN KEY (reject) REFERENCES public.directus_operations(id); + + +-- +-- Name: directus_operations directus_operations_resolve_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_operations + ADD CONSTRAINT directus_operations_resolve_foreign FOREIGN KEY (resolve) REFERENCES public.directus_operations(id); + + +-- +-- Name: directus_operations directus_operations_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_operations + ADD CONSTRAINT directus_operations_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id) ON DELETE SET NULL; + + +-- +-- Name: directus_panels directus_panels_dashboard_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_panels + ADD CONSTRAINT directus_panels_dashboard_foreign FOREIGN KEY (dashboard) REFERENCES public.directus_dashboards(id) ON DELETE CASCADE; + + +-- +-- Name: directus_panels directus_panels_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_panels + ADD CONSTRAINT directus_panels_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id) ON DELETE SET NULL; + + +-- +-- Name: directus_permissions directus_permissions_policy_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_permissions + ADD CONSTRAINT directus_permissions_policy_foreign FOREIGN KEY (policy) REFERENCES public.directus_policies(id) ON DELETE CASCADE; + + +-- +-- Name: directus_presets directus_presets_role_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_presets + ADD CONSTRAINT directus_presets_role_foreign FOREIGN KEY (role) REFERENCES public.directus_roles(id) ON DELETE CASCADE; + + +-- +-- Name: directus_presets directus_presets_user_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_presets + ADD CONSTRAINT directus_presets_user_foreign FOREIGN KEY ("user") REFERENCES public.directus_users(id) ON DELETE CASCADE; + + +-- +-- Name: directus_revisions directus_revisions_activity_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_revisions + ADD CONSTRAINT directus_revisions_activity_foreign FOREIGN KEY (activity) REFERENCES public.directus_activity(id) ON DELETE CASCADE; + + +-- +-- Name: directus_revisions directus_revisions_parent_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_revisions + ADD CONSTRAINT directus_revisions_parent_foreign FOREIGN KEY (parent) REFERENCES public.directus_revisions(id); + + +-- +-- Name: directus_revisions directus_revisions_version_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_revisions + ADD CONSTRAINT directus_revisions_version_foreign FOREIGN KEY (version) REFERENCES public.directus_versions(id) ON DELETE CASCADE; + + +-- +-- Name: directus_roles directus_roles_parent_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_roles + ADD CONSTRAINT directus_roles_parent_foreign FOREIGN KEY (parent) REFERENCES public.directus_roles(id); + + +-- +-- Name: directus_sessions directus_sessions_share_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_sessions + ADD CONSTRAINT directus_sessions_share_foreign FOREIGN KEY (share) REFERENCES public.directus_shares(id) ON DELETE CASCADE; + + +-- +-- Name: directus_sessions directus_sessions_user_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_sessions + ADD CONSTRAINT directus_sessions_user_foreign FOREIGN KEY ("user") REFERENCES public.directus_users(id) ON DELETE CASCADE; + + +-- +-- Name: directus_settings directus_settings_project_logo_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_settings + ADD CONSTRAINT directus_settings_project_logo_foreign FOREIGN KEY (project_logo) REFERENCES public.directus_files(id); + + +-- +-- Name: directus_settings directus_settings_public_background_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_settings + ADD CONSTRAINT directus_settings_public_background_foreign FOREIGN KEY (public_background) REFERENCES public.directus_files(id); + + +-- +-- Name: directus_settings directus_settings_public_favicon_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_settings + ADD CONSTRAINT directus_settings_public_favicon_foreign FOREIGN KEY (public_favicon) REFERENCES public.directus_files(id); + + +-- +-- Name: directus_settings directus_settings_public_foreground_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_settings + ADD CONSTRAINT directus_settings_public_foreground_foreign FOREIGN KEY (public_foreground) REFERENCES public.directus_files(id); + + +-- +-- Name: directus_settings directus_settings_public_registration_role_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_settings + ADD CONSTRAINT directus_settings_public_registration_role_foreign FOREIGN KEY (public_registration_role) REFERENCES public.directus_roles(id) ON DELETE SET NULL; + + +-- +-- Name: directus_settings directus_settings_storage_default_folder_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_settings + ADD CONSTRAINT directus_settings_storage_default_folder_foreign FOREIGN KEY (storage_default_folder) REFERENCES public.directus_folders(id) ON DELETE SET NULL; + + +-- +-- Name: directus_shares directus_shares_collection_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_shares + ADD CONSTRAINT directus_shares_collection_foreign FOREIGN KEY (collection) REFERENCES public.directus_collections(collection) ON DELETE CASCADE; + + +-- +-- Name: directus_shares directus_shares_role_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_shares + ADD CONSTRAINT directus_shares_role_foreign FOREIGN KEY (role) REFERENCES public.directus_roles(id) ON DELETE CASCADE; + + +-- +-- Name: directus_shares directus_shares_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_shares + ADD CONSTRAINT directus_shares_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id) ON DELETE SET NULL; + + +-- +-- Name: directus_users directus_users_banner_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_users + ADD CONSTRAINT directus_users_banner_foreign FOREIGN KEY (banner) REFERENCES public.directus_files(id) ON DELETE SET NULL; + + +-- +-- Name: directus_users directus_users_role_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_users + ADD CONSTRAINT directus_users_role_foreign FOREIGN KEY (role) REFERENCES public.directus_roles(id) ON DELETE SET NULL; + + +-- +-- Name: directus_versions directus_versions_collection_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_versions + ADD CONSTRAINT directus_versions_collection_foreign FOREIGN KEY (collection) REFERENCES public.directus_collections(collection) ON DELETE CASCADE; + + +-- +-- Name: directus_versions directus_versions_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_versions + ADD CONSTRAINT directus_versions_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id) ON DELETE SET NULL; + + +-- +-- Name: directus_versions directus_versions_user_updated_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_versions + ADD CONSTRAINT directus_versions_user_updated_foreign FOREIGN KEY (user_updated) REFERENCES public.directus_users(id); + + +-- +-- Name: directus_webhooks directus_webhooks_migrated_flow_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.directus_webhooks + ADD CONSTRAINT directus_webhooks_migrated_flow_foreign FOREIGN KEY (migrated_flow) REFERENCES public.directus_flows(id) ON DELETE SET NULL; + + +-- +-- Name: junction_directus_users_files junction_directus_users_files_directus_files_id_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.junction_directus_users_files + ADD CONSTRAINT junction_directus_users_files_directus_files_id_foreign FOREIGN KEY (directus_files_id) REFERENCES public.directus_files(id) ON DELETE SET NULL; + + +-- +-- Name: junction_directus_users_files junction_directus_users_files_directus_users_id_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.junction_directus_users_files + ADD CONSTRAINT junction_directus_users_files_directus_users_id_foreign FOREIGN KEY (directus_users_id) REFERENCES public.directus_users(id) ON DELETE SET NULL; + + +-- +-- Name: sexy_articles sexy_articles_author_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_articles + ADD CONSTRAINT sexy_articles_author_foreign FOREIGN KEY (author) REFERENCES public.directus_users(id) ON DELETE SET NULL; + + +-- +-- Name: sexy_articles sexy_articles_image_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_articles + ADD CONSTRAINT sexy_articles_image_foreign FOREIGN KEY (image) REFERENCES public.directus_files(id); + + +-- +-- Name: sexy_articles sexy_articles_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_articles + ADD CONSTRAINT sexy_articles_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id); + + +-- +-- Name: sexy_model_photos sexy_model_photos_directus_files_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_model_photos + ADD CONSTRAINT sexy_model_photos_directus_files_id_fkey FOREIGN KEY (directus_files_id) REFERENCES public.directus_files(id) ON DELETE CASCADE; + + +-- +-- Name: sexy_model_photos sexy_model_photos_directus_users_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_model_photos + ADD CONSTRAINT sexy_model_photos_directus_users_id_fkey FOREIGN KEY (directus_users_id) REFERENCES public.directus_users(id) ON DELETE CASCADE; + + +-- +-- Name: sexy_recording_plays sexy_recording_plays_recording_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_recording_plays + ADD CONSTRAINT sexy_recording_plays_recording_id_fkey FOREIGN KEY (recording_id) REFERENCES public.sexy_recordings(id) ON DELETE CASCADE; + + +-- +-- Name: sexy_recording_plays sexy_recording_plays_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_recording_plays + ADD CONSTRAINT sexy_recording_plays_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.directus_users(id) ON DELETE CASCADE; + + +-- +-- Name: sexy_recordings sexy_recordings_linked_video_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_recordings + ADD CONSTRAINT sexy_recordings_linked_video_fkey FOREIGN KEY (linked_video) REFERENCES public.sexy_videos(id) ON DELETE SET NULL; + + +-- +-- Name: sexy_recordings sexy_recordings_original_recording_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_recordings + ADD CONSTRAINT sexy_recordings_original_recording_fkey FOREIGN KEY (original_recording_id) REFERENCES public.sexy_recordings(id) ON DELETE SET NULL; + + +-- +-- Name: sexy_recordings sexy_recordings_user_created_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_recordings + ADD CONSTRAINT sexy_recordings_user_created_fkey FOREIGN KEY (user_created) REFERENCES public.directus_users(id) ON DELETE CASCADE; + + +-- +-- Name: sexy_user_achievements sexy_user_achievements_achievement_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_user_achievements + ADD CONSTRAINT sexy_user_achievements_achievement_id_fkey FOREIGN KEY (achievement_id) REFERENCES public.sexy_achievements(id) ON DELETE CASCADE; + + +-- +-- Name: sexy_user_achievements sexy_user_achievements_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_user_achievements + ADD CONSTRAINT sexy_user_achievements_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.directus_users(id) ON DELETE CASCADE; + + +-- +-- Name: sexy_user_points sexy_user_points_recording_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_user_points + ADD CONSTRAINT sexy_user_points_recording_id_fkey FOREIGN KEY (recording_id) REFERENCES public.sexy_recordings(id) ON DELETE SET NULL; + + +-- +-- Name: sexy_user_points sexy_user_points_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_user_points + ADD CONSTRAINT sexy_user_points_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.directus_users(id) ON DELETE CASCADE; + + +-- +-- Name: sexy_user_stats sexy_user_stats_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_user_stats + ADD CONSTRAINT sexy_user_stats_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.directus_users(id) ON DELETE CASCADE; + + +-- +-- Name: sexy_video_likes sexy_video_likes_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_video_likes + ADD CONSTRAINT sexy_video_likes_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.directus_users(id) ON DELETE CASCADE; + + +-- +-- Name: sexy_video_likes sexy_video_likes_video_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_video_likes + ADD CONSTRAINT sexy_video_likes_video_id_fkey FOREIGN KEY (video_id) REFERENCES public.sexy_videos(id) ON DELETE CASCADE; + + +-- +-- Name: sexy_video_plays sexy_video_plays_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_video_plays + ADD CONSTRAINT sexy_video_plays_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.directus_users(id) ON DELETE SET NULL; + + +-- +-- Name: sexy_video_plays sexy_video_plays_video_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_video_plays + ADD CONSTRAINT sexy_video_plays_video_id_fkey FOREIGN KEY (video_id) REFERENCES public.sexy_videos(id) ON DELETE CASCADE; + + +-- +-- Name: sexy_videos_directus_users sexy_videos_directus_users_directus_users_id_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_videos_directus_users + ADD CONSTRAINT sexy_videos_directus_users_directus_users_id_foreign FOREIGN KEY (directus_users_id) REFERENCES public.directus_users(id) ON DELETE SET NULL; + + +-- +-- Name: sexy_videos_directus_users sexy_videos_directus_users_sexy_videos_id_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_videos_directus_users + ADD CONSTRAINT sexy_videos_directus_users_sexy_videos_id_foreign FOREIGN KEY (sexy_videos_id) REFERENCES public.sexy_videos(id) ON DELETE SET NULL; + + +-- +-- Name: sexy_videos sexy_videos_image_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_videos + ADD CONSTRAINT sexy_videos_image_foreign FOREIGN KEY (image) REFERENCES public.directus_files(id) ON DELETE SET NULL; + + +-- +-- Name: sexy_videos_models sexy_videos_models_directus_users_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_videos_models + ADD CONSTRAINT sexy_videos_models_directus_users_id_fkey FOREIGN KEY (directus_users_id) REFERENCES public.directus_users(id) ON DELETE CASCADE; + + +-- +-- Name: sexy_videos_models sexy_videos_models_sexy_videos_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_videos_models + ADD CONSTRAINT sexy_videos_models_sexy_videos_id_fkey FOREIGN KEY (sexy_videos_id) REFERENCES public.sexy_videos(id) ON DELETE CASCADE; + + +-- +-- Name: sexy_videos sexy_videos_movie_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_videos + ADD CONSTRAINT sexy_videos_movie_foreign FOREIGN KEY (movie) REFERENCES public.directus_files(id) ON DELETE SET NULL; + + +-- +-- Name: sexy_videos sexy_videos_user_created_foreign; Type: FK CONSTRAINT; Schema: public; Owner: directus +-- + +ALTER TABLE ONLY public.sexy_videos + ADD CONSTRAINT sexy_videos_user_created_foreign FOREIGN KEY (user_created) REFERENCES public.directus_users(id); + + +-- +-- Name: SCHEMA public; Type: ACL; Schema: -; Owner: pg_database_owner +-- + +REVOKE USAGE ON SCHEMA public FROM PUBLIC; +GRANT ALL ON SCHEMA public TO PUBLIC; + + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict dH8fx3AdK64TG7IecPmrgrOWxeiXn3AKobbF7dTmH75S3dVHMMUX0JW7Gkw8UDz +