all these changes

This commit is contained in:
Jake Kasper
2026-04-09 13:19:47 -05:00
parent e83a51a051
commit 65315f36d1
39102 changed files with 7932979 additions and 567 deletions

View File

@@ -0,0 +1,83 @@
-- Align development schema with the tables expected by the current API.
-- Earlier project changes were stored in ad hoc SQL files, so Flyway never
-- applied them to fresh dev databases.
-- Support multiple lawn sections per application plan.
CREATE TABLE IF NOT EXISTS application_plan_sections (
id SERIAL PRIMARY KEY,
plan_id INTEGER NOT NULL REFERENCES application_plans(id) ON DELETE CASCADE,
lawn_section_id INTEGER NOT NULL REFERENCES lawn_sections(id) ON DELETE CASCADE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(plan_id, lawn_section_id)
);
INSERT INTO application_plan_sections (plan_id, lawn_section_id)
SELECT ap.id, ap.lawn_section_id
FROM application_plans ap
WHERE ap.lawn_section_id IS NOT NULL
ON CONFLICT (plan_id, lawn_section_id) DO NOTHING;
CREATE INDEX IF NOT EXISTS idx_application_plan_sections_plan_id
ON application_plan_sections(plan_id);
CREATE INDEX IF NOT EXISTS idx_application_plan_sections_section_id
ON application_plan_sections(lawn_section_id);
-- Add missing spreader support fields on user equipment.
ALTER TABLE user_equipment
ADD COLUMN IF NOT EXISTS brand VARCHAR(100),
ADD COLUMN IF NOT EXISTS notes TEXT;
UPDATE user_equipment
SET brand = manufacturer
WHERE brand IS NULL AND manufacturer IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_user_equipment_brand_model
ON user_equipment(brand, model);
-- Add product spreader settings used by product/admin/application routes.
CREATE TABLE IF NOT EXISTS product_spreader_settings (
id SERIAL PRIMARY KEY,
product_id INTEGER REFERENCES products(id) ON DELETE CASCADE,
user_product_id INTEGER REFERENCES user_products(id) ON DELETE CASCADE,
equipment_id INTEGER REFERENCES user_equipment(id) ON DELETE CASCADE,
spreader_brand VARCHAR(100),
spreader_model VARCHAR(100),
setting_value VARCHAR(20) NOT NULL,
rate_description VARCHAR(200),
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CHECK (
(product_id IS NOT NULL AND user_product_id IS NULL) OR
(product_id IS NULL AND user_product_id IS NOT NULL)
)
);
CREATE INDEX IF NOT EXISTS idx_product_spreader_settings_product
ON product_spreader_settings(product_id);
CREATE INDEX IF NOT EXISTS idx_product_spreader_settings_user_product
ON product_spreader_settings(user_product_id);
CREATE INDEX IF NOT EXISTS idx_product_spreader_settings_brand
ON product_spreader_settings(spreader_brand);
CREATE INDEX IF NOT EXISTS idx_product_spreader_settings_equipment
ON product_spreader_settings(equipment_id);
CREATE OR REPLACE FUNCTION update_product_spreader_settings_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';
DROP TRIGGER IF EXISTS update_product_spreader_settings_updated_at_trigger
ON product_spreader_settings;
CREATE TRIGGER update_product_spreader_settings_updated_at_trigger
BEFORE UPDATE ON product_spreader_settings
FOR EACH ROW
EXECUTE PROCEDURE update_product_spreader_settings_updated_at();

View File

@@ -0,0 +1,17 @@
-- Align user_equipment with the nozzle-aware equipment routes.
ALTER TABLE user_equipment
ADD COLUMN IF NOT EXISTS orifice_size VARCHAR(20),
ADD COLUMN IF NOT EXISTS spray_angle INTEGER,
ADD COLUMN IF NOT EXISTS flow_rate_gpm DECIMAL(6, 3),
ADD COLUMN IF NOT EXISTS droplet_size VARCHAR(50),
ADD COLUMN IF NOT EXISTS spray_pattern VARCHAR(50),
ADD COLUMN IF NOT EXISTS pressure_range_psi VARCHAR(50),
ADD COLUMN IF NOT EXISTS thread_size VARCHAR(20),
ADD COLUMN IF NOT EXISTS material VARCHAR(50),
ADD COLUMN IF NOT EXISTS color_code VARCHAR(50),
ADD COLUMN IF NOT EXISTS quantity_owned INTEGER DEFAULT 1;
CREATE INDEX IF NOT EXISTS idx_user_equipment_nozzle_specs
ON user_equipment(orifice_size, droplet_size, spray_angle)
WHERE orifice_size IS NOT NULL;

View File

@@ -0,0 +1,7 @@
-- Align application_plans with the nozzle-aware application planning routes.
ALTER TABLE application_plans
ADD COLUMN IF NOT EXISTS nozzle_id INTEGER REFERENCES user_equipment(id);
CREATE INDEX IF NOT EXISTS idx_application_plans_nozzle
ON application_plans(nozzle_id);

