lifetracker/packages/db/schema.ts
2024-12-07 15:45:00 -08:00

357 lines
9.7 KiB
TypeScript

import type { AdapterAccount } from "@auth/core/adapters";
import { createId } from "@paralleldrive/cuid2";
import { relations, SQL, sql } from "drizzle-orm";
import { date } from "drizzle-orm/mysql-core";
import {
AnySQLiteColumn,
index,
integer,
real,
primaryKey,
sqliteTable,
text,
unique,
} from "drizzle-orm/sqlite-core";
function createdAtField() {
return integer("createdAt", { mode: "timestamp" })
.notNull()
.$defaultFn(() => new Date());
}
export function calcInverseColor(hexcode: string): string {
const hex = hexcode.replace("#", "");
const r = parseInt(hex.substr(0, 2), 16);
const g = parseInt(hex.substr(2, 2), 16);
const b = parseInt(hex.substr(4, 2), 16);
const luminance = r * 0.299 + g * 0.587 + b * 0.114;
return luminance > 186 ? "#000000" : "#ffffff";
}
// export function calcCategoryParent(parentId: string | null){
// return parentId ??
// }
export const config = sqliteTable("config", {
key: text("key").notNull().primaryKey(),
value: text("value").notNull(),
});
export const apiKeys = sqliteTable(
"apiKey",
{
id: text("id")
.notNull()
.primaryKey()
.$defaultFn(() => createId()),
name: text("name").notNull(),
createdAt: createdAtField(),
keyId: text("keyId").notNull().unique(),
keyHash: text("keyHash").notNull(),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
},
(ak) => ({
unq: unique().on(ak.name, ak.userId),
}),
);
export const users = sqliteTable("user", {
id: text("id")
.notNull()
.primaryKey()
.$defaultFn(() => createId()),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
image: text("image"),
password: text("password"),
role: text("role", { enum: ["admin", "user"] }).default("user"),
timezone: text("timezone").notNull().default("America/Los_Angeles"),
});
export const accounts = sqliteTable(
"account",
{
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").$type<AdapterAccount["type"]>().notNull(),
provider: text("provider").notNull(),
providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
expires_at: integer("expires_at"),
token_type: text("token_type"),
scope: text("scope"),
id_token: text("id_token"),
session_state: text("session_state"),
},
(account) => ({
compoundKey: primaryKey({
columns: [account.provider, account.providerAccountId],
}),
}),
);
export const sessions = sqliteTable("session", {
sessionToken: text("sessionToken")
.notNull()
.primaryKey()
.$defaultFn(() => createId()),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
});
export const verificationTokens = sqliteTable(
"verificationToken",
{
identifier: text("identifier").notNull(),
token: text("token").notNull(),
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
},
(vt) => ({
compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }),
}),
);
export const metrics = sqliteTable("metric", {
id: text("id")
.notNull()
.primaryKey()
.$defaultFn(() => createId()),
name: text("name").notNull(),
description: text("description"),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").notNull(),
unit: text("unit"),
goal: real("goal"),
icon: text("icon").notNull(),
createdAt: createdAtField()
},
(m) => ({
uniq: unique().on(m.userId, m.name)
}),
);
export const measurements = sqliteTable("measurement", {
id: text("id")
.notNull()
.primaryKey()
.$defaultFn(() => createId()),
hourId: text("hourId").references(() => hours.id),
dayId: text("dayId").notNull().references(() => days.id, { onDelete: "cascade" }),
metricId: text("metricId").notNull().references(() => metrics.id, { onDelete: "cascade" }),
createdAt: createdAtField(),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
value: text("value").notNull(),
},
(m) => ({
// uniq: unique().on(m.dayId, m.metricId)
}),
);
export const days = sqliteTable("day", {
id: text("id")
.notNull()
.primaryKey()
.$defaultFn(() => createId()),
date: text("date").notNull().unique(),
mood: integer("mood"),
comment: text("comment"),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
});
export const hours = sqliteTable(
"hour",
{
id: text("id")
.notNull()
.primaryKey()
.$defaultFn(() => createId()),
createdAt: createdAtField(),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
comment: text("comment"),
time: integer("time").notNull(),
dayId: text("dayId").notNull().references(() => days.id, { onDelete: "cascade" }),
categoryId: text("categoryId").references(() => categories.id),
},
(e) => ({
uniq: unique().on(e.dayId, e.time)
}),
)
export const colors = sqliteTable(
"color",
{
id: text("id")
.notNull()
.primaryKey()
.$defaultFn(() => createId()),
createdAt: createdAtField(),
name: text("name").notNull(),
hexcode: text("hexcode").notNull(),
inverse: text("inverse"),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
},
(c) => ({
// Name should be unique per user
uniq: unique().on(c.userId, c.name),
// Hexcode should start with a pound sign, or have one prepended
// TODO. No internet, so no chatGPT cheating nor regular google looking up,
// and fuck if the in built documentation makes any sense
}),
);
export const categories = sqliteTable(
"category",
{
id: text("id")
.notNull()
.primaryKey()
.$defaultFn(() => createId()),
createdAt: createdAtField(),
name: text("name").notNull(),
code: real("code").notNull(),
description: text("description"),
colorId: text("colorId")
.notNull()
.references(() => colors.id),
parentId: text("parentId").references(() => categories.id),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
},
(c) => ({
uniq: unique().on(c.userId, c.code)
}),
);
// Relations
export const apiKeyRelations = relations(apiKeys, ({ one }) => ({
user: one(users, {
fields: [apiKeys.userId],
references: [users.id],
}),
}));
export const userRelations = relations(users, ({ many }) => ({
categories: many(categories),
colors: many(colors),
days: many(days),
hours: many(hours),
metrics: many(metrics),
}));
export const colorsRelations = relations(
colors,
({ many, one }) => ({
user: one(users, {
fields: [colors.userId],
references: [users.id],
}),
categories: many(categories),
}),
);
export const categoriesRelations = relations(
categories,
({ many, one }) => ({
user: one(users, {
fields: [categories.userId],
references: [users.id],
}),
color: one(colors, {
fields: [categories.colorId],
references: [colors.id],
}),
parent: one(categories, {
fields: [categories.parentId],
references: [categories.id],
}),
hours: many(hours),
}),
);
export const daysRelations = relations(
days,
({ many, one }) => ({
user: one(users, {
fields: [days.userId],
references: [users.id],
}),
hours: many(hours),
measurements: many(measurements),
}),
);
export const hoursRelations = relations(
hours,
({ many, one }) => ({
user: one(users, {
fields: [hours.userId],
references: [users.id],
}),
categories: one(categories, {
fields: [hours.categoryId],
references: [categories.id],
}
),
day: one(days, {
fields: [hours.dayId],
references: [days.id],
}),
measurements: many(measurements),
}),
);
export const metricsRelations = relations(
metrics,
({ many, one }) => ({
user: one(users, {
fields: [metrics.userId],
references: [users.id],
}),
measurements: many(measurements),
}),
);
export const measurementsRelations = relations(
measurements,
({ one }) => ({
metric: one(metrics, {
fields: [measurements.metricId],
references: [metrics.id],
}),
day: one(days, {
fields: [measurements.dayId],
references: [days.id],
}),
hour: measurements.hourId ? one(hours, {
fields: [measurements.hourId],
references: [hours.id],
}) : undefined,
}),
);