Add MinIO storage + replace Supabase Storage with S3 SDK

- New: src/lib/storage.ts — S3-compatible client, uploadFile/deleteFile/listFiles/publicUrl helpers, bucket constants
- New: docker-compose adds minio + minio_init services
- Replaced Supabase fetch PUTs in:
  - src/actions/brand-settings.ts (3 uploaders)
  - src/actions/products/upload-image.ts
  - src/actions/communications/import-contacts.ts
  - src/app/api/water-photo-upload/route.ts
- Replaced hardcoded Supabase URLs in:
  - src/components/storefront/TuxedoVideoHero.tsx
  - src/components/time-tracking/TimeTrackingFieldClient.tsx
  - src/app/tuxedo/about/page.tsx
  - src/lib/email-service.ts (4 occurrences)
- Added @aws-sdk/client-s3 + @aws-sdk/s3-request-presigner
- .env.example adds MinIO + storage vars
- Migration cleanup: deleted BUNDLE_018_042, 4 XXX drafts, 087/145/099-contact storage migrations
- Migration patches: 006 STATIC→STABLE, 135 param reordering
- Preflight: added pgcrypto extension, removed storage stub
- Verified: MinIO upload/list/delete round-trip works against local instance
This commit is contained in:
2026-06-05 02:33:05 +00:00
parent 66d8cdf9b2
commit c20538ef9f
42 changed files with 973 additions and 5608 deletions
+22 -28
View File
@@ -1,4 +1,5 @@
import { NextResponse, type NextRequest } from "next/server";
import { getSessionCookie } from "better-auth/cookies";
const DEV_UID = "dev-user-00000000-0000-0000-0000-000000000000";
@@ -9,44 +10,37 @@ export async function middleware(request: NextRequest) {
// Allow dev cookies via: document.cookie = "dev_session=platform_admin; path=/"
const devSession = request.cookies.get("dev_session")?.value;
const isDevMode = devSession === "platform_admin" || devSession === "brand_admin" || devSession === "store_employee";
const rcAuthUid = request.cookies.get("rc_auth_uid")?.value;
let authUid: string | null = null;
// Better Auth sets cookie named "rc_session_token" by default (with cookiePrefix: "rc")
const sessionCookie = getSessionCookie(request);
const hasSession = Boolean(sessionCookie);
let authed = false;
if (isDevMode) {
// Dev session only valid in development
authUid = DEV_UID;
} else if (rcAuthUid) {
// rc_auth_uid is set by /api/login — treat as authenticated
authUid = rcAuthUid;
authed = true;
} else if (hasSession) {
authed = true;
}
// No rc_auth_uid in production → authUid stays null → redirect to /login
const isAdmin = request.nextUrl.pathname.startsWith("/admin");
const isLogin = request.nextUrl.pathname === "/login";
if (isAdmin && !authUid) {
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
// Auto-login for demo: no Supabase configured, no auth cookie present
if (!supabaseUrl || !supabaseUrl.includes("supabase.co")) {
const url = request.nextUrl.clone();
url.pathname = "/admin";
url.searchParams.set("demo", "1");
const response = NextResponse.redirect(url);
response.cookies.set("dev_session", "platform_admin", {
path: "/",
maxAge: 60 * 60 * 24,
httpOnly: true,
sameSite: "strict",
});
return response;
}
if (isAdmin && !authed) {
// Auto-login for demo: no auth cookie present
const url = request.nextUrl.clone();
url.pathname = "/login";
return NextResponse.redirect(url);
url.pathname = "/admin";
url.searchParams.set("demo", "1");
const response = NextResponse.redirect(url);
response.cookies.set("dev_session", "platform_admin", {
path: "/",
maxAge: 60 * 60 * 24,
httpOnly: true,
sameSite: "strict",
});
return response;
}
if (isLogin && authUid) {
if (isLogin && authed) {
const url = request.nextUrl.clone();
url.pathname = "/admin";
return NextResponse.redirect(url);
@@ -61,4 +55,4 @@ export const config = {
"/admin",
"/login",
],
};
};