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_widgets

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

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.

Workflow engine

workflows

Workflow definitions stored as a JSON DSL.

ColumnTypeNotes
iduuid PK
namevarcharHuman-readable name
descriptiontextOptional
dsljsonbFull workflow DSL (nodes, edges, trigger)
enabledbooleanWhether the workflow is active
createdAttimestamptz
updatedAttimestamptz

workflow_executions

Execution logs for each workflow run.

ColumnTypeNotes
iduuid PK
workflowIduuid FK→ workflows(id)
statusvarcharcompleted / failed / running
triggerDatajsonbData that fired the trigger
resultjsonbFinal output and node logs
startedAttimestamptz
completedAttimestamptzNullable

IoT integration

objects

IoT object metadata — physical devices or data points from the upstream gateway.

ColumnTypeNotes
iduuid PK
codevarcharUnique upstream identifier
namevarcharHuman-readable name
typevarcharObject category
descriptiontextOptional
metadatajsonbAdditional upstream properties
createdAttimestamptz

Carbon tracking

carbon_factors

Emission factors for carbon calculations.

ColumnTypeNotes
iduuid PK
namevarcharFactor name
valuerealkg CO₂e per unit
unitvarchare.g. kWh,
categoryvarcharGrouping category
createdAttimestamptz

carbon_factor_nodes

Tree-structured nodes for organizing carbon factors.

ColumnTypeNotes
iduuid PK
parentIduuid FK→ carbon_factor_nodes(id), nullable
factorIduuid FK→ carbon_factors(id), nullable
namevarcharNode label
sortOrderintegerDisplay order

3D scene configuration

dashboard_models

Single-row 3D scene configuration for the web portal.

ColumnTypeNotes
idserial PK
modelFileUrlvarcharPath to the glTF/glB model
cameraPresetvarcharNamed camera angle
ambientLightIntensityreal0–1 ambient light level
hotspotsjsonbArray of
labelsjsonbArray of
updatedAttimestamptz

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.