Initial commit: DrinkTracker full-stack app

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>
This commit is contained in:
JP Scott
2026-03-01 12:27:08 -07:00
commit 969bc9347a
115 changed files with 19397 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
import { NextResponse } from "next/server"
import { z } from "zod"
import bcrypt from "bcryptjs"
import { prisma } from "@/lib/prisma"
const registerSchema = z.object({
name: z
.string()
.min(1, "Name is required")
.max(100, "Name must be 100 characters or less"),
email: z
.string()
.min(1, "Email is required")
.email("Invalid email address"),
password: z
.string()
.min(8, "Password must be at least 8 characters"),
})
export async function POST(request: Request) {
try {
const body = await request.json()
const result = registerSchema.safeParse(body)
if (!result.success) {
const errors = result.error.flatten().fieldErrors
return NextResponse.json(
{ error: "Validation failed", details: errors },
{ status: 400 }
)
}
const { name, email, password } = result.data
const existingUser = await prisma.user.findUnique({ where: { email } })
if (existingUser) {
return NextResponse.json(
{ error: "An account with this email already exists" },
{ status: 409 }
)
}
const hashedPassword = await bcrypt.hash(password, 10)
const user = await prisma.user.create({
data: {
name,
email,
password: hashedPassword,
emailVerified: new Date(),
},
})
return NextResponse.json(
{ id: user.id, name: user.name, email: user.email },
{ status: 201 }
)
} catch {
return NextResponse.json(
{ error: "Something went wrong. Please try again." },
{ status: 500 }
)
}
}