migrate: replace Supabase REST with Drizzle/pg in water-log/time-tracking/reports/etc (wave 4)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getAdminUser } from "@/lib/admin-permissions";
|
||||
import { getAIClient } from "@/actions/integrations/ai-providers";
|
||||
import { svcHeaders } from "@/lib/svc-headers";
|
||||
import { pool } from "@/lib/db";
|
||||
|
||||
const SYSTEM = `You are a data analyst for a B2B produce wholesale platform.
|
||||
Given a natural language customer query, return a JSON response indicating which predefined query to run and what parameters to use.
|
||||
@@ -82,40 +82,34 @@ Use "recent_orders" for recent order questions.`;
|
||||
const queryType = parsed.queryType ?? "recent_orders";
|
||||
const days = parsed.days ?? 30;
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
||||
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
||||
|
||||
// Route to appropriate RPC based on query type
|
||||
let rpcName = "get_recent_orders_insights";
|
||||
let rpcParams: Record<string, unknown> = { p_brand_id: effectiveBrandId, p_days: days };
|
||||
let rpcArgs: unknown[] = [effectiveBrandId, days];
|
||||
|
||||
if (queryType === "dormant") {
|
||||
rpcName = "get_dormant_customers_insights";
|
||||
rpcParams = { p_brand_id: effectiveBrandId, p_days: days };
|
||||
rpcArgs = [effectiveBrandId, days];
|
||||
} else if (queryType === "trending") {
|
||||
rpcName = "get_trending_products_insights";
|
||||
rpcParams = { p_brand_id: effectiveBrandId, p_days: days };
|
||||
rpcArgs = [effectiveBrandId, days];
|
||||
} else if (queryType === "top_customers") {
|
||||
rpcName = "get_top_customers_insights";
|
||||
rpcParams = { p_brand_id: effectiveBrandId, p_days: days };
|
||||
rpcArgs = [effectiveBrandId, days];
|
||||
} else if (queryType === "at_risk") {
|
||||
rpcName = "get_at_risk_customers_insights";
|
||||
rpcParams = { p_brand_id: effectiveBrandId };
|
||||
rpcArgs = [effectiveBrandId];
|
||||
}
|
||||
|
||||
const dbResponse = await fetch(
|
||||
`${supabaseUrl}/rest/v1/rpc/${rpcName}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { ...svcHeaders(supabaseKey!), "Content-Type": "application/json" },
|
||||
body: JSON.stringify(rpcParams),
|
||||
}
|
||||
);
|
||||
|
||||
let results: unknown[] = [];
|
||||
if (dbResponse.ok) {
|
||||
const data = await dbResponse.json();
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT ${rpcName}(${rpcArgs.map((_, i) => `$${i + 1}`).join(", ")}) AS result`,
|
||||
rpcArgs,
|
||||
);
|
||||
const data = rows[0]?.result;
|
||||
results = Array.isArray(data) ? data.slice(0, 100) : [];
|
||||
} catch {
|
||||
results = [];
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
@@ -129,4 +123,4 @@ Use "recent_orders" for recent order questions.`;
|
||||
} catch (err) {
|
||||
return NextResponse.json({ error: "AI analysis failed" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { z } from "zod";
|
||||
import { emailLimiter, checkRateLimit, rateLimitExceeded, securityHeaders } from "@/lib/rate-limit";
|
||||
import { analytics } from "@/lib/analytics";
|
||||
import { captureError } from "@/lib/sentry";
|
||||
import { pool } from "@/lib/db";
|
||||
|
||||
// Helper functions
|
||||
function apiResponse(data: unknown, status: number = 200) {
|
||||
@@ -44,44 +45,31 @@ export async function POST(req: NextRequest) {
|
||||
// Parse and validate body
|
||||
const body = await req.json();
|
||||
const validation = createCampaignSchema.safeParse(body);
|
||||
|
||||
|
||||
if (!validation.success) {
|
||||
return validationError(validation.error);
|
||||
}
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
|
||||
|
||||
// Create campaign via RPC
|
||||
const res = await fetch(
|
||||
`${supabaseUrl}/rest/v1/rpc/create_campaign`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
apikey: serviceKey,
|
||||
"Content-Type": "application/json",
|
||||
Prefer: "return=representation",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
p_brand_id: validation.data.brand_id,
|
||||
p_name: validation.data.name,
|
||||
p_subject: validation.data.subject,
|
||||
p_content: validation.data.content,
|
||||
p_type: validation.data.type,
|
||||
p_segment_id: validation.data.segment_id,
|
||||
p_contact_ids: validation.data.contact_ids,
|
||||
p_scheduled_at: validation.data.scheduled_at,
|
||||
}),
|
||||
}
|
||||
const { rows: campaignRows } = await pool.query<{ create_campaign: { id: string; [k: string]: unknown } | null }>(
|
||||
`SELECT create_campaign($1, $2, $3, $4, $5, $6, $7::uuid[], $8) AS create_campaign`,
|
||||
[
|
||||
validation.data.brand_id,
|
||||
validation.data.name,
|
||||
validation.data.subject,
|
||||
validation.data.content,
|
||||
validation.data.type,
|
||||
validation.data.segment_id ?? null,
|
||||
validation.data.contact_ids ?? null,
|
||||
validation.data.scheduled_at ?? null,
|
||||
],
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
const error = await res.json();
|
||||
return apiError(error.message || "Failed to create campaign", 500);
|
||||
const campaign = campaignRows[0]?.create_campaign;
|
||||
if (!campaign) {
|
||||
return apiError("Failed to create campaign", 500);
|
||||
}
|
||||
|
||||
const campaign = await res.json();
|
||||
|
||||
// Track analytics
|
||||
const audienceSize = validation.data.contact_ids?.length || 0;
|
||||
analytics.campaignCreated(campaign.id, validation.data.type, audienceSize);
|
||||
@@ -102,24 +90,13 @@ export async function GET(req: NextRequest) {
|
||||
return apiError("brand_id is required", 400);
|
||||
}
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
||||
|
||||
const res = await fetch(
|
||||
`${supabaseUrl}/rest/v1/communication_campaigns?brand_id=eq.${brand_id}&select=*&order=created_at.desc`,
|
||||
{
|
||||
headers: {
|
||||
apikey: anonKey,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
const { rows: campaigns } = await pool.query(
|
||||
`SELECT * FROM communication_campaigns
|
||||
WHERE brand_id = $1
|
||||
ORDER BY created_at DESC`,
|
||||
[brand_id],
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return apiError("Failed to fetch campaigns", 500);
|
||||
}
|
||||
|
||||
const campaigns = await res.json();
|
||||
return apiResponse(campaigns);
|
||||
} catch (error) {
|
||||
captureError(error as Error, { path: "/api/campaigns", method: "GET" });
|
||||
@@ -137,4 +114,4 @@ export async function OPTIONS() {
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { z } from "zod";
|
||||
import { apiLimiter, checkRateLimit, rateLimitExceeded, securityHeaders } from "@/lib/rate-limit";
|
||||
import { analytics } from "@/lib/analytics";
|
||||
import { captureError } from "@/lib/sentry";
|
||||
import { pool } from "@/lib/db";
|
||||
|
||||
// Helper functions
|
||||
function apiResponse(data: unknown, status: number = 200) {
|
||||
@@ -38,39 +39,25 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
const body = await req.json();
|
||||
const validation = createReferralSchema.safeParse(body);
|
||||
|
||||
|
||||
if (!validation.success) {
|
||||
return validationError(validation.error);
|
||||
}
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
|
||||
|
||||
// Redeem referral via RPC
|
||||
const res = await fetch(
|
||||
`${supabaseUrl}/rest/v1/rpc/redeem_referral`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
apikey: serviceKey,
|
||||
"Content-Type": "application/json",
|
||||
Prefer: "return=representation",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
p_brand_id: validation.data.brand_id,
|
||||
p_referred_email: validation.data.referred_email,
|
||||
p_referral_code: validation.data.referral_code,
|
||||
}),
|
||||
}
|
||||
const { rows } = await pool.query<{ redeem_referral: { referred_user_id?: string; [k: string]: unknown } | null }>(
|
||||
`SELECT redeem_referral($1, $2, $3) AS redeem_referral`,
|
||||
[
|
||||
validation.data.brand_id,
|
||||
validation.data.referred_email,
|
||||
validation.data.referral_code,
|
||||
],
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
const error = await res.json();
|
||||
return apiError(error.message || "Failed to redeem referral", 500);
|
||||
const referral = rows[0]?.redeem_referral;
|
||||
if (!referral) {
|
||||
return apiError("Failed to redeem referral", 500);
|
||||
}
|
||||
|
||||
const referral = await res.json();
|
||||
|
||||
analytics.referralCompleted(validation.data.referral_code, referral.referred_user_id);
|
||||
|
||||
return apiResponse(referral, 201);
|
||||
@@ -89,24 +76,13 @@ export async function GET(req: NextRequest) {
|
||||
return apiError("brand_id is required", 400);
|
||||
}
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
||||
|
||||
const res = await fetch(
|
||||
`${supabaseUrl}/rest/v1/referral_codes?brand_id=eq.${brand_id}&select=*&order=created_at.desc`,
|
||||
{
|
||||
headers: {
|
||||
apikey: anonKey,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
const { rows: referrals } = await pool.query(
|
||||
`SELECT * FROM referral_codes
|
||||
WHERE brand_id = $1
|
||||
ORDER BY created_at DESC`,
|
||||
[brand_id],
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return apiError("Failed to fetch referrals", 500);
|
||||
}
|
||||
|
||||
const referrals = await res.json();
|
||||
return apiResponse(referrals);
|
||||
} catch (error) {
|
||||
captureError(error as Error, { path: "/api/referrals", method: "GET" });
|
||||
@@ -124,4 +100,4 @@ export async function OPTIONS() {
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { apiLimiter, checkRateLimit, rateLimitExceeded, securityHeaders } from "@/lib/rate-limit";
|
||||
import { captureError } from "@/lib/sentry";
|
||||
import { pool } from "@/lib/db";
|
||||
|
||||
// Helper functions
|
||||
function apiResponse(data: unknown, status: number = 200) {
|
||||
@@ -50,34 +51,21 @@ export async function GET(req: NextRequest) {
|
||||
return validationError(validation.error);
|
||||
}
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
|
||||
|
||||
// Get report via RPC
|
||||
const res = await fetch(
|
||||
`${supabaseUrl}/rest/v1/rpc/generate_report`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
apikey: serviceKey,
|
||||
"Content-Type": "application/json",
|
||||
Prefer: "return=representation",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
p_brand_id: validation.data.brand_id,
|
||||
p_start_date: validation.data.start_date,
|
||||
p_end_date: validation.data.end_date,
|
||||
p_report_type: validation.data.report_type,
|
||||
}),
|
||||
}
|
||||
const { rows } = await pool.query<{ generate_report: unknown }>(
|
||||
`SELECT generate_report($1, $2::timestamptz, $3::timestamptz, $4) AS generate_report`,
|
||||
[
|
||||
validation.data.brand_id,
|
||||
validation.data.start_date,
|
||||
validation.data.end_date,
|
||||
validation.data.report_type,
|
||||
],
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
const report = rows[0]?.generate_report;
|
||||
if (report == null) {
|
||||
return apiError("Failed to generate report", 500);
|
||||
}
|
||||
|
||||
const report = await res.json();
|
||||
|
||||
return apiResponse(report);
|
||||
} catch (error) {
|
||||
captureError(error as Error, { path: "/api/reports", method: "GET" });
|
||||
@@ -95,4 +83,4 @@ export async function OPTIONS() {
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { apiLimiter, checkRateLimit, rateLimitExceeded, securityHeaders } from "@/lib/rate-limit";
|
||||
import { captureError } from "@/lib/sentry";
|
||||
import { pool } from "@/lib/db";
|
||||
|
||||
// Helper functions
|
||||
function apiResponse(data: unknown, status: number = 200) {
|
||||
@@ -43,44 +44,30 @@ export async function POST(req: NextRequest) {
|
||||
// Parse and validate body
|
||||
const body = await req.json();
|
||||
const validation = createWaterLogSchema.safeParse(body);
|
||||
|
||||
|
||||
if (!validation.success) {
|
||||
return validationError(validation.error);
|
||||
}
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
|
||||
|
||||
// Create water log via RPC
|
||||
const res = await fetch(
|
||||
`${supabaseUrl}/rest/v1/rpc/create_water_log`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
apikey: serviceKey,
|
||||
"Content-Type": "application/json",
|
||||
Prefer: "return=representation",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
p_brand_id: validation.data.brand_id,
|
||||
p_field_id: validation.data.field_id,
|
||||
p_field_name: validation.data.field_name,
|
||||
p_gallons: validation.data.gallons,
|
||||
p_duration_minutes: validation.data.duration_minutes,
|
||||
p_water_method: validation.data.water_method,
|
||||
p_notes: validation.data.notes,
|
||||
p_logged_at: validation.data.logged_at,
|
||||
}),
|
||||
}
|
||||
const { rows } = await pool.query<{ create_water_log: { id: string; [k: string]: unknown } | null }>(
|
||||
`SELECT create_water_log($1, $2, $3, $4, $5, $6, $7, $8) AS create_water_log`,
|
||||
[
|
||||
validation.data.brand_id,
|
||||
validation.data.field_id ?? null,
|
||||
validation.data.field_name ?? null,
|
||||
validation.data.gallons,
|
||||
validation.data.duration_minutes ?? null,
|
||||
validation.data.water_method ?? null,
|
||||
validation.data.notes ?? null,
|
||||
validation.data.logged_at ?? null,
|
||||
],
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
const error = await res.json();
|
||||
return apiError(error.message || "Failed to create water log", 500);
|
||||
const waterLog = rows[0]?.create_water_log;
|
||||
if (!waterLog) {
|
||||
return apiError("Failed to create water log", 500);
|
||||
}
|
||||
|
||||
const waterLog = await res.json();
|
||||
|
||||
return apiResponse(waterLog, 201);
|
||||
} catch (error) {
|
||||
captureError(error as Error, { path: "/api/water-logs", method: "POST" });
|
||||
@@ -97,24 +84,14 @@ export async function GET(req: NextRequest) {
|
||||
return apiError("brand_id is required", 400);
|
||||
}
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
||||
|
||||
const res = await fetch(
|
||||
`${supabaseUrl}/rest/v1/water_logs?brand_id=eq.${brand_id}&select=*&order=logged_at.desc&limit=100`,
|
||||
{
|
||||
headers: {
|
||||
apikey: anonKey,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
const { rows: waterLogs } = await pool.query(
|
||||
`SELECT * FROM water_logs
|
||||
WHERE brand_id = $1
|
||||
ORDER BY logged_at DESC
|
||||
LIMIT 100`,
|
||||
[brand_id],
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return apiError("Failed to fetch water logs", 500);
|
||||
}
|
||||
|
||||
const waterLogs = await res.json();
|
||||
return apiResponse(waterLogs);
|
||||
} catch (error) {
|
||||
captureError(error as Error, { path: "/api/water-logs", method: "GET" });
|
||||
@@ -132,4 +109,4 @@ export async function OPTIONS() {
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
import { svcHeaders } from "@/lib/svc-headers";
|
||||
import { pool } from "@/lib/db";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
@@ -9,34 +9,27 @@ export async function POST(request: Request) {
|
||||
return NextResponse.json({ success: false, error: "Missing params" }, { status: 400 });
|
||||
}
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
||||
|
||||
// Get admin settings
|
||||
const settingsRes = await fetch(
|
||||
`${supabaseUrl}/rest/v1/rpc/get_water_admin_settings`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { ...svcHeaders(supabaseKey), "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ p_brand_id: brandId }),
|
||||
}
|
||||
const settingsRes = await pool.query<{
|
||||
get_water_admin_settings: { enabled: boolean; session_duration_hours?: number } | null;
|
||||
}>(
|
||||
`SELECT get_water_admin_settings($1) AS "get_water_admin_settings"`,
|
||||
[brandId],
|
||||
);
|
||||
const settings = await settingsRes.json();
|
||||
if (!settingsRes.ok || !settings?.enabled) {
|
||||
const settings = settingsRes.rows[0]?.get_water_admin_settings;
|
||||
if (!settings?.enabled) {
|
||||
return NextResponse.json({ success: false, error: "Admin portal not enabled" }, { status: 403 });
|
||||
}
|
||||
|
||||
// Verify PIN
|
||||
const verifyRes = await fetch(
|
||||
`${supabaseUrl}/rest/v1/rpc/verify_water_admin_pin`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { ...svcHeaders(supabaseKey), "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ p_brand_id: brandId, p_pin: pin }),
|
||||
}
|
||||
const verifyRes = await pool.query<{
|
||||
verify_water_admin_pin: { success: boolean; session_id?: string } | null;
|
||||
}>(
|
||||
`SELECT verify_water_admin_pin($1, $2) AS "verify_water_admin_pin"`,
|
||||
[brandId, pin],
|
||||
);
|
||||
const verifyData = await verifyRes.json();
|
||||
if (!verifyRes.ok || !verifyData?.success) {
|
||||
const verifyData = verifyRes.rows[0]?.verify_water_admin_pin;
|
||||
if (!verifyData?.success || !verifyData.session_id) {
|
||||
return NextResponse.json({ success: false, error: "Invalid PIN" }, { status: 401 });
|
||||
}
|
||||
|
||||
@@ -56,4 +49,4 @@ export async function POST(request: Request) {
|
||||
} catch {
|
||||
return NextResponse.json({ success: false, error: "Server error" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getAdminUser } from "@/lib/admin-permissions";
|
||||
import { svcHeaders } from "@/lib/svc-headers";
|
||||
import { pool } from "@/lib/db";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const adminUser = await getAdminUser();
|
||||
@@ -18,31 +18,26 @@ export async function GET(request: NextRequest) {
|
||||
// Use brand_id from session (always Tuxedo for water log) or fallback to env
|
||||
const brandId = adminUser.brand_id ?? process.env.TUXEDO_BRAND_ID ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
||||
type WaterEntry = {
|
||||
id: string;
|
||||
user_id: string | null;
|
||||
headgate_id: string | null;
|
||||
measurement: number | null;
|
||||
unit: string | null;
|
||||
notes: string | null;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
const response = await fetch(
|
||||
`${supabaseUrl}/rest/v1/rpc/get_water_entries`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
...svcHeaders(supabaseKey),
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ p_brand_id: brandId, p_limit: 10000 }),
|
||||
}
|
||||
const { rows: data } = await pool.query<{ get_water_entries: WaterEntry[] | null }>(
|
||||
`SELECT get_water_entries($1, $2) AS "get_water_entries"`,
|
||||
[brandId, 10000],
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json({ error: "Failed to fetch water log" }, { status: 500 });
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const entries = data[0]?.get_water_entries ?? [];
|
||||
|
||||
if (format === "csv") {
|
||||
const headers = ["id", "user_id", "headgate_id", "measurement", "unit", "notes", "created_at"];
|
||||
const csvRows = [headers.join(",")];
|
||||
for (const row of data) {
|
||||
for (const row of entries) {
|
||||
csvRows.push([
|
||||
row.id,
|
||||
row.user_id ?? "",
|
||||
@@ -61,5 +56,5 @@ export async function GET(request: NextRequest) {
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json(data);
|
||||
return NextResponse.json(entries);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user