From 576c4acc01c8050f868ba9f8edbd09f8f9565dbd Mon Sep 17 00:00:00 2001 From: Aditya Pulipaka Date: Tue, 5 May 2026 18:38:53 +0000 Subject: [PATCH] email works now + schema more closely follows original --- db/schema.sql | 175 +++++++++--- mailer.js | 2 +- oldVersion.sql | 714 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 849 insertions(+), 42 deletions(-) create mode 100644 oldVersion.sql diff --git a/db/schema.sql b/db/schema.sql index 1846ba1..4f24fb2 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -1,29 +1,54 @@ --- BlindMaster schema. Idempotent: CREATE … IF NOT EXISTS so it works on a --- fresh DB and replays cleanly against an existing one. +-- BlindMaster schema. Idempotent: safe on fresh DB and replays cleanly against +-- an existing one. Source of truth for table structure, constraints, indexes, +-- and triggers. Restored from oldVersion.sql backup + newer additions. --- ── Core auth ─────────────────────────────────────────────────────────────── +-- ── Trigger: auto-delete empty groups ─────────────────────────────────────── +CREATE OR REPLACE FUNCTION public.delete_group_if_empty() RETURNS trigger + LANGUAGE plpgsql AS $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM group_peripherals WHERE group_id = OLD.group_id) THEN + DELETE FROM groups WHERE id = OLD.group_id; + END IF; + RETURN OLD; +END; +$$; + +-- ── Core auth ──────────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - name TEXT, + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, email TEXT NOT NULL UNIQUE, password_hash_string TEXT NOT NULL, + name TEXT, verification_token TEXT, is_verified BOOLEAN NOT NULL DEFAULT FALSE, timezone TEXT DEFAULT 'America/Chicago', apns_token TEXT, - fcm_token TEXT, -- legacy (FCM era), kept harmless created_at TIMESTAMPTZ DEFAULT NOW() ); --- One active session token per user. Code does delete-then-insert on login, --- so PK on user_id enforces it. -CREATE TABLE IF NOT EXISTS user_tokens ( +CREATE UNIQUE INDEX IF NOT EXISTS users_id_idx ON users(id); + +-- UNLOGGED: ephemeral session state — no WAL writes, cleared on crash (correct +-- behaviour; clients reconnect anyway). One row per user enforced by PK. +CREATE UNLOGGED TABLE IF NOT EXISTS user_tokens ( user_id INTEGER PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, - token TEXT NOT NULL UNIQUE, - connected BOOLEAN NOT NULL DEFAULT FALSE, - socket TEXT + token TEXT NOT NULL, + connected BOOLEAN DEFAULT FALSE, + socket VARCHAR(255) ); +-- Convert to UNLOGGED if the table already exists as a logged table. +-- user_tokens holds only live socket state; clearing it on restart is correct. +DO $$ BEGIN + IF EXISTS (SELECT 1 FROM pg_class WHERE relname = 'user_tokens' AND relpersistence = 'p') THEN + ALTER TABLE user_tokens SET UNLOGGED; + END IF; +END $$; + +CREATE INDEX IF NOT EXISTS idx_user_tokens_token ON user_tokens(token); +CREATE INDEX IF NOT EXISTS idx_user_tokens_active_socket ON user_tokens(user_id, socket) WHERE connected = true; +CREATE INDEX IF NOT EXISTS idx_user_tokens_socket_connected ON user_tokens(socket) WHERE connected = true; + CREATE TABLE IF NOT EXISTS password_reset_tokens ( email TEXT PRIMARY KEY, token TEXT NOT NULL, @@ -38,43 +63,60 @@ CREATE TABLE IF NOT EXISTS user_pending_emails ( expires_at TIMESTAMPTZ NOT NULL ); --- ── Hardware: hubs (devices) and their controlled blinds (peripherals) ────── +-- ── Hardware: hubs (devices) and their controlled blinds (peripherals) ─────── CREATE TABLE IF NOT EXISTS devices ( - id SERIAL PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, - device_name TEXT NOT NULL, - max_ports INTEGER NOT NULL DEFAULT 4, + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + device_name TEXT NOT NULL, + max_ports INTEGER NOT NULL DEFAULT 4, battery_soc SMALLINT, timezone TEXT, UNIQUE (user_id, device_name) ); --- token UNIQUE so socket-auth can `select device_id … where token=$1` quickly. -CREATE TABLE IF NOT EXISTS device_tokens ( - device_id INTEGER NOT NULL REFERENCES devices(id) ON DELETE CASCADE, - token TEXT NOT NULL UNIQUE, - connected BOOLEAN NOT NULL DEFAULT FALSE, - socket TEXT -); -CREATE INDEX IF NOT EXISTS device_tokens_device_id_idx ON device_tokens(device_id); +CREATE INDEX IF NOT EXISTS idx_devices_user_id ON devices(user_id); +CREATE INDEX IF NOT EXISTS idx_devices_max_ports ON devices(max_ports); +-- UNLOGGED: ephemeral device connection state, same rationale as user_tokens. +CREATE UNLOGGED TABLE IF NOT EXISTS device_tokens ( + device_id INTEGER NOT NULL REFERENCES devices(id) ON DELETE CASCADE, + token TEXT NOT NULL, + connected BOOLEAN DEFAULT FALSE, + socket VARCHAR(255) +); + +DO $$ BEGIN + IF EXISTS (SELECT 1 FROM pg_class WHERE relname = 'device_tokens' AND relpersistence = 'p') THEN + ALTER TABLE device_tokens SET UNLOGGED; + END IF; +END $$; + +CREATE INDEX IF NOT EXISTS idx_device_tokens_token ON device_tokens(token); +CREATE INDEX IF NOT EXISTS idx_device_tokens_device_id_connected ON device_tokens(device_id) WHERE connected = true; +CREATE INDEX IF NOT EXISTS idx_device_tokens_active_socket ON device_tokens(device_id, socket) WHERE connected = true; +CREATE INDEX IF NOT EXISTS idx_device_tokens_socket_connected ON device_tokens(socket) WHERE connected = true; + +-- autovacuum tuned aggressively: last_pos is updated on every blind movement. CREATE TABLE IF NOT EXISTS peripherals ( - id SERIAL PRIMARY KEY, + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, device_id INTEGER NOT NULL REFERENCES devices(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, peripheral_number INTEGER NOT NULL, peripheral_name TEXT NOT NULL, - last_pos INTEGER, - last_set TIMESTAMPTZ, calibrated BOOLEAN DEFAULT FALSE, await_calib BOOLEAN DEFAULT FALSE, - UNIQUE (device_id, peripheral_number) -); -CREATE INDEX IF NOT EXISTS peripherals_user_id_idx ON peripherals(user_id); + last_pos INTEGER DEFAULT 0 CHECK (last_pos >= 0 AND last_pos <= 10), + last_set TIMESTAMPTZ, + UNIQUE (device_id, peripheral_number), + UNIQUE (device_id, peripheral_name) +) WITH (autovacuum_vacuum_scale_factor = 0.02); --- ── Multi-blind groups (e.g. "all kitchen blinds") ────────────────────────── +CREATE INDEX IF NOT EXISTS idx_peripherals_device_id ON peripherals(device_id); +CREATE INDEX IF NOT EXISTS idx_peripherals_id_user ON peripherals(id, user_id); + +-- ── Multi-blind groups ─────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS groups ( - id SERIAL PRIMARY KEY, + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, name TEXT NOT NULL, timezone TEXT, @@ -82,17 +124,68 @@ CREATE TABLE IF NOT EXISTS groups ( UNIQUE (user_id, name) ); +CREATE INDEX IF NOT EXISTS idx_groups_user_id ON groups(user_id); + CREATE TABLE IF NOT EXISTS group_peripherals ( group_id INTEGER NOT NULL REFERENCES groups(id) ON DELETE CASCADE, peripheral_id INTEGER NOT NULL REFERENCES peripherals(id) ON DELETE CASCADE, PRIMARY KEY (group_id, peripheral_id) ); --- ── Idempotent column additions for older DBs ─────────────────────────────── --- These mirror the runtime-bootstrap ALTERs the index.js used to do on every --- startup. Safe on fresh DBs because the columns are already in CREATE TABLE. -ALTER TABLE users ADD COLUMN IF NOT EXISTS apns_token TEXT; -ALTER TABLE users ADD COLUMN IF NOT EXISTS timezone TEXT DEFAULT 'America/Chicago'; -ALTER TABLE devices ADD COLUMN IF NOT EXISTS battery_soc SMALLINT; -ALTER TABLE devices ADD COLUMN IF NOT EXISTS timezone TEXT; -ALTER TABLE groups ADD COLUMN IF NOT EXISTS timezone TEXT; +CREATE INDEX IF NOT EXISTS idx_group_peripherals_group_id ON group_peripherals(group_id); +CREATE INDEX IF NOT EXISTS idx_group_peripherals_peripheral_id ON group_peripherals(peripheral_id); + +-- Trigger fires after any peripheral is removed from a group; deletes the group +-- itself if it is now empty. +DO $$ BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_cleanup_empty_groups' + ) THEN + CREATE TRIGGER trigger_cleanup_empty_groups + AFTER DELETE ON group_peripherals + FOR EACH ROW EXECUTE FUNCTION public.delete_group_if_empty(); + END IF; +END $$; + +-- ── Schedules ──────────────────────────────────────────────────────────────── +CREATE TABLE IF NOT EXISTS schedules ( + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + schedule_type VARCHAR(50) NOT NULL CHECK (schedule_type IN ('peripheral', 'group')), + peripheral_id INTEGER REFERENCES peripherals(id) ON DELETE CASCADE, + device_id INTEGER REFERENCES devices(id) ON DELETE CASCADE, + peripheral_number INTEGER, + group_id INTEGER REFERENCES groups(id) ON DELETE CASCADE, + target_position INTEGER NOT NULL, + cron_expression VARCHAR(100) NOT NULL, + cron_minute VARCHAR(50) NOT NULL, + cron_hour VARCHAR(50) NOT NULL, + cron_days VARCHAR(50) NOT NULL, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT peripheral_or_group CHECK ( + (schedule_type = 'peripheral' AND peripheral_id IS NOT NULL AND group_id IS NULL) OR + (schedule_type = 'group' AND group_id IS NOT NULL AND peripheral_id IS NULL) + ) +); + +CREATE INDEX IF NOT EXISTS idx_schedules_user_id ON schedules(user_id); + +-- Prevent duplicate cron times for the same target. +CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_peripheral_schedule + ON schedules(peripheral_id, cron_expression) + WHERE schedule_type = 'peripheral'; + +CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_group_schedule + ON schedules(group_id, cron_expression) + WHERE schedule_type = 'group'; + +-- ── Idempotent column additions for older DBs ──────────────────────────────── +ALTER TABLE users ADD COLUMN IF NOT EXISTS apns_token TEXT; +ALTER TABLE users ADD COLUMN IF NOT EXISTS timezone TEXT DEFAULT 'America/Chicago'; +ALTER TABLE users ADD COLUMN IF NOT EXISTS verification_token TEXT; +ALTER TABLE users ADD COLUMN IF NOT EXISTS is_verified BOOLEAN NOT NULL DEFAULT FALSE; +ALTER TABLE users ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ DEFAULT NOW(); +ALTER TABLE devices ADD COLUMN IF NOT EXISTS battery_soc SMALLINT; +ALTER TABLE devices ADD COLUMN IF NOT EXISTS timezone TEXT; +ALTER TABLE groups ADD COLUMN IF NOT EXISTS timezone TEXT; diff --git a/mailer.js b/mailer.js index bf69e50..9d82e5e 100644 --- a/mailer.js +++ b/mailer.js @@ -18,7 +18,7 @@ async function sendMail({ to, subject, html }) { return null; } return mg.messages.create(DOMAIN, { - from: `"BlindMaster" <${FROM}>`, + from: FROM, to: [to], subject, html, diff --git a/oldVersion.sql b/oldVersion.sql new file mode 100644 index 0000000..ca024f7 --- /dev/null +++ b/oldVersion.sql @@ -0,0 +1,714 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 17.4 +-- Dumped by pg_dump version 17.4 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET transaction_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: delete_group_if_empty(); Type: FUNCTION; Schema: public; Owner: postgres +-- + +CREATE FUNCTION public.delete_group_if_empty() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + -- Check if the group has any members remaining in the junction table + IF NOT EXISTS (SELECT 1 FROM group_peripherals WHERE group_id = OLD.group_id) THEN + -- If no members exist, delete the group itself + DELETE FROM groups WHERE id = OLD.group_id; + END IF; + RETURN OLD; +END; +$$; + + +ALTER FUNCTION public.delete_group_if_empty() OWNER TO postgres; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: device_tokens; Type: TABLE; Schema: public; Owner: BlindMasterApp +-- + +CREATE UNLOGGED TABLE public.device_tokens ( + device_id integer NOT NULL, + token text NOT NULL, + connected boolean DEFAULT false, + socket character varying(255) +); + + +ALTER TABLE public.device_tokens OWNER TO "BlindMasterApp"; + +-- +-- Name: devices; Type: TABLE; Schema: public; Owner: BlindMasterApp +-- + +CREATE TABLE public.devices ( + id integer NOT NULL, + user_id integer NOT NULL, + device_name text NOT NULL, + max_ports integer DEFAULT 4 NOT NULL +); + + +ALTER TABLE public.devices OWNER TO "BlindMasterApp"; + +-- +-- Name: COLUMN devices.max_ports; Type: COMMENT; Schema: public; Owner: BlindMasterApp +-- + +COMMENT ON COLUMN public.devices.max_ports IS 'Maximum number of ports/peripherals this device supports (1 for C6, 4 for multi-port)'; + + +-- +-- Name: devices_id_seq; Type: SEQUENCE; Schema: public; Owner: BlindMasterApp +-- + +CREATE SEQUENCE public.devices_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.devices_id_seq OWNER TO "BlindMasterApp"; + +-- +-- Name: devices_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: BlindMasterApp +-- + +ALTER SEQUENCE public.devices_id_seq OWNED BY public.devices.id; + + +-- +-- Name: group_peripherals; Type: TABLE; Schema: public; Owner: BlindMasterApp +-- + +CREATE TABLE public.group_peripherals ( + group_id integer NOT NULL, + peripheral_id integer NOT NULL +); + + +ALTER TABLE public.group_peripherals OWNER TO "BlindMasterApp"; + +-- +-- Name: groups; Type: TABLE; Schema: public; Owner: BlindMasterApp +-- + +CREATE TABLE public.groups ( + id integer NOT NULL, + user_id integer NOT NULL, + name text NOT NULL, + created_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.groups OWNER TO "BlindMasterApp"; + +-- +-- Name: groups_id_seq; Type: SEQUENCE; Schema: public; Owner: BlindMasterApp +-- + +CREATE SEQUENCE public.groups_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.groups_id_seq OWNER TO "BlindMasterApp"; + +-- +-- Name: groups_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: BlindMasterApp +-- + +ALTER SEQUENCE public.groups_id_seq OWNED BY public.groups.id; + + +-- +-- Name: peripherals; Type: TABLE; Schema: public; Owner: BlindMasterApp +-- + +CREATE TABLE public.peripherals ( + id integer NOT NULL, + device_id integer NOT NULL, + peripheral_number integer NOT NULL, + peripheral_name text NOT NULL, + calibrated boolean DEFAULT false, + await_calib boolean DEFAULT false, + last_pos integer DEFAULT 0, + last_set timestamp without time zone, + user_id integer NOT NULL, + CONSTRAINT peripherals_last_pos_check CHECK (((last_pos >= 0) AND (last_pos <= 10))) +) +WITH (autovacuum_vacuum_scale_factor='0.02'); + + +ALTER TABLE public.peripherals OWNER TO "BlindMasterApp"; + +-- +-- Name: peripherals_id_seq; Type: SEQUENCE; Schema: public; Owner: BlindMasterApp +-- + +CREATE SEQUENCE public.peripherals_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.peripherals_id_seq OWNER TO "BlindMasterApp"; + +-- +-- Name: peripherals_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: BlindMasterApp +-- + +ALTER SEQUENCE public.peripherals_id_seq OWNED BY public.peripherals.id; + + +-- +-- Name: schedules; Type: TABLE; Schema: public; Owner: BlindMasterApp +-- + +CREATE TABLE public.schedules ( + id integer NOT NULL, + user_id integer NOT NULL, + schedule_type character varying(50) NOT NULL, + peripheral_id integer, + device_id integer, + peripheral_number integer, + group_id integer, + target_position integer NOT NULL, + cron_expression character varying(100) NOT NULL, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + cron_minute character varying(50) NOT NULL, + cron_hour character varying(50) NOT NULL, + cron_days character varying(50) NOT NULL, + CONSTRAINT peripheral_or_group CHECK (((((schedule_type)::text = 'peripheral'::text) AND (peripheral_id IS NOT NULL) AND (group_id IS NULL)) OR (((schedule_type)::text = 'group'::text) AND (group_id IS NOT NULL) AND (peripheral_id IS NULL)))), + CONSTRAINT schedule_type_check CHECK (((schedule_type)::text = ANY ((ARRAY['peripheral'::character varying, 'group'::character varying])::text[]))) +); + + +ALTER TABLE public.schedules OWNER TO "BlindMasterApp"; + +-- +-- Name: schedules_id_seq; Type: SEQUENCE; Schema: public; Owner: BlindMasterApp +-- + +CREATE SEQUENCE public.schedules_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.schedules_id_seq OWNER TO "BlindMasterApp"; + +-- +-- Name: schedules_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: BlindMasterApp +-- + +ALTER SEQUENCE public.schedules_id_seq OWNED BY public.schedules.id; + + +-- +-- Name: user_tokens; Type: TABLE; Schema: public; Owner: BlindMasterApp +-- + +CREATE UNLOGGED TABLE public.user_tokens ( + user_id integer NOT NULL, + token text NOT NULL, + connected boolean DEFAULT false, + socket character varying(255) +); + + +ALTER TABLE public.user_tokens OWNER TO "BlindMasterApp"; + +-- +-- Name: users; Type: TABLE; Schema: public; Owner: BlindMasterApp +-- + +CREATE TABLE public.users ( + id integer NOT NULL, + email text NOT NULL, + password_hash_string text NOT NULL, + name text +); + + +ALTER TABLE public.users OWNER TO "BlindMasterApp"; + +-- +-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE public.users ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.users_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: devices id; Type: DEFAULT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.devices ALTER COLUMN id SET DEFAULT nextval('public.devices_id_seq'::regclass); + + +-- +-- Name: groups id; Type: DEFAULT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.groups ALTER COLUMN id SET DEFAULT nextval('public.groups_id_seq'::regclass); + + +-- +-- Name: peripherals id; Type: DEFAULT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.peripherals ALTER COLUMN id SET DEFAULT nextval('public.peripherals_id_seq'::regclass); + + +-- +-- Name: schedules id; Type: DEFAULT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.schedules ALTER COLUMN id SET DEFAULT nextval('public.schedules_id_seq'::regclass); + + +-- +-- Data for Name: device_tokens; Type: TABLE DATA; Schema: public; Owner: BlindMasterApp +-- + +COPY public.device_tokens (device_id, token, connected, socket) FROM stdin; +\. + + +-- +-- Data for Name: devices; Type: TABLE DATA; Schema: public; Owner: BlindMasterApp +-- + +COPY public.devices (id, user_id, device_name, max_ports) FROM stdin; +44 9 biob 1 +\. + + +-- +-- Data for Name: group_peripherals; Type: TABLE DATA; Schema: public; Owner: BlindMasterApp +-- + +COPY public.group_peripherals (group_id, peripheral_id) FROM stdin; +6 32 +\. + + +-- +-- Data for Name: groups; Type: TABLE DATA; Schema: public; Owner: BlindMasterApp +-- + +COPY public.groups (id, user_id, name, created_at) FROM stdin; +6 9 joij 2026-01-02 12:47:17.736192 +\. + + +-- +-- Data for Name: peripherals; Type: TABLE DATA; Schema: public; Owner: BlindMasterApp +-- + +COPY public.peripherals (id, device_id, peripheral_number, peripheral_name, calibrated, await_calib, last_pos, last_set, user_id) FROM stdin; +32 44 1 Main Blind t f 10 2026-01-04 20:38:00.034 9 +\. + + +-- +-- Data for Name: schedules; Type: TABLE DATA; Schema: public; Owner: BlindMasterApp +-- + +COPY public.schedules (id, user_id, schedule_type, peripheral_id, device_id, peripheral_number, group_id, target_position, cron_expression, created_at, updated_at, cron_minute, cron_hour, cron_days) FROM stdin; +16 9 group \N \N \N 6 0 0 12 * * 5 2026-01-04 14:46:06.294036 2026-01-04 14:46:06.294036 0 12 5 +20 9 peripheral 32 44 1 \N 6 57 14 * * 0,3,6 2026-01-04 14:56:24.45907 2026-01-04 14:56:24.45907 57 14 0,3,6 +\. + + +-- +-- Data for Name: user_tokens; Type: TABLE DATA; Schema: public; Owner: BlindMasterApp +-- + +COPY public.user_tokens (user_id, token, connected, socket) FROM stdin; +\. + + +-- +-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: BlindMasterApp +-- + +COPY public.users (id, email, password_hash_string, name) FROM stdin; +7 test@test.argon $argon2id$v=19$m=65536,t=3,p=4$eSNAQlKShPixO68C/bpXrA$pKCHfEkNi7gyTY+HRvk1KRvS1yor53R4m2IbyAwK7qM testArgon +8 pulipakaaditya@gmail.com $argon2id$v=19$m=65536,t=3,p=4$NpfdCsdtcD7bf3Osb3zE/w$Asw6cu5iJB575u/riWWfuXtM9Uw7Gi4sGcJpBuMxIb0 adi +9 n@n.n $argon2id$v=19$m=65536,t=3,p=4$CmQTWJbTIX5kQ6EgAhuhGw$029IVkydHw+iA8V1z34r9JFiKkzUSNWlwEvsA02sTPI n +\. + + +-- +-- Name: devices_id_seq; Type: SEQUENCE SET; Schema: public; Owner: BlindMasterApp +-- + +SELECT pg_catalog.setval('public.devices_id_seq', 44, true); + + +-- +-- Name: groups_id_seq; Type: SEQUENCE SET; Schema: public; Owner: BlindMasterApp +-- + +SELECT pg_catalog.setval('public.groups_id_seq', 6, true); + + +-- +-- Name: peripherals_id_seq; Type: SEQUENCE SET; Schema: public; Owner: BlindMasterApp +-- + +SELECT pg_catalog.setval('public.peripherals_id_seq', 32, true); + + +-- +-- Name: schedules_id_seq; Type: SEQUENCE SET; Schema: public; Owner: BlindMasterApp +-- + +SELECT pg_catalog.setval('public.schedules_id_seq', 20, true); + + +-- +-- Name: users_id_seq; Type: SEQUENCE SET; Schema: public; Owner: BlindMasterApp +-- + +SELECT pg_catalog.setval('public.users_id_seq', 9, true); + + +-- +-- Name: devices devices_pkey; Type: CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.devices + ADD CONSTRAINT devices_pkey PRIMARY KEY (id); + + +-- +-- Name: devices devices_user_id_device_name_key; Type: CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.devices + ADD CONSTRAINT devices_user_id_device_name_key UNIQUE (user_id, device_name); + + +-- +-- Name: group_peripherals group_peripherals_pkey; Type: CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.group_peripherals + ADD CONSTRAINT group_peripherals_pkey PRIMARY KEY (group_id, peripheral_id); + + +-- +-- Name: groups groups_pkey; Type: CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.groups + ADD CONSTRAINT groups_pkey PRIMARY KEY (id); + + +-- +-- Name: groups groups_user_id_name_key; Type: CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.groups + ADD CONSTRAINT groups_user_id_name_key UNIQUE (user_id, name); + + +-- +-- Name: peripherals peripherals_device_id_peripheral_name_key; Type: CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.peripherals + ADD CONSTRAINT peripherals_device_id_peripheral_name_key UNIQUE (device_id, peripheral_name); + + +-- +-- Name: peripherals peripherals_device_id_peripheral_number_key; Type: CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.peripherals + ADD CONSTRAINT peripherals_device_id_peripheral_number_key UNIQUE (device_id, peripheral_number); + + +-- +-- Name: peripherals peripherals_pkey; Type: CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.peripherals + ADD CONSTRAINT peripherals_pkey PRIMARY KEY (id); + + +-- +-- Name: schedules schedules_pkey; Type: CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.schedules + ADD CONSTRAINT schedules_pkey PRIMARY KEY (id); + + +-- +-- Name: users users_email_key; Type: CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT users_email_key UNIQUE (email); + + +-- +-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT users_pkey PRIMARY KEY (id); + + +-- +-- Name: idx_device_tokens_active_socket; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE INDEX idx_device_tokens_active_socket ON public.device_tokens USING btree (device_id, socket) WHERE (connected = true); + + +-- +-- Name: idx_device_tokens_device_id_connected; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE INDEX idx_device_tokens_device_id_connected ON public.device_tokens USING btree (device_id) WHERE (connected = true); + + +-- +-- Name: idx_device_tokens_socket_connected; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE INDEX idx_device_tokens_socket_connected ON public.device_tokens USING btree (socket) WHERE (connected = true); + + +-- +-- Name: idx_device_tokens_token; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE INDEX idx_device_tokens_token ON public.device_tokens USING btree (token); + + +-- +-- Name: idx_devices_max_ports; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE INDEX idx_devices_max_ports ON public.devices USING btree (max_ports); + + +-- +-- Name: idx_devices_user_id; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE INDEX idx_devices_user_id ON public.devices USING btree (user_id); + + +-- +-- Name: idx_group_peripherals_group_id; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE INDEX idx_group_peripherals_group_id ON public.group_peripherals USING btree (group_id); + + +-- +-- Name: idx_group_peripherals_peripheral_id; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE INDEX idx_group_peripherals_peripheral_id ON public.group_peripherals USING btree (peripheral_id); + + +-- +-- Name: idx_groups_user_id; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE INDEX idx_groups_user_id ON public.groups USING btree (user_id); + + +-- +-- Name: idx_peripherals_device_id; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE INDEX idx_peripherals_device_id ON public.peripherals USING btree (device_id); + + +-- +-- Name: idx_peripherals_id_user; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE INDEX idx_peripherals_id_user ON public.peripherals USING btree (id, user_id); + + +-- +-- Name: idx_schedules_user_id; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE INDEX idx_schedules_user_id ON public.schedules USING btree (user_id); + + +-- +-- Name: idx_unique_group_schedule; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE UNIQUE INDEX idx_unique_group_schedule ON public.schedules USING btree (group_id, cron_expression) WHERE ((schedule_type)::text = 'group'::text); + + +-- +-- Name: idx_unique_peripheral_schedule; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE UNIQUE INDEX idx_unique_peripheral_schedule ON public.schedules USING btree (peripheral_id, cron_expression) WHERE ((schedule_type)::text = 'peripheral'::text); + + +-- +-- Name: idx_user_tokens_active_socket; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE INDEX idx_user_tokens_active_socket ON public.user_tokens USING btree (user_id, socket) WHERE (connected = true); + + +-- +-- Name: idx_user_tokens_socket_connected; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE INDEX idx_user_tokens_socket_connected ON public.user_tokens USING btree (socket) WHERE (connected = true); + + +-- +-- Name: idx_user_tokens_token; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE INDEX idx_user_tokens_token ON public.user_tokens USING btree (token); + + +-- +-- Name: users_id_idx; Type: INDEX; Schema: public; Owner: BlindMasterApp +-- + +CREATE UNIQUE INDEX users_id_idx ON public.users USING btree (id); + + +-- +-- Name: group_peripherals trigger_cleanup_empty_groups; Type: TRIGGER; Schema: public; Owner: BlindMasterApp +-- + +CREATE TRIGGER trigger_cleanup_empty_groups AFTER DELETE ON public.group_peripherals FOR EACH ROW EXECUTE FUNCTION public.delete_group_if_empty(); + + +-- +-- Name: peripherals fk_device; Type: FK CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.peripherals + ADD CONSTRAINT fk_device FOREIGN KEY (device_id) REFERENCES public.devices(id) ON DELETE CASCADE; + + +-- +-- Name: peripherals fk_user; Type: FK CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.peripherals + ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: group_peripherals group_peripherals_group_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.group_peripherals + ADD CONSTRAINT group_peripherals_group_id_fkey FOREIGN KEY (group_id) REFERENCES public.groups(id) ON DELETE CASCADE; + + +-- +-- Name: group_peripherals group_peripherals_peripheral_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.group_peripherals + ADD CONSTRAINT group_peripherals_peripheral_id_fkey FOREIGN KEY (peripheral_id) REFERENCES public.peripherals(id) ON DELETE CASCADE; + + +-- +-- Name: groups groups_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.groups + ADD CONSTRAINT groups_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: schedules schedules_device_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.schedules + ADD CONSTRAINT schedules_device_id_fkey FOREIGN KEY (device_id) REFERENCES public.devices(id) ON DELETE CASCADE; + + +-- +-- Name: schedules schedules_group_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.schedules + ADD CONSTRAINT schedules_group_id_fkey FOREIGN KEY (group_id) REFERENCES public.groups(id) ON DELETE CASCADE; + + +-- +-- Name: schedules schedules_peripheral_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.schedules + ADD CONSTRAINT schedules_peripheral_id_fkey FOREIGN KEY (peripheral_id) REFERENCES public.peripherals(id) ON DELETE CASCADE; + + +-- +-- Name: schedules schedules_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: BlindMasterApp +-- + +ALTER TABLE ONLY public.schedules + ADD CONSTRAINT schedules_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- PostgreSQL database dump complete +-- +