Files
chronus/src/db/schema.ts
Atridad Lahiji 5e70dd6bb8
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m49s
2.0.0
2026-01-18 14:27:47 -07:00

323 lines
9.0 KiB
TypeScript

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),
}),
);