Skip to content

Database Schema

EcoCtrl persists everything in a single PostgreSQL database. The schema is defined with Drizzle ORM under packages/server/src/schemas/, one file per table.

Workflow

CommandPurpose
pnpm db:generateDiff the current schema against the previous one and emit a new migration in packages/server/drizzle/.
pnpm db:migrateApply pending migrations.
pnpm db:pushPush the current schema directly (dev shortcut — no migration history).
pnpm db:seedInsert sample users, dashboards and energy data.
pnpm db:refreshDrop → push → seed → open Drizzle Studio. Destructive.
pnpm db:studioOpen Drizzle Studio against the configured database.

Schemas re-export from schemas/index.ts, so any new table only needs to be added once there.

Tables

Identity & sessions

users

ColumnTypeNotes
iduuid PKGenerated server-side.
usernamevarchar(255)Unique-ish — also used as login identifier.
passwordvarchar(255)bcrypt hash, nullable for OAuth-only accounts.
emailvarchar(255)Required, used for verification codes.
rolevarchar(100)Defaults to the lowest role from USER_ROLE_LIST.
statusvarchar(20)online / offline.
lastLoginvarchar(50)Free-form timestamp string.
avatarUrlvarchar(500)Nullable.
preferencesjsonbUI preferences blob.
createdAttimestamptzdefaultNow().

oauth_accounts

Links a users.id to one or more external providers (WeChat, Feishu).

ColumnTypeNotes
iduuid PK
userIduuid FK → users(id)ON DELETE CASCADE.
providervarchar(50)wechat, feishu.
providerUserIdvarchar(255)The provider's stable user id.
providerEmailvarchar(255)Optional.
accessTokenvarchar(1000)Provider-issued.
refreshTokenvarchar(1000)Provider-issued.
expiresAttimestamptzProvider-issued expiry.

refresh_tokens

ColumnTypeNotes
iduuid PK
userIduuid FK → users(id)ON DELETE CASCADE.
tokenHashvarchar(255)sha256 of the issued refresh token.
expiresAttimestamptz7 days from issuance.
createdAttimestamptz

Each successful login deletes the user's previous refresh tokens before inserting the new one — this is the mechanism behind single-device sessions.

user_settings

Per-user dashboard layout preferences stored as a single jsonb blob, keyed by userId.

IoT integration

iot_tokens

Single-row token cache for the upstream IoT gateway.

ColumnTypeNotes
idserial PK
accessTokentext
refreshTokentext
expiresAtbigintAbsolute expiry as ms-epoch.

Operational data

alerts

Real-time event log surfaced on the dashboard. Columns: id, device, level, message, time, status (pending / acknowledged).

faults

Persistent fault records. Columns: id, device, level, time, status, createdAt.

fault_stats

Single-row snapshot of fault metrics: totalCount, trend, mttr, avgResponseTime, snapshotAt.

maintenance_reminders

Maintenance task queue. Columns: id, task, description, dueDate, priority, status, assignee, location, estimatedHours, lastCompleted.

energy_readings

Hourly kWh readings. Columns: id, hour (string label like 09:00), kWh (real), readingAt.

energy_areas

Per-area energy summary cards. Columns: id, title, current, target, color, powerFactor, loadRate.

Dashboard configuration

dashboard_stats

Numeric KPI cards on the admin dashboard. Columns: id, key, value, unit, trend, trendType, snapshotAt.

dashboard_widgets

Drag-and-drop widget grid. Columns include layout metrics (layoutX/Y/W/H), dataType, a freeform dataJson blob, enabled, hidden, sortOrder.

three_d_configs

3D scene configuration consumed by apps/web's Babylon scene. Stores cameraPreset, ambientLightIntensity, and JSON arrays of hotspots and labels.

Reports & backups

report_plans, report_templates

Scheduled report jobs and their templates.

backup_schedules

Single-row record holding nextBackup (timestamp string).

Platform & files

platform_configs

Single-row global config: platform name, refresh interval, alert toggles, timezone, backup retention, session timeout, and SMTP credentials. syncSmtpFromEnv() updates this row from environment variables on every boot.

models

Uploaded 3D model metadata. fileUrl points at /static/models/<filename>.

files

Generic upload metadata: name, mime type, size, fileUrl.

Adding a new table

  1. Create packages/server/src/schemas/<name>.ts — export the pgTable(...) definition.
  2. Re-export it from schemas/index.ts.
  3. Run pnpm db:generate and review the generated SQL migration.
  4. Apply it with pnpm db:migrate (or pnpm db:push in dev).
  5. Add a repository module under repositories/<name>.ts exposing createXxx, findXxx, etc.
  6. Wire routes that need it through the repository.

Never call Drizzle directly from a route — every read/write goes through the repository layer so the routes stay focused on validation and response shaping.

Released under the MIT License.