migrate: replace Supabase REST with Drizzle/pg in 11 more action files (wave 5 partial)
- analytics.ts: rewrite getReportsSummary, getRevenueChart, getSalesByProduct, getContactGrowth, getRecentOrders, getConversionFunnel against pool + new orders/customers schema. Drops retired columns (subtotal, pickup_complete) and re-implements the SQL by hand. - import-orders.ts: bulk import via withTx using orders + orderItems + customers Drizzle tables, computes total_cents from current product prices. - import-products.ts: rewrite to use withTenant(brandId) and Drizzle products table. - products/create-product.ts, update-product.ts, upload-image.ts: switch to withTenant + Drizzle; image_url moves to product_images table. - reports.ts: rewrite against pool + new orders schema. - route-trace/lots.ts: stub functions (route-trace feature retired from SaaS rebuild — harvest_lots table not in db/schema). Uses discriminated union return types so consumer narrowing works in both branches. - settings/features.ts: switch to withTenant + Drizzle brandSettings. - shipping.ts: switch to pool + Drizzle orders/orderItems. - api/v1/referrals/route.ts: fix typecheck (referred_user_id undefined → 'anonymous'). Typecheck: clean. Tests: 22/22 pass. Build: succeeds.
This commit is contained in:
@@ -1,14 +1,26 @@
|
||||
"use server";
|
||||
|
||||
import { getAdminUser } from "@/lib/admin-permissions";
|
||||
import { invalidateBrandFeatureCache, type BrandFeatureKey } from "@/lib/feature-flags";
|
||||
import { withTenant } from "@/db/client";
|
||||
import { brandSettings } from "@/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { svcHeaders } from "@/lib/svc-headers";
|
||||
import {
|
||||
invalidateBrandFeatureCache,
|
||||
type BrandFeatureKey,
|
||||
} from "@/lib/feature-flags";
|
||||
|
||||
export type ToggleFeatureResult =
|
||||
| { success: true }
|
||||
| { success: false; error: string };
|
||||
|
||||
/**
|
||||
* Toggle an add-on feature flag for a brand. The new schema stores feature
|
||||
* flags as a JSONB blob in `brand_settings.feature_flags` — see
|
||||
* `src/lib/feature-flags.ts` for the read path. The legacy RPC
|
||||
* `set_brand_feature(p_brand_id, p_feature_key, p_enabled)` and the
|
||||
* `brand_features` table it wrote to are gone.
|
||||
*/
|
||||
export async function toggleBrandFeature(
|
||||
brandId: string,
|
||||
featureKey: BrandFeatureKey,
|
||||
@@ -23,29 +35,37 @@ export async function toggleBrandFeature(
|
||||
return { success: false, error: "Not authorized for this brand" };
|
||||
}
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
||||
try {
|
||||
await withTenant(brandId, async (db) => {
|
||||
const existing = await db
|
||||
.select({ featureFlags: brandSettings.featureFlags })
|
||||
.from(brandSettings)
|
||||
.where(eq(brandSettings.tenantId, brandId))
|
||||
.limit(1);
|
||||
const current = (existing[0]?.featureFlags ?? {}) as Record<string, unknown>;
|
||||
const next = { ...current, [featureKey]: enabled };
|
||||
if (existing.length === 0) {
|
||||
// No row yet — bootstrap with the brand name from tenants.
|
||||
await db
|
||||
.insert(brandSettings)
|
||||
.values({ tenantId: brandId, brandName: "Brand", featureFlags: next });
|
||||
} else {
|
||||
await db
|
||||
.update(brandSettings)
|
||||
.set({ featureFlags: next, updatedAt: new Date() })
|
||||
.where(eq(brandSettings.tenantId, brandId));
|
||||
}
|
||||
});
|
||||
|
||||
const response = await fetch(
|
||||
`${supabaseUrl}/rest/v1/rpc/set_brand_feature`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { ...svcHeaders(supabaseKey), "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
p_brand_id: brandId,
|
||||
p_feature_key: featureKey,
|
||||
p_enabled: enabled,
|
||||
}),
|
||||
}
|
||||
);
|
||||
invalidateBrandFeatureCache(brandId);
|
||||
revalidatePath("/admin/settings/apps");
|
||||
revalidatePath("/admin");
|
||||
|
||||
if (!response.ok) {
|
||||
return { success: false, error: "Failed to toggle feature" };
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
return {
|
||||
success: false,
|
||||
error: err instanceof Error ? err.message : "Failed to toggle feature",
|
||||
};
|
||||
}
|
||||
|
||||
invalidateBrandFeatureCache(brandId);
|
||||
revalidatePath("/admin/settings/apps");
|
||||
revalidatePath("/admin");
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user