feat(storage): MinIO object storage, Neon Auth, Supabase removal
Deploy to route.crispygoat.com / deploy (push) Failing after 17s
Deploy to route.crispygoat.com / deploy (push) Failing after 17s
- 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:
@@ -3,7 +3,7 @@
|
||||
import { and, desc, eq, SQL } 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 { campaigns, emailTemplates } from "@/db/schema";
|
||||
|
||||
export type CampaignType = "marketing" | "operational" | "transactional";
|
||||
@@ -83,7 +83,7 @@ function stripHtml(html: string | null): string {
|
||||
function rowToCampaign(c: CampaignRow, t?: TemplateRow | null): Campaign {
|
||||
return {
|
||||
id: c.id,
|
||||
brand_id: c.tenantId,
|
||||
brand_id: c.brandId,
|
||||
name: c.name,
|
||||
subject: t?.subject ?? null,
|
||||
body_text: stripHtml(t?.bodyHtml ?? null),
|
||||
@@ -111,7 +111,7 @@ export async function getCommunicationCampaigns(brandId?: string): Promise<ListC
|
||||
|
||||
try {
|
||||
const rows = activeBrandId
|
||||
? await withTenant(activeBrandId, (db) =>
|
||||
? await withBrand(activeBrandId, (db) =>
|
||||
db
|
||||
.select({
|
||||
campaign: campaigns,
|
||||
@@ -178,7 +178,7 @@ export async function upsertCampaign(params: {
|
||||
const subject = params.subject ?? "";
|
||||
const bodyHtml = params.body_html ?? (params.body_text ? `<p style="white-space:pre-wrap">${escapeHtml(params.body_text)}</p>` : "<p></p>");
|
||||
|
||||
const { row, template } = await withTenant(params.brand_id, async (db) => {
|
||||
const { row, template } = await withBrand(params.brand_id, async (db) => {
|
||||
// Resolve / create the linked email_templates row.
|
||||
let templateId: string | null = params.template_id ?? null;
|
||||
let templateRow: TemplateRow | null = null;
|
||||
@@ -187,7 +187,7 @@ export async function upsertCampaign(params: {
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(emailTemplates)
|
||||
.where(and(eq(emailTemplates.id, templateId), eq(emailTemplates.tenantId, params.brand_id)))
|
||||
.where(and(eq(emailTemplates.id, templateId), eq(emailTemplates.brandId, params.brand_id)))
|
||||
.limit(1);
|
||||
if (existing[0]) {
|
||||
const updated = await db
|
||||
@@ -207,7 +207,7 @@ export async function upsertCampaign(params: {
|
||||
const inserted = await db
|
||||
.insert(emailTemplates)
|
||||
.values({
|
||||
tenantId: params.brand_id,
|
||||
brandId: params.brand_id,
|
||||
name: params.name,
|
||||
subject,
|
||||
bodyHtml,
|
||||
@@ -229,7 +229,7 @@ export async function upsertCampaign(params: {
|
||||
scheduledFor: scheduled,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(and(eq(campaigns.id, params.id), eq(campaigns.tenantId, params.brand_id)))
|
||||
.where(and(eq(campaigns.id, params.id), eq(campaigns.brandId, params.brand_id)))
|
||||
.returning();
|
||||
if (!updated[0]) {
|
||||
return { row: null, template: null };
|
||||
@@ -239,7 +239,7 @@ export async function upsertCampaign(params: {
|
||||
const inserted = await db
|
||||
.insert(campaigns)
|
||||
.values({
|
||||
tenantId: params.brand_id,
|
||||
brandId: params.brand_id,
|
||||
name: params.name,
|
||||
templateId,
|
||||
status,
|
||||
@@ -274,8 +274,8 @@ export async function deleteCampaign(campaignId: string, brandId?: string): Prom
|
||||
|
||||
try {
|
||||
if (activeBrandId) {
|
||||
await withTenant(activeBrandId, (db) =>
|
||||
db.delete(campaigns).where(and(eq(campaigns.id, campaignId), eq(campaigns.tenantId, activeBrandId))),
|
||||
await withBrand(activeBrandId, (db) =>
|
||||
db.delete(campaigns).where(and(eq(campaigns.id, campaignId), eq(campaigns.brandId, activeBrandId))),
|
||||
);
|
||||
} else {
|
||||
await withPlatformAdmin((db) => db.delete(campaigns).where(eq(campaigns.id, campaignId)));
|
||||
@@ -297,12 +297,12 @@ export async function getCampaignById(campaignId: string, brandId?: string): Pro
|
||||
|
||||
try {
|
||||
const result = activeBrandId
|
||||
? await withTenant(activeBrandId, (db) =>
|
||||
? await withBrand(activeBrandId, (db) =>
|
||||
db
|
||||
.select({ campaign: campaigns, template: emailTemplates })
|
||||
.from(campaigns)
|
||||
.leftJoin(emailTemplates, eq(emailTemplates.id, campaigns.templateId))
|
||||
.where(and(eq(campaigns.id, campaignId), eq(campaigns.tenantId, activeBrandId)))
|
||||
.where(and(eq(campaigns.id, campaignId), eq(campaigns.brandId, activeBrandId)))
|
||||
.limit(1)
|
||||
.then((r) => r[0] ?? null),
|
||||
)
|
||||
@@ -318,7 +318,7 @@ export async function getCampaignById(campaignId: string, brandId?: string): Pro
|
||||
|
||||
if (!result) return null;
|
||||
|
||||
if (adminUser.role === "brand_admin" && result.campaign.tenantId !== adminUser.brand_id) {
|
||||
if (adminUser.role === "brand_admin" && result.campaign.brandId !== adminUser.brand_id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user