Commit Graph

3 Commits

Author SHA1 Message Date
mAi
f9c245fbcc fix(db): cascade-delete cables when a port is removed
The schema has ON DELETE SET NULL on cables.from_port_id /
cables.to_port_id, but the cables CHECK constraint requires exactly one
of (port/device/io) to be non-null per side. Setting both refs to NULL
on a port-delete violates the CHECK, blowing up the DELETE with a 500.

DeletePort now opens a tx, deletes any cable that referenced the port
on either side, then deletes the port. Same observable effect from m's
POV: cables that point at a deleted port are gone (he can re-draw with
the manual cable tool if he still wants them).
2026-05-16 01:18:55 +02:00
mAi
c61bff7cf2 feat(backend): ports CRUD endpoints for slice 7
New store methods on internal/db/ports.go:
- CreatePort / GetPort / UpdatePort / DeletePort (all project-scoped)
- ListPortsForDevice for the inspector's per-device list

New handlers (internal/server/ports.go):
- GET    /api/projects/:pid/devices/:id/ports
- POST   /api/projects/:pid/devices/:id/ports  ← {type_id, label?, x_offset, y_offset}
- PATCH  /api/projects/:pid/ports/:id           ← partial
- DELETE /api/projects/:pid/ports/:id          (cables ref → ON DELETE SET NULL)

Lets slice 7's +Port tool add/remove instance ports without going
through the type-seeded auto-creation path from slice 4.
2026-05-16 01:10:59 +02:00
mAi
8cb237fe8e feat(db): device_types store + port seeding on device create
Catalog: 11 built-ins from §2.2 + the v4.1 trio (Screen, Keyboard, Mouse)
seeded in migration 002, totalling 16 built-in types.

Store layer:
- internal/db/device_types.go — CRUD for device_types. Built-ins
  (project_id NULL) reject PATCH/DELETE with new ErrForbidden sentinel
  (handler maps to HTTP 403). Project-custom types accept full CRUD;
  cross-project access returns ErrNotFound. Replacing the port profile
  on UPDATE is one transaction.
- internal/db/ports.go — ListPortsForProject for the snapshot loader +
  seedPortsFromType(tx, …) used by CreateDevice. Layout is "evenly spaced
  along the configured edge", per-edge group ordering by sort_order +
  id. Labels are "<prefix>" for count==1 and "<prefix> N" 1-indexed for
  count>1.
- Device gains a nullable TypeID + tri-state on UpdateDevice. CreateDevice
  validates the type is built-in or a project-custom row of the same
  project, then seeds the device's ports in the same transaction.

Snapshot now populates Ports from the store; field type tightened to
[]Port.

Tests (15 new, all green with -race):
- 16 built-ins seeded with correct names + project_id=NULL + built_in=1
- Port-profile totals match the §2.2 table for every built-in type
- Project-custom create + name-collision-with-built-in → 409 (ErrConflict)
- Per-project name UNIQUE — same custom name across projects is fine
- PATCH/DELETE built-in → ErrForbidden
- Cross-project custom PATCH → ErrNotFound
- CreateDevice with NAS type → 2 ports along bottom edge, evenly spaced,
  labels set
- CreateDevice with PC type → 5 ports incl. "USB 1" + "USB 2"
- CreateDevice without type_id → 0 ports (freeform fallback)
- Cross-project custom type on CreateDevice → ErrInvalidInput
- Snapshot includes the seeded ports
2026-05-16 00:27:49 +02:00