Next.js 14 drink collection tracker with AI-powered search, menu scanning, ratings, wishlist, sharing, and CSV backup/restore. Features: - Auth (credentials + OAuth ready) - Drink collection with ratings and reviews - AI search via Claude/OpenAI with search history - Menu photo scanning with AI extraction - Wishlist / Try Later system - Public sharing via slug URLs - CSV backup and restore (merge/replace modes) - Docker Compose for Postgres + MinIO + dev server Security: docker-compose files use env var interpolation instead of hardcoded secrets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
225 lines
6.6 KiB
SQL
225 lines
6.6 KiB
SQL
-- CreateEnum
|
|
CREATE TYPE "DrinkType" AS ENUM ('BEER', 'WINE', 'COCKTAIL', 'SPIRIT', 'OTHER');
|
|
|
|
-- CreateEnum
|
|
CREATE TYPE "ScanStatus" AS ENUM ('UPLOADING', 'PROCESSING', 'COMPLETED', 'FAILED');
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "User" (
|
|
"id" TEXT NOT NULL,
|
|
"name" TEXT,
|
|
"email" TEXT,
|
|
"emailVerified" TIMESTAMP(3),
|
|
"image" TEXT,
|
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "Account" (
|
|
"id" TEXT NOT NULL,
|
|
"userId" TEXT NOT NULL,
|
|
"type" TEXT NOT NULL,
|
|
"provider" TEXT NOT NULL,
|
|
"providerAccountId" TEXT NOT NULL,
|
|
"refresh_token" TEXT,
|
|
"access_token" TEXT,
|
|
"expires_at" INTEGER,
|
|
"token_type" TEXT,
|
|
"scope" TEXT,
|
|
"id_token" TEXT,
|
|
"session_state" TEXT,
|
|
|
|
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "Session" (
|
|
"id" TEXT NOT NULL,
|
|
"sessionToken" TEXT NOT NULL,
|
|
"userId" TEXT NOT NULL,
|
|
"expires" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "VerificationToken" (
|
|
"identifier" TEXT NOT NULL,
|
|
"token" TEXT NOT NULL,
|
|
"expires" TIMESTAMP(3) NOT NULL
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "UserApiKey" (
|
|
"id" TEXT NOT NULL,
|
|
"userId" TEXT NOT NULL,
|
|
"provider" TEXT NOT NULL,
|
|
"encryptedKey" TEXT NOT NULL,
|
|
"iv" TEXT NOT NULL,
|
|
"label" TEXT,
|
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "UserApiKey_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "UserPreference" (
|
|
"id" TEXT NOT NULL,
|
|
"userId" TEXT NOT NULL,
|
|
"preferredStyles" TEXT[],
|
|
"avoidedStyles" TEXT[],
|
|
"minAbv" DOUBLE PRECISION,
|
|
"maxAbv" DOUBLE PRECISION,
|
|
"defaultProvider" TEXT,
|
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "UserPreference_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "Drink" (
|
|
"id" TEXT NOT NULL,
|
|
"userId" TEXT NOT NULL,
|
|
"name" TEXT NOT NULL,
|
|
"type" "DrinkType" NOT NULL,
|
|
"subType" TEXT,
|
|
"brewery" TEXT,
|
|
"region" TEXT,
|
|
"abv" DOUBLE PRECISION,
|
|
"description" TEXT,
|
|
"imageUrl" TEXT,
|
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "Drink_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "Rating" (
|
|
"id" TEXT NOT NULL,
|
|
"userId" TEXT NOT NULL,
|
|
"drinkId" TEXT NOT NULL,
|
|
"score" INTEGER NOT NULL,
|
|
"notes" TEXT,
|
|
"wouldReorder" BOOLEAN NOT NULL DEFAULT false,
|
|
"location" TEXT,
|
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "Rating_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "MenuScan" (
|
|
"id" TEXT NOT NULL,
|
|
"userId" TEXT NOT NULL,
|
|
"imageUrl" TEXT NOT NULL,
|
|
"status" "ScanStatus" NOT NULL DEFAULT 'UPLOADING',
|
|
"aiProvider" TEXT,
|
|
"aiRawResponse" JSONB,
|
|
"errorMessage" TEXT,
|
|
"processedAt" TIMESTAMP(3),
|
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "MenuScan_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "MenuItem" (
|
|
"id" TEXT NOT NULL,
|
|
"scanId" TEXT NOT NULL,
|
|
"name" TEXT NOT NULL,
|
|
"type" "DrinkType" NOT NULL,
|
|
"subType" TEXT,
|
|
"brewery" TEXT,
|
|
"abv" DOUBLE PRECISION,
|
|
"price" TEXT,
|
|
"description" TEXT,
|
|
"matchedDrinkId" TEXT,
|
|
"userRating" INTEGER,
|
|
"aiRecommended" BOOLEAN NOT NULL DEFAULT false,
|
|
"aiReason" TEXT,
|
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
CONSTRAINT "MenuItem_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "UserApiKey_userId_provider_key" ON "UserApiKey"("userId", "provider");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "UserPreference_userId_key" ON "UserPreference"("userId");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "Drink_userId_idx" ON "Drink"("userId");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "Drink_userId_type_idx" ON "Drink"("userId", "type");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "Drink_userId_name_idx" ON "Drink"("userId", "name");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "Rating_userId_idx" ON "Rating"("userId");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "Rating_drinkId_idx" ON "Rating"("drinkId");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "MenuScan_userId_idx" ON "MenuScan"("userId");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "MenuItem_scanId_idx" ON "MenuItem"("scanId");
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "UserApiKey" ADD CONSTRAINT "UserApiKey_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "UserPreference" ADD CONSTRAINT "UserPreference_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "Drink" ADD CONSTRAINT "Drink_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "Rating" ADD CONSTRAINT "Rating_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "Rating" ADD CONSTRAINT "Rating_drinkId_fkey" FOREIGN KEY ("drinkId") REFERENCES "Drink"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "MenuScan" ADD CONSTRAINT "MenuScan_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "MenuItem" ADD CONSTRAINT "MenuItem_scanId_fkey" FOREIGN KEY ("scanId") REFERENCES "MenuScan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "MenuItem" ADD CONSTRAINT "MenuItem_matchedDrinkId_fkey" FOREIGN KEY ("matchedDrinkId") REFERENCES "Drink"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|