feat(db): add phase1 device role, links, and commands schema
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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,19 +418,23 @@ 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: {
|
});
|
||||||
securitySchemes: {
|
|
||||||
cookieAuth: {
|
document.components = {
|
||||||
type: 'apiKey',
|
...(document.components ?? {}),
|
||||||
in: 'cookie',
|
securitySchemes: {
|
||||||
name: 'better-auth.session_token',
|
cookieAuth: {
|
||||||
description: 'Better Auth session cookie',
|
type: 'apiKey',
|
||||||
},
|
in: 'cookie',
|
||||||
basicAuth: {
|
name: 'better-auth.session_token',
|
||||||
type: 'http',
|
description: 'Better Auth session cookie',
|
||||||
scheme: 'basic',
|
},
|
||||||
},
|
basicAuth: {
|
||||||
|
type: 'http',
|
||||||
|
scheme: 'basic',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
|
return document;
|
||||||
}
|
}
|
||||||
|
|||||||
40
Backend/drizzle/0006_steady_control_plane.sql
Normal file
40
Backend/drizzle/0006_steady_control_plane.sql
Normal 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;
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user