From c165c6b47062d0684a470c5c08bd78afaedb485a Mon Sep 17 00:00:00 2001 From: Anton Shubin <2spy4x@gmail.com> Date: Mon, 29 Jul 2024 21:04:18 +0800 Subject: [PATCH] docs: add intial db structure idea --- docs/db.sql | 273 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 docs/db.sql diff --git a/docs/db.sql b/docs/db.sql new file mode 100644 index 0000000..038e183 --- /dev/null +++ b/docs/db.sql @@ -0,0 +1,273 @@ +-- Postgres database for lamp-control system + +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + first_name VARCHAR(50), + last_name VARCHAR(50), + role SMALLINT NOT NULL DEFAULT 0 CHECK (role BETWEEN 0 AND 3), -- 0=viewer, 1=user, 2=operator, 3=administrator + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + last_login TIMESTAMPTZ, + banned_at TIMESTAMPTZ, + deleted_at TIMESTAMPTZ +); + +CREATE TABLE user_sessions ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id), + session_token VARCHAR(255) UNIQUE NOT NULL, + expires_at TIMESTAMPTZ NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE zones ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMPTZ +); + +CREATE TABLE lamp_profiles ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + voltage INTEGER NOT NULL, -- mV (millivolts) + current INTEGER NOT NULL, -- mA (milliamps) + runtime INTEGER NOT NULL, -- minutes + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMPTZ +); + +CREATE TABLE sensors ( + id SERIAL PRIMARY KEY, + device_id VARCHAR(36) UNIQUE, + gb_id VARCHAR(36) UNIQUE, + latitude DECIMAL(9,6), + longitude DECIMAL(9,6), + zone_id INT REFERENCES zones(id), + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMPTZ +); + +CREATE TABLE lamps_relay_boxes ( + id SERIAL PRIMARY KEY, + device_id VARCHAR(36) UNIQUE, -- uuid + gb_id VARCHAR(36) UNIQUE, -- uuid + identification_number VARCHAR(50) UNIQUE, + -- TODO: calculate status instead of storing it? + status SMALLINT NOT NULL DEFAULT 0 CHECK (status BETWEEN 0 AND 2), -- 0=off, 1=on, 2=error + latitude DECIMAL(9,6), + longitude DECIMAL(9,6), + lamp_post_number VARCHAR(50) NOT NULL, + running_minutes INTEGER NOT NULL DEFAULT 0, + zone_id INT REFERENCES zones(id), + profile_id INT REFERENCES lamp_profiles(id), + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMPTZ +); +CREATE INDEX idx_lamp_relay_boxes_zone_id ON lamp_relay_boxes(zone_id); + +-- TODO: maybe calculate running minutes based on metrics instead of status changes? +-- Function to calculate running minutes -- Modified function name and logic +CREATE OR REPLACE FUNCTION update_lamp_relay_box_running_minutes() +RETURNS TRIGGER AS $$ +BEGIN + IF NEW.status = 1 AND OLD.status = 0 THEN + UPDATE lamp_relay_boxes + SET running_minutes = running_minutes + EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - OLD.updated_at)) / 60 + WHERE id = NEW.id; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Trigger to update running minutes when lamp_relay_box status changes -- Modified trigger name +CREATE TRIGGER trg_update_lamp_relay_box_running_minutes +AFTER UPDATE OF status ON lamp_relay_boxes +FOR EACH ROW +EXECUTE FUNCTION trg_update_lamp_relay_box_running_minutes(); + +CREATE TABLE lamp_relay_box_logs ( + id SERIAL PRIMARY KEY, + lamp_relay_box_id INT NOT NULL REFERENCES lamp_relay_boxes(id), + status SMALLINT NOT NULL, -- 0=off, 1=on, 2=error + event_type SMALLINT NOT NULL, -- 0=manual, 1=scheduled, 2=error, 3=gb + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP +); +CREATE INDEX idx_lamp_relay_box_logs_lamp_relay_box_id_created_at ON lamp_relay_box_logs(lamp_relay_box_id, created_at); + +CREATE TABLE metrics ( + id SERIAL PRIMARY KEY, + lamp_relay_box_id INTEGER NOT NULL REFERENCES lamp_relay_boxes(id), + metric SMALLINT NOT NULL CHECK (metric BETWEEN 0 AND 6), -- 0=current, 1=voltage, 2=power, 3=energy, 4=lux, 5=brightness, 6=motions + value INTEGER NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP +); +CREATE INDEX idx_metrics_lamp_relay_box_id_metric_created_at ON metrics(lamp_relay_box_id, metric, created_at); + +-- Create a view for real-time lamp_relay_box status +CREATE VIEW real_time_lamp_relay_box_status AS +SELECT + l.id, + l.identification_number, + l.status, -- TODO: calculate status instead of storing it? + l.latitude, + l.longitude, + lux.value AS lux, + motions.value AS motions, + power.value AS power, + current.value AS current, + voltage.value AS voltage, + energy.value AS energy, + brightness.value AS brightness, + GREATEST(lux.created_at, motions.created_at, power.created_at, current.created_at, voltage.created_at, energy.created_at, brightness.created_at) AS last_updated +FROM lamp_relay_boxes l +LEFT JOIN LATERAL ( + SELECT value, created_at + FROM metrics + WHERE lamp_relay_box_id = l.id + AND metric = 0 -- current + ORDER BY created_at DESC + LIMIT 1 +) current ON true +LEFT JOIN LATERAL ( + SELECT value, created_at + FROM metrics + WHERE lamp_relay_box_id = l.id + AND metric = 1 -- voltage + ORDER BY created_at DESC + LIMIT 1 +) voltage ON true +LEFT JOIN LATERAL ( + SELECT value, created_at + FROM metrics + WHERE lamp_relay_box_id = l.id + AND metric = 2 -- power + ORDER BY created_at DESC + LIMIT 1 +) power ON true +LEFT JOIN LATERAL ( + SELECT value, created_at + FROM metrics + WHERE lamp_relay_box_id = l.id + AND metric = 3 -- energy + ORDER BY created_at DESC + LIMIT 1 +) energy ON true +LEFT JOIN LATERAL ( + SELECT value, created_at + FROM metrics + WHERE lamp_relay_box_id = l.id + AND metric = 4 -- lux + ORDER BY created_at DESC + LIMIT 1 +) lux ON true +LEFT JOIN LATERAL ( + SELECT value, created_at + FROM metrics + WHERE lamp_relay_box_id = l.id + AND metric = 5 -- brightness + ORDER BY created_at DESC + LIMIT 1 +) brightness ON true +LEFT JOIN LATERAL ( + SELECT value, created_at + FROM metrics + WHERE lamp_relay_box_id = l.id + AND metric = 6 -- motions + ORDER BY created_at DESC + LIMIT 1 +) motions ON true; + +-- Function to update lamp_relay_box status based on metrics +CREATE OR REPLACE FUNCTION update_lamp_relay_box_status() +RETURNS TRIGGER AS $$ +BEGIN + UPDATE lamp_relay_boxes + SET + status = CASE + WHEN NEW.power_watts > 0 THEN 1 + ELSE 0 + END, + updated_at = NEW.created_at + WHERE id = NEW.lamp_relay_box_id; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Trigger to update lamp_relay_box status when new metrics is inserted +CREATE TRIGGER trigger_update_lamp_relay_box_status +AFTER INSERT ON metrics +FOR EACH ROW +EXECUTE FUNCTION update_lamp_relay_box_status(); + +CREATE TABLE schedules ( + id SERIAL PRIMARY KEY, + type SMALLINT NOT NULL, -- 0=manual, 1=scheduled + start_time TIME NOT NULL, + end_time TIME NOT NULL, + status SMALLINT NOT NULL, -- 0=on, 1=off + days_of_week bit(7) NOT NULL, -- Bitmask for days of the week + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMPTZ +); + +CREATE TABLE zone_schedules ( + id SERIAL PRIMARY KEY, + zone_id INT NOT NULL REFERENCES zones(id), + schedule_id INTEGER REFERENCES schedules(id), + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMPTZ +); +CREATE INDEX idx_zone_schedules_zone_id ON zone_schedules(zone_id); + +CREATE TABLE manual_overrides ( + id SERIAL PRIMARY KEY, + zone_id INT NOT NULL REFERENCES zones(id), + user_id INT NOT NULL REFERENCES users(id), + start_time TIMESTAMPTZ NOT NULL, + end_time TIMESTAMPTZ NOT NULL, + type SMALLINT NOT NULL, -- 0=turn_off, 1=turn_on + reason VARCHAR(500), + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMPTZ +); +CREATE INDEX idx_manual_overrides_zone_id_start_time ON manual_overrides(zone_id, start_time); + +CREATE TABLE alerts ( + id SERIAL PRIMARY KEY, + lamp_relay_box_id INT NOT NULL REFERENCES lamp_relay_boxes(id), + type SMALLINT NOT NULL, + description VARCHAR(500), + status SMALLINT NOT NULL DEFAULT 0, -- 0=active, 1=acknowledged, 2=resolved + acknowledged_by INTEGER REFERENCES users(user_id), + acknowledged_at TIMESTAMPTZ, + resolved_by INTEGER REFERENCES users(user_id), + resolved_at TIMESTAMPTZ, + comments TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE maintenance_logs ( + id SERIAL PRIMARY KEY, + lamp_relay_box_id INTEGER REFERENCES lamp_relay_boxes(lamp_relay_box_id), + user_id INTEGER REFERENCES users(user_id), + action_type SMALLINT NOT NULL, -- 0=inspection, 1=repair, 2=replacement + description TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- Index for querying by lamp_relay_box in chronological order +CREATE INDEX idx_maintenance_logs_lamp_relay_box_id_created_at ON maintenance_logs(lamp_relay_box_id, created_at); + +-- Index for querying by user in chronological order +CREATE INDEX idx_maintenance_logs_user_id_created_at ON maintenance_logs(user_id, created_at); + +