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

62
src/lib/s3.ts Normal file
View File

@@ -0,0 +1,62 @@
import {
S3Client,
PutObjectCommand,
GetObjectCommand,
DeleteObjectCommand,
} from "@aws-sdk/client-s3"
const s3Client = new S3Client({
endpoint: `http${process.env.MINIO_USE_SSL === "true" ? "s" : ""}://${process.env.MINIO_ENDPOINT}:${process.env.MINIO_PORT}`,
region: "us-east-1",
credentials: {
accessKeyId: process.env.MINIO_ACCESS_KEY!,
secretAccessKey: process.env.MINIO_SECRET_KEY!,
},
forcePathStyle: true,
})
const BUCKET = process.env.MINIO_BUCKET || "drink-images"
export async function uploadImage(
key: string,
body: Buffer,
contentType: string
): Promise<string> {
await s3Client.send(
new PutObjectCommand({
Bucket: BUCKET,
Key: key,
Body: body,
ContentType: contentType,
})
)
const useSSL = process.env.MINIO_USE_SSL === "true"
const protocol = useSSL ? "https" : "http"
return `${protocol}://${process.env.MINIO_ENDPOINT}:${process.env.MINIO_PORT}/${BUCKET}/${key}`
}
export async function getImage(key: string) {
const response = await s3Client.send(
new GetObjectCommand({
Bucket: BUCKET,
Key: key,
})
)
return response
}
export async function deleteImage(key: string) {
await s3Client.send(
new DeleteObjectCommand({
Bucket: BUCKET,
Key: key,
})
)
}
export function getImageUrl(key: string): string {
const useSSL = process.env.MINIO_USE_SSL === "true"
const protocol = useSSL ? "https" : "http"
return `${protocol}://${process.env.MINIO_ENDPOINT}:${process.env.MINIO_PORT}/${BUCKET}/${key}`
}