211 lines
6 KiB
TypeScript
211 lines
6 KiB
TypeScript
import { router, protectedProcedure } from '@/src/trpc/trpc-index'
|
|
import { z } from 'zod';
|
|
import { db } from '@/src/db/db_index'
|
|
import { storeInfo, productInfo } from '@/src/db/schema'
|
|
import { eq, inArray } from 'drizzle-orm';
|
|
import { ApiError } from '@/src/lib/api-error'
|
|
import { extractKeyFromPresignedUrl, deleteImageUtil, scaffoldAssetUrl } from '@/src/lib/s3-client'
|
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
import { scheduleStoreInitialization } from '@/src/stores/store-initializer'
|
|
|
|
|
|
export const storeRouter = router({
|
|
getStores: protectedProcedure
|
|
.query(async ({ ctx }) => {
|
|
const stores = await db.query.storeInfo.findMany({
|
|
with: {
|
|
owner: true,
|
|
},
|
|
});
|
|
|
|
Promise.all(stores.map(async store => {
|
|
if(store.imageUrl)
|
|
store.imageUrl = scaffoldAssetUrl(store.imageUrl)
|
|
})).catch((e) => {
|
|
throw new ApiError("Unable to find store image urls")
|
|
}
|
|
)
|
|
return {
|
|
stores,
|
|
count: stores.length,
|
|
};
|
|
}),
|
|
|
|
getStoreById: protectedProcedure
|
|
.input(z.object({
|
|
id: z.number(),
|
|
}))
|
|
.query(async ({ input, ctx }) => {
|
|
const { id } = input;
|
|
|
|
const store = await db.query.storeInfo.findFirst({
|
|
where: eq(storeInfo.id, id),
|
|
with: {
|
|
owner: true,
|
|
},
|
|
});
|
|
|
|
if (!store) {
|
|
throw new ApiError("Store not found", 404);
|
|
}
|
|
store.imageUrl = scaffoldAssetUrl(store.imageUrl);
|
|
return {
|
|
store,
|
|
};
|
|
}),
|
|
|
|
createStore: protectedProcedure
|
|
.input(z.object({
|
|
name: z.string().min(1, "Name is required"),
|
|
description: z.string().optional(),
|
|
imageUrl: z.string().optional(),
|
|
owner: z.number().min(1, "Owner is required"),
|
|
products: z.array(z.number()).optional(),
|
|
}))
|
|
.mutation(async ({ input, ctx }) => {
|
|
const { name, description, imageUrl, owner, products } = input;
|
|
|
|
// const imageKey = imageUrl ? extractKeyFromPresignedUrl(imageUrl) : undefined;
|
|
const imageKey = imageUrl
|
|
|
|
const [newStore] = await db
|
|
.insert(storeInfo)
|
|
.values({
|
|
name,
|
|
description,
|
|
imageUrl: imageKey,
|
|
owner,
|
|
})
|
|
.returning();
|
|
|
|
// Assign selected products to this store
|
|
if (products && products.length > 0) {
|
|
await db
|
|
.update(productInfo)
|
|
.set({ storeId: newStore.id })
|
|
.where(inArray(productInfo.id, products));
|
|
}
|
|
|
|
// Reinitialize stores to reflect changes
|
|
scheduleStoreInitialization()
|
|
|
|
return {
|
|
store: newStore,
|
|
message: "Store created successfully",
|
|
};
|
|
}),
|
|
|
|
updateStore: protectedProcedure
|
|
.input(z.object({
|
|
id: z.number(),
|
|
name: z.string().min(1, "Name is required"),
|
|
description: z.string().optional(),
|
|
imageUrl: z.string().optional(),
|
|
owner: z.number().min(1, "Owner is required"),
|
|
products: z.array(z.number()).optional(),
|
|
}))
|
|
.mutation(async ({ input, ctx }) => {
|
|
const { id, name, description, imageUrl, owner, products } = input;
|
|
|
|
const existingStore = await db.query.storeInfo.findFirst({
|
|
where: eq(storeInfo.id, id),
|
|
});
|
|
|
|
if (!existingStore) {
|
|
throw new ApiError("Store not found", 404);
|
|
}
|
|
|
|
const oldImageKey = existingStore.imageUrl;
|
|
const newImageKey = imageUrl ? extractKeyFromPresignedUrl(imageUrl) : oldImageKey;
|
|
|
|
// Delete old image only if:
|
|
// 1. New image provided and keys are different, OR
|
|
// 2. No new image but old exists (clearing the image)
|
|
if (oldImageKey && (
|
|
(newImageKey && newImageKey !== oldImageKey) ||
|
|
(!newImageKey)
|
|
)) {
|
|
try {
|
|
await deleteImageUtil({keys: [oldImageKey]});
|
|
} catch (error) {
|
|
console.error('Failed to delete old image:', error);
|
|
// Continue with update even if deletion fails
|
|
}
|
|
}
|
|
|
|
const [updatedStore] = await db
|
|
.update(storeInfo)
|
|
.set({
|
|
name,
|
|
description,
|
|
imageUrl: newImageKey,
|
|
owner,
|
|
})
|
|
.where(eq(storeInfo.id, id))
|
|
.returning();
|
|
|
|
if (!updatedStore) {
|
|
throw new ApiError("Store not found", 404);
|
|
}
|
|
|
|
// Update products if provided
|
|
if (products) {
|
|
// First, set storeId to null for products not in the list but currently assigned to this store
|
|
await db
|
|
.update(productInfo)
|
|
.set({ storeId: null })
|
|
.where(eq(productInfo.storeId, id));
|
|
|
|
// Then, assign the selected products to this store
|
|
if (products.length > 0) {
|
|
await db
|
|
.update(productInfo)
|
|
.set({ storeId: id })
|
|
.where(inArray(productInfo.id, products));
|
|
}
|
|
}
|
|
|
|
// Reinitialize stores to reflect changes
|
|
scheduleStoreInitialization()
|
|
|
|
return {
|
|
store: updatedStore,
|
|
message: "Store updated successfully",
|
|
};
|
|
}),
|
|
|
|
deleteStore: protectedProcedure
|
|
.input(z.object({
|
|
storeId: z.number(),
|
|
}))
|
|
.mutation(async ({ input, ctx }) => {
|
|
const { storeId } = input;
|
|
|
|
const result = await db.transaction(async (tx) => {
|
|
// First, update all products of this store to set storeId to null
|
|
await tx
|
|
.update(productInfo)
|
|
.set({ storeId: null })
|
|
.where(eq(productInfo.storeId, storeId));
|
|
|
|
// Then delete the store
|
|
const [deletedStore] = await tx
|
|
.delete(storeInfo)
|
|
.where(eq(storeInfo.id, storeId))
|
|
.returning();
|
|
|
|
if (!deletedStore) {
|
|
throw new ApiError("Store not found", 404);
|
|
}
|
|
|
|
return {
|
|
message: "Store deleted successfully",
|
|
};
|
|
});
|
|
|
|
// Reinitialize stores to reflect changes (outside transaction)
|
|
scheduleStoreInitialization()
|
|
|
|
return result;
|
|
}),
|
|
});
|