diff --git a/.frontmatter/database/taxonomyDb.json b/.frontmatter/database/taxonomyDb.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/.frontmatter/database/taxonomyDb.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/drizzle.config.ts b/drizzle.config.ts
index f570da9..5c46e1a 100644
--- a/drizzle.config.ts
+++ b/drizzle.config.ts
@@ -3,8 +3,13 @@ import type { Config } from "drizzle-kit";
export default {
schema: "./src/db/schema.ts",
out: "./drizzle/migrations",
- driver: "pg",
+ dialect: "postgresql",
dbCredentials: {
- connectionString: process.env.DATABASE_URL!,
+ host: process.env.DB_HOST!,
+ port: Number(process.env.DB_PORT!),
+ user: process.env.DB_USER!,
+ password: process.env.DB_PASSWORD!,
+ database: process.env.DB_NAME!,
+ ssl: false,
},
} satisfies Config;
\ No newline at end of file
diff --git a/drizzle/migrations/0000_luxuriant_albert_cleary.sql b/drizzle/migrations/0000_luxuriant_albert_cleary.sql
new file mode 100644
index 0000000..fd8c8e5
--- /dev/null
+++ b/drizzle/migrations/0000_luxuriant_albert_cleary.sql
@@ -0,0 +1,77 @@
+-- Current sql file was generated after introspecting the database
+-- If you want to run this migration please uncomment this code before executing migrations
+/*
+CREATE TABLE "product_category_mappings" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "feed_name" varchar(255),
+ "feed_category_value" varchar(255),
+ "canonical_category_id" integer,
+ "confidence_score" double precision,
+ "last_reviewed_by" varchar(255),
+ "last_reviewed_at" timestamp
+);
+--> statement-breakpoint
+CREATE TABLE "categories" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "parent_id" integer,
+ "slug" varchar(255) NOT NULL,
+ CONSTRAINT "categories_slug_key" UNIQUE("slug")
+);
+--> statement-breakpoint
+CREATE TABLE "products" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "brand" varchar(255),
+ "description" text,
+ "upc" varchar(32),
+ "mpn" varchar(64),
+ "canonical_category_id" integer,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now()
+);
+--> statement-breakpoint
+CREATE TABLE "offers" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "product_id" integer,
+ "feed_name" varchar(255) NOT NULL,
+ "feed_sku" varchar(255),
+ "price" numeric(10, 2),
+ "url" text,
+ "in_stock" boolean,
+ "vendor" varchar(255),
+ "last_seen_at" timestamp DEFAULT now(),
+ "raw_data" jsonb,
+ CONSTRAINT "offers_product_id_feed_name_feed_sku_key" UNIQUE("product_id","feed_name","feed_sku"),
+ CONSTRAINT "offers_feed_unique" UNIQUE("feed_name","feed_sku")
+);
+--> statement-breakpoint
+CREATE TABLE "offer_price_history" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "offer_id" integer,
+ "price" numeric(10, 2) NOT NULL,
+ "seen_at" timestamp DEFAULT now()
+);
+--> statement-breakpoint
+CREATE TABLE "feeds" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "name" varchar(255) NOT NULL,
+ "url" text,
+ "last_imported_at" timestamp,
+ CONSTRAINT "feeds_name_key" UNIQUE("name")
+);
+--> statement-breakpoint
+CREATE TABLE "product_attributes" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "product_id" integer,
+ "name" varchar(255) NOT NULL,
+ "value" varchar(255) NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "product_category_mappings" ADD CONSTRAINT "product_category_mappings_canonical_category_id_fkey" FOREIGN KEY ("canonical_category_id") REFERENCES "public"."categories"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "categories" ADD CONSTRAINT "categories_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "public"."categories"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "products" ADD CONSTRAINT "products_canonical_category_id_fkey" FOREIGN KEY ("canonical_category_id") REFERENCES "public"."categories"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "offers" ADD CONSTRAINT "offers_product_id_fkey" FOREIGN KEY ("product_id") REFERENCES "public"."products"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "offer_price_history" ADD CONSTRAINT "offer_price_history_offer_id_fkey" FOREIGN KEY ("offer_id") REFERENCES "public"."offers"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "product_attributes" ADD CONSTRAINT "product_attributes_product_id_fkey" FOREIGN KEY ("product_id") REFERENCES "public"."products"("id") ON DELETE cascade ON UPDATE no action;
+*/
\ No newline at end of file
diff --git a/drizzle/migrations/0001_superb_umar.sql b/drizzle/migrations/0001_superb_umar.sql
new file mode 100644
index 0000000..8332688
--- /dev/null
+++ b/drizzle/migrations/0001_superb_umar.sql
@@ -0,0 +1,7 @@
+DROP TABLE "product_category_mappings" CASCADE;--> statement-breakpoint
+DROP TABLE "categories" CASCADE;--> statement-breakpoint
+DROP TABLE "products" CASCADE;--> statement-breakpoint
+DROP TABLE "offers" CASCADE;--> statement-breakpoint
+DROP TABLE "offer_price_history" CASCADE;--> statement-breakpoint
+DROP TABLE "feeds" CASCADE;--> statement-breakpoint
+DROP TABLE "product_attributes" CASCADE;
\ No newline at end of file
diff --git a/drizzle/migrations/meta/0000_snapshot.json b/drizzle/migrations/meta/0000_snapshot.json
new file mode 100644
index 0000000..a055cd2
--- /dev/null
+++ b/drizzle/migrations/meta/0000_snapshot.json
@@ -0,0 +1,493 @@
+{
+ "id": "00000000-0000-0000-0000-000000000000",
+ "prevId": "",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.product_category_mappings": {
+ "name": "product_category_mappings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "feed_name": {
+ "name": "feed_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "feed_category_value": {
+ "name": "feed_category_value",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "canonical_category_id": {
+ "name": "canonical_category_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confidence_score": {
+ "name": "confidence_score",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_reviewed_by": {
+ "name": "last_reviewed_by",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_reviewed_at": {
+ "name": "last_reviewed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "product_category_mappings_canonical_category_id_fkey": {
+ "name": "product_category_mappings_canonical_category_id_fkey",
+ "tableFrom": "product_category_mappings",
+ "tableTo": "categories",
+ "schemaTo": "public",
+ "columnsFrom": [
+ "canonical_category_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {},
+ "policies": {},
+ "isRLSEnabled": false
+ },
+ "public.categories": {
+ "name": "categories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "categories_parent_id_fkey": {
+ "name": "categories_parent_id_fkey",
+ "tableFrom": "categories",
+ "tableTo": "categories",
+ "schemaTo": "public",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "categories_slug_key": {
+ "columns": [
+ "slug"
+ ],
+ "nullsNotDistinct": false,
+ "name": "categories_slug_key"
+ }
+ },
+ "checkConstraints": {},
+ "policies": {},
+ "isRLSEnabled": false
+ },
+ "public.products": {
+ "name": "products",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "brand": {
+ "name": "brand",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "upc": {
+ "name": "upc",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mpn": {
+ "name": "mpn",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "canonical_category_id": {
+ "name": "canonical_category_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "products_canonical_category_id_fkey": {
+ "name": "products_canonical_category_id_fkey",
+ "tableFrom": "products",
+ "tableTo": "categories",
+ "schemaTo": "public",
+ "columnsFrom": [
+ "canonical_category_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {},
+ "policies": {},
+ "isRLSEnabled": false
+ },
+ "public.offers": {
+ "name": "offers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "product_id": {
+ "name": "product_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "feed_name": {
+ "name": "feed_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "feed_sku": {
+ "name": "feed_sku",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "price": {
+ "name": "price",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "in_stock": {
+ "name": "in_stock",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor": {
+ "name": "vendor",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_seen_at": {
+ "name": "last_seen_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "raw_data": {
+ "name": "raw_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "offers_product_id_fkey": {
+ "name": "offers_product_id_fkey",
+ "tableFrom": "offers",
+ "tableTo": "products",
+ "schemaTo": "public",
+ "columnsFrom": [
+ "product_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "offers_product_id_feed_name_feed_sku_key": {
+ "columns": [
+ "product_id",
+ "feed_name",
+ "feed_sku"
+ ],
+ "nullsNotDistinct": false,
+ "name": "offers_product_id_feed_name_feed_sku_key"
+ },
+ "offers_feed_unique": {
+ "columns": [
+ "feed_name",
+ "feed_sku"
+ ],
+ "nullsNotDistinct": false,
+ "name": "offers_feed_unique"
+ }
+ },
+ "checkConstraints": {},
+ "policies": {},
+ "isRLSEnabled": false
+ },
+ "public.offer_price_history": {
+ "name": "offer_price_history",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "offer_id": {
+ "name": "offer_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "price": {
+ "name": "price",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "seen_at": {
+ "name": "seen_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "offer_price_history_offer_id_fkey": {
+ "name": "offer_price_history_offer_id_fkey",
+ "tableFrom": "offer_price_history",
+ "tableTo": "offers",
+ "schemaTo": "public",
+ "columnsFrom": [
+ "offer_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {},
+ "policies": {},
+ "isRLSEnabled": false
+ },
+ "public.feeds": {
+ "name": "feeds",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_imported_at": {
+ "name": "last_imported_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "feeds_name_key": {
+ "columns": [
+ "name"
+ ],
+ "nullsNotDistinct": false,
+ "name": "feeds_name_key"
+ }
+ },
+ "checkConstraints": {},
+ "policies": {},
+ "isRLSEnabled": false
+ },
+ "public.product_attributes": {
+ "name": "product_attributes",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "product_id": {
+ "name": "product_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "product_attributes_product_id_fkey": {
+ "name": "product_attributes_product_id_fkey",
+ "tableFrom": "product_attributes",
+ "tableTo": "products",
+ "schemaTo": "public",
+ "columnsFrom": [
+ "product_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {},
+ "policies": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/migrations/meta/0001_snapshot.json b/drizzle/migrations/meta/0001_snapshot.json
new file mode 100644
index 0000000..7628b12
--- /dev/null
+++ b/drizzle/migrations/meta/0001_snapshot.json
@@ -0,0 +1,18 @@
+{
+ "id": "919e511e-3cff-4bd0-b4ec-20865db2d1d3",
+ "prevId": "00000000-0000-0000-0000-000000000000",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {},
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/migrations/meta/_journal.json b/drizzle/migrations/meta/_journal.json
new file mode 100644
index 0000000..73582fd
--- /dev/null
+++ b/drizzle/migrations/meta/_journal.json
@@ -0,0 +1,20 @@
+{
+ "version": "7",
+ "dialect": "postgresql",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "7",
+ "when": 1751488452074,
+ "tag": "0000_luxuriant_albert_cleary",
+ "breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "7",
+ "when": 1751488491929,
+ "tag": "0001_superb_umar",
+ "breakpoints": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/drizzle/migrations/relations.ts b/drizzle/migrations/relations.ts
new file mode 100644
index 0000000..2f800c4
--- /dev/null
+++ b/drizzle/migrations/relations.ts
@@ -0,0 +1,53 @@
+import { relations } from "drizzle-orm/relations";
+import { categories, productCategoryMappings, products, offers, offerPriceHistory, productAttributes } from "./schema";
+
+export const productCategoryMappingsRelations = relations(productCategoryMappings, ({one}) => ({
+ category: one(categories, {
+ fields: [productCategoryMappings.canonicalCategoryId],
+ references: [categories.id]
+ }),
+}));
+
+export const categoriesRelations = relations(categories, ({one, many}) => ({
+ productCategoryMappings: many(productCategoryMappings),
+ category: one(categories, {
+ fields: [categories.parentId],
+ references: [categories.id],
+ relationName: "categories_parentId_categories_id"
+ }),
+ categories: many(categories, {
+ relationName: "categories_parentId_categories_id"
+ }),
+ products: many(products),
+}));
+
+export const productsRelations = relations(products, ({one, many}) => ({
+ category: one(categories, {
+ fields: [products.canonicalCategoryId],
+ references: [categories.id]
+ }),
+ offers: many(offers),
+ productAttributes: many(productAttributes),
+}));
+
+export const offersRelations = relations(offers, ({one, many}) => ({
+ product: one(products, {
+ fields: [offers.productId],
+ references: [products.id]
+ }),
+ offerPriceHistories: many(offerPriceHistory),
+}));
+
+export const offerPriceHistoryRelations = relations(offerPriceHistory, ({one}) => ({
+ offer: one(offers, {
+ fields: [offerPriceHistory.offerId],
+ references: [offers.id]
+ }),
+}));
+
+export const productAttributesRelations = relations(productAttributes, ({one}) => ({
+ product: one(products, {
+ fields: [productAttributes.productId],
+ references: [products.id]
+ }),
+}));
\ No newline at end of file
diff --git a/drizzle/migrations/schema.ts b/drizzle/migrations/schema.ts
new file mode 100644
index 0000000..7bf1ae8
--- /dev/null
+++ b/drizzle/migrations/schema.ts
@@ -0,0 +1,108 @@
+import { pgTable, foreignKey, serial, varchar, integer, doublePrecision, timestamp, unique, text, numeric, boolean, jsonb } from "drizzle-orm/pg-core"
+import { sql } from "drizzle-orm"
+
+
+
+export const productCategoryMappings = pgTable("product_category_mappings", {
+ id: serial().primaryKey().notNull(),
+ feedName: varchar("feed_name", { length: 255 }),
+ feedCategoryValue: varchar("feed_category_value", { length: 255 }),
+ canonicalCategoryId: integer("canonical_category_id"),
+ confidenceScore: doublePrecision("confidence_score"),
+ lastReviewedBy: varchar("last_reviewed_by", { length: 255 }),
+ lastReviewedAt: timestamp("last_reviewed_at", { mode: 'string' }),
+}, (table) => [
+ foreignKey({
+ columns: [table.canonicalCategoryId],
+ foreignColumns: [categories.id],
+ name: "product_category_mappings_canonical_category_id_fkey"
+ }),
+]);
+
+export const categories = pgTable("categories", {
+ id: serial().primaryKey().notNull(),
+ name: varchar({ length: 255 }).notNull(),
+ parentId: integer("parent_id"),
+ slug: varchar({ length: 255 }).notNull(),
+}, (table) => [
+ foreignKey({
+ columns: [table.parentId],
+ foreignColumns: [table.id],
+ name: "categories_parent_id_fkey"
+ }),
+ unique("categories_slug_key").on(table.slug),
+]);
+
+export const products = pgTable("products", {
+ id: serial().primaryKey().notNull(),
+ name: varchar({ length: 255 }).notNull(),
+ brand: varchar({ length: 255 }),
+ description: text(),
+ upc: varchar({ length: 32 }),
+ mpn: varchar({ length: 64 }),
+ canonicalCategoryId: integer("canonical_category_id"),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow(),
+ updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow(),
+}, (table) => [
+ foreignKey({
+ columns: [table.canonicalCategoryId],
+ foreignColumns: [categories.id],
+ name: "products_canonical_category_id_fkey"
+ }),
+]);
+
+export const offers = pgTable("offers", {
+ id: serial().primaryKey().notNull(),
+ productId: integer("product_id"),
+ feedName: varchar("feed_name", { length: 255 }).notNull(),
+ feedSku: varchar("feed_sku", { length: 255 }),
+ price: numeric({ precision: 10, scale: 2 }),
+ url: text(),
+ inStock: boolean("in_stock"),
+ vendor: varchar({ length: 255 }),
+ lastSeenAt: timestamp("last_seen_at", { mode: 'string' }).defaultNow(),
+ rawData: jsonb("raw_data"),
+}, (table) => [
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [products.id],
+ name: "offers_product_id_fkey"
+ }).onDelete("cascade"),
+ unique("offers_product_id_feed_name_feed_sku_key").on(table.productId, table.feedName, table.feedSku),
+ unique("offers_feed_unique").on(table.feedName, table.feedSku),
+]);
+
+export const offerPriceHistory = pgTable("offer_price_history", {
+ id: serial().primaryKey().notNull(),
+ offerId: integer("offer_id"),
+ price: numeric({ precision: 10, scale: 2 }).notNull(),
+ seenAt: timestamp("seen_at", { mode: 'string' }).defaultNow(),
+}, (table) => [
+ foreignKey({
+ columns: [table.offerId],
+ foreignColumns: [offers.id],
+ name: "offer_price_history_offer_id_fkey"
+ }).onDelete("cascade"),
+]);
+
+export const feeds = pgTable("feeds", {
+ id: serial().primaryKey().notNull(),
+ name: varchar({ length: 255 }).notNull(),
+ url: text(),
+ lastImportedAt: timestamp("last_imported_at", { mode: 'string' }),
+}, (table) => [
+ unique("feeds_name_key").on(table.name),
+]);
+
+export const productAttributes = pgTable("product_attributes", {
+ id: serial().primaryKey().notNull(),
+ productId: integer("product_id"),
+ name: varchar({ length: 255 }).notNull(),
+ value: varchar({ length: 255 }).notNull(),
+}, (table) => [
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [products.id],
+ name: "product_attributes_product_id_fkey"
+ }).onDelete("cascade"),
+]);
diff --git a/frontmatter.json b/frontmatter.json
new file mode 100644
index 0000000..a88f78b
--- /dev/null
+++ b/frontmatter.json
@@ -0,0 +1,52 @@
+{
+ "$schema": "https://frontmatter.codes/frontmatter.schema.json",
+ "frontMatter.taxonomy.contentTypes": [
+ {
+ "name": "default",
+ "pageBundle": false,
+ "previewPath": null,
+ "fields": [
+ {
+ "title": "Title",
+ "name": "title",
+ "type": "string"
+ },
+ {
+ "title": "Description",
+ "name": "description",
+ "type": "string"
+ },
+ {
+ "title": "Publishing date",
+ "name": "date",
+ "type": "datetime",
+ "default": "{{now}}",
+ "isPublishDate": true
+ },
+ {
+ "title": "Content preview",
+ "name": "preview",
+ "type": "image"
+ },
+ {
+ "title": "Is in draft",
+ "name": "draft",
+ "type": "draft"
+ },
+ {
+ "title": "Tags",
+ "name": "tags",
+ "type": "tags"
+ },
+ {
+ "title": "Categories",
+ "name": "categories",
+ "type": "categories"
+ }
+ ]
+ }
+ ],
+ "frontMatter.framework.id": "next",
+ "frontMatter.content.publicFolder": "public",
+ "frontMatter.preview.host": "http://localhost:3000"
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index dad7efc..21e657b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,6 @@
"bcryptjs": "^3.0.2",
"daisyui": "^4.7.3",
"date-fns": "^4.1.0",
- "drizzle-kit": "^0.31.4",
"drizzle-orm": "^0.44.2",
"lucide-react": "^0.525.0",
"next": "^14.2.30",
@@ -33,6 +32,7 @@
"@types/pg": "^8.15.4",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
+ "drizzle-kit": "^0.31.4",
"eslint": "^9",
"eslint-config-next": "15.3.4",
"typescript": "^5"
@@ -159,7 +159,8 @@
"node_modules/@drizzle-team/brocli": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz",
- "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="
+ "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==",
+ "dev": true
},
"node_modules/@emnapi/core": {
"version": "1.4.3",
@@ -197,6 +198,7 @@
"resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz",
"integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==",
"deprecated": "Merged into tsx: https://tsx.is",
+ "dev": true,
"dependencies": {
"esbuild": "~0.18.20",
"source-map-support": "^0.5.21"
@@ -209,6 +211,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"optional": true,
"os": [
"android"
@@ -224,6 +227,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"android"
@@ -239,6 +243,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"android"
@@ -254,6 +259,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"darwin"
@@ -269,6 +275,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"darwin"
@@ -284,6 +291,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -299,6 +307,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -314,6 +323,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -329,6 +339,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -344,6 +355,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -359,6 +371,7 @@
"cpu": [
"loong64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -374,6 +387,7 @@
"cpu": [
"mips64el"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -389,6 +403,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -404,6 +419,7 @@
"cpu": [
"riscv64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -419,6 +435,7 @@
"cpu": [
"s390x"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -434,6 +451,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -449,6 +467,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"netbsd"
@@ -464,6 +483,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"openbsd"
@@ -479,6 +499,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"sunos"
@@ -494,6 +515,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"win32"
@@ -509,6 +531,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"optional": true,
"os": [
"win32"
@@ -524,6 +547,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"win32"
@@ -536,6 +560,7 @@
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
+ "dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
@@ -573,6 +598,7 @@
"resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz",
"integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==",
"deprecated": "Merged into tsx: https://tsx.is",
+ "dev": true,
"dependencies": {
"@esbuild-kit/core-utils": "^3.3.2",
"get-tsconfig": "^4.7.0"
@@ -585,6 +611,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"optional": true,
"os": [
"aix"
@@ -600,6 +627,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"optional": true,
"os": [
"android"
@@ -615,6 +643,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"android"
@@ -630,6 +659,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"android"
@@ -645,6 +675,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"darwin"
@@ -660,6 +691,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"darwin"
@@ -675,6 +707,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -690,6 +723,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -705,6 +739,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -720,6 +755,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -735,6 +771,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -750,6 +787,7 @@
"cpu": [
"loong64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -765,6 +803,7 @@
"cpu": [
"mips64el"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -780,6 +819,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -795,6 +835,7 @@
"cpu": [
"riscv64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -810,6 +851,7 @@
"cpu": [
"s390x"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -825,6 +867,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -840,6 +883,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"netbsd"
@@ -855,6 +899,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"netbsd"
@@ -870,6 +915,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"openbsd"
@@ -885,6 +931,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"openbsd"
@@ -900,6 +947,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"sunos"
@@ -915,6 +963,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"win32"
@@ -930,6 +979,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"optional": true,
"os": [
"win32"
@@ -945,6 +995,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"win32"
@@ -2652,7 +2703,8 @@
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true
},
"node_modules/busboy": {
"version": "1.6.0",
@@ -2985,6 +3037,7 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+ "dev": true,
"dependencies": {
"ms": "^2.1.3"
},
@@ -3063,6 +3116,8 @@
"version": "0.31.4",
"resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.4.tgz",
"integrity": "sha512-tCPWVZWZqWVx2XUsVpJRnH9Mx0ClVOf5YUHerZ5so1OKSlqww4zy1R5ksEdGRcO3tM3zj0PYN6V48TbQCL1RfA==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
"@drizzle-team/brocli": "^0.10.2",
"@esbuild-kit/esm-loader": "^2.5.5",
@@ -3399,6 +3454,7 @@
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
+ "dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
@@ -3438,6 +3494,7 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz",
"integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
+ "dev": true,
"dependencies": {
"debug": "^4.3.4"
},
@@ -4136,6 +4193,7 @@
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz",
"integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==",
+ "dev": true,
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
@@ -5059,7 +5117,8 @@
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
},
"node_modules/mz": {
"version": "2.7.0",
@@ -6038,6 +6097,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
@@ -6297,6 +6357,7 @@
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -6313,6 +6374,7 @@
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
diff --git a/package.json b/package.json
index 7f968d2..61684a8 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,6 @@
"bcryptjs": "^3.0.2",
"daisyui": "^4.7.3",
"date-fns": "^4.1.0",
- "drizzle-kit": "^0.31.4",
"drizzle-orm": "^0.44.2",
"lucide-react": "^0.525.0",
"next": "^14.2.30",
@@ -34,6 +33,7 @@
"@types/pg": "^8.15.4",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
+ "drizzle-kit": "^0.31.4",
"eslint": "^9",
"eslint-config-next": "15.3.4",
"typescript": "^5"
diff --git a/src/app/(main)/parts/[category]/page.tsx b/src/app/(main)/parts/[category]/page.tsx
index d5eb557..0920fad 100644
--- a/src/app/(main)/parts/[category]/page.tsx
+++ b/src/app/(main)/parts/[category]/page.tsx
@@ -3,14 +3,12 @@ import { useEffect, useState, useMemo } from 'react';
import { useParams } from 'next/navigation';
const columns = [
- 'brandName',
- 'productName',
- 'department',
- 'category',
- 'subcategory',
- 'retailPrice',
- 'salePrice',
- 'imageUrl',
+ 'name',
+ 'brand',
+ 'description',
+ 'slug',
+ 'createdAt',
+ 'updatedAt',
];
export default function PartsCategoryPage() {
@@ -61,14 +59,14 @@ export default function PartsCategoryPage() {
});
}, [products, categoryParam]);
- const brandOptions = useMemo(() => Array.from(new Set(filteredByCategory.map(p => p.brandName).filter(Boolean))).sort(), [filteredByCategory]);
+ const brandOptions = useMemo(() => Array.from(new Set(filteredByCategory.map(p => p.brand).filter(Boolean))).sort(), [filteredByCategory]);
const departmentOptions = useMemo(() => Array.from(new Set(filteredByCategory.map(p => p.department).filter(Boolean))).sort(), [filteredByCategory]);
const subcategoryOptions = useMemo(() => Array.from(new Set(filteredByCategory.map(p => p.subcategory).filter(Boolean))).sort(), [filteredByCategory]);
// Further filter by sidebar filters
const filteredProducts = useMemo(() => {
return filteredByCategory.filter(p =>
- (!brand || p.brandName === brand) &&
+ (!brand || p.brand === brand) &&
(!department || p.department === department) &&
(!subcategory || p.subcategory === subcategory)
);
@@ -138,19 +136,19 @@ export default function PartsCategoryPage() {
) : paginatedProducts.length === 0 ? (
| No products found. |
) : (
- paginatedProducts.map((product, i) => (
-
- {columns.map(col => (
-
- {col === 'imageUrl' && product[col] ? (
-
- ) : (
- product[col] ?? ''
- )}
- |
- ))}
-
- ))
+ paginatedProducts
+ .filter(product => columns.some(col => product[col] && String(product[col]).trim() !== ''))
+ .map((product, i) => (
+
+ {columns.map(col => (
+ |
+ {typeof product[col] === 'object' && product[col] !== null
+ ? JSON.stringify(product[col])
+ : product[col] ?? ''}
+ |
+ ))}
+
+ ))
)}
diff --git a/src/app/(main)/parts/page.tsx b/src/app/(main)/parts/page.tsx
index cd3da06..7b56f4d 100644
--- a/src/app/(main)/parts/page.tsx
+++ b/src/app/(main)/parts/page.tsx
@@ -343,7 +343,7 @@ const useCanonicalCategories = () => {
const [categories, setCategories] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
- fetch('/api/product-categories')
+ fetch('/api/categories')
.then(res => res.json())
.then(data => {
setCategories(data.data);
@@ -390,14 +390,12 @@ function getDescendantCategoryIds(categories: any[], selectedId: string): string
}
const columns = [
- 'brandName',
- 'productName',
- 'department',
- 'category',
- 'subcategory',
- 'retailPrice',
- 'salePrice',
- 'imageUrl',
+ 'name',
+ 'brand',
+ 'description',
+ 'slug',
+ 'createdAt',
+ 'updatedAt',
];
export default function PartsPage() {
@@ -413,6 +411,20 @@ export default function PartsPage() {
const [category, setCategory] = useState('');
const [subcategory, setSubcategory] = useState('');
+ // Category data from canonical API
+ const { categories, loading: categoriesLoading } = useCanonicalCategories();
+ const flatCategories = useMemo(() => flattenCategories(categories), [categories]);
+
+ // Updated filter options
+ const categoryOptions = useMemo(
+ () => flatCategories.filter(cat => cat.parentId === null).map(cat => ({ value: String(cat.id), label: cat.name })),
+ [flatCategories]
+ );
+ const subcategoryOptions = useMemo(
+ () => flatCategories.filter(cat => cat.parentId === parseInt(category)).map(cat => ({ value: String(cat.id), label: cat.name })),
+ [flatCategories, category]
+ );
+
useEffect(() => {
setLoading(true);
setError(null);
@@ -431,16 +443,14 @@ export default function PartsPage() {
// Get unique filter options from all products
const brandOptions = useMemo(() => Array.from(new Set(products.map(p => p.brandName).filter(Boolean))).sort(), [products]);
const departmentOptions = useMemo(() => Array.from(new Set(products.map(p => p.department).filter(Boolean))).sort(), [products]);
- const categoryOptions = useMemo(() => Array.from(new Set(products.map(p => p.category).filter(Boolean))).sort(), [products]);
- const subcategoryOptions = useMemo(() => Array.from(new Set(products.map(p => p.subcategory).filter(Boolean))).sort(), [products]);
- // Filter products before rendering
+ // Filter products before rendering (match by category/subcategory ID if available)
const filteredProducts = useMemo(() => {
return products.filter(p =>
(!brand || p.brandName === brand) &&
(!department || p.department === department) &&
- (!category || p.category === category) &&
- (!subcategory || p.subcategory === subcategory)
+ (!category || String(p.categoryId) === category) &&
+ (!subcategory || String(p.subcategoryId) === subcategory)
);
}, [products, brand, department, category, subcategory]);
@@ -465,25 +475,18 @@ export default function PartsPage() {
{brandOptions.map(opt => )}
-
-
-
-
-
setSubcategory(e.target.value)}>
- {subcategoryOptions.map(opt => )}
+ {subcategoryOptions.map(opt => )}
@@ -505,19 +508,19 @@ export default function PartsPage() {
) : paginatedProducts.length === 0 ? (
| No products found. |
) : (
- paginatedProducts.map((product, i) => (
-
- {columns.map(col => (
-
- {col === 'imageUrl' && product[col] ? (
-
- ) : (
- product[col] ?? ''
- )}
- |
- ))}
-
- ))
+ paginatedProducts
+ .filter(product => columns.some(col => product[col] && String(product[col]).trim() !== ''))
+ .map((product, i) => (
+
+ {columns.map(col => (
+ |
+ {typeof product[col] === 'object' && product[col] !== null
+ ? JSON.stringify(product[col])
+ : product[col] ?? ''}
+ |
+ ))}
+
+ ))
)}
diff --git a/src/app/api/products/[slug]/route.ts b/src/app/api/products/[slug]/route.ts
index 5cfdf07..e51c1b4 100644
--- a/src/app/api/products/[slug]/route.ts
+++ b/src/app/api/products/[slug]/route.ts
@@ -1,5 +1,5 @@
import { db } from '@/db';
-import { bb_products } from '@/db/schema';
+import { products } from '@/db/schema';
function slugify(name: string): string {
return name
@@ -13,7 +13,7 @@ export async function GET(
{ params }: { params: { slug: string } }
) {
try {
- const allProducts = await db.select().from(bb_products);
+ const allProducts = await db.select().from(products);
const mapped = allProducts.map((item: any) => ({
id: item.uuid,
name: item.productName,
diff --git a/src/app/api/products/route.ts b/src/app/api/products/route.ts
index cc25942..fb4b6f1 100644
--- a/src/app/api/products/route.ts
+++ b/src/app/api/products/route.ts
@@ -1,5 +1,5 @@
import { db } from '@/db';
-import { bb_products } from '@/db/schema';
+import { products } from '@/db/schema';
import { NextResponse } from 'next/server';
import { sql } from 'drizzle-orm';
@@ -18,11 +18,11 @@ export async function GET(req: Request) {
const offset = (page - 1) * limit;
// Get total count using raw SQL
- const totalResult = await db.execute(sql`SELECT COUNT(*)::int AS count FROM bb_products`);
+ const totalResult = await db.execute(sql`SELECT COUNT(*)::int AS count FROM products`);
const total = Number(totalResult.rows?.[0]?.count || 0);
// Get paginated products
- const allProducts = await db.select().from(bb_products).limit(limit).offset(offset);
+ const allProducts = await db.select().from(products).limit(limit).offset(offset);
const mapped = allProducts.map((item: any) => ({
...item,
slug: slugify(item.productName || item.product_name || item.name || ''),
diff --git a/src/app/api/test-products/route.ts b/src/app/api/test-products/route.ts
index 8072201..7ccad9d 100644
--- a/src/app/api/test-products/route.ts
+++ b/src/app/api/test-products/route.ts
@@ -1,9 +1,9 @@
import { db } from "@/db";
-import { bb_products } from "@/db/schema";
+import { products } from "@/db/schema";
export async function GET() {
try {
- const allProducts = await db.select().from(bb_products).limit(50);
+ const allProducts = await db.select().from(products).limit(50);
const mapped = allProducts.map((item: any) => ({
id: item.uuid,
name: item.productName,
diff --git a/src/db/schema-org.ts b/src/db/schema-org.ts
new file mode 100644
index 0000000..54bce38
--- /dev/null
+++ b/src/db/schema-org.ts
@@ -0,0 +1,571 @@
+import { pgTableCreator, integer, varchar, text, numeric, timestamp, unique, check, date, boolean, uuid, bigint, real, doublePrecision, primaryKey, pgView, index, serial } from "drizzle-orm/pg-core";
+import { relations, sql } from "drizzle-orm";
+import { DATABASE_PREFIX as prefix } from "@/lib/constants";
+
+export const pgTable = pgTableCreator((name) => (prefix == "" || prefix == null) ? name: `${prefix}_${name}`);
+///
+export const products = pgTable("products", {
+ id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "products_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
+ name: varchar({ length: 255 }).notNull(),
+ description: text().notNull(),
+ price: numeric().notNull(),
+ resellerId: integer("reseller_id").notNull(),
+ categoryId: integer("category_id").notNull(),
+ stockQty: integer("stock_qty").default(0),
+ updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
+ deletedAt: timestamp("deleted_at", { mode: 'string' }),
+});
+
+export const categories = pgTable("categories", {
+ id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "categories_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
+ name: varchar({ length: 100 }).notNull(),
+ parentCategoryId: integer("parent_category_id"),
+ updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
+ deletedAt: timestamp("deleted_at", { mode: 'string' }),
+ uuid: uuid().defaultRandom(),
+});
+
+export const productFeeds = pgTable("product_feeds", {
+ id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "productfeeds_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
+ resellerId: integer("reseller_id").notNull(),
+ feedUrl: varchar("feed_url", { length: 255 }).notNull(),
+ lastUpdate: timestamp("last_update", { mode: 'string' }),
+ updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
+ deletedAt: timestamp("deleted_at", { mode: 'string' }),
+ uuid: uuid().defaultRandom(),
+}, (table) => {
+ return {
+ productFeedsUuidUnique: unique("product_feeds_uuid_unique").on(table.uuid),
+ }
+});
+
+export const userActivityLog = pgTable("user_activity_log", {
+ // You can use { mode: "bigint" } if numbers are exceeding js number limitations
+ id: bigint({ mode: "number" }).primaryKey().generatedAlwaysAsIdentity({ name: "user_activity_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
+ // You can use { mode: "bigint" } if numbers are exceeding js number limitations
+ userId: bigint("user_id", { mode: "number" }).notNull(),
+ activity: text().notNull(),
+ timestamp: timestamp({ mode: 'string' }).default(sql`CURRENT_TIMESTAMP`),
+});
+
+export const brands = pgTable("brands", {
+ id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "brands_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
+ name: varchar({ length: 100 }).notNull(),
+ updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
+ deletedAt: timestamp("deleted_at", { mode: 'string' }),
+ uuid: uuid().defaultRandom(),
+}, (table) => {
+ return {
+ brandsUuidUnique: unique("brands_uuid_unique").on(table.uuid),
+ }
+});
+
+
+export const manufacturer = pgTable("manufacturer", {
+ id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "manufacturer_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
+ name: varchar({ length: 100 }).notNull(),
+ updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
+ deletedAt: timestamp("deleted_at", { mode: 'string' }),
+ uuid: uuid().defaultRandom(),
+}, (table) => {
+ return {
+ manufacturerUuidUnique: unique("manufacturer_uuid_unique").on(table.uuid),
+ }
+});
+
+export const states = pgTable("states", {
+ id: integer().primaryKey().generatedByDefaultAsIdentity({ name: "states_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
+ state: varchar({ length: 50 }),
+ abbreviation: varchar({ length: 50 }),
+});
+
+export const componentType = pgTable("component_type", {
+ id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "component_type_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
+ name: varchar({ length: 100 }).notNull(),
+ updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
+ deletedAt: timestamp("deleted_at", { mode: 'string' }),
+ uuid: uuid().defaultRandom(),
+}, (table) => {
+ return {
+ componentTypeUuidUnique: unique("component_type_uuid_unique").on(table.uuid),
+ }
+});
+
+export const aeroPrecision = pgTable("aero_precision", {
+ sku: text().primaryKey().notNull(),
+ manufacturerId: text("manufacturer_id"),
+ brandName: text("brand_name"),
+ productName: text("product_name"),
+ longDescription: text("long_description"),
+ shortDescription: text("short_description"),
+ department: text(),
+ category: text(),
+ subcategory: text(),
+ thumbUrl: text("thumb_url"),
+ imageUrl: text("image_url"),
+ buyLink: text("buy_link"),
+ keywords: text(),
+ reviews: text(),
+ retailPrice: numeric("retail_price"),
+ salePrice: numeric("sale_price"),
+ brandPageLink: text("brand_page_link"),
+ brandLogoImage: text("brand_logo_image"),
+ productPageViewTracking: text("product_page_view_tracking"),
+ variantsXml: text("variants_xml"),
+ mediumImageUrl: text("medium_image_url"),
+ productContentWidget: text("product_content_widget"),
+ googleCategorization: text("google_categorization"),
+ itemBasedCommission: text("item_based_commission"),
+ uuid: uuid().defaultRandom(),
+});
+
+export const compartment = pgTable("compartment", {
+ id: uuid().defaultRandom().primaryKey().notNull(),
+ name: varchar({ length: 100 }).notNull(),
+ description: varchar({ length: 300 }),
+ updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
+ deletedAt: timestamp("deleted_at", { mode: 'string' }),
+});
+
+export const builds = pgTable("builds", {
+ id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "build_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
+ accountId: integer("account_id").notNull(),
+ name: varchar({ length: 255 }).notNull(),
+ description: text(),
+ updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
+ deletedAt: timestamp("deleted_at", { mode: 'string' }),
+ uuid: uuid().defaultRandom(),
+}, (table) => {
+ return {
+ buildsUuidUnique: unique("builds_uuid_unique").on(table.uuid),
+ }
+});
+
+export const bb_products = pgTable("bb_products", {
+ uuid: uuid().defaultRandom().primaryKey().notNull(),
+ upc: varchar("UPC", { length: 100 }),
+ sku: varchar("SKU", { length: 50 }),
+ manufacturerId: varchar("MANUFACTURER_ID", { length: 50 }),
+ brandName: varchar("BRAND_NAME", { length: 50 }),
+ productName: varchar("PRODUCT_NAME", { length: 255 }),
+ longDescription: text("LONG_DESCRIPTION"),
+ shortDescription: varchar("SHORT_DESCRIPTION", { length: 500 }),
+ department: varchar("DEPARTMENT", { length: 100 }),
+ category: varchar("CATEGORY", { length: 100 }),
+ subcategory: varchar("SUBCATEGORY", { length: 100 }),
+ thumbUrl: varchar("THUMB_URL", { length: 500 }),
+ imageUrl: varchar("IMAGE_URL", { length: 500 }),
+ buyLink: varchar("BUY_LINK", { length: 500 }),
+ keywords: varchar("KEYWORDS", { length: 500 }),
+ reviews: varchar("REVIEWS", { length: 500 }),
+ retailPrice: varchar("RETAIL_PRICE", { length: 50 }),
+ salePrice: varchar("SALE_PRICE", { length: 50 }),
+ brandPageLink: varchar("BRAND_PAGE_LINK", { length: 500 }),
+ brandLogoImage: varchar("BRAND_LOGO_IMAGE", { length: 500 }),
+ productPageViewTracking: varchar("PRODUCT_PAGE_VIEW_TRACKING", { length: 500 }),
+ parentGroupId: varchar("PARENT_GROUP_ID", { length: 200 }),
+ fineline: varchar("FINELINE", { length: 200 }),
+ superfineline: varchar("SUPERFINELINE", { length: 200 }),
+ modelnumber: varchar("MODELNUMBER", { length: 100 }),
+ caliber: varchar("CALIBER", { length: 200 }),
+ mediumImageUrl: varchar("MEDIUM_IMAGE_URL", { length: 500 }),
+ productContentWidget: varchar("PRODUCT_CONTENT_WIDGET", { length: 500 }),
+ googleCategorization: varchar("GOOGLE_CATEGORIZATION", { length: 500 }),
+ itemBasedCommission: varchar("ITEM_BASED_COMMISSION", { length: 500 }),
+ itemBasedCommissionRate: varchar("ITEM_BASED_COMMISSION RATE", { length: 50 }),
+ updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
+ deletedAt: timestamp("deleted_at", { mode: 'string' }),
+});
+
+export const psa_old = pgTable("psa_old", {
+ sku: varchar("SKU", { length: 50 }),
+ manufacturerId: varchar("MANUFACTURER_ID", { length: 50 }),
+ brandName: varchar("BRAND_NAME", { length: 50 }),
+ productName: varchar("PRODUCT_NAME", { length: 255 }),
+ longDescription: text("LONG_DESCRIPTION"),
+ shortDescription: varchar("SHORT_DESCRIPTION", { length: 50 }),
+ department: varchar("DEPARTMENT", { length: 50 }),
+ category: varchar("CATEGORY", { length: 50 }),
+ subcategory: varchar("SUBCATEGORY", { length: 50 }),
+ thumbUrl: varchar("THUMB_URL", { length: 50 }),
+ imageUrl: varchar("IMAGE_URL", { length: 50 }),
+ buyLink: varchar("BUY_LINK", { length: 128 }),
+ keywords: varchar("KEYWORDS", { length: 50 }),
+ reviews: varchar("REVIEWS", { length: 50 }),
+ retailPrice: real("RETAIL_PRICE"),
+ salePrice: real("SALE_PRICE"),
+ brandPageLink: varchar("BRAND_PAGE_LINK", { length: 50 }),
+ brandLogoImage: varchar("BRAND_LOGO_IMAGE", { length: 50 }),
+ productPageViewTracking: varchar("PRODUCT_PAGE_VIEW_TRACKING", { length: 256 }),
+ parentGroupId: varchar("PARENT_GROUP_ID", { length: 50 }),
+ fineline: varchar("FINELINE", { length: 50 }),
+ superfineline: varchar("SUPERFINELINE", { length: 200 }),
+ modelnumber: varchar("MODELNUMBER", { length: 50 }),
+ caliber: varchar("CALIBER", { length: 200 }),
+ upc: varchar("UPC", { length: 100 }),
+ mediumImageUrl: varchar("MEDIUM_IMAGE_URL", { length: 50 }),
+ productContentWidget: varchar("PRODUCT_CONTENT_WIDGET", { length: 256 }),
+ googleCategorization: varchar("GOOGLE_CATEGORIZATION", { length: 50 }),
+ itemBasedCommission: varchar("ITEM_BASED_COMMISSION", { length: 50 }),
+ uuid: uuid().defaultRandom(),
+});
+export const psa = pgTable("psa", {
+ id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "psa_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
+ sku: varchar("SKU", { length: 50 }),
+ manufacturerId: varchar("MANUFACTURER_ID", { length: 50 }),
+ brandName: varchar("BRAND_NAME", { length: 50 }),
+ productName: varchar("PRODUCT_NAME", { length: 255 }),
+ longDescription: text("LONG_DESCRIPTION"),
+ shortDescription: varchar("SHORT_DESCRIPTION", { length: 50 }),
+ department: varchar("DEPARTMENT", { length: 50 }),
+ category: varchar("CATEGORY", { length: 50 }),
+ subcategory: varchar("SUBCATEGORY", { length: 50 }),
+ thumbUrl: varchar("THUMB_URL", { length: 50 }),
+ imageUrl: varchar("IMAGE_URL", { length: 50 }),
+ buyLink: varchar("BUY_LINK", { length: 128 }),
+ keywords: varchar("KEYWORDS", { length: 50 }),
+ reviews: varchar("REVIEWS", { length: 50 }),
+ retailPrice: real("RETAIL_PRICE"),
+ salePrice: real("SALE_PRICE"),
+ brandPageLink: varchar("BRAND_PAGE_LINK", { length: 50 }),
+ brandLogoImage: varchar("BRAND_LOGO_IMAGE", { length: 50 }),
+ productPageViewTracking: varchar("PRODUCT_PAGE_VIEW_TRACKING", { length: 256 }),
+ parentGroupId: varchar("PARENT_GROUP_ID", { length: 50 }),
+ fineline: varchar("FINELINE", { length: 50 }),
+ superfineline: varchar("SUPERFINELINE", { length: 200 }),
+ modelnumber: varchar("MODELNUMBER", { length: 50 }),
+ caliber: varchar("CALIBER", { length: 200 }),
+ upc: varchar("UPC", { length: 100 }),
+ mediumImageUrl: varchar("MEDIUM_IMAGE_URL", { length: 50 }),
+ productContentWidget: varchar("PRODUCT_CONTENT_WIDGET", { length: 256 }),
+ googleCategorization: varchar("GOOGLE_CATEGORIZATION", { length: 50 }),
+ itemBasedCommission: varchar("ITEM_BASED_COMMISSION", { length: 50 }),
+ uuid: uuid().defaultRandom(),
+});
+
+export const lipseycatalog = pgTable("lipseycatalog", {
+ id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "lipseycatalog_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
+ itemno: varchar({ length: 20 }).notNull(),
+ description1: text(),
+ description2: text(),
+ upc: varchar({ length: 20 }),
+ manufacturermodelno: varchar({ length: 30 }),
+ msrp: doublePrecision(),
+ model: text(),
+ calibergauge: text(),
+ manufacturer: text(),
+ type: text(),
+ action: text(),
+ barrellength: text(),
+ capacity: text(),
+ finish: text(),
+ overalllength: text(),
+ receiver: text(),
+ safety: text(),
+ sights: text(),
+ stockframegrips: text(),
+ magazine: text(),
+ weight: text(),
+ imagename: text(),
+ chamber: text(),
+ drilledandtapped: text(),
+ rateoftwist: text(),
+ itemtype: text(),
+ additionalfeature1: text(),
+ additionalfeature2: text(),
+ additionalfeature3: text(),
+ shippingweight: text(),
+ boundbookmanufacturer: text(),
+ boundbookmodel: text(),
+ boundbooktype: text(),
+ nfathreadpattern: text(),
+ nfaattachmentmethod: text(),
+ nfabaffletype: text(),
+ silencercanbedisassembled: text(),
+ silencerconstructionmaterial: text(),
+ nfadbreduction: text(),
+ silenceroutsidediameter: text(),
+ nfaform3Caliber: text(),
+ opticmagnification: text(),
+ maintubesize: text(),
+ adjustableobjective: text(),
+ objectivesize: text(),
+ opticadjustments: text(),
+ illuminatedreticle: text(),
+ reticle: text(),
+ exclusive: text(),
+ quantity: varchar({ length: 10 }).default(sql`NULL`),
+ allocated: text(),
+ onsale: text(),
+ price: doublePrecision(),
+ currentprice: doublePrecision(),
+ retailmap: doublePrecision(),
+ fflrequired: text(),
+ sotrequired: text(),
+ exclusivetype: text(),
+ scopecoverincluded: text(),
+ special: text(),
+ sightstype: text(),
+ case: text(),
+ choke: text(),
+ dbreduction: text(),
+ family: text(),
+ finishtype: text(),
+ frame: text(),
+ griptype: varchar({ length: 30 }),
+ handgunslidematerial: text(),
+ countryoforigin: varchar({ length: 4 }),
+ itemlength: text(),
+ itemwidth: text(),
+ itemheight: text(),
+ packagelength: doublePrecision(),
+ packagewidth: doublePrecision(),
+ packageheight: doublePrecision(),
+ itemgroup: varchar({ length: 40 }),
+ updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
+ deletedAt: timestamp("deleted_at", { mode: 'string' }),
+});
+
+export const buildsComponents = pgTable("builds_components", {
+ id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "build_components_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
+ buildId: integer("build_id").notNull(),
+ productId: integer("product_id").notNull(),
+ updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
+ deletedAt: timestamp("deleted_at", { mode: 'string' }),
+ uuid: uuid().defaultRandom(),
+}, (table) => {
+ return {
+ buildsComponentsUuidUnique: unique("builds_components_uuid_unique").on(table.uuid),
+ }
+});
+
+export const balResellers = pgTable("bal_resellers", {
+ id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "resellers_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
+ name: varchar({ length: 100 }).notNull(),
+ websiteUrl: varchar("website_url", { length: 255 }),
+ contactEmail: varchar("contact_email", { length: 100 }),
+ updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
+ deletedAt: timestamp("deleted_at", { mode: 'string' }),
+ uuid: uuid().defaultRandom(),
+}, (table) => {
+ return {
+ balResellersUuidUnique: unique("bal_resellers_uuid_unique").on(table.uuid),
+ }
+});
+
+export const verificationTokens = pgTable("verificationTokens", {
+ identifier: varchar("identifier").notNull(),
+ token: varchar("token").notNull(),
+ expires: timestamp("expires").notNull(),
+});
+
+export const authenticator = pgTable("authenticator", {
+ credentialId: text().notNull(),
+ userId: text().notNull(),
+ providerAccountId: text().notNull(),
+ credentialPublicKey: text().notNull(),
+ counter: integer().notNull(),
+ credentialDeviceType: text().notNull(),
+ credentialBackedUp: boolean().notNull(),
+ transports: text(),
+}, (table) => {
+ return {
+ authenticatorUserIdCredentialIdPk: primaryKey({ columns: [table.credentialId, table.userId], name: "authenticator_userId_credentialID_pk"}),
+ authenticatorCredentialIdUnique: unique("authenticator_credentialID_unique").on(table.credentialId),
+ }
+});
+
+export const accounts = pgTable("accounts", {
+ id: uuid("id").primaryKey().defaultRandom(),
+ uuid: uuid("uuid").defaultRandom(),
+ userId: uuid("user_id").notNull(),
+ type: varchar("type").notNull(),
+ provider: text().notNull(),
+ providerAccountId: varchar("provider_account_id").notNull(),
+ refreshToken: text("refresh_token"),
+ accessToken: text("access_token"),
+ expiresAt: integer("expires_at"),
+ tokenType: varchar("token_type"),
+ idToken: text("id_token"),
+ sessionState: varchar("session_state"),
+ scope: text(),
+}
+);
+
+/* export const vw_accounts = pgView("vw_accounts", {
+ uuid: uuid().defaultRandom(),
+ userId: text("user_id").notNull(),
+ type: text().notNull(),
+ provider: text().notNull(),
+ providerAccountId: text("provider_account_id").notNull(),
+ refreshToken: text("refresh_token"),
+ accessToken: text("access_token"),
+ expiresAt: integer("expires_at"),
+ tokenType: text("token_type"),
+ scope: text(),
+ idToken: text("id_token"),
+ sessionState: text("session_state"),
+ first_name: text("first_name"),
+ last_name: text("last_name"),
+
+},) */
+
+/* From here down is the authentication library Lusia tables */
+
+export const users = pgTable("user",
+ {
+ id: varchar("id", { length: 21 }).primaryKey(),
+ name: varchar("name"),
+ username: varchar({ length: 50 }),
+ discordId: varchar("discord_id", { length: 255 }).unique(),
+ email: varchar("email", { length: 255 }).unique().notNull(),
+ emailVerified: boolean("email_verified").default(false).notNull(),
+ hashedPassword: varchar("hashed_password", { length: 255 }),
+ first_name: varchar("first_name", { length: 50 }),
+ last_name: varchar("last_name", { length: 50 }),
+ full_name: varchar("full_name", { length: 50 }),
+ profilePicture: varchar("profile_picture", { length: 255 }),
+ image: text("image"),
+ dateOfBirth: date("date_of_birth"),
+ phoneNumber: varchar("phone_number", { length: 20 }),
+ createdAt: timestamp("created_at", { mode: 'string' }).default(sql`CURRENT_TIMESTAMP`),
+ updatedAt: timestamp("updated_at", { mode: 'string' }).default(sql`CURRENT_TIMESTAMP`),
+ isAdmin: boolean("is_admin").default(false),
+ lastLogin: timestamp("last_login", { mode: 'string' }),
+ buildPrivacySetting: text("build_privacy_setting").default('public'),
+ uuid: uuid().defaultRandom(),
+ avatar: varchar("avatar", { length: 255 }),
+ stripeSubscriptionId: varchar("stripe_subscription_id", { length: 191 }),
+ stripePriceId: varchar("stripe_price_id", { length: 191 }),
+ stripeCustomerId: varchar("stripe_customer_id", { length: 191 }),
+ stripeCurrentPeriodEnd: timestamp("stripe_current_period_end"),
+}, (table) => ({
+ usersUsernameKey: unique("users_username_key").on(table.username),
+ usersEmailKey: unique("users_email_key").on(table.email),
+ usersBuildPrivacySettingCheck: check("users_build_privacy_setting_check", sql`build_privacy_setting = ANY (ARRAY['private'::text, 'public'::text])`),
+ emailIdx: index("user_email_idx").on(table.email),
+ discordIdx: index("user_discord_idx").on(table.discordId),
+ }),
+);
+export type User = typeof users.$inferSelect;
+export type NewUser = typeof users.$inferInsert;
+
+export const session = pgTable(
+ "session",
+ {
+ sessionToken: varchar("sessionToken", { length: 255 }).primaryKey(),
+ userId: varchar("userId", { length: 21 }).notNull(),
+ expires: timestamp("expires", { withTimezone: true, mode: "date" }).notNull(),
+ }
+);
+
+export const emailVerificationCodes = pgTable(
+ "email_verification_codes",
+ {
+ id: serial("id").primaryKey(),
+ userId: varchar("user_id", { length: 21 }).unique().notNull(),
+ email: varchar("email", { length: 255 }).notNull(),
+ code: varchar("code", { length: 8 }).notNull(),
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }).notNull(),
+ },
+ (t) => ({
+ userIdx: index("verification_code_user_idx").on(t.userId),
+ emailIdx: index("verification_code_email_idx").on(t.email),
+ }),
+ );
+
+ export const passwordResetTokens = pgTable(
+ "password_reset_tokens",
+ {
+ id: varchar("id", { length: 40 }).primaryKey(),
+ userId: varchar("user_id", { length: 21 }).notNull(),
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }).notNull(),
+ },
+ (t) => ({
+ userIdx: index("password_token_user_idx").on(t.userId),
+ }),
+ );
+
+ export const posts = pgTable(
+ "posts",
+ {
+ id: varchar("id", { length: 15 }).primaryKey(),
+ userId: varchar("user_id", { length: 255 }).notNull(),
+ title: varchar("title", { length: 255 }).notNull(),
+ excerpt: varchar("excerpt", { length: 255 }).notNull(),
+ content: text("content").notNull(),
+ status: varchar("status", { length: 10, enum: ["draft", "published"] })
+ .default("draft")
+ .notNull(),
+ tags: varchar("tags", { length: 255 }),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at", { mode: "date" }).$onUpdate(() => new Date()),
+ },
+ (t) => ({
+ userIdx: index("post_user_idx").on(t.userId),
+ createdAtIdx: index("post_created_at_idx").on(t.createdAt),
+ }),
+ );
+
+ export const postRelations = relations(posts, ({ one }) => ({
+ user: one(users, {
+ fields: [posts.userId],
+ references: [users.id],
+ }),
+ }));
+
+ export type Post = typeof posts.$inferSelect;
+ export type NewPost = typeof posts.$inferInsert;
+
+ export const vwUserSessions = pgView("vw_user_sessions", { id: varchar({ length: 255 }),
+ userId: varchar("user_id", { length: 21 }),
+ uId: varchar("u_id", { length: 21 }),
+ uEmail: varchar("u_email", { length: 255 }),
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: 'string' }),
+ createdAt: timestamp("created_at", { mode: 'string' }),
+ updatedAt: timestamp("updated_at", { mode: 'string' }),
+}).existing();
+//as(sql`SELECT s.id, s.user_id, u.id AS u_id, u.email AS u_email, s.expires_at, s.created_at, s.updated_at FROM sessions s, users u WHERE s.user_id::text = u.id::text`);
+
+// Default Drizzle File
+
+// import { pgTable, serial, text, integer, timestamp } from "drizzle-orm/pg-core";
+
+// export const products = pgTable("products", {
+// id: serial("id").primaryKey(),
+// name: text("name").notNull(),
+// description: text("description"),
+// price: integer("price"),
+// createdAt: timestamp("created_at").defaultNow(),
+// // Add more fields as needed
+// });
+
+export const affiliateCategoryMap = pgTable("affiliate_category_map", {
+ id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "affiliate_category_map_id_seq", startWith: 1, increment: 1 }),
+ feedname: varchar("feedname", { length: 100 }).notNull(),
+ affiliatecategory: varchar("affiliatecategory", { length: 255 }).notNull(),
+ buildercategoryid: integer("buildercategoryid").notNull(),
+ notes: varchar("notes", { length: 255 }),
+});
+
+export const product_categories = pgTable("product_categories", {
+ id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "product_categories_id_seq", startWith: 1, increment: 1 }),
+ name: varchar({ length: 100 }).notNull(),
+ parent_category_id: integer("parent_category_id"),
+ type: varchar({ length: 50 }),
+ sort_order: integer("sort_order"),
+ created_at: timestamp("created_at", { mode: 'string' }).defaultNow(),
+ updated_at: timestamp("updated_at", { mode: 'string' }).defaultNow(),
+});
\ No newline at end of file
diff --git a/src/db/schema.ts b/src/db/schema.ts
index 54bce38..7bf1ae8 100644
--- a/src/db/schema.ts
+++ b/src/db/schema.ts
@@ -1,571 +1,108 @@
-import { pgTableCreator, integer, varchar, text, numeric, timestamp, unique, check, date, boolean, uuid, bigint, real, doublePrecision, primaryKey, pgView, index, serial } from "drizzle-orm/pg-core";
-import { relations, sql } from "drizzle-orm";
-import { DATABASE_PREFIX as prefix } from "@/lib/constants";
+import { pgTable, foreignKey, serial, varchar, integer, doublePrecision, timestamp, unique, text, numeric, boolean, jsonb } from "drizzle-orm/pg-core"
+import { sql } from "drizzle-orm"
-export const pgTable = pgTableCreator((name) => (prefix == "" || prefix == null) ? name: `${prefix}_${name}`);
-///
-export const products = pgTable("products", {
- id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "products_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
- name: varchar({ length: 255 }).notNull(),
- description: text().notNull(),
- price: numeric().notNull(),
- resellerId: integer("reseller_id").notNull(),
- categoryId: integer("category_id").notNull(),
- stockQty: integer("stock_qty").default(0),
- updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
- createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
- deletedAt: timestamp("deleted_at", { mode: 'string' }),
-});
+
+
+export const productCategoryMappings = pgTable("product_category_mappings", {
+ id: serial().primaryKey().notNull(),
+ feedName: varchar("feed_name", { length: 255 }),
+ feedCategoryValue: varchar("feed_category_value", { length: 255 }),
+ canonicalCategoryId: integer("canonical_category_id"),
+ confidenceScore: doublePrecision("confidence_score"),
+ lastReviewedBy: varchar("last_reviewed_by", { length: 255 }),
+ lastReviewedAt: timestamp("last_reviewed_at", { mode: 'string' }),
+}, (table) => [
+ foreignKey({
+ columns: [table.canonicalCategoryId],
+ foreignColumns: [categories.id],
+ name: "product_category_mappings_canonical_category_id_fkey"
+ }),
+]);
export const categories = pgTable("categories", {
- id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "categories_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
- name: varchar({ length: 100 }).notNull(),
- parentCategoryId: integer("parent_category_id"),
- updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
- createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
- deletedAt: timestamp("deleted_at", { mode: 'string' }),
- uuid: uuid().defaultRandom(),
-});
-
-export const productFeeds = pgTable("product_feeds", {
- id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "productfeeds_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
- resellerId: integer("reseller_id").notNull(),
- feedUrl: varchar("feed_url", { length: 255 }).notNull(),
- lastUpdate: timestamp("last_update", { mode: 'string' }),
- updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
- createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
- deletedAt: timestamp("deleted_at", { mode: 'string' }),
- uuid: uuid().defaultRandom(),
-}, (table) => {
- return {
- productFeedsUuidUnique: unique("product_feeds_uuid_unique").on(table.uuid),
- }
-});
-
-export const userActivityLog = pgTable("user_activity_log", {
- // You can use { mode: "bigint" } if numbers are exceeding js number limitations
- id: bigint({ mode: "number" }).primaryKey().generatedAlwaysAsIdentity({ name: "user_activity_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
- // You can use { mode: "bigint" } if numbers are exceeding js number limitations
- userId: bigint("user_id", { mode: "number" }).notNull(),
- activity: text().notNull(),
- timestamp: timestamp({ mode: 'string' }).default(sql`CURRENT_TIMESTAMP`),
-});
-
-export const brands = pgTable("brands", {
- id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "brands_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
- name: varchar({ length: 100 }).notNull(),
- updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
- createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
- deletedAt: timestamp("deleted_at", { mode: 'string' }),
- uuid: uuid().defaultRandom(),
-}, (table) => {
- return {
- brandsUuidUnique: unique("brands_uuid_unique").on(table.uuid),
- }
-});
-
-
-export const manufacturer = pgTable("manufacturer", {
- id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "manufacturer_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
- name: varchar({ length: 100 }).notNull(),
- updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
- createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
- deletedAt: timestamp("deleted_at", { mode: 'string' }),
- uuid: uuid().defaultRandom(),
-}, (table) => {
- return {
- manufacturerUuidUnique: unique("manufacturer_uuid_unique").on(table.uuid),
- }
-});
-
-export const states = pgTable("states", {
- id: integer().primaryKey().generatedByDefaultAsIdentity({ name: "states_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
- state: varchar({ length: 50 }),
- abbreviation: varchar({ length: 50 }),
-});
-
-export const componentType = pgTable("component_type", {
- id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "component_type_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
- name: varchar({ length: 100 }).notNull(),
- updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
- createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
- deletedAt: timestamp("deleted_at", { mode: 'string' }),
- uuid: uuid().defaultRandom(),
-}, (table) => {
- return {
- componentTypeUuidUnique: unique("component_type_uuid_unique").on(table.uuid),
- }
-});
-
-export const aeroPrecision = pgTable("aero_precision", {
- sku: text().primaryKey().notNull(),
- manufacturerId: text("manufacturer_id"),
- brandName: text("brand_name"),
- productName: text("product_name"),
- longDescription: text("long_description"),
- shortDescription: text("short_description"),
- department: text(),
- category: text(),
- subcategory: text(),
- thumbUrl: text("thumb_url"),
- imageUrl: text("image_url"),
- buyLink: text("buy_link"),
- keywords: text(),
- reviews: text(),
- retailPrice: numeric("retail_price"),
- salePrice: numeric("sale_price"),
- brandPageLink: text("brand_page_link"),
- brandLogoImage: text("brand_logo_image"),
- productPageViewTracking: text("product_page_view_tracking"),
- variantsXml: text("variants_xml"),
- mediumImageUrl: text("medium_image_url"),
- productContentWidget: text("product_content_widget"),
- googleCategorization: text("google_categorization"),
- itemBasedCommission: text("item_based_commission"),
- uuid: uuid().defaultRandom(),
-});
-
-export const compartment = pgTable("compartment", {
- id: uuid().defaultRandom().primaryKey().notNull(),
- name: varchar({ length: 100 }).notNull(),
- description: varchar({ length: 300 }),
- updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
- createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
- deletedAt: timestamp("deleted_at", { mode: 'string' }),
-});
-
-export const builds = pgTable("builds", {
- id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "build_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
- accountId: integer("account_id").notNull(),
+ id: serial().primaryKey().notNull(),
name: varchar({ length: 255 }).notNull(),
+ parentId: integer("parent_id"),
+ slug: varchar({ length: 255 }).notNull(),
+}, (table) => [
+ foreignKey({
+ columns: [table.parentId],
+ foreignColumns: [table.id],
+ name: "categories_parent_id_fkey"
+ }),
+ unique("categories_slug_key").on(table.slug),
+]);
+
+export const products = pgTable("products", {
+ id: serial().primaryKey().notNull(),
+ name: varchar({ length: 255 }).notNull(),
+ brand: varchar({ length: 255 }),
description: text(),
- updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
- createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
- deletedAt: timestamp("deleted_at", { mode: 'string' }),
- uuid: uuid().defaultRandom(),
-}, (table) => {
- return {
- buildsUuidUnique: unique("builds_uuid_unique").on(table.uuid),
- }
-});
+ upc: varchar({ length: 32 }),
+ mpn: varchar({ length: 64 }),
+ canonicalCategoryId: integer("canonical_category_id"),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow(),
+ updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow(),
+}, (table) => [
+ foreignKey({
+ columns: [table.canonicalCategoryId],
+ foreignColumns: [categories.id],
+ name: "products_canonical_category_id_fkey"
+ }),
+]);
-export const bb_products = pgTable("bb_products", {
- uuid: uuid().defaultRandom().primaryKey().notNull(),
- upc: varchar("UPC", { length: 100 }),
- sku: varchar("SKU", { length: 50 }),
- manufacturerId: varchar("MANUFACTURER_ID", { length: 50 }),
- brandName: varchar("BRAND_NAME", { length: 50 }),
- productName: varchar("PRODUCT_NAME", { length: 255 }),
- longDescription: text("LONG_DESCRIPTION"),
- shortDescription: varchar("SHORT_DESCRIPTION", { length: 500 }),
- department: varchar("DEPARTMENT", { length: 100 }),
- category: varchar("CATEGORY", { length: 100 }),
- subcategory: varchar("SUBCATEGORY", { length: 100 }),
- thumbUrl: varchar("THUMB_URL", { length: 500 }),
- imageUrl: varchar("IMAGE_URL", { length: 500 }),
- buyLink: varchar("BUY_LINK", { length: 500 }),
- keywords: varchar("KEYWORDS", { length: 500 }),
- reviews: varchar("REVIEWS", { length: 500 }),
- retailPrice: varchar("RETAIL_PRICE", { length: 50 }),
- salePrice: varchar("SALE_PRICE", { length: 50 }),
- brandPageLink: varchar("BRAND_PAGE_LINK", { length: 500 }),
- brandLogoImage: varchar("BRAND_LOGO_IMAGE", { length: 500 }),
- productPageViewTracking: varchar("PRODUCT_PAGE_VIEW_TRACKING", { length: 500 }),
- parentGroupId: varchar("PARENT_GROUP_ID", { length: 200 }),
- fineline: varchar("FINELINE", { length: 200 }),
- superfineline: varchar("SUPERFINELINE", { length: 200 }),
- modelnumber: varchar("MODELNUMBER", { length: 100 }),
- caliber: varchar("CALIBER", { length: 200 }),
- mediumImageUrl: varchar("MEDIUM_IMAGE_URL", { length: 500 }),
- productContentWidget: varchar("PRODUCT_CONTENT_WIDGET", { length: 500 }),
- googleCategorization: varchar("GOOGLE_CATEGORIZATION", { length: 500 }),
- itemBasedCommission: varchar("ITEM_BASED_COMMISSION", { length: 500 }),
- itemBasedCommissionRate: varchar("ITEM_BASED_COMMISSION RATE", { length: 50 }),
- updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
- createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
- deletedAt: timestamp("deleted_at", { mode: 'string' }),
-});
+export const offers = pgTable("offers", {
+ id: serial().primaryKey().notNull(),
+ productId: integer("product_id"),
+ feedName: varchar("feed_name", { length: 255 }).notNull(),
+ feedSku: varchar("feed_sku", { length: 255 }),
+ price: numeric({ precision: 10, scale: 2 }),
+ url: text(),
+ inStock: boolean("in_stock"),
+ vendor: varchar({ length: 255 }),
+ lastSeenAt: timestamp("last_seen_at", { mode: 'string' }).defaultNow(),
+ rawData: jsonb("raw_data"),
+}, (table) => [
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [products.id],
+ name: "offers_product_id_fkey"
+ }).onDelete("cascade"),
+ unique("offers_product_id_feed_name_feed_sku_key").on(table.productId, table.feedName, table.feedSku),
+ unique("offers_feed_unique").on(table.feedName, table.feedSku),
+]);
-export const psa_old = pgTable("psa_old", {
- sku: varchar("SKU", { length: 50 }),
- manufacturerId: varchar("MANUFACTURER_ID", { length: 50 }),
- brandName: varchar("BRAND_NAME", { length: 50 }),
- productName: varchar("PRODUCT_NAME", { length: 255 }),
- longDescription: text("LONG_DESCRIPTION"),
- shortDescription: varchar("SHORT_DESCRIPTION", { length: 50 }),
- department: varchar("DEPARTMENT", { length: 50 }),
- category: varchar("CATEGORY", { length: 50 }),
- subcategory: varchar("SUBCATEGORY", { length: 50 }),
- thumbUrl: varchar("THUMB_URL", { length: 50 }),
- imageUrl: varchar("IMAGE_URL", { length: 50 }),
- buyLink: varchar("BUY_LINK", { length: 128 }),
- keywords: varchar("KEYWORDS", { length: 50 }),
- reviews: varchar("REVIEWS", { length: 50 }),
- retailPrice: real("RETAIL_PRICE"),
- salePrice: real("SALE_PRICE"),
- brandPageLink: varchar("BRAND_PAGE_LINK", { length: 50 }),
- brandLogoImage: varchar("BRAND_LOGO_IMAGE", { length: 50 }),
- productPageViewTracking: varchar("PRODUCT_PAGE_VIEW_TRACKING", { length: 256 }),
- parentGroupId: varchar("PARENT_GROUP_ID", { length: 50 }),
- fineline: varchar("FINELINE", { length: 50 }),
- superfineline: varchar("SUPERFINELINE", { length: 200 }),
- modelnumber: varchar("MODELNUMBER", { length: 50 }),
- caliber: varchar("CALIBER", { length: 200 }),
- upc: varchar("UPC", { length: 100 }),
- mediumImageUrl: varchar("MEDIUM_IMAGE_URL", { length: 50 }),
- productContentWidget: varchar("PRODUCT_CONTENT_WIDGET", { length: 256 }),
- googleCategorization: varchar("GOOGLE_CATEGORIZATION", { length: 50 }),
- itemBasedCommission: varchar("ITEM_BASED_COMMISSION", { length: 50 }),
- uuid: uuid().defaultRandom(),
-});
-export const psa = pgTable("psa", {
- id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "psa_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
- sku: varchar("SKU", { length: 50 }),
- manufacturerId: varchar("MANUFACTURER_ID", { length: 50 }),
- brandName: varchar("BRAND_NAME", { length: 50 }),
- productName: varchar("PRODUCT_NAME", { length: 255 }),
- longDescription: text("LONG_DESCRIPTION"),
- shortDescription: varchar("SHORT_DESCRIPTION", { length: 50 }),
- department: varchar("DEPARTMENT", { length: 50 }),
- category: varchar("CATEGORY", { length: 50 }),
- subcategory: varchar("SUBCATEGORY", { length: 50 }),
- thumbUrl: varchar("THUMB_URL", { length: 50 }),
- imageUrl: varchar("IMAGE_URL", { length: 50 }),
- buyLink: varchar("BUY_LINK", { length: 128 }),
- keywords: varchar("KEYWORDS", { length: 50 }),
- reviews: varchar("REVIEWS", { length: 50 }),
- retailPrice: real("RETAIL_PRICE"),
- salePrice: real("SALE_PRICE"),
- brandPageLink: varchar("BRAND_PAGE_LINK", { length: 50 }),
- brandLogoImage: varchar("BRAND_LOGO_IMAGE", { length: 50 }),
- productPageViewTracking: varchar("PRODUCT_PAGE_VIEW_TRACKING", { length: 256 }),
- parentGroupId: varchar("PARENT_GROUP_ID", { length: 50 }),
- fineline: varchar("FINELINE", { length: 50 }),
- superfineline: varchar("SUPERFINELINE", { length: 200 }),
- modelnumber: varchar("MODELNUMBER", { length: 50 }),
- caliber: varchar("CALIBER", { length: 200 }),
- upc: varchar("UPC", { length: 100 }),
- mediumImageUrl: varchar("MEDIUM_IMAGE_URL", { length: 50 }),
- productContentWidget: varchar("PRODUCT_CONTENT_WIDGET", { length: 256 }),
- googleCategorization: varchar("GOOGLE_CATEGORIZATION", { length: 50 }),
- itemBasedCommission: varchar("ITEM_BASED_COMMISSION", { length: 50 }),
- uuid: uuid().defaultRandom(),
-});
+export const offerPriceHistory = pgTable("offer_price_history", {
+ id: serial().primaryKey().notNull(),
+ offerId: integer("offer_id"),
+ price: numeric({ precision: 10, scale: 2 }).notNull(),
+ seenAt: timestamp("seen_at", { mode: 'string' }).defaultNow(),
+}, (table) => [
+ foreignKey({
+ columns: [table.offerId],
+ foreignColumns: [offers.id],
+ name: "offer_price_history_offer_id_fkey"
+ }).onDelete("cascade"),
+]);
-export const lipseycatalog = pgTable("lipseycatalog", {
- id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "lipseycatalog_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
- itemno: varchar({ length: 20 }).notNull(),
- description1: text(),
- description2: text(),
- upc: varchar({ length: 20 }),
- manufacturermodelno: varchar({ length: 30 }),
- msrp: doublePrecision(),
- model: text(),
- calibergauge: text(),
- manufacturer: text(),
- type: text(),
- action: text(),
- barrellength: text(),
- capacity: text(),
- finish: text(),
- overalllength: text(),
- receiver: text(),
- safety: text(),
- sights: text(),
- stockframegrips: text(),
- magazine: text(),
- weight: text(),
- imagename: text(),
- chamber: text(),
- drilledandtapped: text(),
- rateoftwist: text(),
- itemtype: text(),
- additionalfeature1: text(),
- additionalfeature2: text(),
- additionalfeature3: text(),
- shippingweight: text(),
- boundbookmanufacturer: text(),
- boundbookmodel: text(),
- boundbooktype: text(),
- nfathreadpattern: text(),
- nfaattachmentmethod: text(),
- nfabaffletype: text(),
- silencercanbedisassembled: text(),
- silencerconstructionmaterial: text(),
- nfadbreduction: text(),
- silenceroutsidediameter: text(),
- nfaform3Caliber: text(),
- opticmagnification: text(),
- maintubesize: text(),
- adjustableobjective: text(),
- objectivesize: text(),
- opticadjustments: text(),
- illuminatedreticle: text(),
- reticle: text(),
- exclusive: text(),
- quantity: varchar({ length: 10 }).default(sql`NULL`),
- allocated: text(),
- onsale: text(),
- price: doublePrecision(),
- currentprice: doublePrecision(),
- retailmap: doublePrecision(),
- fflrequired: text(),
- sotrequired: text(),
- exclusivetype: text(),
- scopecoverincluded: text(),
- special: text(),
- sightstype: text(),
- case: text(),
- choke: text(),
- dbreduction: text(),
- family: text(),
- finishtype: text(),
- frame: text(),
- griptype: varchar({ length: 30 }),
- handgunslidematerial: text(),
- countryoforigin: varchar({ length: 4 }),
- itemlength: text(),
- itemwidth: text(),
- itemheight: text(),
- packagelength: doublePrecision(),
- packagewidth: doublePrecision(),
- packageheight: doublePrecision(),
- itemgroup: varchar({ length: 40 }),
- updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
- createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
- deletedAt: timestamp("deleted_at", { mode: 'string' }),
-});
+export const feeds = pgTable("feeds", {
+ id: serial().primaryKey().notNull(),
+ name: varchar({ length: 255 }).notNull(),
+ url: text(),
+ lastImportedAt: timestamp("last_imported_at", { mode: 'string' }),
+}, (table) => [
+ unique("feeds_name_key").on(table.name),
+]);
-export const buildsComponents = pgTable("builds_components", {
- id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "build_components_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
- buildId: integer("build_id").notNull(),
- productId: integer("product_id").notNull(),
- updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
- createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
- deletedAt: timestamp("deleted_at", { mode: 'string' }),
- uuid: uuid().defaultRandom(),
-}, (table) => {
- return {
- buildsComponentsUuidUnique: unique("builds_components_uuid_unique").on(table.uuid),
- }
-});
-
-export const balResellers = pgTable("bal_resellers", {
- id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "resellers_id_seq", startWith: 1, increment: 1, minValue: 1, maxValue: 2147483647, cache: 1 }),
- name: varchar({ length: 100 }).notNull(),
- websiteUrl: varchar("website_url", { length: 255 }),
- contactEmail: varchar("contact_email", { length: 100 }),
- updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(),
- createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
- deletedAt: timestamp("deleted_at", { mode: 'string' }),
- uuid: uuid().defaultRandom(),
-}, (table) => {
- return {
- balResellersUuidUnique: unique("bal_resellers_uuid_unique").on(table.uuid),
- }
-});
-
-export const verificationTokens = pgTable("verificationTokens", {
- identifier: varchar("identifier").notNull(),
- token: varchar("token").notNull(),
- expires: timestamp("expires").notNull(),
-});
-
-export const authenticator = pgTable("authenticator", {
- credentialId: text().notNull(),
- userId: text().notNull(),
- providerAccountId: text().notNull(),
- credentialPublicKey: text().notNull(),
- counter: integer().notNull(),
- credentialDeviceType: text().notNull(),
- credentialBackedUp: boolean().notNull(),
- transports: text(),
-}, (table) => {
- return {
- authenticatorUserIdCredentialIdPk: primaryKey({ columns: [table.credentialId, table.userId], name: "authenticator_userId_credentialID_pk"}),
- authenticatorCredentialIdUnique: unique("authenticator_credentialID_unique").on(table.credentialId),
- }
-});
-
-export const accounts = pgTable("accounts", {
- id: uuid("id").primaryKey().defaultRandom(),
- uuid: uuid("uuid").defaultRandom(),
- userId: uuid("user_id").notNull(),
- type: varchar("type").notNull(),
- provider: text().notNull(),
- providerAccountId: varchar("provider_account_id").notNull(),
- refreshToken: text("refresh_token"),
- accessToken: text("access_token"),
- expiresAt: integer("expires_at"),
- tokenType: varchar("token_type"),
- idToken: text("id_token"),
- sessionState: varchar("session_state"),
- scope: text(),
-}
-);
-
-/* export const vw_accounts = pgView("vw_accounts", {
- uuid: uuid().defaultRandom(),
- userId: text("user_id").notNull(),
- type: text().notNull(),
- provider: text().notNull(),
- providerAccountId: text("provider_account_id").notNull(),
- refreshToken: text("refresh_token"),
- accessToken: text("access_token"),
- expiresAt: integer("expires_at"),
- tokenType: text("token_type"),
- scope: text(),
- idToken: text("id_token"),
- sessionState: text("session_state"),
- first_name: text("first_name"),
- last_name: text("last_name"),
-
-},) */
-
-/* From here down is the authentication library Lusia tables */
-
-export const users = pgTable("user",
- {
- id: varchar("id", { length: 21 }).primaryKey(),
- name: varchar("name"),
- username: varchar({ length: 50 }),
- discordId: varchar("discord_id", { length: 255 }).unique(),
- email: varchar("email", { length: 255 }).unique().notNull(),
- emailVerified: boolean("email_verified").default(false).notNull(),
- hashedPassword: varchar("hashed_password", { length: 255 }),
- first_name: varchar("first_name", { length: 50 }),
- last_name: varchar("last_name", { length: 50 }),
- full_name: varchar("full_name", { length: 50 }),
- profilePicture: varchar("profile_picture", { length: 255 }),
- image: text("image"),
- dateOfBirth: date("date_of_birth"),
- phoneNumber: varchar("phone_number", { length: 20 }),
- createdAt: timestamp("created_at", { mode: 'string' }).default(sql`CURRENT_TIMESTAMP`),
- updatedAt: timestamp("updated_at", { mode: 'string' }).default(sql`CURRENT_TIMESTAMP`),
- isAdmin: boolean("is_admin").default(false),
- lastLogin: timestamp("last_login", { mode: 'string' }),
- buildPrivacySetting: text("build_privacy_setting").default('public'),
- uuid: uuid().defaultRandom(),
- avatar: varchar("avatar", { length: 255 }),
- stripeSubscriptionId: varchar("stripe_subscription_id", { length: 191 }),
- stripePriceId: varchar("stripe_price_id", { length: 191 }),
- stripeCustomerId: varchar("stripe_customer_id", { length: 191 }),
- stripeCurrentPeriodEnd: timestamp("stripe_current_period_end"),
-}, (table) => ({
- usersUsernameKey: unique("users_username_key").on(table.username),
- usersEmailKey: unique("users_email_key").on(table.email),
- usersBuildPrivacySettingCheck: check("users_build_privacy_setting_check", sql`build_privacy_setting = ANY (ARRAY['private'::text, 'public'::text])`),
- emailIdx: index("user_email_idx").on(table.email),
- discordIdx: index("user_discord_idx").on(table.discordId),
- }),
-);
-export type User = typeof users.$inferSelect;
-export type NewUser = typeof users.$inferInsert;
-
-export const session = pgTable(
- "session",
- {
- sessionToken: varchar("sessionToken", { length: 255 }).primaryKey(),
- userId: varchar("userId", { length: 21 }).notNull(),
- expires: timestamp("expires", { withTimezone: true, mode: "date" }).notNull(),
- }
-);
-
-export const emailVerificationCodes = pgTable(
- "email_verification_codes",
- {
- id: serial("id").primaryKey(),
- userId: varchar("user_id", { length: 21 }).unique().notNull(),
- email: varchar("email", { length: 255 }).notNull(),
- code: varchar("code", { length: 8 }).notNull(),
- expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }).notNull(),
- },
- (t) => ({
- userIdx: index("verification_code_user_idx").on(t.userId),
- emailIdx: index("verification_code_email_idx").on(t.email),
- }),
- );
-
- export const passwordResetTokens = pgTable(
- "password_reset_tokens",
- {
- id: varchar("id", { length: 40 }).primaryKey(),
- userId: varchar("user_id", { length: 21 }).notNull(),
- expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }).notNull(),
- },
- (t) => ({
- userIdx: index("password_token_user_idx").on(t.userId),
- }),
- );
-
- export const posts = pgTable(
- "posts",
- {
- id: varchar("id", { length: 15 }).primaryKey(),
- userId: varchar("user_id", { length: 255 }).notNull(),
- title: varchar("title", { length: 255 }).notNull(),
- excerpt: varchar("excerpt", { length: 255 }).notNull(),
- content: text("content").notNull(),
- status: varchar("status", { length: 10, enum: ["draft", "published"] })
- .default("draft")
- .notNull(),
- tags: varchar("tags", { length: 255 }),
- createdAt: timestamp("created_at").defaultNow().notNull(),
- updatedAt: timestamp("updated_at", { mode: "date" }).$onUpdate(() => new Date()),
- },
- (t) => ({
- userIdx: index("post_user_idx").on(t.userId),
- createdAtIdx: index("post_created_at_idx").on(t.createdAt),
- }),
- );
-
- export const postRelations = relations(posts, ({ one }) => ({
- user: one(users, {
- fields: [posts.userId],
- references: [users.id],
- }),
- }));
-
- export type Post = typeof posts.$inferSelect;
- export type NewPost = typeof posts.$inferInsert;
-
- export const vwUserSessions = pgView("vw_user_sessions", { id: varchar({ length: 255 }),
- userId: varchar("user_id", { length: 21 }),
- uId: varchar("u_id", { length: 21 }),
- uEmail: varchar("u_email", { length: 255 }),
- expiresAt: timestamp("expires_at", { withTimezone: true, mode: 'string' }),
- createdAt: timestamp("created_at", { mode: 'string' }),
- updatedAt: timestamp("updated_at", { mode: 'string' }),
-}).existing();
-//as(sql`SELECT s.id, s.user_id, u.id AS u_id, u.email AS u_email, s.expires_at, s.created_at, s.updated_at FROM sessions s, users u WHERE s.user_id::text = u.id::text`);
-
-// Default Drizzle File
-
-// import { pgTable, serial, text, integer, timestamp } from "drizzle-orm/pg-core";
-
-// export const products = pgTable("products", {
-// id: serial("id").primaryKey(),
-// name: text("name").notNull(),
-// description: text("description"),
-// price: integer("price"),
-// createdAt: timestamp("created_at").defaultNow(),
-// // Add more fields as needed
-// });
-
-export const affiliateCategoryMap = pgTable("affiliate_category_map", {
- id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "affiliate_category_map_id_seq", startWith: 1, increment: 1 }),
- feedname: varchar("feedname", { length: 100 }).notNull(),
- affiliatecategory: varchar("affiliatecategory", { length: 255 }).notNull(),
- buildercategoryid: integer("buildercategoryid").notNull(),
- notes: varchar("notes", { length: 255 }),
-});
-
-export const product_categories = pgTable("product_categories", {
- id: integer().primaryKey().generatedAlwaysAsIdentity({ name: "product_categories_id_seq", startWith: 1, increment: 1 }),
- name: varchar({ length: 100 }).notNull(),
- parent_category_id: integer("parent_category_id"),
- type: varchar({ length: 50 }),
- sort_order: integer("sort_order"),
- created_at: timestamp("created_at", { mode: 'string' }).defaultNow(),
- updated_at: timestamp("updated_at", { mode: 'string' }).defaultNow(),
-});
\ No newline at end of file
+export const productAttributes = pgTable("product_attributes", {
+ id: serial().primaryKey().notNull(),
+ productId: integer("product_id"),
+ name: varchar({ length: 255 }).notNull(),
+ value: varchar({ length: 255 }).notNull(),
+}, (table) => [
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [products.id],
+ name: "product_attributes_product_id_fkey"
+ }).onDelete("cascade"),
+]);