Initial API scaffold: Elysia + Bun + Drizzle + BetterAuth + LangChain
This commit is contained in:
16
src/db/index.ts
Normal file
16
src/db/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import postgres from 'postgres';
|
||||
import * as schema from './schema';
|
||||
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
|
||||
if (!connectionString) {
|
||||
throw new Error('DATABASE_URL environment variable is required');
|
||||
}
|
||||
|
||||
// For query purposes
|
||||
const queryClient = postgres(connectionString);
|
||||
export const db = drizzle(queryClient, { schema });
|
||||
|
||||
// For migrations (uses a different client)
|
||||
export const migrationClient = postgres(connectionString, { max: 1 });
|
||||
163
src/db/schema.ts
Normal file
163
src/db/schema.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import { pgTable, text, timestamp, uuid, boolean, jsonb, integer } from 'drizzle-orm/pg-core';
|
||||
import { relations } from 'drizzle-orm';
|
||||
|
||||
// Users table (managed by BetterAuth, but we define it for relations)
|
||||
export const users = pgTable('users', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
email: text('email').notNull().unique(),
|
||||
name: text('name').notNull(),
|
||||
emailVerified: boolean('email_verified').default(false),
|
||||
image: text('image'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// BetterAuth session table
|
||||
export const sessions = pgTable('sessions', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
||||
token: text('token').notNull().unique(),
|
||||
expiresAt: timestamp('expires_at').notNull(),
|
||||
ipAddress: text('ip_address'),
|
||||
userAgent: text('user_agent'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// BetterAuth account table (for OAuth providers)
|
||||
export const accounts = pgTable('accounts', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
||||
accountId: text('account_id').notNull(),
|
||||
providerId: text('provider_id').notNull(),
|
||||
accessToken: text('access_token'),
|
||||
refreshToken: text('refresh_token'),
|
||||
accessTokenExpiresAt: timestamp('access_token_expires_at'),
|
||||
refreshTokenExpiresAt: timestamp('refresh_token_expires_at'),
|
||||
scope: text('scope'),
|
||||
idToken: text('id_token'),
|
||||
password: text('password'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// BetterAuth verification table
|
||||
export const verifications = pgTable('verifications', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
identifier: text('identifier').notNull(),
|
||||
value: text('value').notNull(),
|
||||
expiresAt: timestamp('expires_at').notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// Clients table
|
||||
export const clients = pgTable('clients', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
||||
|
||||
// Basic info
|
||||
firstName: text('first_name').notNull(),
|
||||
lastName: text('last_name').notNull(),
|
||||
email: text('email'),
|
||||
phone: text('phone'),
|
||||
|
||||
// Address
|
||||
street: text('street'),
|
||||
city: text('city'),
|
||||
state: text('state'),
|
||||
zip: text('zip'),
|
||||
|
||||
// Professional
|
||||
company: text('company'),
|
||||
role: text('role'),
|
||||
industry: text('industry'),
|
||||
|
||||
// Personal
|
||||
birthday: timestamp('birthday'),
|
||||
anniversary: timestamp('anniversary'),
|
||||
interests: jsonb('interests').$type<string[]>().default([]),
|
||||
family: jsonb('family').$type<{ spouse?: string; children?: string[] }>(),
|
||||
notes: text('notes'),
|
||||
|
||||
// Organization
|
||||
tags: jsonb('tags').$type<string[]>().default([]),
|
||||
|
||||
// Tracking
|
||||
lastContactedAt: timestamp('last_contacted_at'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// Events table (birthdays, anniversaries, follow-ups)
|
||||
export const events = pgTable('events', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
||||
clientId: uuid('client_id').references(() => clients.id, { onDelete: 'cascade' }).notNull(),
|
||||
|
||||
type: text('type').notNull(), // 'birthday' | 'anniversary' | 'followup' | 'custom'
|
||||
title: text('title').notNull(),
|
||||
date: timestamp('date').notNull(),
|
||||
recurring: boolean('recurring').default(false),
|
||||
reminderDays: integer('reminder_days').default(7),
|
||||
lastTriggered: timestamp('last_triggered'),
|
||||
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// Communications table (emails, messages)
|
||||
export const communications = pgTable('communications', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
||||
clientId: uuid('client_id').references(() => clients.id, { onDelete: 'cascade' }).notNull(),
|
||||
|
||||
type: text('type').notNull(), // 'email' | 'birthday' | 'followup'
|
||||
subject: text('subject'),
|
||||
content: text('content').notNull(),
|
||||
aiGenerated: boolean('ai_generated').default(false),
|
||||
aiModel: text('ai_model'), // Which model was used
|
||||
status: text('status').default('draft'), // 'draft' | 'approved' | 'sent'
|
||||
sentAt: timestamp('sent_at'),
|
||||
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// Relations
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
clients: many(clients),
|
||||
events: many(events),
|
||||
communications: many(communications),
|
||||
sessions: many(sessions),
|
||||
accounts: many(accounts),
|
||||
}));
|
||||
|
||||
export const clientsRelations = relations(clients, ({ one, many }) => ({
|
||||
user: one(users, {
|
||||
fields: [clients.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
events: many(events),
|
||||
communications: many(communications),
|
||||
}));
|
||||
|
||||
export const eventsRelations = relations(events, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [events.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
client: one(clients, {
|
||||
fields: [events.clientId],
|
||||
references: [clients.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const communicationsRelations = relations(communications, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [communications.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
client: one(clients, {
|
||||
fields: [communications.clientId],
|
||||
references: [clients.id],
|
||||
}),
|
||||
}));
|
||||
Reference in New Issue
Block a user