From 79f475348e8440cab7e81948dd99a9d2c7edf531 Mon Sep 17 00:00:00 2001 From: salirezav Date: Thu, 18 Dec 2025 15:59:24 -0500 Subject: [PATCH 01/13] feat: Integrate Supabase containers into main docker-compose - Add all Supabase services (db, rest, auth, realtime, storage, studio, meta, inbucket) - Add migration runner service to automatically run migrations on startup - Configure all services to use shared network for inter-service communication - Add documentation for Supabase docker-compose integration - Add helper script for generating Supabase secrets - Update web service to connect to Supabase via network --- docker-compose.yml | 271 ++++++++++++++++++++++++++++++ docs/SUPABASE_DOCKER_COMPOSE.md | 107 ++++++++++++ scripts/setup-supabase-secrets.sh | 30 ++++ 3 files changed, 408 insertions(+) create mode 100644 docs/SUPABASE_DOCKER_COMPOSE.md create mode 100755 scripts/setup-supabase-secrets.sh diff --git a/docker-compose.yml b/docker-compose.yml index bd8ee27..33e138f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,257 @@ +networks: + usda-vision-network: + driver: bridge + +volumes: + supabase-db: + driver: local + supabase-storage: + services: + # Supabase Database + supabase-db: + container_name: usda-vision-supabase-db + image: supabase/postgres:17.1.0.147 + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + volumes: + - supabase-db:/var/lib/postgresql/data + environment: + POSTGRES_HOST: /var/run/postgresql + PGPORT: 5432 + POSTGRES_PORT: 5432 + PGDATABASE: postgres + POSTGRES_DB: postgres + PGUSER: supabase_admin + POSTGRES_USER: supabase_admin + PGPASSWORD: ${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password} + JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-token-with-at-least-32-characters-long} + JWT_EXP: ${JWT_EXP:-3600} + ports: + - "54322:5432" + networks: + - usda-vision-network + restart: unless-stopped + + # Supabase PostgREST API + supabase-rest: + container_name: usda-vision-supabase-rest + image: postgrest/postgrest:v12.2.0 + depends_on: + supabase-db: + condition: service_healthy + supabase-migrate: + condition: service_completed_successfully + environment: + PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password}@supabase-db:5432/postgres + PGRST_DB_SCHEMAS: public,graphql_public + PGRST_DB_EXTRA_SEARCH_PATH: public,extensions + PGRST_DB_ANON_ROLE: anon + PGRST_JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-token-with-at-least-32-characters-long} + PGRST_DB_USE_LEGACY_GUCS: "false" + PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-token-with-at-least-32-characters-long} + PGRST_APP_SETTINGS_JWT_EXP: ${JWT_EXP:-3600} + ports: + - "54321:3000" + networks: + - usda-vision-network + restart: unless-stopped + + # Supabase GoTrue (Auth) + supabase-auth: + container_name: usda-vision-supabase-auth + image: supabase/gotrue:v2.156.0 + depends_on: + supabase-db: + condition: service_healthy + environment: + GOTRUE_API_HOST: 0.0.0.0 + GOTRUE_API_PORT: 9999 + API_EXTERNAL_URL: http://localhost:54321 + GOTRUE_DB_DRIVER: postgres + GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password}@supabase-db:5432/postgres + GOTRUE_SITE_URL: http://localhost:8080 + GOTRUE_URI_ALLOW_LIST: http://localhost:8080,http://localhost:3000,https://localhost:3000 + GOTRUE_DISABLE_SIGNUP: "false" + GOTRUE_JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-token-with-at-least-32-characters-long} + GOTRUE_JWT_EXP: ${JWT_EXP:-3600} + GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated + GOTRUE_EXTERNAL_EMAIL_ENABLED: "true" + GOTRUE_MAILER_AUTOCONFIRM: "true" + GOTRUE_SMS_AUTOCONFIRM: "true" + GOTRUE_SMS_PROVIDER: twilio + GOTRUE_ENABLE_SIGNUP: "true" + GOTRUE_ENABLE_ANONYMOUS_SIGN_INS: "false" + GOTRUE_ENABLE_MANUAL_LINKING: "false" + GOTRUE_PASSWORD_MIN_LENGTH: 6 + GOTRUE_REFRESH_TOKEN_ROTATION_ENABLED: "true" + GOTRUE_REFRESH_TOKEN_REUSE_INTERVAL: 10 + ports: + - "9999:9999" + networks: + - usda-vision-network + restart: unless-stopped + + # Supabase Realtime + supabase-realtime: + container_name: usda-vision-supabase-realtime + image: supabase/realtime:v2.30.25 + depends_on: + supabase-db: + condition: service_healthy + supabase-rest: + condition: service_started + environment: + PORT: 4000 + DB_HOST: supabase-db + DB_PORT: 5432 + DB_USER: supabase_realtime_admin + DB_PASSWORD: ${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password} + DB_NAME: postgres + DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime' + DB_ENC_KEY: supabaserealtime + API_JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-token-with-at-least-32-characters-long} + FLY_ALLOC_ID: fly123 + FLY_APP_NAME: realtime + SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq + ERL_AFLAGS: -proto_dist inet_tcp + ENABLE_TAILSCALE: "false" + DNS_NODES: "''" + ports: + - "4000:4000" + networks: + - usda-vision-network + restart: unless-stopped + + # Supabase Storage + supabase-storage: + container_name: usda-vision-supabase-storage + image: supabase/storage-api:v1.11.8 + depends_on: + supabase-db: + condition: service_healthy + supabase-rest: + condition: service_started + environment: + ANON_KEY: ${ANON_KEY:-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0} + SERVICE_KEY: ${SERVICE_KEY:-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU} + POSTGREST_URL: http://supabase-rest:3000 + PGRST_JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-token-with-at-least-32-characters-long} + DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password}@supabase-db:5432/postgres + FILE_SIZE_LIMIT: 52428800 + STORAGE_BACKEND: file + FILE_STORAGE_BACKEND_PATH: /var/lib/storage + TENANT_ID: stub + REGION: stub + GLOBAL_S3_BUCKET: stub + ENABLE_IMAGE_TRANSFORMATION: "false" + volumes: + - supabase-storage:/var/lib/storage + ports: + - "5000:5000" + networks: + - usda-vision-network + restart: unless-stopped + + # Supabase Studio + supabase-studio: + container_name: usda-vision-supabase-studio + image: supabase/studio:20241218-5c0e5a0 + depends_on: + supabase-rest: + condition: service_started + supabase-auth: + condition: service_started + environment: + STUDIO_PG_META_URL: http://supabase-meta:8080 + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password} + DEFAULT_ORGANIZATION_NAME: Default Organization + DEFAULT_PROJECT_NAME: Default Project + SUPABASE_URL: http://supabase-rest:3000 + SUPABASE_PUBLIC_URL: http://localhost:54321 + SUPABASE_ANON_KEY: ${ANON_KEY:-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0} + SUPABASE_SERVICE_KEY: ${SERVICE_KEY:-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU} + ports: + - "54323:3000" + networks: + - usda-vision-network + restart: unless-stopped + + # Supabase Meta (for Studio) + supabase-meta: + container_name: usda-vision-supabase-meta + image: supabase/postgres-meta:v0.88.0 + depends_on: + supabase-db: + condition: service_healthy + environment: + PG_META_PORT: 8080 + PG_META_DB_HOST: supabase-db + PG_META_DB_PORT: 5432 + PG_META_DB_NAME: postgres + PG_META_DB_USER: supabase_admin + PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password} + ports: + - "54328:8080" + networks: + - usda-vision-network + restart: unless-stopped + + # Migration Runner - runs migrations after database is ready + supabase-migrate: + container_name: usda-vision-supabase-migrate + image: postgres:17-alpine + depends_on: + supabase-db: + condition: service_healthy + environment: + PGHOST: supabase-db + PGPORT: 5432 + PGDATABASE: postgres + PGUSER: supabase_admin + PGPASSWORD: ${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password} + volumes: + - ./management-dashboard-web-app/supabase/migrations:/migrations:ro + - ./management-dashboard-web-app/supabase/seed_01_users.sql:/seed_01_users.sql:ro + - ./management-dashboard-web-app/supabase/seed_02_phase2_experiments.sql:/seed_02_phase2_experiments.sql:ro + command: > + sh -c " + echo 'Waiting for database to be ready...'; + until pg_isready -h supabase-db -p 5432 -U supabase_admin; do + sleep 2; + done; + echo 'Database is ready. Running migrations...'; + for migration in /migrations/*.sql; do + if [ -f \"\$$migration\" ]; then + echo \"Running migration: \$$(basename \$$migration)\"; + psql -h supabase-db -U supabase_admin -d postgres -f \$$migration || echo \"Migration \$$(basename \$$migration) may have already been applied\"; + fi; + done; + echo 'Running seed files...'; + psql -h supabase-db -U supabase_admin -d postgres -f /seed_01_users.sql || echo 'Seed 01 may have already been applied'; + psql -h supabase-db -U supabase_admin -d postgres -f /seed_02_phase2_experiments.sql || echo 'Seed 02 may have already been applied'; + echo 'Migrations and seeds completed!'; + " + networks: + - usda-vision-network + restart: "no" + + # Supabase Inbucket (Email Testing) + supabase-inbucket: + container_name: usda-vision-supabase-inbucket + image: inbucket/inbucket:stable + ports: + - "54324:9000" + - "54325:2500" + - "54326:1100" + networks: + - usda-vision-network + restart: unless-stopped + api: container_name: usda-vision-api build: @@ -76,6 +329,7 @@ services: environment: - CHOKIDAR_USEPOLLING=true - TZ=America/New_York + - VITE_SUPABASE_URL=http://localhost:54321 command: > sh -lc " npm install; @@ -86,6 +340,13 @@ services: - "host.docker.internal:host-gateway" ports: - "8080:8080" + networks: + - usda-vision-network + depends_on: + supabase-rest: + condition: service_started + supabase-auth: + condition: service_started video-remote: container_name: usda-vision-video-remote @@ -107,6 +368,8 @@ services: - "host.docker.internal:host-gateway" ports: - "3001:3001" + networks: + - usda-vision-network vision-system-remote: container_name: usda-vision-vision-system-remote @@ -127,6 +390,8 @@ services: - "host.docker.internal:host-gateway" ports: - "3002:3002" + networks: + - usda-vision-network scheduling-remote: container_name: usda-vision-scheduling-remote @@ -148,6 +413,8 @@ services: - "host.docker.internal:host-gateway" ports: - "3003:3003" + networks: + - usda-vision-network media-api: container_name: usda-vision-media-api @@ -162,6 +429,8 @@ services: - /mnt/nfs_share:/mnt/nfs_share ports: - "8090:8090" + networks: + - usda-vision-network deploy: resources: limits: @@ -185,3 +454,5 @@ services: - "8554:8554" # RTSP - "8889:8889" # WebRTC HTTP API - "8189:8189" # WebRTC UDP + networks: + - usda-vision-network diff --git a/docs/SUPABASE_DOCKER_COMPOSE.md b/docs/SUPABASE_DOCKER_COMPOSE.md new file mode 100644 index 0000000..c486857 --- /dev/null +++ b/docs/SUPABASE_DOCKER_COMPOSE.md @@ -0,0 +1,107 @@ +# Supabase Integration with Docker Compose + +The Supabase containers are now integrated into the main `docker-compose.yml` file, so you no longer need to start them separately from the `management-dashboard-web-app` directory. + +## What Changed + +All Supabase services are now defined in the root `docker-compose.yml`: +- **supabase-db**: PostgreSQL database (port 54322) +- **supabase-rest**: PostgREST API (port 54321) +- **supabase-auth**: GoTrue authentication service (port 9999) +- **supabase-realtime**: Realtime subscriptions (port 4000) +- **supabase-storage**: Storage API (port 5000) +- **supabase-studio**: Supabase Studio UI (port 54323) +- **supabase-meta**: Database metadata service (port 54328) +- **supabase-inbucket**: Email testing server (port 54324) +- **supabase-migrate**: Migration runner (runs once on startup) + +## Usage + +### Starting All Services + +Simply run from the project root: + +```bash +docker compose up -d +``` + +This will start all services including Supabase containers. + +### Environment Variables + +The Supabase services use the following environment variables (with defaults for local development): + +- `POSTGRES_PASSWORD`: Database password (default: `your-super-secret-and-long-postgres-password`) +- `JWT_SECRET`: JWT signing secret (default: `your-super-secret-jwt-token-with-at-least-32-characters-long`) +- `ANON_KEY`: Anonymous/public key for client-side access +- `SERVICE_KEY`: Service role key for server-side access + +You can set these in a `.env` file in the project root, or export them before running `docker compose up`. + +### Web App Configuration + +Make sure your `management-dashboard-web-app/.env` file has: + +```env +VITE_SUPABASE_URL=http://localhost:54321 +VITE_SUPABASE_ANON_KEY= +``` + +The default anon key for local development is: +``` +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0 +``` + +### Migrations + +Migrations are automatically run on first startup via the `supabase-migrate` service. The service: +1. Waits for the database to be ready +2. Runs all migrations from `management-dashboard-web-app/supabase/migrations/` in alphabetical order +3. Runs seed files (`seed_01_users.sql` and `seed_02_phase2_experiments.sql`) + +If you need to re-run migrations, you can: +1. Stop the containers: `docker compose down` +2. Remove the database volume: `docker volume rm usda-vision_supabase-db` +3. Start again: `docker compose up -d` + +### Accessing Services + +- **Supabase API**: http://localhost:54321 +- **Supabase Studio**: http://localhost:54323 +- **Email Testing (Inbucket)**: http://localhost:54324 +- **Database (direct)**: localhost:54322 + +### Network + +All services are on the `usda-vision-network` bridge network, so they can communicate with each other using service names (e.g., `supabase-db`, `supabase-rest`). + +## Migration from Supabase CLI + +If you were previously using `supabase start` from the `management-dashboard-web-app` directory: + +1. Stop any running Supabase containers from the CLI +2. The new setup uses the same ports, so make sure nothing is conflicting +3. Start the new setup with `docker compose up -d` from the project root + +## Troubleshooting + +### Port Conflicts + +If you get port conflicts, make sure: +- No other Supabase instances are running +- The Supabase CLI isn't running containers (`supabase stop` if needed) + +### Migration Issues + +If migrations fail: +1. Check the logs: `docker compose logs supabase-migrate` +2. Ensure migration files are valid SQL +3. You may need to manually connect to the database and fix issues + +### Database Connection Issues + +If services can't connect to the database: +1. Check database is healthy: `docker compose ps supabase-db` +2. Check logs: `docker compose logs supabase-db` +3. Ensure the database password matches across all services + diff --git a/scripts/setup-supabase-secrets.sh b/scripts/setup-supabase-secrets.sh new file mode 100755 index 0000000..aa7c743 --- /dev/null +++ b/scripts/setup-supabase-secrets.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Script to generate Supabase JWT secrets and keys for local development +# This generates the anon key and service role key based on the JWT secret + +set -e + +# Default values (can be overridden by environment variables) +JWT_SECRET="${JWT_SECRET:-$(openssl rand -base64 32)}" +POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password}" + +# Generate JWT tokens (anon and service_role) +# These are simplified versions - in production, use Supabase's key generation +ANON_KEY="${ANON_KEY:-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0}" +SERVICE_KEY="${SERVICE_KEY:-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU}" + +echo "Supabase Configuration for Docker Compose" +echo "=========================================" +echo "" +echo "Add these to your .env file or export them:" +echo "" +echo "POSTGRES_PASSWORD=${POSTGRES_PASSWORD}" +echo "JWT_SECRET=${JWT_SECRET}" +echo "ANON_KEY=${ANON_KEY}" +echo "SERVICE_KEY=${SERVICE_KEY}" +echo "" +echo "For the web app (.env in management-dashboard-web-app/):" +echo "VITE_SUPABASE_URL=http://localhost:54321" +echo "VITE_SUPABASE_ANON_KEY=${ANON_KEY}" +echo "" + From a52f3fbf2465bae77b0ef53aa978ed32e1715140 Mon Sep 17 00:00:00 2001 From: salirezav Date: Thu, 18 Dec 2025 18:27:04 -0500 Subject: [PATCH 02/13] Refactor Supabase services in docker-compose.yml for better organization and testing - Commented out all Supabase services to facilitate testing with Supabase CLI. - Updated README to include Supabase directory in project structure. - Adjusted documentation for migration paths in Supabase Docker Compose guide. - Enhanced docker-compose-reset.sh to explicitly remove Supabase volumes and wait for migrations to complete. --- README.md | 1 + docker-compose.yml | 544 +-- docs/DOCKER_COMPOSE_ORGANIZATION.md | 104 + docs/DOCKER_RATE_LIMIT_FIX.md | 141 + docs/RUNNING_SUPABASE_SERVICES.md | 258 ++ docs/SUPABASE_DOCKER_COMPOSE.md | 2 +- docs/SUPABASE_MIGRATION.md | 93 + scripts/docker-compose-reset.sh | 87 +- scripts/pull-supabase-images.sh | 80 + supabase/.branches/_current_branch | 1 + supabase/.temp/cli-latest | 1 + supabase/README.md | 99 + supabase/config.toml | 333 ++ .../00001_extensions_and_utilities.sql | 87 + supabase/migrations/00002_users_and_roles.sql | 237 ++ supabase/migrations/00003_machine_types.sql | 63 + .../migrations/00004_experiment_phases.sql | 77 + supabase/migrations/00005_experiments.sql | 70 + .../00006_experiment_repetitions.sql | 69 + .../migrations/00007_cracker_parameters.sql | 89 + .../migrations/00008_phase_data_tables.sql | 274 ++ .../00009_conductor_availability.sql | 193 + supabase/migrations/00011_views.sql | 195 + .../00012_unified_phase_executions.sql | 300 ++ ...dd_repetition_id_to_cracker_parameters.sql | 46 + ..._experiments_with_reps_and_phases_view.sql | 277 ++ supabase/seed_01_users.sql | 600 ++++ supabase/seed_02_phase2_experiments.sql | 3196 +++++++++++++++++ 28 files changed, 7264 insertions(+), 253 deletions(-) create mode 100644 docs/DOCKER_COMPOSE_ORGANIZATION.md create mode 100644 docs/DOCKER_RATE_LIMIT_FIX.md create mode 100644 docs/RUNNING_SUPABASE_SERVICES.md create mode 100644 docs/SUPABASE_MIGRATION.md mode change 100644 => 100755 scripts/docker-compose-reset.sh create mode 100755 scripts/pull-supabase-images.sh create mode 100644 supabase/.branches/_current_branch create mode 100644 supabase/.temp/cli-latest create mode 100644 supabase/README.md create mode 100755 supabase/config.toml create mode 100644 supabase/migrations/00001_extensions_and_utilities.sql create mode 100644 supabase/migrations/00002_users_and_roles.sql create mode 100644 supabase/migrations/00003_machine_types.sql create mode 100644 supabase/migrations/00004_experiment_phases.sql create mode 100644 supabase/migrations/00005_experiments.sql create mode 100644 supabase/migrations/00006_experiment_repetitions.sql create mode 100644 supabase/migrations/00007_cracker_parameters.sql create mode 100644 supabase/migrations/00008_phase_data_tables.sql create mode 100644 supabase/migrations/00009_conductor_availability.sql create mode 100644 supabase/migrations/00011_views.sql create mode 100644 supabase/migrations/00012_unified_phase_executions.sql create mode 100644 supabase/migrations/00013_add_repetition_id_to_cracker_parameters.sql create mode 100644 supabase/migrations/00014_experiments_with_reps_and_phases_view.sql create mode 100755 supabase/seed_01_users.sql create mode 100644 supabase/seed_02_phase2_experiments.sql diff --git a/README.md b/README.md index 74fc7e0..37f6fae 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ A unified monorepo combining the camera API service and the web dashboard for US - `camera-management-api/` - Python API service for camera management (USDA-Vision-Cameras) - `management-dashboard-web-app/` - React web dashboard for experiment management (pecan_experiments) +- `supabase/` - Database configuration, migrations, and seed data (shared infrastructure) ## Quick Start diff --git a/docker-compose.yml b/docker-compose.yml index 33e138f..1a2d85f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,250 +8,297 @@ volumes: supabase-storage: services: - # Supabase Database - supabase-db: - container_name: usda-vision-supabase-db - image: supabase/postgres:17.1.0.147 - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 10s - timeout: 5s - retries: 5 - volumes: - - supabase-db:/var/lib/postgresql/data - environment: - POSTGRES_HOST: /var/run/postgresql - PGPORT: 5432 - POSTGRES_PORT: 5432 - PGDATABASE: postgres - POSTGRES_DB: postgres - PGUSER: supabase_admin - POSTGRES_USER: supabase_admin - PGPASSWORD: ${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password} - JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-token-with-at-least-32-characters-long} - JWT_EXP: ${JWT_EXP:-3600} - ports: - - "54322:5432" - networks: - - usda-vision-network - restart: unless-stopped - - # Supabase PostgREST API - supabase-rest: - container_name: usda-vision-supabase-rest - image: postgrest/postgrest:v12.2.0 - depends_on: - supabase-db: - condition: service_healthy - supabase-migrate: - condition: service_completed_successfully - environment: - PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password}@supabase-db:5432/postgres - PGRST_DB_SCHEMAS: public,graphql_public - PGRST_DB_EXTRA_SEARCH_PATH: public,extensions - PGRST_DB_ANON_ROLE: anon - PGRST_JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-token-with-at-least-32-characters-long} - PGRST_DB_USE_LEGACY_GUCS: "false" - PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-token-with-at-least-32-characters-long} - PGRST_APP_SETTINGS_JWT_EXP: ${JWT_EXP:-3600} - ports: - - "54321:3000" - networks: - - usda-vision-network - restart: unless-stopped - - # Supabase GoTrue (Auth) - supabase-auth: - container_name: usda-vision-supabase-auth - image: supabase/gotrue:v2.156.0 - depends_on: - supabase-db: - condition: service_healthy - environment: - GOTRUE_API_HOST: 0.0.0.0 - GOTRUE_API_PORT: 9999 - API_EXTERNAL_URL: http://localhost:54321 - GOTRUE_DB_DRIVER: postgres - GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password}@supabase-db:5432/postgres - GOTRUE_SITE_URL: http://localhost:8080 - GOTRUE_URI_ALLOW_LIST: http://localhost:8080,http://localhost:3000,https://localhost:3000 - GOTRUE_DISABLE_SIGNUP: "false" - GOTRUE_JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-token-with-at-least-32-characters-long} - GOTRUE_JWT_EXP: ${JWT_EXP:-3600} - GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated - GOTRUE_EXTERNAL_EMAIL_ENABLED: "true" - GOTRUE_MAILER_AUTOCONFIRM: "true" - GOTRUE_SMS_AUTOCONFIRM: "true" - GOTRUE_SMS_PROVIDER: twilio - GOTRUE_ENABLE_SIGNUP: "true" - GOTRUE_ENABLE_ANONYMOUS_SIGN_INS: "false" - GOTRUE_ENABLE_MANUAL_LINKING: "false" - GOTRUE_PASSWORD_MIN_LENGTH: 6 - GOTRUE_REFRESH_TOKEN_ROTATION_ENABLED: "true" - GOTRUE_REFRESH_TOKEN_REUSE_INTERVAL: 10 - ports: - - "9999:9999" - networks: - - usda-vision-network - restart: unless-stopped - - # Supabase Realtime - supabase-realtime: - container_name: usda-vision-supabase-realtime - image: supabase/realtime:v2.30.25 - depends_on: - supabase-db: - condition: service_healthy - supabase-rest: - condition: service_started - environment: - PORT: 4000 - DB_HOST: supabase-db - DB_PORT: 5432 - DB_USER: supabase_realtime_admin - DB_PASSWORD: ${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password} - DB_NAME: postgres - DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime' - DB_ENC_KEY: supabaserealtime - API_JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-token-with-at-least-32-characters-long} - FLY_ALLOC_ID: fly123 - FLY_APP_NAME: realtime - SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq - ERL_AFLAGS: -proto_dist inet_tcp - ENABLE_TAILSCALE: "false" - DNS_NODES: "''" - ports: - - "4000:4000" - networks: - - usda-vision-network - restart: unless-stopped - - # Supabase Storage - supabase-storage: - container_name: usda-vision-supabase-storage - image: supabase/storage-api:v1.11.8 - depends_on: - supabase-db: - condition: service_healthy - supabase-rest: - condition: service_started - environment: - ANON_KEY: ${ANON_KEY:-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0} - SERVICE_KEY: ${SERVICE_KEY:-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU} - POSTGREST_URL: http://supabase-rest:3000 - PGRST_JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-token-with-at-least-32-characters-long} - DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password}@supabase-db:5432/postgres - FILE_SIZE_LIMIT: 52428800 - STORAGE_BACKEND: file - FILE_STORAGE_BACKEND_PATH: /var/lib/storage - TENANT_ID: stub - REGION: stub - GLOBAL_S3_BUCKET: stub - ENABLE_IMAGE_TRANSFORMATION: "false" - volumes: - - supabase-storage:/var/lib/storage - ports: - - "5000:5000" - networks: - - usda-vision-network - restart: unless-stopped - - # Supabase Studio - supabase-studio: - container_name: usda-vision-supabase-studio - image: supabase/studio:20241218-5c0e5a0 - depends_on: - supabase-rest: - condition: service_started - supabase-auth: - condition: service_started - environment: - STUDIO_PG_META_URL: http://supabase-meta:8080 - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password} - DEFAULT_ORGANIZATION_NAME: Default Organization - DEFAULT_PROJECT_NAME: Default Project - SUPABASE_URL: http://supabase-rest:3000 - SUPABASE_PUBLIC_URL: http://localhost:54321 - SUPABASE_ANON_KEY: ${ANON_KEY:-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0} - SUPABASE_SERVICE_KEY: ${SERVICE_KEY:-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU} - ports: - - "54323:3000" - networks: - - usda-vision-network - restart: unless-stopped - - # Supabase Meta (for Studio) - supabase-meta: - container_name: usda-vision-supabase-meta - image: supabase/postgres-meta:v0.88.0 - depends_on: - supabase-db: - condition: service_healthy - environment: - PG_META_PORT: 8080 - PG_META_DB_HOST: supabase-db - PG_META_DB_PORT: 5432 - PG_META_DB_NAME: postgres - PG_META_DB_USER: supabase_admin - PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password} - ports: - - "54328:8080" - networks: - - usda-vision-network - restart: unless-stopped - - # Migration Runner - runs migrations after database is ready - supabase-migrate: - container_name: usda-vision-supabase-migrate - image: postgres:17-alpine - depends_on: - supabase-db: - condition: service_healthy - environment: - PGHOST: supabase-db - PGPORT: 5432 - PGDATABASE: postgres - PGUSER: supabase_admin - PGPASSWORD: ${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password} - volumes: - - ./management-dashboard-web-app/supabase/migrations:/migrations:ro - - ./management-dashboard-web-app/supabase/seed_01_users.sql:/seed_01_users.sql:ro - - ./management-dashboard-web-app/supabase/seed_02_phase2_experiments.sql:/seed_02_phase2_experiments.sql:ro - command: > - sh -c " - echo 'Waiting for database to be ready...'; - until pg_isready -h supabase-db -p 5432 -U supabase_admin; do - sleep 2; - done; - echo 'Database is ready. Running migrations...'; - for migration in /migrations/*.sql; do - if [ -f \"\$$migration\" ]; then - echo \"Running migration: \$$(basename \$$migration)\"; - psql -h supabase-db -U supabase_admin -d postgres -f \$$migration || echo \"Migration \$$(basename \$$migration) may have already been applied\"; - fi; - done; - echo 'Running seed files...'; - psql -h supabase-db -U supabase_admin -d postgres -f /seed_01_users.sql || echo 'Seed 01 may have already been applied'; - psql -h supabase-db -U supabase_admin -d postgres -f /seed_02_phase2_experiments.sql || echo 'Seed 02 may have already been applied'; - echo 'Migrations and seeds completed!'; - " - networks: - - usda-vision-network - restart: "no" - - # Supabase Inbucket (Email Testing) - supabase-inbucket: - container_name: usda-vision-supabase-inbucket - image: inbucket/inbucket:stable - ports: - - "54324:9000" - - "54325:2500" - - "54326:1100" - networks: - - usda-vision-network - restart: unless-stopped + # ============================================================================ + # Supabase Services (Database & Backend) - COMMENTED OUT + # ============================================================================ + # All Supabase services are grouped together and labeled with "supabase" + # namespace for better organization. They start by default with all services. + # To manage Supabase services separately, use: + # - Filter by label: docker compose ps --filter "label=com.usda-vision.service=supabase" + # - Or use service names: docker compose ps supabase-* + # + # NOTE: Currently commented out to test Supabase CLI setup from management-dashboard-web-app + # # # Supabase Database + # # supabase-db: + # container_name: usda-vision-supabase-db + # image: public.ecr.aws/supabase/postgres:17.4.1.068 + # labels: + # - "com.usda-vision.service=supabase" + # - "com.usda-vision.service.group=database" + # healthcheck: + # test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"] + # interval: 10s + # timeout: 5s + # retries: 5 + # start_period: 30s + # volumes: + # - supabase-db:/var/lib/postgresql/data + # environment: + # POSTGRES_HOST: /var/run/postgresql + # PGPORT: 5432 + # POSTGRES_PORT: 5432 + # PGDATABASE: postgres + # POSTGRES_DB: postgres + # # Use postgres as the default user (Supabase image will create supabase_admin during init) + # PGUSER: postgres + # POSTGRES_USER: postgres + # PGPASSWORD: ${POSTGRES_PASSWORD:-postgres} + # POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} + # # Supabase-specific environment variables + # JWT_SECRET: ${JWT_SECRET:-super-secret-jwt-token-with-at-least-32-characters-long} + # JWT_EXP: ${JWT_EXP:-3600} + # ports: + # - "54322:5432" + # networks: + # - usda-vision-network + # restart: unless-stopped + # + # # Supabase PostgREST API + # supabase-rest: + # container_name: usda-vision-supabase-rest + # image: public.ecr.aws/supabase/postgrest:v12.2.12 + # labels: + # - "com.usda-vision.service=supabase" + # - "com.usda-vision.service.group=api" + # depends_on: + # supabase-db: + # condition: service_healthy + # supabase-migrate: + # condition: service_completed_successfully + # environment: + # PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD:-postgres}@supabase-db:5432/postgres + # PGRST_DB_SCHEMAS: public,graphql_public + # PGRST_DB_EXTRA_SEARCH_PATH: public,extensions + # PGRST_DB_ANON_ROLE: anon + # PGRST_JWT_SECRET: ${JWT_SECRET:-super-secret-jwt-token-with-at-least-32-characters-long} + # PGRST_DB_USE_LEGACY_GUCS: "false" + # PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-token-with-at-least-32-characters-long} + # PGRST_APP_SETTINGS_JWT_EXP: ${JWT_EXP:-3600} + # ports: + # - "54321:3000" + # networks: + # - usda-vision-network + # restart: unless-stopped + # + # # Supabase GoTrue (Auth) + # supabase-auth: + # container_name: usda-vision-supabase-auth + # image: public.ecr.aws/supabase/gotrue:v2.177.0 + # labels: + # - "com.usda-vision.service=supabase" + # - "com.usda-vision.service.group=auth" + # depends_on: + # supabase-db: + # condition: service_healthy + # environment: + # GOTRUE_API_HOST: 0.0.0.0 + # GOTRUE_API_PORT: 9999 + # API_EXTERNAL_URL: http://localhost:54321 + # GOTRUE_DB_DRIVER: postgres + # GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD:-postgres}@supabase-db:5432/postgres + # GOTRUE_SITE_URL: http://localhost:8080 + # GOTRUE_URI_ALLOW_LIST: http://localhost:8080,http://localhost:3000,https://localhost:3000 + # GOTRUE_DISABLE_SIGNUP: "false" + # GOTRUE_JWT_SECRET: ${JWT_SECRET:-super-secret-jwt-token-with-at-least-32-characters-long} + # GOTRUE_JWT_EXP: ${JWT_EXP:-3600} + # GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated + # GOTRUE_EXTERNAL_EMAIL_ENABLED: "true" + # GOTRUE_MAILER_AUTOCONFIRM: "true" + # GOTRUE_SMS_AUTOCONFIRM: "true" + # GOTRUE_SMS_PROVIDER: twilio + # GOTRUE_ENABLE_SIGNUP: "true" + # GOTRUE_ENABLE_ANONYMOUS_SIGN_INS: "false" + # GOTRUE_ENABLE_MANUAL_LINKING: "false" + # GOTRUE_PASSWORD_MIN_LENGTH: 6 + # GOTRUE_REFRESH_TOKEN_ROTATION_ENABLED: "true" + # GOTRUE_REFRESH_TOKEN_REUSE_INTERVAL: 10 + # ports: + # - "9999:9999" + # networks: + # - usda-vision-network + # restart: unless-stopped + # + # # Supabase Realtime + # supabase-realtime: + # container_name: usda-vision-supabase-realtime + # image: public.ecr.aws/supabase/realtime:v2.41.10 + # labels: + # - "com.usda-vision.service=supabase" + # - "com.usda-vision.service.group=realtime" + # depends_on: + # supabase-db: + # condition: service_healthy + # supabase-rest: + # condition: service_started + # environment: + # PORT: 4000 + # DB_HOST: supabase-db + # DB_PORT: 5432 + # DB_USER: supabase_realtime_admin + # DB_PASSWORD: ${POSTGRES_PASSWORD:-postgres} + # DB_NAME: postgres + # DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime' + # DB_ENC_KEY: supabaserealtime + # API_JWT_SECRET: ${JWT_SECRET:-super-secret-jwt-token-with-at-least-32-characters-long} + # FLY_ALLOC_ID: fly123 + # FLY_APP_NAME: realtime + # SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq + # ERL_AFLAGS: -proto_dist inet_tcp + # ENABLE_TAILSCALE: "false" + # DNS_NODES: "''" + # ports: + # - "4000:4000" + # networks: + # - usda-vision-network + # restart: unless-stopped + # + # # Supabase Storage + # supabase-storage: + # container_name: usda-vision-supabase-storage + # image: public.ecr.aws/supabase/storage-api:v1.25.12 + # labels: + # - "com.usda-vision.service=supabase" + # - "com.usda-vision.service.group=storage" + # depends_on: + # supabase-db: + # condition: service_healthy + # supabase-rest: + # condition: service_started + # environment: + # ANON_KEY: ${ANON_KEY:-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0} + # SERVICE_KEY: ${SERVICE_KEY:-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU} + # POSTGREST_URL: http://supabase-rest:3000 + # PGRST_JWT_SECRET: ${JWT_SECRET:-super-secret-jwt-token-with-at-least-32-characters-long} + # DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD:-postgres}@supabase-db:5432/postgres + # FILE_SIZE_LIMIT: 52428800 + # STORAGE_BACKEND: file + # FILE_STORAGE_BACKEND_PATH: /var/lib/storage + # TENANT_ID: stub + # REGION: stub + # GLOBAL_S3_BUCKET: stub + # ENABLE_IMAGE_TRANSFORMATION: "false" + # volumes: + # - supabase-storage:/var/lib/storage + # ports: + # - "5000:5000" + # networks: + # - usda-vision-network + # restart: unless-stopped + # + # # Supabase Studio + # supabase-studio: + # container_name: usda-vision-supabase-studio + # image: public.ecr.aws/supabase/studio:2025.07.28-sha-578b707 + # labels: + # - "com.usda-vision.service=supabase" + # - "com.usda-vision.service.group=studio" + # depends_on: + # supabase-rest: + # condition: service_started + # supabase-auth: + # condition: service_started + # environment: + # STUDIO_PG_META_URL: http://supabase-meta:8080 + # POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} + # DEFAULT_ORGANIZATION_NAME: Default Organization + # DEFAULT_PROJECT_NAME: Default Project + # SUPABASE_URL: http://supabase-rest:3000 + # SUPABASE_PUBLIC_URL: http://localhost:54321 + # SUPABASE_ANON_KEY: ${ANON_KEY:-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0} + # SUPABASE_SERVICE_KEY: ${SERVICE_KEY:-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU} + # ports: + # - "54323:3000" + # networks: + # - usda-vision-network + # restart: unless-stopped + # + # # Supabase Meta (for Studio) + # supabase-meta: + # container_name: usda-vision-supabase-meta + # image: public.ecr.aws/supabase/postgres-meta:v0.91.3 + # labels: + # - "com.usda-vision.service=supabase" + # - "com.usda-vision.service.group=meta" + # depends_on: + # supabase-db: + # condition: service_healthy + # environment: + # PG_META_PORT: 8080 + # PG_META_DB_HOST: supabase-db + # PG_META_DB_PORT: 5432 + # PG_META_DB_NAME: postgres + # PG_META_DB_USER: supabase_admin + # PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD:-your-super-secret-and-long-postgres-password} + # ports: + # - "54328:8080" + # networks: + # - usda-vision-network + # restart: unless-stopped + # + # # Migration Runner - runs migrations after database is ready + # supabase-migrate: + # container_name: usda-vision-supabase-migrate + # image: postgres:17-alpine + # labels: + # - "com.usda-vision.service=supabase" + # - "com.usda-vision.service.group=migration" + # depends_on: + # supabase-db: + # condition: service_healthy + # environment: + # PGHOST: supabase-db + # PGPORT: 5432 + # PGDATABASE: postgres + # PGUSER: postgres + # PGPASSWORD: ${POSTGRES_PASSWORD:-postgres} + # volumes: + # - ./supabase/migrations:/migrations:ro + # - ./supabase/seed_01_users.sql:/seed_01_users.sql:ro + # - ./supabase/seed_02_phase2_experiments.sql:/seed_02_phase2_experiments.sql:ro + # command: > + # sh -c " + # echo 'Waiting for database to be ready...'; + # until pg_isready -h supabase-db -p 5432 -U postgres -d postgres; do + # sleep 2; + # done; + # echo 'Waiting for Supabase initialization to complete...'; + # # Wait for Supabase schemas to be initialized (auth schema is a good indicator) + # until psql -h supabase-db -U postgres -d postgres -tc \"SELECT 1 FROM information_schema.schemata WHERE schema_name = 'auth'\" | grep -q 1; do + # echo ' Waiting for Supabase schemas...'; + # sleep 3; + # done; + # echo 'Database and Supabase schemas are ready. Running migrations...'; + # for migration in /migrations/*.sql; do + # if [ -f \"\$$migration\" ]; then + # echo \"Running migration: \$$(basename \$$migration)\"; + # psql -h supabase-db -U postgres -d postgres -f \$$migration || echo \"Migration \$$(basename \$$migration) may have already been applied\"; + # fi; + # done; + # echo 'Running seed files...'; + # psql -h supabase-db -U postgres -d postgres -f /seed_01_users.sql || echo 'Seed 01 may have already been applied'; + # psql -h supabase-db -U postgres -d postgres -f /seed_02_phase2_experiments.sql || echo 'Seed 02 may have already been applied'; + # echo 'Migrations and seeds completed!'; + # " + # networks: + # - usda-vision-network + # restart: "no" + # + # # Supabase Inbucket (Email Testing) + # supabase-inbucket: + # container_name: usda-vision-supabase-inbucket + # image: inbucket/inbucket:stable + # labels: + # - "com.usda-vision.service=supabase" + # - "com.usda-vision.service.group=email" + # ports: + # - "54324:9000" + # - "54325:2500" + # - "54326:1100" + # networks: + # - usda-vision-network + # restart: unless-stopped + # api: container_name: usda-vision-api build: @@ -342,11 +389,12 @@ services: - "8080:8080" networks: - usda-vision-network - depends_on: - supabase-rest: - condition: service_started - supabase-auth: - condition: service_started + # depends_on: + # supabase-rest: + # condition: service_started + # supabase-auth: + # condition: service_started + # NOTE: Supabase dependencies commented out - using Supabase CLI instead video-remote: container_name: usda-vision-video-remote diff --git a/docs/DOCKER_COMPOSE_ORGANIZATION.md b/docs/DOCKER_COMPOSE_ORGANIZATION.md new file mode 100644 index 0000000..21cd1a6 --- /dev/null +++ b/docs/DOCKER_COMPOSE_ORGANIZATION.md @@ -0,0 +1,104 @@ +# Docker Compose Service Organization + +## Service Namespaces + +Services in the docker-compose.yml are organized using labels to create logical namespaces. This allows for better organization and easier management of related services. + +## Supabase Namespace + +All Supabase services are grouped under the `com.usda-vision.service=supabase` label namespace: + +### Services in Supabase Namespace + +- **supabase-db** - PostgreSQL database (group: `database`) +- **supabase-rest** - PostgREST API (group: `api`) +- **supabase-auth** - GoTrue authentication (group: `auth`) +- **supabase-realtime** - Realtime subscriptions (group: `realtime`) +- **supabase-storage** - Storage API (group: `storage`) +- **supabase-studio** - Supabase Studio UI (group: `studio`) +- **supabase-meta** - Database metadata service (group: `meta`) +- **supabase-migrate** - Migration runner (group: `migration`) +- **supabase-inbucket** - Email testing server (group: `email`) + +### Managing Supabase Services + +#### List all Supabase services: +```bash +docker compose ps --filter "label=com.usda-vision.service=supabase" +``` + +#### View logs for all Supabase services: +```bash +docker compose logs supabase-* +``` + +#### Stop all Supabase services: +```bash +docker compose stop supabase-* +``` + +#### Start all Supabase services: +```bash +docker compose start supabase-* +``` + +#### Restart all Supabase services: +```bash +docker compose restart supabase-* +``` + +#### View logs for a specific Supabase service group: +```bash +# Database services +docker compose logs supabase-db supabase-migrate + +# API services +docker compose logs supabase-rest supabase-auth + +# UI services +docker compose logs supabase-studio supabase-meta +``` + +### Label Structure + +Each Supabase service has two labels: +- `com.usda-vision.service=supabase` - Identifies it as part of the Supabase namespace +- `com.usda-vision.service.group=` - Identifies the service's functional group + +### Benefits + +1. **Better Organization**: All Supabase services are visually grouped in the compose file +2. **Easy Filtering**: Use labels to filter and manage related services +3. **Clear Ownership**: Makes it obvious which services belong together +4. **No Behavior Change**: Services still start by default with `docker compose up` +5. **Flexible Management**: Can manage Supabase services separately when needed + +## Future Namespaces + +You can extend this pattern to other service groups: + +```yaml +services: + api: + labels: + - "com.usda-vision.service=application" + - "com.usda-vision.service.group=api" + + web: + labels: + - "com.usda-vision.service=application" + - "com.usda-vision.service.group=frontend" +``` + +## Visual Organization in docker-compose.yml + +The Supabase services are also visually organized with a comment section: + +```yaml +# ============================================================================ +# Supabase Services (Database & Backend) +# ============================================================================ +``` + +This makes it easy to find and understand the Supabase services section when viewing the file. + diff --git a/docs/DOCKER_RATE_LIMIT_FIX.md b/docs/DOCKER_RATE_LIMIT_FIX.md new file mode 100644 index 0000000..3e77845 --- /dev/null +++ b/docs/DOCKER_RATE_LIMIT_FIX.md @@ -0,0 +1,141 @@ +# Fixing Docker Hub Rate Limit Issues + +## Problem + +When pulling multiple Docker images, you may encounter: +``` +Error response from daemon: toomanyrequests: Rate exceeded +``` + +This happens because Docker Hub limits the number of image pulls for anonymous users. + +## Solutions + +### Solution 1: Login to Docker Hub (Recommended) + +Logging in to Docker Hub increases your rate limit significantly: + +```bash +docker login +``` + +Enter your Docker Hub username and password. This increases your rate limit from ~100 pulls per 6 hours to ~200 pulls per 6 hours. + +### Solution 2: Pull Images One at a Time + +Use the provided script to pull images with delays: + +```bash +./scripts/pull-supabase-images.sh +``` + +This script: +- Pulls images one at a time +- Waits 10 seconds between pulls +- Retries on rate limit errors +- Shows progress and summary + +### Solution 3: Manual Pull with Delays + +Pull images manually with delays: + +```bash +# Pull one image at a time with delays +docker compose pull supabase-db +sleep 10 +docker compose pull supabase-rest +sleep 10 +docker compose pull supabase-auth +sleep 10 +# ... continue for other services +``` + +### Solution 4: Wait and Retry + +If you hit the rate limit: + +1. **Wait 5-10 minutes** for the rate limit window to reset +2. **Try again**: + ```bash + docker compose pull supabase-* + ``` + +### Solution 5: Use Cached Images + +Check if images are already available locally: + +```bash +# Check what Supabase images you have +docker images | grep supabase + +# If images exist, you can start services without pulling +docker compose up -d supabase-* +``` + +## Current Rate Limits + +- **Anonymous users**: ~100 pulls per 6 hours per IP +- **Authenticated users**: ~200 pulls per 6 hours per account +- **Paid plans**: Higher limits + +## Quick Fix for Supabase Services + +If you just need to start Supabase services and have some images cached: + +```bash +# 1. Check what's already available +docker images | grep -E "(supabase|postgres|inbucket)" + +# 2. Try starting services (will only pull missing images) +docker compose up -d supabase-db supabase-rest supabase-auth supabase-realtime supabase-storage supabase-studio supabase-meta supabase-migrate supabase-inbucket + +# 3. If rate limited, wait 5 minutes and try pulling specific images: +docker compose pull supabase-rest +# Wait 10 seconds +docker compose pull supabase-auth +# Continue... +``` + +## Alternative: Use AWS ECR Directly + +Since Supabase images are on AWS ECR (`public.ecr.aws/supabase/`), you can pull directly: + +```bash +# Pull directly from ECR (may have different rate limits) +docker pull public.ecr.aws/supabase/postgrest:v12.2.12 +docker pull public.ecr.aws/supabase/gotrue:v2.177.0 +# ... etc +``` + +## Prevention + +1. **Always login**: `docker login` before pulling many images +2. **Use local images**: Keep images locally when possible +3. **Pull gradually**: Don't pull all images at once +4. **Use image caching**: Docker caches layers, so subsequent pulls are faster + +## Verify Images Are Available + +After pulling, verify: + +```bash +# List all Supabase-related images +docker images | grep -E "(supabase|postgrest|gotrue|realtime|storage|studio|postgres-meta|inbucket)" + +# Check specific service images +docker images public.ecr.aws/supabase/postgrest +docker images public.ecr.aws/supabase/gotrue +``` + +## Start Services After Pulling + +Once images are pulled: + +```bash +# Start all Supabase services +docker compose up -d supabase-db supabase-rest supabase-auth supabase-realtime supabase-storage supabase-studio supabase-meta supabase-migrate supabase-inbucket + +# Or start everything +docker compose up -d +``` + diff --git a/docs/RUNNING_SUPABASE_SERVICES.md b/docs/RUNNING_SUPABASE_SERVICES.md new file mode 100644 index 0000000..7cb9a1b --- /dev/null +++ b/docs/RUNNING_SUPABASE_SERVICES.md @@ -0,0 +1,258 @@ +# Running Supabase Services with Docker Compose + +## Quick Start + +### Start All Supabase Services + +```bash +# From project root +docker compose up -d supabase-db supabase-rest supabase-auth supabase-realtime supabase-storage supabase-studio supabase-meta supabase-migrate supabase-inbucket +``` + +Or use the shorthand (all services starting with `supabase-`): + +```bash +docker compose up -d $(docker compose config --services | grep supabase | tr '\n' ' ') +``` + +### Start All Services (Including Supabase) + +```bash +# Start everything including Supabase +docker compose up -d +``` + +## Individual Service Management + +### Start Specific Services + +```bash +# Start just the database +docker compose up -d supabase-db + +# Start database + API +docker compose up -d supabase-db supabase-rest + +# Start all core services (db, rest, auth) +docker compose up -d supabase-db supabase-rest supabase-auth +``` + +### Stop Supabase Services + +```bash +# Stop all Supabase services +docker compose stop supabase-* + +# Or stop specific services +docker compose stop supabase-db supabase-rest +``` + +### Restart Supabase Services + +```bash +# Restart all Supabase services +docker compose restart supabase-* + +# Restart specific service +docker compose restart supabase-db +``` + +## Checking Service Status + +### List All Supabase Services + +```bash +# Using labels (recommended) +docker compose ps --filter "label=com.usda-vision.service=supabase" + +# Using service name pattern +docker compose ps supabase-* + +# Or just check all services +docker compose ps +``` + +### View Logs + +```bash +# All Supabase services +docker compose logs -f supabase-* + +# Specific service +docker compose logs -f supabase-db + +# Last 50 lines of a service +docker compose logs --tail=50 supabase-migrate +``` + +### Check Service Health + +```bash +# Check if database is healthy +docker compose ps supabase-db + +# Check migration status +docker compose logs supabase-migrate | tail -20 + +# Check all Supabase services status +docker compose ps --filter "label=com.usda-vision.service=supabase" --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" +``` + +## Service Dependencies + +Supabase services have dependencies. Start them in this order for best results: + +1. **supabase-db** - Database (must start first) +2. **supabase-migrate** - Runs migrations (depends on db) +3. **supabase-rest** - API (depends on db + migrate) +4. **supabase-auth** - Authentication (depends on db) +5. **supabase-meta** - Metadata service (depends on db) +6. **supabase-realtime** - Realtime (depends on db + rest) +7. **supabase-storage** - Storage (depends on db + rest) +8. **supabase-studio** - Studio UI (depends on rest + auth) +9. **supabase-inbucket** - Email testing (independent) + +**Note**: Docker Compose handles dependencies automatically via `depends_on`, so you can start them all at once and they'll start in the correct order. + +## Common Commands + +### Start Everything + +```bash +docker compose up -d +``` + +### Start Only Supabase + +```bash +docker compose up -d supabase-db supabase-rest supabase-auth supabase-realtime supabase-storage supabase-studio supabase-meta supabase-migrate supabase-inbucket +``` + +### Stop Everything + +```bash +docker compose down +``` + +### Stop Only Supabase (Keep Other Services Running) + +```bash +docker compose stop supabase-* +``` + +### View Real-time Logs + +```bash +# All Supabase services +docker compose logs -f supabase-* + +# Specific service +docker compose logs -f supabase-db +``` + +### Reset Supabase (Remove Volumes) + +```bash +# Stop and remove Supabase containers and volumes +docker compose down -v supabase-* + +# Or use the reset script +./scripts/docker-compose-reset.sh +``` + +## Troubleshooting + +### Rate Limit Errors + +If you see `toomanyrequests: Rate exceeded`, Docker Hub is rate-limiting you. Solutions: + +1. **Wait a few minutes** and try again +2. **Login to Docker Hub** (increases rate limit): + ```bash + docker login + ``` +3. **Use cached images** if available: + ```bash + docker images | grep supabase + ``` + +### Services Not Starting + +1. **Check logs**: + ```bash + docker compose logs supabase-db + ``` + +2. **Check if ports are in use**: + ```bash + # Check if port 54322 (database) is in use + sudo lsof -i :54322 + ``` + +3. **Verify network exists**: + ```bash + docker network ls | grep usda-vision + ``` + +### Migration Issues + +If migrations fail: + +1. **Check migration logs**: + ```bash + docker compose logs supabase-migrate + ``` + +2. **Manually run migrations**: + ```bash + docker compose exec supabase-db psql -U postgres -d postgres -f /path/to/migration.sql + ``` + +3. **Reset database** (⚠️ **WARNING**: This deletes all data): + ```bash + docker compose down -v supabase-db + docker compose up -d supabase-db + ``` + +## Service URLs + +Once running, access services at: + +- **Supabase API**: http://localhost:54321 +- **Supabase Studio**: http://localhost:54323 +- **Database (direct)**: `postgresql://postgres:postgres@localhost:54322/postgres` +- **Email Testing (Inbucket)**: http://localhost:54324 +- **Realtime**: ws://localhost:4000 +- **Storage API**: http://localhost:5000 + +## Best Practices + +1. **Always start database first** (or let Compose handle dependencies) +2. **Wait for migrations** before starting dependent services +3. **Check logs** if services fail to start +4. **Use labels** to filter and manage Supabase services +5. **Keep volumes** for persistent data (don't use `-v` flag unless resetting) + +## Example: Full Startup Sequence + +```bash +# 1. Start database +docker compose up -d supabase-db + +# 2. Wait for database to be ready (check logs) +docker compose logs -f supabase-db + +# 3. Start all other Supabase services +docker compose up -d supabase-rest supabase-auth supabase-realtime supabase-storage supabase-studio supabase-meta supabase-migrate supabase-inbucket + +# 4. Verify all services are running +docker compose ps --filter "label=com.usda-vision.service=supabase" +``` + +Or simply: + +```bash +# Start everything at once (Compose handles dependencies) +docker compose up -d +``` + diff --git a/docs/SUPABASE_DOCKER_COMPOSE.md b/docs/SUPABASE_DOCKER_COMPOSE.md index c486857..da90499 100644 --- a/docs/SUPABASE_DOCKER_COMPOSE.md +++ b/docs/SUPABASE_DOCKER_COMPOSE.md @@ -56,7 +56,7 @@ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImF Migrations are automatically run on first startup via the `supabase-migrate` service. The service: 1. Waits for the database to be ready -2. Runs all migrations from `management-dashboard-web-app/supabase/migrations/` in alphabetical order +2. Runs all migrations from `supabase/migrations/` in alphabetical order 3. Runs seed files (`seed_01_users.sql` and `seed_02_phase2_experiments.sql`) If you need to re-run migrations, you can: diff --git a/docs/SUPABASE_MIGRATION.md b/docs/SUPABASE_MIGRATION.md new file mode 100644 index 0000000..974ca29 --- /dev/null +++ b/docs/SUPABASE_MIGRATION.md @@ -0,0 +1,93 @@ +# Supabase Directory Migration + +## What Changed + +The Supabase configuration has been moved from `management-dashboard-web-app/supabase/` to the project root `supabase/` directory. + +## Why This Change? + +1. **Better Architecture**: Supabase is shared infrastructure, not specific to the web app +2. **Monorepo Best Practice**: Infrastructure concerns should be at the root level +3. **Easier Access**: Other services (APIs, scripts) can now easily reference the database +4. **Clearer Ownership**: Makes it obvious that Supabase is a project-wide resource + +## Migration Steps + +### For Docker Compose Users + +No action needed! The `docker-compose.yml` has been updated to use the new paths. Just restart your containers: + +```bash +docker compose down +docker compose up -d +``` + +### For Supabase CLI Users + +**Before** (old way): +```bash +cd management-dashboard-web-app +supabase start +supabase db reset +``` + +**After** (new way): +```bash +# From project root - no need to cd! +supabase start +supabase db reset +``` + +The Supabase CLI automatically looks for the `supabase/` directory in the current working directory, so you can now run all Supabase commands from the project root. + +### Updating Your Workflow + +If you have scripts or documentation that reference the old path, update them: + +- ❌ `management-dashboard-web-app/supabase/migrations/` +- ✅ `supabase/migrations/` + +- ❌ `management-dashboard-web-app/supabase/config.toml` +- ✅ `supabase/config.toml` + +## Backward Compatibility + +The old directory (`management-dashboard-web-app/supabase/`) can be kept for reference, but it's no longer used by docker-compose or the Supabase CLI. You can safely remove it after verifying everything works: + +```bash +# After verifying everything works with the new location +rm -rf management-dashboard-web-app/supabase +``` + +## Verification + +To verify the migration worked: + +1. **Check docker-compose paths**: + ```bash + grep -r "supabase" docker-compose.yml + # Should show: ./supabase/ (not ./management-dashboard-web-app/supabase/) + ``` + +2. **Test Supabase CLI**: + ```bash + # From project root + supabase status + # Should work without needing to cd into management-dashboard-web-app + ``` + +3. **Test migrations**: + ```bash + docker compose up -d + docker compose logs supabase-migrate + # Should show migrations running successfully + ``` + +## Benefits + +✅ Run Supabase commands from project root +✅ Clearer project structure +✅ Easier to share database across services +✅ Better alignment with monorepo best practices +✅ Infrastructure separated from application code + diff --git a/scripts/docker-compose-reset.sh b/scripts/docker-compose-reset.sh old mode 100644 new mode 100755 index 9af7176..8183614 --- a/scripts/docker-compose-reset.sh +++ b/scripts/docker-compose-reset.sh @@ -2,8 +2,10 @@ # Docker Compose Reset Script # This script performs a complete reset of the Docker Compose environment: # - Stops and removes containers, networks, and volumes +# - Explicitly removes Supabase volumes (supabase-db, supabase-storage) # - Prunes unused Docker resources (containers, images, networks, volumes) # - Rebuilds and starts all services in detached mode +# - Waits for Supabase migrations to complete set -e # Exit on error @@ -24,7 +26,23 @@ echo "1. Stopping and removing containers, networks, and volumes..." docker compose down -v echo "" -echo "2. Pruning unused Docker resources..." +echo "2. Removing Supabase volumes explicitly..." +# Find and remove Supabase volumes (they may be named with project prefix) +# Docker Compose typically names volumes as: _ +# Try to find volumes containing 'supabase-db' or 'supabase-storage' +SUPABASE_VOLUMES=$(docker volume ls --format "{{.Name}}" | grep -E "(supabase-db|supabase-storage)" || true) + +if [ -n "$SUPABASE_VOLUMES" ]; then + echo "$SUPABASE_VOLUMES" | while read -r volume; do + echo " - Removing Supabase volume: $volume" + docker volume rm "$volume" 2>/dev/null || echo " (Volume may have been removed already or is in use)" + done +else + echo " - No Supabase volumes found (they may have been removed already)" +fi +echo "" + +echo "3. Pruning unused Docker resources..." echo " - Pruning unused containers..." docker container prune -f @@ -38,18 +56,79 @@ echo " - Pruning unused volumes..." docker volume prune -f echo "" -echo "3. Rebuilding and starting all services in detached mode..." +echo "4. Rebuilding and starting all services in detached mode..." docker compose up --build -d echo "" -echo "4. Checking service status..." +echo "5. Waiting for Supabase database to be ready..." +# Wait for database to be healthy +MAX_WAIT=60 +WAIT_COUNT=0 +while [ $WAIT_COUNT -lt $MAX_WAIT ]; do + if docker compose ps supabase-db | grep -q "healthy"; then + echo " ✓ Supabase database is healthy" + break + fi + echo " Waiting for database... ($WAIT_COUNT/$MAX_WAIT seconds)" + sleep 2 + WAIT_COUNT=$((WAIT_COUNT + 2)) +done + +if [ $WAIT_COUNT -ge $MAX_WAIT ]; then + echo " ⚠ Warning: Database may not be fully ready" +fi +echo "" + +echo "6. Waiting for Supabase migrations to complete..." +# Wait for migration container to complete (it has restart: "no", so it should exit when done) +MAX_WAIT=120 +WAIT_COUNT=0 +MIGRATE_CONTAINER="usda-vision-supabase-migrate" + +while [ $WAIT_COUNT -lt $MAX_WAIT ]; do + # Check if container exists and its status + if docker ps -a --format "{{.Names}}\t{{.Status}}" | grep -q "^${MIGRATE_CONTAINER}"; then + CONTAINER_STATUS=$(docker ps -a --format "{{.Names}}\t{{.Status}}" | grep "^${MIGRATE_CONTAINER}" | awk '{print $2}') + + if echo "$CONTAINER_STATUS" | grep -q "Exited"; then + EXIT_CODE=$(docker inspect "$MIGRATE_CONTAINER" --format='{{.State.ExitCode}}' 2>/dev/null || echo "1") + if [ "$EXIT_CODE" = "0" ]; then + echo " ✓ Supabase migrations completed successfully" + break + else + echo " ⚠ Warning: Migrations may have failed (exit code: $EXIT_CODE)" + echo " Check logs with: docker compose logs supabase-migrate" + break + fi + fi + fi + + echo " Waiting for migrations... ($WAIT_COUNT/$MAX_WAIT seconds)" + sleep 2 + WAIT_COUNT=$((WAIT_COUNT + 2)) +done + +if [ $WAIT_COUNT -ge $MAX_WAIT ]; then + echo " ⚠ Warning: Migration timeout - check logs with: docker compose logs supabase-migrate" + echo " Note: Migrations may still be running or the container may not have started yet" +fi +echo "" + +echo "7. Checking service status..." docker compose ps echo "" echo "=== Docker Compose Reset Complete ===" echo "" echo "All services have been reset and are running in detached mode." -echo "Use 'docker compose logs -f' to view logs or 'docker compose ps' to check status." +echo "" +echo "Useful commands:" +echo " - View all logs: docker compose logs -f" +echo " - View Supabase logs: docker compose logs -f supabase-db supabase-rest supabase-auth" +echo " - View migration logs: docker compose logs supabase-migrate" +echo " - Check service status: docker compose ps" +echo " - Access Supabase Studio: http://localhost:54323" +echo "" diff --git a/scripts/pull-supabase-images.sh b/scripts/pull-supabase-images.sh new file mode 100755 index 0000000..d09d0d6 --- /dev/null +++ b/scripts/pull-supabase-images.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# Script to pull Supabase Docker images one at a time to avoid rate limits + +set -e + +echo "=== Pulling Supabase Images ===" +echo "" +echo "This script pulls images one at a time with delays to avoid Docker Hub rate limits." +echo "" + +cd "$(dirname "$0")/.." + +# List of Supabase services to pull +SERVICES=( + "supabase-db" + "supabase-rest" + "supabase-auth" + "supabase-realtime" + "supabase-storage" + "supabase-studio" + "supabase-meta" + "supabase-migrate" + "supabase-inbucket" +) + +# Delay between pulls (in seconds) +DELAY=10 + +SUCCESS=0 +FAILED=0 + +for service in "${SERVICES[@]}"; do + echo "Pulling image for: $service" + + if docker compose pull "$service" 2>&1 | grep -q "Error\|rate\|toomanyrequests"; then + echo " ⚠ Rate limit hit or error. Waiting ${DELAY} seconds..." + sleep $DELAY + + # Try once more + if ! docker compose pull "$service" 2>&1 | tail -1 | grep -q "Error\|rate\|toomanyrequests"; then + echo " ✓ Successfully pulled $service" + ((SUCCESS++)) + else + echo " ✗ Failed to pull $service (rate limited)" + ((FAILED++)) + echo " → You may need to wait longer or login to Docker Hub: docker login" + fi + else + echo " ✓ Successfully pulled $service" + ((SUCCESS++)) + fi + + # Small delay between pulls + if [ $SUCCESS -lt ${#SERVICES[@]} ]; then + echo " Waiting ${DELAY} seconds before next pull..." + sleep $DELAY + fi + + echo "" +done + +echo "=== Summary ===" +echo "Successfully pulled: $SUCCESS" +echo "Failed: $FAILED" +echo "" + +if [ $FAILED -eq 0 ]; then + echo "✓ All images pulled successfully!" + echo "" + echo "You can now start the services:" + echo " docker compose up -d supabase-*" +else + echo "⚠ Some images failed to pull due to rate limits." + echo "" + echo "Solutions:" + echo " 1. Wait a few minutes and run this script again" + echo " 2. Login to Docker Hub: docker login" + echo " 3. Pull images manually one at a time with delays" +fi + diff --git a/supabase/.branches/_current_branch b/supabase/.branches/_current_branch new file mode 100644 index 0000000..88d050b --- /dev/null +++ b/supabase/.branches/_current_branch @@ -0,0 +1 @@ +main \ No newline at end of file diff --git a/supabase/.temp/cli-latest b/supabase/.temp/cli-latest new file mode 100644 index 0000000..8c68db7 --- /dev/null +++ b/supabase/.temp/cli-latest @@ -0,0 +1 @@ +v2.67.1 \ No newline at end of file diff --git a/supabase/README.md b/supabase/README.md new file mode 100644 index 0000000..9f76dd2 --- /dev/null +++ b/supabase/README.md @@ -0,0 +1,99 @@ +# Supabase Database Module + +This directory contains all Supabase configuration, migrations, and seed data for the USDA Vision project. + +## Structure + +``` +supabase/ +├── config.toml # Supabase CLI configuration +├── migrations/ # Database migration files (run in order) +│ ├── 00001_extensions_and_utilities.sql +│ ├── 00002_users_and_roles.sql +│ └── ... +├── seed_01_users.sql # Initial user data +└── seed_02_phase2_experiments.sql # Initial experiment data +``` + +## Usage + +### With Docker Compose (Recommended) + +The Supabase containers are managed by the main `docker-compose.yml` at the project root. Migrations and seeds are automatically run on startup. + +```bash +# From project root +docker compose up -d +``` + +### With Supabase CLI + +If you need to use Supabase CLI commands, run them from the project root (this directory's parent): + +```bash +# From project root +cd /path/to/USDA-VISION + +# Start Supabase (if not using docker-compose) +supabase start + +# Run migrations manually +supabase db reset + +# Generate types +supabase gen types typescript --local > management-dashboard-web-app/src/types/supabase.ts +``` + +**Note**: The Supabase CLI looks for the `supabase/` directory in the current working directory. Since we've moved it to the root, you can now run Supabase commands from the project root instead of needing to `cd` into `management-dashboard-web-app`. + +## Migration Workflow + +1. **Create a new migration**: + ```bash + supabase migration new migration_name + ``` + +2. **Apply migrations**: + - Automatically via docker-compose on startup + - Manually: `supabase db reset` (from project root) + +3. **Check migration status**: + ```bash + supabase migration list + ``` + +## Seed Data + +Seed files are run automatically after migrations when using docker-compose. They populate the database with initial data: +- `seed_01_users.sql`: Creates admin user and initial user profiles +- `seed_02_phase2_experiments.sql`: Creates initial experiment data + +## Configuration + +The `config.toml` file contains all Supabase service configurations: +- Database port: 54322 +- API port: 54321 +- Studio port: 54323 +- Inbucket (email testing): 54324 + +See `config.toml` for detailed configuration options. + +## Accessing Services + +- **Supabase Studio**: http://localhost:54323 +- **API**: http://localhost:54321 +- **Database**: `postgresql://postgres:postgres@localhost:54322/postgres` +- **Email Testing (Inbucket)**: http://localhost:54324 + +## Best Practices + +1. **Migrations are versioned**: Always use numbered prefixes (e.g., `00001_`, `00002_`) +2. **Migrations should be idempotent**: Use `IF NOT EXISTS` and `CREATE OR REPLACE` where possible +3. **Test migrations locally**: Always test migrations before committing +4. **Document breaking changes**: Add notes in migration files for schema changes + +## Related Documentation + +- [Supabase Docker Compose Integration](../../docs/SUPABASE_DOCKER_COMPOSE.md) +- [Database Schema](../../docs/database_schema.md) + diff --git a/supabase/config.toml b/supabase/config.toml new file mode 100755 index 0000000..70cea46 --- /dev/null +++ b/supabase/config.toml @@ -0,0 +1,333 @@ +# For detailed configuration reference documentation, visit: +# https://supabase.com/docs/guides/local-development/cli/config +# A string used to distinguish different Supabase projects on the same host. Defaults to the +# working directory name when running `supabase init`. +project_id = "pecan_experiments" + +[api] +enabled = true +# Port to use for the API URL. +port = 54321 +# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API +# endpoints. `public` and `graphql_public` schemas are included by default. +schemas = ["public", "graphql_public"] +# Extra schemas to add to the search_path of every request. +extra_search_path = ["public", "extensions"] +# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size +# for accidental or malicious requests. +max_rows = 1000 + +[api.tls] +# Enable HTTPS endpoints locally using a self-signed certificate. +enabled = false + +[db] +# Port to use for the local database URL. +port = 54322 +# Port used by db diff command to initialize the shadow database. +shadow_port = 54320 +# The database major version to use. This has to be the same as your remote database's. Run `SHOW +# server_version;` on the remote database to check. +major_version = 17 + +[db.pooler] +enabled = false +# Port to use for the local connection pooler. +port = 54329 +# Specifies when a server connection can be reused by other clients. +# Configure one of the supported pooler modes: `transaction`, `session`. +pool_mode = "transaction" +# How many server connections to allow per user/database pair. +default_pool_size = 20 +# Maximum number of client connections allowed. +max_client_conn = 100 + +# [db.vault] +# secret_key = "env(SECRET_VALUE)" + +[db.migrations] +# If disabled, migrations will be skipped during a db push or reset. +enabled = true +# Specifies an ordered list of schema files that describe your database. +# Supports glob patterns relative to supabase directory: "./schemas/*.sql" +schema_paths = [] + +[db.seed] +# If enabled, seeds the database after migrations during a db reset. +enabled = true +# Specifies an ordered list of seed files to load during db reset. +# Supports glob patterns relative to supabase directory: "./seeds/*.sql" +sql_paths = ["./seed_01_users.sql", "./seed_02_phase2_experiments.sql"] +# , "./seed_04_phase2_jc_experiments.sql", "./seed_05_meyer_experiments.sql"] + +[db.network_restrictions] +# Enable management of network restrictions. +enabled = false +# List of IPv4 CIDR blocks allowed to connect to the database. +# Defaults to allow all IPv4 connections. Set empty array to block all IPs. +allowed_cidrs = ["0.0.0.0/0"] +# List of IPv6 CIDR blocks allowed to connect to the database. +# Defaults to allow all IPv6 connections. Set empty array to block all IPs. +allowed_cidrs_v6 = ["::/0"] + +[realtime] +enabled = true +# Bind realtime via either IPv4 or IPv6. (default: IPv4) +# ip_version = "IPv6" +# The maximum length in bytes of HTTP request headers. (default: 4096) +# max_header_length = 4096 + +[studio] +enabled = true +# Port to use for Supabase Studio. +port = 54323 +# External URL of the API server that frontend connects to. +api_url = "http://exp-dash" +# OpenAI API Key to use for Supabase AI in the Supabase Studio. +openai_api_key = "env(OPENAI_API_KEY)" + +# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they +# are monitored, and you can view the emails that would have been sent from the web interface. +[inbucket] +enabled = true +# Port to use for the email testing server web interface. +port = 54324 +# Uncomment to expose additional ports for testing user applications that send emails. +# smtp_port = 54325 +# pop3_port = 54326 +# admin_email = "admin@email.com" +# sender_name = "Admin" + +[storage] +enabled = true +# The maximum file size allowed (e.g. "5MB", "500KB"). +file_size_limit = "50MiB" + +# Image transformation API is available to Supabase Pro plan. +# [storage.image_transformation] +# enabled = true + +# Uncomment to configure local storage buckets +# [storage.buckets.images] +# public = false +# file_size_limit = "50MiB" +# allowed_mime_types = ["image/png", "image/jpeg"] +# objects_path = "./images" + +[auth] +enabled = true +# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used +# in emails. +site_url = "http://exp-dash:3000" +# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. +additional_redirect_urls = ["https://exp-dash:3000"] +# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). +jwt_expiry = 3600 +# If disabled, the refresh token will never expire. +enable_refresh_token_rotation = true +# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. +# Requires enable_refresh_token_rotation = true. +refresh_token_reuse_interval = 10 +# Allow/disallow new user signups to your project. +enable_signup = true +# Allow/disallow anonymous sign-ins to your project. +enable_anonymous_sign_ins = false +# Allow/disallow testing manual linking of accounts +enable_manual_linking = false +# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more. +minimum_password_length = 6 +# Passwords that do not meet the following requirements will be rejected as weak. Supported values +# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols` +password_requirements = "" + +[auth.rate_limit] +# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled. +email_sent = 2 +# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled. +sms_sent = 30 +# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true. +anonymous_users = 30 +# Number of sessions that can be refreshed in a 5 minute interval per IP address. +token_refresh = 150 +# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users). +sign_in_sign_ups = 30 +# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address. +token_verifications = 30 +# Number of Web3 logins that can be made in a 5 minute interval per IP address. +web3 = 30 + +# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`. +# [auth.captcha] +# enabled = true +# provider = "hcaptcha" +# secret = "" + +[auth.email] +# Allow/disallow new user signups via email to your project. +enable_signup = true +# If enabled, a user will be required to confirm any email change on both the old, and new email +# addresses. If disabled, only the new email is required to confirm. +double_confirm_changes = true +# If enabled, users need to confirm their email address before signing in. +enable_confirmations = false +# If enabled, users will need to reauthenticate or have logged in recently to change their password. +secure_password_change = false +# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. +max_frequency = "1s" +# Number of characters used in the email OTP. +otp_length = 6 +# Number of seconds before the email OTP expires (defaults to 1 hour). +otp_expiry = 3600 + +# Use a production-ready SMTP server +# [auth.email.smtp] +# enabled = true +# host = "smtp.sendgrid.net" +# port = 587 +# user = "apikey" +# pass = "env(SENDGRID_API_KEY)" +# admin_email = "admin@email.com" +# sender_name = "Admin" + +# Uncomment to customize email template +# [auth.email.template.invite] +# subject = "You have been invited" +# content_path = "./supabase/templates/invite.html" + +[auth.sms] +# Allow/disallow new user signups via SMS to your project. +enable_signup = false +# If enabled, users need to confirm their phone number before signing in. +enable_confirmations = false +# Template for sending OTP to users +template = "Your code is {{ .Code }}" +# Controls the minimum amount of time that must pass before sending another sms otp. +max_frequency = "5s" + +# Use pre-defined map of phone number to OTP for testing. +# [auth.sms.test_otp] +# 4152127777 = "123456" + +# Configure logged in session timeouts. +# [auth.sessions] +# Force log out after the specified duration. +# timebox = "24h" +# Force log out if the user has been inactive longer than the specified duration. +# inactivity_timeout = "8h" + +# This hook runs before a new user is created and allows developers to reject the request based on the incoming user object. +# [auth.hook.before_user_created] +# enabled = true +# uri = "pg-functions://postgres/auth/before-user-created-hook" + +# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used. +# [auth.hook.custom_access_token] +# enabled = true +# uri = "pg-functions:////" + +# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`. +[auth.sms.twilio] +enabled = false +account_sid = "" +message_service_sid = "" +# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead: +auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)" + +# Multi-factor-authentication is available to Supabase Pro plan. +[auth.mfa] +# Control how many MFA factors can be enrolled at once per user. +max_enrolled_factors = 10 + +# Control MFA via App Authenticator (TOTP) +[auth.mfa.totp] +enroll_enabled = false +verify_enabled = false + +# Configure MFA via Phone Messaging +[auth.mfa.phone] +enroll_enabled = false +verify_enabled = false +otp_length = 6 +template = "Your code is {{ .Code }}" +max_frequency = "5s" + +# Configure MFA via WebAuthn +# [auth.mfa.web_authn] +# enroll_enabled = true +# verify_enabled = true + +# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, +# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, +# `twitter`, `slack`, `spotify`, `workos`, `zoom`. +[auth.external.apple] +enabled = false +client_id = "" +# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: +secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" +# Overrides the default auth redirectUrl. +redirect_uri = "" +# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, +# or any other third-party OIDC providers. +url = "" +# If enabled, the nonce check will be skipped. Required for local sign in with Google auth. +skip_nonce_check = false + +# Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard. +# You can configure "web3" rate limit in the [auth.rate_limit] section and set up [auth.captcha] if self-hosting. +[auth.web3.solana] +enabled = false + +# Use Firebase Auth as a third-party provider alongside Supabase Auth. +[auth.third_party.firebase] +enabled = false +# project_id = "my-firebase-project" + +# Use Auth0 as a third-party provider alongside Supabase Auth. +[auth.third_party.auth0] +enabled = false +# tenant = "my-auth0-tenant" +# tenant_region = "us" + +# Use AWS Cognito (Amplify) as a third-party provider alongside Supabase Auth. +[auth.third_party.aws_cognito] +enabled = false +# user_pool_id = "my-user-pool-id" +# user_pool_region = "us-east-1" + +# Use Clerk as a third-party provider alongside Supabase Auth. +[auth.third_party.clerk] +enabled = false +# Obtain from https://clerk.com/setup/supabase +# domain = "example.clerk.accounts.dev" + +[edge_runtime] +enabled = true +# Configure one of the supported request policies: `oneshot`, `per_worker`. +# Use `oneshot` for hot reload, or `per_worker` for load testing. +policy = "oneshot" +# Port to attach the Chrome inspector for debugging edge functions. +inspector_port = 8083 +# The Deno major version to use. +deno_version = 1 + +# [edge_runtime.secrets] +# secret_key = "env(SECRET_VALUE)" + +[analytics] +enabled = true +port = 54327 +# Configure one of the supported backends: `postgres`, `bigquery`. +backend = "postgres" + +# Experimental features may be deprecated any time +[experimental] +# Configures Postgres storage engine to use OrioleDB (S3) +orioledb_version = "" +# Configures S3 bucket URL, eg. .s3-.amazonaws.com +s3_host = "env(S3_HOST)" +# Configures S3 bucket region, eg. us-east-1 +s3_region = "env(S3_REGION)" +# Configures AWS_ACCESS_KEY_ID for S3 bucket +s3_access_key = "env(S3_ACCESS_KEY)" +# Configures AWS_SECRET_ACCESS_KEY for S3 bucket +s3_secret_key = "env(S3_SECRET_KEY)" diff --git a/supabase/migrations/00001_extensions_and_utilities.sql b/supabase/migrations/00001_extensions_and_utilities.sql new file mode 100644 index 0000000..bfcf2a2 --- /dev/null +++ b/supabase/migrations/00001_extensions_and_utilities.sql @@ -0,0 +1,87 @@ +-- Extensions and Utility Functions +-- This migration creates required extensions and utility functions used across the database + +-- ============================================= +-- 1. EXTENSIONS +-- ============================================= + +-- Enable UUID generation +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- Enable password hashing +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + +-- ============================================= +-- 2. UTILITY FUNCTIONS +-- ============================================= + +-- Function to handle updated_at timestamp +CREATE OR REPLACE FUNCTION public.handle_updated_at() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Helper function to get current user's roles +CREATE OR REPLACE FUNCTION public.get_user_roles() +RETURNS TEXT[] AS $$ +BEGIN + RETURN ARRAY( + SELECT r.name + FROM public.user_roles ur + JOIN public.roles r ON ur.role_id = r.id + WHERE ur.user_id = auth.uid() + ); +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Helper function to get current user's first role (for backward compatibility) +CREATE OR REPLACE FUNCTION public.get_user_role() +RETURNS TEXT AS $$ +BEGIN + -- Return the first role found (for backward compatibility) + RETURN ( + SELECT r.name + FROM public.user_roles ur + JOIN public.roles r ON ur.role_id = r.id + WHERE ur.user_id = auth.uid() + LIMIT 1 + ); +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Helper function to check if user is admin +CREATE OR REPLACE FUNCTION public.is_admin() +RETURNS BOOLEAN AS $$ +BEGIN + RETURN 'admin' = ANY(public.get_user_roles()); +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Helper function to check if user has specific role +CREATE OR REPLACE FUNCTION public.has_role(role_name TEXT) +RETURNS BOOLEAN AS $$ +BEGIN + RETURN role_name = ANY(public.get_user_roles()); +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Helper function to check if user can manage experiments +CREATE OR REPLACE FUNCTION public.can_manage_experiments() +RETURNS BOOLEAN AS $$ +BEGIN + RETURN EXISTS ( + SELECT 1 + FROM public.user_roles ur + JOIN public.roles r ON ur.role_id = r.id + WHERE ur.user_id = auth.uid() + AND r.name IN ('admin', 'conductor') + ); +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + + + + diff --git a/supabase/migrations/00002_users_and_roles.sql b/supabase/migrations/00002_users_and_roles.sql new file mode 100644 index 0000000..09d1f2f --- /dev/null +++ b/supabase/migrations/00002_users_and_roles.sql @@ -0,0 +1,237 @@ +-- Users and Roles +-- This migration creates user-related tables with clean separation + +-- ============================================= +-- 1. ROLES TABLE +-- ============================================= + +CREATE TABLE IF NOT EXISTS public.roles ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + name TEXT NOT NULL UNIQUE, + description TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- ============================================= +-- 2. USER PROFILES TABLE +-- ============================================= + +CREATE TABLE IF NOT EXISTS public.user_profiles ( + id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, + email TEXT NOT NULL UNIQUE, + first_name TEXT, + last_name TEXT, + status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'suspended')), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- ============================================= +-- 3. USER ROLES JUNCTION TABLE +-- ============================================= + +CREATE TABLE IF NOT EXISTS public.user_roles ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES public.user_profiles(id) ON DELETE CASCADE, + role_id UUID NOT NULL REFERENCES public.roles(id) ON DELETE CASCADE, + assigned_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + assigned_by UUID REFERENCES public.user_profiles(id), + UNIQUE(user_id, role_id) +); + +-- ============================================= +-- 4. INDEXES FOR PERFORMANCE +-- ============================================= + +CREATE INDEX IF NOT EXISTS idx_user_profiles_email ON public.user_profiles(email); +CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON public.user_roles(user_id); +CREATE INDEX IF NOT EXISTS idx_user_roles_role_id ON public.user_roles(role_id); + +-- ============================================= +-- 5. TRIGGERS +-- ============================================= + +-- Create trigger for updated_at on user_profiles +CREATE TRIGGER set_updated_at_user_profiles + BEFORE UPDATE ON public.user_profiles + FOR EACH ROW + EXECUTE FUNCTION public.handle_updated_at(); + +-- Create trigger for updated_at on roles +CREATE TRIGGER set_updated_at_roles + BEFORE UPDATE ON public.roles + FOR EACH ROW + EXECUTE FUNCTION public.handle_updated_at(); + +-- ============================================= +-- 6. GRANT PERMISSIONS +-- ============================================= + +GRANT ALL ON public.roles TO authenticated; +GRANT ALL ON public.user_profiles TO authenticated; +GRANT ALL ON public.user_roles TO authenticated; + +-- ============================================= +-- 7. ENABLE ROW LEVEL SECURITY +-- ============================================= + +ALTER TABLE public.roles ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.user_profiles ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.user_roles ENABLE ROW LEVEL SECURITY; + +-- ============================================= +-- 8. CREATE RLS POLICIES +-- ============================================= + +-- Create RLS policies for roles (read-only for all authenticated users) +CREATE POLICY "Roles are viewable by authenticated users" ON public.roles + FOR SELECT USING (auth.role() = 'authenticated'); + +-- Create RLS policies for user_profiles +CREATE POLICY "User profiles are viewable by authenticated users" ON public.user_profiles + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "User profiles are insertable by authenticated users" ON public.user_profiles + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "User profiles are updatable by authenticated users" ON public.user_profiles + FOR UPDATE USING (auth.role() = 'authenticated'); + +CREATE POLICY "User profiles are deletable by authenticated users" ON public.user_profiles + FOR DELETE USING (auth.role() = 'authenticated'); + +-- Create RLS policies for user_roles +CREATE POLICY "User roles are viewable by authenticated users" ON public.user_roles + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "User roles are insertable by authenticated users" ON public.user_roles + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "User roles are updatable by authenticated users" ON public.user_roles + FOR UPDATE USING (auth.role() = 'authenticated'); + +CREATE POLICY "User roles are deletable by authenticated users" ON public.user_roles + FOR DELETE USING (auth.role() = 'authenticated'); + +-- ============================================= +-- 9. USER MANAGEMENT FUNCTIONS +-- ============================================= + +-- Function to create a new user with roles +CREATE OR REPLACE FUNCTION public.create_user_with_roles( + user_email TEXT, + role_names TEXT[], + temp_password TEXT +) +RETURNS JSON AS $$ +DECLARE + new_user_id UUID; + encrypted_pwd TEXT; + role_name TEXT; + role_id_val UUID; + assigned_by_id UUID; + result JSON; + user_roles_array TEXT[]; +BEGIN + -- Generate new user ID + new_user_id := uuid_generate_v4(); + + -- Encrypt the password + encrypted_pwd := crypt(temp_password, gen_salt('bf')); + + -- Get the current user ID for assigned_by, but only if they have a profile + -- Otherwise, use the new user ID (which we'll create next) + SELECT id INTO assigned_by_id + FROM public.user_profiles + WHERE id = auth.uid(); + + -- If no valid assigned_by user found, use the new user ID (self-assigned) + IF assigned_by_id IS NULL THEN + assigned_by_id := new_user_id; + END IF; + + -- Create user in auth.users + INSERT INTO auth.users ( + instance_id, + id, + aud, + role, + email, + encrypted_password, + email_confirmed_at, + created_at, + updated_at, + confirmation_token, + email_change, + email_change_token_new, + recovery_token + ) VALUES ( + '00000000-0000-0000-0000-000000000000', + new_user_id, + 'authenticated', + 'authenticated', + user_email, + encrypted_pwd, + NOW(), + NOW(), + NOW(), + '', + '', + '', + '' + ); + + -- Create user profile + INSERT INTO public.user_profiles (id, email, status) + VALUES (new_user_id, user_email, 'active'); + + -- Assign roles + user_roles_array := ARRAY[]::TEXT[]; + FOREACH role_name IN ARRAY role_names + LOOP + -- Get role ID + SELECT id INTO role_id_val + FROM public.roles + WHERE name = role_name; + + -- If role exists, assign it + IF role_id_val IS NOT NULL THEN + INSERT INTO public.user_roles (user_id, role_id, assigned_by) + VALUES (new_user_id, role_id_val, assigned_by_id) + ON CONFLICT (user_id, role_id) DO NOTHING; + + -- Add to roles array for return value + user_roles_array := array_append(user_roles_array, role_name); + END IF; + END LOOP; + + -- Return the result as JSON + result := json_build_object( + 'user_id', new_user_id::TEXT, + 'email', user_email, + 'temp_password', temp_password, + 'roles', user_roles_array, + 'status', 'active' + ); + + RETURN result; + +EXCEPTION + WHEN unique_violation THEN + RAISE EXCEPTION 'User with email % already exists', user_email; + WHEN OTHERS THEN + RAISE EXCEPTION 'Error creating user: %', SQLERRM; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Grant execute permission on the function +GRANT EXECUTE ON FUNCTION public.create_user_with_roles(TEXT, TEXT[], TEXT) TO authenticated; + +-- Comment for documentation +COMMENT ON FUNCTION public.create_user_with_roles(TEXT, TEXT[], TEXT) IS +'Creates a new user in auth.users, creates a profile in user_profiles, and assigns the specified roles. Returns user information including user_id, email, temp_password, roles, and status.'; + + + + diff --git a/supabase/migrations/00003_machine_types.sql b/supabase/migrations/00003_machine_types.sql new file mode 100644 index 0000000..684a037 --- /dev/null +++ b/supabase/migrations/00003_machine_types.sql @@ -0,0 +1,63 @@ +-- Machine Types +-- This migration creates the machine types table + +-- ============================================= +-- 1. MACHINE TYPES TABLE +-- ============================================= + +CREATE TABLE IF NOT EXISTS public.machine_types ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + name TEXT NOT NULL UNIQUE, + description TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + created_by UUID NOT NULL REFERENCES public.user_profiles(id) +); + +-- ============================================= +-- 2. INDEXES FOR PERFORMANCE +-- ============================================= + +CREATE INDEX IF NOT EXISTS idx_machine_types_name ON public.machine_types(name); + +-- ============================================= +-- 3. TRIGGERS +-- ============================================= + +-- Create trigger for updated_at on machine_types +CREATE TRIGGER set_updated_at_machine_types + BEFORE UPDATE ON public.machine_types + FOR EACH ROW + EXECUTE FUNCTION public.handle_updated_at(); + +-- ============================================= +-- 4. GRANT PERMISSIONS +-- ============================================= + +GRANT ALL ON public.machine_types TO authenticated; + +-- ============================================= +-- 5. ENABLE ROW LEVEL SECURITY +-- ============================================= + +ALTER TABLE public.machine_types ENABLE ROW LEVEL SECURITY; + +-- ============================================= +-- 6. CREATE RLS POLICIES +-- ============================================= + +CREATE POLICY "Machine types are viewable by authenticated users" ON public.machine_types + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Machine types are insertable by authenticated users" ON public.machine_types + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Machine types are updatable by authenticated users" ON public.machine_types + FOR UPDATE USING (auth.role() = 'authenticated'); + +CREATE POLICY "Machine types are deletable by authenticated users" ON public.machine_types + FOR DELETE USING (auth.role() = 'authenticated'); + + + + diff --git a/supabase/migrations/00004_experiment_phases.sql b/supabase/migrations/00004_experiment_phases.sql new file mode 100644 index 0000000..7cbb581 --- /dev/null +++ b/supabase/migrations/00004_experiment_phases.sql @@ -0,0 +1,77 @@ +-- Experiment Phases +-- This migration creates the experiment phases table + +-- ============================================= +-- 1. EXPERIMENT PHASES TABLE +-- ============================================= + +CREATE TABLE IF NOT EXISTS public.experiment_phases ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + name TEXT NOT NULL UNIQUE, + description TEXT, + has_soaking BOOLEAN NOT NULL DEFAULT false, + has_airdrying BOOLEAN NOT NULL DEFAULT false, + has_cracking BOOLEAN NOT NULL DEFAULT false, + has_shelling BOOLEAN NOT NULL DEFAULT false, + cracking_machine_type_id UUID REFERENCES public.machine_types(id) ON DELETE SET NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + created_by UUID NOT NULL REFERENCES public.user_profiles(id), + + -- Ensure at least one phase is selected + CONSTRAINT check_at_least_one_phase + CHECK (has_soaking = true OR has_airdrying = true OR has_cracking = true OR has_shelling = true), + + -- If has_cracking is true, then cracking_machine_type_id must not be null + CONSTRAINT ck_experiment_phases_machine_required_when_cracking + CHECK ((has_cracking = false) OR (cracking_machine_type_id IS NOT NULL)) +); + +-- ============================================= +-- 2. INDEXES FOR PERFORMANCE +-- ============================================= + +CREATE INDEX IF NOT EXISTS idx_experiment_phases_name ON public.experiment_phases(name); +CREATE INDEX IF NOT EXISTS idx_experiment_phases_cracking_machine_type_id ON public.experiment_phases(cracking_machine_type_id); + +-- ============================================= +-- 3. TRIGGERS +-- ============================================= + +-- Create trigger for updated_at on experiment_phases +CREATE TRIGGER set_updated_at_experiment_phases + BEFORE UPDATE ON public.experiment_phases + FOR EACH ROW + EXECUTE FUNCTION public.handle_updated_at(); + +-- ============================================= +-- 4. GRANT PERMISSIONS +-- ============================================= + +GRANT ALL ON public.experiment_phases TO authenticated; + +-- ============================================= +-- 5. ENABLE ROW LEVEL SECURITY +-- ============================================= + +ALTER TABLE public.experiment_phases ENABLE ROW LEVEL SECURITY; + +-- ============================================= +-- 6. CREATE RLS POLICIES +-- ============================================= + +CREATE POLICY "Experiment phases are viewable by authenticated users" ON public.experiment_phases + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Experiment phases are insertable by authenticated users" ON public.experiment_phases + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Experiment phases are updatable by authenticated users" ON public.experiment_phases + FOR UPDATE USING (auth.role() = 'authenticated'); + +CREATE POLICY "Experiment phases are deletable by authenticated users" ON public.experiment_phases + FOR DELETE USING (auth.role() = 'authenticated'); + + + + diff --git a/supabase/migrations/00005_experiments.sql b/supabase/migrations/00005_experiments.sql new file mode 100644 index 0000000..a3ade01 --- /dev/null +++ b/supabase/migrations/00005_experiments.sql @@ -0,0 +1,70 @@ +-- Experiments +-- This migration creates the experiments table + +-- ============================================= +-- 1. EXPERIMENTS TABLE +-- ============================================= + +CREATE TABLE IF NOT EXISTS public.experiments ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + experiment_number INTEGER NOT NULL, + reps_required INTEGER NOT NULL CHECK (reps_required > 0), + weight_per_repetition_lbs DOUBLE PRECISION NOT NULL DEFAULT 5.0 CHECK (weight_per_repetition_lbs > 0), + results_status TEXT NOT NULL DEFAULT 'valid' CHECK (results_status IN ('valid', 'invalid')), + completion_status BOOLEAN NOT NULL DEFAULT false, + phase_id UUID NOT NULL REFERENCES public.experiment_phases(id) ON DELETE SET NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + created_by UUID NOT NULL REFERENCES public.user_profiles(id), + + -- Ensure unique combination of experiment_number and phase_id + CONSTRAINT unique_experiment_number_phase UNIQUE (experiment_number, phase_id) +); + +-- ============================================= +-- 2. INDEXES FOR PERFORMANCE +-- ============================================= + +CREATE INDEX IF NOT EXISTS idx_experiments_phase_id ON public.experiments(phase_id); +CREATE INDEX IF NOT EXISTS idx_experiments_experiment_number ON public.experiments(experiment_number); +CREATE INDEX IF NOT EXISTS idx_experiments_created_by ON public.experiments(created_by); +CREATE INDEX IF NOT EXISTS idx_experiments_id ON public.experiments(id); + +-- ============================================= +-- 3. TRIGGERS +-- ============================================= + +-- Create trigger for updated_at on experiments +CREATE TRIGGER set_updated_at_experiments + BEFORE UPDATE ON public.experiments + FOR EACH ROW + EXECUTE FUNCTION public.handle_updated_at(); + +-- ============================================= +-- 4. GRANT PERMISSIONS +-- ============================================= + +GRANT ALL ON public.experiments TO authenticated; + +-- ============================================= +-- 5. ENABLE ROW LEVEL SECURITY +-- ============================================= + +ALTER TABLE public.experiments ENABLE ROW LEVEL SECURITY; + +-- ============================================= +-- 6. CREATE RLS POLICIES +-- ============================================= + +CREATE POLICY "Experiments are viewable by authenticated users" ON public.experiments + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Experiments are insertable by authenticated users" ON public.experiments + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Experiments are updatable by authenticated users" ON public.experiments + FOR UPDATE USING (auth.role() = 'authenticated'); + +CREATE POLICY "Experiments are deletable by authenticated users" ON public.experiments + FOR DELETE USING (auth.role() = 'authenticated'); + diff --git a/supabase/migrations/00006_experiment_repetitions.sql b/supabase/migrations/00006_experiment_repetitions.sql new file mode 100644 index 0000000..660c8c6 --- /dev/null +++ b/supabase/migrations/00006_experiment_repetitions.sql @@ -0,0 +1,69 @@ +-- Experiment Repetitions +-- This migration creates the experiment repetitions table + +-- ============================================= +-- 1. EXPERIMENT REPETITIONS TABLE +-- ============================================= + +CREATE TABLE IF NOT EXISTS public.experiment_repetitions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + experiment_id UUID NOT NULL REFERENCES public.experiments(id) ON DELETE CASCADE, + repetition_number INTEGER NOT NULL CHECK (repetition_number > 0), + scheduled_date TIMESTAMP WITH TIME ZONE, + status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'in_progress', 'completed', 'cancelled')), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + created_by UUID NOT NULL REFERENCES public.user_profiles(id), + + -- Ensure unique repetition numbers per experiment + UNIQUE(experiment_id, repetition_number) +); + +-- ============================================= +-- 2. INDEXES FOR PERFORMANCE +-- ============================================= + +CREATE INDEX IF NOT EXISTS idx_experiment_repetitions_experiment_id ON public.experiment_repetitions(experiment_id); +CREATE INDEX IF NOT EXISTS idx_experiment_repetitions_created_by ON public.experiment_repetitions(created_by); + +-- ============================================= +-- 3. TRIGGERS +-- ============================================= + +-- Create trigger for updated_at on experiment_repetitions +CREATE TRIGGER set_updated_at_experiment_repetitions + BEFORE UPDATE ON public.experiment_repetitions + FOR EACH ROW + EXECUTE FUNCTION public.handle_updated_at(); + +-- ============================================= +-- 4. GRANT PERMISSIONS +-- ============================================= + +GRANT ALL ON public.experiment_repetitions TO authenticated; + +-- ============================================= +-- 5. ENABLE ROW LEVEL SECURITY +-- ============================================= + +ALTER TABLE public.experiment_repetitions ENABLE ROW LEVEL SECURITY; + +-- ============================================= +-- 6. CREATE RLS POLICIES +-- ============================================= + +CREATE POLICY "Experiment repetitions are viewable by authenticated users" ON public.experiment_repetitions + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Experiment repetitions are insertable by authenticated users" ON public.experiment_repetitions + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Experiment repetitions are updatable by authenticated users" ON public.experiment_repetitions + FOR UPDATE USING (auth.role() = 'authenticated'); + +CREATE POLICY "Experiment repetitions are deletable by authenticated users" ON public.experiment_repetitions + FOR DELETE USING (auth.role() = 'authenticated'); + + + + diff --git a/supabase/migrations/00007_cracker_parameters.sql b/supabase/migrations/00007_cracker_parameters.sql new file mode 100644 index 0000000..6c2501f --- /dev/null +++ b/supabase/migrations/00007_cracker_parameters.sql @@ -0,0 +1,89 @@ +-- Cracker Parameters +-- This migration creates machine-specific parameter tables (must be created before cracking table) + +-- ============================================= +-- 1. JC CRACKER PARAMETERS TABLE +-- ============================================= + +CREATE TABLE IF NOT EXISTS public.jc_cracker_parameters ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + plate_contact_frequency_hz DOUBLE PRECISION NOT NULL CHECK (plate_contact_frequency_hz > 0), + throughput_rate_pecans_sec DOUBLE PRECISION NOT NULL CHECK (throughput_rate_pecans_sec > 0), + crush_amount_in DOUBLE PRECISION NOT NULL CHECK (crush_amount_in >= 0), + entry_exit_height_diff_in DOUBLE PRECISION NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- ============================================= +-- 2. MEYER CRACKER PARAMETERS TABLE +-- ============================================= + +CREATE TABLE IF NOT EXISTS public.meyer_cracker_parameters ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + motor_speed_hz DOUBLE PRECISION NOT NULL CHECK (motor_speed_hz > 0), + jig_displacement_inches DOUBLE PRECISION NOT NULL, + spring_stiffness_nm DOUBLE PRECISION NOT NULL CHECK (spring_stiffness_nm > 0), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- ============================================= +-- 3. TRIGGERS +-- ============================================= + +CREATE TRIGGER set_updated_at_jc_cracker_parameters + BEFORE UPDATE ON public.jc_cracker_parameters + FOR EACH ROW + EXECUTE FUNCTION public.handle_updated_at(); + +CREATE TRIGGER set_updated_at_meyer_cracker_parameters + BEFORE UPDATE ON public.meyer_cracker_parameters + FOR EACH ROW + EXECUTE FUNCTION public.handle_updated_at(); + +-- ============================================= +-- 4. GRANT PERMISSIONS +-- ============================================= + +GRANT ALL ON public.jc_cracker_parameters TO authenticated; +GRANT ALL ON public.meyer_cracker_parameters TO authenticated; + +-- ============================================= +-- 5. ENABLE ROW LEVEL SECURITY +-- ============================================= + +ALTER TABLE public.jc_cracker_parameters ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.meyer_cracker_parameters ENABLE ROW LEVEL SECURITY; + +-- ============================================= +-- 6. CREATE RLS POLICIES +-- ============================================= + +CREATE POLICY "JC Cracker parameters are viewable by authenticated users" ON public.jc_cracker_parameters + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "JC Cracker parameters are insertable by authenticated users" ON public.jc_cracker_parameters + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "JC Cracker parameters are updatable by authenticated users" ON public.jc_cracker_parameters + FOR UPDATE USING (auth.role() = 'authenticated'); + +CREATE POLICY "JC Cracker parameters are deletable by authenticated users" ON public.jc_cracker_parameters + FOR DELETE USING (auth.role() = 'authenticated'); + +CREATE POLICY "Meyer Cracker parameters are viewable by authenticated users" ON public.meyer_cracker_parameters + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Meyer Cracker parameters are insertable by authenticated users" ON public.meyer_cracker_parameters + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Meyer Cracker parameters are updatable by authenticated users" ON public.meyer_cracker_parameters + FOR UPDATE USING (auth.role() = 'authenticated'); + +CREATE POLICY "Meyer Cracker parameters are deletable by authenticated users" ON public.meyer_cracker_parameters + FOR DELETE USING (auth.role() = 'authenticated'); + + + + diff --git a/supabase/migrations/00008_phase_data_tables.sql b/supabase/migrations/00008_phase_data_tables.sql new file mode 100644 index 0000000..96819dd --- /dev/null +++ b/supabase/migrations/00008_phase_data_tables.sql @@ -0,0 +1,274 @@ +-- Phase Data Tables +-- This migration creates phase-specific data entry tables (soaking, airdrying, cracking, shelling) + +-- ============================================= +-- 1. SOAKING TABLE +-- ============================================= + +CREATE TABLE IF NOT EXISTS public.soaking ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + repetition_id UUID NOT NULL REFERENCES public.experiment_repetitions(id) ON DELETE CASCADE, + scheduled_start_time TIMESTAMP WITH TIME ZONE NOT NULL, + actual_start_time TIMESTAMP WITH TIME ZONE, + soaking_duration_minutes INTEGER NOT NULL CHECK (soaking_duration_minutes > 0), + scheduled_end_time TIMESTAMP WITH TIME ZONE NOT NULL, + actual_end_time TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + created_by UUID NOT NULL REFERENCES public.user_profiles(id), + + -- Ensure only one soaking per repetition + CONSTRAINT unique_soaking_per_repetition UNIQUE (repetition_id) +); + +-- ============================================= +-- 2. AIRDRYING TABLE +-- ============================================= + +CREATE TABLE IF NOT EXISTS public.airdrying ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + repetition_id UUID NOT NULL REFERENCES public.experiment_repetitions(id) ON DELETE CASCADE, + scheduled_start_time TIMESTAMP WITH TIME ZONE NOT NULL, + actual_start_time TIMESTAMP WITH TIME ZONE, + duration_minutes INTEGER NOT NULL CHECK (duration_minutes > 0), + scheduled_end_time TIMESTAMP WITH TIME ZONE NOT NULL, + actual_end_time TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + created_by UUID NOT NULL REFERENCES public.user_profiles(id), + + -- Ensure only one airdrying per repetition + CONSTRAINT unique_airdrying_per_repetition UNIQUE (repetition_id) +); + +-- ============================================= +-- 3. CRACKING TABLE +-- ============================================= + +CREATE TABLE IF NOT EXISTS public.cracking ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + repetition_id UUID NOT NULL REFERENCES public.experiment_repetitions(id) ON DELETE CASCADE, + machine_type_id UUID NOT NULL REFERENCES public.machine_types(id), + scheduled_start_time TIMESTAMP WITH TIME ZONE NOT NULL, + actual_start_time TIMESTAMP WITH TIME ZONE, + actual_end_time TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + created_by UUID NOT NULL REFERENCES public.user_profiles(id), + + -- Ensure only one cracking per repetition + CONSTRAINT unique_cracking_per_repetition UNIQUE (repetition_id) +); + +-- ============================================= +-- 4. SHELLING TABLE +-- ============================================= + +CREATE TABLE IF NOT EXISTS public.shelling ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + repetition_id UUID NOT NULL REFERENCES public.experiment_repetitions(id) ON DELETE CASCADE, + scheduled_start_time TIMESTAMP WITH TIME ZONE NOT NULL, + actual_start_time TIMESTAMP WITH TIME ZONE, + actual_end_time TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + created_by UUID NOT NULL REFERENCES public.user_profiles(id), + + -- Ensure only one shelling per repetition + CONSTRAINT unique_shelling_per_repetition UNIQUE (repetition_id) +); + +-- ============================================= +-- 5. INDEXES FOR PERFORMANCE +-- ============================================= + +-- Create indexes for repetition references +CREATE INDEX IF NOT EXISTS idx_soaking_repetition_id ON public.soaking(repetition_id); +CREATE INDEX IF NOT EXISTS idx_airdrying_repetition_id ON public.airdrying(repetition_id); +CREATE INDEX IF NOT EXISTS idx_cracking_repetition_id ON public.cracking(repetition_id); +CREATE INDEX IF NOT EXISTS idx_shelling_repetition_id ON public.shelling(repetition_id); + +-- Create indexes for machine type references +CREATE INDEX IF NOT EXISTS idx_cracking_machine_type_id ON public.cracking(machine_type_id); + +-- Create indexes for created_by references +CREATE INDEX IF NOT EXISTS idx_soaking_created_by ON public.soaking(created_by); +CREATE INDEX IF NOT EXISTS idx_airdrying_created_by ON public.airdrying(created_by); +CREATE INDEX IF NOT EXISTS idx_cracking_created_by ON public.cracking(created_by); +CREATE INDEX IF NOT EXISTS idx_shelling_created_by ON public.shelling(created_by); + +-- ============================================= +-- 6. TRIGGER FUNCTIONS FOR AUTOMATIC TIMESTAMP CALCULATIONS +-- ============================================= + +-- Function to calculate scheduled end time for soaking +CREATE OR REPLACE FUNCTION calculate_soaking_scheduled_end_time() +RETURNS TRIGGER AS $$ +BEGIN + NEW.scheduled_end_time = NEW.scheduled_start_time + (NEW.soaking_duration_minutes || ' minutes')::INTERVAL; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Function to calculate scheduled end time for airdrying +CREATE OR REPLACE FUNCTION calculate_airdrying_scheduled_end_time() +RETURNS TRIGGER AS $$ +BEGIN + NEW.scheduled_end_time = NEW.scheduled_start_time + (NEW.duration_minutes || ' minutes')::INTERVAL; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Function to set airdrying scheduled start time based on soaking end time +CREATE OR REPLACE FUNCTION set_airdrying_scheduled_start_time() +RETURNS TRIGGER AS $$ +BEGIN + -- If this is a new airdrying record and no scheduled_start_time is provided, + -- try to get it from the associated soaking's scheduled_end_time for the same repetition + IF NEW.scheduled_start_time IS NULL THEN + SELECT s.scheduled_end_time INTO NEW.scheduled_start_time + FROM public.soaking s + WHERE s.repetition_id = NEW.repetition_id + LIMIT 1; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Function to set cracking scheduled start time based on airdrying end time +CREATE OR REPLACE FUNCTION set_cracking_scheduled_start_time() +RETURNS TRIGGER AS $$ +BEGIN + -- If this is a new cracking record and no scheduled_start_time is provided, + -- try to get it from the associated airdrying's scheduled_end_time for the same repetition + IF NEW.scheduled_start_time IS NULL THEN + SELECT a.scheduled_end_time INTO NEW.scheduled_start_time + FROM public.airdrying a + WHERE a.repetition_id = NEW.repetition_id + LIMIT 1; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- ============================================= +-- 7. TRIGGERS +-- ============================================= + +-- Triggers for automatic timestamp calculations +DROP TRIGGER IF EXISTS trigger_calculate_soaking_scheduled_end_time ON public.soaking; +CREATE TRIGGER trigger_calculate_soaking_scheduled_end_time + BEFORE INSERT OR UPDATE ON public.soaking + FOR EACH ROW + EXECUTE FUNCTION calculate_soaking_scheduled_end_time(); + +DROP TRIGGER IF EXISTS trigger_calculate_airdrying_scheduled_end_time ON public.airdrying; +CREATE TRIGGER trigger_calculate_airdrying_scheduled_end_time + BEFORE INSERT OR UPDATE ON public.airdrying + FOR EACH ROW + EXECUTE FUNCTION calculate_airdrying_scheduled_end_time(); + +DROP TRIGGER IF EXISTS trigger_set_airdrying_scheduled_start_time ON public.airdrying; +CREATE TRIGGER trigger_set_airdrying_scheduled_start_time + BEFORE INSERT ON public.airdrying + FOR EACH ROW + EXECUTE FUNCTION set_airdrying_scheduled_start_time(); + +DROP TRIGGER IF EXISTS trigger_set_cracking_scheduled_start_time ON public.cracking; +CREATE TRIGGER trigger_set_cracking_scheduled_start_time + BEFORE INSERT ON public.cracking + FOR EACH ROW + EXECUTE FUNCTION set_cracking_scheduled_start_time(); + +-- Triggers for updated_at on all phase tables +CREATE TRIGGER set_updated_at_soaking + BEFORE UPDATE ON public.soaking + FOR EACH ROW + EXECUTE FUNCTION public.handle_updated_at(); + +CREATE TRIGGER set_updated_at_airdrying + BEFORE UPDATE ON public.airdrying + FOR EACH ROW + EXECUTE FUNCTION public.handle_updated_at(); + +CREATE TRIGGER set_updated_at_cracking + BEFORE UPDATE ON public.cracking + FOR EACH ROW + EXECUTE FUNCTION public.handle_updated_at(); + +CREATE TRIGGER set_updated_at_shelling + BEFORE UPDATE ON public.shelling + FOR EACH ROW + EXECUTE FUNCTION public.handle_updated_at(); + +-- ============================================= +-- 8. GRANT PERMISSIONS +-- ============================================= + +GRANT ALL ON public.soaking TO authenticated; +GRANT ALL ON public.airdrying TO authenticated; +GRANT ALL ON public.cracking TO authenticated; +GRANT ALL ON public.shelling TO authenticated; + +-- ============================================= +-- 9. ENABLE ROW LEVEL SECURITY +-- ============================================= + +ALTER TABLE public.soaking ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.airdrying ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.cracking ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.shelling ENABLE ROW LEVEL SECURITY; + +-- ============================================= +-- 10. CREATE RLS POLICIES +-- ============================================= + +-- Create RLS policies for phase tables +CREATE POLICY "Soaking data is viewable by authenticated users" ON public.soaking + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Soaking data is insertable by authenticated users" ON public.soaking + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Soaking data is updatable by authenticated users" ON public.soaking + FOR UPDATE USING (auth.role() = 'authenticated'); + +CREATE POLICY "Soaking data is deletable by authenticated users" ON public.soaking + FOR DELETE USING (auth.role() = 'authenticated'); + +CREATE POLICY "Airdrying data is viewable by authenticated users" ON public.airdrying + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Airdrying data is insertable by authenticated users" ON public.airdrying + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Airdrying data is updatable by authenticated users" ON public.airdrying + FOR UPDATE USING (auth.role() = 'authenticated'); + +CREATE POLICY "Airdrying data is deletable by authenticated users" ON public.airdrying + FOR DELETE USING (auth.role() = 'authenticated'); + +CREATE POLICY "Cracking data is viewable by authenticated users" ON public.cracking + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Cracking data is insertable by authenticated users" ON public.cracking + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Cracking data is updatable by authenticated users" ON public.cracking + FOR UPDATE USING (auth.role() = 'authenticated'); + +CREATE POLICY "Cracking data is deletable by authenticated users" ON public.cracking + FOR DELETE USING (auth.role() = 'authenticated'); + +CREATE POLICY "Shelling data is viewable by authenticated users" ON public.shelling + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Shelling data is insertable by authenticated users" ON public.shelling + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Shelling data is updatable by authenticated users" ON public.shelling + FOR UPDATE USING (auth.role() = 'authenticated'); + +CREATE POLICY "Shelling data is deletable by authenticated users" ON public.shelling + FOR DELETE USING (auth.role() = 'authenticated'); + diff --git a/supabase/migrations/00009_conductor_availability.sql b/supabase/migrations/00009_conductor_availability.sql new file mode 100644 index 0000000..cf0a866 --- /dev/null +++ b/supabase/migrations/00009_conductor_availability.sql @@ -0,0 +1,193 @@ +-- Conductor Availability +-- This migration creates the conductor availability table + +-- ============================================= +-- 1. CONDUCTOR AVAILABILITY TABLE +-- ============================================= + +CREATE TABLE IF NOT EXISTS public.conductor_availability ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES public.user_profiles(id) ON DELETE CASCADE, + available_from TIMESTAMP WITH TIME ZONE NOT NULL, + available_to TIMESTAMP WITH TIME ZONE NOT NULL, + notes TEXT, -- Optional notes about the availability + status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'cancelled')), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + created_by UUID NOT NULL REFERENCES public.user_profiles(id), + + -- Ensure available_to is after available_from + CONSTRAINT valid_time_range CHECK (available_to > available_from), + + -- Ensure availability is in the future (can be modified if needed for past records) + CONSTRAINT future_availability CHECK (available_from >= NOW() - INTERVAL '1 day') +); + +-- ============================================= +-- 2. INDEXES FOR PERFORMANCE +-- ============================================= + +CREATE INDEX IF NOT EXISTS idx_conductor_availability_user_id ON public.conductor_availability(user_id); +CREATE INDEX IF NOT EXISTS idx_conductor_availability_available_from ON public.conductor_availability(available_from); +CREATE INDEX IF NOT EXISTS idx_conductor_availability_available_to ON public.conductor_availability(available_to); +CREATE INDEX IF NOT EXISTS idx_conductor_availability_status ON public.conductor_availability(status); +CREATE INDEX IF NOT EXISTS idx_conductor_availability_created_by ON public.conductor_availability(created_by); +CREATE INDEX IF NOT EXISTS idx_conductor_availability_time_range ON public.conductor_availability(available_from, available_to); + +-- ============================================= +-- 3. FUNCTIONS FOR OVERLAP PREVENTION +-- ============================================= + +-- Function to check for overlapping availabilities +CREATE OR REPLACE FUNCTION public.check_availability_overlap() +RETURNS TRIGGER AS $$ +DECLARE + overlap_count INTEGER; +BEGIN + -- Check for overlapping availabilities for the same user + SELECT COUNT(*) INTO overlap_count + FROM public.conductor_availability + WHERE user_id = NEW.user_id + AND id != COALESCE(NEW.id, '00000000-0000-0000-0000-000000000000'::UUID) + AND status = 'active' + AND ( + -- New availability starts during an existing one + (NEW.available_from >= available_from AND NEW.available_from < available_to) OR + -- New availability ends during an existing one + (NEW.available_to > available_from AND NEW.available_to <= available_to) OR + -- New availability completely contains an existing one + (NEW.available_from <= available_from AND NEW.available_to >= available_to) OR + -- Existing availability completely contains the new one + (available_from <= NEW.available_from AND available_to >= NEW.available_to) + ); + + IF overlap_count > 0 THEN + RAISE EXCEPTION 'Availability overlaps with existing availability for user %. Please adjust the time range or cancel the conflicting availability.', NEW.user_id; + END IF; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Function to get available conductors for a specific time range +CREATE OR REPLACE FUNCTION public.get_available_conductors( + start_time TIMESTAMP WITH TIME ZONE, + end_time TIMESTAMP WITH TIME ZONE +) +RETURNS TABLE ( + user_id UUID, + email TEXT, + available_from TIMESTAMP WITH TIME ZONE, + available_to TIMESTAMP WITH TIME ZONE +) AS $$ +BEGIN + RETURN QUERY + SELECT + ca.user_id, + up.email, + ca.available_from, + ca.available_to + FROM public.conductor_availability ca + JOIN public.user_profiles up ON ca.user_id = up.id + JOIN public.user_roles ur ON up.id = ur.user_id + JOIN public.roles r ON ur.role_id = r.id + WHERE ca.status = 'active' + AND r.name = 'conductor' + AND ca.available_from <= start_time + AND ca.available_to >= end_time + ORDER BY up.email; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Function to check if a conductor is available for a specific time range +CREATE OR REPLACE FUNCTION public.is_conductor_available( + conductor_user_id UUID, + start_time TIMESTAMP WITH TIME ZONE, + end_time TIMESTAMP WITH TIME ZONE +) +RETURNS BOOLEAN AS $$ +DECLARE + availability_count INTEGER; +BEGIN + SELECT COUNT(*) INTO availability_count + FROM public.conductor_availability + WHERE user_id = conductor_user_id + AND status = 'active' + AND available_from <= start_time + AND available_to >= end_time; + + RETURN availability_count > 0; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- ============================================= +-- 4. TRIGGERS +-- ============================================= + +-- Create trigger for updated_at on conductor_availability +CREATE TRIGGER set_updated_at_conductor_availability + BEFORE UPDATE ON public.conductor_availability + FOR EACH ROW + EXECUTE FUNCTION public.handle_updated_at(); + +-- Create trigger to prevent overlapping availabilities +CREATE TRIGGER trigger_check_availability_overlap + BEFORE INSERT OR UPDATE ON public.conductor_availability + FOR EACH ROW + EXECUTE FUNCTION public.check_availability_overlap(); + +-- ============================================= +-- 5. GRANT PERMISSIONS +-- ============================================= + +GRANT ALL ON public.conductor_availability TO authenticated; + +-- ============================================= +-- 6. ENABLE ROW LEVEL SECURITY +-- ============================================= + +ALTER TABLE public.conductor_availability ENABLE ROW LEVEL SECURITY; + +-- ============================================= +-- 7. CREATE RLS POLICIES +-- ============================================= + +CREATE POLICY "conductor_availability_select_policy" ON public.conductor_availability + FOR SELECT + TO authenticated + USING ( + -- Users can view their own availability, admins can view all + user_id = auth.uid() OR public.is_admin() + ); + +CREATE POLICY "conductor_availability_insert_policy" ON public.conductor_availability + FOR INSERT + TO authenticated + WITH CHECK ( + -- Users can create their own availability, admins can create for anyone + (user_id = auth.uid() AND created_by = auth.uid()) OR public.is_admin() + ); + +CREATE POLICY "conductor_availability_update_policy" ON public.conductor_availability + FOR UPDATE + TO authenticated + USING ( + -- Users can update their own availability, admins can update any + user_id = auth.uid() OR public.is_admin() + ) + WITH CHECK ( + -- Users can update their own availability, admins can update any + user_id = auth.uid() OR public.is_admin() + ); + +CREATE POLICY "conductor_availability_delete_policy" ON public.conductor_availability + FOR DELETE + TO authenticated + USING ( + -- Users can delete their own availability, admins can delete any + user_id = auth.uid() OR public.is_admin() + ); + + + + diff --git a/supabase/migrations/00011_views.sql b/supabase/migrations/00011_views.sql new file mode 100644 index 0000000..29885bf --- /dev/null +++ b/supabase/migrations/00011_views.sql @@ -0,0 +1,195 @@ +-- Views +-- This migration creates views for easier querying (must run last after all tables are created) + +-- ============================================= +-- 1. CREATE VIEWS FOR EASIER QUERYING +-- ============================================= + +-- View for experiments with all phase information +-- Note: Since phases are now per-repetition, this view shows phase data from the first repetition +CREATE OR REPLACE VIEW public.experiments_with_phases AS +SELECT + e.id, + e.experiment_number, + e.reps_required, + e.weight_per_repetition_lbs, + e.results_status, + e.completion_status, + e.phase_id, + e.created_at, + e.updated_at, + e.created_by, + ep.name as phase_name, + ep.description as phase_description, + ep.has_soaking, + ep.has_airdrying, + ep.has_cracking, + ep.has_shelling, + er.id as first_repetition_id, + er.repetition_number as first_repetition_number, + s.id as soaking_id, + s.scheduled_start_time as soaking_scheduled_start, + s.actual_start_time as soaking_actual_start, + s.soaking_duration_minutes, + s.scheduled_end_time as soaking_scheduled_end, + s.actual_end_time as soaking_actual_end, + ad.id as airdrying_id, + ad.scheduled_start_time as airdrying_scheduled_start, + ad.actual_start_time as airdrying_actual_start, + ad.duration_minutes as airdrying_duration, + ad.scheduled_end_time as airdrying_scheduled_end, + ad.actual_end_time as airdrying_actual_end, + c.id as cracking_id, + c.scheduled_start_time as cracking_scheduled_start, + c.actual_start_time as cracking_actual_start, + c.actual_end_time as cracking_actual_end, + mt.name as machine_type_name, + sh.id as shelling_id, + sh.scheduled_start_time as shelling_scheduled_start, + sh.actual_start_time as shelling_actual_start, + sh.actual_end_time as shelling_actual_end +FROM public.experiments e +LEFT JOIN public.experiment_phases ep ON e.phase_id = ep.id +LEFT JOIN LATERAL ( + SELECT id, repetition_number + FROM public.experiment_repetitions + WHERE experiment_id = e.id + ORDER BY repetition_number + LIMIT 1 +) er ON true +LEFT JOIN public.soaking s ON s.repetition_id = er.id +LEFT JOIN public.airdrying ad ON ad.repetition_id = er.id +LEFT JOIN public.cracking c ON c.repetition_id = er.id +LEFT JOIN public.machine_types mt ON c.machine_type_id = mt.id +LEFT JOIN public.shelling sh ON sh.repetition_id = er.id; + +-- View for repetitions with phase information +CREATE OR REPLACE VIEW public.repetitions_with_phases AS +SELECT + er.id, + er.experiment_id, + er.repetition_number, + er.status, + er.created_at, + er.updated_at, + er.created_by, + e.experiment_number, + e.phase_id, + e.weight_per_repetition_lbs, + ep.name as phase_name, + ep.has_soaking, + ep.has_airdrying, + ep.has_cracking, + ep.has_shelling, + s.scheduled_start_time as soaking_scheduled_start, + s.actual_start_time as soaking_actual_start, + s.soaking_duration_minutes, + s.scheduled_end_time as soaking_scheduled_end, + s.actual_end_time as soaking_actual_end, + ad.scheduled_start_time as airdrying_scheduled_start, + ad.actual_start_time as airdrying_actual_start, + ad.duration_minutes as airdrying_duration, + ad.scheduled_end_time as airdrying_scheduled_end, + ad.actual_end_time as airdrying_actual_end, + c.scheduled_start_time as cracking_scheduled_start, + c.actual_start_time as cracking_actual_start, + c.actual_end_time as cracking_actual_end, + mt.name as machine_type_name, + sh.scheduled_start_time as shelling_scheduled_start, + sh.actual_start_time as shelling_actual_start, + sh.actual_end_time as shelling_actual_end +FROM public.experiment_repetitions er +JOIN public.experiments e ON er.experiment_id = e.id +LEFT JOIN public.experiment_phases ep ON e.phase_id = ep.id +LEFT JOIN public.soaking s ON er.id = s.repetition_id +LEFT JOIN public.airdrying ad ON er.id = ad.repetition_id +LEFT JOIN public.cracking c ON er.id = c.repetition_id +LEFT JOIN public.machine_types mt ON c.machine_type_id = mt.id +LEFT JOIN public.shelling sh ON er.id = sh.repetition_id; + +-- View for available conductors with their roles +CREATE OR REPLACE VIEW public.available_conductors AS +SELECT + ca.*, + up.email, + up.first_name, + up.last_name, + r.name as role_name +FROM public.conductor_availability ca +JOIN public.user_profiles up ON ca.user_id = up.id +JOIN public.user_roles ur ON up.id = ur.user_id +JOIN public.roles r ON ur.role_id = r.id +WHERE ca.status = 'active' +AND r.name = 'conductor'; + +-- ============================================= +-- 2. GRANT PERMISSIONS FOR VIEWS +-- ============================================= + +GRANT SELECT ON public.experiments_with_phases TO authenticated; +GRANT SELECT ON public.repetitions_with_phases TO authenticated; +GRANT SELECT ON public.available_conductors TO authenticated; + +-- ============================================= +-- 3. COMMENTS FOR DOCUMENTATION +-- ============================================= + +COMMENT ON VIEW public.experiments_with_phases IS 'Comprehensive view of experiments with all phase information and timing details'; +COMMENT ON VIEW public.repetitions_with_phases IS 'View of experiment repetitions with associated phase data'; +COMMENT ON VIEW public.available_conductors IS 'View of currently available conductors with their profile information'; + +-- ============================================= +-- 4. SAMPLE DATA FUNCTIONS (OPTIONAL) +-- ============================================= + +-- Function to create sample roles +CREATE OR REPLACE FUNCTION public.create_sample_roles() +RETURNS VOID AS $$ +BEGIN + INSERT INTO public.roles (name, description) VALUES + ('admin', 'System administrator with full access'), + ('conductor', 'Experiment conductor with limited access'), + ('researcher', 'Research staff with read-only access') + ON CONFLICT (name) DO NOTHING; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Function to create sample machine types +CREATE OR REPLACE FUNCTION public.create_sample_machine_types() +RETURNS VOID AS $$ +BEGIN + INSERT INTO public.machine_types (name, description, created_by) VALUES + ('JC Cracker', 'Johnson Cracker machine for pecan shelling', (SELECT id FROM public.user_profiles LIMIT 1)), + ('Meyer Cracker', 'Meyer Cracker machine for pecan shelling', (SELECT id FROM public.user_profiles LIMIT 1)) + ON CONFLICT (name) DO NOTHING; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Function to create sample experiment phases +CREATE OR REPLACE FUNCTION public.create_sample_experiment_phases() +RETURNS VOID AS $$ +DECLARE + jc_cracker_id UUID; + meyer_cracker_id UUID; +BEGIN + -- Get machine type IDs + SELECT id INTO jc_cracker_id FROM public.machine_types WHERE name = 'JC Cracker'; + SELECT id INTO meyer_cracker_id FROM public.machine_types WHERE name = 'Meyer Cracker'; + + INSERT INTO public.experiment_phases (name, description, has_soaking, has_airdrying, has_cracking, has_shelling, cracking_machine_type_id, created_by) VALUES + ('Full Process - JC Cracker', 'Complete pecan processing with JC Cracker', true, true, true, true, jc_cracker_id, (SELECT id FROM public.user_profiles LIMIT 1)), + ('Full Process - Meyer Cracker', 'Complete pecan processing with Meyer Cracker', true, true, true, true, meyer_cracker_id, (SELECT id FROM public.user_profiles LIMIT 1)), + ('Cracking Only - JC Cracker', 'JC Cracker cracking process only', false, false, true, false, jc_cracker_id, (SELECT id FROM public.user_profiles LIMIT 1)), + ('Cracking Only - Meyer Cracker', 'Meyer Cracker cracking process only', false, false, true, false, meyer_cracker_id, (SELECT id FROM public.user_profiles LIMIT 1)) + ON CONFLICT (name) DO NOTHING; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- ============================================= +-- 5. GRANT PERMISSIONS FOR SAMPLE DATA FUNCTIONS +-- ============================================= + +GRANT EXECUTE ON FUNCTION public.create_sample_roles() TO authenticated; +GRANT EXECUTE ON FUNCTION public.create_sample_machine_types() TO authenticated; +GRANT EXECUTE ON FUNCTION public.create_sample_experiment_phases() TO authenticated; + diff --git a/supabase/migrations/00012_unified_phase_executions.sql b/supabase/migrations/00012_unified_phase_executions.sql new file mode 100644 index 0000000..978e21f --- /dev/null +++ b/supabase/migrations/00012_unified_phase_executions.sql @@ -0,0 +1,300 @@ +-- Unified Phase Executions Table +-- This migration replaces the separate phase tables (soaking, airdrying, cracking, shelling) +-- with a unified table that properly supports repetitions + +-- ============================================= +-- 1. CREATE UNIFIED PHASE EXECUTIONS TABLE +-- ============================================= + +CREATE TABLE IF NOT EXISTS public.experiment_phase_executions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + repetition_id UUID NOT NULL REFERENCES public.experiment_repetitions(id) ON DELETE CASCADE, + phase_type TEXT NOT NULL CHECK (phase_type IN ('soaking', 'airdrying', 'cracking', 'shelling')), + + -- Scheduling fields (common to all phases) + scheduled_start_time TIMESTAMP WITH TIME ZONE NOT NULL, + scheduled_end_time TIMESTAMP WITH TIME ZONE, + actual_start_time TIMESTAMP WITH TIME ZONE, + actual_end_time TIMESTAMP WITH TIME ZONE, + + -- Phase-specific parameters (nullable, only relevant for specific phases) + -- Soaking + soaking_duration_minutes INTEGER CHECK (soaking_duration_minutes > 0), + + -- Airdrying + duration_minutes INTEGER CHECK (duration_minutes > 0), + + -- Cracking + machine_type_id UUID REFERENCES public.machine_types(id), + + -- Status tracking + status TEXT NOT NULL DEFAULT 'pending' + CHECK (status IN ('pending', 'scheduled', 'in_progress', 'completed', 'cancelled')), + + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + created_by UUID NOT NULL REFERENCES public.user_profiles(id), + + -- Ensure one execution per phase type per repetition + CONSTRAINT unique_phase_per_repetition UNIQUE (repetition_id, phase_type) +); + +-- ============================================= +-- 2. INDEXES FOR PERFORMANCE +-- ============================================= + +CREATE INDEX IF NOT EXISTS idx_phase_executions_repetition_id + ON public.experiment_phase_executions(repetition_id); +CREATE INDEX IF NOT EXISTS idx_phase_executions_phase_type + ON public.experiment_phase_executions(phase_type); +CREATE INDEX IF NOT EXISTS idx_phase_executions_status + ON public.experiment_phase_executions(status); +CREATE INDEX IF NOT EXISTS idx_phase_executions_scheduled_start_time + ON public.experiment_phase_executions(scheduled_start_time); +CREATE INDEX IF NOT EXISTS idx_phase_executions_machine_type_id + ON public.experiment_phase_executions(machine_type_id); +CREATE INDEX IF NOT EXISTS idx_phase_executions_created_by + ON public.experiment_phase_executions(created_by); + +-- ============================================= +-- 3. FUNCTION: Calculate Sequential Phase Start Times +-- ============================================= + +CREATE OR REPLACE FUNCTION calculate_sequential_phase_start_time() +RETURNS TRIGGER AS $$ +DECLARE + prev_phase_end_time TIMESTAMP WITH TIME ZONE; + phase_order TEXT[] := ARRAY['soaking', 'airdrying', 'cracking', 'shelling']; + current_phase_index INT; + prev_phase_name TEXT; +BEGIN + -- Find current phase index in the sequence + SELECT array_position(phase_order, NEW.phase_type) INTO current_phase_index; + + -- If not the first phase, get previous phase's end time from the same repetition + IF current_phase_index > 1 THEN + prev_phase_name := phase_order[current_phase_index - 1]; + + SELECT scheduled_end_time INTO prev_phase_end_time + FROM public.experiment_phase_executions + WHERE repetition_id = NEW.repetition_id + AND phase_type = prev_phase_name + ORDER BY created_at DESC + LIMIT 1; + + -- If previous phase exists and has an end time, use it as start time + IF prev_phase_end_time IS NOT NULL THEN + NEW.scheduled_start_time := prev_phase_end_time; + END IF; + END IF; + + -- Calculate end time based on duration (for phases with duration) + IF NEW.phase_type = 'soaking' AND NEW.soaking_duration_minutes IS NOT NULL THEN + NEW.scheduled_end_time := NEW.scheduled_start_time + + (NEW.soaking_duration_minutes || ' minutes')::INTERVAL; + ELSIF NEW.phase_type = 'airdrying' AND NEW.duration_minutes IS NOT NULL THEN + NEW.scheduled_end_time := NEW.scheduled_start_time + + (NEW.duration_minutes || ' minutes')::INTERVAL; + END IF; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- ============================================= +-- 4. FUNCTION: Auto-create Phase Executions for New Repetition +-- ============================================= + +CREATE OR REPLACE FUNCTION create_phase_executions_for_repetition() +RETURNS TRIGGER AS $$ +DECLARE + exp_phase_config RECORD; + phase_type_list TEXT[] := ARRAY[]::TEXT[]; + phase_name TEXT; +BEGIN + -- Get experiment phase configuration + -- Note: Phase durations may need to be set later when scheduling + SELECT + ep.has_soaking, + ep.has_airdrying, + ep.has_cracking, + ep.has_shelling, + ep.cracking_machine_type_id + INTO exp_phase_config + FROM public.experiments e + JOIN public.experiment_phases ep ON e.phase_id = ep.id + WHERE e.id = NEW.experiment_id; + + -- Build list of phases to create based on experiment configuration + IF exp_phase_config.has_soaking THEN + phase_type_list := array_append(phase_type_list, 'soaking'); + END IF; + IF exp_phase_config.has_airdrying THEN + phase_type_list := array_append(phase_type_list, 'airdrying'); + END IF; + IF exp_phase_config.has_cracking THEN + phase_type_list := array_append(phase_type_list, 'cracking'); + END IF; + IF exp_phase_config.has_shelling THEN + phase_type_list := array_append(phase_type_list, 'shelling'); + END IF; + + -- Create phase executions for each required phase + FOREACH phase_name IN ARRAY phase_type_list + LOOP + INSERT INTO public.experiment_phase_executions ( + repetition_id, + phase_type, + scheduled_start_time, + status, + created_by, + -- Phase-specific parameters + soaking_duration_minutes, + duration_minutes, + machine_type_id + ) + VALUES ( + NEW.id, + phase_name, + NOW(), -- Default start time, will be updated when scheduled or by sequential calculation + 'pending', + NEW.created_by, + -- Set phase-specific parameters + -- Note: Durations will be set when the repetition is scheduled + -- These can be NULL initially and updated later + NULL, -- soaking_duration_minutes (set when scheduled) + NULL, -- duration_minutes (set when scheduled) + CASE WHEN phase_name = 'cracking' + THEN exp_phase_config.cracking_machine_type_id + ELSE NULL END + ); + END LOOP; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- ============================================= +-- 5. TRIGGERS +-- ============================================= + +-- Trigger to automatically calculate sequential times and durations +CREATE TRIGGER trigger_calculate_sequential_phase_times + BEFORE INSERT OR UPDATE ON public.experiment_phase_executions + FOR EACH ROW + EXECUTE FUNCTION calculate_sequential_phase_start_time(); + +-- Trigger to auto-create phases when repetition is created +CREATE TRIGGER trigger_create_phase_executions + AFTER INSERT ON public.experiment_repetitions + FOR EACH ROW + EXECUTE FUNCTION create_phase_executions_for_repetition(); + +-- Trigger for updated_at +CREATE TRIGGER set_updated_at_phase_executions + BEFORE UPDATE ON public.experiment_phase_executions + FOR EACH ROW + EXECUTE FUNCTION public.handle_updated_at(); + +-- ============================================= +-- 6. CREATE VIEWS FOR PHASE-SPECIFIC ACCESS (Backward Compatibility) +-- ============================================= + +-- These views allow existing code to work with phase-specific "tables" +CREATE OR REPLACE VIEW public.soaking_view AS +SELECT + id, + (SELECT experiment_id FROM experiment_repetitions WHERE id = repetition_id) as experiment_id, + repetition_id, + scheduled_start_time, + actual_start_time, + soaking_duration_minutes, + scheduled_end_time, + actual_end_time, + created_at, + updated_at, + created_by +FROM public.experiment_phase_executions +WHERE phase_type = 'soaking'; + +CREATE OR REPLACE VIEW public.airdrying_view AS +SELECT + id, + (SELECT experiment_id FROM experiment_repetitions WHERE id = repetition_id) as experiment_id, + repetition_id, + scheduled_start_time, + actual_start_time, + duration_minutes, + scheduled_end_time, + actual_end_time, + created_at, + updated_at, + created_by +FROM public.experiment_phase_executions +WHERE phase_type = 'airdrying'; + +CREATE OR REPLACE VIEW public.cracking_view AS +SELECT + id, + (SELECT experiment_id FROM experiment_repetitions WHERE id = repetition_id) as experiment_id, + repetition_id, + machine_type_id, + scheduled_start_time, + actual_start_time, + actual_end_time, + created_at, + updated_at, + created_by +FROM public.experiment_phase_executions +WHERE phase_type = 'cracking'; + +CREATE OR REPLACE VIEW public.shelling_view AS +SELECT + id, + (SELECT experiment_id FROM experiment_repetitions WHERE id = repetition_id) as experiment_id, + repetition_id, + scheduled_start_time, + actual_start_time, + actual_end_time, + created_at, + updated_at, + created_by +FROM public.experiment_phase_executions +WHERE phase_type = 'shelling'; + +-- ============================================= +-- 7. GRANT PERMISSIONS +-- ============================================= + +GRANT ALL ON public.experiment_phase_executions TO authenticated; +GRANT SELECT ON public.soaking_view TO authenticated; +GRANT SELECT ON public.airdrying_view TO authenticated; +GRANT SELECT ON public.cracking_view TO authenticated; +GRANT SELECT ON public.shelling_view TO authenticated; + +-- ============================================= +-- 8. ENABLE ROW LEVEL SECURITY +-- ============================================= + +ALTER TABLE public.experiment_phase_executions ENABLE ROW LEVEL SECURITY; + +-- ============================================= +-- 9. CREATE RLS POLICIES +-- ============================================= + +CREATE POLICY "Phase executions are viewable by authenticated users" + ON public.experiment_phase_executions + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Phase executions are insertable by authenticated users" + ON public.experiment_phase_executions + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Phase executions are updatable by authenticated users" + ON public.experiment_phase_executions + FOR UPDATE USING (auth.role() = 'authenticated'); + +CREATE POLICY "Phase executions are deletable by authenticated users" + ON public.experiment_phase_executions + FOR DELETE USING (auth.role() = 'authenticated'); + diff --git a/supabase/migrations/00013_add_repetition_id_to_cracker_parameters.sql b/supabase/migrations/00013_add_repetition_id_to_cracker_parameters.sql new file mode 100644 index 0000000..5d4efeb --- /dev/null +++ b/supabase/migrations/00013_add_repetition_id_to_cracker_parameters.sql @@ -0,0 +1,46 @@ +-- Add repetition_id foreign key to cracker parameters tables +-- This migration adds a foreign key to link cracker parameters to their repetitions + +-- ============================================= +-- 1. ADD REPETITION_ID TO JC CRACKER PARAMETERS +-- ============================================= + +ALTER TABLE public.jc_cracker_parameters +ADD COLUMN IF NOT EXISTS repetition_id UUID REFERENCES public.experiment_repetitions(id) ON DELETE CASCADE; + +-- Add index for performance +CREATE INDEX IF NOT EXISTS idx_jc_cracker_parameters_repetition_id + ON public.jc_cracker_parameters(repetition_id); + +-- Add unique constraint to ensure one parameter set per repetition +ALTER TABLE public.jc_cracker_parameters +ADD CONSTRAINT unique_jc_cracker_parameters_per_repetition + UNIQUE (repetition_id); + +-- ============================================= +-- 2. ADD REPETITION_ID TO MEYER CRACKER PARAMETERS +-- ============================================= + +ALTER TABLE public.meyer_cracker_parameters +ADD COLUMN IF NOT EXISTS repetition_id UUID REFERENCES public.experiment_repetitions(id) ON DELETE CASCADE; + +-- Add index for performance +CREATE INDEX IF NOT EXISTS idx_meyer_cracker_parameters_repetition_id + ON public.meyer_cracker_parameters(repetition_id); + +-- Add unique constraint to ensure one parameter set per repetition +ALTER TABLE public.meyer_cracker_parameters +ADD CONSTRAINT unique_meyer_cracker_parameters_per_repetition + UNIQUE (repetition_id); + + + + + + + + + + + + diff --git a/supabase/migrations/00014_experiments_with_reps_and_phases_view.sql b/supabase/migrations/00014_experiments_with_reps_and_phases_view.sql new file mode 100644 index 0000000..ea0cd59 --- /dev/null +++ b/supabase/migrations/00014_experiments_with_reps_and_phases_view.sql @@ -0,0 +1,277 @@ +-- View: Experiments with All Repetitions and Phase Parameters +-- This view provides a comprehensive view of experiments with all their repetitions +-- and all phase execution parameters (soaking, airdrying, cracking, shelling) + +CREATE OR REPLACE VIEW public.experiments_with_all_reps_and_phases AS +SELECT + -- Experiment fields + e.id as experiment_id, + e.experiment_number, + e.reps_required, + e.weight_per_repetition_lbs, + e.results_status, + e.completion_status, + e.phase_id, + e.created_at as experiment_created_at, + e.updated_at as experiment_updated_at, + e.created_by as experiment_created_by, + + -- Phase information + ep.name as phase_name, + ep.description as phase_description, + ep.has_soaking, + ep.has_airdrying, + ep.has_cracking, + ep.has_shelling, + ep.cracking_machine_type_id as phase_cracking_machine_type_id, + + -- Repetition fields + er.id as repetition_id, + er.repetition_number, + er.status as repetition_status, + er.scheduled_date, + er.created_at as repetition_created_at, + er.updated_at as repetition_updated_at, + er.created_by as repetition_created_by, + + -- Soaking phase execution + soaking_e.id as soaking_execution_id, + soaking_e.scheduled_start_time as soaking_scheduled_start, + soaking_e.actual_start_time as soaking_actual_start, + soaking_e.soaking_duration_minutes, + soaking_e.scheduled_end_time as soaking_scheduled_end, + soaking_e.actual_end_time as soaking_actual_end, + soaking_e.status as soaking_status, + + -- Airdrying phase execution + airdrying_e.id as airdrying_execution_id, + airdrying_e.scheduled_start_time as airdrying_scheduled_start, + airdrying_e.actual_start_time as airdrying_actual_start, + airdrying_e.duration_minutes as airdrying_duration_minutes, + airdrying_e.scheduled_end_time as airdrying_scheduled_end, + airdrying_e.actual_end_time as airdrying_actual_end, + airdrying_e.status as airdrying_status, + + -- Cracking phase execution + cracking_e.id as cracking_execution_id, + cracking_e.scheduled_start_time as cracking_scheduled_start, + cracking_e.actual_start_time as cracking_actual_start, + cracking_e.scheduled_end_time as cracking_scheduled_end, + cracking_e.actual_end_time as cracking_actual_end, + cracking_e.machine_type_id as cracking_machine_type_id, + cracking_e.status as cracking_status, + mt.name as machine_type_name, + + -- Shelling phase execution + shelling_e.id as shelling_execution_id, + shelling_e.scheduled_start_time as shelling_scheduled_start, + shelling_e.actual_start_time as shelling_actual_start, + shelling_e.scheduled_end_time as shelling_scheduled_end, + shelling_e.actual_end_time as shelling_actual_end, + shelling_e.status as shelling_status + +FROM public.experiments e +LEFT JOIN public.experiment_phases ep ON e.phase_id = ep.id +LEFT JOIN public.experiment_repetitions er ON er.experiment_id = e.id +LEFT JOIN public.experiment_phase_executions soaking_e + ON soaking_e.repetition_id = er.id AND soaking_e.phase_type = 'soaking' +LEFT JOIN public.experiment_phase_executions airdrying_e + ON airdrying_e.repetition_id = er.id AND airdrying_e.phase_type = 'airdrying' +LEFT JOIN public.experiment_phase_executions cracking_e + ON cracking_e.repetition_id = er.id AND cracking_e.phase_type = 'cracking' +LEFT JOIN public.experiment_phase_executions shelling_e + ON shelling_e.repetition_id = er.id AND shelling_e.phase_type = 'shelling' +LEFT JOIN public.machine_types mt ON cracking_e.machine_type_id = mt.id +ORDER BY e.experiment_number, er.repetition_number; + +-- Grant permissions +GRANT SELECT ON public.experiments_with_all_reps_and_phases TO authenticated; + +-- Function: Get experiment with all repetitions and phase parameters +-- This function returns a JSON structure with experiment and all its repetitions +CREATE OR REPLACE FUNCTION public.get_experiment_with_reps_and_phases(p_experiment_id UUID) +RETURNS TABLE ( + experiment_id UUID, + experiment_number INTEGER, + phase_name TEXT, + repetitions JSONB +) AS $$ +BEGIN + RETURN QUERY + SELECT + e.id, + e.experiment_number, + ep.name, + COALESCE( + jsonb_agg( + jsonb_build_object( + 'repetition_id', er.id, + 'repetition_number', er.repetition_number, + 'status', er.status, + 'scheduled_date', er.scheduled_date, + 'soaking', jsonb_build_object( + 'scheduled_start', soaking_e.scheduled_start_time, + 'actual_start', soaking_e.actual_start_time, + 'duration_minutes', soaking_e.soaking_duration_minutes, + 'scheduled_end', soaking_e.scheduled_end_time, + 'actual_end', soaking_e.actual_end_time, + 'status', soaking_e.status + ), + 'airdrying', jsonb_build_object( + 'scheduled_start', airdrying_e.scheduled_start_time, + 'actual_start', airdrying_e.actual_start_time, + 'duration_minutes', airdrying_e.duration_minutes, + 'scheduled_end', airdrying_e.scheduled_end_time, + 'actual_end', airdrying_e.actual_end_time, + 'status', airdrying_e.status + ), + 'cracking', jsonb_build_object( + 'scheduled_start', cracking_e.scheduled_start_time, + 'actual_start', cracking_e.actual_start_time, + 'scheduled_end', cracking_e.scheduled_end_time, + 'actual_end', cracking_e.actual_end_time, + 'machine_type_id', cracking_e.machine_type_id, + 'machine_type_name', mt.name, + 'status', cracking_e.status + ), + 'shelling', jsonb_build_object( + 'scheduled_start', shelling_e.scheduled_start_time, + 'actual_start', shelling_e.actual_start_time, + 'scheduled_end', shelling_e.scheduled_end_time, + 'actual_end', shelling_e.actual_end_time, + 'status', shelling_e.status + ) + ) + ORDER BY er.repetition_number + ), + '[]'::jsonb + ) as repetitions + FROM public.experiments e + LEFT JOIN public.experiment_phases ep ON e.phase_id = ep.id + LEFT JOIN public.experiment_repetitions er ON er.experiment_id = e.id + LEFT JOIN public.experiment_phase_executions soaking_e + ON soaking_e.repetition_id = er.id AND soaking_e.phase_type = 'soaking' + LEFT JOIN public.experiment_phase_executions airdrying_e + ON airdrying_e.repetition_id = er.id AND airdrying_e.phase_type = 'airdrying' + LEFT JOIN public.experiment_phase_executions cracking_e + ON cracking_e.repetition_id = er.id AND cracking_e.phase_type = 'cracking' + LEFT JOIN public.experiment_phase_executions shelling_e + ON shelling_e.repetition_id = er.id AND shelling_e.phase_type = 'shelling' + LEFT JOIN public.machine_types mt ON cracking_e.machine_type_id = mt.id + WHERE e.id = p_experiment_id + GROUP BY e.id, e.experiment_number, ep.name; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Grant execute permission +GRANT EXECUTE ON FUNCTION public.get_experiment_with_reps_and_phases(UUID) TO authenticated; + +-- Update the existing experiments_with_phases view to use unified table +CREATE OR REPLACE VIEW public.experiments_with_phases AS +SELECT + e.id, + e.experiment_number, + e.reps_required, + e.weight_per_repetition_lbs, + e.results_status, + e.completion_status, + e.phase_id, + e.created_at, + e.updated_at, + e.created_by, + ep.name as phase_name, + ep.description as phase_description, + ep.has_soaking, + ep.has_airdrying, + ep.has_cracking, + ep.has_shelling, + er.id as first_repetition_id, + er.repetition_number as first_repetition_number, + soaking_e.id as soaking_id, + soaking_e.scheduled_start_time as soaking_scheduled_start, + soaking_e.actual_start_time as soaking_actual_start, + soaking_e.soaking_duration_minutes, + soaking_e.scheduled_end_time as soaking_scheduled_end, + soaking_e.actual_end_time as soaking_actual_end, + airdrying_e.id as airdrying_id, + airdrying_e.scheduled_start_time as airdrying_scheduled_start, + airdrying_e.actual_start_time as airdrying_actual_start, + airdrying_e.duration_minutes as airdrying_duration, + airdrying_e.scheduled_end_time as airdrying_scheduled_end, + airdrying_e.actual_end_time as airdrying_actual_end, + cracking_e.id as cracking_id, + cracking_e.scheduled_start_time as cracking_scheduled_start, + cracking_e.actual_start_time as cracking_actual_start, + cracking_e.actual_end_time as cracking_actual_end, + mt.name as machine_type_name, + shelling_e.id as shelling_id, + shelling_e.scheduled_start_time as shelling_scheduled_start, + shelling_e.actual_start_time as shelling_actual_start, + shelling_e.actual_end_time as shelling_actual_end +FROM public.experiments e +LEFT JOIN public.experiment_phases ep ON e.phase_id = ep.id +LEFT JOIN LATERAL ( + SELECT id, repetition_number + FROM public.experiment_repetitions + WHERE experiment_id = e.id + ORDER BY repetition_number + LIMIT 1 +) er ON true +LEFT JOIN public.experiment_phase_executions soaking_e + ON soaking_e.repetition_id = er.id AND soaking_e.phase_type = 'soaking' +LEFT JOIN public.experiment_phase_executions airdrying_e + ON airdrying_e.repetition_id = er.id AND airdrying_e.phase_type = 'airdrying' +LEFT JOIN public.experiment_phase_executions cracking_e + ON cracking_e.repetition_id = er.id AND cracking_e.phase_type = 'cracking' +LEFT JOIN public.experiment_phase_executions shelling_e + ON shelling_e.repetition_id = er.id AND shelling_e.phase_type = 'shelling' +LEFT JOIN public.machine_types mt ON cracking_e.machine_type_id = mt.id; + +-- Update repetitions_with_phases view to use unified table +CREATE OR REPLACE VIEW public.repetitions_with_phases AS +SELECT + er.id, + er.experiment_id, + er.repetition_number, + er.status, + er.created_at, + er.updated_at, + er.created_by, + e.experiment_number, + e.phase_id, + e.weight_per_repetition_lbs, + ep.name as phase_name, + ep.has_soaking, + ep.has_airdrying, + ep.has_cracking, + ep.has_shelling, + soaking_e.scheduled_start_time as soaking_scheduled_start, + soaking_e.actual_start_time as soaking_actual_start, + soaking_e.soaking_duration_minutes, + soaking_e.scheduled_end_time as soaking_scheduled_end, + soaking_e.actual_end_time as soaking_actual_end, + airdrying_e.scheduled_start_time as airdrying_scheduled_start, + airdrying_e.actual_start_time as airdrying_actual_start, + airdrying_e.duration_minutes as airdrying_duration, + airdrying_e.scheduled_end_time as airdrying_scheduled_end, + airdrying_e.actual_end_time as airdrying_actual_end, + cracking_e.scheduled_start_time as cracking_scheduled_start, + cracking_e.actual_start_time as cracking_actual_start, + cracking_e.actual_end_time as cracking_actual_end, + mt.name as machine_type_name, + shelling_e.scheduled_start_time as shelling_scheduled_start, + shelling_e.actual_start_time as shelling_actual_start, + shelling_e.actual_end_time as shelling_actual_end +FROM public.experiment_repetitions er +JOIN public.experiments e ON er.experiment_id = e.id +LEFT JOIN public.experiment_phases ep ON e.phase_id = ep.id +LEFT JOIN public.experiment_phase_executions soaking_e + ON er.id = soaking_e.repetition_id AND soaking_e.phase_type = 'soaking' +LEFT JOIN public.experiment_phase_executions airdrying_e + ON er.id = airdrying_e.repetition_id AND airdrying_e.phase_type = 'airdrying' +LEFT JOIN public.experiment_phase_executions cracking_e + ON er.id = cracking_e.repetition_id AND cracking_e.phase_type = 'cracking' +LEFT JOIN public.experiment_phase_executions shelling_e + ON er.id = shelling_e.repetition_id AND shelling_e.phase_type = 'shelling' +LEFT JOIN public.machine_types mt ON cracking_e.machine_type_id = mt.id; + diff --git a/supabase/seed_01_users.sql b/supabase/seed_01_users.sql new file mode 100755 index 0000000..2ae1120 --- /dev/null +++ b/supabase/seed_01_users.sql @@ -0,0 +1,600 @@ +-- Seed Data for USDA Vision Pecan Experiments System +-- This file populates the database with initial data + +-- ============================================= +-- 1. INSERT ROLES +-- ============================================= + +INSERT INTO public.roles (name, description) VALUES +('admin', 'System administrator with full access to all features'), +('conductor', 'Experiment conductor who can manage experiments and view all data'), +('analyst', 'Data analyst who can view and analyze experiment results'), +('data recorder', 'Data entry specialist who can record experiment measurements'); + +-- ============================================= +-- 2. CREATE ADMIN USER +-- ============================================= + +-- Create admin user in auth.users +INSERT INTO auth.users ( + instance_id, + id, + aud, + role, + email, + encrypted_password, + email_confirmed_at, + created_at, + updated_at, + confirmation_token, + email_change, + email_change_token_new, + recovery_token +) VALUES ( + '00000000-0000-0000-0000-000000000000', + uuid_generate_v4(), + 'authenticated', + 'authenticated', + 's.alireza.v@gmail.com', + crypt('admin123', gen_salt('bf')), + NOW(), + NOW(), + NOW(), + '', + '', + '', + '' +); + +-- Create user profile +INSERT INTO public.user_profiles (id, email, first_name, last_name, status) +SELECT id, email, 'Alireza', 'Vaezi', 'active' +FROM auth.users +WHERE email = 's.alireza.v@gmail.com' +; + +-- Assign admin role +INSERT INTO public.user_roles (user_id, role_id, assigned_by) +SELECT + up.id, + r.id, + up.id +FROM public.user_profiles up +CROSS JOIN public.roles r +WHERE up.email = 's.alireza.v@gmail.com' +AND r.name = 'admin' +; + +-- ============================================= +-- 3. CREATE ADDITIONAL USERS +-- ============================================= + +-- Create Claire Floyd (Conductor & Data Recorder) +INSERT INTO auth.users ( + instance_id, + id, + aud, + role, + email, + encrypted_password, + email_confirmed_at, + created_at, + updated_at, + confirmation_token, + email_change, + email_change_token_new, + recovery_token +) VALUES ( + '00000000-0000-0000-0000-000000000000', + uuid_generate_v4(), + 'authenticated', + 'authenticated', + 'Ashlyn.Floyd@uga.edu', + crypt('password123', gen_salt('bf')), + NOW(), + NOW(), + NOW(), + '', + '', + '', + '' +); + +INSERT INTO public.user_profiles (id, email, first_name, last_name, status) +SELECT id, email, 'Claire', 'Floyd', 'active' +FROM auth.users +WHERE email = 'Ashlyn.Floyd@uga.edu' +; + +INSERT INTO public.user_roles (user_id, role_id, assigned_by) +SELECT + up.id, + r.id, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +FROM public.user_profiles up +CROSS JOIN public.roles r +WHERE up.email = 'Ashlyn.Floyd@uga.edu' +AND r.name IN ('conductor', 'data recorder') +; + +-- Create Bruna Dos-Santos (Conductor & Data Recorder) +INSERT INTO auth.users ( + instance_id, + id, + aud, + role, + email, + encrypted_password, + email_confirmed_at, + created_at, + updated_at, + confirmation_token, + email_change, + email_change_token_new, + recovery_token +) VALUES ( + '00000000-0000-0000-0000-000000000000', + uuid_generate_v4(), + 'authenticated', + 'authenticated', + 'bkvsantos@uga.edu', + crypt('password123', gen_salt('bf')), + NOW(), + NOW(), + NOW(), + '', + '', + '', + '' +); + +INSERT INTO public.user_profiles (id, email, first_name, last_name, status) +SELECT id, email, 'Bruna', 'Dos-Santos', 'active' +FROM auth.users +WHERE email = 'bkvsantos@uga.edu' +; + +INSERT INTO public.user_roles (user_id, role_id, assigned_by) +SELECT + up.id, + r.id, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +FROM public.user_profiles up +CROSS JOIN public.roles r +WHERE up.email = 'bkvsantos@uga.edu' +AND r.name IN ('conductor', 'data recorder') +; + +-- Create Beni Rodriguez (Conductor & Data Recorder) +INSERT INTO auth.users ( + instance_id, + id, + aud, + role, + email, + encrypted_password, + email_confirmed_at, + created_at, + updated_at, + confirmation_token, + email_change, + email_change_token_new, + recovery_token +) VALUES ( + '00000000-0000-0000-0000-000000000000', + uuid_generate_v4(), + 'authenticated', + 'authenticated', + 'Beni.Rodriguez@uga.edu', + crypt('password123', gen_salt('bf')), + NOW(), + NOW(), + NOW(), + '', + '', + '', + '' +); + +INSERT INTO public.user_profiles (id, email, first_name, last_name, status) +SELECT id, email, 'Beni', 'Rodriguez', 'active' +FROM auth.users +WHERE email = 'Beni.Rodriguez@uga.edu' +; + +INSERT INTO public.user_roles (user_id, role_id, assigned_by) +SELECT + up.id, + r.id, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +FROM public.user_profiles up +CROSS JOIN public.roles r +WHERE up.email = 'Beni.Rodriguez@uga.edu' +AND r.name IN ('conductor', 'data recorder') +; + +-- Create Brendan Surio (Data Recorder) +INSERT INTO auth.users ( + instance_id, + id, + aud, + role, + email, + encrypted_password, + email_confirmed_at, + created_at, + updated_at, + confirmation_token, + email_change, + email_change_token_new, + recovery_token +) VALUES ( + '00000000-0000-0000-0000-000000000000', + uuid_generate_v4(), + 'authenticated', + 'authenticated', + 'Brendan.Surio@uga.edu', + crypt('password123', gen_salt('bf')), + NOW(), + NOW(), + NOW(), + '', + '', + '', + '' +); + +INSERT INTO public.user_profiles (id, email, first_name, last_name, status) +SELECT id, email, 'Brendan', 'Surio', 'active' +FROM auth.users +WHERE email = 'Brendan.Surio@uga.edu' +; + +INSERT INTO public.user_roles (user_id, role_id, assigned_by) +SELECT + up.id, + r.id, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +FROM public.user_profiles up +CROSS JOIN public.roles r +WHERE up.email = 'Brendan.Surio@uga.edu' +AND r.name IN ('conductor', 'data recorder') + +; + +-- Create William Mcconnell (Data Recorder) +INSERT INTO auth.users ( + instance_id, + id, + aud, + role, + email, + encrypted_password, + email_confirmed_at, + created_at, + updated_at, + confirmation_token, + email_change, + email_change_token_new, + recovery_token +) VALUES ( + '00000000-0000-0000-0000-000000000000', + uuid_generate_v4(), + 'authenticated', + 'authenticated', + 'William.McConnell@uga.edu', + crypt('password123', gen_salt('bf')), + NOW(), + NOW(), + NOW(), + '', + '', + '', + '' +); + +INSERT INTO public.user_profiles (id, email, first_name, last_name, status) +SELECT id, email, 'William', 'Mcconnell', 'active' +FROM auth.users +WHERE email = 'William.McConnell@uga.edu' +; + +INSERT INTO public.user_roles (user_id, role_id, assigned_by) +SELECT + up.id, + r.id, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +FROM public.user_profiles up +CROSS JOIN public.roles r +WHERE up.email = 'William.McConnell@uga.edu' +AND r.name IN ('conductor', 'data recorder') + +; + +-- Create Camille Deguzman (Data Recorder) +INSERT INTO auth.users ( + instance_id, + id, + aud, + role, + email, + encrypted_password, + email_confirmed_at, + created_at, + updated_at, + confirmation_token, + email_change, + email_change_token_new, + recovery_token +) VALUES ( + '00000000-0000-0000-0000-000000000000', + uuid_generate_v4(), + 'authenticated', + 'authenticated', + 'cpd08598@uga.edu', + crypt('password123', gen_salt('bf')), + NOW(), + NOW(), + NOW(), + '', + '', + '', + '' +); + +INSERT INTO public.user_profiles (id, email, first_name, last_name, status) +SELECT id, email, 'Camille', 'Deguzman', 'active' +FROM auth.users +WHERE email = 'cpd08598@uga.edu' +; + +INSERT INTO public.user_roles (user_id, role_id, assigned_by) +SELECT + up.id, + r.id, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +FROM public.user_profiles up +CROSS JOIN public.roles r +WHERE up.email = 'cpd08598@uga.edu' +AND r.name IN ('conductor', 'data recorder') + +; + +-- Create Justin Hetzler (Data Recorder) +INSERT INTO auth.users ( + instance_id, + id, + aud, + role, + email, + encrypted_password, + email_confirmed_at, + created_at, + updated_at, + confirmation_token, + email_change, + email_change_token_new, + recovery_token +) VALUES ( + '00000000-0000-0000-0000-000000000000', + uuid_generate_v4(), + 'authenticated', + 'authenticated', + 'Justin.Hetzler@uga.edu', + crypt('password123', gen_salt('bf')), + NOW(), + NOW(), + NOW(), + '', + '', + '', + '' +); + +INSERT INTO public.user_profiles (id, email, first_name, last_name, status) +SELECT id, email, 'Justin', 'Hetzler', 'active' +FROM auth.users +WHERE email = 'Justin.Hetzler@uga.edu' +; + +INSERT INTO public.user_roles (user_id, role_id, assigned_by) +SELECT + up.id, + r.id, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +FROM public.user_profiles up +CROSS JOIN public.roles r +WHERE up.email = 'Justin.Hetzler@uga.edu' +AND r.name IN ('conductor', 'data recorder') + +; + +-- Create Joshua Wilson (Data Recorder) +INSERT INTO auth.users ( + instance_id, + id, + aud, + role, + email, + encrypted_password, + email_confirmed_at, + created_at, + updated_at, + confirmation_token, + email_change, + email_change_token_new, + recovery_token +) VALUES ( + '00000000-0000-0000-0000-000000000000', + uuid_generate_v4(), + 'authenticated', + 'authenticated', + 'jdw58940@uga.edu', + crypt('password123', gen_salt('bf')), + NOW(), + NOW(), + NOW(), + '', + '', + '', + '' +); + +INSERT INTO public.user_profiles (id, email, first_name, last_name, status) +SELECT id, email, 'Joshua', 'Wilson', 'active' +FROM auth.users +WHERE email = 'jdw58940@uga.edu' +; + +INSERT INTO public.user_roles (user_id, role_id, assigned_by) +SELECT + up.id, + r.id, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +FROM public.user_profiles up +CROSS JOIN public.roles r +WHERE up.email = 'jdw58940@uga.edu' +AND r.name IN ('conductor', 'data recorder') + +; + +-- Create Sydney Orlofsky (Data Recorder) +INSERT INTO auth.users ( + instance_id, + id, + aud, + role, + email, + encrypted_password, + email_confirmed_at, + created_at, + updated_at, + confirmation_token, + email_change, + email_change_token_new, + recovery_token +) VALUES ( + '00000000-0000-0000-0000-000000000000', + uuid_generate_v4(), + 'authenticated', + 'authenticated', + 'Sydney.Orlofsky@uga.edu', + crypt('password123', gen_salt('bf')), + NOW(), + NOW(), + NOW(), + '', + '', + '', + '' +); + +INSERT INTO public.user_profiles (id, email, first_name, last_name, status) +SELECT id, email, 'Sydney', 'Orlofsky', 'active' +FROM auth.users +WHERE email = 'Sydney.Orlofsky@uga.edu' +; + +INSERT INTO public.user_roles (user_id, role_id, assigned_by) +SELECT + up.id, + r.id, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +FROM public.user_profiles up +CROSS JOIN public.roles r +WHERE up.email = 'Sydney.Orlofsky@uga.edu' +AND r.name IN ('conductor', 'data recorder') + +; + +-- Create engr-ugaif user (Conductor, Analyst & Data Recorder) +INSERT INTO auth.users ( + instance_id, + id, + aud, + role, + email, + encrypted_password, + email_confirmed_at, + created_at, + updated_at, + confirmation_token, + email_change, + email_change_token_new, + recovery_token +) VALUES ( + '00000000-0000-0000-0000-000000000000', + uuid_generate_v4(), + 'authenticated', + 'authenticated', + 'engr-ugaif@uga.edu', + crypt('1048lab&2021', gen_salt('bf')), + NOW(), + NOW(), + NOW(), + '', + '', + '', + '' +); + +INSERT INTO public.user_profiles (id, email, status) +SELECT id, email, 'active' +FROM auth.users +WHERE email = 'engr-ugaif@uga.edu' +; + +INSERT INTO public.user_roles (user_id, role_id, assigned_by) +SELECT + up.id, + r.id, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +FROM public.user_profiles up +CROSS JOIN public.roles r +WHERE up.email = 'engr-ugaif@uga.edu' +AND r.name IN ('conductor', 'analyst', 'data recorder') +; + +-- ============================================= +-- 4. CREATE MACHINE TYPES +-- ============================================= + +-- Insert default machine types +INSERT INTO public.machine_types (name, description, created_by) VALUES +('JC Cracker', 'JC Cracker machine with plate contact frequency and throughput parameters', (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com')), +('Meyer Cracker', 'Meyer Cracker machine with motor speed and jig displacement parameters', (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com')) +ON CONFLICT (name) DO NOTHING; + +-- ============================================= +-- 5. CREATE EXPERIMENT PHASES +-- ============================================= + +-- Create "Phase 2 of JC Experiments" phase +INSERT INTO public.experiment_phases (name, description, has_soaking, has_airdrying, has_cracking, has_shelling, cracking_machine_type_id, created_by) +SELECT + 'Phase 2 of JC Experiments', + 'Second phase of JC Cracker experiments for pecan processing optimization', + true, + true, + true, + false, + (SELECT id FROM public.machine_types WHERE name = 'JC Cracker'), + up.id +FROM public.user_profiles up +WHERE up.email = 's.alireza.v@gmail.com' +; + +-- Create "Post Workshop Meyer Experiments" phase +INSERT INTO public.experiment_phases (name, description, has_soaking, has_airdrying, has_cracking, has_shelling, cracking_machine_type_id, created_by) +SELECT + 'Post Workshop Meyer Experiments', + 'Post workshop Meyer Cracker experiments for pecan processing optimization', + true, + true, + true, + false, + (SELECT id FROM public.machine_types WHERE name = 'Meyer Cracker'), + up.id +FROM public.user_profiles up +WHERE up.email = 's.alireza.v@gmail.com' +; diff --git a/supabase/seed_02_phase2_experiments.sql b/supabase/seed_02_phase2_experiments.sql new file mode 100644 index 0000000..6117e7f --- /dev/null +++ b/supabase/seed_02_phase2_experiments.sql @@ -0,0 +1,3196 @@ +-- ============================================== +-- 6. CREATE EXPERIMENTS FOR PHASE 2 +-- ============================================== + +-- This seed file creates experiments from phase_2_JC_experimental_run_sheet.csv +-- Each experiment has 3 repetitions with specific parameters +-- Experiment numbers are incremented by 1 (CSV 0-19 becomes DB 1-20) + +-- Create experiments +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 1, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 2, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 3, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 4, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 5, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 6, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 7, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 8, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 9, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 10, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 11, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 12, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 13, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 14, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 15, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 16, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 17, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 18, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 19, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +INSERT INTO public.experiments (experiment_number, reps_required, weight_per_repetition_lbs, phase_id, created_by) +VALUES ( + 20, + 3, + 33, + (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments'), + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Create repetitions for each experiment +-- Note: Phase executions are automatically created by trigger when repetitions are inserted + +-- Experiment 1, Repetition 1 (CSV experiment_number 0) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 1 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 1, Repetition 2 (CSV experiment_number 0) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 1 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 1, Repetition 3 (CSV experiment_number 0) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 1 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 2, Repetition 1 (CSV experiment_number 1) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 2 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 2, Repetition 2 (CSV experiment_number 1) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 2 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 2, Repetition 3 (CSV experiment_number 1) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 2 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 3, Repetition 1 (CSV experiment_number 2) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 3 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 3, Repetition 2 (CSV experiment_number 2) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 3 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 3, Repetition 3 (CSV experiment_number 2) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 3 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 4, Repetition 1 (CSV experiment_number 3) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 4 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 4, Repetition 2 (CSV experiment_number 3) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 4 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 4, Repetition 3 (CSV experiment_number 3) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 4 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 5, Repetition 1 (CSV experiment_number 4) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 5 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 5, Repetition 2 (CSV experiment_number 4) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 5 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 5, Repetition 3 (CSV experiment_number 4) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 5 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 6, Repetition 1 (CSV experiment_number 5) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 6 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 6, Repetition 2 (CSV experiment_number 5) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 6 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 6, Repetition 3 (CSV experiment_number 5) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 6 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 7, Repetition 1 (CSV experiment_number 6) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 7 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 7, Repetition 2 (CSV experiment_number 6) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 7 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 7, Repetition 3 (CSV experiment_number 6) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 7 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 8, Repetition 1 (CSV experiment_number 7) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 8 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 8, Repetition 2 (CSV experiment_number 7) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 8 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 8, Repetition 3 (CSV experiment_number 7) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 8 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 9, Repetition 1 (CSV experiment_number 8) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 9 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 9, Repetition 2 (CSV experiment_number 8) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 9 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 9, Repetition 3 (CSV experiment_number 8) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 9 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 10, Repetition 1 (CSV experiment_number 9) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 10 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 10, Repetition 2 (CSV experiment_number 9) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 10 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 10, Repetition 3 (CSV experiment_number 9) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 10 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 11, Repetition 1 (CSV experiment_number 10) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 11 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 11, Repetition 2 (CSV experiment_number 10) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 11 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 11, Repetition 3 (CSV experiment_number 10) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 11 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 12, Repetition 1 (CSV experiment_number 11) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 12 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 12, Repetition 2 (CSV experiment_number 11) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 12 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 12, Repetition 3 (CSV experiment_number 11) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 12 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 13, Repetition 1 (CSV experiment_number 12) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 13 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 13, Repetition 2 (CSV experiment_number 12) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 13 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 13, Repetition 3 (CSV experiment_number 12) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 13 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 14, Repetition 1 (CSV experiment_number 13) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 14 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 14, Repetition 2 (CSV experiment_number 13) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 14 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 14, Repetition 3 (CSV experiment_number 13) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 14 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 15, Repetition 1 (CSV experiment_number 14) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 15 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 15, Repetition 2 (CSV experiment_number 14) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 15 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 15, Repetition 3 (CSV experiment_number 14) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 15 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 16, Repetition 1 (CSV experiment_number 15) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 16 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 16, Repetition 2 (CSV experiment_number 15) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 16 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 16, Repetition 3 (CSV experiment_number 15) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 16 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 17, Repetition 1 (CSV experiment_number 16) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 17 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 17, Repetition 2 (CSV experiment_number 16) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 17 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 17, Repetition 3 (CSV experiment_number 16) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 17 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 18, Repetition 1 (CSV experiment_number 17) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 18 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 18, Repetition 2 (CSV experiment_number 17) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 18 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 18, Repetition 3 (CSV experiment_number 17) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 18 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 19, Repetition 1 (CSV experiment_number 18) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 19 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 19, Repetition 2 (CSV experiment_number 18) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 19 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 19, Repetition 3 (CSV experiment_number 18) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 19 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 20, Repetition 1 (CSV experiment_number 19) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 20 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 1, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 20, Repetition 2 (CSV experiment_number 19) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 20 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 2, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Experiment 20, Repetition 3 (CSV experiment_number 19) +INSERT INTO public.experiment_repetitions (experiment_id, repetition_number, created_by) +VALUES ( + (SELECT id FROM public.experiments WHERE experiment_number = 20 AND phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 3, + (SELECT id FROM public.user_profiles WHERE email = 's.alireza.v@gmail.com') +); + +-- Update phase executions with parameters from CSV + +-- Experiment 1, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 2040 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 1 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 19 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 1 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 1, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 2040 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 1 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 19 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 1 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 1, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 2040 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 1 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 19 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 1 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 2, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1440 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 2 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 27 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 2 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 2, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1440 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 2 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 27 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 2 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 2, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1440 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 2 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 27 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 2 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 3, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 2280 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 3 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 10 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 3 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 3, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 2280 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 3 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 10 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 3 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 3, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 2280 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 3 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 10 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 3 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 4, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 660 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 4 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 36 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 4 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 4, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 660 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 4 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 36 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 4 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 4, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 660 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 4 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 36 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 4 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 5, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 780 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 5 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 41 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 5 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 5, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 780 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 5 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 41 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 5 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 5, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 780 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 5 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 41 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 5 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 6, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1800 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 6 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 33 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 6 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 6, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1800 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 6 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 33 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 6 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 6, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1800 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 6 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 33 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 6 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 7, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 600 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 7 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 22 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 7 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 7, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 600 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 7 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 22 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 7 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 7, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 600 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 7 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 22 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 7 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 8, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 900 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 8 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 30 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 8 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 8, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 900 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 8 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 30 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 8 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 8, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 900 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 8 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 30 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 8 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 9, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1620 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 9 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 12 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 9 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 9, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1620 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 9 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 12 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 9 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 9, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1620 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 9 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 12 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 9 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 10, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1920 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 10 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 26 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 10 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 10, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1920 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 10 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 26 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 10 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 10, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1920 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 10 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 26 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 10 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 11, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1560 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 11 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 60 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 11 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 11, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1560 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 11 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 60 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 11 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 11, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1560 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 11 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 60 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 11 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 12, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1440 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 12 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 59 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 12 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 12, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1440 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 12 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 59 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 12 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 12, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1440 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 12 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 59 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 12 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 13, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1680 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 13 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 59 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 13 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 13, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1680 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 13 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 59 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 13 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 13, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1680 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 13 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 59 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 13 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 14, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1260 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 14 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 59 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 14 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 14, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1260 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 14 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 59 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 14 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 14, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1260 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 14 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 59 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 14 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 15, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1320 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 15 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 59 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 15 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 15, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1320 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 15 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 59 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 15 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 15, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1320 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 15 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 59 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 15 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 16, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 960 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 16 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 60 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 16 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 16, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 960 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 16 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 60 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 16 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 16, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 960 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 16 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 60 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 16 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 17, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1200 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 17 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 59 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 17 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 17, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1200 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 17 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 59 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 17 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 17, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1200 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 17 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 59 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 17 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 18, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 2040 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 18 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 60 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 18 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 18, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 2040 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 18 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 60 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 18 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 18, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 2040 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 18 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 60 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 18 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 19, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1080 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 19 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 49 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 19 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 19, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1080 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 19 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 49 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 19 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 19, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 1080 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 19 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 49 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 19 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 20, Repetition 1 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 660 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 20 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 25 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 20 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 20, Repetition 2 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 660 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 20 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 25 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 20 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Experiment 20, Repetition 3 - Update phase executions +UPDATE public.experiment_phase_executions +SET soaking_duration_minutes = 660 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 20 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'soaking'; + +UPDATE public.experiment_phase_executions +SET duration_minutes = 25 +WHERE repetition_id = ( + SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 20 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments') +) +AND phase_type = 'airdrying'; + +-- Create JC cracker parameters for each repetition +-- Each parameter set is linked to its repetition via repetition_id + +-- Experiment 1, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 1 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 53.0, + 28.0, + 0.05, + -0.09 +); + +-- Experiment 1, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 1 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 53.0, + 28.0, + 0.05, + -0.09 +); + +-- Experiment 1, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 1 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 53.0, + 28.0, + 0.05, + -0.09 +); + +-- Experiment 2, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 2 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 34.0, + 29.0, + 0.03, + 0.01 +); + +-- Experiment 2, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 2 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 34.0, + 29.0, + 0.03, + 0.01 +); + +-- Experiment 2, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 2 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 34.0, + 29.0, + 0.03, + 0.01 +); + +-- Experiment 3, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 3 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 60.0, + 28.0, + 0.06, + -0.1 +); + +-- Experiment 3, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 3 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 60.0, + 28.0, + 0.06, + -0.1 +); + +-- Experiment 3, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 3 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 60.0, + 28.0, + 0.06, + -0.1 +); + +-- Experiment 4, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 4 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 42.0, + 13.0, + 0.07, + -0.07 +); + +-- Experiment 4, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 4 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 42.0, + 13.0, + 0.07, + -0.07 +); + +-- Experiment 4, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 4 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 42.0, + 13.0, + 0.07, + -0.07 +); + +-- Experiment 5, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 5 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 41.0, + 38.0, + 0.05, + 0.03 +); + +-- Experiment 5, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 5 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 41.0, + 38.0, + 0.05, + 0.03 +); + +-- Experiment 5, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 5 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 41.0, + 38.0, + 0.05, + 0.03 +); + +-- Experiment 6, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 6 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 30.0, + 36.0, + 0.05, + -0.04 +); + +-- Experiment 6, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 6 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 30.0, + 36.0, + 0.05, + -0.04 +); + +-- Experiment 6, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 6 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 30.0, + 36.0, + 0.05, + -0.04 +); + +-- Experiment 7, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 7 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 37.0, + 30.0, + 0.06, + 0.02 +); + +-- Experiment 7, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 7 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 37.0, + 30.0, + 0.06, + 0.02 +); + +-- Experiment 7, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 7 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 37.0, + 30.0, + 0.06, + 0.02 +); + +-- Experiment 8, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 8 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 35.0, + 32.0, + 0.05, + -0.07 +); + +-- Experiment 8, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 8 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 35.0, + 32.0, + 0.05, + -0.07 +); + +-- Experiment 8, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 8 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 35.0, + 32.0, + 0.05, + -0.07 +); + +-- Experiment 9, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 9 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 55.0, + 24.0, + 0.04, + 0.04 +); + +-- Experiment 9, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 9 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 55.0, + 24.0, + 0.04, + 0.04 +); + +-- Experiment 9, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 9 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 55.0, + 24.0, + 0.04, + 0.04 +); + +-- Experiment 10, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 10 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 47.0, + 26.0, + 0.07, + 0.03 +); + +-- Experiment 10, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 10 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 47.0, + 26.0, + 0.07, + 0.03 +); + +-- Experiment 10, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 10 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 47.0, + 26.0, + 0.07, + 0.03 +); + +-- Experiment 11, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 11 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 44.0, + 12.0, + 0.08, + -0.1 +); + +-- Experiment 11, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 11 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 44.0, + 12.0, + 0.08, + -0.1 +); + +-- Experiment 11, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 11 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 44.0, + 12.0, + 0.08, + -0.1 +); + +-- Experiment 12, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 12 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 42.0, + 25.0, + 0.07, + -0.05 +); + +-- Experiment 12, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 12 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 42.0, + 25.0, + 0.07, + -0.05 +); + +-- Experiment 12, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 12 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 42.0, + 25.0, + 0.07, + -0.05 +); + +-- Experiment 13, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 13 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 37.0, + 23.0, + 0.06, + -0.08 +); + +-- Experiment 13, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 13 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 37.0, + 23.0, + 0.06, + -0.08 +); + +-- Experiment 13, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 13 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 37.0, + 23.0, + 0.06, + -0.08 +); + +-- Experiment 14, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 14 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 41.0, + 21.0, + 0.06, + -0.09 +); + +-- Experiment 14, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 14 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 41.0, + 21.0, + 0.06, + -0.09 +); + +-- Experiment 14, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 14 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 41.0, + 21.0, + 0.06, + -0.09 +); + +-- Experiment 15, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 15 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 45.0, + 17.0, + 0.07, + -0.08 +); + +-- Experiment 15, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 15 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 45.0, + 17.0, + 0.07, + -0.08 +); + +-- Experiment 15, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 15 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 45.0, + 17.0, + 0.07, + -0.08 +); + +-- Experiment 16, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 16 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 30.0, + 24.0, + 0.07, + 0.02 +); + +-- Experiment 16, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 16 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 30.0, + 24.0, + 0.07, + 0.02 +); + +-- Experiment 16, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 16 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 30.0, + 24.0, + 0.07, + 0.02 +); + +-- Experiment 17, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 17 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 41.0, + 14.0, + 0.07, + 0.04 +); + +-- Experiment 17, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 17 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 41.0, + 14.0, + 0.07, + 0.04 +); + +-- Experiment 17, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 17 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 41.0, + 14.0, + 0.07, + 0.04 +); + +-- Experiment 18, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 18 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 34.0, + 29.0, + 0.07, + -0.09 +); + +-- Experiment 18, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 18 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 34.0, + 29.0, + 0.07, + -0.09 +); + +-- Experiment 18, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 18 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 34.0, + 29.0, + 0.07, + -0.09 +); + +-- Experiment 19, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 19 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 38.0, + 35.0, + 0.07, + -0.08 +); + +-- Experiment 19, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 19 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 38.0, + 35.0, + 0.07, + -0.08 +); + +-- Experiment 19, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 19 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 38.0, + 35.0, + 0.07, + -0.08 +); + +-- Experiment 20, Repetition 1 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 20 + AND er.repetition_number = 1 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 56.0, + 34.0, + 0.06, + -0.09 +); + +-- Experiment 20, Repetition 2 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 20 + AND er.repetition_number = 2 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 56.0, + 34.0, + 0.06, + -0.09 +); + +-- Experiment 20, Repetition 3 - JC Cracker Parameters +INSERT INTO public.jc_cracker_parameters ( + repetition_id, + plate_contact_frequency_hz, + throughput_rate_pecans_sec, + crush_amount_in, + entry_exit_height_diff_in +) VALUES ( + (SELECT er.id FROM public.experiment_repetitions er + JOIN public.experiments e ON er.experiment_id = e.id + WHERE e.experiment_number = 20 + AND er.repetition_number = 3 + AND e.phase_id = (SELECT id FROM public.experiment_phases WHERE name = 'Phase 2 of JC Experiments')), + 56.0, + 34.0, + 0.06, + -0.09 +); From 8cbb2d7f00ac1fe6fbf2cca2e510bb25580324bf Mon Sep 17 00:00:00 2001 From: salirezav Date: Thu, 18 Dec 2025 19:57:27 -0500 Subject: [PATCH 03/13] feat: Add dynamic host IP detection for Docker Compose and Supabase config - Add docker-compose.sh wrapper script that auto-detects host IP - Update docker-compose.yml to use environment variable substitution - Update Supabase config.toml files to use HOST_SITE_URL and SUPABASE_API_URL env vars - Add scripts/get-host-ip.sh for IP detection - Add scripts/set-host-env.sh for environment setup - Add scripts/supabase-with-env.sh wrapper for Supabase CLI - Add documentation for Docker Compose environment setup - Update README.md with new usage instructions - Replace hardcoded URLs with dynamic environment variables --- README.md | 17 +- docker-compose.sh | 90 ++++++++++ docker-compose.yml | 11 +- docs/DOCKER_COMPOSE_ENV_SETUP.md | 167 +++++++++++++++++ docs/HOST_IP_CONFIGURATION.md | 168 ++++++++++++++++++ .../supabase/config.toml | 12 +- scripts/get-host-ip.sh | 86 +++++++++ scripts/set-host-env.sh | 33 ++++ scripts/supabase-with-env.sh | 24 +++ supabase/config.toml | 12 +- 10 files changed, 603 insertions(+), 17 deletions(-) create mode 100755 docker-compose.sh create mode 100644 docs/DOCKER_COMPOSE_ENV_SETUP.md create mode 100644 docs/HOST_IP_CONFIGURATION.md create mode 100755 scripts/get-host-ip.sh create mode 100755 scripts/set-host-env.sh create mode 100755 scripts/supabase-with-env.sh diff --git a/README.md b/README.md index 37f6fae..8ae816f 100644 --- a/README.md +++ b/README.md @@ -12,18 +12,21 @@ A unified monorepo combining the camera API service and the web dashboard for US ### Production Mode (Docker Compose) -1) Copy env template and set values (for web/Supabase): +**Recommended**: Use the `docker-compose.sh` wrapper script which automatically detects your host IP and sets all environment variables: ```bash -cp .env.example .env -# set VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY in .env +./docker-compose.sh up --build -d ``` -2) Start the stack: +The wrapper script automatically: +- Detects your host machine's IP address +- Sets all required environment variables (VITE_*, HOST_SITE_URL, etc.) +- Generates/updates the `.env` file +- Runs docker-compose with your arguments -```bash -docker compose up --build -``` +**Alternative**: If you prefer to use `docker compose` directly, the `.env` file will be auto-generated on first run, or you can set environment variables manually. + +For more details, see [Docker Compose Environment Setup](docs/DOCKER_COMPOSE_ENV_SETUP.md). - Web: - API: diff --git a/docker-compose.sh b/docker-compose.sh new file mode 100755 index 0000000..17484e0 --- /dev/null +++ b/docker-compose.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# Docker Compose wrapper that automatically detects and sets host IP +# Usage: ./docker-compose.sh [docker-compose-command] [args...] +# Example: ./docker-compose.sh up -d + +set -e + +# Get the script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$SCRIPT_DIR" + +# Change to project root +cd "$PROJECT_ROOT" || exit 1 + +# Detect host IP +HOST_IP=$("$SCRIPT_DIR/scripts/get-host-ip.sh") +if [ $? -ne 0 ] || [ -z "$HOST_IP" ] || [ "$HOST_IP" = "127.0.0.1" ]; then + echo "Warning: Could not detect host IP, using localhost" >&2 + HOST_IP="localhost" +fi + +# Set environment variables for docker-compose +export HOST_IP +export HOST_SITE_URL="http://${HOST_IP}:3000" +export SUPABASE_API_URL="http://${HOST_IP}:54321" +export VITE_SUPABASE_URL="http://${HOST_IP}:54321" +export VITE_MEDIA_API_URL="http://${HOST_IP}:8090" +export VITE_VISION_API_URL="http://${HOST_IP}:8000" + +# Also set for Supabase CLI (runs on host) +export HOST_SITE_URL +export SUPABASE_API_URL + +echo "Auto-detected host IP: $HOST_IP" >&2 +echo "Environment variables set:" >&2 +echo " HOST_IP=$HOST_IP" >&2 +echo " HOST_SITE_URL=$HOST_SITE_URL" >&2 +echo " SUPABASE_API_URL=$SUPABASE_API_URL" >&2 +echo " VITE_SUPABASE_URL=$VITE_SUPABASE_URL" >&2 +echo " VITE_MEDIA_API_URL=$VITE_MEDIA_API_URL" >&2 +echo " VITE_VISION_API_URL=$VITE_VISION_API_URL" >&2 +echo "" >&2 + +# Generate/update .env file for docker-compose +ENV_FILE="$PROJECT_ROOT/.env" +cat > "$ENV_FILE" <&2 + # Backup original if it doesn't have our marker + if ! grep -q "# Auto-updated by docker-compose.sh" "$WEB_ENV_FILE" 2>/dev/null; then + cp "$WEB_ENV_FILE" "$WEB_ENV_FILE.backup.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true + fi + + # Update the file, preserving other settings + # Read existing file and replace URLs + sed -i.bak \ + -e "s|VITE_SUPABASE_URL=.*|VITE_SUPABASE_URL=$VITE_SUPABASE_URL|" \ + -e "s|VITE_VISION_API_URL=.*|VITE_VISION_API_URL=$VITE_VISION_API_URL|" \ + -e "s|VITE_MEDIA_API_URL=.*|VITE_MEDIA_API_URL=$VITE_MEDIA_API_URL|" \ + -e "s|VITE_VIDEO_REMOTE_URL=.*|VITE_VIDEO_REMOTE_URL=http://${HOST_IP}:3001/assets/remoteEntry.js?v=\$(date +%s)|" \ + -e "s|VITE_VISION_SYSTEM_REMOTE_URL=.*|VITE_VISION_SYSTEM_REMOTE_URL=http://${HOST_IP}:3002/assets/remoteEntry.js|" \ + "$WEB_ENV_FILE" 2>/dev/null || true + + # Add marker comment if not present + if ! grep -q "# Auto-updated by docker-compose.sh" "$WEB_ENV_FILE" 2>/dev/null; then + echo "" >> "$WEB_ENV_FILE" + echo "# Auto-updated by docker-compose.sh" >> "$WEB_ENV_FILE" + fi + + rm -f "$WEB_ENV_FILE.bak" 2>/dev/null || true +fi + +echo "Updated .env file with detected values" >&2 +echo "" >&2 + +# Run docker-compose with all arguments +exec docker compose "$@" + diff --git a/docker-compose.yml b/docker-compose.yml index 1a2d85f..588eecf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -376,7 +376,8 @@ services: environment: - CHOKIDAR_USEPOLLING=true - TZ=America/New_York - - VITE_SUPABASE_URL=http://localhost:54321 + # Use environment variable with fallback to localhost + - VITE_SUPABASE_URL=${VITE_SUPABASE_URL:-http://localhost:54321} command: > sh -lc " npm install; @@ -403,8 +404,9 @@ services: environment: - CHOKIDAR_USEPOLLING=true - TZ=America/New_York - - VITE_MEDIA_API_URL=http://exp-dash:8090 - - VITE_VISION_API_URL=http://exp-dash:8000 + # Use environment variables with fallback to localhost + - VITE_MEDIA_API_URL=${VITE_MEDIA_API_URL:-http://localhost:8090} + - VITE_VISION_API_URL=${VITE_VISION_API_URL:-http://localhost:8000} volumes: - ./video-remote:/app command: > @@ -426,7 +428,8 @@ services: environment: - CHOKIDAR_USEPOLLING=true - TZ=America/New_York - - VITE_VISION_API_URL=http://exp-dash:8000 + # Use environment variable with fallback to localhost + - VITE_VISION_API_URL=${VITE_VISION_API_URL:-http://localhost:8000} volumes: - ./vision-system-remote:/app command: > diff --git a/docs/DOCKER_COMPOSE_ENV_SETUP.md b/docs/DOCKER_COMPOSE_ENV_SETUP.md new file mode 100644 index 0000000..3cea8a0 --- /dev/null +++ b/docs/DOCKER_COMPOSE_ENV_SETUP.md @@ -0,0 +1,167 @@ +# Docker Compose Environment Setup + +## Overview + +Docker Compose now automatically detects the host machine's IP address and sets all necessary environment variables. No manual script execution is required! + +## Quick Start + +Instead of running `docker compose` directly, use the wrapper script: + +```bash +./docker-compose.sh up -d +./docker-compose.sh down +./docker-compose.sh logs -f +# etc. +``` + +The wrapper script automatically: +1. Detects your host machine's IP address +2. Sets `HOST_SITE_URL` for Supabase auth redirects +3. Sets all `VITE_*` environment variables for React apps +4. Generates/updates the `.env` file +5. Runs docker-compose with all your arguments + +## What Gets Auto-Configured + +The script automatically sets these environment variables: + +- `HOST_IP` - The detected host machine IP (e.g., `192.168.1.87`) +- `HOST_SITE_URL` - Full URL for Supabase auth (e.g., `http://192.168.1.87:3000`) +- `VITE_SUPABASE_URL` - Supabase API URL (e.g., `http://192.168.1.87:54321`) +- `VITE_MEDIA_API_URL` - Media API URL (e.g., `http://192.168.1.87:8090`) +- `VITE_VISION_API_URL` - Vision API URL (e.g., `http://192.168.1.87:8000`) + +## Manual Override + +If you need to override the auto-detected values, you can: + +1. **Set environment variables before running**: + ```bash + export HOST_IP=192.168.1.100 + ./docker-compose.sh up -d + ``` + +2. **Edit the `.env` file** (generated by the script): + ```bash + # Edit .env file + nano .env + # Then run docker compose normally + docker compose up -d + ``` + +3. **Use docker-compose directly** (with fallback values): + ```bash + # The docker-compose.yml has fallback values, so this still works + docker compose up -d + ``` + +## Environment Files + +### Root `.env` File + +The root `.env` file is auto-generated by `docker-compose.sh` and contains: +``` +HOST_IP=192.168.1.87 +HOST_SITE_URL=http://192.168.1.87:3000 +VITE_SUPABASE_URL=http://192.168.1.87:54321 +VITE_MEDIA_API_URL=http://192.168.1.87:8090 +VITE_VISION_API_URL=http://192.168.1.87:8000 +``` + +This file is automatically updated each time you run `docker-compose.sh`. + +### `management-dashboard-web-app/.env` + +This file is optional and can contain: +- `VITE_SUPABASE_URL` - Overrides the docker-compose value +- `VITE_SUPABASE_ANON_KEY` - Required for Supabase authentication + +Example: +```env +VITE_SUPABASE_URL=http://localhost:54321 +VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +## For Supabase CLI + +When using Supabase CLI (not Docker Compose services), you still need to set `HOST_SITE_URL`: + +```bash +# Option 1: Use the environment setup script +source ./scripts/set-host-env.sh +supabase start + +# Option 2: Use the wrapper script +./scripts/supabase-with-env.sh start + +# Option 3: Manual +export HOST_SITE_URL="http://$(./scripts/get-host-ip.sh):3000" +supabase start +``` + +## How It Works + +1. **IP Detection**: `scripts/get-host-ip.sh` detects the host IP using multiple methods +2. **Variable Setting**: `docker-compose.sh` sets all environment variables +3. **Env File Generation**: Creates/updates `.env` file for docker-compose to read +4. **Docker Compose**: Reads `.env` and substitutes `${VARIABLE}` syntax in `docker-compose.yml` + +## Troubleshooting + +### IP Detection Fails + +If the script can't detect your IP: + +```bash +# Check what IP is detected +./scripts/get-host-ip.sh + +# Manually set it +export HOST_IP=192.168.1.100 +./docker-compose.sh up -d +``` + +### Services Can't Connect + +Make sure: +1. The detected IP is accessible from your network +2. Firewall allows connections on the required ports +3. Services are using the correct URLs (check container logs) + +### Using Different Ports + +If your services run on different ports, edit `.env` after it's generated: + +```bash +./docker-compose.sh config # Generates .env +nano .env # Edit ports +docker compose up -d # Use docker compose directly now +``` + +## Migration from Manual Setup + +If you were previously setting environment variables manually: + +1. **Remove manual exports** from your shell profile +2. **Use `./docker-compose.sh`** instead of `docker compose` +3. **Delete old `.env` files** if they have hardcoded values (the script will regenerate) + +## Example Workflow + +```bash +# 1. Start all services (auto-detects IP) +./docker-compose.sh up -d + +# 2. Check logs +./docker-compose.sh logs -f web + +# 3. Stop services +./docker-compose.sh down + +# 4. Restart with new IP detection +./docker-compose.sh up -d +``` + +That's it! No more manual environment variable setup needed. + diff --git a/docs/HOST_IP_CONFIGURATION.md b/docs/HOST_IP_CONFIGURATION.md new file mode 100644 index 0000000..850d30f --- /dev/null +++ b/docs/HOST_IP_CONFIGURATION.md @@ -0,0 +1,168 @@ +# Host IP Configuration for Supabase + +## Overview + +The Supabase `config.toml` file uses the `site_url` setting to configure authentication redirects. Instead of hardcoding the host IP address, we now use environment variables that are automatically detected from the host machine. + +## Problem + +Previously, the `site_url` was hardcoded (e.g., `http://exp-dash:3000`), which: +- Doesn't work when the host IP changes +- Requires manual updates for different environments +- Is not portable across different machines + +## Solution + +We now use the `HOST_SITE_URL` environment variable, which is automatically detected using the host machine's IP address. + +## Usage + +### Automatic Detection (Recommended) + +Use the provided scripts to automatically detect and set the host IP: + +```bash +# Option 1: Source the environment setup script +source ./scripts/set-host-env.sh + +# Then run Supabase CLI commands normally +supabase start +supabase db reset +``` + +Or use the wrapper script that does this automatically: + +```bash +# Use the wrapper script for Supabase CLI commands +./scripts/supabase-with-env.sh start +./scripts/supabase-with-env.sh db reset +``` + +### Manual Configuration + +If automatic detection doesn't work or you want to use a specific IP: + +```bash +# Set the environment variable manually +export HOST_SITE_URL="http://192.168.1.100:3000" + +# Or with a different port +export HOST_SITE_URL="http://192.168.1.100:8080" + +# Then run Supabase commands +supabase start +``` + +### For Docker Compose + +If you're using Docker Compose and need to set this for Supabase CLI commands run on the host: + +```bash +# Before running docker compose or Supabase CLI +export HOST_SITE_URL="http://$(./scripts/get-host-ip.sh):3000" +docker compose up -d +``` + +Or add it to your shell profile (`~/.bashrc` or `~/.zshrc`): + +```bash +# Add to ~/.bashrc or ~/.zshrc +export HOST_SITE_URL="http://$(/path/to/USDA-VISION/scripts/get-host-ip.sh):3000" +``` + +## How It Works + +1. **IP Detection Script** (`scripts/get-host-ip.sh`): + - Tries multiple methods to detect the host IP + - Works both inside Docker containers and on the host machine + - Falls back to `127.0.0.1` if detection fails + +2. **Environment Setup Script** (`scripts/set-host-env.sh`): + - Calls the IP detection script + - Sets `HOST_SITE_URL` environment variable + - Can be sourced to set the variable in your current shell + +3. **Supabase Wrapper Script** (`scripts/supabase-with-env.sh`): + - Automatically sets `HOST_SITE_URL` before running Supabase CLI + - Transparent wrapper - just use it instead of `supabase` command + +4. **Config File** (`supabase/config.toml`): + - Uses `env(HOST_SITE_URL)` to read the environment variable + - Supabase CLI automatically substitutes the value + +## Detection Methods + +The IP detection script tries these methods in order: + +1. **Default Gateway** (for containers): Gets the gateway IP which is typically the host +2. **host.docker.internal**: Uses Docker's special DNS name (if available) +3. **Network Interface**: Checks common network interfaces (eth0, enp0s8, etc.) +4. **Hostname Resolution**: Uses `hostname -I` to get the host IP +5. **Fallback**: Returns `127.0.0.1` if all methods fail + +## Troubleshooting + +### IP Detection Fails + +If the script can't detect the IP: + +1. **Check network interfaces**: + ```bash + ip addr show + # or + ifconfig + ``` + +2. **Manually set the IP**: + ```bash + export HOST_SITE_URL="http://YOUR_ACTUAL_IP:3000" + ``` + +3. **Verify the IP is accessible**: + ```bash + ping $(./scripts/get-host-ip.sh) + ``` + +### Supabase CLI Doesn't Use the Variable + +Make sure you: +1. Set `HOST_SITE_URL` before running Supabase CLI +2. Run Supabase CLI from the project root (where `supabase/` directory is) +3. Check that `config.toml` uses `env(HOST_SITE_URL)` syntax + +### Port Mismatch + +If your React app runs on a different port, adjust the port in the URL: + +```bash +export HOST_SITE_URL="http://$(./scripts/get-host-ip.sh):8080" +``` + +## Example Workflow + +```bash +# 1. Navigate to project root +cd /path/to/USDA-VISION + +# 2. Set the environment variable (automatic detection) +source ./scripts/set-host-env.sh + +# 3. Verify it's set +echo $HOST_SITE_URL +# Output: http://192.168.1.100:3000 + +# 4. Start Supabase +supabase start + +# Or use the wrapper script (does steps 2-4 automatically) +./scripts/supabase-with-env.sh start +``` + +## Integration with Docker Compose + +If you're running Supabase via Docker Compose (not CLI), the containers use their own networking. The `HOST_SITE_URL` is primarily for Supabase CLI commands that run on the host machine. + +For container-to-container communication, use Docker service names (e.g., `http://web:8080`). + +For browser-based redirects from Supabase Auth back to your React app, use the actual host IP that the browser can access. + diff --git a/management-dashboard-web-app/supabase/config.toml b/management-dashboard-web-app/supabase/config.toml index 70cea46..b4e8807 100755 --- a/management-dashboard-web-app/supabase/config.toml +++ b/management-dashboard-web-app/supabase/config.toml @@ -82,7 +82,9 @@ enabled = true # Port to use for Supabase Studio. port = 54323 # External URL of the API server that frontend connects to. -api_url = "http://exp-dash" +# Uses SUPABASE_API_URL environment variable (set by docker-compose.sh) +# Format: http://:54321 +api_url = "env(SUPABASE_API_URL)" # OpenAI API Key to use for Supabase AI in the Supabase Studio. openai_api_key = "env(OPENAI_API_KEY)" @@ -118,9 +120,13 @@ file_size_limit = "50MiB" enabled = true # The base URL of your website. Used as an allow-list for redirects and for constructing URLs used # in emails. -site_url = "http://exp-dash:3000" +# Uses HOST_SITE_URL environment variable, which should be set to the full URL (e.g., http://:3000) +# Set this via: export HOST_SITE_URL="http://$(./scripts/get-host-ip.sh):3000" +# Or manually: export HOST_SITE_URL="http://192.168.1.100:3000" +site_url = "env(HOST_SITE_URL)" # A list of *exact* URLs that auth providers are permitted to redirect to post authentication. -additional_redirect_urls = ["https://exp-dash:3000"] +# Uses HOST_SITE_URL environment variable (same as site_url) +additional_redirect_urls = ["env(HOST_SITE_URL)"] # How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). jwt_expiry = 3600 # If disabled, the refresh token will never expire. diff --git a/scripts/get-host-ip.sh b/scripts/get-host-ip.sh new file mode 100755 index 0000000..f4e8b26 --- /dev/null +++ b/scripts/get-host-ip.sh @@ -0,0 +1,86 @@ +#!/bin/bash +# Script to detect the host machine's IP address +# This is useful for Docker containers that need to connect back to services on the host + +# Method 1: Try to get IP from default gateway (works in most Docker setups) +# The default gateway in a Docker container is typically the host machine +get_ip_from_gateway() { + # Try to get the default gateway IP + if command -v ip >/dev/null 2>&1; then + ip route show default | awk '/default/ {print $3}' 2>/dev/null | head -1 + elif command -v route >/dev/null 2>&1; then + route -n | awk '/^0.0.0.0/ {print $2}' 2>/dev/null | head -1 + fi +} + +# Method 2: Try host.docker.internal (works on Docker Desktop and with extra_hosts) +get_ip_from_host_docker_internal() { + if command -v getent >/dev/null 2>&1; then + getent hosts host.docker.internal 2>/dev/null | awk '{print $1}' | head -1 + elif command -v nslookup >/dev/null 2>&1; then + nslookup host.docker.internal 2>/dev/null | grep -A1 "Name:" | grep "Address:" | awk '{print $2}' | head -1 + fi +} + +# Method 3: Get IP from network interface (for host machine) +get_ip_from_interface() { + # Try common network interfaces + for interface in eth0 enp0s8 enp0s3 ens33; do + if command -v ip >/dev/null 2>&1; then + ip addr show "$interface" 2>/dev/null | grep -oP 'inet \K[\d.]+' | head -1 + elif command -v ifconfig >/dev/null 2>&1; then + ifconfig "$interface" 2>/dev/null | grep -oP 'inet \K[\d.]+' | head -1 + fi + done +} + +# Method 4: Get IP from hostname (if hostname resolves to an IP) +get_ip_from_hostname() { + # Try to resolve the hostname + if command -v hostname >/dev/null 2>&1; then + hostname -I 2>/dev/null | awk '{print $1}' + fi +} + +# Main logic: Try methods in order of reliability +detect_host_ip() { + local ip="" + + # If running inside Docker, try gateway method first + if [ -f /.dockerenv ] || [ -n "${DOCKER_CONTAINER:-}" ]; then + ip=$(get_ip_from_gateway) + if [ -n "$ip" ] && [ "$ip" != "127.0.0.1" ]; then + echo "$ip" + return 0 + fi + + # Try host.docker.internal + ip=$(get_ip_from_host_docker_internal) + if [ -n "$ip" ] && [ "$ip" != "127.0.0.1" ]; then + echo "$ip" + return 0 + fi + fi + + # Try interface method (works on host machine) + ip=$(get_ip_from_interface) + if [ -n "$ip" ] && [ "$ip" != "127.0.0.1" ]; then + echo "$ip" + return 0 + fi + + # Try hostname method + ip=$(get_ip_from_hostname) + if [ -n "$ip" ] && [ "$ip" != "127.0.0.1" ]; then + echo "$ip" + return 0 + fi + + # Fallback: return localhost (not ideal but better than nothing) + echo "127.0.0.1" + return 1 +} + +# Execute and output the IP +detect_host_ip + diff --git a/scripts/set-host-env.sh b/scripts/set-host-env.sh new file mode 100755 index 0000000..9df9f60 --- /dev/null +++ b/scripts/set-host-env.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Script to set HOST_SITE_URL environment variable based on detected host IP +# This should be sourced before running Supabase CLI commands or docker-compose +# Usage: source ./scripts/set-host-env.sh + +# Get the script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Detect host IP +HOST_IP=$("$SCRIPT_DIR/get-host-ip.sh") +if [ $? -ne 0 ] || [ -z "$HOST_IP" ]; then + echo "Warning: Could not detect host IP, using localhost" >&2 + HOST_IP="127.0.0.1" +fi + +# Set the environment variable +# Default port is 3000, but can be overridden +PORT="${HOST_PORT:-3000}" +export HOST_SITE_URL="http://${HOST_IP}:${PORT}" + +# Only print messages if not being sourced (when run directly) +if [ "${BASH_SOURCE[0]}" = "${0}" ]; then + echo "Set HOST_SITE_URL=$HOST_SITE_URL" >&2 + echo "To use this in your current shell, run:" >&2 + echo " source $SCRIPT_DIR/set-host-env.sh" >&2 + echo "Or export it manually:" >&2 + echo " export HOST_SITE_URL=\"$HOST_SITE_URL\"" >&2 +else + # Being sourced - just set the variable silently (or with a brief message) + echo "HOST_SITE_URL set to: $HOST_SITE_URL" >&2 +fi + diff --git a/scripts/supabase-with-env.sh b/scripts/supabase-with-env.sh new file mode 100755 index 0000000..d29f423 --- /dev/null +++ b/scripts/supabase-with-env.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Wrapper script for Supabase CLI that automatically sets HOST_SITE_URL +# Usage: ./scripts/supabase-with-env.sh [args...] + +# Get the script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Source the environment setup script +source "$SCRIPT_DIR/set-host-env.sh" + +# Change to project root (Supabase CLI looks for supabase/ directory) +cd "$PROJECT_ROOT" || exit 1 + +# Check if supabase command exists +if ! command -v supabase >/dev/null 2>&1; then + echo "Error: Supabase CLI is not installed or not in PATH" >&2 + echo "Install it from: https://supabase.com/docs/guides/cli" >&2 + exit 1 +fi + +# Run the supabase command with all arguments +exec supabase "$@" + diff --git a/supabase/config.toml b/supabase/config.toml index 70cea46..b4e8807 100755 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -82,7 +82,9 @@ enabled = true # Port to use for Supabase Studio. port = 54323 # External URL of the API server that frontend connects to. -api_url = "http://exp-dash" +# Uses SUPABASE_API_URL environment variable (set by docker-compose.sh) +# Format: http://:54321 +api_url = "env(SUPABASE_API_URL)" # OpenAI API Key to use for Supabase AI in the Supabase Studio. openai_api_key = "env(OPENAI_API_KEY)" @@ -118,9 +120,13 @@ file_size_limit = "50MiB" enabled = true # The base URL of your website. Used as an allow-list for redirects and for constructing URLs used # in emails. -site_url = "http://exp-dash:3000" +# Uses HOST_SITE_URL environment variable, which should be set to the full URL (e.g., http://:3000) +# Set this via: export HOST_SITE_URL="http://$(./scripts/get-host-ip.sh):3000" +# Or manually: export HOST_SITE_URL="http://192.168.1.100:3000" +site_url = "env(HOST_SITE_URL)" # A list of *exact* URLs that auth providers are permitted to redirect to post authentication. -additional_redirect_urls = ["https://exp-dash:3000"] +# Uses HOST_SITE_URL environment variable (same as site_url) +additional_redirect_urls = ["env(HOST_SITE_URL)"] # How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). jwt_expiry = 3600 # If disabled, the refresh token will never expire. From 11e65e46949bfa37897fc4fea87342838f65d26b Mon Sep 17 00:00:00 2001 From: salirezav Date: Thu, 18 Dec 2025 20:17:14 -0500 Subject: [PATCH 04/13] feat: Add local Supabase configuration for Vite development - Introduced a new backup environment file containing local Supabase and Vision API configurations. - Configured environment variables for Supabase URL, anonymous key, and various Vision API settings. - Enabled modules for video, vision system, and scheduling in the development environment. --- .../.env.backup.20251218_195621 | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100755 management-dashboard-web-app/.env.backup.20251218_195621 diff --git a/management-dashboard-web-app/.env.backup.20251218_195621 b/management-dashboard-web-app/.env.backup.20251218_195621 new file mode 100755 index 0000000..3efeb2e --- /dev/null +++ b/management-dashboard-web-app/.env.backup.20251218_195621 @@ -0,0 +1,17 @@ +# Local Supabase config for Vite dev server +VITE_SUPABASE_URL=http://exp-dash:54321 +VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0 + + +# Vision API Configuration +VITE_VISION_API_URL=http://exp-dash:8000 +VITE_ENABLE_VIDEO_MODULE=true +VITE_VIDEO_REMOTE_URL=http://exp-dash:3001/assets/remoteEntry.js?v=1761849082 +VITE_MEDIA_API_URL=http://exp-dash:8090 + +# Vision System Module +VITE_ENABLE_VISION_SYSTEM_MODULE=true +VITE_VISION_SYSTEM_REMOTE_URL=http://exp-dash:3002/assets/remoteEntry.js + +# Enable scheduling module +VITE_ENABLE_SCHEDULING_MODULE=true From 35ab2e79ce7d44bdbc13dc73d3a20edab0218aee Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Fri, 19 Dec 2025 16:05:48 -0500 Subject: [PATCH 05/13] add external url to allowed hosts --- management-dashboard-web-app/vite.config.ts | 2 +- scheduling-remote/vite.config.ts | 2 +- video-remote/vite.config.ts | 2 +- vision-system-remote/vite.config.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/management-dashboard-web-app/vite.config.ts b/management-dashboard-web-app/vite.config.ts index 7d7b35f..da8ba2c 100755 --- a/management-dashboard-web-app/vite.config.ts +++ b/management-dashboard-web-app/vite.config.ts @@ -31,7 +31,7 @@ export default defineConfig({ ], server: { // Allow connections from the VM hostname and any other host/IP - allowedHosts: ['exp-dash', 'localhost'], + allowedHosts: ['exp-dash', 'localhost', 'usda-dash.ugaif.com'], // Proxy API calls from the browser to the API container via the compose service name proxy: { '/api': { diff --git a/scheduling-remote/vite.config.ts b/scheduling-remote/vite.config.ts index f26fa29..a404b21 100644 --- a/scheduling-remote/vite.config.ts +++ b/scheduling-remote/vite.config.ts @@ -22,7 +22,7 @@ export default defineConfig({ server: { port: 3003, host: '0.0.0.0', - allowedHosts: ['exp-dash', 'localhost'], + allowedHosts: ['exp-dash', 'localhost', 'usda-dash.ugaif.com'], cors: true }, build: { diff --git a/video-remote/vite.config.ts b/video-remote/vite.config.ts index 1aab8ac..781ffdc 100644 --- a/video-remote/vite.config.ts +++ b/video-remote/vite.config.ts @@ -23,7 +23,7 @@ export default defineConfig({ server: { port: 3001, host: '0.0.0.0', - allowedHosts: ['exp-dash', 'localhost'], + allowedHosts: ['exp-dash', 'localhost', 'usda-dash.ugaif.com'], cors: true }, build: { diff --git a/vision-system-remote/vite.config.ts b/vision-system-remote/vite.config.ts index ddbc9d5..4c4a951 100644 --- a/vision-system-remote/vite.config.ts +++ b/vision-system-remote/vite.config.ts @@ -22,7 +22,7 @@ export default defineConfig({ server: { port: 3002, host: '0.0.0.0', - allowedHosts: ['exp-dash', 'localhost'], + allowedHosts: ['exp-dash', 'localhost', 'usda-dash.ugaif.com'], cors: true }, build: { From bac8bdec90109df0b359b55b681421f69f6b85c5 Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Fri, 19 Dec 2025 17:29:28 -0500 Subject: [PATCH 06/13] updated dark color scheme to show text on login --- management-dashboard-web-app/src/index.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management-dashboard-web-app/src/index.css b/management-dashboard-web-app/src/index.css index fee4611..3481cfe 100755 --- a/management-dashboard-web-app/src/index.css +++ b/management-dashboard-web-app/src/index.css @@ -179,7 +179,7 @@ ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentColor); + border-color: var(--color-gray-200); } button:not(:disabled), From 460403bde2c4f13d5ff94a39cd7aafcad3a84b60 Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Fri, 19 Dec 2025 17:40:41 -0500 Subject: [PATCH 07/13] respect font import better with safari --- management-dashboard-web-app/src/index.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management-dashboard-web-app/src/index.css b/management-dashboard-web-app/src/index.css index 3481cfe..ac7f845 100755 --- a/management-dashboard-web-app/src/index.css +++ b/management-dashboard-web-app/src/index.css @@ -1,4 +1,4 @@ -@import url("https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap") layer(base); +@import url("https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap"); @import "tailwindcss"; From 5ffb4a3cfcd2532ad8297565cb64263313ac01c5 Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Fri, 19 Dec 2025 17:52:45 -0500 Subject: [PATCH 08/13] apply text color to body --- management-dashboard-web-app/src/index.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management-dashboard-web-app/src/index.css b/management-dashboard-web-app/src/index.css index ac7f845..8169f40 100755 --- a/management-dashboard-web-app/src/index.css +++ b/management-dashboard-web-app/src/index.css @@ -188,7 +188,7 @@ } body { - @apply relative font-normal font-outfit z-1 bg-gray-50 dark:bg-gray-900; + @apply relative font-normal font-outfit z-1 bg-gray-50 dark:bg-gray-900 dark:text-gray-300; } } From d6774a751b87372df0b37953f6421c6eaff9634a Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Fri, 19 Dec 2025 18:07:57 -0500 Subject: [PATCH 09/13] actually fix text color --- management-dashboard-web-app/src/components/Login.tsx | 11 ++++++++--- management-dashboard-web-app/src/index.css | 6 +++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/management-dashboard-web-app/src/components/Login.tsx b/management-dashboard-web-app/src/components/Login.tsx index f564f30..062a99c 100755 --- a/management-dashboard-web-app/src/components/Login.tsx +++ b/management-dashboard-web-app/src/components/Login.tsx @@ -39,10 +39,10 @@ export function Login({ onLoginSuccess }: LoginProps) {
-

