import { sqliteTable, text, integer, real, primaryKey, foreignKey, index, } from "drizzle-orm/sqlite-core"; import { nanoid } from "nanoid"; export const users = sqliteTable("users", { id: text("id") .primaryKey() .$defaultFn(() => nanoid()), email: text("email").notNull().unique(), passwordHash: text("password_hash").notNull(), name: text("name").notNull(), isSiteAdmin: integer("is_site_admin", { mode: "boolean" }).default(false), createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn( () => new Date(), ), }); export const organizations = sqliteTable("organizations", { id: text("id") .primaryKey() .$defaultFn(() => nanoid()), name: text("name").notNull(), logoUrl: text("logo_url"), street: text("street"), city: text("city"), state: text("state"), zip: text("zip"), country: text("country"), createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn( () => new Date(), ), }); export const members = sqliteTable( "members", { userId: text("user_id").notNull(), organizationId: text("organization_id").notNull(), role: text("role").notNull().default("member"), // 'owner', 'admin', 'member' joinedAt: integer("joined_at", { mode: "timestamp" }).$defaultFn( () => new Date(), ), }, (table: any) => ({ pk: primaryKey({ columns: [table.userId, table.organizationId] }), userFk: foreignKey({ columns: [table.userId], foreignColumns: [users.id], }), orgFk: foreignKey({ columns: [table.organizationId], foreignColumns: [organizations.id], }), userIdIdx: index("members_user_id_idx").on(table.userId), organizationIdIdx: index("members_organization_id_idx").on( table.organizationId, ), }), ); export const clients = sqliteTable( "clients", { id: text("id") .primaryKey() .$defaultFn(() => nanoid()), organizationId: text("organization_id").notNull(), name: text("name").notNull(), email: text("email"), phone: text("phone"), street: text("street"), city: text("city"), state: text("state"), zip: text("zip"), country: text("country"), createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn( () => new Date(), ), }, (table: any) => ({ orgFk: foreignKey({ columns: [table.organizationId], foreignColumns: [organizations.id], }), organizationIdIdx: index("clients_organization_id_idx").on( table.organizationId, ), }), ); export const categories = sqliteTable( "categories", { id: text("id") .primaryKey() .$defaultFn(() => nanoid()), organizationId: text("organization_id").notNull(), name: text("name").notNull(), color: text("color"), createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn( () => new Date(), ), }, (table: any) => ({ orgFk: foreignKey({ columns: [table.organizationId], foreignColumns: [organizations.id], }), organizationIdIdx: index("categories_organization_id_idx").on( table.organizationId, ), }), ); export const timeEntries = sqliteTable( "time_entries", { id: text("id") .primaryKey() .$defaultFn(() => nanoid()), userId: text("user_id").notNull(), organizationId: text("organization_id").notNull(), clientId: text("client_id").notNull(), categoryId: text("category_id").notNull(), startTime: integer("start_time", { mode: "timestamp" }).notNull(), endTime: integer("end_time", { mode: "timestamp" }), description: text("description"), isManual: integer("is_manual", { mode: "boolean" }).default(false), createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn( () => new Date(), ), }, (table: any) => ({ userFk: foreignKey({ columns: [table.userId], foreignColumns: [users.id], }), orgFk: foreignKey({ columns: [table.organizationId], foreignColumns: [organizations.id], }), clientFk: foreignKey({ columns: [table.clientId], foreignColumns: [clients.id], }), categoryFk: foreignKey({ columns: [table.categoryId], foreignColumns: [categories.id], }), userIdIdx: index("time_entries_user_id_idx").on(table.userId), organizationIdIdx: index("time_entries_organization_id_idx").on( table.organizationId, ), clientIdIdx: index("time_entries_client_id_idx").on(table.clientId), startTimeIdx: index("time_entries_start_time_idx").on(table.startTime), }), ); export const tags = sqliteTable( "tags", { id: text("id") .primaryKey() .$defaultFn(() => nanoid()), organizationId: text("organization_id").notNull(), name: text("name").notNull(), color: text("color"), createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn( () => new Date(), ), }, (table: any) => ({ orgFk: foreignKey({ columns: [table.organizationId], foreignColumns: [organizations.id], }), organizationIdIdx: index("tags_organization_id_idx").on( table.organizationId, ), }), ); export const timeEntryTags = sqliteTable( "time_entry_tags", { timeEntryId: text("time_entry_id").notNull(), tagId: text("tag_id").notNull(), }, (table: any) => ({ pk: primaryKey({ columns: [table.timeEntryId, table.tagId] }), timeEntryFk: foreignKey({ columns: [table.timeEntryId], foreignColumns: [timeEntries.id], }), tagFk: foreignKey({ columns: [table.tagId], foreignColumns: [tags.id], }), timeEntryIdIdx: index("time_entry_tags_time_entry_id_idx").on( table.timeEntryId, ), tagIdIdx: index("time_entry_tags_tag_id_idx").on(table.tagId), }), ); export const sessions = sqliteTable( "sessions", { id: text("id").primaryKey(), userId: text("user_id").notNull(), expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(), }, (table: any) => ({ userFk: foreignKey({ columns: [table.userId], foreignColumns: [users.id], }), userIdIdx: index("sessions_user_id_idx").on(table.userId), }), ); export const siteSettings = sqliteTable("site_settings", { id: text("id") .primaryKey() .$defaultFn(() => nanoid()), key: text("key").notNull().unique(), value: text("value").notNull(), updatedAt: integer("updated_at", { mode: "timestamp" }).$defaultFn( () => new Date(), ), }); export const apiTokens = sqliteTable( "api_tokens", { id: text("id") .primaryKey() .$defaultFn(() => nanoid()), userId: text("user_id").notNull(), name: text("name").notNull(), token: text("token").notNull().unique(), scopes: text("scopes").notNull().default("*"), lastUsedAt: integer("last_used_at", { mode: "timestamp" }), createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn( () => new Date(), ), }, (table: any) => ({ userFk: foreignKey({ columns: [table.userId], foreignColumns: [users.id], }), userIdIdx: index("api_tokens_user_id_idx").on(table.userId), }), ); export const invoices = sqliteTable( "invoices", { id: text("id") .primaryKey() .$defaultFn(() => nanoid()), organizationId: text("organization_id").notNull(), clientId: text("client_id").notNull(), number: text("number").notNull(), type: text("type").notNull().default("invoice"), // 'invoice' or 'quote' status: text("status").notNull().default("draft"), // 'draft', 'sent', 'paid', 'void', 'accepted', 'declined' issueDate: integer("issue_date", { mode: "timestamp" }).notNull(), dueDate: integer("due_date", { mode: "timestamp" }).notNull(), notes: text("notes"), currency: text("currency").default("USD").notNull(), subtotal: integer("subtotal").notNull().default(0), // in cents taxRate: real("tax_rate").default(0), // percentage taxAmount: integer("tax_amount").notNull().default(0), // in cents total: integer("total").notNull().default(0), // in cents createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn( () => new Date(), ), }, (table: any) => ({ orgFk: foreignKey({ columns: [table.organizationId], foreignColumns: [organizations.id], }), clientFk: foreignKey({ columns: [table.clientId], foreignColumns: [clients.id], }), organizationIdIdx: index("invoices_organization_id_idx").on( table.organizationId, ), clientIdIdx: index("invoices_client_id_idx").on(table.clientId), }), ); export const invoiceItems = sqliteTable( "invoice_items", { id: text("id") .primaryKey() .$defaultFn(() => nanoid()), invoiceId: text("invoice_id").notNull(), description: text("description").notNull(), quantity: real("quantity").notNull().default(1), unitPrice: integer("unit_price").notNull().default(0), // in cents amount: integer("amount").notNull().default(0), // in cents }, (table: any) => ({ invoiceFk: foreignKey({ columns: [table.invoiceId], foreignColumns: [invoices.id], }), invoiceIdIdx: index("invoice_items_invoice_id_idx").on(table.invoiceId), }), );