feat(db): add guests table and claimed_by_guest_id column
This commit is contained in:
@@ -103,6 +103,16 @@ export async function initializeDatabase() {
|
|||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
// Create guests table
|
||||||
|
sqlite.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS guests (
|
||||||
|
id TEXT PRIMARY KEY NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT (unixepoch()) NOT NULL,
|
||||||
|
updated_at INTEGER DEFAULT (unixepoch()) NOT NULL
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
// Run migrations for existing databases
|
// Run migrations for existing databases
|
||||||
try {
|
try {
|
||||||
const columns = sqlite.pragma('table_info(wishlists)') as Array<{ name: string }>;
|
const columns = sqlite.pragma('table_info(wishlists)') as Array<{ name: string }>;
|
||||||
@@ -127,6 +137,26 @@ export async function initializeDatabase() {
|
|||||||
sqlite.exec('ALTER TABLE wishlists ADD COLUMN preferences TEXT');
|
sqlite.exec('ALTER TABLE wishlists ADD COLUMN preferences TEXT');
|
||||||
console.log('✅ Added preferences column to wishlists table');
|
console.log('✅ Added preferences column to wishlists table');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const itemColumns = sqlite.pragma('table_info(wishlist_items)') as Array<{ name: string }>;
|
||||||
|
|
||||||
|
const hasClaimedByGuestId = itemColumns.some((col) => col.name === 'claimed_by_guest_id');
|
||||||
|
if (!hasClaimedByGuestId) {
|
||||||
|
sqlite.exec('ALTER TABLE wishlist_items ADD COLUMN claimed_by_guest_id TEXT REFERENCES guests(id) ON DELETE SET NULL');
|
||||||
|
console.log('✅ Added claimed_by_guest_id column to wishlist_items');
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasClaimedByName = itemColumns.some((col) => col.name === 'claimed_by_name');
|
||||||
|
const hasClaimedByToken = itemColumns.some((col) => col.name === 'claimed_by_token');
|
||||||
|
if (hasClaimedByName || hasClaimedByToken) {
|
||||||
|
if (hasClaimedByName) {
|
||||||
|
sqlite.exec('UPDATE wishlist_items SET claimed_by_name = NULL');
|
||||||
|
}
|
||||||
|
if (hasClaimedByToken) {
|
||||||
|
sqlite.exec('UPDATE wishlist_items SET claimed_by_token = NULL');
|
||||||
|
}
|
||||||
|
console.log('ℹ️ Legacy claim columns nullified (claimed_by_name / claimed_by_token)');
|
||||||
|
}
|
||||||
} catch (migrationError) {
|
} catch (migrationError) {
|
||||||
console.log('Migration already applied or not needed');
|
console.log('Migration already applied or not needed');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,12 @@ import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core';
|
|||||||
import { createId } from '@paralleldrive/cuid2';
|
import { createId } from '@paralleldrive/cuid2';
|
||||||
import { sql } from 'drizzle-orm';
|
import { sql } from 'drizzle-orm';
|
||||||
|
|
||||||
/**
|
|
||||||
* Database schema for family wishlist app
|
|
||||||
*
|
|
||||||
* Simplified for self-hosted family use:
|
|
||||||
* - No Settings table (use env vars instead)
|
|
||||||
* - Image URLs only (no upload handling in MVP)
|
|
||||||
* - Simplified purchase URLs (no usage tracking)
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Wishlists table
|
|
||||||
export const wishlists = sqliteTable('wishlists', {
|
export const wishlists = sqliteTable('wishlists', {
|
||||||
id: text('id').primaryKey().$defaultFn(() => createId()),
|
id: text('id').primaryKey().$defaultFn(() => createId()),
|
||||||
name: text('name').notNull(),
|
name: text('name').notNull(),
|
||||||
slug: text('slug').notNull().unique(),
|
slug: text('slug').notNull().unique(),
|
||||||
description: text('description'),
|
description: text('description'),
|
||||||
preferences: text('preferences'), // General interests/likes section
|
preferences: text('preferences'),
|
||||||
imageUrl: text('image_url'),
|
imageUrl: text('image_url'),
|
||||||
isPublic: integer('is_public', { mode: 'boolean' }).notNull().default(false),
|
isPublic: integer('is_public', { mode: 'boolean' }).notNull().default(false),
|
||||||
sortOrder: integer('sort_order').notNull().default(0),
|
sortOrder: integer('sort_order').notNull().default(0),
|
||||||
@@ -25,7 +15,13 @@ export const wishlists = sqliteTable('wishlists', {
|
|||||||
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wishlist items table
|
export const guests = sqliteTable('guests', {
|
||||||
|
id: text('id').primaryKey().$defaultFn(() => createId()),
|
||||||
|
name: text('name').notNull(),
|
||||||
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),
|
||||||
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),
|
||||||
|
});
|
||||||
|
|
||||||
export const wishlistItems = sqliteTable('wishlist_items', {
|
export const wishlistItems = sqliteTable('wishlist_items', {
|
||||||
id: text('id').primaryKey().$defaultFn(() => createId()),
|
id: text('id').primaryKey().$defaultFn(() => createId()),
|
||||||
wishlistId: text('wishlist_id').notNull().references(() => wishlists.id, { onDelete: 'cascade' }),
|
wishlistId: text('wishlist_id').notNull().references(() => wishlists.id, { onDelete: 'cascade' }),
|
||||||
@@ -34,18 +30,15 @@ export const wishlistItems = sqliteTable('wishlist_items', {
|
|||||||
price: real('price'),
|
price: real('price'),
|
||||||
currency: text('currency').notNull().default('USD'),
|
currency: text('currency').notNull().default('USD'),
|
||||||
quantity: integer('quantity').notNull().default(1),
|
quantity: integer('quantity').notNull().default(1),
|
||||||
imageUrl: text('images'), // Stored as 'images' in DB but exposed as imageUrl
|
imageUrl: text('images'),
|
||||||
purchaseUrls: text('purchase_urls', { mode: 'json' }).$type<Array<{
|
purchaseUrls: text('purchase_urls', { mode: 'json' }).$type<Array<{
|
||||||
label: string;
|
label: string;
|
||||||
url: string;
|
url: string;
|
||||||
}>>(),
|
}>>(),
|
||||||
|
|
||||||
isArchived: integer('is_archived', { mode: 'boolean' }).notNull().default(false),
|
isArchived: integer('is_archived', { mode: 'boolean' }).notNull().default(false),
|
||||||
|
|
||||||
// Claim information
|
claimedByGuestId: text('claimed_by_guest_id').references(() => guests.id, { onDelete: 'set null' }),
|
||||||
claimedByName: text('claimed_by_name'),
|
|
||||||
claimedByNote: text('claimed_by_note'),
|
claimedByNote: text('claimed_by_note'),
|
||||||
claimedByToken: text('claimed_by_token').unique(),
|
|
||||||
claimedAt: integer('claimed_at', { mode: 'timestamp' }),
|
claimedAt: integer('claimed_at', { mode: 'timestamp' }),
|
||||||
isPurchased: integer('is_purchased', { mode: 'boolean' }).notNull().default(false),
|
isPurchased: integer('is_purchased', { mode: 'boolean' }).notNull().default(false),
|
||||||
|
|
||||||
@@ -54,7 +47,6 @@ export const wishlistItems = sqliteTable('wishlist_items', {
|
|||||||
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Settings table
|
|
||||||
export const settings = sqliteTable('settings', {
|
export const settings = sqliteTable('settings', {
|
||||||
id: text('id').primaryKey().$defaultFn(() => createId()),
|
id: text('id').primaryKey().$defaultFn(() => createId()),
|
||||||
key: text('key').notNull().unique(),
|
key: text('key').notNull().unique(),
|
||||||
@@ -62,7 +54,7 @@ export const settings = sqliteTable('settings', {
|
|||||||
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Type exports
|
|
||||||
export type Wishlist = typeof wishlists.$inferSelect;
|
export type Wishlist = typeof wishlists.$inferSelect;
|
||||||
export type WishlistItem = typeof wishlistItems.$inferSelect;
|
export type WishlistItem = typeof wishlistItems.$inferSelect;
|
||||||
|
export type Guest = typeof guests.$inferSelect;
|
||||||
export type Setting = typeof settings.$inferSelect;
|
export type Setting = typeof settings.$inferSelect;
|
||||||
|
|||||||
Reference in New Issue
Block a user