+

Sign in to your account

-

+

RBAC Authentication System

@@ -58,7 +58,12 @@ export function Login({ onLoginSuccess }: LoginProps) { type="email" autoComplete="email" required - className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm" + className="appearance-none rounded-none relative block w-full px-3 py-2 + border border-gray-300 dark:border-gray-700 + bg-white dark:bg-gray-800 + placeholder-gray-500 dark:placeholder-gray-400 + text-gray-900 dark:text-gray-100 + rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm" placeholder="Email address" value={email} onChange={(e) => setEmail(e.target.value)} diff --git a/management-dashboard-web-app/src/index.css b/management-dashboard-web-app/src/index.css index 8169f40..fee4611 100755 --- a/management-dashboard-web-app/src/index.css +++ b/management-dashboard-web-app/src/index.css @@ -1,4 +1,4 @@ -@import url("https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap") layer(base); @import "tailwindcss"; @@ -179,7 +179,7 @@ ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200); + border-color: var(--color-gray-200, currentColor); } button:not(:disabled), @@ -188,7 +188,7 @@ } body { - @apply relative font-normal font-outfit z-1 bg-gray-50 dark:bg-gray-900 dark:text-gray-300; + @apply relative font-normal font-outfit z-1 bg-gray-50 dark:bg-gray-900; } } From c6c3677c54b82b1f4342094106a0f504b0df8729 Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Fri, 19 Dec 2025 18:11:33 -0500 Subject: [PATCH 10/13] fix for password too --- management-dashboard-web-app/src/components/Login.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/management-dashboard-web-app/src/components/Login.tsx b/management-dashboard-web-app/src/components/Login.tsx index 062a99c..5a0431c 100755 --- a/management-dashboard-web-app/src/components/Login.tsx +++ b/management-dashboard-web-app/src/components/Login.tsx @@ -79,7 +79,12 @@ export function Login({ onLoginSuccess }: LoginProps) { type="password" autoComplete="current-password" required - className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm" + className="appearance-none rounded-none relative block w-full px-3 py-2 + border border-gray-300 dark:border-gray-700 + bg-white dark:bg-gray-800 + placeholder-gray-500 dark:placeholder-gray-400 + text-gray-900 dark:text-gray-100 + rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} From b1e93f7d08cf60098e23ba7a9a55d8dacd8e006e Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Fri, 19 Dec 2025 18:14:59 -0500 Subject: [PATCH 11/13] round bottom border on password --- management-dashboard-web-app/src/components/Login.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management-dashboard-web-app/src/components/Login.tsx b/management-dashboard-web-app/src/components/Login.tsx index 5a0431c..745880f 100755 --- a/management-dashboard-web-app/src/components/Login.tsx +++ b/management-dashboard-web-app/src/components/Login.tsx @@ -63,7 +63,7 @@ export function Login({ onLoginSuccess }: LoginProps) { bg-white dark:bg-gray-800 placeholder-gray-500 dark:placeholder-gray-400 text-gray-900 dark:text-gray-100 - rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm" + rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm" placeholder="Email address" value={email} onChange={(e) => setEmail(e.target.value)} From 199b7dc123ebb4468976d78c1bd14579f7200be6 Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Fri, 19 Dec 2025 18:20:11 -0500 Subject: [PATCH 12/13] round bottom border on password --- management-dashboard-web-app/src/components/Login.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/management-dashboard-web-app/src/components/Login.tsx b/management-dashboard-web-app/src/components/Login.tsx index 745880f..8e4929f 100755 --- a/management-dashboard-web-app/src/components/Login.tsx +++ b/management-dashboard-web-app/src/components/Login.tsx @@ -63,7 +63,7 @@ export function Login({ onLoginSuccess }: LoginProps) { bg-white dark:bg-gray-800 placeholder-gray-500 dark:placeholder-gray-400 text-gray-900 dark:text-gray-100 - rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm" + rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm" placeholder="Email address" value={email} onChange={(e) => setEmail(e.target.value)} @@ -84,7 +84,7 @@ export function Login({ onLoginSuccess }: LoginProps) { bg-white dark:bg-gray-800 placeholder-gray-500 dark:placeholder-gray-400 text-gray-900 dark:text-gray-100 - rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm" + rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} From 3c24900e15dbd5d3049c249a2b8890d0f48ce56f Mon Sep 17 00:00:00 2001 From: UGA Innovation Factory Date: Fri, 19 Dec 2025 18:35:42 -0500 Subject: [PATCH 13/13] update --- management-dashboard-web-app/src/index.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management-dashboard-web-app/src/index.css b/management-dashboard-web-app/src/index.css index fee4611..44d0a2a 100755 --- a/management-dashboard-web-app/src/index.css +++ b/management-dashboard-web-app/src/index.css @@ -11,7 +11,7 @@ --breakpoint-*: initial; --breakpoint-2xsm: 375px; --breakpoint-xsm: 425px; - --breakpoint-3xl: 2000px; + --breakpoint-3xl: 2000px; --breakpoint-sm: 640px; --breakpoint-md: 768px; --breakpoint-lg: 1024px;