176 lines
5.5 KiB
TypeScript
176 lines
5.5 KiB
TypeScript
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 };
|
|
}),
|
|
});
|