feat(db): add phase1 device role, links, and commands schema

This commit is contained in:
2025-12-21 15:50:00 +00:00
parent cdaab7f0c1
commit 4fa525d8db
4 changed files with 107 additions and 17 deletions

View File

@@ -1,4 +1,4 @@
import { pgTable, timestamp, uuid, varchar, text, boolean } from 'drizzle-orm/pg-core'; import { pgTable, timestamp, uuid, varchar, text, boolean, integer, jsonb, unique } from 'drizzle-orm/pg-core';
export const users = pgTable('users', { export const users = pgTable('users', {
id: uuid('id').defaultRandom().primaryKey(), id: uuid('id').defaultRandom().primaryKey(),
@@ -15,11 +15,49 @@ export const devices = pgTable('devices', {
id: uuid('id').defaultRandom().primaryKey(), id: uuid('id').defaultRandom().primaryKey(),
userId: uuid('user_id').notNull().references(() => users.id), userId: uuid('user_id').notNull().references(() => users.id),
name: varchar('name', { length: 255 }), name: varchar('name', { length: 255 }),
role: varchar('role', { length: 32 }).default('client').notNull(),
platform: varchar('platform', { length: 32 }),
appVersion: varchar('app_version', { length: 64 }),
pushToken: text('push_token'),
status: varchar('status', { length: 32 }).default('offline').notNull(),
isCamera: boolean('is_camera').default(false).notNull(), isCamera: boolean('is_camera').default(false).notNull(),
lastSeenAt: timestamp('last_seen_at', { withTimezone: true }), lastSeenAt: timestamp('last_seen_at', { withTimezone: true }),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
}); });
export const deviceLinks = pgTable(
'device_links',
{
id: uuid('id').defaultRandom().primaryKey(),
ownerUserId: uuid('owner_user_id').notNull().references(() => users.id),
cameraDeviceId: uuid('camera_device_id').notNull().references(() => devices.id),
clientDeviceId: uuid('client_device_id').notNull().references(() => devices.id),
status: varchar('status', { length: 32 }).default('active').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
},
(table) => ({
uniqueDevicePair: unique('device_links_camera_client_unique').on(table.cameraDeviceId, table.clientDeviceId),
}),
);
export const deviceCommands = pgTable('device_commands', {
id: uuid('id').defaultRandom().primaryKey(),
ownerUserId: uuid('owner_user_id').notNull().references(() => users.id),
sourceDeviceId: uuid('source_device_id').notNull().references(() => devices.id),
targetDeviceId: uuid('target_device_id').notNull().references(() => devices.id),
commandType: varchar('command_type', { length: 64 }).notNull(),
payload: jsonb('payload').$type<Record<string, unknown> | null>().default(null),
status: varchar('status', { length: 32 }).default('queued').notNull(),
retryCount: integer('retry_count').default(0).notNull(),
lastDispatchedAt: timestamp('last_dispatched_at', { withTimezone: true }),
acknowledgedAt: timestamp('acknowledged_at', { withTimezone: true }),
error: text('error'),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
});
export const events = pgTable('events', { export const events = pgTable('events', {
id: uuid('id').defaultRandom().primaryKey(), id: uuid('id').defaultRandom().primaryKey(),
userId: uuid('user_id').notNull().references(() => users.id), userId: uuid('user_id').notNull().references(() => users.id),
@@ -99,6 +137,8 @@ export const verifications = pgTable('verification', {
export const schema = { export const schema = {
users, users,
devices, devices,
deviceLinks,
deviceCommands,
events, events,
videos, videos,
notifications, notifications,

View File

@@ -405,8 +405,7 @@ registry.registerPath({
export function buildOpenApiDocument() { export function buildOpenApiDocument() {
const generator = new OpenApiGeneratorV3(registry.definitions); const generator = new OpenApiGeneratorV3(registry.definitions);
const document = generator.generateDocument({
return generator.generateDocument({
openapi: '3.0.3', openapi: '3.0.3',
info: { info: {
title: 'Backend API', title: 'Backend API',
@@ -419,7 +418,10 @@ export function buildOpenApiDocument() {
{ name: 'Videos', description: 'Authenticated video object operations' }, { name: 'Videos', description: 'Authenticated video object operations' },
{ name: 'Admin', description: 'Basic-auth protected admin operations' }, { name: 'Admin', description: 'Basic-auth protected admin operations' },
], ],
components: { });
document.components = {
...(document.components ?? {}),
securitySchemes: { securitySchemes: {
cookieAuth: { cookieAuth: {
type: 'apiKey', type: 'apiKey',
@@ -432,6 +434,7 @@ export function buildOpenApiDocument() {
scheme: 'basic', scheme: 'basic',
}, },
}, },
}, };
});
return document;
} }

View File

@@ -0,0 +1,40 @@
ALTER TABLE "devices" ADD COLUMN "role" varchar(32) DEFAULT 'client' NOT NULL;--> statement-breakpoint
ALTER TABLE "devices" ADD COLUMN "platform" varchar(32);--> statement-breakpoint
ALTER TABLE "devices" ADD COLUMN "app_version" varchar(64);--> statement-breakpoint
ALTER TABLE "devices" ADD COLUMN "push_token" text;--> statement-breakpoint
ALTER TABLE "devices" ADD COLUMN "status" varchar(32) DEFAULT 'offline' NOT NULL;--> statement-breakpoint
ALTER TABLE "devices" ADD COLUMN "updated_at" timestamp with time zone DEFAULT now() NOT NULL;--> statement-breakpoint
UPDATE "devices" SET "role" = CASE WHEN "is_camera" THEN 'camera' ELSE 'client' END;--> statement-breakpoint
CREATE TABLE "device_links" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"owner_user_id" uuid NOT NULL,
"camera_device_id" uuid NOT NULL,
"client_device_id" uuid NOT NULL,
"status" varchar(32) DEFAULT 'active' NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "device_links_camera_client_unique" UNIQUE("camera_device_id","client_device_id")
);
--> statement-breakpoint
CREATE TABLE "device_commands" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"owner_user_id" uuid NOT NULL,
"source_device_id" uuid NOT NULL,
"target_device_id" uuid NOT NULL,
"command_type" varchar(64) NOT NULL,
"payload" jsonb DEFAULT 'null'::jsonb,
"status" varchar(32) DEFAULT 'queued' NOT NULL,
"retry_count" integer DEFAULT 0 NOT NULL,
"last_dispatched_at" timestamp with time zone,
"acknowledged_at" timestamp with time zone,
"error" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "device_links" ADD CONSTRAINT "device_links_owner_user_id_users_id_fk" FOREIGN KEY ("owner_user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "device_links" ADD CONSTRAINT "device_links_camera_device_id_devices_id_fk" FOREIGN KEY ("camera_device_id") REFERENCES "public"."devices"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "device_links" ADD CONSTRAINT "device_links_client_device_id_devices_id_fk" FOREIGN KEY ("client_device_id") REFERENCES "public"."devices"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "device_commands" ADD CONSTRAINT "device_commands_owner_user_id_users_id_fk" FOREIGN KEY ("owner_user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "device_commands" ADD CONSTRAINT "device_commands_source_device_id_devices_id_fk" FOREIGN KEY ("source_device_id") REFERENCES "public"."devices"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "device_commands" ADD CONSTRAINT "device_commands_target_device_id_devices_id_fk" FOREIGN KEY ("target_device_id") REFERENCES "public"."devices"("id") ON DELETE no action ON UPDATE no action;

View File

@@ -43,6 +43,13 @@
"when": 1770412956419, "when": 1770412956419,
"tag": "0005_sudden_corsair", "tag": "0005_sudden_corsair",
"breakpoints": true "breakpoints": true
},
{
"idx": 6,
"version": "7",
"when": 1770413956419,
"tag": "0006_steady_control_plane",
"breakpoints": true
} }
] ]
} }