feat: drizzle schema + migrations for products/snapshots/alerts
This commit is contained in:
10
src/lib/db/index.ts
Normal file
10
src/lib/db/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { drizzle } from 'drizzle-orm/postgres-js'
|
||||
import postgres from 'postgres'
|
||||
import * as schema from './schema'
|
||||
|
||||
const connectionString = process.env.DATABASE_URL
|
||||
if (!connectionString) throw new Error('DATABASE_URL not set')
|
||||
|
||||
const client = postgres(connectionString, { max: 5 })
|
||||
export const db = drizzle(client, { schema })
|
||||
export * from './schema'
|
||||
46
src/lib/db/schema.ts
Normal file
46
src/lib/db/schema.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { pgTable, uuid, text, timestamp, boolean, integer, numeric, bigserial, jsonb, index, check } from 'drizzle-orm/pg-core'
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
export const products = pgTable('products', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
url: text('url').notNull().unique(),
|
||||
shop: text('shop').notNull(),
|
||||
name: text('name').notNull(),
|
||||
imageUrl: text('image_url'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
enabled: boolean('enabled').notNull().default(true),
|
||||
lastScrapedAt: timestamp('last_scraped_at', { withTimezone: true }),
|
||||
consecutiveFailures: integer('consecutive_failures').notNull().default(0),
|
||||
}, (t) => ({
|
||||
shopCheck: check('shop_check', sql`${t.shop} in ('amazon','idealo','geizhals')`),
|
||||
}))
|
||||
|
||||
export const priceSnapshots = pgTable('price_snapshots', {
|
||||
id: bigserial('id', { mode: 'number' }).primaryKey(),
|
||||
productId: uuid('product_id').notNull().references(() => products.id, { onDelete: 'cascade' }),
|
||||
price: numeric('price', { precision: 10, scale: 2 }),
|
||||
currency: text('currency').notNull().default('EUR'),
|
||||
availability: text('availability'),
|
||||
error: text('error'),
|
||||
scrapedAt: timestamp('scraped_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (t) => ({
|
||||
productScrapedIdx: index('snapshots_product_scraped_idx').on(t.productId, t.scrapedAt.desc()),
|
||||
}))
|
||||
|
||||
export const alerts = pgTable('alerts', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
productId: uuid('product_id').notNull().references(() => products.id, { onDelete: 'cascade' }),
|
||||
type: text('type').notNull(),
|
||||
config: jsonb('config').notNull(),
|
||||
enabled: boolean('enabled').notNull().default(true),
|
||||
lastTriggeredAt: timestamp('last_triggered_at', { withTimezone: true }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (t) => ({
|
||||
typeCheck: check('alert_type_check', sql`${t.type} in ('target_price','all_time_low','percent_drop')`),
|
||||
}))
|
||||
|
||||
export type Product = typeof products.$inferSelect
|
||||
export type NewProduct = typeof products.$inferInsert
|
||||
export type PriceSnapshot = typeof priceSnapshots.$inferSelect
|
||||
export type Alert = typeof alerts.$inferSelect
|
||||
export type NewAlert = typeof alerts.$inferInsert
|
||||
Reference in New Issue
Block a user