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