164 lines
5.5 KiB
TypeScript
164 lines
5.5 KiB
TypeScript
import { pgTable, text, timestamp, uuid, boolean, jsonb, integer } from 'drizzle-orm/pg-core';
|
|
import { relations } from 'drizzle-orm';
|
|
|
|
// Users table (managed by BetterAuth - uses text IDs)
|
|
export const users = pgTable('users', {
|
|
id: text('id').primaryKey(),
|
|
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: text('id').primaryKey(),
|
|
userId: text('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: text('id').primaryKey(),
|
|
userId: text('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: text('id').primaryKey(),
|
|
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: text('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: text('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: text('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],
|
|
}),
|
|
}));
|