Update .gitignore and refactor docker-compose.sh for improved host IP detection
- Added .host-ip to .gitignore to prevent tracking of host IP configurations. - Enhanced docker-compose.sh to prioritize HOST_IP environment variable, fallback to .host-ip file, and finally auto-detect the host IP, improving robustness in various environments. - Updated documentation to reflect changes in Supabase migration paths and removed references to deprecated management-dashboard-web-app directory.
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -6,6 +6,7 @@ __pycache__/
|
|||||||
.uv/
|
.uv/
|
||||||
.env
|
.env
|
||||||
.env.*.local
|
.env.*.local
|
||||||
|
.host-ip
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
|
|
||||||
@@ -35,5 +36,7 @@ management-dashboard-web-app/users.txt
|
|||||||
|
|
||||||
# Jupyter Notebooks
|
# Jupyter Notebooks
|
||||||
*.ipynb
|
*.ipynb
|
||||||
management-dashboard-web-app/supabase/.temp/cli-latest
|
|
||||||
supabase/.temp/cli-latest
|
supabase/.temp/cli-latest
|
||||||
|
|
||||||
|
# Archive env backups (may contain secrets)
|
||||||
|
archive/management-dashboard-web-app/env-backups/
|
||||||
|
|||||||
7
archive/management-dashboard-web-app/README.md
Normal file
7
archive/management-dashboard-web-app/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Archive: management-dashboard-web-app legacy/backup files
|
||||||
|
|
||||||
|
Moved from `management-dashboard-web-app/` so the app directory only contains active code and config.
|
||||||
|
|
||||||
|
- **env-backups/** – Old `.env.backup` and timestamped backup (Supabase URL/key). Keep out of version control if they contain secrets.
|
||||||
|
- **experiment-data/** – CSV run sheets: `phase_2_JC_experimental_run_sheet.csv`, `post_workshop_meyer_experiments.csv`. Source/reference data for experiments.
|
||||||
|
- **test-api-fix.js** – One-off test script for camera config API; not part of the app build.
|
||||||
@@ -12,9 +12,16 @@ PROJECT_ROOT="$SCRIPT_DIR"
|
|||||||
# Change to project root
|
# Change to project root
|
||||||
cd "$PROJECT_ROOT" || exit 1
|
cd "$PROJECT_ROOT" || exit 1
|
||||||
|
|
||||||
# Detect host IP
|
# Host IP: use HOST_IP env, then .host-ip file, then auto-detect
|
||||||
HOST_IP=$("$SCRIPT_DIR/scripts/get-host-ip.sh")
|
if [ -z "$HOST_IP" ] || [ "$HOST_IP" = "127.0.0.1" ]; then
|
||||||
if [ $? -ne 0 ] || [ -z "$HOST_IP" ] || [ "$HOST_IP" = "127.0.0.1" ]; then
|
if [ -f "$PROJECT_ROOT/.host-ip" ]; then
|
||||||
|
HOST_IP=$(cat "$PROJECT_ROOT/.host-ip" | tr -d '\n\r' | awk '{print $1}')
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ -z "$HOST_IP" ] || [ "$HOST_IP" = "127.0.0.1" ]; then
|
||||||
|
HOST_IP=$("$SCRIPT_DIR/scripts/get-host-ip.sh" 2>/dev/null) || true
|
||||||
|
fi
|
||||||
|
if [ -z "$HOST_IP" ] || [ "$HOST_IP" = "127.0.0.1" ]; then
|
||||||
echo "Warning: Could not detect host IP, using localhost" >&2
|
echo "Warning: Could not detect host IP, using localhost" >&2
|
||||||
HOST_IP="localhost"
|
HOST_IP="localhost"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ I've created a migration file that implements a **unified `experiment_phase_exec
|
|||||||
## Files Created
|
## Files Created
|
||||||
|
|
||||||
1. **`docs/database_design_analysis.md`** - Detailed analysis with comparison matrix
|
1. **`docs/database_design_analysis.md`** - Detailed analysis with comparison matrix
|
||||||
2. **`management-dashboard-web-app/supabase/migrations/00012_unified_phase_executions.sql`** - Complete migration implementation
|
2. **`supabase/migrations/00012_unified_phase_executions.sql`** - Complete migration implementation
|
||||||
|
|
||||||
## Migration Path
|
## Migration Path
|
||||||
|
|
||||||
|
|||||||
@@ -50,23 +50,18 @@ If you have scripts or documentation that reference the old path, update them:
|
|||||||
- ❌ `management-dashboard-web-app/supabase/config.toml`
|
- ❌ `management-dashboard-web-app/supabase/config.toml`
|
||||||
- ✅ `supabase/config.toml`
|
- ✅ `supabase/config.toml`
|
||||||
|
|
||||||
## Backward Compatibility
|
## Current State
|
||||||
|
|
||||||
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:
|
The old directory (`management-dashboard-web-app/supabase/`) has been removed. All Supabase and DB configuration, migrations, and seeds now live only under the project root `supabase/` directory. Docker Compose and the Supabase CLI use root `supabase/` exclusively.
|
||||||
|
|
||||||
```bash
|
|
||||||
# After verifying everything works with the new location
|
|
||||||
rm -rf management-dashboard-web-app/supabase
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
To verify the migration worked:
|
To verify the migration:
|
||||||
|
|
||||||
1. **Check docker-compose paths**:
|
1. **Check docker-compose paths** (only root supabase should be referenced):
|
||||||
```bash
|
```bash
|
||||||
grep -r "supabase" docker-compose.yml
|
grep "supabase" docker-compose.yml
|
||||||
# Should show: ./supabase/ (not ./management-dashboard-web-app/supabase/)
|
# Should show only ./supabase/ (no management-dashboard-web-app/supabase/)
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Test Supabase CLI**:
|
2. **Test Supabase CLI**:
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
# Local Supabase config for Vite dev server
|
|
||||||
VITE_SUPABASE_URL=http://127.0.0.1:54321
|
|
||||||
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
|
|
||||||
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
v2.65.5
|
|
||||||
@@ -1,339 +0,0 @@
|
|||||||
# 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.
|
|
||||||
# Uses SUPABASE_API_URL environment variable (set by docker-compose.sh)
|
|
||||||
# Format: http://<host-ip>: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)"
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
# Uses HOST_SITE_URL environment variable, which should be set to the full URL (e.g., http://<host-ip>: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.
|
|
||||||
# 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.
|
|
||||||
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://<database>/<schema>/<hook_name>"
|
|
||||||
|
|
||||||
# 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. <bucket_name>.s3-<region>.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)"
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
-- 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;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,237 +0,0 @@
|
|||||||
-- 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.';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
-- 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');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
-- 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');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
-- 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');
|
|
||||||
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
-- 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');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
-- 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');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,278 +0,0 @@
|
|||||||
-- 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,
|
|
||||||
-- The space (in inches) between the sheller's rings
|
|
||||||
ring_gap_inches NUMERIC(6,2) CHECK (ring_gap_inches > 0),
|
|
||||||
-- The revolutions per minute for the sheller drum
|
|
||||||
drum_rpm INTEGER CHECK (drum_rpm > 0),
|
|
||||||
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');
|
|
||||||
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
-- 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()
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
-- 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;
|
|
||||||
|
|
||||||
@@ -1,374 +0,0 @@
|
|||||||
-- 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);
|
|
||||||
|
|
||||||
-- =============================================
|
|
||||||
-- 2.5. CREATE CONDUCTOR ASSIGNMENTS TABLE
|
|
||||||
-- =============================================
|
|
||||||
|
|
||||||
-- Table to store conductor assignments to phase executions
|
|
||||||
-- This allows multiple conductors to be assigned to each phase execution
|
|
||||||
CREATE TABLE IF NOT EXISTS public.experiment_phase_assignments (
|
|
||||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
||||||
phase_execution_id UUID NOT NULL REFERENCES public.experiment_phase_executions(id) ON DELETE CASCADE,
|
|
||||||
conductor_id UUID NOT NULL REFERENCES public.user_profiles(id) ON DELETE CASCADE,
|
|
||||||
|
|
||||||
-- Scheduled times for this assignment (should match phase_execution times, but stored for clarity)
|
|
||||||
scheduled_start_time TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
||||||
scheduled_end_time TIMESTAMP WITH TIME ZONE,
|
|
||||||
|
|
||||||
-- Status tracking
|
|
||||||
status TEXT NOT NULL DEFAULT 'scheduled'
|
|
||||||
CHECK (status IN ('scheduled', 'in_progress', 'completed', 'cancelled')),
|
|
||||||
|
|
||||||
-- Optional notes about the assignment
|
|
||||||
notes 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),
|
|
||||||
|
|
||||||
-- Ensure scheduled_end_time is after scheduled_start_time
|
|
||||||
CONSTRAINT valid_scheduled_time_range CHECK (scheduled_end_time IS NULL OR scheduled_end_time > scheduled_start_time),
|
|
||||||
|
|
||||||
-- Ensure unique assignment per conductor per phase execution
|
|
||||||
CONSTRAINT unique_conductor_phase_execution UNIQUE (phase_execution_id, conductor_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Indexes for conductor assignments
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_phase_assignments_phase_execution_id
|
|
||||||
ON public.experiment_phase_assignments(phase_execution_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_phase_assignments_conductor_id
|
|
||||||
ON public.experiment_phase_assignments(conductor_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_phase_assignments_status
|
|
||||||
ON public.experiment_phase_assignments(status);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_phase_assignments_scheduled_start_time
|
|
||||||
ON public.experiment_phase_assignments(scheduled_start_time);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_phase_assignments_created_by
|
|
||||||
ON public.experiment_phase_assignments(created_by);
|
|
||||||
|
|
||||||
-- Trigger for updated_at on conductor assignments
|
|
||||||
CREATE TRIGGER set_updated_at_phase_assignments
|
|
||||||
BEFORE UPDATE ON public.experiment_phase_assignments
|
|
||||||
FOR EACH ROW
|
|
||||||
EXECUTE FUNCTION public.handle_updated_at();
|
|
||||||
|
|
||||||
-- Grant permissions
|
|
||||||
GRANT ALL ON public.experiment_phase_assignments TO authenticated;
|
|
||||||
|
|
||||||
-- Enable Row Level Security
|
|
||||||
ALTER TABLE public.experiment_phase_assignments ENABLE ROW LEVEL SECURITY;
|
|
||||||
|
|
||||||
-- RLS Policies for conductor assignments
|
|
||||||
CREATE POLICY "Phase assignments are viewable by authenticated users"
|
|
||||||
ON public.experiment_phase_assignments
|
|
||||||
FOR SELECT USING (auth.role() = 'authenticated');
|
|
||||||
|
|
||||||
CREATE POLICY "Phase assignments are insertable by authenticated users"
|
|
||||||
ON public.experiment_phase_assignments
|
|
||||||
FOR INSERT WITH CHECK (auth.role() = 'authenticated');
|
|
||||||
|
|
||||||
CREATE POLICY "Phase assignments are updatable by authenticated users"
|
|
||||||
ON public.experiment_phase_assignments
|
|
||||||
FOR UPDATE USING (auth.role() = 'authenticated');
|
|
||||||
|
|
||||||
CREATE POLICY "Phase assignments are deletable by authenticated users"
|
|
||||||
ON public.experiment_phase_assignments
|
|
||||||
FOR DELETE USING (auth.role() = 'authenticated');
|
|
||||||
|
|
||||||
-- =============================================
|
|
||||||
-- 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');
|
|
||||||
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
-- 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);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,277 +0,0 @@
|
|||||||
-- 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;
|
|
||||||
|
|
||||||
@@ -1,600 +0,0 @@
|
|||||||
-- 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'
|
|
||||||
;
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -42,10 +42,32 @@ get_ip_from_hostname() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Method 5: Prefer 100.x.x.x (e.g. Tailscale) when present on the host
|
||||||
|
get_ip_prefer_100() {
|
||||||
|
local ip=""
|
||||||
|
if command -v ip >/dev/null 2>&1; then
|
||||||
|
# Third column is ADDR/CIDR (e.g. 100.93.40.84/32); extract IPv4 and prefer 100.x
|
||||||
|
ip=$(ip -br addr show 2>/dev/null | awk '{print $3}' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -E '^100\.' | head -1)
|
||||||
|
fi
|
||||||
|
if [ -z "$ip" ] && command -v hostname >/dev/null 2>&1; then
|
||||||
|
ip=$(hostname -I 2>/dev/null | tr ' ' '\n' | grep -E '^100\.' | head -1)
|
||||||
|
fi
|
||||||
|
echo "$ip"
|
||||||
|
}
|
||||||
|
|
||||||
# Main logic: Try methods in order of reliability
|
# Main logic: Try methods in order of reliability
|
||||||
detect_host_ip() {
|
detect_host_ip() {
|
||||||
local ip=""
|
local ip=""
|
||||||
|
|
||||||
|
# When on the host (not in Docker), prefer 100.x.x.x (Tailscale) so the dashboard is reachable via that IP
|
||||||
|
if [ ! -f /.dockerenv ] && [ -z "${DOCKER_CONTAINER:-}" ]; then
|
||||||
|
ip=$(get_ip_prefer_100)
|
||||||
|
if [ -n "$ip" ] && [ "$ip" != "127.0.0.1" ]; then
|
||||||
|
echo "$ip"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# If running inside Docker, try gateway method first
|
# If running inside Docker, try gateway method first
|
||||||
if [ -f /.dockerenv ] || [ -n "${DOCKER_CONTAINER:-}" ]; then
|
if [ -f /.dockerenv ] || [ -n "${DOCKER_CONTAINER:-}" ]; then
|
||||||
ip=$(get_ip_from_gateway)
|
ip=$(get_ip_from_gateway)
|
||||||
@@ -53,7 +75,7 @@ detect_host_ip() {
|
|||||||
echo "$ip"
|
echo "$ip"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Try host.docker.internal
|
# Try host.docker.internal
|
||||||
ip=$(get_ip_from_host_docker_internal)
|
ip=$(get_ip_from_host_docker_internal)
|
||||||
if [ -n "$ip" ] && [ "$ip" != "127.0.0.1" ]; then
|
if [ -n "$ip" ] && [ "$ip" != "127.0.0.1" ]; then
|
||||||
@@ -61,21 +83,21 @@ detect_host_ip() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Try interface method (works on host machine)
|
# Try interface method (works on host machine)
|
||||||
ip=$(get_ip_from_interface)
|
ip=$(get_ip_from_interface)
|
||||||
if [ -n "$ip" ] && [ "$ip" != "127.0.0.1" ]; then
|
if [ -n "$ip" ] && [ "$ip" != "127.0.0.1" ]; then
|
||||||
echo "$ip"
|
echo "$ip"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Try hostname method
|
# Try hostname method
|
||||||
ip=$(get_ip_from_hostname)
|
ip=$(get_ip_from_hostname)
|
||||||
if [ -n "$ip" ] && [ "$ip" != "127.0.0.1" ]; then
|
if [ -n "$ip" ] && [ "$ip" != "127.0.0.1" ]; then
|
||||||
echo "$ip"
|
echo "$ip"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fallback: return localhost (not ideal but better than nothing)
|
# Fallback: return localhost (not ideal but better than nothing)
|
||||||
echo "127.0.0.1"
|
echo "127.0.0.1"
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
0
management-dashboard-web-app/supabase/.gitignore → supabase/.gitignore
vendored
Executable file → Normal file
0
management-dashboard-web-app/supabase/.gitignore → supabase/.gitignore
vendored
Executable file → Normal file
@@ -1 +1 @@
|
|||||||
v2.67.1
|
v2.75.0
|
||||||
Reference in New Issue
Block a user