-- Meshkee CMS — location reference tree (country → province → city) CREATE TYPE city_level AS ENUM ('country', 'province', 'city'); CREATE TABLE cities ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, parent_id BIGINT, level city_level NOT NULL, name_fa VARCHAR(255) NOT NULL, name_en VARCHAR(255) NOT NULL, landline_code VARCHAR(10), slug VARCHAR(100) NOT NULL, sort_order INTEGER NOT NULL DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT cities_slug_unique UNIQUE (slug), CONSTRAINT cities_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES cities (id) ON DELETE CASCADE, CONSTRAINT cities_slug_format CHECK (slug ~ '^[a-z0-9]+(?:-[a-z0-9]+)*$'), CONSTRAINT cities_name_fa_nonempty CHECK (char_length(trim(name_fa)) > 0), CONSTRAINT cities_name_en_nonempty CHECK (char_length(trim(name_en)) > 0), CONSTRAINT cities_landline_code_nonempty_optional CHECK ( landline_code IS NULL OR char_length(trim(landline_code)) > 0 ), CONSTRAINT cities_country_root CHECK ( (level = 'country' AND parent_id IS NULL) OR (level <> 'country' AND parent_id IS NOT NULL) ) ); CREATE INDEX idx_cities_parent_id ON cities (parent_id); CREATE INDEX idx_cities_level ON cities (level); CREATE INDEX idx_cities_level_parent ON cities (level, parent_id, sort_order); CREATE INDEX idx_cities_active ON cities (is_active) WHERE is_active = TRUE; CREATE TRIGGER cities_set_updated_at BEFORE UPDATE ON cities FOR EACH ROW EXECUTE FUNCTION set_updated_at(); CREATE OR REPLACE FUNCTION cities_validate_parent_level() RETURNS TRIGGER AS $$ DECLARE parent_level city_level; BEGIN IF NEW.level = 'country' THEN RETURN NEW; END IF; SELECT level INTO parent_level FROM cities WHERE id = NEW.parent_id; IF NOT FOUND THEN RAISE EXCEPTION 'parent city not found'; END IF; IF NEW.level = 'province' AND parent_level <> 'country' THEN RAISE EXCEPTION 'province parent must be a country'; END IF; IF NEW.level = 'city' AND parent_level <> 'province' THEN RAISE EXCEPTION 'city parent must be a province'; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER cities_validate_parent_level BEFORE INSERT OR UPDATE ON cities FOR EACH ROW EXECUTE FUNCTION cities_validate_parent_level();