feat(db): @mosaic/db — Drizzle schema, PG connection, migrations (#67)
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #67.
This commit is contained in:
@@ -43,6 +43,18 @@ User confirmed: start the planning gate.
|
|||||||
| 2 | 2026-03-13 | Vertical slice | P1-001, P1-007, P1-008, P2-001, P5-002, P6-005 | Communication spine built and merged (PR #61). Gateway + TUI + Discord. 3-agent gatekeeper review, 10/16 issues remediated, 4 deferred. |
|
| 2 | 2026-03-13 | Vertical slice | P1-001, P1-007, P1-008, P2-001, P5-002, P6-005 | Communication spine built and merged (PR #61). Gateway + TUI + Discord. 3-agent gatekeeper review, 10/16 issues remediated, 4 deferred. |
|
||||||
| 3 | 2026-03-13 | Foundation | P0-002, P0-005, P0-006 | Foundation layer merged (PR #65). Docker Compose (PG+pgvector, Valkey, OTEL Collector, Jaeger), OTEL auto-instrumentation in gateway, @mosaic/types with DTOs + Socket.IO typed event maps. |
|
| 3 | 2026-03-13 | Foundation | P0-002, P0-005, P0-006 | Foundation layer merged (PR #65). Docker Compose (PG+pgvector, Valkey, OTEL Collector, Jaeger), OTEL auto-instrumentation in gateway, @mosaic/types with DTOs + Socket.IO typed event maps. |
|
||||||
|
|
||||||
|
### Session 4 — Docker Compose fix
|
||||||
|
|
||||||
|
| Session | Date | Milestone | Tasks Done | Outcome |
|
||||||
|
| ------- | ---------- | ---------- | ---------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| 4 | 2026-03-12 | Foundation | (fix) | Fixed Jaeger tag (2→2.6.0), remapped PG/Valkey ports (5433/6380) to avoid host conflicts. PR #66 merged to main. |
|
||||||
|
|
||||||
|
**Verification evidence:**
|
||||||
|
|
||||||
|
- All 4 containers healthy (PG, Valkey, OTEL Collector, Jaeger)
|
||||||
|
- OTEL pipeline proven: `mosaic-gateway` service visible in Jaeger UI
|
||||||
|
- Gateway traces flow through Collector → Jaeger
|
||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
(none at this time)
|
(none at this time)
|
||||||
|
|||||||
@@ -4,7 +4,13 @@ import tsParser from '@typescript-eslint/parser';
|
|||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
{
|
{
|
||||||
ignores: ['**/dist/**', '**/node_modules/**', '**/.next/**', '**/coverage/**'],
|
ignores: [
|
||||||
|
'**/dist/**',
|
||||||
|
'**/node_modules/**',
|
||||||
|
'**/.next/**',
|
||||||
|
'**/coverage/**',
|
||||||
|
'**/drizzle.config.ts',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.{ts,tsx}'],
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
|||||||
10
packages/db/drizzle.config.ts
Normal file
10
packages/db/drizzle.config.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { defineConfig } from 'drizzle-kit';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: './src/schema.ts',
|
||||||
|
out: './drizzle',
|
||||||
|
dialect: 'postgresql',
|
||||||
|
dbCredentials: {
|
||||||
|
url: process.env['DATABASE_URL'] ?? 'postgresql://mosaic:mosaic@localhost:5433/mosaic',
|
||||||
|
},
|
||||||
|
});
|
||||||
166
packages/db/drizzle/0000_loud_ezekiel_stane.sql
Normal file
166
packages/db/drizzle/0000_loud_ezekiel_stane.sql
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
CREATE TABLE "accounts" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"account_id" text NOT NULL,
|
||||||
|
"provider_id" text NOT NULL,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"access_token" text,
|
||||||
|
"refresh_token" text,
|
||||||
|
"id_token" text,
|
||||||
|
"access_token_expires_at" timestamp with time zone,
|
||||||
|
"refresh_token_expires_at" timestamp with time zone,
|
||||||
|
"scope" text,
|
||||||
|
"password" text,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "agents" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"provider" text NOT NULL,
|
||||||
|
"model" text NOT NULL,
|
||||||
|
"status" text DEFAULT 'idle' NOT NULL,
|
||||||
|
"config" jsonb,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "appreciations" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"from_user" text,
|
||||||
|
"to_user" text,
|
||||||
|
"message" text NOT NULL,
|
||||||
|
"metadata" jsonb,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "conversations" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"title" text,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"project_id" uuid,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "events" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"type" text NOT NULL,
|
||||||
|
"title" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"date" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"metadata" jsonb,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "messages" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"conversation_id" uuid NOT NULL,
|
||||||
|
"role" text NOT NULL,
|
||||||
|
"content" text NOT NULL,
|
||||||
|
"metadata" jsonb,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "missions" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"status" text DEFAULT 'planning' NOT NULL,
|
||||||
|
"project_id" uuid,
|
||||||
|
"metadata" jsonb,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "projects" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"status" text DEFAULT 'active' NOT NULL,
|
||||||
|
"owner_id" text,
|
||||||
|
"metadata" jsonb,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "sessions" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"expires_at" timestamp with time zone NOT NULL,
|
||||||
|
"token" text NOT NULL,
|
||||||
|
"ip_address" text,
|
||||||
|
"user_agent" text,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "sessions_token_unique" UNIQUE("token")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "tasks" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"title" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"status" text DEFAULT 'not-started' NOT NULL,
|
||||||
|
"priority" text DEFAULT 'medium' NOT NULL,
|
||||||
|
"project_id" uuid,
|
||||||
|
"mission_id" uuid,
|
||||||
|
"assignee" text,
|
||||||
|
"tags" jsonb,
|
||||||
|
"due_date" timestamp with time zone,
|
||||||
|
"metadata" jsonb,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "tickets" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"title" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"status" text DEFAULT 'open' NOT NULL,
|
||||||
|
"priority" text DEFAULT 'medium' NOT NULL,
|
||||||
|
"source" text,
|
||||||
|
"metadata" jsonb,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "users" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"email" text NOT NULL,
|
||||||
|
"email_verified" boolean DEFAULT false NOT NULL,
|
||||||
|
"image" text,
|
||||||
|
"role" text DEFAULT 'member' NOT NULL,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "users_email_unique" UNIQUE("email")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "verifications" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"identifier" text NOT NULL,
|
||||||
|
"value" text NOT NULL,
|
||||||
|
"expires_at" timestamp with time zone NOT NULL,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "conversations" ADD CONSTRAINT "conversations_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "conversations" ADD CONSTRAINT "conversations_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "messages" ADD CONSTRAINT "messages_conversation_id_conversations_id_fk" FOREIGN KEY ("conversation_id") REFERENCES "public"."conversations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "missions" ADD CONSTRAINT "missions_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "projects" ADD CONSTRAINT "projects_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_mission_id_missions_id_fk" FOREIGN KEY ("mission_id") REFERENCES "public"."missions"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
CREATE INDEX "conversations_user_id_idx" ON "conversations" USING btree ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "conversations_project_id_idx" ON "conversations" USING btree ("project_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "events_type_idx" ON "events" USING btree ("type");--> statement-breakpoint
|
||||||
|
CREATE INDEX "events_date_idx" ON "events" USING btree ("date");--> statement-breakpoint
|
||||||
|
CREATE INDEX "messages_conversation_id_idx" ON "messages" USING btree ("conversation_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "missions_project_id_idx" ON "missions" USING btree ("project_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "tasks_project_id_idx" ON "tasks" USING btree ("project_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "tasks_mission_id_idx" ON "tasks" USING btree ("mission_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "tasks_status_idx" ON "tasks" USING btree ("status");--> statement-breakpoint
|
||||||
|
CREATE INDEX "tickets_status_idx" ON "tickets" USING btree ("status");
|
||||||
1122
packages/db/drizzle/meta/0000_snapshot.json
Normal file
1122
packages/db/drizzle/meta/0000_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
13
packages/db/drizzle/meta/_journal.json
Normal file
13
packages/db/drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1773368153122,
|
||||||
|
"tag": "0000_loud_ezekiel_stane",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@mosaic/db",
|
"name": "@mosaic/db",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -13,10 +14,21 @@
|
|||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"test": "vitest run"
|
"test": "vitest run",
|
||||||
|
"db:generate": "drizzle-kit generate",
|
||||||
|
"db:migrate": "drizzle-kit migrate",
|
||||||
|
"db:push": "drizzle-kit push",
|
||||||
|
"db:studio": "drizzle-kit studio"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"drizzle-kit": "^0.31.9",
|
||||||
|
"tsx": "^4.0.0",
|
||||||
"typescript": "^5.8.0",
|
"typescript": "^5.8.0",
|
||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"drizzle-orm": "^0.45.1",
|
||||||
|
"postgres": "^3.4.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
packages/db/src/client.ts
Normal file
18
packages/db/src/client.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
||||||
|
import postgres from 'postgres';
|
||||||
|
import * as schema from './schema.js';
|
||||||
|
import { DEFAULT_DATABASE_URL } from './defaults.js';
|
||||||
|
|
||||||
|
export type Db = PostgresJsDatabase<typeof schema>;
|
||||||
|
|
||||||
|
export interface DbHandle {
|
||||||
|
db: Db;
|
||||||
|
close: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDb(url?: string): DbHandle {
|
||||||
|
const connectionString = url ?? process.env['DATABASE_URL'] ?? DEFAULT_DATABASE_URL;
|
||||||
|
const sql = postgres(connectionString);
|
||||||
|
const db = drizzle(sql, { schema });
|
||||||
|
return { db, close: () => sql.end() };
|
||||||
|
}
|
||||||
1
packages/db/src/defaults.ts
Normal file
1
packages/db/src/defaults.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const DEFAULT_DATABASE_URL = 'postgresql://mosaic:mosaic@localhost:5433/mosaic';
|
||||||
@@ -1 +1,3 @@
|
|||||||
export const VERSION = '0.0.0';
|
export { createDb, type Db, type DbHandle } from './client.js';
|
||||||
|
export { runMigrations } from './migrate.js';
|
||||||
|
export * from './schema.js';
|
||||||
|
|||||||
18
packages/db/src/migrate.ts
Normal file
18
packages/db/src/migrate.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { dirname, resolve } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||||
|
import { migrate } from 'drizzle-orm/postgres-js/migrator';
|
||||||
|
import postgres from 'postgres';
|
||||||
|
import { DEFAULT_DATABASE_URL } from './defaults.js';
|
||||||
|
|
||||||
|
export async function runMigrations(url?: string): Promise<void> {
|
||||||
|
const connectionString = url ?? process.env['DATABASE_URL'] ?? DEFAULT_DATABASE_URL;
|
||||||
|
const sql = postgres(connectionString, { max: 1 });
|
||||||
|
const db = drizzle(sql);
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
try {
|
||||||
|
await migrate(db, { migrationsFolder: resolve(__dirname, '../drizzle') });
|
||||||
|
} finally {
|
||||||
|
await sql.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
213
packages/db/src/schema.ts
Normal file
213
packages/db/src/schema.ts
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
/**
|
||||||
|
* Unified schema file — all tables defined here.
|
||||||
|
* drizzle-kit reads this file directly (avoids CJS/ESM extension issues).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { pgTable, text, timestamp, boolean, uuid, jsonb, index } from 'drizzle-orm/pg-core';
|
||||||
|
|
||||||
|
// ─── Auth (BetterAuth-compatible) ────────────────────────────────────────────
|
||||||
|
|
||||||
|
export const users = pgTable('users', {
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
name: text('name').notNull(),
|
||||||
|
email: text('email').notNull().unique(),
|
||||||
|
emailVerified: boolean('email_verified').notNull().default(false),
|
||||||
|
image: text('image'),
|
||||||
|
role: text('role').notNull().default('member'),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const sessions = pgTable('sessions', {
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
||||||
|
token: text('token').notNull().unique(),
|
||||||
|
ipAddress: text('ip_address'),
|
||||||
|
userAgent: text('user_agent'),
|
||||||
|
userId: text('user_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id, { onDelete: 'cascade' }),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const accounts = pgTable('accounts', {
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
accountId: text('account_id').notNull(),
|
||||||
|
providerId: text('provider_id').notNull(),
|
||||||
|
userId: text('user_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id, { onDelete: 'cascade' }),
|
||||||
|
accessToken: text('access_token'),
|
||||||
|
refreshToken: text('refresh_token'),
|
||||||
|
idToken: text('id_token'),
|
||||||
|
accessTokenExpiresAt: timestamp('access_token_expires_at', { withTimezone: true }),
|
||||||
|
refreshTokenExpiresAt: timestamp('refresh_token_expires_at', { withTimezone: true }),
|
||||||
|
scope: text('scope'),
|
||||||
|
password: text('password'),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const verifications = pgTable('verifications', {
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
identifier: text('identifier').notNull(),
|
||||||
|
value: text('value').notNull(),
|
||||||
|
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Brain ───────────────────────────────────────────────────────────────────
|
||||||
|
// Declared before Chat because conversations references projects.
|
||||||
|
|
||||||
|
export const projects = pgTable('projects', {
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
name: text('name').notNull(),
|
||||||
|
description: text('description'),
|
||||||
|
status: text('status', { enum: ['active', 'paused', 'completed', 'archived'] })
|
||||||
|
.notNull()
|
||||||
|
.default('active'),
|
||||||
|
ownerId: text('owner_id').references(() => users.id, { onDelete: 'set null' }),
|
||||||
|
metadata: jsonb('metadata'),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const missions = pgTable(
|
||||||
|
'missions',
|
||||||
|
{
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
name: text('name').notNull(),
|
||||||
|
description: text('description'),
|
||||||
|
status: text('status', { enum: ['planning', 'active', 'paused', 'completed', 'failed'] })
|
||||||
|
.notNull()
|
||||||
|
.default('planning'),
|
||||||
|
projectId: uuid('project_id').references(() => projects.id, { onDelete: 'set null' }),
|
||||||
|
metadata: jsonb('metadata'),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
},
|
||||||
|
(t) => [index('missions_project_id_idx').on(t.projectId)],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const tasks = pgTable(
|
||||||
|
'tasks',
|
||||||
|
{
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
title: text('title').notNull(),
|
||||||
|
description: text('description'),
|
||||||
|
status: text('status', {
|
||||||
|
enum: ['not-started', 'in-progress', 'blocked', 'done', 'cancelled'],
|
||||||
|
})
|
||||||
|
.notNull()
|
||||||
|
.default('not-started'),
|
||||||
|
priority: text('priority', { enum: ['critical', 'high', 'medium', 'low'] })
|
||||||
|
.notNull()
|
||||||
|
.default('medium'),
|
||||||
|
projectId: uuid('project_id').references(() => projects.id, { onDelete: 'set null' }),
|
||||||
|
missionId: uuid('mission_id').references(() => missions.id, { onDelete: 'set null' }),
|
||||||
|
assignee: text('assignee'),
|
||||||
|
tags: jsonb('tags').$type<string[]>(),
|
||||||
|
dueDate: timestamp('due_date', { withTimezone: true }),
|
||||||
|
metadata: jsonb('metadata'),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
},
|
||||||
|
(t) => [
|
||||||
|
index('tasks_project_id_idx').on(t.projectId),
|
||||||
|
index('tasks_mission_id_idx').on(t.missionId),
|
||||||
|
index('tasks_status_idx').on(t.status),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const events = pgTable(
|
||||||
|
'events',
|
||||||
|
{
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
type: text('type').notNull(),
|
||||||
|
title: text('title').notNull(),
|
||||||
|
description: text('description'),
|
||||||
|
date: timestamp('date', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
metadata: jsonb('metadata'),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
},
|
||||||
|
(t) => [index('events_type_idx').on(t.type), index('events_date_idx').on(t.date)],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const agents = pgTable('agents', {
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
name: text('name').notNull(),
|
||||||
|
provider: text('provider').notNull(),
|
||||||
|
model: text('model').notNull(),
|
||||||
|
status: text('status', { enum: ['idle', 'active', 'error', 'offline'] })
|
||||||
|
.notNull()
|
||||||
|
.default('idle'),
|
||||||
|
config: jsonb('config'),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tickets = pgTable(
|
||||||
|
'tickets',
|
||||||
|
{
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
title: text('title').notNull(),
|
||||||
|
description: text('description'),
|
||||||
|
status: text('status', { enum: ['open', 'in-progress', 'resolved', 'closed'] })
|
||||||
|
.notNull()
|
||||||
|
.default('open'),
|
||||||
|
priority: text('priority', { enum: ['critical', 'high', 'medium', 'low'] })
|
||||||
|
.notNull()
|
||||||
|
.default('medium'),
|
||||||
|
source: text('source'),
|
||||||
|
metadata: jsonb('metadata'),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
},
|
||||||
|
(t) => [index('tickets_status_idx').on(t.status)],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const appreciations = pgTable('appreciations', {
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
fromUser: text('from_user'),
|
||||||
|
toUser: text('to_user'),
|
||||||
|
message: text('message').notNull(),
|
||||||
|
metadata: jsonb('metadata'),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Chat ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export const conversations = pgTable(
|
||||||
|
'conversations',
|
||||||
|
{
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
title: text('title'),
|
||||||
|
userId: text('user_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id, { onDelete: 'cascade' }),
|
||||||
|
projectId: uuid('project_id').references(() => projects.id, { onDelete: 'set null' }),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
},
|
||||||
|
(t) => [
|
||||||
|
index('conversations_user_id_idx').on(t.userId),
|
||||||
|
index('conversations_project_id_idx').on(t.projectId),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const messages = pgTable(
|
||||||
|
'messages',
|
||||||
|
{
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
conversationId: uuid('conversation_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => conversations.id, { onDelete: 'cascade' }),
|
||||||
|
role: text('role', { enum: ['user', 'assistant', 'system'] }).notNull(),
|
||||||
|
content: text('content').notNull(),
|
||||||
|
metadata: jsonb('metadata'),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||||
|
},
|
||||||
|
(t) => [index('messages_conversation_id_idx').on(t.conversationId)],
|
||||||
|
);
|
||||||
857
pnpm-lock.yaml
generated
857
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user