import { ConvexHttpClient } from "convex/browser" import { fetchQuery } from "convex/nextjs" import { makeFunctionReference } from "convex/server" import { api } from "@/convex/_generated/api" import { hasConvexUrl } from "@/lib/convex-config" import type { Product } from "@/lib/products/types" import type { Manual } from "@/lib/manuals-types" type ConvexProductDoc = { _id: string name: string description?: string price: number currency: string images: string[] metadata?: Record stripeProductId?: string stripePriceId?: string active: boolean } type ConvexManualDoc = { filename: string path: string manufacturer: string category: string size?: number lastModified?: number searchTerms?: string[] commonNames?: string[] thumbnailUrl?: string manualUrl?: string } const LIST_MANUALS = makeFunctionReference<"query">("manuals:list") const MANUALS_DASHBOARD = makeFunctionReference<"query">("manuals:dashboard") let cachedServerConvexClient: ConvexHttpClient | null | undefined function getServerConvexClient() { if (cachedServerConvexClient !== undefined) { return cachedServerConvexClient } const convexUrl = process.env.CONVEX_URL || process.env.NEXT_PUBLIC_CONVEX_URL if (!convexUrl) { cachedServerConvexClient = null return cachedServerConvexClient } const client = new ConvexHttpClient(convexUrl) const adminKey = process.env.CONVEX_SELF_HOSTED_ADMIN_KEY if (adminKey) { client.setAdminAuth(adminKey) } cachedServerConvexClient = client return cachedServerConvexClient } type ConvexOrderItem = { productId?: string | null productName: string price: number quantity: number priceId: string image?: string } export type ConvexAdminOrder = { id: string customerEmail: string customerName?: string totalAmount: number currency: string status: "pending" | "paid" | "fulfilled" | "cancelled" | "refunded" stripeSessionId: string | null paymentIntentId: string | null createdAt: string updatedAt: string shippingAddress?: { name?: string address?: string city?: string state?: string zipCode?: string country?: string } items: ConvexOrderItem[] } async function safeFetchQuery(label: string, query: Promise, fallback: TData): Promise { try { return await query } catch (error) { console.error(`[convex-service] ${label} failed`, error) return fallback } } function mapConvexProduct(product: ConvexProductDoc): Product { return { id: product._id, stripeId: product.stripeProductId ?? product._id, name: product.name, description: product.description ?? null, price: product.price, currency: product.currency, images: product.images ?? [], metadata: product.metadata, priceId: product.stripePriceId, } } function mapConvexManual(manual: ConvexManualDoc): Manual { return { filename: manual.filename, path: manual.path, manufacturer: manual.manufacturer, category: manual.category, size: manual.size, lastModified: manual.lastModified ? new Date(manual.lastModified) : undefined, searchTerms: manual.searchTerms, commonNames: manual.commonNames, thumbnailUrl: manual.thumbnailUrl, } } export async function listConvexProducts(): Promise { if (!hasConvexUrl()) { return [] } const products = await safeFetchQuery("products.listActive", fetchQuery(api.products.listActive, {}), [] as ConvexProductDoc[]) return (products as ConvexProductDoc[]).map(mapConvexProduct) } export async function listConvexAdminProducts(search?: string): Promise { if (!hasConvexUrl()) { return [] } const products = await safeFetchQuery( "products.listAdmin", fetchQuery(api.products.listAdmin, { search }), [] as ConvexProductDoc[], ) return (products as ConvexProductDoc[]).map(mapConvexProduct) } export async function getConvexProduct(id: string): Promise { if (!hasConvexUrl()) { return null } const products = await safeFetchQuery("products.listAdmin", fetchQuery(api.products.listAdmin, {}), [] as ConvexProductDoc[]) const match = (products as ConvexProductDoc[]).find((product) => { return product._id === id || product.stripeProductId === id }) return match ? mapConvexProduct(match) : null } export async function listConvexManuals(): Promise { if (!hasConvexUrl()) { return [] } const client = getServerConvexClient() if (!client) { return [] } const manuals = await safeFetchQuery("manuals.list", client.query(LIST_MANUALS, {}), [] as ConvexManualDoc[]) return (manuals as ConvexManualDoc[]).map(mapConvexManual) } export async function getConvexManualDashboard() { if (!hasConvexUrl()) { return null } const client = getServerConvexClient() if (!client) { return null } return await safeFetchQuery("manuals.dashboard", client.query(MANUALS_DASHBOARD, {}), null) } export async function getConvexOrderMetrics() { if (!hasConvexUrl()) { return null } return await safeFetchQuery("orders.getMetrics", fetchQuery(api.orders.getMetrics, {}), null) } export async function listConvexOrders(options?: { status?: ConvexAdminOrder["status"] search?: string }): Promise { if (!hasConvexUrl()) { return [] } return await safeFetchQuery( "orders.listAdmin", fetchQuery(api.orders.listAdmin, { status: options?.status, search: options?.search, }), [] as ConvexAdminOrder[], ) }