enh
This commit is contained in:
parent
a3680a3259
commit
7064625cba
60 changed files with 481 additions and 6247 deletions
|
|
@ -48,3 +48,6 @@ react-native. They are available in the common-ui as MyText, MyTextInput, MyTouc
|
|||
|
||||
## Important Notes
|
||||
- Don't do anything with git. Don't do git add or git commit. That will be managed entirely by the user
|
||||
|
||||
## Change Log
|
||||
- there should be a change log file. unless explicitly specified, assume change-log.txt as the name of the log file. Ensure the file exists. If it doesn't exist create it. Before changing any file which is not a md file or txt file write the change to that file exactly. Write the diff to it and then start making changes. for every change add the current time stamp.
|
||||
|
|
|
|||
|
|
@ -2,15 +2,12 @@ import 'dotenv/config';
|
|||
import { serve } from '@hono/node-server';
|
||||
import initFunc from '@/src/lib/init';
|
||||
import { createApp } from '@/src/app'
|
||||
// import signedUrlCache from '@/src/lib/signed-url-cache';
|
||||
import { seed } from '@/src/lib/seed';
|
||||
import '@/src/jobs/jobs-index';
|
||||
|
||||
seed()
|
||||
initFunc()
|
||||
|
||||
// signedUrlCache.loadFromDisk(); // Disabled for Workers compatibility
|
||||
|
||||
const app = createApp()
|
||||
|
||||
serve({
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
import axiosParent from "axios";
|
||||
import { getPhonePeBaseUrl } from "@/src/lib/env-exporter"
|
||||
|
||||
export const phonepeAxios = axiosParent.create({
|
||||
baseURL: getPhonePeBaseUrl(),
|
||||
timeout: 40000,
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
});
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
// catchAsync is no longer needed with Hono
|
||||
// Hono handles async errors automatically
|
||||
// This file is kept for backward compatibility but should be removed in the future
|
||||
|
||||
import { Context } from 'hono';
|
||||
|
||||
const catchAsync = (fn: (c: Context) => Promise<Response>) => {
|
||||
return fn;
|
||||
};
|
||||
|
||||
export default catchAsync;
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
import { deleteImageUtil, getOriginalUrlFromSignedUrl } from "@/src/lib/s3-client"
|
||||
import { getS3Url } from "@/src/lib/env-exporter"
|
||||
|
||||
function extractS3Key(url: string): string | null {
|
||||
try {
|
||||
// Check if this is a signed URL first and get the original if it is
|
||||
const originalUrl = getOriginalUrlFromSignedUrl(url) || url;
|
||||
|
||||
// Find the index of '.com/' in the URL
|
||||
// const comIndex = originalUrl.indexOf(".com/");
|
||||
const baseUrlIndex = originalUrl.indexOf(getS3Url());
|
||||
|
||||
// If '.com/' is found, return everything after it
|
||||
if (baseUrlIndex !== -1) {
|
||||
return originalUrl.substring(baseUrlIndex + getS3Url().length); // +5 to skip '.com/'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error extracting key from URL:", error);
|
||||
}
|
||||
|
||||
// Return null if the pattern isn't found or there was an error
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
export async function deleteS3Image(imageUrl: string) {
|
||||
try {
|
||||
// First check if this is a signed URL and get the original if it is
|
||||
const originalUrl = getOriginalUrlFromSignedUrl(imageUrl) || imageUrl;
|
||||
|
||||
const key = extractS3Key(originalUrl || "");
|
||||
|
||||
|
||||
if (!key) {
|
||||
throw new Error("Invalid image URL format");
|
||||
}
|
||||
const deleteS3 = await deleteImageUtil({keys: [key] });
|
||||
if (!deleteS3) {
|
||||
throw new Error("Failed to delete image from S3");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting image from S3:", error);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
// import fs from "fs";
|
||||
// import path from "path";
|
||||
//
|
||||
// export class DiskPersistedSet {
|
||||
// private set: Set<string>;
|
||||
// private readonly filePath: string;
|
||||
// private dirty = false;
|
||||
//
|
||||
// constructor(filePath: string = "./persister") {
|
||||
// this.filePath = path.resolve(filePath);
|
||||
//
|
||||
// // ✅ Ensure file exists
|
||||
// if (!fs.existsSync(this.filePath)) {
|
||||
// fs.writeFileSync(this.filePath, "", "utf8");
|
||||
// }
|
||||
//
|
||||
// // ✅ Load existing values from file
|
||||
// const contents = fs.readFileSync(this.filePath, "utf8");
|
||||
// this.set = new Set(
|
||||
// contents.split("\n").map(x => x.trim()).filter(x => x.length > 0)
|
||||
// );
|
||||
//
|
||||
// this.registerExitHandlers();
|
||||
// }
|
||||
//
|
||||
// private persist() {
|
||||
// if (!this.dirty) return;
|
||||
// fs.writeFileSync(this.filePath, Array.from(this.set).join("\n"), "utf8");
|
||||
// this.dirty = false;
|
||||
// }
|
||||
//
|
||||
// private markDirty() {
|
||||
// this.dirty = true;
|
||||
// }
|
||||
//
|
||||
// add(value: string): void {
|
||||
// if (!this.set.has(value)) {
|
||||
// this.set.add(value);
|
||||
// this.markDirty();
|
||||
// this.persist();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// delete(value: string): void {
|
||||
// if (this.set.delete(value)) {
|
||||
// this.markDirty();
|
||||
// this.persist();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// has(value: string): boolean {
|
||||
// return this.set.has(value);
|
||||
// }
|
||||
//
|
||||
// values(): string[] {
|
||||
// return Array.from(this.set);
|
||||
// }
|
||||
//
|
||||
// clear(): void {
|
||||
// if (this.set.size > 0) {
|
||||
// this.set.clear();
|
||||
// this.markDirty();
|
||||
// this.persist();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private registerExitHandlers() {
|
||||
// const flush = () => this.persist();
|
||||
//
|
||||
// process.on("exit", flush);
|
||||
// process.on("SIGINT", () => { flush(); process.exit(); });
|
||||
// process.on("SIGTERM", () => { flush(); process.exit(); });
|
||||
// process.on("uncaughtException", (err) => {
|
||||
// console.error("Uncaught exception. Flushing DiskPersistedSet:", err);
|
||||
// flush();
|
||||
// process.exit(1);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
// import redisClient from '@/src/lib/redis-client'
|
||||
|
||||
export async function enqueue(queueName: string, eventData: any): Promise<boolean> {
|
||||
try {
|
||||
const jsonData = JSON.stringify(eventData);
|
||||
// const result = await redisClient.lPush(queueName, jsonData);
|
||||
// return result > 0;
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Event enqueue error:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,245 +0,0 @@
|
|||
import { sendPushNotificationsMany } from "@/src/lib/expo-service"
|
||||
// import { usersTable, notifCredsTable, notificationTable } from "@/src/db/schema";
|
||||
|
||||
// Core notification dispatch methods (renamed for clarity)
|
||||
export async function dispatchBulkNotification({
|
||||
userIds = [],
|
||||
pushTokens = [],
|
||||
title,
|
||||
body,
|
||||
data,
|
||||
}: {
|
||||
userIds?: number[];
|
||||
pushTokens?: string[];
|
||||
title: string;
|
||||
body: string;
|
||||
data?: Record<string, unknown>;
|
||||
}) {
|
||||
try {
|
||||
let allPushTokens: string[] = [];
|
||||
|
||||
// Add provided pushTokens directly
|
||||
if (pushTokens && pushTokens.length > 0) {
|
||||
allPushTokens.push(...pushTokens.filter(Boolean));
|
||||
}
|
||||
|
||||
// Fetch push tokens for userIds
|
||||
// if (userIds && userIds.length > 0) {
|
||||
// const tokensFromDb = await db.query.notifCredsTable.findMany({
|
||||
// where: inArray(notifCredsTable.userId, userIds),
|
||||
// columns: { pushToken: true },
|
||||
// });
|
||||
// allPushTokens.push(
|
||||
// ...tokensFromDb.map((t) => t.pushToken).filter(Boolean)
|
||||
// );
|
||||
// }
|
||||
|
||||
// Remove duplicates
|
||||
allPushTokens = Array.from(new Set(allPushTokens));
|
||||
|
||||
if (allPushTokens.length === 0) {
|
||||
console.warn(`No push tokens found for users: ${userIds?.join(", ")}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const messages = allPushTokens.map((pushToken) => ({
|
||||
pushToken,
|
||||
title,
|
||||
body,
|
||||
data,
|
||||
}));
|
||||
await sendPushNotificationsMany(messages);
|
||||
} catch (error) {
|
||||
console.error("Error dispatching bulk notifications:", error);
|
||||
throw new Error("Failed to dispatch notifications");
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to dispatch notification to a single user or pushToken
|
||||
export async function dispatchUserNotification({
|
||||
userId,
|
||||
pushToken,
|
||||
title,
|
||||
body,
|
||||
data,
|
||||
}: {
|
||||
userId?: number;
|
||||
pushToken?: string;
|
||||
title: string;
|
||||
body: string;
|
||||
data?: Record<string, unknown>;
|
||||
}) {
|
||||
// Add entry to notificationTable if userId is provided
|
||||
if (userId) {
|
||||
try {
|
||||
// await db.insert(notificationTable).values({
|
||||
// userId,
|
||||
// title,
|
||||
// body,
|
||||
// payload: data,
|
||||
// // addedOn will default to now
|
||||
// });
|
||||
} catch (err) {
|
||||
console.error('Failed to insert notificationTable entry:', err);
|
||||
}
|
||||
}
|
||||
|
||||
await dispatchBulkNotification({
|
||||
userIds: userId ? [userId] : [],
|
||||
pushTokens: pushToken ? [pushToken] : [],
|
||||
title,
|
||||
body,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PURPOSE-SPECIFIC NOTIFICATION METHODS
|
||||
// =============================================================================
|
||||
|
||||
// Order-related notifications
|
||||
export const notifyOrderPlaced = (orderId: number, userId: number) =>
|
||||
dispatchUserNotification({
|
||||
title: 'Order Placed Successfully! 🎉',
|
||||
body: `Your order #${orderId} has been placed and is being processed.`,
|
||||
userId,
|
||||
data: { orderId, type: 'order_placed' }
|
||||
});
|
||||
|
||||
export const notifyOrderConfirmed = (orderId: number, userId: number) =>
|
||||
dispatchUserNotification({
|
||||
title: 'Order Confirmed! ✅',
|
||||
body: `Your order #${orderId} has been confirmed and is being prepared.`,
|
||||
userId,
|
||||
data: { orderId, type: 'order_confirmed' }
|
||||
});
|
||||
|
||||
export const notifyOrderReady = (orderId: number, userId: number) =>
|
||||
dispatchUserNotification({
|
||||
title: 'Order Ready for Pickup! 🍽️',
|
||||
body: `Your order #${orderId} is ready for pickup.`,
|
||||
userId,
|
||||
data: { orderId, type: 'order_ready' }
|
||||
});
|
||||
|
||||
export const notifyOrderCancelled = (orderId: number, userId: number, reason?: string) =>
|
||||
dispatchUserNotification({
|
||||
title: 'Order Cancelled ❌',
|
||||
body: `Your order #${orderId} has been cancelled.${reason ? ` Reason: ${reason}` : ''}`,
|
||||
userId,
|
||||
data: { orderId, type: 'order_cancelled', reason }
|
||||
});
|
||||
|
||||
// Delivery notifications
|
||||
export const notifyDeliveryAssigned = (orderId: number, userId: number, deliveryPerson: string) =>
|
||||
dispatchUserNotification({
|
||||
title: 'Delivery Partner Assigned 🚚',
|
||||
body: `${deliveryPerson} will deliver your order #${orderId}.`,
|
||||
userId,
|
||||
data: { orderId, deliveryPerson, type: 'delivery_assigned' }
|
||||
});
|
||||
|
||||
export const notifyOutForDelivery = (orderId: number, userId: number, eta: string) =>
|
||||
dispatchUserNotification({
|
||||
title: 'Out for Delivery! 📦',
|
||||
body: `Your order #${orderId} is out for delivery. Expected arrival: ${eta}`,
|
||||
userId,
|
||||
data: { orderId, eta, type: 'out_for_delivery' }
|
||||
});
|
||||
|
||||
export const notifyDelivered = (orderId: number, userId: number) =>
|
||||
dispatchUserNotification({
|
||||
title: 'Order Delivered! 🎊',
|
||||
body: `Your order #${orderId} has been delivered successfully.`,
|
||||
userId,
|
||||
data: { orderId, type: 'delivered' }
|
||||
});
|
||||
|
||||
// Payment notifications
|
||||
export const notifyPaymentSuccess = (orderId: number, userId: number, amount: number) =>
|
||||
dispatchUserNotification({
|
||||
title: 'Payment Successful 💳',
|
||||
body: `Payment of ₹${amount} for order #${orderId} was successful.`,
|
||||
userId,
|
||||
data: { orderId, amount, type: 'payment_success' }
|
||||
});
|
||||
|
||||
export const notifyPaymentFailed = (orderId: number, userId: number) =>
|
||||
dispatchUserNotification({
|
||||
title: 'Payment Failed ❌',
|
||||
body: `Payment for order #${orderId} failed. Please try again.`,
|
||||
userId,
|
||||
data: { orderId, type: 'payment_failed' }
|
||||
});
|
||||
|
||||
export const notifyRefundProcessed = (orderId: number, userId: number, amount: number) =>
|
||||
dispatchUserNotification({
|
||||
title: 'Refund Processed 💰',
|
||||
body: `Refund of ₹${amount} for order #${orderId} has been processed.`,
|
||||
userId,
|
||||
data: { orderId, amount, type: 'refund_processed' }
|
||||
});
|
||||
|
||||
// Promotional notifications
|
||||
export const notifyNewOffer = (userIds: number[], offerTitle: string, offerDetails: string) =>
|
||||
dispatchBulkNotification({
|
||||
title: `New Offer: ${offerTitle} 🎁`,
|
||||
body: offerDetails,
|
||||
userIds,
|
||||
data: { type: 'promotion', offerTitle }
|
||||
});
|
||||
|
||||
export const notifyFlashSale = (userIds: number[], productName: string, discount: number) =>
|
||||
dispatchBulkNotification({
|
||||
title: 'Flash Sale! ⚡',
|
||||
body: `Get ${discount}% off on ${productName}. Limited time offer!`,
|
||||
userIds,
|
||||
data: { type: 'flash_sale', productName, discount }
|
||||
});
|
||||
|
||||
export const notifyLoyaltyPointsEarned = (userId: number, points: number, reason: string) =>
|
||||
dispatchUserNotification({
|
||||
title: 'Loyalty Points Earned! ⭐',
|
||||
body: `You earned ${points} points for ${reason}.`,
|
||||
userId,
|
||||
data: { points, reason, type: 'loyalty_points' }
|
||||
});
|
||||
|
||||
// Account notifications
|
||||
export const notifyWelcome = (userId: number, userName: string) =>
|
||||
dispatchUserNotification({
|
||||
title: 'Welcome to Meat Farmer! 🥩',
|
||||
body: `Hi ${userName}, welcome to our fresh meat delivery service.`,
|
||||
userId,
|
||||
data: { type: 'welcome' }
|
||||
});
|
||||
|
||||
export const notifyPasswordReset = (userId: number) =>
|
||||
dispatchUserNotification({
|
||||
title: 'Password Reset Successful 🔐',
|
||||
body: 'Your password has been reset successfully.',
|
||||
userId,
|
||||
data: { type: 'password_reset' }
|
||||
});
|
||||
|
||||
export const notifyAccountVerified = (userId: number) =>
|
||||
dispatchUserNotification({
|
||||
title: 'Account Verified! ✅',
|
||||
body: 'Your account has been successfully verified.',
|
||||
userId,
|
||||
data: { type: 'account_verified' }
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// BACKWARD COMPATIBILITY (DEPRECATED)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @deprecated Use notifyOrderConfirmed() or other purpose-specific methods instead
|
||||
*/
|
||||
export const sendNotifToSingleUser = dispatchUserNotification;
|
||||
|
||||
/**
|
||||
* @deprecated Use notifyNewOffer() or other purpose-specific methods instead
|
||||
*/
|
||||
export const sendNotifToManyUsers = dispatchBulkNotification;
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
// import { createClient, RedisClientType } from 'redis';
|
||||
import { getRedisUrl } from '@/src/lib/env-exporter'
|
||||
|
||||
const createClient = (args:any) => {}
|
||||
class RedisClient {
|
||||
// private client: RedisClientType;
|
||||
// private subscriberClient: RedisClientType | null = null;
|
||||
// private isConnected: boolean = false;
|
||||
//
|
||||
private client: any;
|
||||
private subscriberrlient: any;
|
||||
private isConnected: any = false;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.client = createClient({
|
||||
url: getRedisUrl(),
|
||||
});
|
||||
|
||||
// this.client.on('error', (err) => {
|
||||
// console.error('Redis Client Error:', err);
|
||||
// });
|
||||
//
|
||||
// this.client.on('connect', () => {
|
||||
// console.log('Redis Client Connected');
|
||||
// this.isConnected = true;
|
||||
// });
|
||||
//
|
||||
// this.client.on('disconnect', () => {
|
||||
// console.log('Redis Client Disconnected');
|
||||
// this.isConnected = false;
|
||||
// });
|
||||
//
|
||||
// this.client.on('ready', () => {
|
||||
// console.log('Redis Client Ready');
|
||||
// });
|
||||
//
|
||||
// this.client.on('reconnecting', () => {
|
||||
// console.log('Redis Client Reconnecting');
|
||||
// });
|
||||
|
||||
// Connect immediately (fire and forget)
|
||||
// this.client.connect().catch((err) => {
|
||||
// console.error('Failed to connect Redis:', err);
|
||||
// });
|
||||
}
|
||||
|
||||
async set(key: string, value: string, ttlSeconds?: number): Promise<string | null> {
|
||||
if (ttlSeconds) {
|
||||
return await this.client.setEx(key, ttlSeconds, value);
|
||||
} else {
|
||||
return await this.client.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
async get(key: string): Promise<string | null> {
|
||||
return await this.client.get(key);
|
||||
}
|
||||
|
||||
async exists(key: string): Promise<boolean> {
|
||||
const result = await this.client.exists(key);
|
||||
return result === 1;
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<number> {
|
||||
return await this.client.del(key);
|
||||
}
|
||||
|
||||
async lPush(key: string, value: string): Promise<number> {
|
||||
return await this.client.lPush(key, value);
|
||||
}
|
||||
|
||||
async KEYS(pattern: string): Promise<string[]> {
|
||||
return await this.client.KEYS(pattern);
|
||||
}
|
||||
|
||||
async MGET(keys: string[]): Promise<(string | null)[]> {
|
||||
return await this.client.MGET(keys);
|
||||
}
|
||||
|
||||
// Publish message to a channel
|
||||
async publish(channel: string, message: string): Promise<number> {
|
||||
return await this.client.publish(channel, message);
|
||||
}
|
||||
|
||||
// Subscribe to a channel with callback
|
||||
async subscribe(channel: string, callback: (message: string) => void): Promise<void> {
|
||||
// if (!this.subscriberClient) {
|
||||
// this.subscriberClient = createClient({
|
||||
// url: redisUrl,
|
||||
// });
|
||||
//
|
||||
// this.subscriberClient.on('error', (err) => {
|
||||
// console.error('Redis Subscriber Error:', err);
|
||||
// });
|
||||
//
|
||||
// this.subscriberClient.on('connect', () => {
|
||||
// console.log('Redis Subscriber Connected');
|
||||
// });
|
||||
//
|
||||
// await this.subscriberClient.connect();
|
||||
// }
|
||||
//
|
||||
// await this.subscriberClient.subscribe(channel, callback);
|
||||
console.log(`Subscribed to channel: ${channel}`);
|
||||
}
|
||||
|
||||
// Unsubscribe from a channel
|
||||
async unsubscribe(channel: string): Promise<void> {
|
||||
// if (this.subscriberClient) {
|
||||
// await this.subscriberClient.unsubscribe(channel);
|
||||
// console.log(`Unsubscribed from channel: ${channel}`);
|
||||
// }
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
// if (this.isConnected) {
|
||||
// this.client.disconnect();
|
||||
// }
|
||||
// if (this.subscriberClient) {
|
||||
// this.subscriberClient.disconnect();
|
||||
// }
|
||||
}
|
||||
|
||||
get isClientConnected(): boolean {
|
||||
return this.isConnected;
|
||||
}
|
||||
}
|
||||
|
||||
const redisClient = new RedisClient();
|
||||
|
||||
export default redisClient;
|
||||
export { RedisClient };
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export function getSlotSequenceKey(slotId: number | string): string {
|
||||
return `slot_sequence_${slotId}`;
|
||||
}
|
||||
|
|
@ -1,263 +0,0 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const CACHE_FILE_PATH = path.join('.', 'assets', 'signed-url-cache.json');
|
||||
|
||||
// Interface for cache entries with TTL
|
||||
interface CacheEntry {
|
||||
value: string;
|
||||
expiresAt: number; // Timestamp when this entry expires
|
||||
}
|
||||
|
||||
class SignedURLCache {
|
||||
private originalToSignedCache: Map<string, CacheEntry>;
|
||||
private signedToOriginalCache: Map<string, CacheEntry>;
|
||||
|
||||
constructor() {
|
||||
this.originalToSignedCache = new Map();
|
||||
this.signedToOriginalCache = new Map();
|
||||
|
||||
// Create cache directory if it doesn't exist
|
||||
const cacheDir = path.dirname(CACHE_FILE_PATH);
|
||||
if (!fs.existsSync(cacheDir)) {
|
||||
console.log('creating the directory')
|
||||
|
||||
fs.mkdirSync(cacheDir, { recursive: true });
|
||||
}
|
||||
else {
|
||||
console.log('the directory is already present')
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a signed URL from the cache using an original URL as the key
|
||||
*/
|
||||
get(originalUrl: string): string | undefined {
|
||||
const entry = this.originalToSignedCache.get(originalUrl);
|
||||
|
||||
// If no entry or entry has expired, return undefined
|
||||
if (!entry || Date.now() > entry.expiresAt) {
|
||||
if (entry) {
|
||||
// Remove expired entry
|
||||
this.originalToSignedCache.delete(originalUrl);
|
||||
// Also remove from reverse mapping if it exists
|
||||
this.signedToOriginalCache.delete(entry.value);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the original URL from the cache using a signed URL as the key
|
||||
*/
|
||||
getOriginalUrl(signedUrl: string): string | undefined {
|
||||
const entry = this.signedToOriginalCache.get(signedUrl);
|
||||
|
||||
// If no entry or entry has expired, return undefined
|
||||
if (!entry || Date.now() > entry.expiresAt) {
|
||||
if (entry) {
|
||||
// Remove expired entry
|
||||
this.signedToOriginalCache.delete(signedUrl);
|
||||
// Also remove from primary mapping if it exists
|
||||
this.originalToSignedCache.delete(entry.value);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value in the cache with a TTL (Time To Live)
|
||||
* @param originalUrl The original S3 URL
|
||||
* @param signedUrl The signed URL
|
||||
* @param ttlMs Time to live in milliseconds (default: 3 days)
|
||||
*/
|
||||
set(originalUrl: string, signedUrl: string, ttlMs: number = 259200000): void {
|
||||
const expiresAt = Date.now() + ttlMs;
|
||||
|
||||
const entry: CacheEntry = {
|
||||
value: signedUrl,
|
||||
expiresAt
|
||||
};
|
||||
|
||||
const reverseEntry: CacheEntry = {
|
||||
value: originalUrl,
|
||||
expiresAt
|
||||
};
|
||||
|
||||
this.originalToSignedCache.set(originalUrl, entry);
|
||||
this.signedToOriginalCache.set(signedUrl, reverseEntry);
|
||||
}
|
||||
|
||||
has(originalUrl: string): boolean {
|
||||
const entry = this.originalToSignedCache.get(originalUrl);
|
||||
|
||||
// Entry exists and hasn't expired
|
||||
return !!entry && Date.now() <= entry.expiresAt;
|
||||
}
|
||||
|
||||
hasSignedUrl(signedUrl: string): boolean {
|
||||
const entry = this.signedToOriginalCache.get(signedUrl);
|
||||
|
||||
// Entry exists and hasn't expired
|
||||
return !!entry && Date.now() <= entry.expiresAt;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.originalToSignedCache.clear();
|
||||
this.signedToOriginalCache.clear();
|
||||
this.saveToDisk();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all expired entries from the cache
|
||||
* @returns The number of expired entries that were removed
|
||||
*/
|
||||
clearExpired(): number {
|
||||
const now = Date.now();
|
||||
let removedCount = 0;
|
||||
|
||||
// Clear expired entries from original to signed cache
|
||||
for (const [originalUrl, entry] of this.originalToSignedCache.entries()) {
|
||||
if (now > entry.expiresAt) {
|
||||
this.originalToSignedCache.delete(originalUrl);
|
||||
removedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear expired entries from signed to original cache
|
||||
for (const [signedUrl, entry] of this.signedToOriginalCache.entries()) {
|
||||
if (now > entry.expiresAt) {
|
||||
this.signedToOriginalCache.delete(signedUrl);
|
||||
// No need to increment removedCount as we've already counted these in the first loop
|
||||
}
|
||||
}
|
||||
|
||||
if (removedCount > 0) {
|
||||
console.log(`SignedURLCache: Cleared ${removedCount} expired entries`);
|
||||
}
|
||||
|
||||
return removedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the cache to disk
|
||||
*/
|
||||
saveToDisk(): void {
|
||||
try {
|
||||
// Remove expired entries before saving
|
||||
const removedCount = this.clearExpired();
|
||||
|
||||
// Convert Maps to serializable objects
|
||||
const serializedOriginalToSigned: Record<string, { value: string; expiresAt: number }> = {};
|
||||
const serializedSignedToOriginal: Record<string, { value: string; expiresAt: number }> = {};
|
||||
|
||||
for (const [originalUrl, entry] of this.originalToSignedCache.entries()) {
|
||||
serializedOriginalToSigned[originalUrl] = {
|
||||
value: entry.value,
|
||||
expiresAt: entry.expiresAt
|
||||
};
|
||||
}
|
||||
|
||||
for (const [signedUrl, entry] of this.signedToOriginalCache.entries()) {
|
||||
serializedSignedToOriginal[signedUrl] = {
|
||||
value: entry.value,
|
||||
expiresAt: entry.expiresAt
|
||||
};
|
||||
}
|
||||
|
||||
const serializedCache = {
|
||||
originalToSigned: serializedOriginalToSigned,
|
||||
signedToOriginal: serializedSignedToOriginal
|
||||
};
|
||||
|
||||
// Write to file
|
||||
fs.writeFileSync(
|
||||
CACHE_FILE_PATH,
|
||||
JSON.stringify(serializedCache),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
console.log(`SignedURLCache: Saved ${this.originalToSignedCache.size} entries to disk`);
|
||||
} catch (error) {
|
||||
console.error('Error saving SignedURLCache to disk:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the cache from disk
|
||||
*/
|
||||
loadFromDisk(): void {
|
||||
try {
|
||||
if (fs.existsSync(CACHE_FILE_PATH)) {
|
||||
// Read from file
|
||||
const data = fs.readFileSync(CACHE_FILE_PATH, 'utf8');
|
||||
|
||||
// Parse the data
|
||||
const parsedData = JSON.parse(data) as {
|
||||
originalToSigned: Record<string, { value: string; expiresAt: number }>,
|
||||
signedToOriginal: Record<string, { value: string; expiresAt: number }>
|
||||
};
|
||||
|
||||
// Only load entries that haven't expired yet
|
||||
const now = Date.now();
|
||||
let loadedCount = 0;
|
||||
let expiredCount = 0;
|
||||
|
||||
// Load original to signed mappings
|
||||
if (parsedData.originalToSigned) {
|
||||
for (const [originalUrl, entry] of Object.entries(parsedData.originalToSigned)) {
|
||||
if (now <= entry.expiresAt) {
|
||||
this.originalToSignedCache.set(originalUrl, entry);
|
||||
loadedCount++;
|
||||
} else {
|
||||
expiredCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load signed to original mappings
|
||||
if (parsedData.signedToOriginal) {
|
||||
for (const [signedUrl, entry] of Object.entries(parsedData.signedToOriginal)) {
|
||||
if (now <= entry.expiresAt) {
|
||||
this.signedToOriginalCache.set(signedUrl, entry);
|
||||
// Don't increment loadedCount as these are pairs of what we already counted
|
||||
} else {
|
||||
// Don't increment expiredCount as these are pairs of what we already counted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`SignedURLCache: Loaded ${loadedCount} valid entries from disk (skipped ${expiredCount} expired entries)`);
|
||||
} else {
|
||||
console.log('SignedURLCache: No cache file found, starting with empty cache');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading SignedURLCache from disk:', error);
|
||||
// Start with empty caches if loading fails
|
||||
this.originalToSignedCache = new Map();
|
||||
this.signedToOriginalCache = new Map();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a singleton instance to be used throughout the application
|
||||
const signedUrlCache = new SignedURLCache();
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('SignedURLCache: Saving cache before shutdown...');
|
||||
signedUrlCache.saveToDisk();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('SignedURLCache: Saving cache before shutdown...');
|
||||
signedUrlCache.saveToDisk();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
export default signedUrlCache;
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
// SIGNED URL CACHE - DISABLED
|
||||
// This file has been disabled to make the backend compatible with Cloudflare Workers.
|
||||
// File system operations are not available in the Workers environment.
|
||||
//
|
||||
// To re-enable caching, migrate to Cloudflare R2 or another object storage solution.
|
||||
// Original file saved as: signed-url-cache-old.ts
|
||||
//
|
||||
// Impact of disabling:
|
||||
// - S3 signed URLs are generated fresh on every request
|
||||
// - Increased AWS API calls (higher costs)
|
||||
// - Slightly slower image loading
|
||||
// - No file system dependencies (Workers-compatible)
|
||||
|
||||
export default {
|
||||
get: () => undefined,
|
||||
set: () => {},
|
||||
getOriginalUrl: () => undefined,
|
||||
has: () => false,
|
||||
hasSignedUrl: () => false,
|
||||
clear: () => {},
|
||||
clearExpired: () => 0,
|
||||
saveToDisk: () => {},
|
||||
loadFromDisk: () => {},
|
||||
};
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
import { Context, Next } from 'hono';
|
||||
import { jwtVerify, errors } from 'jose';
|
||||
import { ApiError } from '@/src/lib/api-error'
|
||||
import { getEncodedJwtSecret } from '@/src/lib/env-exporter';
|
||||
|
||||
export const verifyToken = async (c: Context, next: Next) => {
|
||||
try {
|
||||
// Get token from Authorization header
|
||||
const authHeader = c.req.header('authorization');
|
||||
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
throw new ApiError('Access denied. No token provided', 401);
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
throw new ApiError('Access denied. Invalid token format', 401);
|
||||
}
|
||||
|
||||
// Verify token
|
||||
const { payload } = await jwtVerify(token, getEncodedJwtSecret());
|
||||
|
||||
|
||||
// Add user info to context
|
||||
c.set('user', payload);
|
||||
|
||||
await next();
|
||||
} catch (error) {
|
||||
if (error instanceof errors.JOSEError) {
|
||||
throw new ApiError('Invalid Auth Credentials', 401);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const requireRole = (roles: string[]) => {
|
||||
return async (c: Context, next: Next) => {
|
||||
try {
|
||||
const user = c.get('user');
|
||||
if (!user) {
|
||||
throw new ApiError('Authentication required', 401);
|
||||
}
|
||||
|
||||
// Check if user has any of the required roles
|
||||
const userRoles = user.roles || [];
|
||||
const hasPermission = roles.some(role => userRoles.includes(role));
|
||||
|
||||
if (!hasPermission) {
|
||||
throw new ApiError('Access denied. Insufficient permissions', 403);
|
||||
}
|
||||
|
||||
await next();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -1,281 +0,0 @@
|
|||
// // Postgres Importer - Intermediate layer to avoid direct postgresService imports in dbService
|
||||
// // This file re-exports everything from postgresService
|
||||
//
|
||||
// // Re-export database connection
|
||||
// export { db } from 'postgresService'
|
||||
//
|
||||
// // Re-export all schema exports
|
||||
// export * from 'postgresService'
|
||||
//
|
||||
// // Re-export all helper methods from postgresService
|
||||
// export {
|
||||
// // Admin - Banner
|
||||
// getBanners,
|
||||
// getBannerById,
|
||||
// createBanner,
|
||||
// updateBanner,
|
||||
// deleteBanner,
|
||||
// // Admin - Complaint
|
||||
// getComplaints,
|
||||
// resolveComplaint,
|
||||
// // Admin - Constants
|
||||
// getAllConstants,
|
||||
// upsertConstants,
|
||||
// // Admin - Coupon
|
||||
// getAllCoupons,
|
||||
// getCouponById,
|
||||
// invalidateCoupon,
|
||||
// validateCoupon,
|
||||
// getReservedCoupons,
|
||||
// getUsersForCoupon,
|
||||
// createCouponWithRelations,
|
||||
// updateCouponWithRelations,
|
||||
// generateCancellationCoupon,
|
||||
// createReservedCouponWithProducts,
|
||||
// createCouponForUser,
|
||||
// checkUsersExist,
|
||||
// checkCouponExists,
|
||||
// checkReservedCouponExists,
|
||||
// getOrderWithUser,
|
||||
// // Admin - Order
|
||||
// updateOrderNotes,
|
||||
// getOrderDetails,
|
||||
// updateOrderPackaged,
|
||||
// updateOrderDelivered,
|
||||
// updateOrderItemPackaging,
|
||||
// removeDeliveryCharge,
|
||||
// getSlotOrders,
|
||||
// updateAddressCoords,
|
||||
// getAllOrders,
|
||||
// rebalanceSlots,
|
||||
// cancelOrder,
|
||||
// deleteOrderById,
|
||||
// // Admin - Product
|
||||
// getAllProducts,
|
||||
// getProductById,
|
||||
// deleteProduct,
|
||||
// createProduct,
|
||||
// updateProduct,
|
||||
// checkProductExistsByName,
|
||||
// checkUnitExists,
|
||||
// getProductImagesById,
|
||||
// createSpecialDealsForProduct,
|
||||
// updateProductDeals,
|
||||
// replaceProductTags,
|
||||
// toggleProductOutOfStock,
|
||||
// updateSlotProducts,
|
||||
// getSlotsProductIds,
|
||||
// getAllUnits,
|
||||
// getAllProductTags,
|
||||
// getAllProductTagInfos,
|
||||
// getProductTagInfoById,
|
||||
// createProductTag,
|
||||
// getProductTagById,
|
||||
// updateProductTag,
|
||||
// deleteProductTag,
|
||||
// checkProductTagExistsByName,
|
||||
// getProductReviews,
|
||||
// respondToReview,
|
||||
// getAllProductGroups,
|
||||
// createProductGroup,
|
||||
// updateProductGroup,
|
||||
// deleteProductGroup,
|
||||
// addProductToGroup,
|
||||
// removeProductFromGroup,
|
||||
// updateProductPrices,
|
||||
// // Admin - Slots
|
||||
// getActiveSlotsWithProducts,
|
||||
// getActiveSlots,
|
||||
// getSlotsAfterDate,
|
||||
// getSlotByIdWithRelations,
|
||||
// createSlotWithRelations,
|
||||
// updateSlotWithRelations,
|
||||
// deleteSlotById,
|
||||
// updateSlotCapacity,
|
||||
// getSlotDeliverySequence,
|
||||
// updateSlotDeliverySequence,
|
||||
// // Admin - Staff User
|
||||
// getStaffUserByName,
|
||||
// getStaffUserById,
|
||||
// getAllStaff,
|
||||
// getAllUsers,
|
||||
// getUserWithDetails,
|
||||
// updateUserSuspensionStatus,
|
||||
// checkStaffUserExists,
|
||||
// checkStaffRoleExists,
|
||||
// createStaffUser,
|
||||
// getAllRoles,
|
||||
// // Admin - Store
|
||||
// getAllStores,
|
||||
// getStoreById,
|
||||
// createStore,
|
||||
// updateStore,
|
||||
// deleteStore,
|
||||
// // Admin - User
|
||||
// createUserByMobile,
|
||||
// getUserByMobile,
|
||||
// getUnresolvedComplaintsCount,
|
||||
// getAllUsersWithFilters,
|
||||
// getOrderCountsByUserIds,
|
||||
// getLastOrdersByUserIds,
|
||||
// getSuspensionStatusesByUserIds,
|
||||
// getUserBasicInfo,
|
||||
// getUserSuspensionStatus,
|
||||
// getUserOrders,
|
||||
// getOrderStatusesByOrderIds,
|
||||
// getItemCountsByOrderIds,
|
||||
// upsertUserSuspension,
|
||||
// searchUsers,
|
||||
// getAllNotifCreds,
|
||||
// getAllUnloggedTokens,
|
||||
// getNotifTokensByUserIds,
|
||||
// getUserIncidentsWithRelations,
|
||||
// createUserIncident,
|
||||
// // Admin - Vendor Snippets
|
||||
// checkVendorSnippetExists,
|
||||
// getVendorSnippetById,
|
||||
// getVendorSnippetByCode,
|
||||
// getAllVendorSnippets,
|
||||
// createVendorSnippet,
|
||||
// updateVendorSnippet,
|
||||
// deleteVendorSnippet,
|
||||
// getProductsByIds,
|
||||
// getVendorSlotById,
|
||||
// getVendorOrdersBySlotId,
|
||||
// getOrderItemsByOrderIds,
|
||||
// getOrderStatusByOrderIds,
|
||||
// updateVendorOrderItemPackaging,
|
||||
// getVendorOrders,
|
||||
// // User - Address
|
||||
// getUserDefaultAddress,
|
||||
// getUserAddresses,
|
||||
// getUserAddressById,
|
||||
// clearUserDefaultAddress,
|
||||
// createUserAddress,
|
||||
// updateUserAddress,
|
||||
// deleteUserAddress,
|
||||
// hasOngoingOrdersForAddress,
|
||||
// // User - Banners
|
||||
// getUserActiveBanners,
|
||||
// // User - Complaint
|
||||
// getUserComplaints,
|
||||
// createUserComplaint,
|
||||
// // User - Stores
|
||||
// getUserStoreSummaries,
|
||||
// getUserStoreDetail,
|
||||
// // User - Product
|
||||
// getUserProductDetailById,
|
||||
// getUserProductReviews,
|
||||
// getUserProductByIdBasic,
|
||||
// createUserProductReview,
|
||||
// getAllProductsWithUnits,
|
||||
// type ProductSummaryData,
|
||||
// // User - Slots
|
||||
// getUserActiveSlotsList,
|
||||
// getUserProductAvailability,
|
||||
// // User - Payments
|
||||
// getUserPaymentOrderById,
|
||||
// getUserPaymentByOrderId,
|
||||
// getUserPaymentByMerchantOrderId,
|
||||
// updateUserPaymentSuccess,
|
||||
// updateUserOrderPaymentStatus,
|
||||
// markUserPaymentFailed,
|
||||
// // User - Auth
|
||||
// getUserAuthByEmail,
|
||||
// getUserAuthByMobile,
|
||||
// getUserAuthById,
|
||||
// getUserAuthCreds,
|
||||
// getUserAuthDetails,
|
||||
// isUserSuspended,
|
||||
// createUserAuthWithCreds,
|
||||
// createUserAuthWithMobile,
|
||||
// upsertUserAuthPassword,
|
||||
// deleteUserAuthAccount,
|
||||
// // UV API helpers
|
||||
// createUserWithProfile,
|
||||
// getUserDetailsByUserId,
|
||||
// updateUserProfile,
|
||||
// // User - Coupon
|
||||
// getUserActiveCouponsWithRelations,
|
||||
// getUserAllCouponsWithRelations,
|
||||
// getUserReservedCouponByCode,
|
||||
// redeemUserReservedCoupon,
|
||||
// // User - Profile
|
||||
// getUserProfileById,
|
||||
// getUserProfileDetailById,
|
||||
// getUserWithCreds,
|
||||
// getUserNotifCred,
|
||||
// upsertUserNotifCred,
|
||||
// deleteUserUnloggedToken,
|
||||
// getUserUnloggedToken,
|
||||
// upsertUserUnloggedToken,
|
||||
// // User - Order
|
||||
// validateAndGetUserCoupon,
|
||||
// applyDiscountToUserOrder,
|
||||
// getUserAddressByIdAndUser,
|
||||
// getOrderProductById,
|
||||
// checkUserSuspended,
|
||||
// getUserSlotCapacityStatus,
|
||||
// placeUserOrderTransaction,
|
||||
// deleteUserCartItemsForOrder,
|
||||
// recordUserCouponUsage,
|
||||
// getUserOrdersWithRelations,
|
||||
// getUserOrderCount,
|
||||
// getUserOrderByIdWithRelations,
|
||||
// getUserCouponUsageForOrder,
|
||||
// getUserOrderBasic,
|
||||
// cancelUserOrderTransaction,
|
||||
// updateUserOrderNotes,
|
||||
// // Store Helpers
|
||||
// getAllBannersForCache,
|
||||
// getAllProductsForCache,
|
||||
// getAllStoresForCache,
|
||||
// getAllDeliverySlotsForCache,
|
||||
// getAllSpecialDealsForCache,
|
||||
// getAllProductTagsForCache,
|
||||
// getAllTagsForCache,
|
||||
// getAllTagProductMappings,
|
||||
// getAllSlotsWithProductsForCache,
|
||||
// getAllUserNegativityScores,
|
||||
// getUserNegativityScore,
|
||||
// type BannerData,
|
||||
// type ProductBasicData,
|
||||
// type StoreBasicData,
|
||||
// type DeliverySlotData,
|
||||
// type SpecialDealData,
|
||||
// type ProductTagData,
|
||||
// type TagBasicData,
|
||||
// type TagProductMapping,
|
||||
// type SlotWithProductsData,
|
||||
// type UserNegativityData,
|
||||
// // Automated Jobs
|
||||
// toggleFlashDeliveryForItems,
|
||||
// toggleKeyVal,
|
||||
// getAllKeyValStore,
|
||||
// // Post-order handler helpers
|
||||
// getOrdersByIdsWithFullData,
|
||||
// getOrderByIdWithFullData,
|
||||
// type OrderWithFullData,
|
||||
// type OrderWithCancellationData,
|
||||
// // Common API helpers
|
||||
// getSuspendedProductIds,
|
||||
// getNextDeliveryDateWithCapacity,
|
||||
// getStoresSummary,
|
||||
// healthCheck,
|
||||
// // Delete orders helper
|
||||
// deleteOrdersWithRelations,
|
||||
// // Seed helpers
|
||||
// seedUnits,
|
||||
// seedStaffRoles,
|
||||
// seedStaffPermissions,
|
||||
// seedRolePermissions,
|
||||
// seedKeyValStore,
|
||||
// type UnitSeedData,
|
||||
// type RolePermissionAssignment,
|
||||
// type KeyValSeedData,
|
||||
// type StaffRoleName,
|
||||
// type StaffPermissionName,
|
||||
// // Upload URL Helpers
|
||||
// createUploadUrlStatus,
|
||||
// claimUploadUrlStatus,
|
||||
// } from 'postgresService'
|
||||
|
|
@ -69,14 +69,10 @@ export {
|
|||
checkProductExistsByName,
|
||||
checkUnitExists,
|
||||
getProductImagesById,
|
||||
createSpecialDealsForSku,
|
||||
updateSkuDeals,
|
||||
replaceProductTags,
|
||||
replaceTagProducts,
|
||||
mergeSkus,
|
||||
updateSlotProducts,
|
||||
getSlotsProductIds,
|
||||
getAllUnits,
|
||||
getAllProductTags,
|
||||
getAllProductTagInfos,
|
||||
getProductTagInfoById,
|
||||
|
|
@ -91,8 +87,6 @@ export {
|
|||
createProductGroup,
|
||||
updateProductGroup,
|
||||
deleteProductGroup,
|
||||
addProductToGroup,
|
||||
removeProductFromGroup,
|
||||
updateProductPrices,
|
||||
// Merge duplicate products into multi-SKU products
|
||||
mergeDuplicateProducts,
|
||||
|
|
@ -114,7 +108,6 @@ export {
|
|||
getAllStaff,
|
||||
getAllUsers,
|
||||
getUserWithDetails,
|
||||
updateUserSuspensionStatus,
|
||||
checkStaffUserExists,
|
||||
checkStaffRoleExists,
|
||||
createStaffUser,
|
||||
|
|
@ -156,8 +149,6 @@ export {
|
|||
getProductsByIds,
|
||||
getVendorSlotById,
|
||||
getVendorOrdersBySlotId,
|
||||
getOrderItemsByOrderIds,
|
||||
getOrderStatusByOrderIds,
|
||||
updateVendorOrderItemPackaging,
|
||||
getVendorOrders,
|
||||
// User - Address
|
||||
|
|
@ -218,7 +209,6 @@ export {
|
|||
getUserProfileById,
|
||||
getUserProfileDetailById,
|
||||
getUserWithCreds,
|
||||
getUserNotifCred,
|
||||
upsertUserNotifCred,
|
||||
deleteUserUnloggedToken,
|
||||
getUserUnloggedToken,
|
||||
|
|
@ -269,8 +259,6 @@ export {
|
|||
// Post-order handler helpers
|
||||
getOrdersByIdsWithFullData,
|
||||
getOrderByIdWithFullData,
|
||||
type OrderWithFullData,
|
||||
type OrderWithCancellationData,
|
||||
// Common API helpers
|
||||
getSuspendedSkuIds,
|
||||
getNextDeliveryDateWithCapacity,
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
// Database Types - Re-exports from shared package
|
||||
// Central type definitions for backend database operations
|
||||
|
||||
export type { Banner } from '@packages/shared';
|
||||
472
change-log.txt
Normal file
472
change-log.txt
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
=== Session change log ===
|
||||
Session started: 2026-09-02
|
||||
Rule: Before changing any file that is not a .md or .txt file, write the current timestamp followed by the exact diff here first, then make the change.
|
||||
|
||||
(no file changes made yet in this session)
|
||||
|
||||
[2026-09-02 20:34:48] DELETE dead files per DEAD_CODE_REPORT.md §1 (28 files):
|
||||
|
||||
Removed in packages/db_helper_sqlite/src/helper_methods/:
|
||||
- banner.ts (entire file — legacy duplicate, never imported)
|
||||
- complaint.ts (entire file — legacy duplicate, never imported)
|
||||
- const.ts (entire file — legacy duplicate, never imported)
|
||||
- coupon.ts (entire file — legacy duplicate, never imported)
|
||||
- order.ts (entire file — legacy duplicate, never imported)
|
||||
- product.ts (entire file — legacy duplicate, never imported)
|
||||
- slots.ts (entire file — legacy duplicate, never imported)
|
||||
- staff-user.ts (entire file — legacy duplicate, never imported)
|
||||
- store.ts (entire file — legacy duplicate, never imported)
|
||||
- user.ts (entire file — legacy duplicate, never imported)
|
||||
- vendor-snippets.ts (entire file — legacy duplicate, never imported)
|
||||
|
||||
Removed in packages/db_helper_postgres/src/helper_methods/: (same 11 files as above)
|
||||
- banner.ts, complaint.ts, const.ts, coupon.ts, order.ts, product.ts, slots.ts, staff-user.ts, store.ts, user.ts, vendor-snippets.ts (entire files — legacy duplicates, never imported)
|
||||
|
||||
Removed in both packages (db_helper_sqlite + db_helper_postgres):
|
||||
- src/user-apis/tags.ts (entire file — imported by no one)
|
||||
- src/common-apis/utils.ts (entire file — imported by no one; formatDate/generateCode/calculateDiscount unreferenced)
|
||||
- src/db/types.ts (entire file — imported by no one)
|
||||
|
||||
Kept: helper_methods/upload-url.ts (alive — re-exported by index.ts, used by backend).
|
||||
|
||||
Command: rm <each file listed above>
|
||||
|
||||
[2026-09-02 20:38:02] COMPLETED deletion of 28 dead files (DEAD_CODE_REPORT.md §1).
|
||||
Verification:
|
||||
- grep for 'helper_methods|user-apis/tags|common-apis/utils|db/types' across both packages + apps/backend: only remaining match is the live import of helper_methods/upload-url.ts in both index.ts files.
|
||||
- tsc --noEmit on both packages reports pre-existing errors only (in untouched live files: src/db/seed.ts, src/user-apis/order.ts, src/user-apis/product.ts, src/admin-apis/complaint.ts, src/admin-apis/product.ts). No errors reference any deleted module.
|
||||
|
||||
[2026-09-02 20:45:45] DELETE dead exported methods per DEAD_CODE_REPORT.md §2a (8 symbols, both packages).
|
||||
|
||||
=== packages/db_helper_sqlite/src/admin-apis/product.ts ===
|
||||
- removed getAllUnits (lines 650-657):
|
||||
export async function getAllUnits(): Promise<AdminUnit[]> {
|
||||
const allUnits = await db.query.units.findMany({
|
||||
orderBy: units.shortNotation,
|
||||
})
|
||||
return allUnits.map(mapUnit)
|
||||
}
|
||||
- removed addProductToGroup (lines 1032-1034):
|
||||
export async function addProductToGroup(groupId: number, productId: number): Promise<void> {
|
||||
await db.insert(productGroupMembership).values({ groupId, productId })
|
||||
}
|
||||
- removed removeProductFromGroup (lines 1036-1042):
|
||||
export async function removeProductFromGroup(groupId: number, productId: number): Promise<void> {
|
||||
await db.delete(productGroupMembership)
|
||||
.where(and(
|
||||
eq(productGroupMembership.groupId, groupId),
|
||||
eq(productGroupMembership.productId, productId)
|
||||
))
|
||||
}
|
||||
|
||||
=== packages/db_helper_postgres/src/admin-apis/product.ts ===
|
||||
- identical removals (getAllUnits at 232-239, addProductToGroup at 496-498, removeProductFromGroup at 500-506)
|
||||
|
||||
=== packages/db_helper_sqlite/src/admin-apis/vendor-snippets.ts ===
|
||||
- removed getOrderItemsByOrderIds (lines 196-209):
|
||||
export async function getOrderItemsByOrderIds(orderIds: number[]) {
|
||||
return await db.query.orderItems.findMany({
|
||||
where: inArray(orderItems.orderId, orderIds),
|
||||
with: { sku: { with: { product: true, features: true } } },
|
||||
})
|
||||
}
|
||||
- removed getOrderStatusByOrderIds (lines 210-214):
|
||||
export async function getOrderStatusByOrderIds(orderIds: number[]) {
|
||||
return await db.query.orderStatus.findMany({
|
||||
where: inArray(orderStatus.orderId, orderIds),
|
||||
})
|
||||
}
|
||||
|
||||
=== packages/db_helper_postgres/src/admin-apis/vendor-snippets.ts ===
|
||||
- removed getOrderItemsByOrderIds (lines 188-200) — same signature; body joins product.unit instead of sku:
|
||||
export async function getOrderItemsByOrderIds(orderIds: number[]) {
|
||||
return await db.query.orderItems.findMany({
|
||||
where: inArray(orderItems.orderId, orderIds),
|
||||
with: { product: { with: { unit: true } } },
|
||||
})
|
||||
}
|
||||
- removed getOrderStatusByOrderIds (lines 201-205) — identical to sqlite
|
||||
|
||||
=== packages/db_helper_sqlite/src/admin-apis/staff-user.ts + packages/db_helper_postgres/src/admin-apis/staff-user.ts ===
|
||||
- removed updateUserSuspensionStatus (lines 101-109 in both) — duplicate of live upsertUserSuspension:
|
||||
export async function updateUserSuspensionStatus(userId: number, isSuspended: boolean): Promise<void> {
|
||||
await db
|
||||
.insert(userDetails)
|
||||
.values({ userId, isSuspended })
|
||||
.onConflictDoUpdate({
|
||||
target: userDetails.userId,
|
||||
set: { isSuspended },
|
||||
})
|
||||
}
|
||||
|
||||
=== packages/db_helper_sqlite/src/user-apis/user.ts + packages/db_helper_postgres/src/user-apis/user.ts ===
|
||||
- removed getUserWithCreds (lines 15-28 in both):
|
||||
export async function getUserWithCreds(userId: number) {
|
||||
const result = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.leftJoin(userCreds, eq(users.id, userCreds.userId))
|
||||
.where(eq(users.id, userId))
|
||||
.limit(1)
|
||||
if (result.length === 0) return null
|
||||
return { user: result[0].users, creds: result[0].user_creds }
|
||||
}
|
||||
|
||||
=== packages/db_helper_sqlite/src/user-apis/auth.ts + packages/db_helper_postgres/src/user-apis/auth.ts ===
|
||||
- removed createUserWithCreds (lines 145-168 in both):
|
||||
export async function createUserWithCreds(input: { name; email; mobile; hashedPassword }) {
|
||||
return db.transaction(async (tx) => { ...insert user, insert userCreds, return user... })
|
||||
}
|
||||
|
||||
=== packages/db_helper_sqlite/index.ts (export list cleanup) ===
|
||||
- removed line: getAllUnits,
|
||||
- removed lines: addProductToGroup,
|
||||
removeProductFromGroup,
|
||||
- removed lines: getOrderItemsByOrderIds,
|
||||
getOrderStatusByOrderIds,
|
||||
- removed line: updateUserSuspensionStatus,
|
||||
- removed line: getUserWithCreds as getUserWithCreds,
|
||||
- removed line: createUserWithCreds as createUserAuthWithCreds,
|
||||
|
||||
=== packages/db_helper_postgres/index.ts (export list cleanup) ===
|
||||
- same 8 export-line removals as sqlite index.ts
|
||||
|
||||
=== apps/backend/src/sqliteImporter.ts (re-export chain cleanup) ===
|
||||
- removed line: getAllUnits,
|
||||
- removed lines: addProductToGroup,
|
||||
removeProductFromGroup,
|
||||
- removed lines: getOrderItemsByOrderIds,
|
||||
getOrderStatusByOrderIds,
|
||||
- removed line: updateUserSuspensionStatus,
|
||||
- removed line: createUserAuthWithCreds,
|
||||
- removed line: getUserWithCreds,
|
||||
|
||||
Note: apps/backend/src/dbService.ts uses `export * from '@/src/sqliteImporter'` — no per-symbol change needed there.
|
||||
Any imports left unused by these deletions (e.g. productGroupMembership, inArray, userCreds) will be removed in a follow-up entry if tsc/grep confirms they are unused.
|
||||
|
||||
[2026-09-02 20:49:23] FOLLOW-UP: remove imports/orphans left unused by §2a deletions.
|
||||
|
||||
=== packages/db_helper_sqlite/src/admin-apis/product.ts ===
|
||||
- removed now-unused local type + mapper (only consumer was deleted getAllUnits):
|
||||
line 103: type UnitRow = InferSelectModel<typeof units>
|
||||
line 117: const mapUnit = (unit: UnitRow): AdminUnit => ({
|
||||
id: unit.id,
|
||||
shortNotation: unit.shortNotation,
|
||||
fullName: unit.fullName,
|
||||
})
|
||||
(postgres product.ts keeps mapUnit — still used there at lines 111, 142)
|
||||
|
||||
=== packages/db_helper_sqlite/src/user-apis/user.ts ===
|
||||
- import line 2: removed 'userCreds' from import { notifCreds, unloggedUserTokens, userCreds, userDetails, users } from '../db/schema'
|
||||
|
||||
=== packages/db_helper_postgres/src/user-apis/user.ts ===
|
||||
- same import fix as sqlite user.ts
|
||||
|
||||
[2026-09-02 20:51:30] COMPLETED §2a deletion (8 dead exported methods, both packages + re-export chains).
|
||||
Removed: getAllUnits, addProductToGroup, removeProductFromGroup, getOrderItemsByOrderIds, getOrderStatusByOrderIds, updateUserSuspensionStatus, getUserWithCreds, createUserWithCreds (+ orphaned mapUnit/UnitRow in sqlite product.ts, orphaned userCreds imports in both user.ts).
|
||||
Verification:
|
||||
- grep across packages + apps: zero live references remain (only commented lines in postgresImporter.ts).
|
||||
- tsc --noEmit: sqlite = 4 errors (same as pre-change baseline), postgres = 40 errors (all pre-existing drizzle inference mismatches in untouched files), backend = 17 errors (all pre-existing, in untouched files). No error references any deleted symbol.
|
||||
|
||||
[2026-09-02 20:54:32] DELETE §2b/§2c dead exports + PARITY RENAME + RESTORE two methods for future use.
|
||||
|
||||
--- DELETIONS, packages/db_helper_sqlite/src/admin-apis/product.ts ---
|
||||
- removed createSpecialDealsForSku (lines 1124-1145): inserts specialDeals rows keyed by skuId, returns mapped AdminSpecialDeal[]
|
||||
- removed updateSkuDeals (lines 1147-1241): diff-based sync of specialDeals for a skuId (add/remove/update validTill)
|
||||
- removed mergeSkus (lines 1243-1366, end of file): merges fromSkuId into toSkuId across orderItems, specialDeals, cartItems, couponApplicableProducts, JSON skuIds columns (deliverySlotInfo, homeBanners, coupons, reservedCoupons, vendorSnippets), keyValStore popularItems, then deletes skuFeatures + sku + orphaned product
|
||||
|
||||
--- DELETIONS, packages/db_helper_postgres/src/admin-apis/product.ts ---
|
||||
- removed toggleProductOutOfStock (lines 181-203): flips productInfo.isOutOfStock
|
||||
- removed createSpecialDealsForProduct (lines 677-698): postgres counterpart of createSpecialDealsForSku (keyed by productId)
|
||||
- removed updateProductDeals (lines 700-775): postgres counterpart of updateSkuDeals
|
||||
|
||||
--- DELETION, packages/db_helper_postgres/src/lib/automated-jobs.ts ---
|
||||
- removed toggleFlashDeliveryForItems (lines 10-16): bulk productInfo.isFlashAvailable update
|
||||
- import line 2: removed 'productInfo' from import { productInfo, keyValStore } from '../db/schema'
|
||||
- import line 3: removed 'inArray' from import { inArray, eq } from 'drizzle-orm'
|
||||
|
||||
--- EXPORT LIST, packages/db_helper_sqlite/index.ts ---
|
||||
- removed lines: createSpecialDealsForSku,
|
||||
updateSkuDeals,
|
||||
mergeSkus,
|
||||
|
||||
--- EXPORT LIST, packages/db_helper_postgres/index.ts ---
|
||||
- removed lines: createSpecialDealsForProduct,
|
||||
updateProductDeals,
|
||||
toggleProductOutOfStock,
|
||||
- removed line: toggleFlashDeliveryForItems,
|
||||
|
||||
--- EXPORT LIST, apps/backend/src/sqliteImporter.ts ---
|
||||
- removed lines: createSpecialDealsForSku,
|
||||
updateSkuDeals,
|
||||
mergeSkus,
|
||||
|
||||
--- PARITY RENAME, packages/db_helper_postgres/src/user-apis/product.ts ---
|
||||
- renamed getSuspendedProductIds -> getSuspendedSkuIds (backend calls getSuspendedSkuIds; sqlite exports that name).
|
||||
NOTE: implementation unchanged (queries productInfo.isSuspended) — postgres schema has no sku/marketStats model, so this is the closest postgres equivalent of the sqlite query on productMarketStats.skuId.
|
||||
- packages/db_helper_postgres/index.ts: export line getSuspendedProductIds, -> getSuspendedSkuIds,
|
||||
|
||||
--- RESTORE (user request: needed in future), both packages ---
|
||||
packages/db_helper_sqlite/src/user-apis/user.ts + packages/db_helper_postgres/src/user-apis/user.ts:
|
||||
- re-added getUserWithCreds:
|
||||
export async function getUserWithCreds(userId: number) {
|
||||
const result = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.leftJoin(userCreds, eq(users.id, userCreds.userId))
|
||||
.where(eq(users.id, userId))
|
||||
.limit(1)
|
||||
|
||||
if (result.length === 0) return null
|
||||
return {
|
||||
user: result[0].users,
|
||||
creds: result[0].user_creds,
|
||||
}
|
||||
}
|
||||
- re-added 'userCreds' to the schema import line in both files.
|
||||
|
||||
packages/db_helper_sqlite/src/user-apis/auth.ts + packages/db_helper_postgres/src/user-apis/auth.ts:
|
||||
- re-added createUserWithCreds:
|
||||
export async function createUserWithCreds(input: {
|
||||
name: string
|
||||
email: string
|
||||
mobile: string
|
||||
hashedPassword: string
|
||||
}) {
|
||||
return db.transaction(async (tx) => {
|
||||
const [user] = await tx.insert(users).values({
|
||||
name: input.name,
|
||||
email: input.email,
|
||||
mobile: input.mobile,
|
||||
}).returning()
|
||||
|
||||
await tx.insert(userCreds).values({
|
||||
userId: user.id,
|
||||
userPassword: input.hashedPassword,
|
||||
})
|
||||
|
||||
return user
|
||||
})
|
||||
}
|
||||
|
||||
packages/db_helper_sqlite/index.ts + packages/db_helper_postgres/index.ts:
|
||||
- re-added export lines: createUserWithCreds as createUserAuthWithCreds, (user auth block)
|
||||
getUserWithCreds as getUserWithCreds, (user profile block)
|
||||
|
||||
apps/backend/src/sqliteImporter.ts:
|
||||
- re-added re-export lines: createUserAuthWithCreds, and getUserWithCreds,
|
||||
|
||||
[2026-09-02 20:58:19] FOLLOW-UP: remove orphaned CreateSpecialDealInput (only consumers were the deleted deal functions; zero usages repo-wide).
|
||||
|
||||
=== packages/db_helper_sqlite/src/admin-apis/product.ts ===
|
||||
- removed (line ~1118):
|
||||
export interface CreateSpecialDealInput {
|
||||
quantity: number
|
||||
price: number
|
||||
validTill: string | Date
|
||||
}
|
||||
|
||||
=== packages/db_helper_postgres/src/admin-apis/product.ts ===
|
||||
- removed identical interface (line ~647)
|
||||
|
||||
[2026-09-02 20:59:28] COMPLETED §2b/§2c deletions + parity rename + restoration of createUserWithCreds/getUserWithCreds.
|
||||
Verification:
|
||||
- grep: zero references to createSpecialDealsForSku, updateSkuDeals, mergeSkus, createSpecialDealsForProduct, updateProductDeals, toggleProductOutOfStock, toggleFlashDeliveryForItems, getSuspendedProductIds (only commented postgresImporter.ts lines excluded from check).
|
||||
- Restored methods present in both packages' src + index.ts + backend sqliteImporter.ts.
|
||||
- Postgres now exports getSuspendedSkuIds (renamed from getSuspendedProductIds) matching sqlite/backend usage.
|
||||
- tsc --noEmit baselines unchanged: sqlite 4, postgres 40, backend 17 errors — all pre-existing, none reference touched symbols.
|
||||
|
||||
[2026-09-02 21:01:52] §3 EXPORT-ONLY DEAD cleanup: remove from index.ts (implementations kept), delete non-exported dead types.
|
||||
|
||||
=== packages/db_helper_sqlite/index.ts ===
|
||||
- removed line 10 block: export { staffRoleEnum, staffPermissionEnum } from './src/db/schema'
|
||||
(these enum values still flow to consumers via the existing `export * from './src/db/schema'` — the explicit line was redundant)
|
||||
- user product block: removed type lines type SkuSummary,
|
||||
type OffersPageData,
|
||||
type OffersPageProductData,
|
||||
- store helpers block: removed line type AvailabilityCacheData,
|
||||
- user order block: removed type lines type OrderWithFullData,
|
||||
type OrderWithCancellationData,
|
||||
- SKU Features block: removed lines cleanFeatureValue,
|
||||
splitQuantityFeature,
|
||||
type SkuFeatureLike,
|
||||
(kept composeUnitNotation, composeSkuName — used by backend)
|
||||
|
||||
=== packages/db_helper_postgres/index.ts ===
|
||||
- removed line 11 block: export { staffRoleEnum, staffPermissionEnum } from './src/db/schema';
|
||||
- user order block: removed type lines type OrderWithFullData,
|
||||
type OrderWithCancellationData,
|
||||
|
||||
=== apps/backend/src/sqliteImporter.ts ===
|
||||
- removed type lines type OrderWithFullData,
|
||||
type OrderWithCancellationData,
|
||||
- removed line getUserNotifCred,
|
||||
|
||||
Note: getNotifCred index export line in both packages: getNotifCred as getUserNotifCred, — removed (see below).
|
||||
|
||||
=== packages/db_helper_sqlite/index.ts + packages/db_helper_postgres/index.ts ===
|
||||
- user profile block: removed line getNotifCred as getUserNotifCred,
|
||||
(function getNotifCred kept in user.ts — called internally by upsertNotifCred)
|
||||
|
||||
=== packages/db_helper_sqlite/src/user-apis/order.ts ===
|
||||
- removed dead interface (lines 33-40):
|
||||
export interface PlaceOrderInput {
|
||||
userId: number
|
||||
selectedItems: OrderItemInput[]
|
||||
addressId: number
|
||||
paymentMethod: 'online' | 'cod'
|
||||
couponId?: number
|
||||
userNotes?: string
|
||||
isFlash?: boolean
|
||||
}
|
||||
- removed dead interface (lines 43-51):
|
||||
export interface OrderGroupData {
|
||||
slotId: number | null
|
||||
items: Array<{
|
||||
productId: number
|
||||
quantity: number
|
||||
slotId: number | null
|
||||
product: typeof productInfo.$inferSelect
|
||||
}>
|
||||
}
|
||||
|
||||
=== packages/db_helper_postgres/src/user-apis/order.ts ===
|
||||
- removed identical PlaceOrderInput (lines 29-36) and OrderGroupData (lines 39-47)
|
||||
|
||||
[2026-09-02 21:04:10] CORRECTION to previous entry: while editing packages/db_helper_sqlite/index.ts user product block I accidentally removed getAllSkusSummary, getOffersAndCombos and type ProductSummaryData (all three are USED by backend). Immediately restored them; final state of that block keeps: getAllProductsWithUnits, getAllSkusSummary, getOffersAndCombos, type ProductSummaryData. Only type SkuSummary, type OffersPageData, type OffersPageProductData were meant to be (and now are) removed.
|
||||
|
||||
[2026-09-02 21:07:40] CORRECTION: while editing the user order block of packages/db_helper_sqlite/index.ts I accidentally removed getOrdersByIdsWithFullData and getOrderByIdWithFullData (both USED by backend post-order-handler). Immediately restored. Final state: those two functions kept; only type OrderWithFullData and type OrderWithCancellationData removed from that block. Also fixed an earlier accidental newline removal after the store-helpers export close brace.
|
||||
|
||||
[2026-09-02 21:05:24] FOLLOW-UP: remove orphaned OrderItemInput from user-apis/order.ts in both packages (its only consumer, PlaceOrderInput, was deleted; definition-only now).
|
||||
|
||||
=== packages/db_helper_sqlite/src/user-apis/order.ts ===
|
||||
- removed (lines 27-31):
|
||||
export interface OrderItemInput {
|
||||
productId: number
|
||||
quantity: number
|
||||
slotId: number | null
|
||||
}
|
||||
|
||||
=== packages/db_helper_postgres/src/user-apis/order.ts ===
|
||||
- removed identical interface (lines 23-27)
|
||||
|
||||
[2026-09-02 21:06:16] COMPLETED §3 export-only-dead cleanup.
|
||||
Removed from index.ts (implementations kept in src):
|
||||
- both packages: redundant staffRoleEnum/staffPermissionEnum export block (still exported via `export * from './src/db/schema'`), type OrderWithFullData, type OrderWithCancellationData, getNotifCred as getUserNotifCred
|
||||
- sqlite only: type SkuSummary, type OffersPageData, type OffersPageProductData, type AvailabilityCacheData, cleanFeatureValue, splitQuantityFeature, type SkuFeatureLike (kept composeUnitNotation/composeSkuName)
|
||||
Removed from apps/backend/src/sqliteImporter.ts: type OrderWithFullData, type OrderWithCancellationData, getUserNotifCred
|
||||
Deleted dead types in user-apis/order.ts (both packages): PlaceOrderInput, OrderGroupData, OrderItemInput
|
||||
Verification:
|
||||
- Backend-used exports confirmed intact: getAllSkusSummary, getOffersAndCombos, ProductSummaryData, getOrdersByIdsWithFullData, getOrderByIdWithFullData, getAvailabilityForCache, getAllProductCombosForCache, getUserWithCreds, createUserAuthWithCreds, composeSkuName, composeUnitNotation
|
||||
- tsc --noEmit baselines unchanged: sqlite 4 (seed.ts 1, order.ts 2, product.ts 1), postgres 40, backend 17 — all pre-existing, none reference touched symbols.
|
||||
|
||||
[2026-09-02 21:21:13] §4: remove never-referenced schema tables — addressZones, addressAreas, userNotifications, productCategories (BOTH packages).
|
||||
NOTE: this only removes them from the drizzle schema (code). The actual DB tables/columns/FK constraints still exist in D1/Postgres — dropping them in the DB requires a migration, which the user will handle (per AGENTS.md). Both schema.ts files drift from the live DB until then; drizzle-kit push/generate must NOT be run by the agent.
|
||||
|
||||
=== packages/db_helper_sqlite/src/db/schema.ts ===
|
||||
- removed addressZones table (lines 106-110):
|
||||
export const addressZones = sqliteTable('address_zones', {
|
||||
id: integer().primaryKey({ autoIncrement: true }),
|
||||
zoneName: text('zone_name').notNull(),
|
||||
addedAt: timestampText('added_at').notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
})
|
||||
- removed addressAreas table (lines 112-117):
|
||||
export const addressAreas = sqliteTable('address_areas', {
|
||||
id: integer().primaryKey({ autoIncrement: true }),
|
||||
placeName: text('place_name').notNull(),
|
||||
zoneId: integer('zone_id').references(() => addressZones.id),
|
||||
createdAt: timestampText('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
})
|
||||
- addresses table: changed zoneId column to drop FK (column KEPT — shared UserAddress type and user-apis/address.ts mapUserAddress still expose zoneId):
|
||||
before: zoneId: integer('zone_id').references(() => addressZones.id),
|
||||
after: zoneId: integer('zone_id'),
|
||||
- removed productCategories table (lines 431-434):
|
||||
export const productCategories = sqliteTable('product_categories', {
|
||||
id: integer().primaryKey({ autoIncrement: true }),
|
||||
name: text().notNull(),
|
||||
description: text(),
|
||||
})
|
||||
- removed userNotifications table (lines 545-551):
|
||||
export const userNotifications = sqliteTable('user_notifications', {
|
||||
id: integer().primaryKey({ autoIncrement: true }),
|
||||
title: text('title').notNull(),
|
||||
imageUrl: text('image_url'),
|
||||
createdAt: timestampText('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
body: text('body').notNull(),
|
||||
applicableUsers: jsonText<number[] | null>('applicable_users'),
|
||||
})
|
||||
- addressesRelations: removed line zone: one(addressZones, { fields: [addresses.zoneId], references: [addressZones.id] }),
|
||||
- removed productCategoriesRelations (line 686): export const productCategoriesRelations = relations(productCategories, ({}) => ({}))
|
||||
- removed userNotificationsRelations (lines 720-722):
|
||||
export const userNotificationsRelations = relations(userNotifications, ({}) => ({
|
||||
// No relations needed for now
|
||||
}))
|
||||
- removed addressZonesRelations (lines 749-752):
|
||||
export const addressZonesRelations = relations(addressZones, ({ many }) => ({
|
||||
addresses: many(addresses),
|
||||
areas: many(addressAreas),
|
||||
}))
|
||||
- removed addressAreasRelations (lines 754-756):
|
||||
export const addressAreasRelations = relations(addressAreas, ({ one }) => ({
|
||||
zone: one(addressZones, { fields: [addressAreas.zoneId], references: [addressZones.id] }),
|
||||
}))
|
||||
|
||||
=== packages/db_helper_postgres/src/db/schema.ts === (same removals, postgres style)
|
||||
- removed addressZones table (lines 58-62, mf.table('address_zones', ...))
|
||||
- removed addressAreas table (lines 64-69, mf.table('address_areas', ...))
|
||||
- addresses.zoneId (line 54): dropped .references(() => addressZones.id) — column kept
|
||||
- removed productCategories table (lines 318-322, mf.table('product_categories', ...))
|
||||
- removed userNotifications table (lines 434-440, mf.table('user_notifications', ...))
|
||||
- addressesRelations: removed zone relation line (line 496)
|
||||
- removed productCategoriesRelations (line 573)
|
||||
- removed userNotificationsRelations (lines 607-609)
|
||||
- removed addressZonesRelations (lines 636-639) and addressAreasRelations (lines 641-643)
|
||||
|
||||
[2026-09-02 21:23:14] COMPLETED §4 schema table removal.
|
||||
- Removed from both packages' src/db/schema.ts: addressZones, addressAreas, productCategories, userNotifications tables + productCategoriesRelations, userNotificationsRelations, addressZonesRelations, addressAreasRelations.
|
||||
- addresses.zoneId column KEPT (FK reference + addressesRelations.zone relation removed) because shared UserAddress type and user-apis/address.ts still expose zoneId.
|
||||
- Verified: zero remaining references to the 4 tables anywhere; jsonText/jsonb/text imports still used by other tables; tsc baselines unchanged (sqlite 4, postgres 40, backend 17 — all pre-existing).
|
||||
- REMINDER: DB tables still exist in D1/Postgres until the user runs a drop migration (agent must not run drizzle migrations).
|
||||
|
||||
[2026-09-02 21:24:48] §6: DELETE dead backend files (10 files) + clean commented references to them.
|
||||
|
||||
DELETED (all verified: zero live imports — only commented mentions remain):
|
||||
- apps/backend/src/postgresImporter.ts (entirely commented-out swap scaffold; restore from git when swapping to postgres)
|
||||
- apps/backend/src/lib/signed-url-cache-old.ts
|
||||
- apps/backend/src/lib/signed-url-cache.ts
|
||||
- apps/backend/src/lib/disk-persisted-set.ts (body fully commented out)
|
||||
- apps/backend/src/lib/catch-async.ts
|
||||
- apps/backend/src/lib/axios.ts (phonepeAxios never imported)
|
||||
- apps/backend/src/lib/delete-image.ts (deleteS3Image never imported)
|
||||
- apps/backend/src/lib/event-queue.ts (enqueue never imported; queue-consumer.ts is the live path)
|
||||
- apps/backend/src/lib/notif-service.ts
|
||||
- apps/backend/src/types/db.types.ts
|
||||
|
||||
COMMENT CLEANUP (files are .ts → logged here):
|
||||
=== apps/backend/index.ts ===
|
||||
- removed line 5: // import signedUrlCache from '@/src/lib/signed-url-cache';
|
||||
- removed line 12: // signedUrlCache.loadFromDisk(); // Disabled for Workers compatibility
|
||||
Left untouched: s3-client.ts:141 prose comment (mentions the concept, not an import), dbService.ts prose header.
|
||||
KEPT (verified alive): jobs/cache-creator.ts (Durable Object), lib/queue-consumer.ts, lib/flash-delivery-cron.ts, lib/seed.ts, lib/init.ts, lib/worker-init.ts, src/apis/**, src/test-controller.ts, types/hono.d.ts (ambient).
|
||||
|
||||
[2026-09-02 21:25:58] COMPLETED §6 backend dead-file deletion (10 files) + index.ts comment cleanup.
|
||||
Verification: backend tsc --noEmit now reports 11 errors (was 17) — the 6 that vanished were signed-url-cache-old.ts's own pre-existing errors; remaining 11 are the same pre-existing set in untouched files (app.ts, lib/init.ts, s3-client.ts, main-router.ts, trpc-index.ts, sqlite order.ts/product.ts). No new errors.
|
||||
Note: apps/backend/src/lib/s3-client.ts.txt (a .txt copy of s3-client) still exists — left alone, flagging as possible future cleanup.
|
||||
|
||||
[2026-09-02 21:31:17] Remove 4 dead-code items missed by our report but caught by cmd_v4_dead.md.
|
||||
|
||||
DELETED (3 files, verified zero live imports — only commented references):
|
||||
- apps/backend/src/lib/redis-client.ts (stubbed: createClient = (args:any) => {}; only '// import redisClient' comments remain elsewhere)
|
||||
- apps/backend/src/lib/redisKeyGetters.ts (only commented imports in trpc/apis/admin-apis/apis/slots.ts:7,613,694)
|
||||
- apps/backend/src/middleware/auth.ts (verifyToken/requireRole never imported; app uses middleware/auth.middleware.ts + staff-auth.ts)
|
||||
|
||||
EDITED:
|
||||
=== packages/shared/index.ts ===
|
||||
- removed line 10 (dead type, definition-only):
|
||||
export type CacheFilename = typeof CACHE_FILENAMES[keyof typeof CACHE_FILENAMES]
|
||||
(CACHE_FILENAMES const itself is heavily used by backend cloud_cache.ts and web-ui/user-ui hooks — kept)
|
||||
|
||||
[2026-09-02 21:32:23] COMPLETED removal of 4 missed dead-code items.
|
||||
- Deleted: apps/backend/src/lib/redis-client.ts, apps/backend/src/lib/redisKeyGetters.ts, apps/backend/src/middleware/auth.ts (middleware/ dir still has auth.middleware.ts + staff-auth.ts).
|
||||
- Removed CacheFilename type from packages/shared/index.ts (CACHE_FILENAMES const kept — used by cloud_cache.ts + web-ui/user-ui hooks).
|
||||
- Verified: zero remaining references (excluding comments); backend tsc --noEmit = 11 errors, same pre-existing baseline.
|
||||
|
|
@ -7,9 +7,6 @@ export { db } from './src/db/db_index';
|
|||
// Re-export schema
|
||||
export * from './src/db/schema';
|
||||
|
||||
// Export enum types for type safety
|
||||
export { staffRoleEnum, staffPermissionEnum } from './src/db/schema';
|
||||
|
||||
// Admin API helpers - explicitly namespaced exports to avoid duplicates
|
||||
export {
|
||||
// Banner
|
||||
|
|
@ -80,13 +77,9 @@ export {
|
|||
checkProductExistsByName,
|
||||
checkUnitExists,
|
||||
getProductImagesById,
|
||||
createSpecialDealsForProduct,
|
||||
updateProductDeals,
|
||||
replaceProductTags,
|
||||
toggleProductOutOfStock,
|
||||
updateSlotProducts,
|
||||
getSlotsProductIds,
|
||||
getAllUnits,
|
||||
getAllProductTags,
|
||||
getAllProductTagInfos,
|
||||
getProductTagInfoById,
|
||||
|
|
@ -101,8 +94,6 @@ export {
|
|||
createProductGroup,
|
||||
updateProductGroup,
|
||||
deleteProductGroup,
|
||||
addProductToGroup,
|
||||
removeProductFromGroup,
|
||||
updateProductPrices,
|
||||
} from './src/admin-apis/product';
|
||||
|
||||
|
|
@ -128,7 +119,6 @@ export {
|
|||
getAllStaff,
|
||||
getAllUsers,
|
||||
getUserWithDetails,
|
||||
updateUserSuspensionStatus,
|
||||
checkStaffUserExists,
|
||||
checkStaffRoleExists,
|
||||
createStaffUser,
|
||||
|
|
@ -179,8 +169,6 @@ export {
|
|||
getProductsByIds,
|
||||
getVendorSlotById,
|
||||
getVendorOrdersBySlotId,
|
||||
getOrderItemsByOrderIds,
|
||||
getOrderStatusByOrderIds,
|
||||
updateVendorOrderItemPackaging,
|
||||
getVendorOrders,
|
||||
} from './src/admin-apis/vendor-snippets';
|
||||
|
|
@ -271,7 +259,6 @@ export {
|
|||
getUserById as getUserProfileById,
|
||||
getUserDetailByUserId as getUserProfileDetailById,
|
||||
getUserWithCreds as getUserWithCreds,
|
||||
getNotifCred as getUserNotifCred,
|
||||
upsertNotifCred as upsertUserNotifCred,
|
||||
deleteUnloggedToken as deleteUserUnloggedToken,
|
||||
getUnloggedToken as getUserUnloggedToken,
|
||||
|
|
@ -299,8 +286,6 @@ export {
|
|||
// Post-order handler helpers
|
||||
getOrdersByIdsWithFullData,
|
||||
getOrderByIdWithFullData,
|
||||
type OrderWithFullData,
|
||||
type OrderWithCancellationData,
|
||||
} from './src/user-apis/order';
|
||||
|
||||
// Store Helpers (for cache initialization)
|
||||
|
|
@ -335,7 +320,6 @@ export {
|
|||
|
||||
// Automated Jobs Helpers
|
||||
export {
|
||||
toggleFlashDeliveryForItems,
|
||||
toggleKeyVal,
|
||||
getAllKeyValStore,
|
||||
} from './src/lib/automated-jobs';
|
||||
|
|
@ -347,7 +331,7 @@ export {
|
|||
|
||||
// Common API Helpers
|
||||
export {
|
||||
getSuspendedProductIds,
|
||||
getSuspendedSkuIds,
|
||||
getNextDeliveryDateWithCapacity,
|
||||
} from './src/user-apis/product';
|
||||
|
||||
|
|
|
|||
|
|
@ -178,30 +178,6 @@ export async function updateProduct(id: number, updates: ProductInfoUpdate): Pro
|
|||
return mapProduct(product)
|
||||
}
|
||||
|
||||
export async function toggleProductOutOfStock(id: number): Promise<AdminProduct | null> {
|
||||
const product = await db.query.productInfo.findFirst({
|
||||
where: eq(productInfo.id, id),
|
||||
})
|
||||
|
||||
if (!product) {
|
||||
return null
|
||||
}
|
||||
|
||||
const [updatedProduct] = await db
|
||||
.update(productInfo)
|
||||
.set({
|
||||
isOutOfStock: !product.isOutOfStock,
|
||||
})
|
||||
.where(eq(productInfo.id, id))
|
||||
.returning()
|
||||
|
||||
if (!updatedProduct) {
|
||||
return null
|
||||
}
|
||||
|
||||
return mapProduct(updatedProduct)
|
||||
}
|
||||
|
||||
export async function updateSlotProducts(slotId: string, productIds: string[]): Promise<AdminUpdateSlotProductsResult> {
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, parseInt(slotId)),
|
||||
|
|
@ -229,14 +205,6 @@ export async function updateSlotProducts(slotId: string, productIds: string[]):
|
|||
}
|
||||
}
|
||||
|
||||
export async function getAllUnits(): Promise<AdminUnit[]> {
|
||||
const allUnits = await db.query.units.findMany({
|
||||
orderBy: units.shortNotation,
|
||||
})
|
||||
|
||||
return allUnits.map(mapUnit)
|
||||
}
|
||||
|
||||
export async function getAllProductTags(): Promise<AdminProductTagWithProducts[]> {
|
||||
const tags = await db.query.productTagInfo.findMany({
|
||||
with: {
|
||||
|
|
@ -493,18 +461,6 @@ export async function deleteProductGroup(id: number): Promise<AdminProductGroupI
|
|||
}
|
||||
}
|
||||
|
||||
export async function addProductToGroup(groupId: number, productId: number): Promise<void> {
|
||||
await db.insert(productGroupMembership).values({ groupId, productId })
|
||||
}
|
||||
|
||||
export async function removeProductFromGroup(groupId: number, productId: number): Promise<void> {
|
||||
await db.delete(productGroupMembership)
|
||||
.where(and(
|
||||
eq(productGroupMembership.groupId, groupId),
|
||||
eq(productGroupMembership.productId, productId)
|
||||
))
|
||||
}
|
||||
|
||||
export async function updateProductPrices(updates: Array<{
|
||||
productId: number
|
||||
price?: number
|
||||
|
|
@ -688,101 +644,6 @@ export async function getProductImagesById(productId: number): Promise<string[]
|
|||
return getStringArray(product.images) || []
|
||||
}
|
||||
|
||||
export interface CreateSpecialDealInput {
|
||||
quantity: number
|
||||
price: number
|
||||
validTill: string | Date
|
||||
}
|
||||
|
||||
export async function createSpecialDealsForProduct(
|
||||
productId: number,
|
||||
deals: CreateSpecialDealInput[]
|
||||
): Promise<AdminSpecialDeal[]> {
|
||||
if (deals.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const dealInserts = deals.map((deal) => ({
|
||||
productId,
|
||||
quantity: deal.quantity.toString(),
|
||||
price: deal.price.toString(),
|
||||
validTill: new Date(deal.validTill),
|
||||
}))
|
||||
|
||||
const createdDeals = await db
|
||||
.insert(specialDeals)
|
||||
.values(dealInserts)
|
||||
.returning()
|
||||
|
||||
return createdDeals.map(mapSpecialDeal)
|
||||
}
|
||||
|
||||
export async function updateProductDeals(
|
||||
productId: number,
|
||||
deals: CreateSpecialDealInput[]
|
||||
): Promise<void> {
|
||||
if (deals.length === 0) {
|
||||
await db.delete(specialDeals).where(eq(specialDeals.productId, productId))
|
||||
return
|
||||
}
|
||||
|
||||
const existingDeals = await db.query.specialDeals.findMany({
|
||||
where: eq(specialDeals.productId, productId),
|
||||
})
|
||||
|
||||
const existingDealsMap = new Map(
|
||||
existingDeals.map((deal) => [`${deal.quantity}-${deal.price}`, deal])
|
||||
)
|
||||
const newDealsMap = new Map(
|
||||
deals.map((deal) => [`${deal.quantity}-${deal.price}`, deal])
|
||||
)
|
||||
|
||||
const dealsToAdd = deals.filter((deal) => {
|
||||
const key = `${deal.quantity}-${deal.price}`
|
||||
return !existingDealsMap.has(key)
|
||||
})
|
||||
|
||||
const dealsToRemove = existingDeals.filter((deal) => {
|
||||
const key = `${deal.quantity}-${deal.price}`
|
||||
return !newDealsMap.has(key)
|
||||
})
|
||||
|
||||
const dealsToUpdate = deals.filter((deal) => {
|
||||
const key = `${deal.quantity}-${deal.price}`
|
||||
const existing = existingDealsMap.get(key)
|
||||
const nextValidTill = deal.validTill instanceof Date
|
||||
? deal.validTill.toISOString().split('T')[0]
|
||||
: String(deal.validTill)
|
||||
return existing && existing.validTill.toISOString().split('T')[0] !== nextValidTill
|
||||
})
|
||||
|
||||
if (dealsToRemove.length > 0) {
|
||||
await db.delete(specialDeals).where(
|
||||
inArray(specialDeals.id, dealsToRemove.map((deal) => deal.id))
|
||||
)
|
||||
}
|
||||
|
||||
if (dealsToAdd.length > 0) {
|
||||
const dealInserts = dealsToAdd.map((deal) => ({
|
||||
productId,
|
||||
quantity: deal.quantity.toString(),
|
||||
price: deal.price.toString(),
|
||||
validTill: new Date(deal.validTill),
|
||||
}))
|
||||
await db.insert(specialDeals).values(dealInserts)
|
||||
}
|
||||
|
||||
for (const deal of dealsToUpdate) {
|
||||
const key = `${deal.quantity}-${deal.price}`
|
||||
const existingDeal = existingDealsMap.get(key)
|
||||
if (existingDeal) {
|
||||
await db.update(specialDeals)
|
||||
.set({ validTill: new Date(deal.validTill) })
|
||||
.where(eq(specialDeals.id, existingDeal.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function replaceProductTags(productId: number, tagIds: number[]): Promise<void> {
|
||||
await db.delete(productTags).where(eq(productTags.productId, productId))
|
||||
|
||||
|
|
|
|||
|
|
@ -98,16 +98,6 @@ export async function getUserWithDetails(userId: number): Promise<any | null> {
|
|||
return user || null;
|
||||
}
|
||||
|
||||
export async function updateUserSuspensionStatus(userId: number, isSuspended: boolean): Promise<void> {
|
||||
await db
|
||||
.insert(userDetails)
|
||||
.values({ userId, isSuspended })
|
||||
.onConflictDoUpdate({
|
||||
target: userDetails.userId,
|
||||
set: { isSuspended },
|
||||
});
|
||||
}
|
||||
|
||||
export async function checkStaffUserExists(name: string): Promise<boolean> {
|
||||
const existingUser = await db.query.staffUsers.findFirst({
|
||||
where: eq(staffUsers.name, name),
|
||||
|
|
|
|||
|
|
@ -185,25 +185,6 @@ export async function getVendorOrders() {
|
|||
})
|
||||
}
|
||||
|
||||
export async function getOrderItemsByOrderIds(orderIds: number[]) {
|
||||
return await db.query.orderItems.findMany({
|
||||
where: inArray(orderItems.orderId, orderIds),
|
||||
with: {
|
||||
product: {
|
||||
with: {
|
||||
unit: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function getOrderStatusByOrderIds(orderIds: number[]) {
|
||||
return await db.query.orderStatus.findMany({
|
||||
where: inArray(orderStatus.orderId, orderIds),
|
||||
})
|
||||
}
|
||||
|
||||
export async function updateVendorOrderItemPackaging(
|
||||
orderItemId: number,
|
||||
isPackaged: boolean
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
// Common utility functions that can be used by both admin and user APIs
|
||||
|
||||
export function formatDate(date: Date): string {
|
||||
return date.toISOString();
|
||||
}
|
||||
|
||||
export function generateCode(prefix: string, length: number = 6): string {
|
||||
const timestamp = Date.now().toString().slice(-length);
|
||||
const random = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||
return `${prefix}${timestamp}${random}`;
|
||||
}
|
||||
|
||||
export function calculateDiscount(amount: number, percent: number, maxDiscount?: number): number {
|
||||
let discount = (amount * percent) / 100;
|
||||
if (maxDiscount && discount > maxDiscount) {
|
||||
discount = maxDiscount;
|
||||
}
|
||||
return discount;
|
||||
}
|
||||
|
|
@ -51,20 +51,7 @@ export const addresses = mf.table('addresses', {
|
|||
googleMapsUrl: varchar('google_maps_url', { length: 500 }),
|
||||
adminLatitude: real('admin_latitude'),
|
||||
adminLongitude: real('admin_longitude'),
|
||||
zoneId: integer('zone_id').references(() => addressZones.id),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const addressZones = mf.table('address_zones', {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
zoneName: varchar('zone_name', { length: 255 }).notNull(),
|
||||
addedAt: timestamp('added_at').notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const addressAreas = mf.table('address_areas', {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
placeName: varchar('place_name', { length: 255 }).notNull(),
|
||||
zoneId: integer('zone_id').references(() => addressZones.id),
|
||||
zoneId: integer('zone_id'),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
});
|
||||
|
||||
|
|
@ -315,12 +302,6 @@ export const notifications = mf.table('notifications', {
|
|||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const productCategories = mf.table('product_categories', {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
name: varchar({ length: 255 }).notNull(),
|
||||
description: varchar({ length: 500 }),
|
||||
});
|
||||
|
||||
export const cartItems = mf.table('cart_items', {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
userId: integer('user_id').notNull().references(() => users.id),
|
||||
|
|
@ -431,15 +412,6 @@ export const unloggedUserTokens = mf.table('unlogged_user_tokens', {
|
|||
lastVerified: timestamp('last_verified'),
|
||||
});
|
||||
|
||||
export const userNotifications = mf.table('user_notifications', {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
title: varchar('title', { length: 255 }).notNull(),
|
||||
imageUrl: varchar('image_url', { length: 500 }),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
body: text('body').notNull(),
|
||||
applicableUsers: jsonb('applicable_users'),
|
||||
});
|
||||
|
||||
export const staffRoles = mf.table('staff_roles', {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
roleName: staffRoleEnum('role_name').notNull(),
|
||||
|
|
@ -493,7 +465,6 @@ export const staffUsersRelations = relations(staffUsers, ({ one, many }) => ({
|
|||
export const addressesRelations = relations(addresses, ({ one, many }) => ({
|
||||
user: one(users, { fields: [addresses.userId], references: [users.id] }),
|
||||
orders: many(orders),
|
||||
zone: one(addressZones, { fields: [addresses.zoneId], references: [addressZones.id] }),
|
||||
}));
|
||||
|
||||
export const unitsRelations = relations(units, ({ many }) => ({
|
||||
|
|
@ -570,8 +541,6 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
|
|||
user: one(users, { fields: [notifications.userId], references: [users.id] }),
|
||||
}));
|
||||
|
||||
export const productCategoriesRelations = relations(productCategories, ({}) => ({}));
|
||||
|
||||
export const cartItemsRelations = relations(cartItems, ({ one }) => ({
|
||||
user: one(users, { fields: [cartItems.userId], references: [users.id] }),
|
||||
product: one(productInfo, { fields: [cartItems.productId], references: [productInfo.id] }),
|
||||
|
|
@ -604,10 +573,6 @@ export const notifCredsRelations = relations(notifCreds, ({ one }) => ({
|
|||
user: one(users, { fields: [notifCreds.userId], references: [users.id] }),
|
||||
}));
|
||||
|
||||
export const userNotificationsRelations = relations(userNotifications, ({}) => ({
|
||||
// No relations needed for now
|
||||
}));
|
||||
|
||||
export const storeInfoRelations = relations(storeInfo, ({ one, many }) => ({
|
||||
owner: one(staffUsers, { fields: [storeInfo.owner], references: [staffUsers.id] }),
|
||||
products: many(productInfo),
|
||||
|
|
@ -633,15 +598,6 @@ export const productReviewsRelations = relations(productReviews, ({ one }) => ({
|
|||
product: one(productInfo, { fields: [productReviews.productId], references: [productInfo.id] }),
|
||||
}));
|
||||
|
||||
export const addressZonesRelations = relations(addressZones, ({ many }) => ({
|
||||
addresses: many(addresses),
|
||||
areas: many(addressAreas),
|
||||
}));
|
||||
|
||||
export const addressAreasRelations = relations(addressAreas, ({ one }) => ({
|
||||
zone: one(addressZones, { fields: [addressAreas.zoneId], references: [addressZones.id] }),
|
||||
}));
|
||||
|
||||
export const productGroupInfoRelations = relations(productGroupInfo, ({ many }) => ({
|
||||
memberships: many(productGroupMembership),
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
import type { InferSelectModel } from "drizzle-orm";
|
||||
import type {
|
||||
users,
|
||||
addresses,
|
||||
units,
|
||||
productInfo,
|
||||
deliverySlotInfo,
|
||||
specialDeals,
|
||||
orders,
|
||||
orderItems,
|
||||
payments,
|
||||
notifications,
|
||||
productCategories,
|
||||
cartItems,
|
||||
coupons,
|
||||
} from "@/src/db/schema";
|
||||
|
||||
export type User = InferSelectModel<typeof users>;
|
||||
export type Address = InferSelectModel<typeof addresses>;
|
||||
export type Unit = InferSelectModel<typeof units>;
|
||||
export type ProductInfo = InferSelectModel<typeof productInfo>;
|
||||
export type DeliverySlotInfo = InferSelectModel<typeof deliverySlotInfo>;
|
||||
export type SpecialDeal = InferSelectModel<typeof specialDeals>;
|
||||
export type Order = InferSelectModel<typeof orders>;
|
||||
export type OrderItem = InferSelectModel<typeof orderItems>;
|
||||
export type Payment = InferSelectModel<typeof payments>;
|
||||
export type Notification = InferSelectModel<typeof notifications>;
|
||||
export type ProductCategory = InferSelectModel<typeof productCategories>;
|
||||
export type CartItem = InferSelectModel<typeof cartItems>;
|
||||
export type Coupon = InferSelectModel<typeof coupons>;
|
||||
|
||||
// Combined types
|
||||
export type ProductWithUnit = ProductInfo & {
|
||||
unit: Unit;
|
||||
};
|
||||
|
||||
export type OrderWithItems = Order & {
|
||||
items: (OrderItem & { product: ProductInfo })[];
|
||||
address: Address;
|
||||
slot: DeliverySlotInfo;
|
||||
};
|
||||
|
||||
export type CartItemWithProduct = CartItem & {
|
||||
product: ProductInfo;
|
||||
};
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { homeBanners } from '../db/schema';
|
||||
import { desc, eq } from 'drizzle-orm';
|
||||
|
||||
export interface Banner {
|
||||
id: number;
|
||||
name: string;
|
||||
imageUrl: string;
|
||||
description: string | null;
|
||||
productIds: number[] | null;
|
||||
redirectUrl: string | null;
|
||||
serialNum: number | null;
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
lastUpdated: Date;
|
||||
}
|
||||
|
||||
export async function getBanners(): Promise<Banner[]> {
|
||||
const banners = await db.query.homeBanners.findMany({
|
||||
orderBy: desc(homeBanners.createdAt),
|
||||
});
|
||||
|
||||
return banners.map((banner) => ({
|
||||
id: banner.id,
|
||||
name: banner.name,
|
||||
imageUrl: banner.imageUrl,
|
||||
description: banner.description,
|
||||
productIds: banner.productIds || [],
|
||||
redirectUrl: banner.redirectUrl,
|
||||
serialNum: banner.serialNum,
|
||||
isActive: banner.isActive,
|
||||
createdAt: banner.createdAt,
|
||||
lastUpdated: banner.lastUpdated,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getBannerById(id: number): Promise<Banner | null> {
|
||||
const banner = await db.query.homeBanners.findFirst({
|
||||
where: eq(homeBanners.id, id),
|
||||
});
|
||||
|
||||
if (!banner) return null;
|
||||
|
||||
return {
|
||||
id: banner.id,
|
||||
name: banner.name,
|
||||
imageUrl: banner.imageUrl,
|
||||
description: banner.description,
|
||||
productIds: banner.productIds || [],
|
||||
redirectUrl: banner.redirectUrl,
|
||||
serialNum: banner.serialNum,
|
||||
isActive: banner.isActive,
|
||||
createdAt: banner.createdAt,
|
||||
lastUpdated: banner.lastUpdated,
|
||||
};
|
||||
}
|
||||
|
||||
export type CreateBannerInput = Omit<Banner, 'id' | 'createdAt' | 'lastUpdated'>;
|
||||
|
||||
export async function createBanner(input: CreateBannerInput): Promise<Banner> {
|
||||
const [banner] = await db.insert(homeBanners).values({
|
||||
name: input.name,
|
||||
imageUrl: input.imageUrl,
|
||||
description: input.description,
|
||||
productIds: input.productIds,
|
||||
redirectUrl: input.redirectUrl,
|
||||
serialNum: input.serialNum,
|
||||
isActive: input.isActive,
|
||||
}).returning();
|
||||
|
||||
return {
|
||||
id: banner.id,
|
||||
name: banner.name,
|
||||
imageUrl: banner.imageUrl,
|
||||
description: banner.description,
|
||||
productIds: banner.productIds || [],
|
||||
redirectUrl: banner.redirectUrl,
|
||||
serialNum: banner.serialNum,
|
||||
isActive: banner.isActive,
|
||||
createdAt: banner.createdAt,
|
||||
lastUpdated: banner.lastUpdated,
|
||||
};
|
||||
}
|
||||
|
||||
export type UpdateBannerInput = Partial<Omit<Banner, 'id' | 'createdAt'>>;
|
||||
|
||||
export async function updateBanner(id: number, input: UpdateBannerInput): Promise<Banner> {
|
||||
const [banner] = await db.update(homeBanners)
|
||||
.set({
|
||||
...input,
|
||||
lastUpdated: new Date(),
|
||||
})
|
||||
.where(eq(homeBanners.id, id))
|
||||
.returning();
|
||||
|
||||
return {
|
||||
id: banner.id,
|
||||
name: banner.name,
|
||||
imageUrl: banner.imageUrl,
|
||||
description: banner.description,
|
||||
productIds: banner.productIds || [],
|
||||
redirectUrl: banner.redirectUrl,
|
||||
serialNum: banner.serialNum,
|
||||
isActive: banner.isActive,
|
||||
createdAt: banner.createdAt,
|
||||
lastUpdated: banner.lastUpdated,
|
||||
};
|
||||
}
|
||||
|
||||
export async function deleteBanner(id: number): Promise<void> {
|
||||
await db.delete(homeBanners).where(eq(homeBanners.id, id));
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { complaints, users } from '../db/schema';
|
||||
import { eq, desc, lt } from 'drizzle-orm';
|
||||
|
||||
export interface Complaint {
|
||||
id: number;
|
||||
complaintBody: string;
|
||||
userId: number;
|
||||
orderId: number | null;
|
||||
isResolved: boolean;
|
||||
response: string | null;
|
||||
createdAt: Date;
|
||||
images: string[] | null;
|
||||
}
|
||||
|
||||
export interface ComplaintWithUser extends Complaint {
|
||||
userName: string | null;
|
||||
userMobile: string | null;
|
||||
}
|
||||
|
||||
export async function getComplaints(
|
||||
cursor?: number,
|
||||
limit: number = 20
|
||||
): Promise<{ complaints: ComplaintWithUser[]; hasMore: boolean }> {
|
||||
let whereCondition = cursor ? lt(complaints.id, cursor) : undefined;
|
||||
|
||||
const complaintsData = await db
|
||||
.select({
|
||||
id: complaints.id,
|
||||
complaintBody: complaints.complaintBody,
|
||||
userId: complaints.userId,
|
||||
orderId: complaints.orderId,
|
||||
isResolved: complaints.isResolved,
|
||||
response: complaints.response,
|
||||
createdAt: complaints.createdAt,
|
||||
images: complaints.images,
|
||||
userName: users.name,
|
||||
userMobile: users.mobile,
|
||||
})
|
||||
.from(complaints)
|
||||
.leftJoin(users, eq(complaints.userId, users.id))
|
||||
.where(whereCondition)
|
||||
.orderBy(desc(complaints.id))
|
||||
.limit(limit + 1);
|
||||
|
||||
const hasMore = complaintsData.length > limit;
|
||||
const complaintsToReturn = hasMore ? complaintsData.slice(0, limit) : complaintsData;
|
||||
|
||||
return {
|
||||
complaints: complaintsToReturn.map((c) => ({
|
||||
id: c.id,
|
||||
complaintBody: c.complaintBody,
|
||||
userId: c.userId,
|
||||
orderId: c.orderId,
|
||||
isResolved: c.isResolved,
|
||||
response: c.response,
|
||||
createdAt: c.createdAt,
|
||||
images: c.images,
|
||||
userName: c.userName,
|
||||
userMobile: c.userMobile,
|
||||
})),
|
||||
hasMore,
|
||||
};
|
||||
}
|
||||
|
||||
export async function resolveComplaint(
|
||||
id: number,
|
||||
response?: string
|
||||
): Promise<void> {
|
||||
await db
|
||||
.update(complaints)
|
||||
.set({ isResolved: true, response })
|
||||
.where(eq(complaints.id, id));
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { keyValStore } from '../db/schema';
|
||||
|
||||
export interface Constant {
|
||||
key: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
export async function getAllConstants(): Promise<Constant[]> {
|
||||
const constants = await db.select().from(keyValStore);
|
||||
|
||||
return constants.map(c => ({
|
||||
key: c.key,
|
||||
value: c.value,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function upsertConstants(constants: Constant[]): Promise<void> {
|
||||
await db.transaction(async (tx) => {
|
||||
for (const { key, value } of constants) {
|
||||
await tx.insert(keyValStore)
|
||||
.values({ key, value })
|
||||
.onConflictDoUpdate({
|
||||
target: keyValStore.key,
|
||||
set: { value },
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1,633 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { coupons, reservedCoupons, users } from '../db/schema';
|
||||
import { eq, and, like, or, inArray, lt, desc } from 'drizzle-orm';
|
||||
|
||||
export interface Coupon {
|
||||
id: number;
|
||||
couponCode: string;
|
||||
isUserBased: boolean;
|
||||
discountPercent: string | null;
|
||||
flatDiscount: string | null;
|
||||
minOrder: string | null;
|
||||
productIds: number[] | null;
|
||||
maxValue: string | null;
|
||||
isApplyForAll: boolean;
|
||||
validTill: Date | null;
|
||||
maxLimitForUser: number | null;
|
||||
exclusiveApply: boolean;
|
||||
isInvalidated: boolean;
|
||||
createdAt: Date;
|
||||
createdBy: number;
|
||||
}
|
||||
|
||||
export async function getAllCoupons(
|
||||
cursor?: number,
|
||||
limit: number = 50,
|
||||
search?: string
|
||||
): Promise<{ coupons: any[]; hasMore: boolean }> {
|
||||
let whereCondition = undefined;
|
||||
const conditions = [];
|
||||
|
||||
if (cursor) {
|
||||
conditions.push(lt(coupons.id, cursor));
|
||||
}
|
||||
|
||||
if (search && search.trim()) {
|
||||
conditions.push(like(coupons.couponCode, `%${search}%`));
|
||||
}
|
||||
|
||||
if (conditions.length > 0) {
|
||||
whereCondition = and(...conditions);
|
||||
}
|
||||
|
||||
const result = await db.query.coupons.findMany({
|
||||
where: whereCondition,
|
||||
with: {
|
||||
creator: true,
|
||||
applicableUsers: {
|
||||
with: {
|
||||
user: true,
|
||||
},
|
||||
},
|
||||
applicableProducts: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: (coupons, { desc }) => [desc(coupons.createdAt)],
|
||||
limit: limit + 1,
|
||||
});
|
||||
|
||||
const hasMore = result.length > limit;
|
||||
const couponsList = hasMore ? result.slice(0, limit) : result;
|
||||
|
||||
return { coupons: couponsList, hasMore };
|
||||
}
|
||||
|
||||
export async function getCouponById(id: number): Promise<any | null> {
|
||||
const result = await db.query.coupons.findFirst({
|
||||
where: eq(coupons.id, id),
|
||||
with: {
|
||||
creator: true,
|
||||
applicableUsers: {
|
||||
with: {
|
||||
user: true,
|
||||
},
|
||||
},
|
||||
applicableProducts: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return result || null;
|
||||
}
|
||||
|
||||
export async function invalidateCoupon(id: number): Promise<Coupon> {
|
||||
const result = await db.update(coupons)
|
||||
.set({ isInvalidated: true })
|
||||
.where(eq(coupons.id, id))
|
||||
.returning();
|
||||
|
||||
return result[0];
|
||||
}
|
||||
|
||||
export interface CouponValidationResult {
|
||||
valid: boolean;
|
||||
message?: string;
|
||||
discountAmount?: number;
|
||||
coupon?: Partial<Coupon>;
|
||||
}
|
||||
|
||||
export async function validateCoupon(
|
||||
code: string,
|
||||
userId: number,
|
||||
orderAmount: number
|
||||
): Promise<CouponValidationResult> {
|
||||
const coupon = await db.query.coupons.findFirst({
|
||||
where: and(
|
||||
eq(coupons.couponCode, code.toUpperCase()),
|
||||
eq(coupons.isInvalidated, false)
|
||||
),
|
||||
});
|
||||
|
||||
if (!coupon) {
|
||||
return { valid: false, message: "Coupon not found or invalidated" };
|
||||
}
|
||||
|
||||
// Check expiry date
|
||||
if (coupon.validTill && new Date(coupon.validTill) < new Date()) {
|
||||
return { valid: false, message: "Coupon has expired" };
|
||||
}
|
||||
|
||||
// Check if coupon applies to all users or specific user
|
||||
if (!coupon.isApplyForAll && !coupon.isUserBased) {
|
||||
return { valid: false, message: "Coupon is not available for use" };
|
||||
}
|
||||
|
||||
// Check minimum order amount
|
||||
const minOrderValue = coupon.minOrder ? parseFloat(coupon.minOrder) : 0;
|
||||
if (minOrderValue > 0 && orderAmount < minOrderValue) {
|
||||
return { valid: false, message: `Minimum order amount is ${minOrderValue}` };
|
||||
}
|
||||
|
||||
// Calculate discount
|
||||
let discountAmount = 0;
|
||||
if (coupon.discountPercent) {
|
||||
const percent = parseFloat(coupon.discountPercent);
|
||||
discountAmount = (orderAmount * percent) / 100;
|
||||
} else if (coupon.flatDiscount) {
|
||||
discountAmount = parseFloat(coupon.flatDiscount);
|
||||
}
|
||||
|
||||
// Apply max value limit
|
||||
const maxValueLimit = coupon.maxValue ? parseFloat(coupon.maxValue) : 0;
|
||||
if (maxValueLimit > 0 && discountAmount > maxValueLimit) {
|
||||
discountAmount = maxValueLimit;
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
discountAmount,
|
||||
coupon: {
|
||||
id: coupon.id,
|
||||
discountPercent: coupon.discountPercent,
|
||||
flatDiscount: coupon.flatDiscount,
|
||||
maxValue: coupon.maxValue,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function getReservedCoupons(
|
||||
cursor?: number,
|
||||
limit: number = 50,
|
||||
search?: string
|
||||
): Promise<{ coupons: any[]; hasMore: boolean }> {
|
||||
let whereCondition = undefined;
|
||||
const conditions = [];
|
||||
|
||||
if (cursor) {
|
||||
conditions.push(lt(reservedCoupons.id, cursor));
|
||||
}
|
||||
|
||||
if (search && search.trim()) {
|
||||
conditions.push(or(
|
||||
like(reservedCoupons.secretCode, `%${search}%`),
|
||||
like(reservedCoupons.couponCode, `%${search}%`)
|
||||
));
|
||||
}
|
||||
|
||||
if (conditions.length > 0) {
|
||||
whereCondition = and(...conditions);
|
||||
}
|
||||
|
||||
const result = await db.query.reservedCoupons.findMany({
|
||||
where: whereCondition,
|
||||
with: {
|
||||
redeemedUser: true,
|
||||
creator: true,
|
||||
},
|
||||
orderBy: (reservedCoupons, { desc }) => [desc(reservedCoupons.createdAt)],
|
||||
limit: limit + 1,
|
||||
});
|
||||
|
||||
const hasMore = result.length > limit;
|
||||
const couponsList = hasMore ? result.slice(0, limit) : result;
|
||||
|
||||
return { coupons: couponsList, hasMore };
|
||||
}
|
||||
|
||||
export interface UserMiniInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
mobile: string | null;
|
||||
}
|
||||
|
||||
export async function getUsersForCoupon(
|
||||
search?: string,
|
||||
limit: number = 20,
|
||||
offset: number = 0
|
||||
): Promise<{ users: UserMiniInfo[] }> {
|
||||
let whereCondition = undefined;
|
||||
if (search && search.trim()) {
|
||||
whereCondition = or(
|
||||
like(users.name, `%${search}%`),
|
||||
like(users.mobile, `%${search}%`)
|
||||
);
|
||||
}
|
||||
|
||||
const userList = await db.query.users.findMany({
|
||||
where: whereCondition,
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
mobile: true,
|
||||
},
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: (users, { asc }) => [asc(users.name)],
|
||||
});
|
||||
|
||||
return {
|
||||
users: userList.map(user => ({
|
||||
id: user.id,
|
||||
name: user.name || 'Unknown',
|
||||
mobile: user.mobile,
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// BATCH 2: Transaction Methods
|
||||
// ============================================================================
|
||||
|
||||
import { couponApplicableUsers, couponApplicableProducts, orders, orderStatus } from '../db/schema';
|
||||
|
||||
export interface CreateCouponInput {
|
||||
couponCode: string;
|
||||
isUserBased: boolean;
|
||||
discountPercent?: string;
|
||||
flatDiscount?: string;
|
||||
minOrder?: string;
|
||||
productIds?: number[] | null;
|
||||
maxValue?: string;
|
||||
isApplyForAll: boolean;
|
||||
validTill?: Date;
|
||||
maxLimitForUser?: number;
|
||||
exclusiveApply: boolean;
|
||||
createdBy: number;
|
||||
}
|
||||
|
||||
export async function createCouponWithRelations(
|
||||
input: CreateCouponInput,
|
||||
applicableUsers?: number[],
|
||||
applicableProducts?: number[]
|
||||
): Promise<Coupon> {
|
||||
return await db.transaction(async (tx) => {
|
||||
// Create the coupon
|
||||
const [coupon] = await tx.insert(coupons).values({
|
||||
couponCode: input.couponCode,
|
||||
isUserBased: input.isUserBased,
|
||||
discountPercent: input.discountPercent,
|
||||
flatDiscount: input.flatDiscount,
|
||||
minOrder: input.minOrder,
|
||||
productIds: input.productIds,
|
||||
createdBy: input.createdBy,
|
||||
maxValue: input.maxValue,
|
||||
isApplyForAll: input.isApplyForAll,
|
||||
validTill: input.validTill,
|
||||
maxLimitForUser: input.maxLimitForUser,
|
||||
exclusiveApply: input.exclusiveApply,
|
||||
}).returning();
|
||||
|
||||
// Insert applicable users
|
||||
if (applicableUsers && applicableUsers.length > 0) {
|
||||
await tx.insert(couponApplicableUsers).values(
|
||||
applicableUsers.map(userId => ({
|
||||
couponId: coupon.id,
|
||||
userId,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// Insert applicable products
|
||||
if (applicableProducts && applicableProducts.length > 0) {
|
||||
await tx.insert(couponApplicableProducts).values(
|
||||
applicableProducts.map(productId => ({
|
||||
couponId: coupon.id,
|
||||
productId,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: coupon.id,
|
||||
couponCode: coupon.couponCode,
|
||||
isUserBased: coupon.isUserBased,
|
||||
discountPercent: coupon.discountPercent,
|
||||
flatDiscount: coupon.flatDiscount,
|
||||
minOrder: coupon.minOrder,
|
||||
productIds: coupon.productIds,
|
||||
maxValue: coupon.maxValue,
|
||||
isApplyForAll: coupon.isApplyForAll,
|
||||
validTill: coupon.validTill,
|
||||
maxLimitForUser: coupon.maxLimitForUser,
|
||||
exclusiveApply: coupon.exclusiveApply,
|
||||
isInvalidated: coupon.isInvalidated,
|
||||
createdAt: coupon.createdAt,
|
||||
createdBy: coupon.createdBy,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export interface UpdateCouponInput {
|
||||
couponCode?: string;
|
||||
isUserBased?: boolean;
|
||||
discountPercent?: string;
|
||||
flatDiscount?: string;
|
||||
minOrder?: string;
|
||||
productIds?: number[] | null;
|
||||
maxValue?: string;
|
||||
isApplyForAll?: boolean;
|
||||
validTill?: Date | null;
|
||||
maxLimitForUser?: number;
|
||||
exclusiveApply?: boolean;
|
||||
isInvalidated?: boolean;
|
||||
}
|
||||
|
||||
export async function updateCouponWithRelations(
|
||||
id: number,
|
||||
input: UpdateCouponInput,
|
||||
applicableUsers?: number[],
|
||||
applicableProducts?: number[]
|
||||
): Promise<Coupon> {
|
||||
return await db.transaction(async (tx) => {
|
||||
// Update the coupon
|
||||
const [coupon] = await tx.update(coupons)
|
||||
.set({
|
||||
...input,
|
||||
lastUpdated: new Date(),
|
||||
})
|
||||
.where(eq(coupons.id, id))
|
||||
.returning();
|
||||
|
||||
// Update applicable users: delete existing and insert new
|
||||
if (applicableUsers !== undefined) {
|
||||
await tx.delete(couponApplicableUsers).where(eq(couponApplicableUsers.couponId, id));
|
||||
if (applicableUsers.length > 0) {
|
||||
await tx.insert(couponApplicableUsers).values(
|
||||
applicableUsers.map(userId => ({
|
||||
couponId: id,
|
||||
userId,
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Update applicable products: delete existing and insert new
|
||||
if (applicableProducts !== undefined) {
|
||||
await tx.delete(couponApplicableProducts).where(eq(couponApplicableProducts.couponId, id));
|
||||
if (applicableProducts.length > 0) {
|
||||
await tx.insert(couponApplicableProducts).values(
|
||||
applicableProducts.map(productId => ({
|
||||
couponId: id,
|
||||
productId,
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: coupon.id,
|
||||
couponCode: coupon.couponCode,
|
||||
isUserBased: coupon.isUserBased,
|
||||
discountPercent: coupon.discountPercent,
|
||||
flatDiscount: coupon.flatDiscount,
|
||||
minOrder: coupon.minOrder,
|
||||
productIds: coupon.productIds,
|
||||
maxValue: coupon.maxValue,
|
||||
isApplyForAll: coupon.isApplyForAll,
|
||||
validTill: coupon.validTill,
|
||||
maxLimitForUser: coupon.maxLimitForUser,
|
||||
exclusiveApply: coupon.exclusiveApply,
|
||||
isInvalidated: coupon.isInvalidated,
|
||||
createdAt: coupon.createdAt,
|
||||
createdBy: coupon.createdBy,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function generateCancellationCoupon(
|
||||
orderId: number,
|
||||
staffUserId: number,
|
||||
userId: number,
|
||||
orderAmount: number,
|
||||
couponCode: string
|
||||
): Promise<Coupon> {
|
||||
return await db.transaction(async (tx) => {
|
||||
// Calculate expiry date (30 days from now)
|
||||
const expiryDate = new Date();
|
||||
expiryDate.setDate(expiryDate.getDate() + 30);
|
||||
|
||||
// Create the coupon
|
||||
const [coupon] = await tx.insert(coupons).values({
|
||||
couponCode,
|
||||
isUserBased: true,
|
||||
flatDiscount: orderAmount.toString(),
|
||||
minOrder: orderAmount.toString(),
|
||||
maxValue: orderAmount.toString(),
|
||||
validTill: expiryDate,
|
||||
maxLimitForUser: 1,
|
||||
createdBy: staffUserId,
|
||||
isApplyForAll: false,
|
||||
}).returning();
|
||||
|
||||
// Insert applicable users
|
||||
await tx.insert(couponApplicableUsers).values({
|
||||
couponId: coupon.id,
|
||||
userId,
|
||||
});
|
||||
|
||||
// Update order_status with refund coupon ID
|
||||
await tx.update(orderStatus)
|
||||
.set({ refundCouponId: coupon.id })
|
||||
.where(eq(orderStatus.orderId, orderId));
|
||||
|
||||
return {
|
||||
id: coupon.id,
|
||||
couponCode: coupon.couponCode,
|
||||
isUserBased: coupon.isUserBased,
|
||||
discountPercent: coupon.discountPercent,
|
||||
flatDiscount: coupon.flatDiscount,
|
||||
minOrder: coupon.minOrder,
|
||||
productIds: coupon.productIds,
|
||||
maxValue: coupon.maxValue,
|
||||
isApplyForAll: coupon.isApplyForAll,
|
||||
validTill: coupon.validTill,
|
||||
maxLimitForUser: coupon.maxLimitForUser,
|
||||
exclusiveApply: coupon.exclusiveApply,
|
||||
isInvalidated: coupon.isInvalidated,
|
||||
createdAt: coupon.createdAt,
|
||||
createdBy: coupon.createdBy,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export interface CreateReservedCouponInput {
|
||||
secretCode: string;
|
||||
couponCode: string;
|
||||
discountPercent?: string;
|
||||
flatDiscount?: string;
|
||||
minOrder?: string;
|
||||
productIds?: number[] | null;
|
||||
maxValue?: string;
|
||||
validTill?: Date;
|
||||
maxLimitForUser?: number;
|
||||
exclusiveApply: boolean;
|
||||
createdBy: number;
|
||||
}
|
||||
|
||||
export async function createReservedCouponWithProducts(
|
||||
input: CreateReservedCouponInput,
|
||||
applicableProducts?: number[]
|
||||
): Promise<any> {
|
||||
return await db.transaction(async (tx) => {
|
||||
const [coupon] = await tx.insert(reservedCoupons).values({
|
||||
secretCode: input.secretCode,
|
||||
couponCode: input.couponCode,
|
||||
discountPercent: input.discountPercent,
|
||||
flatDiscount: input.flatDiscount,
|
||||
minOrder: input.minOrder,
|
||||
productIds: input.productIds,
|
||||
maxValue: input.maxValue,
|
||||
validTill: input.validTill,
|
||||
maxLimitForUser: input.maxLimitForUser,
|
||||
exclusiveApply: input.exclusiveApply,
|
||||
createdBy: input.createdBy,
|
||||
}).returning();
|
||||
|
||||
// Insert applicable products if provided
|
||||
if (applicableProducts && applicableProducts.length > 0) {
|
||||
await tx.insert(couponApplicableProducts).values(
|
||||
applicableProducts.map(productId => ({
|
||||
couponId: coupon.id,
|
||||
productId,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
return coupon;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getOrCreateUserByMobile(
|
||||
mobile: string
|
||||
): Promise<{ id: number; mobile: string; name: string | null }> {
|
||||
return await db.transaction(async (tx) => {
|
||||
// Check if user exists
|
||||
let user = await tx.query.users.findFirst({
|
||||
where: eq(users.mobile, mobile),
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
// Create new user
|
||||
const [newUser] = await tx.insert(users).values({
|
||||
name: null,
|
||||
email: null,
|
||||
mobile,
|
||||
}).returning();
|
||||
user = newUser;
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
mobile: user.mobile,
|
||||
name: user.name,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function createCouponForUser(
|
||||
mobile: string,
|
||||
couponCode: string,
|
||||
staffUserId: number
|
||||
): Promise<{ coupon: Coupon; user: { id: number; mobile: string; name: string | null } }> {
|
||||
return await db.transaction(async (tx) => {
|
||||
// Get or create user
|
||||
let user = await tx.query.users.findFirst({
|
||||
where: eq(users.mobile, mobile),
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
const [newUser] = await tx.insert(users).values({
|
||||
name: null,
|
||||
email: null,
|
||||
mobile,
|
||||
}).returning();
|
||||
user = newUser;
|
||||
}
|
||||
|
||||
// Create the coupon
|
||||
const [coupon] = await tx.insert(coupons).values({
|
||||
couponCode,
|
||||
isUserBased: true,
|
||||
discountPercent: "20",
|
||||
minOrder: "1000",
|
||||
maxValue: "500",
|
||||
maxLimitForUser: 1,
|
||||
isApplyForAll: false,
|
||||
exclusiveApply: false,
|
||||
createdBy: staffUserId,
|
||||
validTill: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000), // 90 days from now
|
||||
}).returning();
|
||||
|
||||
// Associate coupon with user
|
||||
await tx.insert(couponApplicableUsers).values({
|
||||
couponId: coupon.id,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
return {
|
||||
coupon: {
|
||||
id: coupon.id,
|
||||
couponCode: coupon.couponCode,
|
||||
isUserBased: coupon.isUserBased,
|
||||
discountPercent: coupon.discountPercent,
|
||||
flatDiscount: coupon.flatDiscount,
|
||||
minOrder: coupon.minOrder,
|
||||
productIds: coupon.productIds,
|
||||
maxValue: coupon.maxValue,
|
||||
isApplyForAll: coupon.isApplyForAll,
|
||||
validTill: coupon.validTill,
|
||||
maxLimitForUser: coupon.maxLimitForUser,
|
||||
exclusiveApply: coupon.exclusiveApply,
|
||||
isInvalidated: coupon.isInvalidated,
|
||||
createdAt: coupon.createdAt,
|
||||
createdBy: coupon.createdBy,
|
||||
},
|
||||
user: {
|
||||
id: user.id,
|
||||
mobile: user.mobile,
|
||||
name: user.name,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Utility Functions
|
||||
// ============================================================================
|
||||
|
||||
export async function checkUsersExist(userIds: number[]): Promise<boolean> {
|
||||
const existingUsers = await db.query.users.findMany({
|
||||
where: inArray(users.id, userIds),
|
||||
columns: { id: true },
|
||||
});
|
||||
return existingUsers.length === userIds.length;
|
||||
}
|
||||
|
||||
export async function checkCouponExists(couponCode: string): Promise<boolean> {
|
||||
const existing = await db.query.coupons.findFirst({
|
||||
where: eq(coupons.couponCode, couponCode),
|
||||
});
|
||||
return !!existing;
|
||||
}
|
||||
|
||||
export async function checkReservedCouponExists(secretCode: string): Promise<boolean> {
|
||||
const existing = await db.query.reservedCoupons.findFirst({
|
||||
where: eq(reservedCoupons.secretCode, secretCode),
|
||||
});
|
||||
return !!existing;
|
||||
}
|
||||
|
||||
export async function getOrderWithUser(orderId: number): Promise<any | null> {
|
||||
return await db.query.orders.findFirst({
|
||||
where: eq(orders.id, orderId),
|
||||
with: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -1,259 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { orders, orderItems, orderStatus, users, addresses, refunds, complaints, payments } from '../db/schema';
|
||||
import { eq, and, gte, lt, desc, inArray, sql } from 'drizzle-orm';
|
||||
|
||||
export async function updateOrderNotes(orderId: number, adminNotes: string | null): Promise<any> {
|
||||
const [result] = await db
|
||||
.update(orders)
|
||||
.set({ adminNotes })
|
||||
.where(eq(orders.id, orderId))
|
||||
.returning();
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getOrderWithDetails(orderId: number): Promise<any | null> {
|
||||
return await db.query.orders.findFirst({
|
||||
where: eq(orders.id, orderId),
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: {
|
||||
with: {
|
||||
unit: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
user: true,
|
||||
address: true,
|
||||
orderStatus: true,
|
||||
slot: true,
|
||||
payments: true,
|
||||
refunds: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getFullOrder(orderId: number): Promise<any | null> {
|
||||
return await db.query.orders.findFirst({
|
||||
where: eq(orders.id, orderId),
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: {
|
||||
with: {
|
||||
unit: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
user: {
|
||||
with: {
|
||||
userDetails: true,
|
||||
},
|
||||
},
|
||||
address: true,
|
||||
orderStatus: true,
|
||||
slot: true,
|
||||
payments: true,
|
||||
refunds: true,
|
||||
complaints: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getOrderDetails(orderId: number): Promise<any | null> {
|
||||
return await db.query.orders.findFirst({
|
||||
where: eq(orders.id, orderId),
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: {
|
||||
with: {
|
||||
unit: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
user: true,
|
||||
address: true,
|
||||
orderStatus: true,
|
||||
slot: true,
|
||||
payments: true,
|
||||
refunds: true,
|
||||
complaints: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAllOrders(
|
||||
limit: number,
|
||||
cursor?: number,
|
||||
slotId?: number | null,
|
||||
filters?: any
|
||||
): Promise<{ orders: any[]; hasMore: boolean }> {
|
||||
let whereConditions = [];
|
||||
|
||||
if (cursor) {
|
||||
whereConditions.push(lt(orders.id, cursor));
|
||||
}
|
||||
|
||||
if (slotId) {
|
||||
whereConditions.push(eq(orders.slotId, slotId));
|
||||
}
|
||||
|
||||
// Add filter conditions
|
||||
if (filters) {
|
||||
if (filters.packagedFilter === 'packaged') {
|
||||
whereConditions.push(eq(orders.isPackaged, true));
|
||||
} else if (filters.packagedFilter === 'not_packaged') {
|
||||
whereConditions.push(eq(orders.isPackaged, false));
|
||||
}
|
||||
|
||||
if (filters.deliveredFilter === 'delivered') {
|
||||
whereConditions.push(eq(orders.isDelivered, true));
|
||||
} else if (filters.deliveredFilter === 'not_delivered') {
|
||||
whereConditions.push(eq(orders.isDelivered, false));
|
||||
}
|
||||
|
||||
if (filters.flashDeliveryFilter === 'flash') {
|
||||
whereConditions.push(eq(orders.isFlashDelivery, true));
|
||||
} else if (filters.flashDeliveryFilter === 'regular') {
|
||||
whereConditions.push(eq(orders.isFlashDelivery, false));
|
||||
}
|
||||
}
|
||||
|
||||
const ordersList = await db.query.orders.findMany({
|
||||
where: whereConditions.length > 0 ? and(...whereConditions) : undefined,
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
user: true,
|
||||
orderStatus: true,
|
||||
slot: true,
|
||||
},
|
||||
orderBy: desc(orders.id),
|
||||
limit: limit + 1,
|
||||
});
|
||||
|
||||
const hasMore = ordersList.length > limit;
|
||||
return { orders: hasMore ? ordersList.slice(0, limit) : ordersList, hasMore };
|
||||
}
|
||||
|
||||
export async function getOrdersBySlotId(slotId: number): Promise<any[]> {
|
||||
return await db.query.orders.findMany({
|
||||
where: eq(orders.slotId, slotId),
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
user: true,
|
||||
orderStatus: true,
|
||||
address: true,
|
||||
},
|
||||
orderBy: desc(orders.createdAt),
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateOrderPackaged(orderId: number, isPackaged: boolean): Promise<any> {
|
||||
const [result] = await db
|
||||
.update(orders)
|
||||
.set({ isPackaged })
|
||||
.where(eq(orders.id, orderId))
|
||||
.returning();
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateOrderDelivered(orderId: number, isDelivered: boolean): Promise<any> {
|
||||
const [result] = await db
|
||||
.update(orders)
|
||||
.set({ isDelivered })
|
||||
.where(eq(orders.id, orderId))
|
||||
.returning();
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateOrderItemPackaging(
|
||||
orderItemId: number,
|
||||
isPackaged: boolean,
|
||||
isPackageVerified: boolean
|
||||
): Promise<void> {
|
||||
await db.update(orderItems)
|
||||
.set({ is_packaged: isPackaged, is_package_verified: isPackageVerified })
|
||||
.where(eq(orderItems.id, orderItemId));
|
||||
}
|
||||
|
||||
export async function updateAddressCoords(addressId: number, lat: number, lng: number): Promise<void> {
|
||||
await db.update(addresses)
|
||||
.set({ lat, lng })
|
||||
.where(eq(addresses.id, addressId));
|
||||
}
|
||||
|
||||
export async function getOrderStatus(orderId: number): Promise<any | null> {
|
||||
return await db.query.orderStatus.findFirst({
|
||||
where: eq(orderStatus.orderId, orderId),
|
||||
});
|
||||
}
|
||||
|
||||
export async function cancelOrder(orderId: number, reason: string): Promise<any> {
|
||||
return await db.transaction(async (tx) => {
|
||||
// Update order status
|
||||
const [order] = await tx.update(orders)
|
||||
.set({ isCancelled: true, cancellationReason: reason })
|
||||
.where(eq(orders.id, orderId))
|
||||
.returning();
|
||||
|
||||
// Create order status entry
|
||||
await tx.insert(orderStatus).values({
|
||||
orderId,
|
||||
isCancelled: true,
|
||||
cancelReason: reason,
|
||||
});
|
||||
|
||||
return order;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getTodaysOrders(slotId?: number): Promise<any[]> {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
|
||||
let whereConditions = [
|
||||
gte(orders.createdAt, today),
|
||||
lt(orders.createdAt, tomorrow),
|
||||
];
|
||||
|
||||
if (slotId) {
|
||||
whereConditions.push(eq(orders.slotId, slotId));
|
||||
}
|
||||
|
||||
return await db.query.orders.findMany({
|
||||
where: and(...whereConditions),
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
user: true,
|
||||
orderStatus: true,
|
||||
},
|
||||
orderBy: desc(orders.createdAt),
|
||||
});
|
||||
}
|
||||
|
||||
export async function removeDeliveryCharge(orderId: number): Promise<any> {
|
||||
const [result] = await db
|
||||
.update(orders)
|
||||
.set({ deliveryCharge: '0' })
|
||||
.where(eq(orders.id, orderId))
|
||||
.returning();
|
||||
return result;
|
||||
}
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { productInfo, units, specialDeals, productTags, productReviews, productGroupInfo, productGroupMembership } from '../db/schema';
|
||||
import { eq, and, inArray, desc, sql, asc } from 'drizzle-orm';
|
||||
|
||||
export async function getAllProducts(): Promise<any[]> {
|
||||
return await db.query.productInfo.findMany({
|
||||
orderBy: productInfo.name,
|
||||
with: {
|
||||
unit: true,
|
||||
store: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getProductById(id: number): Promise<any | null> {
|
||||
return await db.query.productInfo.findFirst({
|
||||
where: eq(productInfo.id, id),
|
||||
with: {
|
||||
unit: true,
|
||||
store: true,
|
||||
specialDeals: true,
|
||||
productTags: {
|
||||
with: {
|
||||
tag: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function createProduct(input: any): Promise<any> {
|
||||
const [product] = await db.insert(productInfo).values(input).returning();
|
||||
return product;
|
||||
}
|
||||
|
||||
export async function updateProduct(id: number, updates: any): Promise<any> {
|
||||
const [product] = await db.update(productInfo)
|
||||
.set(updates)
|
||||
.where(eq(productInfo.id, id))
|
||||
.returning();
|
||||
return product;
|
||||
}
|
||||
|
||||
export async function toggleProductOutOfStock(id: number, isOutOfStock: boolean): Promise<any> {
|
||||
const [product] = await db.update(productInfo)
|
||||
.set({ isOutOfStock })
|
||||
.where(eq(productInfo.id, id))
|
||||
.returning();
|
||||
return product;
|
||||
}
|
||||
|
||||
export async function getAllUnits(): Promise<any[]> {
|
||||
return await db.query.units.findMany({
|
||||
orderBy: units.name,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAllProductTags(): Promise<any[]> {
|
||||
return await db.query.productTags.findMany({
|
||||
with: {
|
||||
products: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getProductReviews(productId: number): Promise<any[]> {
|
||||
return await db.query.productReviews.findMany({
|
||||
where: eq(productReviews.productId, productId),
|
||||
with: {
|
||||
user: true,
|
||||
},
|
||||
orderBy: desc(productReviews.createdAt),
|
||||
});
|
||||
}
|
||||
|
||||
export async function respondToReview(reviewId: number, adminResponse: string): Promise<void> {
|
||||
await db.update(productReviews)
|
||||
.set({ adminResponse })
|
||||
.where(eq(productReviews.id, reviewId));
|
||||
}
|
||||
|
||||
export async function getAllProductGroups(): Promise<any[]> {
|
||||
return await db.query.productGroupInfo.findMany({
|
||||
with: {
|
||||
products: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function createProductGroup(name: string): Promise<any> {
|
||||
const [group] = await db.insert(productGroupInfo).values({ name }).returning();
|
||||
return group;
|
||||
}
|
||||
|
||||
export async function updateProductGroup(id: number, name: string): Promise<any> {
|
||||
const [group] = await db.update(productGroupInfo)
|
||||
.set({ name })
|
||||
.where(eq(productGroupInfo.id, id))
|
||||
.returning();
|
||||
return group;
|
||||
}
|
||||
|
||||
export async function deleteProductGroup(id: number): Promise<void> {
|
||||
await db.delete(productGroupInfo).where(eq(productGroupInfo.id, id));
|
||||
}
|
||||
|
||||
export async function addProductToGroup(groupId: number, productId: number): Promise<void> {
|
||||
await db.insert(productGroupMembership).values({ groupId, productId });
|
||||
}
|
||||
|
||||
export async function removeProductFromGroup(groupId: number, productId: number): Promise<void> {
|
||||
await db.delete(productGroupMembership)
|
||||
.where(and(
|
||||
eq(productGroupMembership.groupId, groupId),
|
||||
eq(productGroupMembership.productId, productId)
|
||||
));
|
||||
}
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { deliverySlotInfo, productInfo, vendorSnippets } from '../db/schema';
|
||||
import { eq, and, inArray, desc } from 'drizzle-orm';
|
||||
|
||||
export async function getAllSlots(): Promise<any[]> {
|
||||
const slots = await db.query.deliverySlotInfo.findMany({
|
||||
orderBy: desc(deliverySlotInfo.createdAt),
|
||||
with: {
|
||||
vendorSnippets: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Fetch products for all slots
|
||||
const allProductIds = new Set<number>();
|
||||
for (const slot of slots) {
|
||||
for (const productId of (slot.productIds || [])) {
|
||||
allProductIds.add(productId);
|
||||
}
|
||||
}
|
||||
|
||||
const productIdsArray = Array.from(allProductIds);
|
||||
const productsData = productIdsArray.length > 0
|
||||
? await db.query.productInfo.findMany({
|
||||
where: inArray(productInfo.id, productIdsArray),
|
||||
})
|
||||
: [];
|
||||
|
||||
const productMap = new Map(productsData.map(p => [p.id, p]));
|
||||
|
||||
return slots.map(slot => ({
|
||||
...slot,
|
||||
products: (slot.productIds || [])
|
||||
.map(productId => productMap.get(productId))
|
||||
.filter((p): p is NonNullable<typeof p> => p != null),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getSlotById(id: number): Promise<any | null> {
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, id),
|
||||
with: {
|
||||
vendorSnippets: {
|
||||
with: {
|
||||
slot: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!slot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fetch products for this slot
|
||||
const productIds = slot.productIds || [];
|
||||
const productsData = productIds.length > 0
|
||||
? await db.query.productInfo.findMany({
|
||||
where: inArray(productInfo.id, productIds),
|
||||
})
|
||||
: [];
|
||||
|
||||
return {
|
||||
...slot,
|
||||
products: productsData,
|
||||
};
|
||||
}
|
||||
|
||||
export async function createSlot(input: any): Promise<any> {
|
||||
const [slot] = await db.insert(deliverySlotInfo).values(input).returning();
|
||||
return slot;
|
||||
}
|
||||
|
||||
export async function updateSlot(id: number, updates: any): Promise<any> {
|
||||
const [slot] = await db.update(deliverySlotInfo)
|
||||
.set(updates)
|
||||
.where(eq(deliverySlotInfo.id, id))
|
||||
.returning();
|
||||
return slot;
|
||||
}
|
||||
|
||||
export async function deleteSlot(id: number): Promise<void> {
|
||||
await db.delete(deliverySlotInfo).where(eq(deliverySlotInfo.id, id));
|
||||
}
|
||||
|
||||
export async function getSlotProducts(slotId: number): Promise<any[]> {
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, slotId),
|
||||
});
|
||||
|
||||
if (!slot) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const productIds = slot.productIds || [];
|
||||
if (productIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return await db.query.productInfo.findMany({
|
||||
where: inArray(productInfo.id, productIds),
|
||||
});
|
||||
}
|
||||
|
||||
export async function addProductToSlot(slotId: number, productId: number): Promise<void> {
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, slotId),
|
||||
});
|
||||
|
||||
if (!slot) {
|
||||
throw new Error(`Slot ${slotId} not found`);
|
||||
}
|
||||
|
||||
const currentProductIds = slot.productIds || [];
|
||||
if (!currentProductIds.includes(productId)) {
|
||||
await db.update(deliverySlotInfo)
|
||||
.set({ productIds: [...currentProductIds, productId] })
|
||||
.where(eq(deliverySlotInfo.id, slotId));
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeProductFromSlot(slotId: number, productId: number): Promise<void> {
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, slotId),
|
||||
});
|
||||
|
||||
if (!slot) {
|
||||
throw new Error(`Slot ${slotId} not found`);
|
||||
}
|
||||
|
||||
const currentProductIds = slot.productIds || [];
|
||||
const updatedProductIds = currentProductIds.filter(id => id !== productId);
|
||||
|
||||
await db.update(deliverySlotInfo)
|
||||
.set({ productIds: updatedProductIds })
|
||||
.where(eq(deliverySlotInfo.id, slotId));
|
||||
}
|
||||
|
||||
export async function clearSlotProducts(slotId: number): Promise<void> {
|
||||
await db.update(deliverySlotInfo)
|
||||
.set({ productIds: [] })
|
||||
.where(eq(deliverySlotInfo.id, slotId));
|
||||
}
|
||||
|
||||
export async function updateSlotCapacity(slotId: number, maxCapacity: number): Promise<any> {
|
||||
const [slot] = await db.update(deliverySlotInfo)
|
||||
.set({ maxCapacity })
|
||||
.where(eq(deliverySlotInfo.id, slotId))
|
||||
.returning();
|
||||
return slot;
|
||||
}
|
||||
|
||||
export async function getSlotDeliverySequence(slotId: number): Promise<any | null> {
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, slotId),
|
||||
columns: {
|
||||
deliverySequence: true,
|
||||
},
|
||||
});
|
||||
return slot?.deliverySequence || null;
|
||||
}
|
||||
|
||||
export async function updateSlotDeliverySequence(slotId: number, sequence: any): Promise<void> {
|
||||
await db.update(deliverySlotInfo)
|
||||
.set({ deliverySequence: sequence })
|
||||
.where(eq(deliverySlotInfo.id, slotId));
|
||||
}
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { staffUsers, staffRoles, users, userDetails, orders } from '../db/schema';
|
||||
import { eq, or, ilike, and, lt, desc } from 'drizzle-orm';
|
||||
|
||||
export interface StaffUser {
|
||||
id: number;
|
||||
name: string;
|
||||
password: string;
|
||||
staffRoleId: number;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export async function getStaffUserByName(name: string): Promise<StaffUser | null> {
|
||||
const staff = await db.query.staffUsers.findFirst({
|
||||
where: eq(staffUsers.name, name),
|
||||
});
|
||||
|
||||
return staff || null;
|
||||
}
|
||||
|
||||
export async function getAllStaff(): Promise<any[]> {
|
||||
const staff = await db.query.staffUsers.findMany({
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
with: {
|
||||
role: {
|
||||
with: {
|
||||
rolePermissions: {
|
||||
with: {
|
||||
permission: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return staff;
|
||||
}
|
||||
|
||||
export async function getStaffByName(name: string): Promise<StaffUser | null> {
|
||||
const staff = await db.query.staffUsers.findFirst({
|
||||
where: eq(staffUsers.name, name),
|
||||
});
|
||||
return staff || null;
|
||||
}
|
||||
|
||||
export async function getAllUsers(
|
||||
cursor?: number,
|
||||
limit: number = 20,
|
||||
search?: string
|
||||
): Promise<{ users: any[]; hasMore: boolean }> {
|
||||
let whereCondition = undefined;
|
||||
|
||||
if (search) {
|
||||
whereCondition = or(
|
||||
ilike(users.name, `%${search}%`),
|
||||
ilike(users.email, `%${search}%`),
|
||||
ilike(users.mobile, `%${search}%`)
|
||||
);
|
||||
}
|
||||
|
||||
if (cursor) {
|
||||
const cursorCondition = lt(users.id, cursor);
|
||||
whereCondition = whereCondition ? and(whereCondition, cursorCondition) : cursorCondition;
|
||||
}
|
||||
|
||||
const allUsers = await db.query.users.findMany({
|
||||
where: whereCondition,
|
||||
with: {
|
||||
userDetails: true,
|
||||
},
|
||||
orderBy: desc(users.id),
|
||||
limit: limit + 1,
|
||||
});
|
||||
|
||||
const hasMore = allUsers.length > limit;
|
||||
const usersToReturn = hasMore ? allUsers.slice(0, limit) : allUsers;
|
||||
|
||||
return { users: usersToReturn, hasMore };
|
||||
}
|
||||
|
||||
export async function getUserWithDetails(userId: number): Promise<any | null> {
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.id, userId),
|
||||
with: {
|
||||
userDetails: true,
|
||||
orders: {
|
||||
orderBy: desc(orders.createdAt),
|
||||
limit: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return user || null;
|
||||
}
|
||||
|
||||
export async function updateUserSuspension(userId: number, isSuspended: boolean): Promise<void> {
|
||||
await db
|
||||
.insert(userDetails)
|
||||
.values({ userId, isSuspended })
|
||||
.onConflictDoUpdate({
|
||||
target: userDetails.userId,
|
||||
set: { isSuspended },
|
||||
});
|
||||
}
|
||||
|
||||
export async function checkStaffUserExists(name: string): Promise<boolean> {
|
||||
const existingUser = await db.query.staffUsers.findFirst({
|
||||
where: eq(staffUsers.name, name),
|
||||
});
|
||||
return !!existingUser;
|
||||
}
|
||||
|
||||
export async function checkStaffRoleExists(roleId: number): Promise<boolean> {
|
||||
const role = await db.query.staffRoles.findFirst({
|
||||
where: eq(staffRoles.id, roleId),
|
||||
});
|
||||
return !!role;
|
||||
}
|
||||
|
||||
export async function createStaffUser(
|
||||
name: string,
|
||||
password: string,
|
||||
roleId: number
|
||||
): Promise<StaffUser> {
|
||||
const [newUser] = await db.insert(staffUsers).values({
|
||||
name: name.trim(),
|
||||
password,
|
||||
staffRoleId: roleId,
|
||||
}).returning();
|
||||
|
||||
return {
|
||||
id: newUser.id,
|
||||
name: newUser.name,
|
||||
password: newUser.password,
|
||||
staffRoleId: newUser.staffRoleId,
|
||||
createdAt: newUser.createdAt,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getAllRoles(): Promise<any[]> {
|
||||
const roles = await db.query.staffRoles.findMany({
|
||||
columns: {
|
||||
id: true,
|
||||
roleName: true,
|
||||
},
|
||||
});
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { storeInfo, productInfo } from '../db/schema';
|
||||
import { eq, inArray } from 'drizzle-orm';
|
||||
|
||||
export interface Store {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string | null;
|
||||
imageUrl: string | null;
|
||||
owner: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export async function getAllStores(): Promise<any[]> {
|
||||
const stores = await db.query.storeInfo.findMany({
|
||||
with: {
|
||||
owner: true,
|
||||
},
|
||||
});
|
||||
|
||||
return stores;
|
||||
}
|
||||
|
||||
export async function getStoreById(id: number): Promise<any | null> {
|
||||
const store = await db.query.storeInfo.findFirst({
|
||||
where: eq(storeInfo.id, id),
|
||||
with: {
|
||||
owner: true,
|
||||
},
|
||||
});
|
||||
|
||||
return store || null;
|
||||
}
|
||||
|
||||
export interface CreateStoreInput {
|
||||
name: string;
|
||||
description?: string;
|
||||
imageUrl?: string;
|
||||
owner: number;
|
||||
}
|
||||
|
||||
export async function createStore(
|
||||
input: CreateStoreInput,
|
||||
products?: number[]
|
||||
): Promise<Store> {
|
||||
const [newStore] = await db
|
||||
.insert(storeInfo)
|
||||
.values({
|
||||
name: input.name,
|
||||
description: input.description,
|
||||
imageUrl: input.imageUrl,
|
||||
owner: input.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));
|
||||
}
|
||||
|
||||
return {
|
||||
id: newStore.id,
|
||||
name: newStore.name,
|
||||
description: newStore.description,
|
||||
imageUrl: newStore.imageUrl,
|
||||
owner: newStore.owner,
|
||||
createdAt: newStore.createdAt,
|
||||
updatedAt: newStore.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateStoreInput {
|
||||
name?: string;
|
||||
description?: string;
|
||||
imageUrl?: string;
|
||||
owner?: number;
|
||||
}
|
||||
|
||||
export async function updateStore(
|
||||
id: number,
|
||||
input: UpdateStoreInput,
|
||||
products?: number[]
|
||||
): Promise<Store> {
|
||||
const [updatedStore] = await db
|
||||
.update(storeInfo)
|
||||
.set({
|
||||
...input,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(storeInfo.id, id))
|
||||
.returning();
|
||||
|
||||
if (!updatedStore) {
|
||||
throw new Error("Store not found");
|
||||
}
|
||||
|
||||
// Update products if provided
|
||||
if (products !== undefined) {
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: updatedStore.id,
|
||||
name: updatedStore.name,
|
||||
description: updatedStore.description,
|
||||
imageUrl: updatedStore.imageUrl,
|
||||
owner: updatedStore.owner,
|
||||
createdAt: updatedStore.createdAt,
|
||||
updatedAt: updatedStore.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export async function deleteStore(id: number): Promise<{ message: string }> {
|
||||
return 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, id));
|
||||
|
||||
// Then delete the store
|
||||
const [deletedStore] = await tx
|
||||
.delete(storeInfo)
|
||||
.where(eq(storeInfo.id, id))
|
||||
.returning();
|
||||
|
||||
if (!deletedStore) {
|
||||
throw new Error("Store not found");
|
||||
}
|
||||
|
||||
return {
|
||||
message: "Store deleted successfully",
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
@ -1,270 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { users, userDetails, orders, orderItems, complaints, notifCreds, unloggedUserTokens, userIncidents, orderStatus } from '../db/schema';
|
||||
import { eq, sql, desc, asc, count, max, inArray } from 'drizzle-orm';
|
||||
|
||||
export async function createUserByMobile(mobile: string): Promise<any> {
|
||||
const [newUser] = await db
|
||||
.insert(users)
|
||||
.values({
|
||||
name: null,
|
||||
email: null,
|
||||
mobile,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return newUser;
|
||||
}
|
||||
|
||||
export async function getUserByMobile(mobile: string): Promise<any | null> {
|
||||
const [existingUser] = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.mobile, mobile))
|
||||
.limit(1);
|
||||
|
||||
return existingUser || null;
|
||||
}
|
||||
|
||||
export async function getUnresolvedComplaintsCount(): Promise<number> {
|
||||
const result = await db
|
||||
.select({ count: count(complaints.id) })
|
||||
.from(complaints)
|
||||
.where(eq(complaints.isResolved, false));
|
||||
|
||||
return result[0]?.count || 0;
|
||||
}
|
||||
|
||||
export async function getAllUsersWithFilters(
|
||||
limit: number,
|
||||
cursor?: number,
|
||||
search?: string
|
||||
): Promise<{ users: any[]; hasMore: boolean }> {
|
||||
const whereConditions = [];
|
||||
|
||||
if (search && search.trim()) {
|
||||
whereConditions.push(sql`${users.mobile} ILIKE ${`%${search.trim()}%`}`);
|
||||
}
|
||||
|
||||
if (cursor) {
|
||||
whereConditions.push(sql`${users.id} > ${cursor}`);
|
||||
}
|
||||
|
||||
const usersList = await db
|
||||
.select({
|
||||
id: users.id,
|
||||
name: users.name,
|
||||
mobile: users.mobile,
|
||||
createdAt: users.createdAt,
|
||||
})
|
||||
.from(users)
|
||||
.where(whereConditions.length > 0 ? sql.join(whereConditions, sql` AND `) : undefined)
|
||||
.orderBy(asc(users.id))
|
||||
.limit(limit + 1);
|
||||
|
||||
const hasMore = usersList.length > limit;
|
||||
const usersToReturn = hasMore ? usersList.slice(0, limit) : usersList;
|
||||
|
||||
return { users: usersToReturn, hasMore };
|
||||
}
|
||||
|
||||
export async function getOrderCountsByUserIds(userIds: number[]): Promise<{ userId: number; totalOrders: number }[]> {
|
||||
if (userIds.length === 0) return [];
|
||||
|
||||
return await db
|
||||
.select({
|
||||
userId: orders.userId,
|
||||
totalOrders: count(orders.id),
|
||||
})
|
||||
.from(orders)
|
||||
.where(sql`${orders.userId} IN (${sql.join(userIds, sql`, `)})`)
|
||||
.groupBy(orders.userId);
|
||||
}
|
||||
|
||||
export async function getLastOrdersByUserIds(userIds: number[]): Promise<{ userId: number; lastOrderDate: Date | null }[]> {
|
||||
if (userIds.length === 0) return [];
|
||||
|
||||
return await db
|
||||
.select({
|
||||
userId: orders.userId,
|
||||
lastOrderDate: max(orders.createdAt),
|
||||
})
|
||||
.from(orders)
|
||||
.where(sql`${orders.userId} IN (${sql.join(userIds, sql`, `)})`)
|
||||
.groupBy(orders.userId);
|
||||
}
|
||||
|
||||
export async function getSuspensionStatusesByUserIds(userIds: number[]): Promise<{ userId: number; isSuspended: boolean }[]> {
|
||||
if (userIds.length === 0) return [];
|
||||
|
||||
return await db
|
||||
.select({
|
||||
userId: userDetails.userId,
|
||||
isSuspended: userDetails.isSuspended,
|
||||
})
|
||||
.from(userDetails)
|
||||
.where(sql`${userDetails.userId} IN (${sql.join(userIds, sql`, `)})`);
|
||||
}
|
||||
|
||||
export async function getUserBasicInfo(userId: number): Promise<any | null> {
|
||||
const user = await db
|
||||
.select({
|
||||
id: users.id,
|
||||
name: users.name,
|
||||
mobile: users.mobile,
|
||||
createdAt: users.createdAt,
|
||||
})
|
||||
.from(users)
|
||||
.where(eq(users.id, userId))
|
||||
.limit(1);
|
||||
|
||||
return user[0] || null;
|
||||
}
|
||||
|
||||
export async function getUserSuspensionStatus(userId: number): Promise<boolean> {
|
||||
const userDetail = await db
|
||||
.select({
|
||||
isSuspended: userDetails.isSuspended,
|
||||
})
|
||||
.from(userDetails)
|
||||
.where(eq(userDetails.userId, userId))
|
||||
.limit(1);
|
||||
|
||||
return userDetail[0]?.isSuspended ?? false;
|
||||
}
|
||||
|
||||
export async function getUserOrders(userId: number): Promise<any[]> {
|
||||
return await db
|
||||
.select({
|
||||
id: orders.id,
|
||||
readableId: orders.readableId,
|
||||
totalAmount: orders.totalAmount,
|
||||
createdAt: orders.createdAt,
|
||||
isFlashDelivery: orders.isFlashDelivery,
|
||||
})
|
||||
.from(orders)
|
||||
.where(eq(orders.userId, userId))
|
||||
.orderBy(desc(orders.createdAt));
|
||||
}
|
||||
|
||||
export async function getOrderStatusesByOrderIds(orderIds: number[]): Promise<{ orderId: number; isDelivered: boolean; isCancelled: boolean }[]> {
|
||||
if (orderIds.length === 0) return [];
|
||||
|
||||
return await db
|
||||
.select({
|
||||
orderId: orderStatus.orderId,
|
||||
isDelivered: orderStatus.isDelivered,
|
||||
isCancelled: orderStatus.isCancelled,
|
||||
})
|
||||
.from(orderStatus)
|
||||
.where(sql`${orderStatus.orderId} IN (${sql.join(orderIds, sql`, `)})`);
|
||||
}
|
||||
|
||||
export async function getItemCountsByOrderIds(orderIds: number[]): Promise<{ orderId: number; itemCount: number }[]> {
|
||||
if (orderIds.length === 0) return [];
|
||||
|
||||
return await db
|
||||
.select({
|
||||
orderId: orderItems.orderId,
|
||||
itemCount: count(orderItems.id),
|
||||
})
|
||||
.from(orderItems)
|
||||
.where(sql`${orderItems.orderId} IN (${sql.join(orderIds, sql`, `)})`)
|
||||
.groupBy(orderItems.orderId);
|
||||
}
|
||||
|
||||
export async function upsertUserSuspension(userId: number, isSuspended: boolean): Promise<void> {
|
||||
const existingDetail = await db
|
||||
.select({ id: userDetails.id })
|
||||
.from(userDetails)
|
||||
.where(eq(userDetails.userId, userId))
|
||||
.limit(1);
|
||||
|
||||
if (existingDetail.length > 0) {
|
||||
await db
|
||||
.update(userDetails)
|
||||
.set({ isSuspended })
|
||||
.where(eq(userDetails.userId, userId));
|
||||
} else {
|
||||
await db
|
||||
.insert(userDetails)
|
||||
.values({
|
||||
userId,
|
||||
isSuspended,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function searchUsers(search?: string): Promise<any[]> {
|
||||
if (search && search.trim()) {
|
||||
return await db
|
||||
.select({
|
||||
id: users.id,
|
||||
name: users.name,
|
||||
mobile: users.mobile,
|
||||
})
|
||||
.from(users)
|
||||
.where(sql`${users.mobile} ILIKE ${`%${search.trim()}%`} OR ${users.name} ILIKE ${`%${search.trim()}%`}`);
|
||||
} else {
|
||||
return await db
|
||||
.select({
|
||||
id: users.id,
|
||||
name: users.name,
|
||||
mobile: users.mobile,
|
||||
})
|
||||
.from(users);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllNotifCreds(): Promise<{ userId: number }[]> {
|
||||
return await db
|
||||
.select({ userId: notifCreds.userId })
|
||||
.from(notifCreds);
|
||||
}
|
||||
|
||||
export async function getAllUnloggedTokens(): Promise<{ token: string }[]> {
|
||||
return await db
|
||||
.select({ token: unloggedUserTokens.token })
|
||||
.from(unloggedUserTokens);
|
||||
}
|
||||
|
||||
export async function getNotifTokensByUserIds(userIds: number[]): Promise<{ token: string }[]> {
|
||||
return await db
|
||||
.select({ token: notifCreds.token })
|
||||
.from(notifCreds)
|
||||
.where(inArray(notifCreds.userId, userIds));
|
||||
}
|
||||
|
||||
export async function getUserIncidentsWithRelations(userId: number): Promise<any[]> {
|
||||
return await db.query.userIncidents.findMany({
|
||||
where: eq(userIncidents.userId, userId),
|
||||
with: {
|
||||
order: {
|
||||
with: {
|
||||
orderStatus: true,
|
||||
},
|
||||
},
|
||||
addedBy: true,
|
||||
},
|
||||
orderBy: desc(userIncidents.dateAdded),
|
||||
});
|
||||
}
|
||||
|
||||
export async function createUserIncident(
|
||||
userId: number,
|
||||
orderId: number | undefined,
|
||||
adminComment: string | undefined,
|
||||
adminUserId: number,
|
||||
negativityScore: number | undefined
|
||||
): Promise<any> {
|
||||
const [incident] = await db.insert(userIncidents)
|
||||
.values({
|
||||
userId,
|
||||
orderId,
|
||||
adminComment,
|
||||
addedBy: adminUserId,
|
||||
negativityScore,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return incident;
|
||||
}
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { vendorSnippets, deliverySlotInfo, productInfo, orders, orderItems, orderStatus } from '../db/schema';
|
||||
import { eq, and, inArray, gt, sql, asc } from 'drizzle-orm';
|
||||
|
||||
export async function checkVendorSnippetExists(snippetCode: string): Promise<boolean> {
|
||||
const existingSnippet = await db.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.snippetCode, snippetCode),
|
||||
});
|
||||
return !!existingSnippet;
|
||||
}
|
||||
|
||||
export async function getVendorSnippetById(id: number): Promise<any | null> {
|
||||
return await db.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.id, id),
|
||||
with: {
|
||||
slot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getVendorSnippetByCode(snippetCode: string): Promise<any | null> {
|
||||
return await db.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.snippetCode, snippetCode),
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAllVendorSnippets(): Promise<any[]> {
|
||||
return await db.query.vendorSnippets.findMany({
|
||||
with: {
|
||||
slot: true,
|
||||
},
|
||||
orderBy: (vendorSnippets, { desc }) => [desc(vendorSnippets.createdAt)],
|
||||
});
|
||||
}
|
||||
|
||||
export interface CreateVendorSnippetInput {
|
||||
snippetCode: string;
|
||||
slotId?: number;
|
||||
productIds: number[];
|
||||
isPermanent: boolean;
|
||||
validTill?: Date;
|
||||
}
|
||||
|
||||
export async function createVendorSnippet(input: CreateVendorSnippetInput): Promise<any> {
|
||||
const [result] = await db.insert(vendorSnippets).values({
|
||||
snippetCode: input.snippetCode,
|
||||
slotId: input.slotId,
|
||||
productIds: input.productIds,
|
||||
isPermanent: input.isPermanent,
|
||||
validTill: input.validTill,
|
||||
}).returning();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateVendorSnippet(id: number, updates: any): Promise<any> {
|
||||
const [result] = await db.update(vendorSnippets)
|
||||
.set(updates)
|
||||
.where(eq(vendorSnippets.id, id))
|
||||
.returning();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function deleteVendorSnippet(id: number): Promise<void> {
|
||||
await db.delete(vendorSnippets)
|
||||
.where(eq(vendorSnippets.id, id));
|
||||
}
|
||||
|
||||
export async function getProductsByIds(productIds: number[]): Promise<any[]> {
|
||||
return await db.query.productInfo.findMany({
|
||||
where: inArray(productInfo.id, productIds),
|
||||
columns: { id: true, name: true },
|
||||
});
|
||||
}
|
||||
|
||||
export async function getVendorSlotById(slotId: number): Promise<any | null> {
|
||||
return await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, slotId),
|
||||
});
|
||||
}
|
||||
|
||||
export async function getVendorOrdersBySlotId(slotId: number): Promise<any[]> {
|
||||
return await db.query.orders.findMany({
|
||||
where: eq(orders.slotId, slotId),
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: {
|
||||
with: {
|
||||
unit: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderStatus: true,
|
||||
user: true,
|
||||
slot: true,
|
||||
},
|
||||
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
|
||||
});
|
||||
}
|
||||
|
||||
export async function getOrderItemsByOrderIds(orderIds: number[]): Promise<any[]> {
|
||||
return await db.query.orderItems.findMany({
|
||||
where: inArray(orderItems.orderId, orderIds),
|
||||
with: {
|
||||
product: {
|
||||
with: {
|
||||
unit: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getOrderStatusByOrderIds(orderIds: number[]): Promise<any[]> {
|
||||
return await db.query.orderStatus.findMany({
|
||||
where: inArray(orderStatus.orderId, orderIds),
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateVendorOrderItemPackaging(orderItemId: number, isPackaged: boolean, isPackageVerified: boolean): Promise<void> {
|
||||
await db.update(orderItems)
|
||||
.set({
|
||||
is_packaged: isPackaged,
|
||||
is_package_verified: isPackageVerified,
|
||||
})
|
||||
.where(eq(orderItems.id, orderItemId));
|
||||
}
|
||||
|
|
@ -1,21 +1,6 @@
|
|||
import { db } from '../db/db_index'
|
||||
import { productInfo, keyValStore } from '../db/schema'
|
||||
import { inArray, eq } from 'drizzle-orm'
|
||||
|
||||
/**
|
||||
* Toggle flash delivery availability for specific products
|
||||
* @param isAvailable - Whether flash delivery should be available
|
||||
* @param productIds - Array of product IDs to update
|
||||
*/
|
||||
export async function toggleFlashDeliveryForItems(
|
||||
isAvailable: boolean,
|
||||
productIds: number[]
|
||||
): Promise<void> {
|
||||
await db
|
||||
.update(productInfo)
|
||||
.set({ isFlashAvailable: isAvailable })
|
||||
.where(inArray(productInfo.id, productIds))
|
||||
}
|
||||
import { keyValStore } from '../db/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
|
||||
/**
|
||||
* Update key-value store
|
||||
|
|
|
|||
|
|
@ -20,32 +20,6 @@ import type {
|
|||
UserOrderDetail,
|
||||
} from '@packages/shared'
|
||||
|
||||
export interface OrderItemInput {
|
||||
productId: number
|
||||
quantity: number
|
||||
slotId: number | null
|
||||
}
|
||||
|
||||
export interface PlaceOrderInput {
|
||||
userId: number
|
||||
selectedItems: OrderItemInput[]
|
||||
addressId: number
|
||||
paymentMethod: 'online' | 'cod'
|
||||
couponId?: number
|
||||
userNotes?: string
|
||||
isFlash?: boolean
|
||||
}
|
||||
|
||||
export interface OrderGroupData {
|
||||
slotId: number | null
|
||||
items: Array<{
|
||||
productId: number
|
||||
quantity: number
|
||||
slotId: number | null
|
||||
product: typeof productInfo.$inferSelect
|
||||
}>
|
||||
}
|
||||
|
||||
export interface PlacedOrder {
|
||||
id: number
|
||||
userId: number
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ export async function getAllProductsWithUnits(tagId?: number): Promise<ProductSu
|
|||
/**
|
||||
* Get all suspended product IDs
|
||||
*/
|
||||
export async function getSuspendedProductIds(): Promise<number[]> {
|
||||
export async function getSuspendedSkuIds(): Promise<number[]> {
|
||||
const suspendedProducts = await db
|
||||
.select({ id: productInfo.id })
|
||||
.from(productInfo)
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { productTags } from '../db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
export async function getAllTags(): Promise<any[]> {
|
||||
return await db.query.productTags.findMany({
|
||||
with: {
|
||||
// products: {
|
||||
// with: {
|
||||
// product: true,
|
||||
// },
|
||||
// },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getTagById(id: number): Promise<any | null> {
|
||||
return await db.query.productTags.findFirst({
|
||||
where: eq(productTags.id, id),
|
||||
with: {
|
||||
// products: {
|
||||
// with: {
|
||||
// product: true,
|
||||
// },
|
||||
// },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -6,9 +6,6 @@ export { db, initDb } from './src/db/db_index'
|
|||
// Re-export schema
|
||||
export * from './src/db/schema'
|
||||
|
||||
// Export enum types for type safety
|
||||
export { staffRoleEnum, staffPermissionEnum } from './src/db/schema'
|
||||
|
||||
// Admin API helpers - explicitly namespaced exports to avoid duplicates
|
||||
export {
|
||||
// Banner
|
||||
|
|
@ -83,14 +80,10 @@ export {
|
|||
checkProductExistsByName,
|
||||
checkUnitExists,
|
||||
getProductImagesById,
|
||||
createSpecialDealsForSku,
|
||||
updateSkuDeals,
|
||||
replaceProductTags,
|
||||
replaceTagProducts,
|
||||
mergeSkus,
|
||||
updateSlotProducts,
|
||||
getSlotsProductIds,
|
||||
getAllUnits,
|
||||
getAllProductTags,
|
||||
getAllProductTagInfos,
|
||||
getProductTagInfoById,
|
||||
|
|
@ -105,8 +98,6 @@ export {
|
|||
createProductGroup,
|
||||
updateProductGroup,
|
||||
deleteProductGroup,
|
||||
addProductToGroup,
|
||||
removeProductFromGroup,
|
||||
updateProductPrices,
|
||||
} from './src/admin-apis/product'
|
||||
|
||||
|
|
@ -137,7 +128,6 @@ export {
|
|||
getAllStaff,
|
||||
getAllUsers,
|
||||
getUserWithDetails,
|
||||
updateUserSuspensionStatus,
|
||||
checkStaffUserExists,
|
||||
checkStaffRoleExists,
|
||||
createStaffUser,
|
||||
|
|
@ -188,8 +178,6 @@ export {
|
|||
getProductsByIds,
|
||||
getVendorSlotById,
|
||||
getVendorOrdersBySlotId,
|
||||
getOrderItemsByOrderIds,
|
||||
getOrderStatusByOrderIds,
|
||||
updateVendorOrderItemPackaging,
|
||||
getVendorOrders,
|
||||
} from './src/admin-apis/vendor-snippets'
|
||||
|
|
@ -233,9 +221,6 @@ export {
|
|||
getAllSkusSummary,
|
||||
getOffersAndCombos,
|
||||
type ProductSummaryData,
|
||||
type SkuSummary,
|
||||
type OffersPageData,
|
||||
type OffersPageProductData,
|
||||
} from './src/user-apis/product'
|
||||
|
||||
export {
|
||||
|
|
@ -285,7 +270,6 @@ export {
|
|||
getUserById as getUserProfileById,
|
||||
getUserDetailByUserId as getUserProfileDetailById,
|
||||
getUserWithCreds as getUserWithCreds,
|
||||
getNotifCred as getUserNotifCred,
|
||||
upsertNotifCred as upsertUserNotifCred,
|
||||
deleteUnloggedToken as deleteUserUnloggedToken,
|
||||
getUnloggedToken as getUserUnloggedToken,
|
||||
|
|
@ -313,8 +297,6 @@ export {
|
|||
// Post-order handler helpers
|
||||
getOrdersByIdsWithFullData,
|
||||
getOrderByIdWithFullData,
|
||||
type OrderWithFullData,
|
||||
type OrderWithCancellationData,
|
||||
} from './src/user-apis/order'
|
||||
|
||||
// Store Helpers (for cache initialization)
|
||||
|
|
@ -331,7 +313,6 @@ export {
|
|||
getAllProductTagsForCache,
|
||||
getAllProductCombosForCache,
|
||||
type ProductBasicData,
|
||||
type AvailabilityCacheData,
|
||||
type StoreBasicData,
|
||||
type DeliverySlotData,
|
||||
type SpecialDealData,
|
||||
|
|
@ -379,11 +360,8 @@ export {
|
|||
|
||||
// SKU Features Helper
|
||||
export {
|
||||
cleanFeatureValue,
|
||||
splitQuantityFeature,
|
||||
composeUnitNotation,
|
||||
composeSkuName,
|
||||
type SkuFeatureLike,
|
||||
} from './src/lib/sku-features'
|
||||
|
||||
// Upload URL Helpers
|
||||
|
|
|
|||
|
|
@ -100,7 +100,6 @@ interface CreateProductInput {
|
|||
productType?: 'item' | 'combo'
|
||||
skus: CreateSkuInput[]
|
||||
}
|
||||
type UnitRow = InferSelectModel<typeof units>
|
||||
type StoreRow = InferSelectModel<typeof storeInfo>
|
||||
type SpecialDealRow = InferSelectModel<typeof specialDeals>
|
||||
type ProductTagInfoRow = InferSelectModel<typeof productTagInfo>
|
||||
|
|
@ -114,12 +113,6 @@ const getStringArray = (value: unknown): string[] | null => {
|
|||
return value.map((item) => String(item))
|
||||
}
|
||||
|
||||
const mapUnit = (unit: UnitRow): AdminUnit => ({
|
||||
id: unit.id,
|
||||
shortNotation: unit.shortNotation,
|
||||
fullName: unit.fullName,
|
||||
})
|
||||
|
||||
const mapStore = (store: StoreRow): Store => ({
|
||||
id: store.id,
|
||||
name: store.name,
|
||||
|
|
@ -647,14 +640,6 @@ export async function updateSlotProducts(slotId: string, productIds: string[]):
|
|||
}
|
||||
}
|
||||
|
||||
export async function getAllUnits(): Promise<AdminUnit[]> {
|
||||
const allUnits = await db.query.units.findMany({
|
||||
orderBy: units.shortNotation,
|
||||
})
|
||||
|
||||
return allUnits.map(mapUnit)
|
||||
}
|
||||
|
||||
export async function getAllProductTags(): Promise<AdminProductTagWithProducts[]> {
|
||||
const tags = await db.query.productTagInfo.findMany({
|
||||
with: {
|
||||
|
|
@ -1029,18 +1014,6 @@ export async function deleteProductGroup(id: number): Promise<AdminProductGroupI
|
|||
}
|
||||
}
|
||||
|
||||
export async function addProductToGroup(groupId: number, productId: number): Promise<void> {
|
||||
await db.insert(productGroupMembership).values({ groupId, productId })
|
||||
}
|
||||
|
||||
export async function removeProductFromGroup(groupId: number, productId: number): Promise<void> {
|
||||
await db.delete(productGroupMembership)
|
||||
.where(and(
|
||||
eq(productGroupMembership.groupId, groupId),
|
||||
eq(productGroupMembership.productId, productId)
|
||||
))
|
||||
}
|
||||
|
||||
export async function updateProductPrices(updates: Array<{
|
||||
productId: number
|
||||
price?: number
|
||||
|
|
@ -1142,101 +1115,6 @@ export async function getProductImagesById(productId: number): Promise<string[]
|
|||
return getStringArray(product.images) || []
|
||||
}
|
||||
|
||||
export interface CreateSpecialDealInput {
|
||||
quantity: number
|
||||
price: number
|
||||
validTill: string | Date
|
||||
}
|
||||
|
||||
export async function createSpecialDealsForSku(
|
||||
skuId: number,
|
||||
deals: CreateSpecialDealInput[]
|
||||
): Promise<AdminSpecialDeal[]> {
|
||||
if (deals.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const dealInserts = deals.map((deal) => ({
|
||||
skuId,
|
||||
quantity: deal.quantity.toString(),
|
||||
price: deal.price.toString(),
|
||||
validTill: new Date(deal.validTill),
|
||||
}))
|
||||
|
||||
const createdDeals = await db
|
||||
.insert(specialDeals)
|
||||
.values(dealInserts)
|
||||
.returning()
|
||||
|
||||
return createdDeals.map(mapSpecialDeal)
|
||||
}
|
||||
|
||||
export async function updateSkuDeals(
|
||||
productId: number,
|
||||
deals: CreateSpecialDealInput[]
|
||||
): Promise<void> {
|
||||
if (deals.length === 0) {
|
||||
await db.delete(specialDeals).where(eq(specialDeals.skuId, productId))
|
||||
return
|
||||
}
|
||||
|
||||
const existingDeals = await db.query.specialDeals.findMany({
|
||||
where: eq(specialDeals.skuId, productId),
|
||||
})
|
||||
|
||||
const existingDealsMap = new Map<string, SpecialDealRow>(
|
||||
existingDeals.map((deal: SpecialDealRow) => [`${deal.quantity}-${deal.price}`, deal])
|
||||
)
|
||||
const newDealsMap = new Map<string, CreateSpecialDealInput>(
|
||||
deals.map((deal) => [`${deal.quantity}-${deal.price}`, deal])
|
||||
)
|
||||
|
||||
const dealsToAdd = deals.filter((deal) => {
|
||||
const key = `${deal.quantity}-${deal.price}`
|
||||
return !existingDealsMap.has(key)
|
||||
})
|
||||
|
||||
const dealsToRemove = existingDeals.filter((deal: SpecialDealRow) => {
|
||||
const key = `${deal.quantity}-${deal.price}`
|
||||
return !newDealsMap.has(key)
|
||||
})
|
||||
|
||||
const dealsToUpdate = deals.filter((deal: CreateSpecialDealInput) => {
|
||||
const key = `${deal.quantity}-${deal.price}`
|
||||
const existing = existingDealsMap.get(key)
|
||||
const nextValidTill = deal.validTill instanceof Date
|
||||
? deal.validTill.toISOString().split('T')[0]
|
||||
: String(deal.validTill)
|
||||
return existing && existing.validTill.toISOString().split('T')[0] !== nextValidTill
|
||||
})
|
||||
|
||||
if (dealsToRemove.length > 0) {
|
||||
await db.delete(specialDeals).where(
|
||||
inArray(specialDeals.id, dealsToRemove.map((deal: SpecialDealRow) => deal.id))
|
||||
)
|
||||
}
|
||||
|
||||
if (dealsToAdd.length > 0) {
|
||||
const dealInserts = dealsToAdd.map((deal) => ({
|
||||
skuId: productId,
|
||||
quantity: deal.quantity.toString(),
|
||||
price: deal.price.toString(),
|
||||
validTill: new Date(deal.validTill),
|
||||
}))
|
||||
await db.insert(specialDeals).values(dealInserts)
|
||||
}
|
||||
|
||||
for (const deal of dealsToUpdate) {
|
||||
const key = `${deal.quantity}-${deal.price}`
|
||||
const existingDeal = existingDealsMap.get(key)
|
||||
if (existingDeal) {
|
||||
await db.update(specialDeals)
|
||||
.set({ validTill: new Date(deal.validTill) })
|
||||
.where(eq(specialDeals.id, existingDeal.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function replaceProductTags(productId: number, tagIds: number[]): Promise<void> {
|
||||
await db.delete(productTags).where(eq(productTags.productId, productId))
|
||||
|
||||
|
|
@ -1266,128 +1144,3 @@ export async function replaceTagProducts(tagId: number, productIds: number[]): P
|
|||
|
||||
await db.insert(productTags).values(productAssociations)
|
||||
}
|
||||
|
||||
export async function mergeSkus(fromSkuId: number, toSkuId: number) {
|
||||
if (fromSkuId === toSkuId) {
|
||||
return { fromSkuId, toSkuId, message: 'SKUs are the same, no merge needed', counts: {} }
|
||||
}
|
||||
|
||||
const fromSku = await db.query.productSkus.findFirst({ where: eq(productSkus.id, fromSkuId) })
|
||||
if (!fromSku) throw new Error(`SKU ${fromSkuId} not found`)
|
||||
|
||||
const toSku = await db.query.productSkus.findFirst({ where: eq(productSkus.id, toSkuId) })
|
||||
if (!toSku) throw new Error(`SKU ${toSkuId} not found`)
|
||||
|
||||
const counts: Record<string, number> = {}
|
||||
|
||||
// 1. order_items — direct update
|
||||
const orderItemsResult = await db.update(orderItems)
|
||||
.set({ skuId: toSkuId })
|
||||
.where(eq(orderItems.skuId, fromSkuId))
|
||||
counts.orderItems = orderItemsResult.changes ?? 0
|
||||
|
||||
// 2. special_deals — direct update
|
||||
const specialDealsResult = await db.update(specialDeals)
|
||||
.set({ skuId: toSkuId })
|
||||
.where(eq(specialDeals.skuId, fromSkuId))
|
||||
counts.specialDeals = specialDealsResult.changes ?? 0
|
||||
|
||||
// 3. cart_items — delete all with fromSkuId
|
||||
const cartResult = await db.delete(cartItems)
|
||||
.where(eq(cartItems.skuId, fromSkuId))
|
||||
counts.cartItems = cartResult.changes ?? 0
|
||||
|
||||
// 4. coupon_applicable_products — update, but handle unique constraint
|
||||
// First delete rows where (coupon_id, toSkuId) already exists
|
||||
const existingCapRows = await db.query.couponApplicableProducts.findMany({
|
||||
where: eq(couponApplicableProducts.skuId, toSkuId),
|
||||
columns: { couponId: true },
|
||||
})
|
||||
const existingCouponIds = new Set(existingCapRows.map((r) => r.couponId))
|
||||
|
||||
if (existingCouponIds.size > 0) {
|
||||
const dupResult = await db.delete(couponApplicableProducts)
|
||||
.where(
|
||||
and(
|
||||
eq(couponApplicableProducts.skuId, fromSkuId),
|
||||
inArray(couponApplicableProducts.couponId, Array.from(existingCouponIds))
|
||||
)
|
||||
)
|
||||
counts.couponDedupDeleted = dupResult.changes ?? 0
|
||||
}
|
||||
|
||||
// Now update remaining rows
|
||||
const capResult = await db.update(couponApplicableProducts)
|
||||
.set({ skuId: toSkuId })
|
||||
.where(eq(couponApplicableProducts.skuId, fromSkuId))
|
||||
counts.couponApplicable = capResult.changes ?? 0
|
||||
|
||||
// 5. JSON arrays — remap fromSkuId to toSkuId
|
||||
const jsonTables: Array<{ table: any; column: string; name: string }> = [
|
||||
{ table: deliverySlotInfo, column: 'skuIds', name: 'deliverySlotInfo' },
|
||||
{ table: homeBanners, column: 'skuIds', name: 'homeBanners' },
|
||||
{ table: coupons, column: 'skuIds', name: 'coupons' },
|
||||
{ table: reservedCoupons, column: 'skuIds', name: 'reservedCoupons' },
|
||||
{ table: vendorSnippets, column: 'skuIds', name: 'vendorSnippets' },
|
||||
]
|
||||
|
||||
for (const { table, column, name } of jsonTables) {
|
||||
const rows = await db.select({ id: table.id, ids: table[column] }).from(table)
|
||||
let updated = 0
|
||||
for (const row of rows) {
|
||||
const ids: number[] = (row.ids as number[]) || []
|
||||
if (!ids.includes(fromSkuId)) continue
|
||||
const newIds = ids.map((id) => (id === fromSkuId ? toSkuId : id))
|
||||
const deduped = [...new Set(newIds)]
|
||||
if (deduped.length !== ids.length || deduped.some((id, i) => id !== ids[i])) {
|
||||
await db.update(table).set({ [column]: deduped } as any).where(eq(table.id, row.id))
|
||||
updated++
|
||||
}
|
||||
}
|
||||
counts[name] = updated
|
||||
}
|
||||
|
||||
// popularItems in key_val_store
|
||||
const kvRow = await db.query.keyValStore.findFirst({
|
||||
where: eq(keyValStore.key, 'popularItems'),
|
||||
})
|
||||
if (kvRow && kvRow.value) {
|
||||
try {
|
||||
const arr: number[] = JSON.parse(kvRow.value)
|
||||
if (arr.includes(fromSkuId)) {
|
||||
const newArr = [...new Set(arr.map((id) => (id === fromSkuId ? toSkuId : id)))]
|
||||
await db.update(keyValStore)
|
||||
.set({ value: JSON.stringify(newArr) })
|
||||
.where(eq(keyValStore.key, 'popularItems'))
|
||||
counts.popularItems = 1
|
||||
}
|
||||
} catch { /* value not valid JSON, skip */ }
|
||||
}
|
||||
|
||||
// 6. Delete SKU features and the SKU itself
|
||||
const featuresResult = await db.delete(skuFeatures).where(eq(skuFeatures.skuId, fromSkuId))
|
||||
counts.skuFeatures = featuresResult.changes ?? 0
|
||||
|
||||
const skuResult = await db.delete(productSkus).where(eq(productSkus.id, fromSkuId))
|
||||
counts.productSkus = skuResult.changes ?? 0
|
||||
|
||||
// 7. Delete orphaned product
|
||||
const remainingSkus = await db.query.productSkus.findMany({
|
||||
where: eq(productSkus.productId, fromSku.productId),
|
||||
columns: { id: true },
|
||||
})
|
||||
|
||||
let orphanedProductId: number | undefined
|
||||
if (remainingSkus.length === 0) {
|
||||
await db.delete(productInfo).where(eq(productInfo.id, fromSku.productId))
|
||||
orphanedProductId = fromSku.productId
|
||||
counts.orphanedProduct = 1
|
||||
}
|
||||
|
||||
return {
|
||||
fromSkuId,
|
||||
toSkuId,
|
||||
orphanedProductId,
|
||||
counts,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,16 +98,6 @@ export async function getUserWithDetails(userId: number): Promise<any | null> {
|
|||
return user || null
|
||||
}
|
||||
|
||||
export async function updateUserSuspensionStatus(userId: number, isSuspended: boolean): Promise<void> {
|
||||
await db
|
||||
.insert(userDetails)
|
||||
.values({ userId, isSuspended })
|
||||
.onConflictDoUpdate({
|
||||
target: userDetails.userId,
|
||||
set: { isSuspended },
|
||||
})
|
||||
}
|
||||
|
||||
export async function checkStaffUserExists(name: string): Promise<boolean> {
|
||||
const existingUser = await db.query.staffUsers.findFirst({
|
||||
where: eq(staffUsers.name, name),
|
||||
|
|
|
|||
|
|
@ -193,26 +193,6 @@ export async function getVendorOrders() {
|
|||
})
|
||||
}
|
||||
|
||||
export async function getOrderItemsByOrderIds(orderIds: number[]) {
|
||||
return await db.query.orderItems.findMany({
|
||||
where: inArray(orderItems.orderId, orderIds),
|
||||
with: {
|
||||
sku: {
|
||||
with: {
|
||||
product: true,
|
||||
features: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function getOrderStatusByOrderIds(orderIds: number[]) {
|
||||
return await db.query.orderStatus.findMany({
|
||||
where: inArray(orderStatus.orderId, orderIds),
|
||||
})
|
||||
}
|
||||
|
||||
export async function updateVendorOrderItemPackaging(
|
||||
orderItemId: number,
|
||||
isPackaged: boolean
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
// Common utility functions that can be used by both admin and user APIs
|
||||
|
||||
export function formatDate(date: Date): string {
|
||||
return date.toISOString()
|
||||
}
|
||||
|
||||
export function generateCode(prefix: string, length: number = 6): string {
|
||||
const timestamp = Date.now().toString().slice(-length)
|
||||
const random = Math.random().toString(36).substring(2, 8).toUpperCase()
|
||||
return `${prefix}${timestamp}${random}`
|
||||
}
|
||||
|
||||
export function calculateDiscount(amount: number, percent: number, maxDiscount?: number): number {
|
||||
let discount = (amount * percent) / 100
|
||||
if (maxDiscount && discount > maxDiscount) {
|
||||
discount = maxDiscount
|
||||
}
|
||||
return discount
|
||||
}
|
||||
|
|
@ -103,19 +103,6 @@ export const userCreds = sqliteTable('user_creds', {
|
|||
createdAt: timestampText('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
})
|
||||
|
||||
export const addressZones = sqliteTable('address_zones', {
|
||||
id: integer().primaryKey({ autoIncrement: true }),
|
||||
zoneName: text('zone_name').notNull(),
|
||||
addedAt: timestampText('added_at').notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
})
|
||||
|
||||
export const addressAreas = sqliteTable('address_areas', {
|
||||
id: integer().primaryKey({ autoIncrement: true }),
|
||||
placeName: text('place_name').notNull(),
|
||||
zoneId: integer('zone_id').references(() => addressZones.id),
|
||||
createdAt: timestampText('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
})
|
||||
|
||||
export const addresses = sqliteTable('addresses', {
|
||||
id: integer().primaryKey({ autoIncrement: true }),
|
||||
userId: integer('user_id').notNull().references(() => users.id),
|
||||
|
|
@ -132,7 +119,7 @@ export const addresses = sqliteTable('addresses', {
|
|||
googleMapsUrl: text('google_maps_url'),
|
||||
adminLatitude: real('admin_latitude'),
|
||||
adminLongitude: real('admin_longitude'),
|
||||
zoneId: integer('zone_id').references(() => addressZones.id),
|
||||
zoneId: integer('zone_id'),
|
||||
createdAt: timestampText('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
})
|
||||
|
||||
|
|
@ -428,12 +415,6 @@ export const notifications = sqliteTable('notifications', {
|
|||
createdAt: timestampText('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
})
|
||||
|
||||
export const productCategories = sqliteTable('product_categories', {
|
||||
id: integer().primaryKey({ autoIncrement: true }),
|
||||
name: text().notNull(),
|
||||
description: text(),
|
||||
})
|
||||
|
||||
export const cartItems = sqliteTable('cart_items', {
|
||||
id: integer().primaryKey({ autoIncrement: true }),
|
||||
userId: integer('user_id').notNull().references(() => users.id),
|
||||
|
|
@ -542,15 +523,6 @@ export const unloggedUserTokens = sqliteTable('unlogged_user_tokens', {
|
|||
lastVerified: timestampText('last_verified'),
|
||||
})
|
||||
|
||||
export const userNotifications = sqliteTable('user_notifications', {
|
||||
id: integer().primaryKey({ autoIncrement: true }),
|
||||
title: text('title').notNull(),
|
||||
imageUrl: text('image_url'),
|
||||
createdAt: timestampText('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
body: text('body').notNull(),
|
||||
applicableUsers: jsonText<number[] | null>('applicable_users'),
|
||||
})
|
||||
|
||||
// Relations
|
||||
export const usersRelations = relations(users, ({ many, one }) => ({
|
||||
addresses: many(addresses),
|
||||
|
|
@ -579,7 +551,6 @@ export const staffUsersRelations = relations(staffUsers, ({ one, many }) => ({
|
|||
export const addressesRelations = relations(addresses, ({ one, many }) => ({
|
||||
user: one(users, { fields: [addresses.userId], references: [users.id] }),
|
||||
orders: many(orders),
|
||||
zone: one(addressZones, { fields: [addresses.zoneId], references: [addressZones.id] }),
|
||||
}))
|
||||
|
||||
export const unitsRelations = relations(units, ({}) => ({
|
||||
|
|
@ -683,8 +654,6 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
|
|||
user: one(users, { fields: [notifications.userId], references: [users.id] }),
|
||||
}))
|
||||
|
||||
export const productCategoriesRelations = relations(productCategories, ({}) => ({}))
|
||||
|
||||
export const cartItemsRelations = relations(cartItems, ({ one }) => ({
|
||||
user: one(users, { fields: [cartItems.userId], references: [users.id] }),
|
||||
sku: one(productSkus, { fields: [cartItems.skuId], references: [productSkus.id] }),
|
||||
|
|
@ -717,10 +686,6 @@ export const notifCredsRelations = relations(notifCreds, ({ one }) => ({
|
|||
user: one(users, { fields: [notifCreds.userId], references: [users.id] }),
|
||||
}))
|
||||
|
||||
export const userNotificationsRelations = relations(userNotifications, ({}) => ({
|
||||
// No relations needed for now
|
||||
}))
|
||||
|
||||
export const storeInfoRelations = relations(storeInfo, ({ one, many }) => ({
|
||||
owner: one(staffUsers, { fields: [storeInfo.owner], references: [staffUsers.id] }),
|
||||
products: many(productInfo),
|
||||
|
|
@ -746,15 +711,6 @@ export const productReviewsRelations = relations(productReviews, ({ one }) => ({
|
|||
product: one(productInfo, { fields: [productReviews.productId], references: [productInfo.id] }),
|
||||
}))
|
||||
|
||||
export const addressZonesRelations = relations(addressZones, ({ many }) => ({
|
||||
addresses: many(addresses),
|
||||
areas: many(addressAreas),
|
||||
}))
|
||||
|
||||
export const addressAreasRelations = relations(addressAreas, ({ one }) => ({
|
||||
zone: one(addressZones, { fields: [addressAreas.zoneId], references: [addressZones.id] }),
|
||||
}))
|
||||
|
||||
export const productGroupInfoRelations = relations(productGroupInfo, ({ many }) => ({
|
||||
memberships: many(productGroupMembership),
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
import type { InferSelectModel } from 'drizzle-orm'
|
||||
import type {
|
||||
users,
|
||||
addresses,
|
||||
units,
|
||||
productInfo,
|
||||
productSkus,
|
||||
skuFeatures,
|
||||
deliverySlotInfo,
|
||||
specialDeals,
|
||||
orders,
|
||||
orderItems,
|
||||
payments,
|
||||
notifications,
|
||||
productCategories,
|
||||
cartItems,
|
||||
coupons,
|
||||
} from '@/src/db/schema'
|
||||
|
||||
export type User = InferSelectModel<typeof users>
|
||||
export type Address = InferSelectModel<typeof addresses>
|
||||
export type Unit = InferSelectModel<typeof units>
|
||||
export type ProductInfo = InferSelectModel<typeof productInfo>
|
||||
export type ProductSku = InferSelectModel<typeof productSkus>
|
||||
export type SkuFeature = InferSelectModel<typeof skuFeatures>
|
||||
export type DeliverySlotInfo = InferSelectModel<typeof deliverySlotInfo>
|
||||
export type SpecialDeal = InferSelectModel<typeof specialDeals>
|
||||
export type Order = InferSelectModel<typeof orders>
|
||||
export type OrderItem = InferSelectModel<typeof orderItems>
|
||||
export type Payment = InferSelectModel<typeof payments>
|
||||
export type Notification = InferSelectModel<typeof notifications>
|
||||
export type ProductCategory = InferSelectModel<typeof productCategories>
|
||||
export type CartItem = InferSelectModel<typeof cartItems>
|
||||
export type Coupon = InferSelectModel<typeof coupons>
|
||||
|
||||
// Combined types
|
||||
export type ProductWithSkus = ProductInfo & {
|
||||
skus: (ProductSku & { features: SkuFeature[] })[]
|
||||
}
|
||||
|
||||
export type OrderWithItems = Order & {
|
||||
items: (OrderItem & { sku: ProductSku & { product: ProductInfo } })[]
|
||||
address: Address
|
||||
slot: DeliverySlotInfo
|
||||
}
|
||||
|
||||
export type CartItemWithSku = CartItem & {
|
||||
sku: ProductSku & { product: ProductInfo }
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
import { db } from '../db/db_index'
|
||||
import { homeBanners } from '../db/schema'
|
||||
import { desc, eq } from 'drizzle-orm'
|
||||
|
||||
export interface Banner {
|
||||
id: number
|
||||
name: string
|
||||
imageUrl: string
|
||||
description: string | null
|
||||
productIds: number[] | null
|
||||
redirectUrl: string | null
|
||||
serialNum: number | null
|
||||
isActive: boolean
|
||||
createdAt: Date
|
||||
lastUpdated: Date
|
||||
}
|
||||
|
||||
type BannerRow = typeof homeBanners.$inferSelect
|
||||
|
||||
export async function getBanners(): Promise<Banner[]> {
|
||||
const banners = await db.query.homeBanners.findMany({
|
||||
orderBy: desc(homeBanners.createdAt),
|
||||
}) as BannerRow[]
|
||||
|
||||
return banners.map((banner) => ({
|
||||
id: banner.id,
|
||||
name: banner.name,
|
||||
imageUrl: banner.imageUrl,
|
||||
description: banner.description,
|
||||
productIds: banner.productIds || [],
|
||||
redirectUrl: banner.redirectUrl,
|
||||
serialNum: banner.serialNum,
|
||||
isActive: banner.isActive,
|
||||
createdAt: banner.createdAt,
|
||||
lastUpdated: banner.lastUpdated,
|
||||
}))
|
||||
}
|
||||
|
||||
export async function getBannerById(id: number): Promise<Banner | null> {
|
||||
const banner = await db.query.homeBanners.findFirst({
|
||||
where: eq(homeBanners.id, id),
|
||||
})
|
||||
|
||||
if (!banner) return null
|
||||
|
||||
return {
|
||||
id: banner.id,
|
||||
name: banner.name,
|
||||
imageUrl: banner.imageUrl,
|
||||
description: banner.description,
|
||||
productIds: banner.productIds || [],
|
||||
redirectUrl: banner.redirectUrl,
|
||||
serialNum: banner.serialNum,
|
||||
isActive: banner.isActive,
|
||||
createdAt: banner.createdAt,
|
||||
lastUpdated: banner.lastUpdated,
|
||||
}
|
||||
}
|
||||
|
||||
export type CreateBannerInput = Omit<Banner, 'id' | 'createdAt' | 'lastUpdated'>
|
||||
|
||||
export async function createBanner(input: CreateBannerInput): Promise<Banner> {
|
||||
const [banner] = await db.insert(homeBanners).values({
|
||||
name: input.name,
|
||||
imageUrl: input.imageUrl,
|
||||
description: input.description,
|
||||
productIds: input.productIds,
|
||||
redirectUrl: input.redirectUrl,
|
||||
serialNum: input.serialNum,
|
||||
isActive: input.isActive,
|
||||
}).returning()
|
||||
|
||||
return {
|
||||
id: banner.id,
|
||||
name: banner.name,
|
||||
imageUrl: banner.imageUrl,
|
||||
description: banner.description,
|
||||
productIds: banner.productIds || [],
|
||||
redirectUrl: banner.redirectUrl,
|
||||
serialNum: banner.serialNum,
|
||||
isActive: banner.isActive,
|
||||
createdAt: banner.createdAt,
|
||||
lastUpdated: banner.lastUpdated,
|
||||
}
|
||||
}
|
||||
|
||||
export type UpdateBannerInput = Partial<Omit<Banner, 'id' | 'createdAt'>>
|
||||
|
||||
export async function updateBanner(id: number, input: UpdateBannerInput): Promise<Banner> {
|
||||
const [banner] = await db.update(homeBanners)
|
||||
.set({
|
||||
...input,
|
||||
lastUpdated: new Date(),
|
||||
})
|
||||
.where(eq(homeBanners.id, id))
|
||||
.returning()
|
||||
|
||||
return {
|
||||
id: banner.id,
|
||||
name: banner.name,
|
||||
imageUrl: banner.imageUrl,
|
||||
description: banner.description,
|
||||
productIds: banner.productIds || [],
|
||||
redirectUrl: banner.redirectUrl,
|
||||
serialNum: banner.serialNum,
|
||||
isActive: banner.isActive,
|
||||
createdAt: banner.createdAt,
|
||||
lastUpdated: banner.lastUpdated,
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteBanner(id: number): Promise<void> {
|
||||
await db.delete(homeBanners).where(eq(homeBanners.id, id))
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
import { db } from '../db/db_index'
|
||||
import { complaints, users } from '../db/schema'
|
||||
import { eq, desc, lt } from 'drizzle-orm'
|
||||
|
||||
export interface Complaint {
|
||||
id: number
|
||||
complaintBody: string
|
||||
userId: number
|
||||
orderId: number | null
|
||||
isResolved: boolean
|
||||
response: string | null
|
||||
createdAt: Date
|
||||
images: string[] | null
|
||||
}
|
||||
|
||||
export interface ComplaintWithUser extends Complaint {
|
||||
userName: string | null
|
||||
userMobile: string | null
|
||||
}
|
||||
|
||||
export async function getComplaints(
|
||||
cursor?: number,
|
||||
limit: number = 20
|
||||
): Promise<{ complaints: ComplaintWithUser[]; hasMore: boolean }> {
|
||||
const whereCondition = cursor ? lt(complaints.id, cursor) : undefined
|
||||
|
||||
const complaintsData = await db
|
||||
.select({
|
||||
id: complaints.id,
|
||||
complaintBody: complaints.complaintBody,
|
||||
userId: complaints.userId,
|
||||
orderId: complaints.orderId,
|
||||
isResolved: complaints.isResolved,
|
||||
response: complaints.response,
|
||||
createdAt: complaints.createdAt,
|
||||
images: complaints.images,
|
||||
userName: users.name,
|
||||
userMobile: users.mobile,
|
||||
})
|
||||
.from(complaints)
|
||||
.leftJoin(users, eq(complaints.userId, users.id))
|
||||
.where(whereCondition)
|
||||
.orderBy(desc(complaints.id))
|
||||
.limit(limit + 1)
|
||||
|
||||
const hasMore = complaintsData.length > limit
|
||||
const complaintsToReturn = hasMore ? complaintsData.slice(0, limit) : complaintsData
|
||||
|
||||
return {
|
||||
complaints: complaintsToReturn.map((c) => ({
|
||||
id: c.id,
|
||||
complaintBody: c.complaintBody,
|
||||
userId: c.userId,
|
||||
orderId: c.orderId,
|
||||
isResolved: c.isResolved,
|
||||
response: c.response,
|
||||
createdAt: c.createdAt,
|
||||
images: c.images,
|
||||
userName: c.userName,
|
||||
userMobile: c.userMobile,
|
||||
})),
|
||||
hasMore,
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveComplaint(
|
||||
id: number,
|
||||
response?: string
|
||||
): Promise<void> {
|
||||
await db
|
||||
.update(complaints)
|
||||
.set({ isResolved: true, response })
|
||||
.where(eq(complaints.id, id))
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
import { db } from '../db/db_index'
|
||||
import { keyValStore } from '../db/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { castConstValue } from '../lib/const-keys'
|
||||
|
||||
export interface Constant {
|
||||
key: string
|
||||
value: any
|
||||
}
|
||||
|
||||
export async function getAllConstants(): Promise<Constant[]> {
|
||||
const constants = await db.select().from(keyValStore)
|
||||
|
||||
return constants.map(c => ({
|
||||
key: c.key,
|
||||
value: castConstValue(c.key, c.value),
|
||||
}))
|
||||
}
|
||||
|
||||
export async function upsertConstants(constants: Constant[]): Promise<void> {
|
||||
await db.transaction(async (tx) => {
|
||||
for (const { key, value } of constants) {
|
||||
const castedValue = castConstValue(key, value)
|
||||
const existing = await tx.query.keyValStore.findFirst({
|
||||
where: eq(keyValStore.key, key),
|
||||
columns: { key: true },
|
||||
})
|
||||
|
||||
if (existing) {
|
||||
await tx.update(keyValStore)
|
||||
.set({ value: castedValue })
|
||||
.where(eq(keyValStore.key, key))
|
||||
} else {
|
||||
await tx.insert(keyValStore)
|
||||
.values({ key, value: castedValue })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -1,632 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { coupons, reservedCoupons, users } from '../db/schema';
|
||||
import { eq, and, like, or, inArray, lt, desc, asc } from 'drizzle-orm';
|
||||
|
||||
export interface Coupon {
|
||||
id: number;
|
||||
couponCode: string;
|
||||
isUserBased: boolean;
|
||||
discountPercent: string | null;
|
||||
flatDiscount: string | null;
|
||||
minOrder: string | null;
|
||||
skuIds: number[] | null;
|
||||
maxValue: string | null;
|
||||
isApplyForAll: boolean;
|
||||
validTill: Date | null;
|
||||
maxLimitForUser: number | null;
|
||||
exclusiveApply: boolean;
|
||||
isInvalidated: boolean;
|
||||
createdAt: Date;
|
||||
createdBy: number;
|
||||
}
|
||||
|
||||
export async function getAllCoupons(
|
||||
cursor?: number,
|
||||
limit: number = 50,
|
||||
search?: string
|
||||
): Promise<{ coupons: any[]; hasMore: boolean }> {
|
||||
let whereCondition = undefined;
|
||||
const conditions = [];
|
||||
|
||||
if (cursor) {
|
||||
conditions.push(lt(coupons.id, cursor));
|
||||
}
|
||||
|
||||
if (search && search.trim()) {
|
||||
conditions.push(like(coupons.couponCode, `%${search}%`));
|
||||
}
|
||||
|
||||
if (conditions.length > 0) {
|
||||
whereCondition = and(...conditions);
|
||||
}
|
||||
|
||||
const result = await db.query.coupons.findMany({
|
||||
where: whereCondition,
|
||||
with: {
|
||||
creator: true,
|
||||
applicableUsers: {
|
||||
with: {
|
||||
user: true,
|
||||
},
|
||||
},
|
||||
applicableProducts: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: (couponsTable: typeof coupons) => [desc(couponsTable.createdAt)],
|
||||
limit: limit + 1,
|
||||
});
|
||||
|
||||
const hasMore = result.length > limit;
|
||||
const couponsList = hasMore ? result.slice(0, limit) : result;
|
||||
|
||||
return { coupons: couponsList, hasMore };
|
||||
}
|
||||
|
||||
export async function getCouponById(id: number): Promise<any | null> {
|
||||
const result = await db.query.coupons.findFirst({
|
||||
where: eq(coupons.id, id),
|
||||
with: {
|
||||
creator: true,
|
||||
applicableUsers: {
|
||||
with: {
|
||||
user: true,
|
||||
},
|
||||
},
|
||||
applicableProducts: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return result || null;
|
||||
}
|
||||
|
||||
export async function invalidateCoupon(id: number): Promise<Coupon> {
|
||||
const result = await db.update(coupons)
|
||||
.set({ isInvalidated: true })
|
||||
.where(eq(coupons.id, id))
|
||||
.returning();
|
||||
|
||||
return result[0];
|
||||
}
|
||||
|
||||
export interface CouponValidationResult {
|
||||
valid: boolean;
|
||||
message?: string;
|
||||
discountAmount?: number;
|
||||
coupon?: Partial<Coupon>;
|
||||
}
|
||||
|
||||
export async function validateCoupon(
|
||||
code: string,
|
||||
userId: number,
|
||||
orderAmount: number
|
||||
): Promise<CouponValidationResult> {
|
||||
const coupon = await db.query.coupons.findFirst({
|
||||
where: and(
|
||||
eq(coupons.couponCode, code.toUpperCase()),
|
||||
eq(coupons.isInvalidated, false)
|
||||
),
|
||||
});
|
||||
|
||||
if (!coupon) {
|
||||
return { valid: false, message: "Coupon not found or invalidated" };
|
||||
}
|
||||
|
||||
// Check expiry date
|
||||
if (coupon.validTill && new Date(coupon.validTill) < new Date()) {
|
||||
return { valid: false, message: "Coupon has expired" };
|
||||
}
|
||||
|
||||
// Check if coupon applies to all users or specific user
|
||||
if (!coupon.isApplyForAll && !coupon.isUserBased) {
|
||||
return { valid: false, message: "Coupon is not available for use" };
|
||||
}
|
||||
|
||||
// Check minimum order amount
|
||||
const minOrderValue = coupon.minOrder ? parseFloat(coupon.minOrder) : 0;
|
||||
if (minOrderValue > 0 && orderAmount < minOrderValue) {
|
||||
return { valid: false, message: `Minimum order amount is ${minOrderValue}` };
|
||||
}
|
||||
|
||||
// Calculate discount
|
||||
let discountAmount = 0;
|
||||
if (coupon.discountPercent) {
|
||||
const percent = parseFloat(coupon.discountPercent);
|
||||
discountAmount = (orderAmount * percent) / 100;
|
||||
} else if (coupon.flatDiscount) {
|
||||
discountAmount = parseFloat(coupon.flatDiscount);
|
||||
}
|
||||
|
||||
// Apply max value limit
|
||||
const maxValueLimit = coupon.maxValue ? parseFloat(coupon.maxValue) : 0;
|
||||
if (maxValueLimit > 0 && discountAmount > maxValueLimit) {
|
||||
discountAmount = maxValueLimit;
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
discountAmount,
|
||||
coupon: {
|
||||
id: coupon.id,
|
||||
discountPercent: coupon.discountPercent,
|
||||
flatDiscount: coupon.flatDiscount,
|
||||
maxValue: coupon.maxValue,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function getReservedCoupons(
|
||||
cursor?: number,
|
||||
limit: number = 50,
|
||||
search?: string
|
||||
): Promise<{ coupons: any[]; hasMore: boolean }> {
|
||||
let whereCondition = undefined;
|
||||
const conditions = [];
|
||||
|
||||
if (cursor) {
|
||||
conditions.push(lt(reservedCoupons.id, cursor));
|
||||
}
|
||||
|
||||
if (search && search.trim()) {
|
||||
conditions.push(or(
|
||||
like(reservedCoupons.secretCode, `%${search}%`),
|
||||
like(reservedCoupons.couponCode, `%${search}%`)
|
||||
));
|
||||
}
|
||||
|
||||
if (conditions.length > 0) {
|
||||
whereCondition = and(...conditions);
|
||||
}
|
||||
|
||||
const result = await db.query.reservedCoupons.findMany({
|
||||
where: whereCondition,
|
||||
with: {
|
||||
redeemedUser: true,
|
||||
creator: true,
|
||||
},
|
||||
orderBy: (reservedCouponsTable: typeof reservedCoupons) => [desc(reservedCouponsTable.createdAt)],
|
||||
limit: limit + 1,
|
||||
});
|
||||
|
||||
const hasMore = result.length > limit;
|
||||
const couponsList = hasMore ? result.slice(0, limit) : result;
|
||||
|
||||
return { coupons: couponsList, hasMore };
|
||||
}
|
||||
|
||||
export interface UserMiniInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
mobile: string | null;
|
||||
}
|
||||
|
||||
export async function getUsersForCoupon(
|
||||
search?: string,
|
||||
limit: number = 20,
|
||||
offset: number = 0
|
||||
): Promise<{ users: UserMiniInfo[] }> {
|
||||
let whereCondition = undefined;
|
||||
if (search && search.trim()) {
|
||||
whereCondition = or(
|
||||
like(users.name, `%${search}%`),
|
||||
like(users.mobile, `%${search}%`)
|
||||
);
|
||||
}
|
||||
|
||||
const userList = await db.query.users.findMany({
|
||||
where: whereCondition,
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
mobile: true,
|
||||
},
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: (usersTable: typeof users) => [asc(usersTable.name)],
|
||||
});
|
||||
|
||||
return {
|
||||
users: userList.map((user: typeof users.$inferSelect) => ({
|
||||
id: user.id,
|
||||
name: user.name || 'Unknown',
|
||||
mobile: user.mobile,
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// BATCH 2: Transaction Methods
|
||||
// ============================================================================
|
||||
|
||||
import { couponApplicableUsers, couponApplicableProducts, orders, orderStatus } from '../db/schema';
|
||||
|
||||
export interface CreateCouponInput {
|
||||
couponCode: string;
|
||||
isUserBased: boolean;
|
||||
discountPercent?: string;
|
||||
flatDiscount?: string;
|
||||
minOrder?: string;
|
||||
skuIds?: number[] | null;
|
||||
maxValue?: string;
|
||||
isApplyForAll: boolean;
|
||||
validTill?: Date;
|
||||
maxLimitForUser?: number;
|
||||
exclusiveApply: boolean;
|
||||
createdBy: number;
|
||||
}
|
||||
|
||||
export async function createCouponWithRelations(
|
||||
input: CreateCouponInput,
|
||||
applicableUsers?: number[],
|
||||
applicableProducts?: number[]
|
||||
): Promise<Coupon> {
|
||||
return await db.transaction(async (tx) => {
|
||||
// Create the coupon
|
||||
const [coupon] = await tx.insert(coupons).values({
|
||||
couponCode: input.couponCode,
|
||||
isUserBased: input.isUserBased,
|
||||
discountPercent: input.discountPercent,
|
||||
flatDiscount: input.flatDiscount,
|
||||
minOrder: input.minOrder,
|
||||
skuIds: input.skuIds,
|
||||
createdBy: input.createdBy,
|
||||
maxValue: input.maxValue,
|
||||
isApplyForAll: input.isApplyForAll,
|
||||
validTill: input.validTill,
|
||||
maxLimitForUser: input.maxLimitForUser,
|
||||
exclusiveApply: input.exclusiveApply,
|
||||
}).returning();
|
||||
|
||||
// Insert applicable users
|
||||
if (applicableUsers && applicableUsers.length > 0) {
|
||||
await tx.insert(couponApplicableUsers).values(
|
||||
applicableUsers.map(userId => ({
|
||||
couponId: coupon.id,
|
||||
userId,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// Insert applicable products
|
||||
if (applicableProducts && applicableProducts.length > 0) {
|
||||
await tx.insert(couponApplicableProducts).values(
|
||||
applicableProducts.map(skuId => ({
|
||||
couponId: coupon.id,
|
||||
skuId,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: coupon.id,
|
||||
couponCode: coupon.couponCode,
|
||||
isUserBased: coupon.isUserBased,
|
||||
discountPercent: coupon.discountPercent,
|
||||
flatDiscount: coupon.flatDiscount,
|
||||
minOrder: coupon.minOrder,
|
||||
skuIds: coupon.skuIds,
|
||||
maxValue: coupon.maxValue,
|
||||
isApplyForAll: coupon.isApplyForAll,
|
||||
validTill: coupon.validTill,
|
||||
maxLimitForUser: coupon.maxLimitForUser,
|
||||
exclusiveApply: coupon.exclusiveApply,
|
||||
isInvalidated: coupon.isInvalidated,
|
||||
createdAt: coupon.createdAt,
|
||||
createdBy: coupon.createdBy,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export interface UpdateCouponInput {
|
||||
couponCode?: string;
|
||||
isUserBased?: boolean;
|
||||
discountPercent?: string;
|
||||
flatDiscount?: string;
|
||||
minOrder?: string;
|
||||
skuIds?: number[] | null;
|
||||
maxValue?: string;
|
||||
isApplyForAll?: boolean;
|
||||
validTill?: Date | null;
|
||||
maxLimitForUser?: number;
|
||||
exclusiveApply?: boolean;
|
||||
isInvalidated?: boolean;
|
||||
}
|
||||
|
||||
export async function updateCouponWithRelations(
|
||||
id: number,
|
||||
input: UpdateCouponInput,
|
||||
applicableUsers?: number[],
|
||||
applicableProducts?: number[]
|
||||
): Promise<Coupon> {
|
||||
return await db.transaction(async (tx) => {
|
||||
// Update the coupon
|
||||
const [coupon] = await tx.update(coupons)
|
||||
.set({
|
||||
...input,
|
||||
})
|
||||
.where(eq(coupons.id, id))
|
||||
.returning();
|
||||
|
||||
// Update applicable users: delete existing and insert new
|
||||
if (applicableUsers !== undefined) {
|
||||
await tx.delete(couponApplicableUsers).where(eq(couponApplicableUsers.couponId, id));
|
||||
if (applicableUsers.length > 0) {
|
||||
await tx.insert(couponApplicableUsers).values(
|
||||
applicableUsers.map(userId => ({
|
||||
couponId: id,
|
||||
userId,
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Update applicable products: delete existing and insert new
|
||||
if (applicableProducts !== undefined) {
|
||||
await tx.delete(couponApplicableProducts).where(eq(couponApplicableProducts.couponId, id));
|
||||
if (applicableProducts.length > 0) {
|
||||
await tx.insert(couponApplicableProducts).values(
|
||||
applicableProducts.map(skuId => ({
|
||||
couponId: id,
|
||||
skuId,
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: coupon.id,
|
||||
couponCode: coupon.couponCode,
|
||||
isUserBased: coupon.isUserBased,
|
||||
discountPercent: coupon.discountPercent,
|
||||
flatDiscount: coupon.flatDiscount,
|
||||
minOrder: coupon.minOrder,
|
||||
skuIds: coupon.skuIds,
|
||||
maxValue: coupon.maxValue,
|
||||
isApplyForAll: coupon.isApplyForAll,
|
||||
validTill: coupon.validTill,
|
||||
maxLimitForUser: coupon.maxLimitForUser,
|
||||
exclusiveApply: coupon.exclusiveApply,
|
||||
isInvalidated: coupon.isInvalidated,
|
||||
createdAt: coupon.createdAt,
|
||||
createdBy: coupon.createdBy,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function generateCancellationCoupon(
|
||||
orderId: number,
|
||||
staffUserId: number,
|
||||
userId: number,
|
||||
orderAmount: number,
|
||||
couponCode: string
|
||||
): Promise<Coupon> {
|
||||
return await db.transaction(async (tx) => {
|
||||
// Calculate expiry date (30 days from now)
|
||||
const expiryDate = new Date();
|
||||
expiryDate.setDate(expiryDate.getDate() + 30);
|
||||
|
||||
// Create the coupon
|
||||
const [coupon] = await tx.insert(coupons).values({
|
||||
couponCode,
|
||||
isUserBased: true,
|
||||
flatDiscount: orderAmount.toString(),
|
||||
minOrder: orderAmount.toString(),
|
||||
maxValue: orderAmount.toString(),
|
||||
validTill: expiryDate,
|
||||
maxLimitForUser: 1,
|
||||
createdBy: staffUserId,
|
||||
isApplyForAll: false,
|
||||
}).returning();
|
||||
|
||||
// Insert applicable users
|
||||
await tx.insert(couponApplicableUsers).values({
|
||||
couponId: coupon.id,
|
||||
userId,
|
||||
});
|
||||
|
||||
// Update order_status with refund coupon ID
|
||||
await tx.update(orderStatus)
|
||||
.set({ refundCouponId: coupon.id })
|
||||
.where(eq(orderStatus.orderId, orderId));
|
||||
|
||||
return {
|
||||
id: coupon.id,
|
||||
couponCode: coupon.couponCode,
|
||||
isUserBased: coupon.isUserBased,
|
||||
discountPercent: coupon.discountPercent,
|
||||
flatDiscount: coupon.flatDiscount,
|
||||
minOrder: coupon.minOrder,
|
||||
skuIds: coupon.skuIds,
|
||||
maxValue: coupon.maxValue,
|
||||
isApplyForAll: coupon.isApplyForAll,
|
||||
validTill: coupon.validTill,
|
||||
maxLimitForUser: coupon.maxLimitForUser,
|
||||
exclusiveApply: coupon.exclusiveApply,
|
||||
isInvalidated: coupon.isInvalidated,
|
||||
createdAt: coupon.createdAt,
|
||||
createdBy: coupon.createdBy,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export interface CreateReservedCouponInput {
|
||||
secretCode: string;
|
||||
couponCode: string;
|
||||
discountPercent?: string;
|
||||
flatDiscount?: string;
|
||||
minOrder?: string;
|
||||
skuIds?: number[] | null;
|
||||
maxValue?: string;
|
||||
validTill?: Date;
|
||||
maxLimitForUser?: number;
|
||||
exclusiveApply: boolean;
|
||||
createdBy: number;
|
||||
}
|
||||
|
||||
export async function createReservedCouponWithProducts(
|
||||
input: CreateReservedCouponInput,
|
||||
applicableProducts?: number[]
|
||||
): Promise<any> {
|
||||
return await db.transaction(async (tx) => {
|
||||
const [coupon] = await tx.insert(reservedCoupons).values({
|
||||
secretCode: input.secretCode,
|
||||
couponCode: input.couponCode,
|
||||
discountPercent: input.discountPercent,
|
||||
flatDiscount: input.flatDiscount,
|
||||
minOrder: input.minOrder,
|
||||
skuIds: input.skuIds,
|
||||
maxValue: input.maxValue,
|
||||
validTill: input.validTill,
|
||||
maxLimitForUser: input.maxLimitForUser,
|
||||
exclusiveApply: input.exclusiveApply,
|
||||
createdBy: input.createdBy,
|
||||
}).returning();
|
||||
|
||||
// Insert applicable products if provided
|
||||
if (applicableProducts && applicableProducts.length > 0) {
|
||||
await tx.insert(couponApplicableProducts).values(
|
||||
applicableProducts.map(skuId => ({
|
||||
couponId: coupon.id,
|
||||
skuId,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
return coupon;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getOrCreateUserByMobile(
|
||||
mobile: string
|
||||
): Promise<{ id: number; mobile: string; name: string | null }> {
|
||||
return await db.transaction(async (tx) => {
|
||||
// Check if user exists
|
||||
let user = await tx.query.users.findFirst({
|
||||
where: eq(users.mobile, mobile),
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
// Create new user
|
||||
const [newUser] = await tx.insert(users).values({
|
||||
name: null,
|
||||
email: null,
|
||||
mobile,
|
||||
}).returning();
|
||||
user = newUser;
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
mobile: user.mobile,
|
||||
name: user.name,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function createCouponForUser(
|
||||
mobile: string,
|
||||
couponCode: string,
|
||||
staffUserId: number
|
||||
): Promise<{ coupon: Coupon; user: { id: number; mobile: string; name: string | null } }> {
|
||||
return await db.transaction(async (tx) => {
|
||||
// Get or create user
|
||||
let user = await tx.query.users.findFirst({
|
||||
where: eq(users.mobile, mobile),
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
const [newUser] = await tx.insert(users).values({
|
||||
name: null,
|
||||
email: null,
|
||||
mobile,
|
||||
}).returning();
|
||||
user = newUser;
|
||||
}
|
||||
|
||||
// Create the coupon
|
||||
const [coupon] = await tx.insert(coupons).values({
|
||||
couponCode,
|
||||
isUserBased: true,
|
||||
discountPercent: "20",
|
||||
minOrder: "1000",
|
||||
maxValue: "500",
|
||||
maxLimitForUser: 1,
|
||||
isApplyForAll: false,
|
||||
exclusiveApply: false,
|
||||
createdBy: staffUserId,
|
||||
validTill: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000), // 90 days from now
|
||||
}).returning();
|
||||
|
||||
// Associate coupon with user
|
||||
await tx.insert(couponApplicableUsers).values({
|
||||
couponId: coupon.id,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
return {
|
||||
coupon: {
|
||||
id: coupon.id,
|
||||
couponCode: coupon.couponCode,
|
||||
isUserBased: coupon.isUserBased,
|
||||
discountPercent: coupon.discountPercent,
|
||||
flatDiscount: coupon.flatDiscount,
|
||||
minOrder: coupon.minOrder,
|
||||
skuIds: coupon.skuIds,
|
||||
maxValue: coupon.maxValue,
|
||||
isApplyForAll: coupon.isApplyForAll,
|
||||
validTill: coupon.validTill,
|
||||
maxLimitForUser: coupon.maxLimitForUser,
|
||||
exclusiveApply: coupon.exclusiveApply,
|
||||
isInvalidated: coupon.isInvalidated,
|
||||
createdAt: coupon.createdAt,
|
||||
createdBy: coupon.createdBy,
|
||||
},
|
||||
user: {
|
||||
id: user.id,
|
||||
mobile: user.mobile,
|
||||
name: user.name,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Utility Functions
|
||||
// ============================================================================
|
||||
|
||||
export async function checkUsersExist(userIds: number[]): Promise<boolean> {
|
||||
const existingUsers = await db.query.users.findMany({
|
||||
where: inArray(users.id, userIds),
|
||||
columns: { id: true },
|
||||
});
|
||||
return existingUsers.length === userIds.length;
|
||||
}
|
||||
|
||||
export async function checkCouponExists(couponCode: string): Promise<boolean> {
|
||||
const existing = await db.query.coupons.findFirst({
|
||||
where: eq(coupons.couponCode, couponCode),
|
||||
});
|
||||
return !!existing;
|
||||
}
|
||||
|
||||
export async function checkReservedCouponExists(secretCode: string): Promise<boolean> {
|
||||
const existing = await db.query.reservedCoupons.findFirst({
|
||||
where: eq(reservedCoupons.secretCode, secretCode),
|
||||
});
|
||||
return !!existing;
|
||||
}
|
||||
|
||||
export async function getOrderWithUser(orderId: number): Promise<any | null> {
|
||||
return await db.query.orders.findFirst({
|
||||
where: eq(orders.id, orderId),
|
||||
with: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -1,269 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { orders, orderItems, orderStatus, users, addresses, refunds, complaints, payments } from '../db/schema';
|
||||
import { eq, and, gte, lt, desc, inArray, sql } from 'drizzle-orm';
|
||||
|
||||
export async function updateOrderNotes(orderId: number, adminNotes: string | null): Promise<any> {
|
||||
const [result] = await db
|
||||
.update(orders)
|
||||
.set({ adminNotes })
|
||||
.where(eq(orders.id, orderId))
|
||||
.returning();
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getOrderWithDetails(orderId: number): Promise<any | null> {
|
||||
return await db.query.orders.findFirst({
|
||||
where: eq(orders.id, orderId),
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: {
|
||||
with: {
|
||||
unit: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
user: true,
|
||||
address: true,
|
||||
orderStatus: true,
|
||||
slot: true,
|
||||
payments: true,
|
||||
refunds: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getFullOrder(orderId: number): Promise<any | null> {
|
||||
return await db.query.orders.findFirst({
|
||||
where: eq(orders.id, orderId),
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: {
|
||||
with: {
|
||||
unit: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
user: {
|
||||
with: {
|
||||
userDetails: true,
|
||||
},
|
||||
},
|
||||
address: true,
|
||||
orderStatus: true,
|
||||
slot: true,
|
||||
payments: true,
|
||||
refunds: true,
|
||||
complaints: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getOrderDetails(orderId: number): Promise<any | null> {
|
||||
return await db.query.orders.findFirst({
|
||||
where: eq(orders.id, orderId),
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: {
|
||||
with: {
|
||||
unit: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
user: true,
|
||||
address: true,
|
||||
orderStatus: true,
|
||||
slot: true,
|
||||
payments: true,
|
||||
refunds: true,
|
||||
complaints: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAllOrders(
|
||||
limit: number,
|
||||
cursor?: number,
|
||||
slotId?: number | null,
|
||||
filters?: any
|
||||
): Promise<{ orders: any[]; hasMore: boolean }> {
|
||||
let whereConditions = [];
|
||||
|
||||
if (cursor) {
|
||||
whereConditions.push(lt(orders.id, cursor));
|
||||
}
|
||||
|
||||
if (slotId) {
|
||||
whereConditions.push(eq(orders.slotId, slotId));
|
||||
}
|
||||
|
||||
// Add filter conditions
|
||||
if (filters) {
|
||||
if (filters.packagedFilter === 'packaged') {
|
||||
whereConditions.push(
|
||||
sql`${orders.id} IN (SELECT ${orderStatus.orderId} FROM ${orderStatus} WHERE ${orderStatus.isPackaged} = 1)`
|
||||
);
|
||||
} else if (filters.packagedFilter === 'not_packaged') {
|
||||
whereConditions.push(
|
||||
sql`${orders.id} IN (SELECT ${orderStatus.orderId} FROM ${orderStatus} WHERE ${orderStatus.isPackaged} = 0)`
|
||||
);
|
||||
}
|
||||
|
||||
if (filters.deliveredFilter === 'delivered') {
|
||||
whereConditions.push(
|
||||
sql`${orders.id} IN (SELECT ${orderStatus.orderId} FROM ${orderStatus} WHERE ${orderStatus.isDelivered} = 1)`
|
||||
);
|
||||
} else if (filters.deliveredFilter === 'not_delivered') {
|
||||
whereConditions.push(
|
||||
sql`${orders.id} IN (SELECT ${orderStatus.orderId} FROM ${orderStatus} WHERE ${orderStatus.isDelivered} = 0)`
|
||||
);
|
||||
}
|
||||
|
||||
if (filters.flashDeliveryFilter === 'flash') {
|
||||
whereConditions.push(eq(orders.isFlashDelivery, true));
|
||||
} else if (filters.flashDeliveryFilter === 'regular') {
|
||||
whereConditions.push(eq(orders.isFlashDelivery, false));
|
||||
}
|
||||
}
|
||||
|
||||
const ordersList = await db.query.orders.findMany({
|
||||
where: whereConditions.length > 0 ? and(...whereConditions) : undefined,
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
user: true,
|
||||
orderStatus: true,
|
||||
slot: true,
|
||||
},
|
||||
orderBy: desc(orders.id),
|
||||
limit: limit + 1,
|
||||
});
|
||||
|
||||
const hasMore = ordersList.length > limit;
|
||||
return { orders: hasMore ? ordersList.slice(0, limit) : ordersList, hasMore };
|
||||
}
|
||||
|
||||
export async function getOrdersBySlotId(slotId: number): Promise<any[]> {
|
||||
return await db.query.orders.findMany({
|
||||
where: eq(orders.slotId, slotId),
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
user: true,
|
||||
orderStatus: true,
|
||||
address: true,
|
||||
},
|
||||
orderBy: desc(orders.createdAt),
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateOrderPackaged(orderId: number, isPackaged: boolean): Promise<any> {
|
||||
const [result] = await db
|
||||
.update(orderStatus)
|
||||
.set({ isPackaged })
|
||||
.where(eq(orderStatus.orderId, orderId))
|
||||
.returning();
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateOrderDelivered(orderId: number, isDelivered: boolean): Promise<any> {
|
||||
const [result] = await db
|
||||
.update(orderStatus)
|
||||
.set({ isDelivered })
|
||||
.where(eq(orderStatus.orderId, orderId))
|
||||
.returning();
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateOrderItemPackaging(
|
||||
orderItemId: number,
|
||||
isPackaged: boolean,
|
||||
isPackageVerified: boolean
|
||||
): Promise<void> {
|
||||
await db.update(orderItems)
|
||||
.set({ is_packaged: isPackaged, is_package_verified: isPackageVerified })
|
||||
.where(eq(orderItems.id, orderItemId));
|
||||
}
|
||||
|
||||
export async function updateAddressCoords(addressId: number, lat: number, lng: number): Promise<void> {
|
||||
await db.update(addresses)
|
||||
.set({ adminLatitude: lat, adminLongitude: lng })
|
||||
.where(eq(addresses.id, addressId));
|
||||
}
|
||||
|
||||
export async function getOrderStatus(orderId: number): Promise<any | null> {
|
||||
return await db.query.orderStatus.findFirst({
|
||||
where: eq(orderStatus.orderId, orderId),
|
||||
});
|
||||
}
|
||||
|
||||
export async function cancelOrder(orderId: number, reason: string): Promise<any> {
|
||||
return await db.transaction(async (tx) => {
|
||||
const order = await tx.query.orders.findFirst({
|
||||
where: eq(orders.id, orderId),
|
||||
})
|
||||
|
||||
if (!order) {
|
||||
return null
|
||||
}
|
||||
|
||||
await tx.update(orderStatus)
|
||||
.set({
|
||||
isCancelled: true,
|
||||
cancelReason: reason,
|
||||
})
|
||||
.where(eq(orderStatus.orderId, orderId))
|
||||
|
||||
return order
|
||||
});
|
||||
}
|
||||
|
||||
export async function getTodaysOrders(slotId?: number): Promise<any[]> {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
|
||||
let whereConditions = [
|
||||
gte(orders.createdAt, today),
|
||||
lt(orders.createdAt, tomorrow),
|
||||
];
|
||||
|
||||
if (slotId) {
|
||||
whereConditions.push(eq(orders.slotId, slotId));
|
||||
}
|
||||
|
||||
return await db.query.orders.findMany({
|
||||
where: and(...whereConditions),
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
user: true,
|
||||
orderStatus: true,
|
||||
},
|
||||
orderBy: desc(orders.createdAt),
|
||||
});
|
||||
}
|
||||
|
||||
export async function removeDeliveryCharge(orderId: number): Promise<any> {
|
||||
const [result] = await db
|
||||
.update(orders)
|
||||
.set({ deliveryCharge: '0' })
|
||||
.where(eq(orders.id, orderId))
|
||||
.returning();
|
||||
return result;
|
||||
}
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { productInfo, units, specialDeals, productTags, productReviews, productGroupInfo, productGroupMembership } from '../db/schema';
|
||||
import { eq, and, inArray, desc, sql, asc } from 'drizzle-orm';
|
||||
|
||||
export async function getAllProducts(): Promise<any[]> {
|
||||
return await db.query.productInfo.findMany({
|
||||
orderBy: productInfo.name,
|
||||
with: {
|
||||
unit: true,
|
||||
store: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getProductById(id: number): Promise<any | null> {
|
||||
return await db.query.productInfo.findFirst({
|
||||
where: eq(productInfo.id, id),
|
||||
with: {
|
||||
unit: true,
|
||||
store: true,
|
||||
specialDeals: true,
|
||||
productTags: {
|
||||
with: {
|
||||
tag: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function createProduct(input: any): Promise<any> {
|
||||
const [product] = await db.insert(productInfo).values(input).returning();
|
||||
return product;
|
||||
}
|
||||
|
||||
export async function updateProduct(id: number, updates: any): Promise<any> {
|
||||
const [product] = await db.update(productInfo)
|
||||
.set(updates)
|
||||
.where(eq(productInfo.id, id))
|
||||
.returning();
|
||||
return product;
|
||||
}
|
||||
|
||||
export async function toggleProductOutOfStock(id: number, isOutOfStock: boolean): Promise<any> {
|
||||
const [product] = await db.update(productInfo)
|
||||
.set({ isOutOfStock })
|
||||
.where(eq(productInfo.id, id))
|
||||
.returning();
|
||||
return product;
|
||||
}
|
||||
|
||||
export async function getAllUnits(): Promise<any[]> {
|
||||
return await db.query.units.findMany({
|
||||
orderBy: units.shortNotation,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAllProductTags(): Promise<any[]> {
|
||||
return await db.query.productTags.findMany({
|
||||
with: {
|
||||
products: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getProductReviews(productId: number): Promise<any[]> {
|
||||
return await db.query.productReviews.findMany({
|
||||
where: eq(productReviews.productId, productId),
|
||||
with: {
|
||||
user: true,
|
||||
},
|
||||
orderBy: desc(productReviews.reviewTime),
|
||||
});
|
||||
}
|
||||
|
||||
export async function respondToReview(reviewId: number, adminResponse: string): Promise<void> {
|
||||
await db.update(productReviews)
|
||||
.set({ adminResponse })
|
||||
.where(eq(productReviews.id, reviewId));
|
||||
}
|
||||
|
||||
export async function getAllProductGroups(): Promise<any[]> {
|
||||
return await db.query.productGroupInfo.findMany({
|
||||
with: {
|
||||
products: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function createProductGroup(name: string): Promise<any> {
|
||||
const [group] = await db.insert(productGroupInfo).values({ groupName: name }).returning();
|
||||
return group;
|
||||
}
|
||||
|
||||
export async function updateProductGroup(id: number, name: string): Promise<any> {
|
||||
const [group] = await db.update(productGroupInfo)
|
||||
.set({ groupName: name })
|
||||
.where(eq(productGroupInfo.id, id))
|
||||
.returning();
|
||||
return group;
|
||||
}
|
||||
|
||||
export async function deleteProductGroup(id: number): Promise<void> {
|
||||
await db.delete(productGroupInfo).where(eq(productGroupInfo.id, id));
|
||||
}
|
||||
|
||||
export async function addProductToGroup(groupId: number, productId: number): Promise<void> {
|
||||
await db.insert(productGroupMembership).values({ groupId, productId });
|
||||
}
|
||||
|
||||
export async function removeProductFromGroup(groupId: number, productId: number): Promise<void> {
|
||||
await db.delete(productGroupMembership)
|
||||
.where(and(
|
||||
eq(productGroupMembership.groupId, groupId),
|
||||
eq(productGroupMembership.productId, productId)
|
||||
));
|
||||
}
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { deliverySlotInfo, productInfo, vendorSnippets } from '../db/schema';
|
||||
import { eq, and, inArray, desc } from 'drizzle-orm';
|
||||
|
||||
export async function getAllSlots(): Promise<any[]> {
|
||||
const slots = await db.query.deliverySlotInfo.findMany({
|
||||
orderBy: desc(deliverySlotInfo.deliveryTime),
|
||||
with: {
|
||||
vendorSnippets: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Fetch products for all slots
|
||||
const allProductIds = new Set<number>();
|
||||
for (const slot of slots) {
|
||||
for (const productId of (slot.productIds || [])) {
|
||||
allProductIds.add(productId);
|
||||
}
|
||||
}
|
||||
|
||||
const productIdsArray = Array.from(allProductIds);
|
||||
const productsData = productIdsArray.length > 0
|
||||
? await db.query.productInfo.findMany({
|
||||
where: inArray(productInfo.id, productIdsArray),
|
||||
})
|
||||
: [];
|
||||
|
||||
const productMap = new Map(productsData.map(p => [p.id, p]));
|
||||
|
||||
return slots.map(slot => ({
|
||||
...slot,
|
||||
products: (slot.productIds || [])
|
||||
.map(productId => productMap.get(productId))
|
||||
.filter((p): p is NonNullable<typeof p> => p != null),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getSlotById(id: number): Promise<any | null> {
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, id),
|
||||
with: {
|
||||
vendorSnippets: {
|
||||
with: {
|
||||
slot: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!slot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fetch products for this slot
|
||||
const productIds = slot.productIds || [];
|
||||
const productIdSet = new Set(productIds)
|
||||
// const productsData = productIds.length > 0
|
||||
// ? await db.query.productInfo.findMany({
|
||||
// where: inArray(productInfo.id, productIds),
|
||||
// })
|
||||
// : [];
|
||||
|
||||
let productsData = productIds.length > 0 ? await db.query.productInfo.findMany({}) : [];
|
||||
productsData = productsData.filter(item => productIdSet.has(item.id))
|
||||
|
||||
return {
|
||||
...slot,
|
||||
products: productsData,
|
||||
};
|
||||
}
|
||||
|
||||
export async function createSlot(input: any): Promise<any> {
|
||||
const [slot] = await db.insert(deliverySlotInfo).values(input).returning();
|
||||
return slot;
|
||||
}
|
||||
|
||||
export async function updateSlot(id: number, updates: any): Promise<any> {
|
||||
const [slot] = await db.update(deliverySlotInfo)
|
||||
.set(updates)
|
||||
.where(eq(deliverySlotInfo.id, id))
|
||||
.returning();
|
||||
return slot;
|
||||
}
|
||||
|
||||
export async function deleteSlot(id: number): Promise<void> {
|
||||
await db.delete(deliverySlotInfo).where(eq(deliverySlotInfo.id, id));
|
||||
}
|
||||
|
||||
export async function getSlotProducts(slotId: number): Promise<any[]> {
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, slotId),
|
||||
});
|
||||
|
||||
if (!slot) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const productIds = slot.productIds || [];
|
||||
if (productIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return await db.query.productInfo.findMany({
|
||||
where: inArray(productInfo.id, productIds),
|
||||
});
|
||||
}
|
||||
|
||||
export async function addProductToSlot(slotId: number, productId: number): Promise<void> {
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, slotId),
|
||||
});
|
||||
|
||||
if (!slot) {
|
||||
throw new Error(`Slot ${slotId} not found`);
|
||||
}
|
||||
|
||||
const currentProductIds = slot.productIds || [];
|
||||
if (!currentProductIds.includes(productId)) {
|
||||
await db.update(deliverySlotInfo)
|
||||
.set({ productIds: [...currentProductIds, productId] })
|
||||
.where(eq(deliverySlotInfo.id, slotId));
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeProductFromSlot(slotId: number, productId: number): Promise<void> {
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, slotId),
|
||||
});
|
||||
|
||||
if (!slot) {
|
||||
throw new Error(`Slot ${slotId} not found`);
|
||||
}
|
||||
|
||||
const currentProductIds = slot.productIds || [];
|
||||
const updatedProductIds = currentProductIds.filter(id => id !== productId);
|
||||
|
||||
await db.update(deliverySlotInfo)
|
||||
.set({ productIds: updatedProductIds })
|
||||
.where(eq(deliverySlotInfo.id, slotId));
|
||||
}
|
||||
|
||||
export async function clearSlotProducts(slotId: number): Promise<void> {
|
||||
await db.update(deliverySlotInfo)
|
||||
.set({ productIds: [] })
|
||||
.where(eq(deliverySlotInfo.id, slotId));
|
||||
}
|
||||
|
||||
export async function updateSlotCapacity(slotId: number, maxCapacity: number): Promise<any> {
|
||||
const [slot] = await db.update(deliverySlotInfo)
|
||||
.set({ isCapacityFull: Boolean(maxCapacity) })
|
||||
.where(eq(deliverySlotInfo.id, slotId))
|
||||
.returning();
|
||||
return slot;
|
||||
}
|
||||
|
||||
export async function getSlotDeliverySequence(slotId: number): Promise<any | null> {
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, slotId),
|
||||
columns: {
|
||||
deliverySequence: true,
|
||||
},
|
||||
});
|
||||
return slot?.deliverySequence || null;
|
||||
}
|
||||
|
||||
export async function updateSlotDeliverySequence(slotId: number, sequence: any): Promise<void> {
|
||||
await db.update(deliverySlotInfo)
|
||||
.set({ deliverySequence: sequence })
|
||||
.where(eq(deliverySlotInfo.id, slotId));
|
||||
}
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { staffUsers, staffRoles, users, userDetails, orders } from '../db/schema';
|
||||
import { eq, or, and, lt, desc, like } from 'drizzle-orm';
|
||||
|
||||
export interface StaffUser {
|
||||
id: number;
|
||||
name: string;
|
||||
password: string;
|
||||
staffRoleId: number;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export async function getStaffUserByName(name: string): Promise<StaffUser | null> {
|
||||
const staff = await db.query.staffUsers.findFirst({
|
||||
where: eq(staffUsers.name, name),
|
||||
});
|
||||
|
||||
return staff || null;
|
||||
}
|
||||
|
||||
export async function getAllStaff(): Promise<any[]> {
|
||||
const staff = await db.query.staffUsers.findMany({
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
with: {
|
||||
role: {
|
||||
with: {
|
||||
rolePermissions: {
|
||||
with: {
|
||||
permission: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return staff;
|
||||
}
|
||||
|
||||
export async function getStaffByName(name: string): Promise<StaffUser | null> {
|
||||
const staff = await db.query.staffUsers.findFirst({
|
||||
where: eq(staffUsers.name, name),
|
||||
});
|
||||
return staff || null;
|
||||
}
|
||||
|
||||
export async function getAllUsers(
|
||||
cursor?: number,
|
||||
limit: number = 20,
|
||||
search?: string
|
||||
): Promise<{ users: any[]; hasMore: boolean }> {
|
||||
let whereCondition = undefined;
|
||||
|
||||
if (search) {
|
||||
whereCondition = or(
|
||||
like(users.name, `%${search}%`),
|
||||
like(users.email, `%${search}%`),
|
||||
like(users.mobile, `%${search}%`)
|
||||
);
|
||||
}
|
||||
|
||||
if (cursor) {
|
||||
const cursorCondition = lt(users.id, cursor);
|
||||
whereCondition = whereCondition ? and(whereCondition, cursorCondition) : cursorCondition;
|
||||
}
|
||||
|
||||
const allUsers = await db.query.users.findMany({
|
||||
where: whereCondition,
|
||||
with: {
|
||||
userDetails: true,
|
||||
},
|
||||
orderBy: desc(users.id),
|
||||
limit: limit + 1,
|
||||
});
|
||||
|
||||
const hasMore = allUsers.length > limit;
|
||||
const usersToReturn = hasMore ? allUsers.slice(0, limit) : allUsers;
|
||||
|
||||
return { users: usersToReturn, hasMore };
|
||||
}
|
||||
|
||||
export async function getUserWithDetails(userId: number): Promise<any | null> {
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.id, userId),
|
||||
with: {
|
||||
userDetails: true,
|
||||
orders: {
|
||||
orderBy: desc(orders.createdAt),
|
||||
limit: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return user || null;
|
||||
}
|
||||
|
||||
export async function updateUserSuspension(userId: number, isSuspended: boolean): Promise<void> {
|
||||
await db
|
||||
.insert(userDetails)
|
||||
.values({ userId, isSuspended })
|
||||
.onConflictDoUpdate({
|
||||
target: userDetails.userId,
|
||||
set: { isSuspended },
|
||||
});
|
||||
}
|
||||
|
||||
export async function checkStaffUserExists(name: string): Promise<boolean> {
|
||||
const existingUser = await db.query.staffUsers.findFirst({
|
||||
where: eq(staffUsers.name, name),
|
||||
});
|
||||
return !!existingUser;
|
||||
}
|
||||
|
||||
export async function checkStaffRoleExists(roleId: number): Promise<boolean> {
|
||||
const role = await db.query.staffRoles.findFirst({
|
||||
where: eq(staffRoles.id, roleId),
|
||||
});
|
||||
return !!role;
|
||||
}
|
||||
|
||||
export async function createStaffUser(
|
||||
name: string,
|
||||
password: string,
|
||||
roleId: number
|
||||
): Promise<StaffUser> {
|
||||
const [newUser] = await db.insert(staffUsers).values({
|
||||
name: name.trim(),
|
||||
password,
|
||||
staffRoleId: roleId,
|
||||
}).returning();
|
||||
|
||||
return {
|
||||
id: newUser.id,
|
||||
name: newUser.name,
|
||||
password: newUser.password,
|
||||
staffRoleId: newUser.staffRoleId ?? roleId,
|
||||
createdAt: newUser.createdAt,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getAllRoles(): Promise<any[]> {
|
||||
const roles = await db.query.staffRoles.findMany({
|
||||
columns: {
|
||||
id: true,
|
||||
roleName: true,
|
||||
},
|
||||
});
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { storeInfo, productInfo } from '../db/schema';
|
||||
import { eq, inArray } from 'drizzle-orm';
|
||||
|
||||
export interface Store {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string | null;
|
||||
imageUrl: string | null;
|
||||
owner: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export async function getAllStores(): Promise<any[]> {
|
||||
const stores = await db.query.storeInfo.findMany({
|
||||
with: {
|
||||
owner: true,
|
||||
},
|
||||
});
|
||||
|
||||
return stores;
|
||||
}
|
||||
|
||||
export async function getStoreById(id: number): Promise<any | null> {
|
||||
const store = await db.query.storeInfo.findFirst({
|
||||
where: eq(storeInfo.id, id),
|
||||
with: {
|
||||
owner: true,
|
||||
},
|
||||
});
|
||||
|
||||
return store || null;
|
||||
}
|
||||
|
||||
export interface CreateStoreInput {
|
||||
name: string;
|
||||
description?: string;
|
||||
imageUrl?: string;
|
||||
owner: number;
|
||||
}
|
||||
|
||||
export async function createStore(
|
||||
input: CreateStoreInput,
|
||||
products?: number[]
|
||||
): Promise<Store> {
|
||||
const [newStore] = await db
|
||||
.insert(storeInfo)
|
||||
.values({
|
||||
name: input.name,
|
||||
description: input.description,
|
||||
imageUrl: input.imageUrl,
|
||||
owner: input.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));
|
||||
}
|
||||
|
||||
return {
|
||||
id: newStore.id,
|
||||
name: newStore.name,
|
||||
description: newStore.description,
|
||||
imageUrl: newStore.imageUrl,
|
||||
owner: newStore.owner,
|
||||
createdAt: newStore.createdAt,
|
||||
updatedAt: newStore.createdAt,
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateStoreInput {
|
||||
name?: string;
|
||||
description?: string;
|
||||
imageUrl?: string;
|
||||
owner?: number;
|
||||
}
|
||||
|
||||
export async function updateStore(
|
||||
id: number,
|
||||
input: UpdateStoreInput,
|
||||
products?: number[]
|
||||
): Promise<Store> {
|
||||
const [updatedStore] = await db
|
||||
.update(storeInfo)
|
||||
.set({
|
||||
...input,
|
||||
})
|
||||
.where(eq(storeInfo.id, id))
|
||||
.returning();
|
||||
|
||||
if (!updatedStore) {
|
||||
throw new Error("Store not found");
|
||||
}
|
||||
|
||||
// Update products if provided
|
||||
if (products !== undefined) {
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: updatedStore.id,
|
||||
name: updatedStore.name,
|
||||
description: updatedStore.description,
|
||||
imageUrl: updatedStore.imageUrl,
|
||||
owner: updatedStore.owner,
|
||||
createdAt: updatedStore.createdAt,
|
||||
updatedAt: updatedStore.createdAt,
|
||||
};
|
||||
}
|
||||
|
||||
export async function deleteStore(id: number): Promise<{ message: string }> {
|
||||
return 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, id));
|
||||
|
||||
// Then delete the store
|
||||
const [deletedStore] = await tx
|
||||
.delete(storeInfo)
|
||||
.where(eq(storeInfo.id, id))
|
||||
.returning();
|
||||
|
||||
if (!deletedStore) {
|
||||
throw new Error("Store not found");
|
||||
}
|
||||
|
||||
return {
|
||||
message: "Store deleted successfully",
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
@ -1,270 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { users, userDetails, orders, orderItems, complaints, notifCreds, unloggedUserTokens, userIncidents, orderStatus } from '../db/schema';
|
||||
import { eq, sql, desc, asc, count, max, inArray, like } from 'drizzle-orm';
|
||||
|
||||
export async function createUserByMobile(mobile: string): Promise<any> {
|
||||
const [newUser] = await db
|
||||
.insert(users)
|
||||
.values({
|
||||
name: null,
|
||||
email: null,
|
||||
mobile,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return newUser;
|
||||
}
|
||||
|
||||
export async function getUserByMobile(mobile: string): Promise<any | null> {
|
||||
const [existingUser] = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.mobile, mobile))
|
||||
.limit(1);
|
||||
|
||||
return existingUser || null;
|
||||
}
|
||||
|
||||
export async function getUnresolvedComplaintsCount(): Promise<number> {
|
||||
const result = await db
|
||||
.select({ count: count(complaints.id) })
|
||||
.from(complaints)
|
||||
.where(eq(complaints.isResolved, false));
|
||||
|
||||
return result[0]?.count || 0;
|
||||
}
|
||||
|
||||
export async function getAllUsersWithFilters(
|
||||
limit: number,
|
||||
cursor?: number,
|
||||
search?: string
|
||||
): Promise<{ users: any[]; hasMore: boolean }> {
|
||||
const whereConditions = [];
|
||||
|
||||
if (search && search.trim()) {
|
||||
whereConditions.push(sql`${users.mobile} LIKE ${`%${search.trim()}%`}`);
|
||||
}
|
||||
|
||||
if (cursor) {
|
||||
whereConditions.push(sql`${users.id} > ${cursor}`);
|
||||
}
|
||||
|
||||
const usersList = await db
|
||||
.select({
|
||||
id: users.id,
|
||||
name: users.name,
|
||||
mobile: users.mobile,
|
||||
createdAt: users.createdAt,
|
||||
})
|
||||
.from(users)
|
||||
.where(whereConditions.length > 0 ? sql.join(whereConditions, sql` AND `) : undefined)
|
||||
.orderBy(asc(users.id))
|
||||
.limit(limit + 1);
|
||||
|
||||
const hasMore = usersList.length > limit;
|
||||
const usersToReturn = hasMore ? usersList.slice(0, limit) : usersList;
|
||||
|
||||
return { users: usersToReturn, hasMore };
|
||||
}
|
||||
|
||||
export async function getOrderCountsByUserIds(userIds: number[]): Promise<{ userId: number; totalOrders: number }[]> {
|
||||
if (userIds.length === 0) return [];
|
||||
|
||||
return await db
|
||||
.select({
|
||||
userId: orders.userId,
|
||||
totalOrders: count(orders.id),
|
||||
})
|
||||
.from(orders)
|
||||
.where(sql`${orders.userId} IN (${sql.join(userIds, sql`, `)})`)
|
||||
.groupBy(orders.userId);
|
||||
}
|
||||
|
||||
export async function getLastOrdersByUserIds(userIds: number[]): Promise<{ userId: number; lastOrderDate: Date | null }[]> {
|
||||
if (userIds.length === 0) return [];
|
||||
|
||||
return await db
|
||||
.select({
|
||||
userId: orders.userId,
|
||||
lastOrderDate: max(orders.createdAt),
|
||||
})
|
||||
.from(orders)
|
||||
.where(sql`${orders.userId} IN (${sql.join(userIds, sql`, `)})`)
|
||||
.groupBy(orders.userId);
|
||||
}
|
||||
|
||||
export async function getSuspensionStatusesByUserIds(userIds: number[]): Promise<{ userId: number; isSuspended: boolean }[]> {
|
||||
if (userIds.length === 0) return [];
|
||||
|
||||
return await db
|
||||
.select({
|
||||
userId: userDetails.userId,
|
||||
isSuspended: userDetails.isSuspended,
|
||||
})
|
||||
.from(userDetails)
|
||||
.where(sql`${userDetails.userId} IN (${sql.join(userIds, sql`, `)})`);
|
||||
}
|
||||
|
||||
export async function getUserBasicInfo(userId: number): Promise<any | null> {
|
||||
const user = await db
|
||||
.select({
|
||||
id: users.id,
|
||||
name: users.name,
|
||||
mobile: users.mobile,
|
||||
createdAt: users.createdAt,
|
||||
})
|
||||
.from(users)
|
||||
.where(eq(users.id, userId))
|
||||
.limit(1);
|
||||
|
||||
return user[0] || null;
|
||||
}
|
||||
|
||||
export async function getUserSuspensionStatus(userId: number): Promise<boolean> {
|
||||
const userDetail = await db
|
||||
.select({
|
||||
isSuspended: userDetails.isSuspended,
|
||||
})
|
||||
.from(userDetails)
|
||||
.where(eq(userDetails.userId, userId))
|
||||
.limit(1);
|
||||
|
||||
return userDetail[0]?.isSuspended ?? false;
|
||||
}
|
||||
|
||||
export async function getUserOrders(userId: number): Promise<any[]> {
|
||||
return await db
|
||||
.select({
|
||||
id: orders.id,
|
||||
readableId: orders.readableId,
|
||||
totalAmount: orders.totalAmount,
|
||||
createdAt: orders.createdAt,
|
||||
isFlashDelivery: orders.isFlashDelivery,
|
||||
})
|
||||
.from(orders)
|
||||
.where(eq(orders.userId, userId))
|
||||
.orderBy(desc(orders.createdAt));
|
||||
}
|
||||
|
||||
export async function getOrderStatusesByOrderIds(orderIds: number[]): Promise<{ orderId: number; isDelivered: boolean; isCancelled: boolean }[]> {
|
||||
if (orderIds.length === 0) return [];
|
||||
|
||||
return await db
|
||||
.select({
|
||||
orderId: orderStatus.orderId,
|
||||
isDelivered: orderStatus.isDelivered,
|
||||
isCancelled: orderStatus.isCancelled,
|
||||
})
|
||||
.from(orderStatus)
|
||||
.where(sql`${orderStatus.orderId} IN (${sql.join(orderIds, sql`, `)})`);
|
||||
}
|
||||
|
||||
export async function getItemCountsByOrderIds(orderIds: number[]): Promise<{ orderId: number; itemCount: number }[]> {
|
||||
if (orderIds.length === 0) return [];
|
||||
|
||||
return await db
|
||||
.select({
|
||||
orderId: orderItems.orderId,
|
||||
itemCount: count(orderItems.id),
|
||||
})
|
||||
.from(orderItems)
|
||||
.where(sql`${orderItems.orderId} IN (${sql.join(orderIds, sql`, `)})`)
|
||||
.groupBy(orderItems.orderId);
|
||||
}
|
||||
|
||||
export async function upsertUserSuspension(userId: number, isSuspended: boolean): Promise<void> {
|
||||
const existingDetail = await db
|
||||
.select({ id: userDetails.id })
|
||||
.from(userDetails)
|
||||
.where(eq(userDetails.userId, userId))
|
||||
.limit(1);
|
||||
|
||||
if (existingDetail.length > 0) {
|
||||
await db
|
||||
.update(userDetails)
|
||||
.set({ isSuspended })
|
||||
.where(eq(userDetails.userId, userId));
|
||||
} else {
|
||||
await db
|
||||
.insert(userDetails)
|
||||
.values({
|
||||
userId,
|
||||
isSuspended,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function searchUsers(search?: string): Promise<any[]> {
|
||||
if (search && search.trim()) {
|
||||
return await db
|
||||
.select({
|
||||
id: users.id,
|
||||
name: users.name,
|
||||
mobile: users.mobile,
|
||||
})
|
||||
.from(users)
|
||||
.where(sql`${users.mobile} LIKE ${`%${search.trim()}%`} OR ${users.name} LIKE ${`%${search.trim()}%`}`);
|
||||
} else {
|
||||
return await db
|
||||
.select({
|
||||
id: users.id,
|
||||
name: users.name,
|
||||
mobile: users.mobile,
|
||||
})
|
||||
.from(users);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllNotifCreds(): Promise<{ userId: number }[]> {
|
||||
return await db
|
||||
.select({ userId: notifCreds.userId })
|
||||
.from(notifCreds);
|
||||
}
|
||||
|
||||
export async function getAllUnloggedTokens(): Promise<{ token: string }[]> {
|
||||
return await db
|
||||
.select({ token: unloggedUserTokens.token })
|
||||
.from(unloggedUserTokens);
|
||||
}
|
||||
|
||||
export async function getNotifTokensByUserIds(userIds: number[]): Promise<{ token: string }[]> {
|
||||
return await db
|
||||
.select({ token: notifCreds.token })
|
||||
.from(notifCreds)
|
||||
.where(inArray(notifCreds.userId, userIds));
|
||||
}
|
||||
|
||||
export async function getUserIncidentsWithRelations(userId: number): Promise<any[]> {
|
||||
return await db.query.userIncidents.findMany({
|
||||
where: eq(userIncidents.userId, userId),
|
||||
with: {
|
||||
order: {
|
||||
with: {
|
||||
orderStatus: true,
|
||||
},
|
||||
},
|
||||
addedBy: true,
|
||||
},
|
||||
orderBy: desc(userIncidents.dateAdded),
|
||||
});
|
||||
}
|
||||
|
||||
export async function createUserIncident(
|
||||
userId: number,
|
||||
orderId: number | undefined,
|
||||
adminComment: string | undefined,
|
||||
adminUserId: number,
|
||||
negativityScore: number | undefined
|
||||
): Promise<any> {
|
||||
const [incident] = await db.insert(userIncidents)
|
||||
.values({
|
||||
userId,
|
||||
orderId,
|
||||
adminComment,
|
||||
addedBy: adminUserId,
|
||||
negativityScore,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return incident;
|
||||
}
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { vendorSnippets, deliverySlotInfo, productInfo, orders, orderItems, orderStatus } from '../db/schema';
|
||||
import { eq, and, inArray, gt, sql, asc, desc } from 'drizzle-orm';
|
||||
|
||||
export async function checkVendorSnippetExists(snippetCode: string): Promise<boolean> {
|
||||
const existingSnippet = await db.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.snippetCode, snippetCode),
|
||||
});
|
||||
return !!existingSnippet;
|
||||
}
|
||||
|
||||
export async function getVendorSnippetById(id: number): Promise<any | null> {
|
||||
return await db.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.id, id),
|
||||
with: {
|
||||
slot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getVendorSnippetByCode(snippetCode: string): Promise<any | null> {
|
||||
return await db.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.snippetCode, snippetCode),
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAllVendorSnippets(): Promise<any[]> {
|
||||
return await db.query.vendorSnippets.findMany({
|
||||
with: {
|
||||
slot: true,
|
||||
},
|
||||
orderBy: desc(vendorSnippets.createdAt),
|
||||
});
|
||||
}
|
||||
|
||||
export interface CreateVendorSnippetInput {
|
||||
snippetCode: string;
|
||||
slotId?: number;
|
||||
productIds: number[];
|
||||
isPermanent: boolean;
|
||||
validTill?: Date;
|
||||
}
|
||||
|
||||
export async function createVendorSnippet(input: CreateVendorSnippetInput): Promise<any> {
|
||||
const [result] = await db.insert(vendorSnippets).values({
|
||||
snippetCode: input.snippetCode,
|
||||
slotId: input.slotId,
|
||||
productIds: input.productIds,
|
||||
isPermanent: input.isPermanent,
|
||||
validTill: input.validTill,
|
||||
}).returning();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateVendorSnippet(id: number, updates: any): Promise<any> {
|
||||
const [result] = await db.update(vendorSnippets)
|
||||
.set(updates)
|
||||
.where(eq(vendorSnippets.id, id))
|
||||
.returning();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function deleteVendorSnippet(id: number): Promise<void> {
|
||||
await db.delete(vendorSnippets)
|
||||
.where(eq(vendorSnippets.id, id));
|
||||
}
|
||||
|
||||
export async function getProductsByIds(productIds: number[]): Promise<any[]> {
|
||||
return await db.query.productInfo.findMany({
|
||||
where: inArray(productInfo.id, productIds),
|
||||
columns: { id: true, name: true },
|
||||
});
|
||||
}
|
||||
|
||||
export async function getVendorSlotById(slotId: number): Promise<any | null> {
|
||||
return await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, slotId),
|
||||
});
|
||||
}
|
||||
|
||||
export async function getVendorOrdersBySlotId(slotId: number): Promise<any[]> {
|
||||
return await db.query.orders.findMany({
|
||||
where: eq(orders.slotId, slotId),
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: {
|
||||
with: {
|
||||
unit: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderStatus: true,
|
||||
user: true,
|
||||
slot: true,
|
||||
},
|
||||
orderBy: desc(orders.createdAt),
|
||||
});
|
||||
}
|
||||
|
||||
export async function getOrderItemsByOrderIds(orderIds: number[]): Promise<any[]> {
|
||||
return await db.query.orderItems.findMany({
|
||||
where: inArray(orderItems.orderId, orderIds),
|
||||
with: {
|
||||
product: {
|
||||
with: {
|
||||
unit: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getOrderStatusByOrderIds(orderIds: number[]): Promise<any[]> {
|
||||
return await db.query.orderStatus.findMany({
|
||||
where: inArray(orderStatus.orderId, orderIds),
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateVendorOrderItemPackaging(orderItemId: number, isPackaged: boolean, isPackageVerified: boolean): Promise<void> {
|
||||
await db.update(orderItems)
|
||||
.set({
|
||||
is_packaged: isPackaged,
|
||||
is_package_verified: isPackageVerified,
|
||||
})
|
||||
.where(eq(orderItems.id, orderItemId));
|
||||
}
|
||||
|
|
@ -24,32 +24,6 @@ import { coerceDate } from '../lib/date'
|
|||
import { runBatched } from '../lib/run-batched'
|
||||
import { composeSkuName, composeUnitNotation } from '../lib/sku-features'
|
||||
|
||||
export interface OrderItemInput {
|
||||
productId: number
|
||||
quantity: number
|
||||
slotId: number | null
|
||||
}
|
||||
|
||||
export interface PlaceOrderInput {
|
||||
userId: number
|
||||
selectedItems: OrderItemInput[]
|
||||
addressId: number
|
||||
paymentMethod: 'online' | 'cod'
|
||||
couponId?: number
|
||||
userNotes?: string
|
||||
isFlash?: boolean
|
||||
}
|
||||
|
||||
export interface OrderGroupData {
|
||||
slotId: number | null
|
||||
items: Array<{
|
||||
productId: number
|
||||
quantity: number
|
||||
slotId: number | null
|
||||
product: typeof productInfo.$inferSelect
|
||||
}>
|
||||
}
|
||||
|
||||
export interface PlacedOrder {
|
||||
id: number
|
||||
userId: number
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import { db } from '../db/db_index'
|
||||
import { productTags } from '../db/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
|
||||
export async function getAllTags(): Promise<any[]> {
|
||||
return db.query.productTags.findMany({
|
||||
with: {
|
||||
// products: {
|
||||
// with: {
|
||||
// product: true,
|
||||
// },
|
||||
// },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function getTagById(id: number): Promise<any | null> {
|
||||
return db.query.productTags.findFirst({
|
||||
where: eq(productTags.id, id),
|
||||
with: {
|
||||
// products: {
|
||||
// with: {
|
||||
// product: true,
|
||||
// },
|
||||
// },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -7,7 +7,5 @@ export const CACHE_FILENAMES = {
|
|||
banners: 'banners.json',
|
||||
} as const
|
||||
|
||||
export type CacheFilename = typeof CACHE_FILENAMES[keyof typeof CACHE_FILENAMES]
|
||||
|
||||
// Re-export all types from the types folder
|
||||
export * from './types'
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue