244 lines
9.4 KiB
TypeScript
244 lines
9.4 KiB
TypeScript
import {
|
|
index,
|
|
integer,
|
|
numeric,
|
|
sqliteTable,
|
|
text,
|
|
uniqueIndex
|
|
} from 'drizzle-orm/sqlite-core';
|
|
|
|
type FilingMetrics = {
|
|
revenue: number | null;
|
|
netIncome: number | null;
|
|
totalAssets: number | null;
|
|
cash: number | null;
|
|
debt: number | null;
|
|
};
|
|
|
|
type FilingAnalysis = {
|
|
provider?: string;
|
|
model?: string;
|
|
text?: string;
|
|
legacyInsights?: string;
|
|
};
|
|
|
|
const authDateColumn = {
|
|
mode: 'timestamp_ms'
|
|
} as const;
|
|
|
|
export const user = sqliteTable('user', {
|
|
id: text('id').primaryKey().notNull(),
|
|
name: text('name').notNull(),
|
|
email: text('email').notNull(),
|
|
emailVerified: integer('emailVerified', { mode: 'boolean' }).notNull().default(false),
|
|
image: text('image'),
|
|
createdAt: integer('createdAt', authDateColumn).notNull(),
|
|
updatedAt: integer('updatedAt', authDateColumn).notNull(),
|
|
role: text('role'),
|
|
banned: integer('banned', { mode: 'boolean' }).default(false),
|
|
banReason: text('banReason'),
|
|
banExpires: integer('banExpires', authDateColumn)
|
|
}, (table) => ({
|
|
userEmailUnique: uniqueIndex('user_email_uidx').on(table.email)
|
|
}));
|
|
|
|
export const organization = sqliteTable('organization', {
|
|
id: text('id').primaryKey().notNull(),
|
|
name: text('name').notNull(),
|
|
slug: text('slug').notNull(),
|
|
logo: text('logo'),
|
|
createdAt: integer('createdAt', authDateColumn).notNull(),
|
|
metadata: text('metadata')
|
|
}, (table) => ({
|
|
organizationSlugUnique: uniqueIndex('organization_slug_uidx').on(table.slug)
|
|
}));
|
|
|
|
export const session = sqliteTable('session', {
|
|
id: text('id').primaryKey().notNull(),
|
|
expiresAt: integer('expiresAt', authDateColumn).notNull(),
|
|
token: text('token').notNull(),
|
|
createdAt: integer('createdAt', authDateColumn).notNull(),
|
|
updatedAt: integer('updatedAt', authDateColumn).notNull(),
|
|
ipAddress: text('ipAddress'),
|
|
userAgent: text('userAgent'),
|
|
userId: text('userId').notNull().references(() => user.id, { onDelete: 'cascade' }),
|
|
impersonatedBy: text('impersonatedBy'),
|
|
activeOrganizationId: text('activeOrganizationId')
|
|
}, (table) => ({
|
|
sessionTokenUnique: uniqueIndex('session_token_uidx').on(table.token),
|
|
sessionUserIdIndex: index('session_userId_idx').on(table.userId)
|
|
}));
|
|
|
|
export const account = sqliteTable('account', {
|
|
id: text('id').primaryKey().notNull(),
|
|
accountId: text('accountId').notNull(),
|
|
providerId: text('providerId').notNull(),
|
|
userId: text('userId').notNull().references(() => user.id, { onDelete: 'cascade' }),
|
|
accessToken: text('accessToken'),
|
|
refreshToken: text('refreshToken'),
|
|
idToken: text('idToken'),
|
|
accessTokenExpiresAt: integer('accessTokenExpiresAt', authDateColumn),
|
|
refreshTokenExpiresAt: integer('refreshTokenExpiresAt', authDateColumn),
|
|
scope: text('scope'),
|
|
password: text('password'),
|
|
createdAt: integer('createdAt', authDateColumn).notNull(),
|
|
updatedAt: integer('updatedAt', authDateColumn).notNull()
|
|
}, (table) => ({
|
|
accountUserIdIndex: index('account_userId_idx').on(table.userId)
|
|
}));
|
|
|
|
export const verification = sqliteTable('verification', {
|
|
id: text('id').primaryKey().notNull(),
|
|
identifier: text('identifier').notNull(),
|
|
value: text('value').notNull(),
|
|
expiresAt: integer('expiresAt', authDateColumn).notNull(),
|
|
createdAt: integer('createdAt', authDateColumn).notNull(),
|
|
updatedAt: integer('updatedAt', authDateColumn).notNull()
|
|
}, (table) => ({
|
|
verificationIdentifierIndex: index('verification_identifier_idx').on(table.identifier)
|
|
}));
|
|
|
|
export const member = sqliteTable('member', {
|
|
id: text('id').primaryKey().notNull(),
|
|
organizationId: text('organizationId').notNull().references(() => organization.id, { onDelete: 'cascade' }),
|
|
userId: text('userId').notNull().references(() => user.id, { onDelete: 'cascade' }),
|
|
role: text('role').notNull().default('member'),
|
|
createdAt: integer('createdAt', authDateColumn).notNull()
|
|
}, (table) => ({
|
|
memberOrganizationIdIndex: index('member_organizationId_idx').on(table.organizationId),
|
|
memberUserIdIndex: index('member_userId_idx').on(table.userId)
|
|
}));
|
|
|
|
export const invitation = sqliteTable('invitation', {
|
|
id: text('id').primaryKey().notNull(),
|
|
organizationId: text('organizationId').notNull().references(() => organization.id, { onDelete: 'cascade' }),
|
|
email: text('email').notNull(),
|
|
role: text('role'),
|
|
status: text('status').notNull().default('pending'),
|
|
expiresAt: integer('expiresAt', authDateColumn).notNull(),
|
|
createdAt: integer('createdAt', authDateColumn).notNull(),
|
|
inviterId: text('inviterId').notNull().references(() => user.id, { onDelete: 'cascade' })
|
|
}, (table) => ({
|
|
invitationOrganizationIdIndex: index('invitation_organizationId_idx').on(table.organizationId),
|
|
invitationEmailIndex: index('invitation_email_idx').on(table.email)
|
|
}));
|
|
|
|
export const watchlistItem = sqliteTable('watchlist_item', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
user_id: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
|
|
ticker: text('ticker').notNull(),
|
|
company_name: text('company_name').notNull(),
|
|
sector: text('sector'),
|
|
created_at: text('created_at').notNull()
|
|
}, (table) => ({
|
|
watchlistUserTickerUnique: uniqueIndex('watchlist_user_ticker_uidx').on(table.user_id, table.ticker),
|
|
watchlistUserCreatedIndex: index('watchlist_user_created_idx').on(table.user_id, table.created_at)
|
|
}));
|
|
|
|
export const holding = sqliteTable('holding', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
user_id: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
|
|
ticker: text('ticker').notNull(),
|
|
shares: numeric('shares').notNull(),
|
|
avg_cost: numeric('avg_cost').notNull(),
|
|
current_price: numeric('current_price'),
|
|
market_value: numeric('market_value').notNull(),
|
|
gain_loss: numeric('gain_loss').notNull(),
|
|
gain_loss_pct: numeric('gain_loss_pct').notNull(),
|
|
last_price_at: text('last_price_at'),
|
|
created_at: text('created_at').notNull(),
|
|
updated_at: text('updated_at').notNull()
|
|
}, (table) => ({
|
|
holdingUserTickerUnique: uniqueIndex('holding_user_ticker_uidx').on(table.user_id, table.ticker),
|
|
holdingUserIndex: index('holding_user_idx').on(table.user_id)
|
|
}));
|
|
|
|
export const filing = sqliteTable('filing', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
ticker: text('ticker').notNull(),
|
|
filing_type: text('filing_type').$type<'10-K' | '10-Q' | '8-K'>().notNull(),
|
|
filing_date: text('filing_date').notNull(),
|
|
accession_number: text('accession_number').notNull(),
|
|
cik: text('cik').notNull(),
|
|
company_name: text('company_name').notNull(),
|
|
filing_url: text('filing_url'),
|
|
submission_url: text('submission_url'),
|
|
primary_document: text('primary_document'),
|
|
metrics: text('metrics', { mode: 'json' }).$type<FilingMetrics | null>(),
|
|
analysis: text('analysis', { mode: 'json' }).$type<FilingAnalysis | null>(),
|
|
created_at: text('created_at').notNull(),
|
|
updated_at: text('updated_at').notNull()
|
|
}, (table) => ({
|
|
filingAccessionUnique: uniqueIndex('filing_accession_uidx').on(table.accession_number),
|
|
filingTickerDateIndex: index('filing_ticker_date_idx').on(table.ticker, table.filing_date),
|
|
filingDateIndex: index('filing_date_idx').on(table.filing_date)
|
|
}));
|
|
|
|
export const filingLink = sqliteTable('filing_link', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
filing_id: integer('filing_id').notNull().references(() => filing.id, { onDelete: 'cascade' }),
|
|
link_type: text('link_type').notNull(),
|
|
url: text('url').notNull(),
|
|
source: text('source').notNull().default('sec'),
|
|
created_at: text('created_at').notNull()
|
|
}, (table) => ({
|
|
filingLinkUnique: uniqueIndex('filing_link_unique_uidx').on(table.filing_id, table.url),
|
|
filingLinkFilingIndex: index('filing_link_filing_idx').on(table.filing_id)
|
|
}));
|
|
|
|
export const taskRun = sqliteTable('task_run', {
|
|
id: text('id').primaryKey().notNull(),
|
|
user_id: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
|
|
task_type: text('task_type').$type<'sync_filings' | 'refresh_prices' | 'analyze_filing' | 'portfolio_insights'>().notNull(),
|
|
status: text('status').$type<'queued' | 'running' | 'completed' | 'failed'>().notNull(),
|
|
priority: integer('priority').notNull(),
|
|
payload: text('payload', { mode: 'json' }).$type<Record<string, unknown>>().notNull(),
|
|
result: text('result', { mode: 'json' }).$type<Record<string, unknown> | null>(),
|
|
error: text('error'),
|
|
attempts: integer('attempts').notNull(),
|
|
max_attempts: integer('max_attempts').notNull(),
|
|
workflow_run_id: text('workflow_run_id'),
|
|
created_at: text('created_at').notNull(),
|
|
updated_at: text('updated_at').notNull(),
|
|
finished_at: text('finished_at')
|
|
}, (table) => ({
|
|
taskUserCreatedIndex: index('task_user_created_idx').on(table.user_id, table.created_at),
|
|
taskStatusIndex: index('task_status_idx').on(table.status),
|
|
taskWorkflowRunUnique: uniqueIndex('task_workflow_run_uidx').on(table.workflow_run_id)
|
|
}));
|
|
|
|
export const portfolioInsight = sqliteTable('portfolio_insight', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
user_id: text('user_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
|
|
provider: text('provider').notNull(),
|
|
model: text('model').notNull(),
|
|
content: text('content').notNull(),
|
|
created_at: text('created_at').notNull()
|
|
}, (table) => ({
|
|
insightUserCreatedIndex: index('insight_user_created_idx').on(table.user_id, table.created_at)
|
|
}));
|
|
|
|
export const authSchema = {
|
|
user,
|
|
session,
|
|
account,
|
|
verification,
|
|
organization,
|
|
member,
|
|
invitation
|
|
};
|
|
|
|
export const appSchema = {
|
|
watchlistItem,
|
|
holding,
|
|
filing,
|
|
filingLink,
|
|
taskRun,
|
|
portfolioInsight
|
|
};
|
|
|
|
export const schema = {
|
|
...authSchema,
|
|
...appSchema
|
|
};
|