import { z } from 'zod'; import { db } from '@/src/db/db_index' import { homeBanners } from '@/src/db/schema' import { eq, and, desc, sql } from 'drizzle-orm'; import { protectedProcedure, router } from '@/src/trpc/trpc-index' import { extractKeyFromPresignedUrl, scaffoldAssetUrl } from '@/src/lib/s3-client' import { ApiError } from '@/src/lib/api-error'; import { scheduleStoreInitialization } from '@/src/stores/store-initializer' export const bannerRouter = router({ // Get all banners getBanners: protectedProcedure .query(async () => { try { const banners = await db.query.homeBanners.findMany({ orderBy: desc(homeBanners.createdAt), // Order by creation date instead // Removed product relationship since we now use productIds array }); // Convert S3 keys to signed URLs for client const bannersWithSignedUrls = await Promise.all( banners.map(async (banner) => { try { return { ...banner, imageUrl: banner.imageUrl ? scaffoldAssetUrl(banner.imageUrl) : banner.imageUrl, // Ensure productIds is always an array productIds: banner.productIds || [], }; } catch (error) { console.error(`Failed to generate signed URL for banner ${banner.id}:`, error); return { ...banner, imageUrl: banner.imageUrl, // Keep original on error // Ensure productIds is always an array productIds: banner.productIds || [], }; } }) ); return { banners: bannersWithSignedUrls, }; } catch(e:any) { console.log(e) throw new ApiError(e.message); } }), // Get single banner by ID getBanner: protectedProcedure .input(z.object({ id: z.number() })) .query(async ({ input }) => { const banner = await db.query.homeBanners.findFirst({ where: eq(homeBanners.id, input.id), // Removed product relationship since we now use productIds array }); if (banner) { try { // Convert S3 key to signed URL for client if (banner.imageUrl) { banner.imageUrl = scaffoldAssetUrl(banner.imageUrl); } } catch (error) { console.error(`Failed to generate signed URL for banner ${banner.id}:`, error); // Keep original imageUrl on error } // Ensure productIds is always an array (handle migration compatibility) if (!banner.productIds) { banner.productIds = []; } } return banner; }), // Create new banner createBanner: protectedProcedure .input(z.object({ name: z.string().min(1), imageUrl: z.string(), description: z.string().optional(), productIds: z.array(z.number()).optional(), redirectUrl: z.string().url().optional(), // serialNum removed completely })) .mutation(async ({ input }) => { try { const imageUrl = extractKeyFromPresignedUrl(input.imageUrl) // const imageUrl = input.imageUrl const [banner] = await db.insert(homeBanners).values({ name: input.name, imageUrl: imageUrl, description: input.description, productIds: input.productIds || [], redirectUrl: input.redirectUrl, serialNum: 999, // Default value, not used isActive: false, // Default to inactive }).returning(); // Reinitialize stores to reflect changes scheduleStoreInitialization() return banner; } catch (error) { console.error('Error creating banner:', error); throw error; // Re-throw to maintain tRPC error handling } }), // Update banner updateBanner: protectedProcedure .input(z.object({ id: z.number(), name: z.string().min(1).optional(), imageUrl: z.string().url().optional(), description: z.string().optional(), productIds: z.array(z.number()).optional(), redirectUrl: z.string().url().optional(), serialNum: z.number().nullable().optional(), isActive: z.boolean().optional(), })) .mutation(async ({ input }) => { try { const { id, ...updateData } = input; const incomingProductIds = input.productIds; // Extract S3 key from presigned URL if imageUrl is provided const processedData = { ...updateData, ...(updateData.imageUrl && { imageUrl: extractKeyFromPresignedUrl(updateData.imageUrl) }), }; // Handle serialNum null case const finalData: any = { ...processedData }; if ('serialNum' in finalData && finalData.serialNum === null) { // Set to null explicitly finalData.serialNum = null; } const [banner] = await db.update(homeBanners) .set({ ...finalData, lastUpdated: new Date(), }) .where(eq(homeBanners.id, id)) .returning(); // Reinitialize stores to reflect changes scheduleStoreInitialization() return banner; } catch (error) { console.error('Error updating banner:', error); throw error; } }), // Delete banner deleteBanner: protectedProcedure .input(z.object({ id: z.number() })) .mutation(async ({ input }) => { await db.delete(homeBanners).where(eq(homeBanners.id, input.id)); // Reinitialize stores to reflect changes scheduleStoreInitialization() return { success: true }; }), });