-- Livestock Marketplace schema (PostgreSQL) -- Generated from specs in README.md CREATE EXTENSION IF NOT EXISTS "pgcrypto"; CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(255) NOT NULL, phone VARCHAR(50) UNIQUE NOT NULL, location_id UUID NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS species ( id SERIAL PRIMARY KEY, name VARCHAR(100) UNIQUE NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS breeds ( id SERIAL PRIMARY KEY, species_id INT NOT NULL REFERENCES species(id) ON DELETE RESTRICT, name VARCHAR(100) NOT NULL, description_text TEXT, description_media_url TEXT, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- Constraint: Only one type of description should be allowed CONSTRAINT chk_description_type CHECK ( (description_text IS NOT NULL AND description_media_url IS NULL) OR (description_text IS NULL AND description_media_url IS NOT NULL) OR (description_text IS NULL AND description_media_url IS NULL) ) ); ------------------------------------------------------- -- ENUM for location type ------------------------------------------------------- CREATE TYPE location_type_enum AS ENUM ('farm', 'home', 'other'); CREATE TYPE location_source_type_enum AS ENUM ( 'gps', 'manual', 'imported', 'unknown' ); CREATE TABLE IF NOT EXISTS location_details ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), is_saved_address BOOLEAN NOT NULL DEFAULT FALSE, location_type location_type_enum NOT NULL, country VARCHAR(100), state VARCHAR(100), district VARCHAR(100), city_village VARCHAR(150), pincode VARCHAR(20), source_type location_source_type_enum NOT NULL DEFAULT 'unknown', source_confidence VARCHAR(50) NOT NULL DEFAULT 'medium', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS user_locations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, lat DECIMAL(10,7) NOT NULL, lng DECIMAL(10,7) NOT NULL, location_detail_id UUID UNIQUE REFERENCES location_details(id) ON DELETE SET NULL, selected_location BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- Selected location is unique per user, so this trigger ensures that only one location is selected per user CREATE OR REPLACE FUNCTION enforce_single_selected_location() RETURNS trigger AS $$ BEGIN -- When changing a location to selected (only act if actually setting to TRUE) IF NEW.selected_location = TRUE THEN -- Unselect all other locations for this user (excluding the current row) UPDATE user_locations SET selected_location = FALSE WHERE user_id = NEW.user_id AND id <> NEW.id AND selected_location = TRUE; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_single_selected_location BEFORE INSERT OR UPDATE ON user_locations FOR EACH ROW EXECUTE FUNCTION enforce_single_selected_location(); ------------------------------------------------------- -- Add FK constraint after both tables exist ------------------------------------------------------- ALTER TABLE users ADD CONSTRAINT fk_users_location FOREIGN KEY (location_id) REFERENCES user_locations(id) ON DELETE SET NULL; ------------------------------------------------------- -- Indexes ------------------------------------------------------- CREATE INDEX idx_user_locations_user ON user_locations(user_id); CREATE INDEX idx_user_locations_selected ON user_locations(selected_location); CREATE INDEX idx_location_details_type ON location_details(location_type); CREATE INDEX idx_user_locations_location_detail ON user_locations(location_detail_id); CREATE TABLE IF NOT EXISTS animals ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), species_id INT NOT NULL REFERENCES species(id) ON DELETE RESTRICT, breed_id INT REFERENCES breeds(id) ON DELETE SET NULL, sex VARCHAR(20) NOT NULL, age_months INT, weight_kg DECIMAL(10,2), color_markings VARCHAR(200), quantity INT NOT NULL DEFAULT 1, purpose VARCHAR(50) NOT NULL, health_status VARCHAR(50) NOT NULL, vaccinated BOOLEAN NOT NULL DEFAULT FALSE, dewormed BOOLEAN NOT NULL DEFAULT FALSE, previous_pregnancies_count INT, pregnancy_status VARCHAR(50), milk_yield_litre_per_day DECIMAL(10,2), ear_tag_no VARCHAR(100), description TEXT, suggested_care TEXT, location_id UUID REFERENCES user_locations(id) ON DELETE SET NULL, created_from VARCHAR(50) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TYPE listing_status_enum AS ENUM ( 'active', 'expired', 'hidden', 'sold', 'deleted' ); CREATE TYPE listing_score_status_enum AS ENUM ( 'pending', 'calculating', 'completed', 'failed' ); CREATE TYPE listing_type_enum AS ENUM ( 'sale', 'stud_service', 'adoption', 'other' ); CREATE TYPE seller_type_enum AS ENUM ( 'owner', 'farmer', 'broker', 'agent', 'other' ); CREATE TABLE IF NOT EXISTS listings ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), seller_id UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT, animal_id UUID NOT NULL UNIQUE REFERENCES animals(id) ON DELETE CASCADE, title VARCHAR(200) NOT NULL, price NUMERIC(12,2) NOT NULL, currency VARCHAR(10) NOT NULL, is_negotiable BOOLEAN NOT NULL DEFAULT TRUE, listing_type listing_type_enum NOT NULL, status listing_status_enum NOT NULL DEFAULT 'active', listing_score INT NOT NULL DEFAULT 0, views_count INT NOT NULL DEFAULT 0, bookmarks_count INT NOT NULL DEFAULT 0, enquiries_call_count INT NOT NULL DEFAULT 0, enquiries_whatsapp_count INT NOT NULL DEFAULT 0, clicks_count INT NOT NULL DEFAULT 0, listing_score_status listing_score_status_enum NOT NULL DEFAULT 'pending', seller_type seller_type_enum NOT NULL DEFAULT 'owner', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS listing_media ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), listing_id UUID NOT NULL REFERENCES listings(id) ON DELETE CASCADE, media_url VARCHAR(500) NOT NULL, media_type VARCHAR(50) NOT NULL, is_primary BOOLEAN NOT NULL DEFAULT FALSE, sort_order INT NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS custom_requirements ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT, requirement_text TEXT NOT NULL, animal_id UUID REFERENCES animals(id) ON DELETE SET NULL, status VARCHAR(50) NOT NULL DEFAULT 'open', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TYPE message_type_enum AS ENUM ('text', 'media', 'both'); CREATE TYPE media_type_enum AS ENUM ('image', 'video', 'audio', 'document'); CREATE TABLE conversations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), buyer_id UUID NOT NULL, seller_id UUID NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), CONSTRAINT fk_conversation_buyer FOREIGN KEY (buyer_id) REFERENCES users(id), CONSTRAINT fk_conversation_seller FOREIGN KEY (seller_id) REFERENCES users(id), CONSTRAINT unique_buyer_seller UNIQUE (buyer_id, seller_id) ); CREATE TABLE conversation_media ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), media_type media_type_enum NOT NULL, -- image | video | audio | document media_url TEXT NOT NULL, thumbnail_url TEXT NULL, duration_sec INT NULL, -- audio/video only file_size_kb INT NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), conversation_id UUID NOT NULL, sender_id UUID NOT NULL, receiver_id UUID NOT NULL, message_type message_type_enum NOT NULL DEFAULT 'text', content TEXT NULL, -- nullable for media-only messages media_id UUID UNIQUE, -- FK for media, enforce 1:1 relationship is_read BOOLEAN NOT NULL DEFAULT FALSE, read_at TIMESTAMP NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), is_deleted BOOLEAN NOT NULL DEFAULT FALSE, CONSTRAINT fk_message_conversation FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE, CONSTRAINT fk_message_sender FOREIGN KEY (sender_id) REFERENCES users(id), CONSTRAINT fk_message_receiver FOREIGN KEY (receiver_id) REFERENCES users(id), CONSTRAINT fk_message_media FOREIGN KEY (media_id) REFERENCES conversation_media(id) ON DELETE SET NULL, -- Ensures consistency between message_type and media usage CONSTRAINT chk_message_media_relation CHECK ( (message_type = 'text' AND media_id IS NULL) OR (message_type = 'media' AND media_id IS NOT NULL AND content IS NULL) OR (message_type = 'both' AND media_id IS NOT NULL AND content IS NOT NULL) ) ); CREATE INDEX idx_conversations_buyer ON conversations(buyer_id); CREATE INDEX idx_conversations_seller ON conversations(seller_id); CREATE INDEX idx_messages_conversation ON messages(conversation_id); CREATE INDEX idx_messages_sender ON messages(sender_id); CREATE INDEX idx_messages_media ON messages(media_id); CREATE TYPE communication_type_enum AS ENUM ('call', 'missed_call', 'voicemail'); CREATE TYPE call_status_enum AS ENUM ( 'initiated', 'ringing', 'answered', 'completed', 'failed', 'busy', 'no_answer' ); CREATE TABLE communication_records ( communication_id BIGSERIAL PRIMARY KEY, conversation_id UUID NOT NULL, -- Conversation this communication belongs to buyer_id UUID NOT NULL, -- Buyer involved seller_id UUID NOT NULL, -- Seller involved request_id UUID NULL, -- Optional: Buy request this communication refers to communication_type communication_type_enum NOT NULL, cpaas_call_id VARCHAR(100) NULL, -- Reference ID returned by CPaaS provider call_status call_status_enum NOT NULL, start_time TIMESTAMP NULL, -- Time CPaaS initiated call end_time TIMESTAMP NULL, -- Time call ended duration_seconds INT DEFAULT 0, -- Duration calculated recording_url TEXT NULL, -- Optional CPaaS recording URL metadata JSON NULL, -- Any extra payload created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- Foreign Keys CONSTRAINT fk_comm_conversation FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE, CONSTRAINT fk_comm_buyer FOREIGN KEY (buyer_id) REFERENCES users(id) ON DELETE CASCADE, CONSTRAINT fk_comm_seller FOREIGN KEY (seller_id) REFERENCES users(id) ON DELETE CASCADE );