diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index 0104d1f..5db6fdf 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -1,4 +1,11 @@ -import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; +import { sqliteTable, text, integer, uniqueIndex, primaryKey } from 'drizzle-orm/sqlite-core'; +import { sql, type SQL } from 'drizzle-orm'; +import type { AnySQLiteColumn } from 'drizzle-orm/sqlite-core'; + +// Helper function for case-insensitive indexing +export function lower(column: AnySQLiteColumn): SQL { + return sql`lower(${column})`; +} // Entry types: 'task' (actionable) or 'thought' (reference) export const entries = sqliteTable('entries', { @@ -36,3 +43,35 @@ export const images = sqliteTable('images', { export type Image = typeof images.$inferSelect; export type NewImage = typeof images.$inferInsert; + +// Tags table: reusable tags for organizing entries +export const tags = sqliteTable( + 'tags', + { + id: text('id').primaryKey(), // nanoid + name: text('name').notNull(), + createdAt: text('created_at') + .notNull() + .$defaultFn(() => new Date().toISOString()) + }, + (table) => [uniqueIndex('tagNameUniqueIndex').on(lower(table.name))] +); + +export type Tag = typeof tags.$inferSelect; +export type NewTag = typeof tags.$inferInsert; + +// Junction table for many-to-many entry-tag relationship +export const entryTags = sqliteTable( + 'entry_tags', + { + entryId: text('entry_id') + .notNull() + .references(() => entries.id, { onDelete: 'cascade' }), + tagId: text('tag_id') + .notNull() + .references(() => tags.id, { onDelete: 'cascade' }) + }, + (t) => [primaryKey({ columns: [t.entryId, t.tagId] })] +); + +export type EntryTag = typeof entryTags.$inferSelect;