Migration 004:
- setup_templates + setup_template_devices + setup_template_requirements
- 3 built-in templates seeded: Living Room (TV+Soundbar+ChromeCast,
2× HDMI), Home Office (PC+Screen+Keyboard+Mouse, 1× HDMI + 2× USB),
Server Rack (NAS+Switch+fritz, 2× RJ45).
Cables store (cables.go):
- CRUD with endpoint validation (port|device|io exactly-one, project-
scoped). Tx-aware: validateEndpointEx + assertCableTypeEx avoid
deadlocks when the solver Apply tx holds the MaxOpenConns(1) connection.
Bundles store (bundles.go):
- CRUD with cable_ids replacement on PATCH. createBundle(ex, …, ownTx)
inherits the caller's tx for solver-internal use; returns a locally-
constructed Bundle when ownTx=false (re-fetching via s.db would
deadlock).
Solver (solver.go) implements design v4.1 §5b.2 exactly:
- Pre-fetch devices/ports/cables/requirements/bundles.
- Reserve ports used by manual cables (auto=0) so the solver can't
reuse them.
- For each requirement (must_connect DESC, id ASC):
* Resolve cable type: preferred, or T = port-types(from) ∩
port-types(to). |T|==0 → unsatisfied "no compat type"; |T|>1 →
"ambiguous"; |T|==1 → that one.
* Pick lowest-id free port on each side. None → unsatisfied with
WhichSide hint + cable-type name.
- Endpoint-pair bundle: ≥2 staged cables between the same device pair
→ auto bundle.
- Diff against existing auto cables by (type_id, MIN(from,to), MAX(from,to))
signature. Matched = kept; new = added; orphans = removed.
- Preview returns the diff without writing; Apply runs in a single tx
that wipes auto bundles, deletes orphan auto cables, inserts new
ones, and rebuilds bundles.
- PortsAndResolve: combo helper for the inspector quick-fix —
inserts a port + re-runs Solve.
Setup-templates store (setup_templates.go):
- List/Get with hydrated devices + requirements.
- ApplyTemplate(projectID, templateID, opts) seeds devices + requirements
in one tx. Per-device name overrides + opt-out. Name collisions skip
the device (skipped_devices); requirements whose endpoints both fail
are also skipped (requirements_skipped). UNIQUE-collision on an
existing requirement is non-fatal; logged in requirements_skipped.
Snapshot: cables + bundles fields tightened to []Cable / []Bundle and
populated from the store.
11 new tests (solver_test.go), all green with -race:
- Basic NAS↔Switch (RJ45) → 1 cable, auto=true
- Ambiguous cable type → unsatisfied
- No free port → unsatisfied with side hint
- Preview doesn't write
- Apply then re-apply → idempotent (kept=N, added=0)
- Manual cable reserves its port → solver can't claim it
- ApplyTemplate Living Room → 3 devices + 2 requirements + 7 ports
(from the device-type port seeder)
- Home Office template then Solve → 3 cables, 0 unsatisfied
- Name-collision pre-existing device → skipped + req-pair skipped
158 lines
8.9 KiB
SQL
158 lines
8.9 KiB
SQL
-- mCables v4.1 setup templates. See docs/design.md §2.4.
|
|
--
|
|
-- A template is a named recipe of (device_types + requirements) that
|
|
-- bootstraps a project from blank to solver-ready in one apply call.
|
|
|
|
CREATE TABLE setup_templates (
|
|
id INTEGER PRIMARY KEY,
|
|
name TEXT NOT NULL UNIQUE,
|
|
description TEXT NOT NULL DEFAULT '',
|
|
built_in INTEGER NOT NULL DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE setup_template_devices (
|
|
id INTEGER PRIMARY KEY,
|
|
template_id INTEGER NOT NULL REFERENCES setup_templates(id) ON DELETE CASCADE,
|
|
device_type_id INTEGER NOT NULL REFERENCES device_types(id) ON DELETE RESTRICT,
|
|
suggested_name TEXT,
|
|
sort_order INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
CREATE INDEX setup_template_devices_template_idx ON setup_template_devices(template_id);
|
|
|
|
CREATE TABLE setup_template_requirements (
|
|
id INTEGER PRIMARY KEY,
|
|
template_id INTEGER NOT NULL REFERENCES setup_templates(id) ON DELETE CASCADE,
|
|
from_template_device_id INTEGER NOT NULL REFERENCES setup_template_devices(id) ON DELETE CASCADE,
|
|
to_template_device_id INTEGER NOT NULL REFERENCES setup_template_devices(id) ON DELETE CASCADE,
|
|
preferred_cable_type_id INTEGER REFERENCES cable_types(id) ON DELETE SET NULL,
|
|
must_connect INTEGER NOT NULL DEFAULT 1 CHECK (must_connect IN (0, 1)),
|
|
CHECK (from_template_device_id != to_template_device_id)
|
|
);
|
|
CREATE INDEX setup_template_reqs_template_idx ON setup_template_requirements(template_id);
|
|
|
|
-- ---------------------------------------------------------------- Living Room
|
|
INSERT INTO setup_templates (name, description, built_in)
|
|
VALUES ('Living Room', 'TV + Soundbar + ChromeCast, HDMI between them.', 1);
|
|
|
|
INSERT INTO setup_template_devices (template_id, device_type_id, suggested_name, sort_order)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Living Room'),
|
|
(SELECT id FROM device_types WHERE name='TV' AND project_id IS NULL),
|
|
'TV', 0;
|
|
INSERT INTO setup_template_devices (template_id, device_type_id, suggested_name, sort_order)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Living Room'),
|
|
(SELECT id FROM device_types WHERE name='Soundbar' AND project_id IS NULL),
|
|
'Soundbar', 1;
|
|
INSERT INTO setup_template_devices (template_id, device_type_id, suggested_name, sort_order)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Living Room'),
|
|
(SELECT id FROM device_types WHERE name='ChromeCast' AND project_id IS NULL),
|
|
'ChromeCast', 2;
|
|
|
|
-- TV ↔ Soundbar (HDMI, must)
|
|
INSERT INTO setup_template_requirements
|
|
(template_id, from_template_device_id, to_template_device_id, preferred_cable_type_id, must_connect)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Living Room'),
|
|
(SELECT id FROM setup_template_devices WHERE template_id = (SELECT id FROM setup_templates WHERE name='Living Room') AND suggested_name='TV'),
|
|
(SELECT id FROM setup_template_devices WHERE template_id = (SELECT id FROM setup_templates WHERE name='Living Room') AND suggested_name='Soundbar'),
|
|
3, 1;
|
|
-- TV ↔ ChromeCast (HDMI, must)
|
|
INSERT INTO setup_template_requirements
|
|
(template_id, from_template_device_id, to_template_device_id, preferred_cable_type_id, must_connect)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Living Room'),
|
|
(SELECT id FROM setup_template_devices WHERE template_id = (SELECT id FROM setup_templates WHERE name='Living Room') AND suggested_name='TV'),
|
|
(SELECT id FROM setup_template_devices WHERE template_id = (SELECT id FROM setup_templates WHERE name='Living Room') AND suggested_name='ChromeCast'),
|
|
3, 1;
|
|
|
|
-- ---------------------------------------------------------------- Home Office
|
|
INSERT INTO setup_templates (name, description, built_in)
|
|
VALUES ('Home Office', 'PC + Screen + Keyboard + Mouse. HDMI + USB.', 1);
|
|
|
|
INSERT INTO setup_template_devices (template_id, device_type_id, suggested_name, sort_order)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Home Office'),
|
|
(SELECT id FROM device_types WHERE name='PC' AND project_id IS NULL),
|
|
'PC', 0;
|
|
INSERT INTO setup_template_devices (template_id, device_type_id, suggested_name, sort_order)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Home Office'),
|
|
(SELECT id FROM device_types WHERE name='Screen' AND project_id IS NULL),
|
|
'Screen', 1;
|
|
INSERT INTO setup_template_devices (template_id, device_type_id, suggested_name, sort_order)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Home Office'),
|
|
(SELECT id FROM device_types WHERE name='Keyboard' AND project_id IS NULL),
|
|
'Keyboard', 2;
|
|
INSERT INTO setup_template_devices (template_id, device_type_id, suggested_name, sort_order)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Home Office'),
|
|
(SELECT id FROM device_types WHERE name='Mouse' AND project_id IS NULL),
|
|
'Mouse', 3;
|
|
|
|
-- PC ↔ Screen (HDMI, must)
|
|
INSERT INTO setup_template_requirements
|
|
(template_id, from_template_device_id, to_template_device_id, preferred_cable_type_id, must_connect)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Home Office'),
|
|
(SELECT id FROM setup_template_devices WHERE template_id = (SELECT id FROM setup_templates WHERE name='Home Office') AND suggested_name='PC'),
|
|
(SELECT id FROM setup_template_devices WHERE template_id = (SELECT id FROM setup_templates WHERE name='Home Office') AND suggested_name='Screen'),
|
|
3, 1;
|
|
-- PC ↔ Keyboard (USB, must)
|
|
INSERT INTO setup_template_requirements
|
|
(template_id, from_template_device_id, to_template_device_id, preferred_cable_type_id, must_connect)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Home Office'),
|
|
(SELECT id FROM setup_template_devices WHERE template_id = (SELECT id FROM setup_templates WHERE name='Home Office') AND suggested_name='PC'),
|
|
(SELECT id FROM setup_template_devices WHERE template_id = (SELECT id FROM setup_templates WHERE name='Home Office') AND suggested_name='Keyboard'),
|
|
2, 1;
|
|
-- PC ↔ Mouse (USB, must)
|
|
INSERT INTO setup_template_requirements
|
|
(template_id, from_template_device_id, to_template_device_id, preferred_cable_type_id, must_connect)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Home Office'),
|
|
(SELECT id FROM setup_template_devices WHERE template_id = (SELECT id FROM setup_templates WHERE name='Home Office') AND suggested_name='PC'),
|
|
(SELECT id FROM setup_template_devices WHERE template_id = (SELECT id FROM setup_templates WHERE name='Home Office') AND suggested_name='Mouse'),
|
|
2, 1;
|
|
|
|
-- ---------------------------------------------------------------- Server Rack
|
|
INSERT INTO setup_templates (name, description, built_in)
|
|
VALUES ('Server Rack', 'NAS + Switch + fritz. Ethernet trunk + power.', 1);
|
|
|
|
INSERT INTO setup_template_devices (template_id, device_type_id, suggested_name, sort_order)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Server Rack'),
|
|
(SELECT id FROM device_types WHERE name='NAS' AND project_id IS NULL),
|
|
'NAS', 0;
|
|
INSERT INTO setup_template_devices (template_id, device_type_id, suggested_name, sort_order)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Server Rack'),
|
|
(SELECT id FROM device_types WHERE name='Switch' AND project_id IS NULL),
|
|
'Switch', 1;
|
|
INSERT INTO setup_template_devices (template_id, device_type_id, suggested_name, sort_order)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Server Rack'),
|
|
(SELECT id FROM device_types WHERE name='fritz' AND project_id IS NULL),
|
|
'fritz', 2;
|
|
|
|
-- NAS ↔ Switch (RJ45, must)
|
|
INSERT INTO setup_template_requirements
|
|
(template_id, from_template_device_id, to_template_device_id, preferred_cable_type_id, must_connect)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Server Rack'),
|
|
(SELECT id FROM setup_template_devices WHERE template_id = (SELECT id FROM setup_templates WHERE name='Server Rack') AND suggested_name='NAS'),
|
|
(SELECT id FROM setup_template_devices WHERE template_id = (SELECT id FROM setup_templates WHERE name='Server Rack') AND suggested_name='Switch'),
|
|
5, 1;
|
|
-- Switch ↔ fritz (RJ45, must)
|
|
INSERT INTO setup_template_requirements
|
|
(template_id, from_template_device_id, to_template_device_id, preferred_cable_type_id, must_connect)
|
|
SELECT
|
|
(SELECT id FROM setup_templates WHERE name='Server Rack'),
|
|
(SELECT id FROM setup_template_devices WHERE template_id = (SELECT id FROM setup_templates WHERE name='Server Rack') AND suggested_name='Switch'),
|
|
(SELECT id FROM setup_template_devices WHERE template_id = (SELECT id FROM setup_templates WHERE name='Server Rack') AND suggested_name='fritz'),
|
|
5, 1;
|