View File

@@ -0,0 +1,21 @@
-- Support application logs that span multiple lawn sections.
CREATE TABLE IF NOT EXISTS application_log_sections (
id SERIAL PRIMARY KEY,
log_id INTEGER NOT NULL REFERENCES application_logs(id) ON DELETE CASCADE,
lawn_section_id INTEGER NOT NULL REFERENCES lawn_sections(id) ON DELETE CASCADE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(log_id, lawn_section_id)
);
INSERT INTO application_log_sections (log_id, lawn_section_id)
SELECT al.id, al.lawn_section_id
FROM application_logs al
WHERE al.lawn_section_id IS NOT NULL
ON CONFLICT (log_id, lawn_section_id) DO NOTHING;
CREATE INDEX IF NOT EXISTS idx_application_log_sections_log_id
ON application_log_sections(log_id);
CREATE INDEX IF NOT EXISTS idx_application_log_sections_section_id
ON application_log_sections(lawn_section_id);

View File

@@ -0,0 +1,8 @@
-- Allow application plans to be archived.
ALTER TABLE application_plans
DROP CONSTRAINT IF EXISTS application_plans_status_check;
ALTER TABLE application_plans
ADD CONSTRAINT application_plans_status_check
CHECK (status IN ('planned', 'in_progress', 'completed', 'cancelled', 'archived'));

View File

@@ -0,0 +1,49 @@
-- Persist actual watering execution runs separately from watering plan configuration.
CREATE TABLE IF NOT EXISTS watering_runs (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
plan_id INTEGER REFERENCES watering_plans(id) ON DELETE SET NULL,
property_id INTEGER NOT NULL REFERENCES properties(id) ON DELETE CASCADE,
run_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
notes TEXT,
total_duration_minutes DECIMAL(10,2) DEFAULT 0,
total_coverage_sqft DECIMAL(12,2) DEFAULT 0,
total_estimated_gallons DECIMAL(12,2) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS watering_run_points (
id SERIAL PRIMARY KEY,
run_id INTEGER NOT NULL REFERENCES watering_runs(id) ON DELETE CASCADE,
plan_point_id INTEGER REFERENCES watering_plan_points(id) ON DELETE SET NULL,
sequence INTEGER NOT NULL,
lat DECIMAL(10,8),
lng DECIMAL(11,8),
sprinkler_mount VARCHAR(20),
sprinkler_head_type VARCHAR(30),
sprinkler_gpm DECIMAL(8,2),
sprinkler_throw_feet DECIMAL(8,2),
sprinkler_degrees INTEGER,
sprinkler_length_feet DECIMAL(8,2),
sprinkler_width_feet DECIMAL(8,2),
coverage_sqft DECIMAL(10,2),
sprinkler_heading_degrees INTEGER,
equipment_id INTEGER REFERENCES user_equipment(id) ON DELETE SET NULL,
equipment_name VARCHAR(255),
actual_duration_minutes DECIMAL(10,2) DEFAULT 0,
actual_gpm DECIMAL(8,2),
estimated_gallons DECIMAL(12,2) DEFAULT 0,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_watering_runs_user_date
ON watering_runs(user_id, run_date DESC);
CREATE INDEX IF NOT EXISTS idx_watering_runs_property_date
ON watering_runs(property_id, run_date DESC);
CREATE INDEX IF NOT EXISTS idx_watering_run_points_run
ON watering_run_points(run_id);

View File

@@ -0,0 +1,5 @@
-- Add profile avatar and notification preferences to users.
ALTER TABLE users
ADD COLUMN IF NOT EXISTS avatar_url TEXT,
ADD COLUMN IF NOT EXISTS notification_preferences JSONB DEFAULT '{"emailReports": true, "weatherAlerts": true, "applicationReminders": true, "wateringReminders": false}'::jsonb;

View File

@@ -0,0 +1,42 @@
-- Email delivery support for invites, password resets, and notification deduplication.
CREATE TABLE IF NOT EXISTS password_reset_tokens (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token_hash VARCHAR(64) NOT NULL UNIQUE,
expires_at TIMESTAMP NOT NULL,
used_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_user
ON password_reset_tokens(user_id, expires_at DESC);
CREATE TABLE IF NOT EXISTS user_invitations (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL,
first_name VARCHAR(100),
last_name VARCHAR(100),
role VARCHAR(50) NOT NULL DEFAULT 'user',
invited_by_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
token_hash VARCHAR(64) NOT NULL UNIQUE,
expires_at TIMESTAMP NOT NULL,
accepted_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_user_invitations_email
ON user_invitations(email, expires_at DESC);
CREATE TABLE IF NOT EXISTS notification_dispatch_log (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
notification_type VARCHAR(100) NOT NULL,
dedupe_key VARCHAR(255) NOT NULL UNIQUE,
metadata JSONB DEFAULT '{}'::jsonb,
sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_notification_dispatch_log_user_type
ON notification_dispatch_log(user_id, notification_type, sent_at DESC);