feat(storage): MinIO object storage, Neon Auth, Supabase removal
Deploy to route.crispygoat.com / deploy (push) Failing after 7s

- Add MinIO/S3-compatible storage client (src/lib/storage.ts) with uploadObject,
  deleteObject, presigned URL helpers, and BUCKETS constant
- Wire product images, brand logos, and water log photos to MinIO via the
  new storage client
- Migrate forgot-password to Neon Auth (remove Supabase /auth/v1/recover call)
- Migrate send-scheduled cron to direct Postgres + Resend (remove Supabase Edge
  Function proxy)
- Add logoUrl to email types (OrderReceipt, Welcome, PasswordReset) and pass
  brand_settings.logo_url from all call sites
- Update email templates to use dynamic logoUrl instead of hardcoded Supabase
  bucket URLs
- Remove hardcoded Supabase URLs from TuxedoVideoHero, TuxedoAboutPage,
  TimeTrackingFieldClient; use brand_settings props + local public/ fallback
- Download brand logos (3) and tuxedo-hero.mp4 (36MB) from Supabase bucket to
  public/ for local development
- Add MinIO env vars to .env.example (endpoint, access key, secret, buckets)
- Fix TimeTrackingFieldClient to destructure logoUrl and brandAccent props
- Fix admin/users.ts logoUrl type (null → undefined for optional string)
- Remove stale sb- cookie from wholesale-auth
- Migrate tuxedo/about page to remove supabase import and use pool query for
  wholesale_settings lookup
This commit is contained in:
openclaw
2026-06-09 11:32:17 -06:00
parent bb464bda65
commit c86e97e816
348 changed files with 6616 additions and 3096 deletions
+11 -11
View File
@@ -3,7 +3,7 @@
import { and, eq } from "drizzle-orm";
import { getAdminUser } from "@/lib/admin-permissions";
import { getActiveBrandId } from "@/lib/brand-scope";
import { withTenant, withPlatformAdmin } from "@/db/client";
import { withBrand, withPlatformAdmin } from "@/db/client";
import { emailTemplates } from "@/db/schema";
export type TemplateType = "email_template" | "sms_template" | "internal_note";
@@ -20,7 +20,7 @@ export type CampaignType = "marketing" | "operational" | "transactional";
*/
export type Template = {
id: string;
tenant_id: string;
brand_id: string;
name: string;
subject: string;
body_text: string;
@@ -42,7 +42,7 @@ export type UpsertTemplateResult = {
function rowToTemplate(row: typeof emailTemplates.$inferSelect): Template {
return {
id: row.id,
tenant_id: row.tenantId,
brand_id: row.brandId,
name: row.name,
subject: row.subject,
body_text: stripHtml(row.bodyHtml),
@@ -79,7 +79,7 @@ export async function getCommunicationTemplates(brandId?: string): Promise<{ suc
try {
const rows = activeBrandId
? await withTenant(activeBrandId, (db) =>
? await withBrand(activeBrandId, (db) =>
db
.select()
.from(emailTemplates)
@@ -121,7 +121,7 @@ export async function upsertTemplate(params: {
try {
const row = params.id
? await withTenant(params.brand_id, async (db) => {
? await withBrand(params.brand_id, async (db) => {
const updated = await db
.update(emailTemplates)
.set({
@@ -133,17 +133,17 @@ export async function upsertTemplate(params: {
.where(
and(
eq(emailTemplates.id, params.id!),
eq(emailTemplates.tenantId, params.brand_id),
eq(emailTemplates.brandId, params.brand_id),
),
)
.returning();
return updated[0] ?? null;
})
: await withTenant(params.brand_id, async (db) => {
: await withBrand(params.brand_id, async (db) => {
const inserted = await db
.insert(emailTemplates)
.values({
tenantId: params.brand_id,
brandId: params.brand_id,
name: params.name,
subject: params.subject,
bodyHtml,
@@ -170,14 +170,14 @@ export async function getTemplateById(templateId: string): Promise<Template | nu
try {
const row = activeBrandId
? await withTenant(activeBrandId, (db) =>
? await withBrand(activeBrandId, (db) =>
db
.select()
.from(emailTemplates)
.where(
and(
eq(emailTemplates.id, templateId),
eq(emailTemplates.tenantId, activeBrandId),
eq(emailTemplates.brandId, activeBrandId),
),
)
.limit(1)
@@ -194,7 +194,7 @@ export async function getTemplateById(templateId: string): Promise<Template | nu
if (!row) return null;
if (adminUser.role === "brand_admin" && row.tenantId !== adminUser.brand_id) {
if (adminUser.role === "brand_admin" && row.brandId !== adminUser.brand_id) {
return null;
}