diff --git a/apps/backend/src/db/db_index.ts b/apps/backend/src/db/db_index.ts index 05aebab..4e2d75a 100755 --- a/apps/backend/src/db/db_index.ts +++ b/apps/backend/src/db/db_index.ts @@ -1,7 +1,7 @@ import { drizzle } from "drizzle-orm/node-postgres" import { migrate } from "drizzle-orm/node-postgres/migrator" import path from "path" -import * as schema from "@/src/db/schema" +import * as schema from "@/src/db/schema-postgres" const db = drizzle({ connection: process.env.DATABASE_URL!, casing: "snake_case", schema: schema }) // const db = drizzle('postgresql://postgres:postgres@localhost:2345/pooler'); diff --git a/apps/backend/src/db/db_index_sqlite.ts b/apps/backend/src/db/db_index_sqlite.ts new file mode 100644 index 0000000..c9390d4 --- /dev/null +++ b/apps/backend/src/db/db_index_sqlite.ts @@ -0,0 +1,10 @@ +import { drizzle } from 'drizzle-orm/bun-sqlite' +import { Database } from 'bun:sqlite' +import * as schema from '@/src/db/schema-sqlite' + +const sqlitePath = process.env.SQLITE_DB_PATH || 'sqlite.db' +const sqlite = new Database(sqlitePath) + +const db = drizzle(sqlite, { schema }) + +export { db } diff --git a/apps/backend/src/db/schema-postgres.ts b/apps/backend/src/db/schema-postgres.ts new file mode 100644 index 0000000..939e04d --- /dev/null +++ b/apps/backend/src/db/schema-postgres.ts @@ -0,0 +1,706 @@ +import { pgTable, pgSchema, integer, varchar, date, boolean, timestamp, numeric, jsonb, pgEnum, unique, real, text, check, decimal } from "drizzle-orm/pg-core"; +import { relations, sql } from "drizzle-orm"; + +const mf = pgSchema('mf'); + + + +export const users = mf.table('users', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + name: varchar({ length: 255 }), + email: varchar({ length: 255 }), + mobile: varchar({ length: 255 }), + createdAt: timestamp('created_at').notNull().defaultNow(), +}, (t) => ({ + unq_email: unique('unique_email').on(t.email), +})); + +export const userDetails = mf.table('user_details', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + userId: integer('user_id').notNull().references(() => users.id).unique(), + bio: varchar('bio', { length: 500 }), + dateOfBirth: date('date_of_birth'), + gender: varchar('gender', { length: 20 }), + occupation: varchar('occupation', { length: 100 }), + profileImage: varchar('profile_image', { length: 500 }), + isSuspended: boolean('is_suspended').notNull().default(false), + createdAt: timestamp('created_at').notNull().defaultNow(), + updatedAt: timestamp('updated_at').notNull().defaultNow(), +}); + +export const userCreds = mf.table('user_creds', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + userId: integer('user_id').notNull().references(() => users.id), + userPassword: varchar('user_password', { length: 255 }).notNull(), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); + +export const addresses = mf.table('addresses', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + userId: integer('user_id').notNull().references(() => users.id), + name: varchar('name', { length: 255 }).notNull(), + phone: varchar('phone', { length: 15 }).notNull(), + addressLine1: varchar('address_line1', { length: 255 }).notNull(), + addressLine2: varchar('address_line2', { length: 255 }), + city: varchar('city', { length: 100 }).notNull(), + state: varchar('state', { length: 100 }).notNull(), + pincode: varchar('pincode', { length: 10 }).notNull(), + isDefault: boolean('is_default').notNull().default(false), + latitude: real('latitude'), + longitude: real('longitude'), + 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), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); + +export const staffUsers = mf.table('staff_users', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + name: varchar({ length: 255 }).notNull(), + password: varchar({ length: 255 }).notNull(), + staffRoleId: integer('staff_role_id').references(() => staffRoles.id), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); + +export const storeInfo = mf.table('store_info', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + name: varchar({ length: 255 }).notNull(), + description: varchar({ length: 500 }), + imageUrl: varchar('image_url', { length: 500 }), + createdAt: timestamp('created_at').notNull().defaultNow(), + owner: integer('owner').notNull().references(() => staffUsers.id), +}); + +export const units = mf.table('units', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + shortNotation: varchar('short_notation', { length: 50 }).notNull(), + fullName: varchar('full_name', { length: 100 }).notNull(), +}, (t) => ({ + unq_short_notation: unique('unique_short_notation').on(t.shortNotation), +})); + +export const productAvailabilityActionEnum = pgEnum('product_availability_action', ['in', 'out']); + +export const productInfo = mf.table('product_info', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + name: varchar({ length: 255 }).notNull(), + shortDescription: varchar('short_description', { length: 500 }), + longDescription: varchar('long_description', { length: 1000 }), + unitId: integer('unit_id').notNull().references(() => units.id), + price: numeric({ precision: 10, scale: 2 }).notNull(), + marketPrice: numeric('market_price', { precision: 10, scale: 2 }), + images: jsonb('images'), + isOutOfStock: boolean('is_out_of_stock').notNull().default(false), + isSuspended: boolean('is_suspended').notNull().default(false), + isFlashAvailable: boolean('is_flash_available').notNull().default(false), + flashPrice: numeric('flash_price', { precision: 10, scale: 2 }), + createdAt: timestamp('created_at').notNull().defaultNow(), + incrementStep: real('increment_step').notNull().default(1), + productQuantity: real('product_quantity').notNull().default(1), + storeId: integer('store_id').references(() => storeInfo.id), + scheduledAvailability: boolean('scheduled_availability').notNull().default(true), +}); + +export const productAvailabilitySchedules = mf.table('product_availability_schedules', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + time: varchar('time', { length: 10 }).notNull(), + scheduleName: varchar('schedule_name', { length: 255 }).notNull().unique(), + action: productAvailabilityActionEnum('action').notNull(), + productIds: integer('product_ids').array().notNull().default([]), + groupIds: integer('group_ids').array().notNull().default([]), + createdAt: timestamp('created_at').notNull().defaultNow(), + lastUpdated: timestamp('last_updated').notNull().defaultNow(), +}); + +export const productGroupInfo = mf.table('product_group_info', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + groupName: varchar('group_name', { length: 255 }).notNull(), + description: varchar({ length: 500 }), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); + +export const productGroupMembership = mf.table('product_group_membership', { + productId: integer('product_id').notNull().references(() => productInfo.id), + groupId: integer('group_id').notNull().references(() => productGroupInfo.id), + addedAt: timestamp('added_at').notNull().defaultNow(), +}, (t) => ({ + pk: unique('product_group_membership_pk').on(t.productId, t.groupId), +})); + +export const homeBanners = mf.table('home_banners', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + name: varchar('name', { length: 255 }).notNull(), + imageUrl: varchar('image_url', { length: 500 }).notNull(), + description: varchar('description', { length: 500 }), + productIds: integer('product_ids').array(), + redirectUrl: varchar('redirect_url', { length: 500 }), + serialNum: integer('serial_num'), + isActive: boolean('is_active').notNull().default(false), + createdAt: timestamp('created_at').notNull().defaultNow(), + lastUpdated: timestamp('last_updated').notNull().defaultNow(), +}); + +export const productReviews = mf.table('product_reviews', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + userId: integer('user_id').notNull().references(() => users.id), + productId: integer('product_id').notNull().references(() => productInfo.id), + reviewBody: text('review_body').notNull(), + imageUrls: jsonb('image_urls').$defaultFn(() => []), + reviewTime: timestamp('review_time').notNull().defaultNow(), + ratings: real('ratings').notNull(), + adminResponse: text('admin_response'), + adminResponseImages: jsonb('admin_response_images').$defaultFn(() => []), +}, (t) => ({ + ratingCheck: check('rating_check', sql`${t.ratings} >= 1 AND ${t.ratings} <= 5`), +})); + +export const uploadStatusEnum = pgEnum('upload_status', ['pending', 'claimed']); + +export const staffRoleEnum = pgEnum('staff_role', ['super_admin', 'admin', 'marketer', 'delivery_staff']); + +export const staffPermissionEnum = pgEnum('staff_permission', ['crud_product', 'make_coupon', 'crud_staff_users']); + +export const uploadUrlStatus = mf.table('upload_url_status', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + createdAt: timestamp('created_at').notNull().defaultNow(), + key: varchar('key', { length: 500 }).notNull(), + status: uploadStatusEnum('status').notNull().default('pending'), +}); + +export const productTagInfo = mf.table('product_tag_info', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + tagName: varchar('tag_name', { length: 100 }).notNull().unique(), + tagDescription: varchar('tag_description', { length: 500 }), + imageUrl: varchar('image_url', { length: 500 }), + isDashboardTag: boolean('is_dashboard_tag').notNull().default(false), + relatedStores: jsonb('related_stores').$defaultFn(() => []), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); + +export const productTags = mf.table('product_tags', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + productId: integer('product_id').notNull().references(() => productInfo.id), + tagId: integer('tag_id').notNull().references(() => productTagInfo.id), + assignedAt: timestamp('assigned_at').notNull().defaultNow(), +}, (t) => ({ + unq_product_tag: unique('unique_product_tag').on(t.productId, t.tagId), +})); + +export const deliverySlotInfo = mf.table('delivery_slot_info', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + deliveryTime: timestamp('delivery_time').notNull(), + freezeTime: timestamp('freeze_time').notNull(), + isActive: boolean('is_active').notNull().default(true), + isFlash: boolean('is_flash').notNull().default(false), + isCapacityFull: boolean('is_capacity_full').notNull().default(false), + deliverySequence: jsonb('delivery_sequence').$defaultFn(() => {}), + groupIds: jsonb('group_ids').$defaultFn(() => []), +}); + +export const vendorSnippets = mf.table('vendor_snippets', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + snippetCode: varchar('snippet_code', { length: 255 }).notNull().unique(), + slotId: integer('slot_id').references(() => deliverySlotInfo.id), + isPermanent: boolean('is_permanent').notNull().default(false), + productIds: integer('product_ids').array().notNull(), + validTill: timestamp('valid_till'), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); + +export const vendorSnippetsRelations = relations(vendorSnippets, ({ one }) => ({ + slot: one(deliverySlotInfo, { fields: [vendorSnippets.slotId], references: [deliverySlotInfo.id] }), +})); + +export const productSlots = mf.table('product_slots', { + productId: integer('product_id').notNull().references(() => productInfo.id), + slotId: integer('slot_id').notNull().references(() => deliverySlotInfo.id), +}, (t) => ({ + pk: unique('product_slot_pk').on(t.productId, t.slotId), +})); + +export const specialDeals = mf.table('special_deals', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + productId: integer('product_id').notNull().references(() => productInfo.id), + quantity: numeric({ precision: 10, scale: 2 }).notNull(), + price: numeric({ precision: 10, scale: 2 }).notNull(), + validTill: timestamp('valid_till').notNull(), +}); + +export const orders = mf.table('orders', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + userId: integer('user_id').notNull().references(() => users.id), + addressId: integer('address_id').notNull().references(() => addresses.id), + slotId: integer('slot_id').references(() => deliverySlotInfo.id), + isCod: boolean('is_cod').notNull().default(false), + isOnlinePayment: boolean('is_online_payment').notNull().default(false), + paymentInfoId: integer('payment_info_id').references(() => paymentInfoTable.id), + totalAmount: numeric('total_amount', { precision: 10, scale: 2 }).notNull(), + deliveryCharge: numeric('delivery_charge', { precision: 10, scale: 2 }).notNull().default('0'), + readableId: integer('readable_id').notNull(), + adminNotes: text('admin_notes'), + userNotes: text('user_notes'), + orderGroupId: varchar('order_group_id', { length: 255 }), + orderGroupProportion: decimal('order_group_proportion', { precision: 10, scale: 4 }), + isFlashDelivery: boolean('is_flash_delivery').notNull().default(false), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); + +export const orderItems = mf.table('order_items', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + orderId: integer('order_id').notNull().references(() => orders.id), + productId: integer('product_id').notNull().references(() => productInfo.id), + quantity: varchar('quantity', { length: 50 }).notNull(), + price: numeric({ precision: 10, scale: 2 }).notNull(), + discountedPrice: numeric('discounted_price', { precision: 10, scale: 2 }), + is_packaged: boolean('is_packaged').notNull().default(false), + is_package_verified: boolean('is_package_verified').notNull().default(false), +}); + +export const paymentStatusEnum = pgEnum('payment_status', ['pending', 'success', 'cod', 'failed']); + +export const orderStatus = mf.table('order_status', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + orderTime: timestamp('order_time').notNull().defaultNow(), + userId: integer('user_id').notNull().references(() => users.id), + orderId: integer('order_id').notNull().references(() => orders.id), + isPackaged: boolean('is_packaged').notNull().default(false), + isDelivered: boolean('is_delivered').notNull().default(false), + isCancelled: boolean('is_cancelled').notNull().default(false), + cancelReason: varchar('cancel_reason', { length: 255 }), + isCancelledByAdmin: boolean('is_cancelled_by_admin'), + paymentStatus: paymentStatusEnum('payment_state').notNull().default('pending'), + cancellationUserNotes: text('cancellation_user_notes'), + cancellationAdminNotes: text('cancellation_admin_notes'), + cancellationReviewed: boolean('cancellation_reviewed').notNull().default(false), + cancellationReviewedAt: timestamp('cancellation_reviewed_at'), + refundCouponId: integer('refund_coupon_id').references(() => coupons.id), +}); + +export const paymentInfoTable = mf.table('payment_info', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + status: varchar({ length: 50 }).notNull(), + gateway: varchar({ length: 50 }).notNull(), + orderId: varchar('order_id', { length: 500 }), + token: varchar({ length: 500 }), + merchantOrderId: varchar('merchant_order_id', { length: 255 }).notNull().unique(), + payload: jsonb('payload'), +}); + +export const payments = mf.table('payments', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + status: varchar({ length: 50 }).notNull(), + gateway: varchar({ length: 50 }).notNull(), + orderId: integer('order_id').notNull().references(() => orders.id), + token: varchar({ length: 500 }), + merchantOrderId: varchar('merchant_order_id', { length: 255 }).notNull().unique(), + payload: jsonb('payload'), +}); + +export const refunds = mf.table('refunds', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + orderId: integer('order_id').notNull().references(() => orders.id), + refundAmount: numeric('refund_amount', { precision: 10, scale: 2 }), + refundStatus: varchar('refund_status', { length: 50 }).default('none'), + merchantRefundId: varchar('merchant_refund_id', { length: 255 }), + refundProcessedAt: timestamp('refund_processed_at'), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); + +export const keyValStore = mf.table('key_val_store', { + key: varchar('key', { length: 255 }).primaryKey(), + value: jsonb('value'), +}); + +export const notifications = mf.table('notifications', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + userId: integer('user_id').notNull().references(() => users.id), + title: varchar({ length: 255 }).notNull(), + body: varchar({ length: 512 }).notNull(), + type: varchar({ length: 50 }), + isRead: boolean('is_read').notNull().default(false), + 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), + productId: integer('product_id').notNull().references(() => productInfo.id), + quantity: numeric({ precision: 10, scale: 2 }).notNull(), + addedAt: timestamp('added_at').notNull().defaultNow(), +}, (t) => ({ + unq_user_product: unique('unique_user_product').on(t.userId, t.productId), +})); + +export const complaints = mf.table('complaints', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + userId: integer('user_id').notNull().references(() => users.id), + orderId: integer('order_id').references(() => orders.id), + complaintBody: varchar('complaint_body', { length: 1000 }).notNull(), + images: jsonb('images'), + response: varchar('response', { length: 1000 }), + isResolved: boolean('is_resolved').notNull().default(false), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); + +export const coupons = mf.table('coupons', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + couponCode: varchar('coupon_code', { length: 50 }).notNull().unique('unique_coupon_code'), + isUserBased: boolean('is_user_based').notNull().default(false), + discountPercent: numeric('discount_percent', { precision: 5, scale: 2 }), + flatDiscount: numeric('flat_discount', { precision: 10, scale: 2 }), + minOrder: numeric('min_order', { precision: 10, scale: 2 }), + productIds: jsonb('product_ids'), + createdBy: integer('created_by').references(() => staffUsers.id), + maxValue: numeric('max_value', { precision: 10, scale: 2 }), + isApplyForAll: boolean('is_apply_for_all').notNull().default(false), + validTill: timestamp('valid_till'), + maxLimitForUser: integer('max_limit_for_user'), + isInvalidated: boolean('is_invalidated').notNull().default(false), + exclusiveApply: boolean('exclusive_apply').notNull().default(false), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); + +export const couponUsage = mf.table('coupon_usage', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + userId: integer('user_id').notNull().references(() => users.id), + couponId: integer('coupon_id').notNull().references(() => coupons.id), + orderId: integer('order_id').references(() => orders.id), + orderItemId: integer('order_item_id').references(() => orderItems.id), + usedAt: timestamp('used_at').notNull().defaultNow(), +}); + +export const couponApplicableUsers = mf.table('coupon_applicable_users', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + couponId: integer('coupon_id').notNull().references(() => coupons.id), + userId: integer('user_id').notNull().references(() => users.id), +}, (t) => ({ + unq_coupon_user: unique('unique_coupon_user').on(t.couponId, t.userId), +})); + +export const couponApplicableProducts = mf.table('coupon_applicable_products', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + couponId: integer('coupon_id').notNull().references(() => coupons.id), + productId: integer('product_id').notNull().references(() => productInfo.id), +}, (t) => ({ + unq_coupon_product: unique('unique_coupon_product').on(t.couponId, t.productId), +})); + +export const userIncidents = mf.table('user_incidents', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + userId: integer('user_id').notNull().references(() => users.id), + orderId: integer('order_id').references(() => orders.id), + dateAdded: timestamp('date_added').notNull().defaultNow(), + adminComment: text('admin_comment'), + addedBy: integer('added_by').references(() => staffUsers.id), + negativityScore: integer('negativity_score'), +}); + +export const reservedCoupons = mf.table('reserved_coupons', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + secretCode: varchar('secret_code', { length: 50 }).notNull().unique(), + couponCode: varchar('coupon_code', { length: 50 }).notNull(), + discountPercent: numeric('discount_percent', { precision: 5, scale: 2 }), + flatDiscount: numeric('flat_discount', { precision: 10, scale: 2 }), + minOrder: numeric('min_order', { precision: 10, scale: 2 }), + productIds: jsonb('product_ids'), + maxValue: numeric('max_value', { precision: 10, scale: 2 }), + validTill: timestamp('valid_till'), + maxLimitForUser: integer('max_limit_for_user'), + exclusiveApply: boolean('exclusive_apply').notNull().default(false), + isRedeemed: boolean('is_redeemed').notNull().default(false), + redeemedBy: integer('redeemed_by').references(() => users.id), + redeemedAt: timestamp('redeemed_at'), + createdBy: integer('created_by').notNull().references(() => staffUsers.id), + createdAt: timestamp('created_at').notNull().defaultNow(), +}, (t) => ({ + unq_secret_code: unique('unique_secret_code').on(t.secretCode), +})); + +export const notifCreds = mf.table('notif_creds', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + token: varchar({ length: 500 }).notNull().unique(), + addedAt: timestamp('added_at').notNull().defaultNow(), + userId: integer('user_id').notNull().references(() => users.id), + lastVerified: timestamp('last_verified'), +}); + +export const unloggedUserTokens = mf.table('unlogged_user_tokens', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + token: varchar({ length: 500 }).notNull().unique(), + addedAt: timestamp('added_at').notNull().defaultNow(), + 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(), + createdAt: timestamp('created_at').notNull().defaultNow(), +}, (t) => ({ + unq_role_name: unique('unique_role_name').on(t.roleName), +})); + +export const staffPermissions = mf.table('staff_permissions', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + permissionName: staffPermissionEnum('permission_name').notNull(), + createdAt: timestamp('created_at').notNull().defaultNow(), +}, (t) => ({ + unq_permission_name: unique('unique_permission_name').on(t.permissionName), +})); + +export const staffRolePermissions = mf.table('staff_role_permissions', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + staffRoleId: integer('staff_role_id').notNull().references(() => staffRoles.id), + staffPermissionId: integer('staff_permission_id').notNull().references(() => staffPermissions.id), + createdAt: timestamp('created_at').notNull().defaultNow(), +}, (t) => ({ + unq_role_permission: unique('unique_role_permission').on(t.staffRoleId, t.staffPermissionId), +})); + +// Relations +export const usersRelations = relations(users, ({ many, one }) => ({ + addresses: many(addresses), + orders: many(orders), + notifications: many(notifications), + cartItems: many(cartItems), + userCreds: one(userCreds), + coupons: many(coupons), + couponUsages: many(couponUsage), + applicableCoupons: many(couponApplicableUsers), + userDetails: one(userDetails), + notifCreds: many(notifCreds), + userIncidents: many(userIncidents), +})); + +export const userCredsRelations = relations(userCreds, ({ one }) => ({ + user: one(users, { fields: [userCreds.userId], references: [users.id] }), +})); + +export const staffUsersRelations = relations(staffUsers, ({ one, many }) => ({ + role: one(staffRoles, { fields: [staffUsers.staffRoleId], references: [staffRoles.id] }), + coupons: many(coupons), + stores: many(storeInfo), +})); + +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 }) => ({ + products: many(productInfo), +})); + +export const productInfoRelations = relations(productInfo, ({ one, many }) => ({ + unit: one(units, { fields: [productInfo.unitId], references: [units.id] }), + store: one(storeInfo, { fields: [productInfo.storeId], references: [storeInfo.id] }), + productSlots: many(productSlots), + specialDeals: many(specialDeals), + orderItems: many(orderItems), + cartItems: many(cartItems), + tags: many(productTags), + applicableCoupons: many(couponApplicableProducts), + reviews: many(productReviews), + groups: many(productGroupMembership), +})); + +export const productTagInfoRelations = relations(productTagInfo, ({ many }) => ({ + products: many(productTags), +})); + +export const productTagsRelations = relations(productTags, ({ one }) => ({ + product: one(productInfo, { fields: [productTags.productId], references: [productInfo.id] }), + tag: one(productTagInfo, { fields: [productTags.tagId], references: [productTagInfo.id] }), +})); + +export const deliverySlotInfoRelations = relations(deliverySlotInfo, ({ many }) => ({ + productSlots: many(productSlots), + orders: many(orders), + vendorSnippets: many(vendorSnippets), +})); + +export const productSlotsRelations = relations(productSlots, ({ one }) => ({ + product: one(productInfo, { fields: [productSlots.productId], references: [productInfo.id] }), + slot: one(deliverySlotInfo, { fields: [productSlots.slotId], references: [deliverySlotInfo.id] }), +})); + +export const specialDealsRelations = relations(specialDeals, ({ one }) => ({ + product: one(productInfo, { fields: [specialDeals.productId], references: [productInfo.id] }), +})); + +export const ordersRelations = relations(orders, ({ one, many }) => ({ + user: one(users, { fields: [orders.userId], references: [users.id] }), + address: one(addresses, { fields: [orders.addressId], references: [addresses.id] }), + slot: one(deliverySlotInfo, { fields: [orders.slotId], references: [deliverySlotInfo.id] }), + orderItems: many(orderItems), + payment: one(payments), + paymentInfo: one(paymentInfoTable, { fields: [orders.paymentInfoId], references: [paymentInfoTable.id] }), + orderStatus: many(orderStatus), + refunds: many(refunds), + couponUsages: many(couponUsage), + userIncidents: many(userIncidents), +})); + +export const orderItemsRelations = relations(orderItems, ({ one }) => ({ + order: one(orders, { fields: [orderItems.orderId], references: [orders.id] }), + product: one(productInfo, { fields: [orderItems.productId], references: [productInfo.id] }), +})); + +export const orderStatusRelations = relations(orderStatus, ({ one }) => ({ + order: one(orders, { fields: [orderStatus.orderId], references: [orders.id] }), + user: one(users, { fields: [orderStatus.userId], references: [users.id] }), + refundCoupon: one(coupons, { fields: [orderStatus.refundCouponId], references: [coupons.id] }), +})); + +export const paymentInfoRelations = relations(paymentInfoTable, ({ one }) => ({ + order: one(orders, { fields: [paymentInfoTable.id], references: [orders.paymentInfoId] }), +})); + +export const paymentsRelations = relations(payments, ({ one }) => ({ + order: one(orders, { fields: [payments.orderId], references: [orders.id] }), +})); + +export const refundsRelations = relations(refunds, ({ one }) => ({ + order: one(orders, { fields: [refunds.orderId], references: [orders.id] }), +})); + +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] }), +})); + +export const complaintsRelations = relations(complaints, ({ one }) => ({ + user: one(users, { fields: [complaints.userId], references: [users.id] }), + order: one(orders, { fields: [complaints.orderId], references: [orders.id] }), +})); + +export const couponsRelations = relations(coupons, ({ one, many }) => ({ + creator: one(staffUsers, { fields: [coupons.createdBy], references: [staffUsers.id] }), + usages: many(couponUsage), + applicableUsers: many(couponApplicableUsers), + applicableProducts: many(couponApplicableProducts), +})); + +export const couponUsageRelations = relations(couponUsage, ({ one }) => ({ + user: one(users, { fields: [couponUsage.userId], references: [users.id] }), + coupon: one(coupons, { fields: [couponUsage.couponId], references: [coupons.id] }), + order: one(orders, { fields: [couponUsage.orderId], references: [orders.id] }), + orderItem: one(orderItems, { fields: [couponUsage.orderItemId], references: [orderItems.id] }), +})); + +export const userDetailsRelations = relations(userDetails, ({ one }) => ({ + user: one(users, { fields: [userDetails.userId], references: [users.id] }), +})); + +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), +})); + +export const couponApplicableUsersRelations = relations(couponApplicableUsers, ({ one }) => ({ + coupon: one(coupons, { fields: [couponApplicableUsers.couponId], references: [coupons.id] }), + user: one(users, { fields: [couponApplicableUsers.userId], references: [users.id] }), +})); + +export const couponApplicableProductsRelations = relations(couponApplicableProducts, ({ one }) => ({ + coupon: one(coupons, { fields: [couponApplicableProducts.couponId], references: [coupons.id] }), + product: one(productInfo, { fields: [couponApplicableProducts.productId], references: [productInfo.id] }), +})); + +export const reservedCouponsRelations = relations(reservedCoupons, ({ one }) => ({ + redeemedUser: one(users, { fields: [reservedCoupons.redeemedBy], references: [users.id] }), + creator: one(staffUsers, { fields: [reservedCoupons.createdBy], references: [staffUsers.id] }), +})); + +export const productReviewsRelations = relations(productReviews, ({ one }) => ({ + user: one(users, { fields: [productReviews.userId], references: [users.id] }), + 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), +})); + +export const productGroupMembershipRelations = relations(productGroupMembership, ({ one }) => ({ + product: one(productInfo, { fields: [productGroupMembership.productId], references: [productInfo.id] }), + group: one(productGroupInfo, { fields: [productGroupMembership.groupId], references: [productGroupInfo.id] }), +})); + +export const homeBannersRelations = relations(homeBanners, ({}) => ({ + // Relations for productIds array would be more complex, skipping for now +})); + +export const staffRolesRelations = relations(staffRoles, ({ many }) => ({ + staffUsers: many(staffUsers), + rolePermissions: many(staffRolePermissions), +})); + +export const staffPermissionsRelations = relations(staffPermissions, ({ many }) => ({ + rolePermissions: many(staffRolePermissions), +})); + +export const staffRolePermissionsRelations = relations(staffRolePermissions, ({ one }) => ({ + role: one(staffRoles, { fields: [staffRolePermissions.staffRoleId], references: [staffRoles.id] }), + permission: one(staffPermissions, { fields: [staffRolePermissions.staffPermissionId], references: [staffPermissions.id] }), +})); + +export const userIncidentsRelations = relations(userIncidents, ({ one }) => ({ + user: one(users, { fields: [userIncidents.userId], references: [users.id] }), + order: one(orders, { fields: [userIncidents.orderId], references: [orders.id] }), + addedBy: one(staffUsers, { fields: [userIncidents.addedBy], references: [staffUsers.id] }), +})); + +export const productAvailabilitySchedulesRelations = relations(productAvailabilitySchedules, ({}) => ({ +})); diff --git a/apps/backend/src/db/schema-sqlite.ts b/apps/backend/src/db/schema-sqlite.ts new file mode 100644 index 0000000..568653d --- /dev/null +++ b/apps/backend/src/db/schema-sqlite.ts @@ -0,0 +1,735 @@ +import { + sqliteTable, + integer, + text, + real, + unique, + check, +} from 'drizzle-orm/sqlite-core' +import { relations, sql } from 'drizzle-orm' + +const epochSeconds = sql`(strftime('%s','now'))` + +const sqliteEnum = ( + _name: string, + values: T +) => (columnName: string) => text(columnName, { enum: values }) + +export const users = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name'), + email: text('email'), + mobile: text('mobile'), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}, (t) => ({ + unq_email: unique('unique_email').on(t.email), +})) + +export const userDetails = sqliteTable('user_details', { + id: integer('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').notNull().references(() => users.id).unique(), + bio: text('bio'), + dateOfBirth: integer('date_of_birth', { mode: 'timestamp' }), + gender: text('gender'), + occupation: text('occupation'), + profileImage: text('profile_image'), + isSuspended: integer('is_suspended', { mode: 'boolean' }).notNull().default(false), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), + updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const userCreds = sqliteTable('user_creds', { + id: integer('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').notNull().references(() => users.id), + userPassword: text('user_password').notNull(), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const addressZones = sqliteTable('address_zones', { + id: integer('id').primaryKey({ autoIncrement: true }), + zoneName: text('zone_name').notNull(), + addedAt: integer('added_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const addresses = sqliteTable('addresses', { + id: integer('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').notNull().references(() => users.id), + name: text('name').notNull(), + phone: text('phone').notNull(), + addressLine1: text('address_line1').notNull(), + addressLine2: text('address_line2'), + city: text('city').notNull(), + state: text('state').notNull(), + pincode: text('pincode').notNull(), + isDefault: integer('is_default', { mode: 'boolean' }).notNull().default(false), + latitude: real('latitude'), + longitude: real('longitude'), + googleMapsUrl: text('google_maps_url'), + adminLatitude: real('admin_latitude'), + adminLongitude: real('admin_longitude'), + zoneId: integer('zone_id').references(() => addressZones.id), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const addressAreas = sqliteTable('address_areas', { + id: integer('id').primaryKey({ autoIncrement: true }), + placeName: text('place_name').notNull(), + zoneId: integer('zone_id').references(() => addressZones.id), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const staffRoleEnum = sqliteEnum('staff_role', [ + 'super_admin', + 'admin', + 'marketer', + 'delivery_staff', +]) + +export const staffPermissionEnum = sqliteEnum('staff_permission', [ + 'crud_product', + 'make_coupon', + 'crud_staff_users', +]) + +export const staffRoles = sqliteTable('staff_roles', { + id: integer('id').primaryKey({ autoIncrement: true }), + roleName: staffRoleEnum('role_name').notNull(), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}, (t) => ({ + unq_role_name: unique('unique_role_name').on(t.roleName), +})) + +export const staffPermissions = sqliteTable('staff_permissions', { + id: integer('id').primaryKey({ autoIncrement: true }), + permissionName: staffPermissionEnum('permission_name').notNull(), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}, (t) => ({ + unq_permission_name: unique('unique_permission_name').on(t.permissionName), +})) + +export const staffRolePermissions = sqliteTable('staff_role_permissions', { + id: integer('id').primaryKey({ autoIncrement: true }), + staffRoleId: integer('staff_role_id').notNull().references(() => staffRoles.id), + staffPermissionId: integer('staff_permission_id').notNull().references(() => staffPermissions.id), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}, (t) => ({ + unq_role_permission: unique('unique_role_permission').on( + t.staffRoleId, + t.staffPermissionId + ), +})) + +export const staffUsers = sqliteTable('staff_users', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + password: text('password').notNull(), + staffRoleId: integer('staff_role_id').references(() => staffRoles.id), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const storeInfo = sqliteTable('store_info', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + description: text('description'), + imageUrl: text('image_url'), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), + owner: integer('owner').notNull().references(() => staffUsers.id), +}) + +export const units = sqliteTable('units', { + id: integer('id').primaryKey({ autoIncrement: true }), + shortNotation: text('short_notation').notNull(), + fullName: text('full_name').notNull(), +}, (t) => ({ + unq_short_notation: unique('unique_short_notation').on(t.shortNotation), +})) + +export const productAvailabilityActionEnum = sqliteEnum( + 'product_availability_action', + ['in', 'out'] +) + +export const productInfo = sqliteTable('product_info', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + shortDescription: text('short_description'), + longDescription: text('long_description'), + unitId: integer('unit_id').notNull().references(() => units.id), + price: text('price').notNull(), + marketPrice: text('market_price'), + images: text('images'), + isOutOfStock: integer('is_out_of_stock', { mode: 'boolean' }).notNull().default(false), + isSuspended: integer('is_suspended', { mode: 'boolean' }).notNull().default(false), + isFlashAvailable: integer('is_flash_available', { mode: 'boolean' }).notNull().default(false), + flashPrice: text('flash_price'), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), + incrementStep: real('increment_step').notNull().default(1), + productQuantity: real('product_quantity').notNull().default(1), + storeId: integer('store_id').references(() => storeInfo.id), + scheduledAvailability: integer('scheduled_availability', { mode: 'boolean' }).notNull().default(true), +}) + +export const productAvailabilitySchedules = sqliteTable('product_availability_schedules', { + id: integer('id').primaryKey({ autoIncrement: true }), + time: text('time').notNull(), + scheduleName: text('schedule_name').notNull().unique(), + action: productAvailabilityActionEnum('action').notNull(), + productIds: text('product_ids').notNull().default('[]'), + groupIds: text('group_ids').notNull().default('[]'), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), + lastUpdated: integer('last_updated', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const productGroupInfo = sqliteTable('product_group_info', { + id: integer('id').primaryKey({ autoIncrement: true }), + groupName: text('group_name').notNull(), + description: text('description'), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const productGroupMembership = sqliteTable('product_group_membership', { + productId: integer('product_id').notNull().references(() => productInfo.id), + groupId: integer('group_id').notNull().references(() => productGroupInfo.id), + addedAt: integer('added_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}, (t) => ({ + pk: unique('product_group_membership_pk').on(t.productId, t.groupId), +})) + +export const homeBanners = sqliteTable('home_banners', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + imageUrl: text('image_url').notNull(), + description: text('description'), + productIds: text('product_ids'), + redirectUrl: text('redirect_url'), + serialNum: integer('serial_num'), + isActive: integer('is_active', { mode: 'boolean' }).notNull().default(false), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), + lastUpdated: integer('last_updated', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const productReviews = sqliteTable('product_reviews', { + id: integer('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').notNull().references(() => users.id), + productId: integer('product_id').notNull().references(() => productInfo.id), + reviewBody: text('review_body').notNull(), + imageUrls: text('image_urls').default('[]'), + reviewTime: integer('review_time', { mode: 'timestamp' }).notNull().default(epochSeconds), + ratings: real('ratings').notNull(), + adminResponse: text('admin_response'), + adminResponseImages: text('admin_response_images').default('[]'), +}, (t) => ({ + ratingCheck: check('rating_check', sql`${t.ratings} >= 1 AND ${t.ratings} <= 5`), +})) + +export const uploadStatusEnum = sqliteEnum('upload_status', ['pending', 'claimed']) + +export const uploadUrlStatus = sqliteTable('upload_url_status', { + id: integer('id').primaryKey({ autoIncrement: true }), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), + key: text('key').notNull(), + status: uploadStatusEnum('status').notNull().default('pending'), +}) + +export const productTagInfo = sqliteTable('product_tag_info', { + id: integer('id').primaryKey({ autoIncrement: true }), + tagName: text('tag_name').notNull().unique(), + tagDescription: text('tag_description'), + imageUrl: text('image_url'), + isDashboardTag: integer('is_dashboard_tag', { mode: 'boolean' }).notNull().default(false), + relatedStores: text('related_stores').default('[]'), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const productTags = sqliteTable('product_tags', { + id: integer('id').primaryKey({ autoIncrement: true }), + productId: integer('product_id').notNull().references(() => productInfo.id), + tagId: integer('tag_id').notNull().references(() => productTagInfo.id), + assignedAt: integer('assigned_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}, (t) => ({ + unq_product_tag: unique('unique_product_tag').on(t.productId, t.tagId), +})) + +export const deliverySlotInfo = sqliteTable('delivery_slot_info', { + id: integer('id').primaryKey({ autoIncrement: true }), + deliveryTime: integer('delivery_time', { mode: 'timestamp' }).notNull(), + freezeTime: integer('freeze_time', { mode: 'timestamp' }).notNull(), + isActive: integer('is_active', { mode: 'boolean' }).notNull().default(true), + isFlash: integer('is_flash', { mode: 'boolean' }).notNull().default(false), + isCapacityFull: integer('is_capacity_full', { mode: 'boolean' }).notNull().default(false), + deliverySequence: text('delivery_sequence').default('{}'), + groupIds: text('group_ids').default('[]'), +}) + +export const vendorSnippets = sqliteTable('vendor_snippets', { + id: integer('id').primaryKey({ autoIncrement: true }), + snippetCode: text('snippet_code').notNull().unique(), + slotId: integer('slot_id').references(() => deliverySlotInfo.id), + isPermanent: integer('is_permanent', { mode: 'boolean' }).notNull().default(false), + productIds: text('product_ids').notNull(), + validTill: integer('valid_till', { mode: 'timestamp' }), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const productSlots = sqliteTable('product_slots', { + productId: integer('product_id').notNull().references(() => productInfo.id), + slotId: integer('slot_id').notNull().references(() => deliverySlotInfo.id), +}, (t) => ({ + pk: unique('product_slot_pk').on(t.productId, t.slotId), +})) + +export const specialDeals = sqliteTable('special_deals', { + id: integer('id').primaryKey({ autoIncrement: true }), + productId: integer('product_id').notNull().references(() => productInfo.id), + quantity: text('quantity').notNull(), + price: text('price').notNull(), + validTill: integer('valid_till', { mode: 'timestamp' }).notNull(), +}) + +export const paymentInfoTable = sqliteTable('payment_info', { + id: integer('id').primaryKey({ autoIncrement: true }), + status: text('status').notNull(), + gateway: text('gateway').notNull(), + orderId: text('order_id'), + token: text('token'), + merchantOrderId: text('merchant_order_id').notNull().unique(), + payload: text('payload'), +}) + +export const orders = sqliteTable('orders', { + id: integer('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').notNull().references(() => users.id), + addressId: integer('address_id').notNull().references(() => addresses.id), + slotId: integer('slot_id').references(() => deliverySlotInfo.id), + isCod: integer('is_cod', { mode: 'boolean' }).notNull().default(false), + isOnlinePayment: integer('is_online_payment', { mode: 'boolean' }).notNull().default(false), + paymentInfoId: integer('payment_info_id').references(() => paymentInfoTable.id), + totalAmount: text('total_amount').notNull(), + deliveryCharge: text('delivery_charge').notNull().default('0'), + readableId: integer('readable_id').notNull(), + adminNotes: text('admin_notes'), + userNotes: text('user_notes'), + orderGroupId: text('order_group_id'), + orderGroupProportion: text('order_group_proportion'), + isFlashDelivery: integer('is_flash_delivery', { mode: 'boolean' }).notNull().default(false), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const orderItems = sqliteTable('order_items', { + id: integer('id').primaryKey({ autoIncrement: true }), + orderId: integer('order_id').notNull().references(() => orders.id), + productId: integer('product_id').notNull().references(() => productInfo.id), + quantity: text('quantity').notNull(), + price: text('price').notNull(), + discountedPrice: text('discounted_price'), + is_packaged: integer('is_packaged', { mode: 'boolean' }).notNull().default(false), + is_package_verified: integer('is_package_verified', { mode: 'boolean' }).notNull().default(false), +}) + +export const paymentStatusEnum = sqliteEnum('payment_status', [ + 'pending', + 'success', + 'cod', + 'failed', +]) + +export const orderStatus = sqliteTable('order_status', { + id: integer('id').primaryKey({ autoIncrement: true }), + orderTime: integer('order_time', { mode: 'timestamp' }).notNull().default(epochSeconds), + userId: integer('user_id').notNull().references(() => users.id), + orderId: integer('order_id').notNull().references(() => orders.id), + isPackaged: integer('is_packaged', { mode: 'boolean' }).notNull().default(false), + isDelivered: integer('is_delivered', { mode: 'boolean' }).notNull().default(false), + isCancelled: integer('is_cancelled', { mode: 'boolean' }).notNull().default(false), + cancelReason: text('cancel_reason'), + isCancelledByAdmin: integer('is_cancelled_by_admin', { mode: 'boolean' }), + paymentStatus: paymentStatusEnum('payment_state').notNull().default('pending'), + cancellationUserNotes: text('cancellation_user_notes'), + cancellationAdminNotes: text('cancellation_admin_notes'), + cancellationReviewed: integer('cancellation_reviewed', { mode: 'boolean' }).notNull().default(false), + cancellationReviewedAt: integer('cancellation_reviewed_at', { mode: 'timestamp' }), + refundCouponId: integer('refund_coupon_id').references(() => coupons.id), +}) + +export const payments = sqliteTable('payments', { + id: integer('id').primaryKey({ autoIncrement: true }), + status: text('status').notNull(), + gateway: text('gateway').notNull(), + orderId: integer('order_id').notNull().references(() => orders.id), + token: text('token'), + merchantOrderId: text('merchant_order_id').notNull().unique(), + payload: text('payload'), +}) + +export const refunds = sqliteTable('refunds', { + id: integer('id').primaryKey({ autoIncrement: true }), + orderId: integer('order_id').notNull().references(() => orders.id), + refundAmount: text('refund_amount'), + refundStatus: text('refund_status').default('none'), + merchantRefundId: text('merchant_refund_id'), + refundProcessedAt: integer('refund_processed_at', { mode: 'timestamp' }), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const keyValStore = sqliteTable('key_val_store', { + key: text('key').primaryKey(), + value: text('value'), +}) + +export const notifications = sqliteTable('notifications', { + id: integer('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').notNull().references(() => users.id), + title: text('title').notNull(), + body: text('body').notNull(), + type: text('type'), + isRead: integer('is_read', { mode: 'boolean' }).notNull().default(false), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const productCategories = sqliteTable('product_categories', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + description: text('description'), +}) + +export const cartItems = sqliteTable('cart_items', { + id: integer('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').notNull().references(() => users.id), + productId: integer('product_id').notNull().references(() => productInfo.id), + quantity: text('quantity').notNull(), + addedAt: integer('added_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}, (t) => ({ + unq_user_product: unique('unique_user_product').on(t.userId, t.productId), +})) + +export const complaints = sqliteTable('complaints', { + id: integer('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').notNull().references(() => users.id), + orderId: integer('order_id').references(() => orders.id), + complaintBody: text('complaint_body').notNull(), + images: text('images'), + response: text('response'), + isResolved: integer('is_resolved', { mode: 'boolean' }).notNull().default(false), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const coupons = sqliteTable('coupons', { + id: integer('id').primaryKey({ autoIncrement: true }), + couponCode: text('coupon_code').notNull().unique('unique_coupon_code'), + isUserBased: integer('is_user_based', { mode: 'boolean' }).notNull().default(false), + discountPercent: text('discount_percent'), + flatDiscount: text('flat_discount'), + minOrder: text('min_order'), + productIds: text('product_ids'), + createdBy: integer('created_by').references(() => staffUsers.id), + maxValue: text('max_value'), + isApplyForAll: integer('is_apply_for_all', { mode: 'boolean' }).notNull().default(false), + validTill: integer('valid_till', { mode: 'timestamp' }), + maxLimitForUser: integer('max_limit_for_user'), + isInvalidated: integer('is_invalidated', { mode: 'boolean' }).notNull().default(false), + exclusiveApply: integer('exclusive_apply', { mode: 'boolean' }).notNull().default(false), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const couponUsage = sqliteTable('coupon_usage', { + id: integer('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').notNull().references(() => users.id), + couponId: integer('coupon_id').notNull().references(() => coupons.id), + orderId: integer('order_id').references(() => orders.id), + orderItemId: integer('order_item_id').references(() => orderItems.id), + usedAt: integer('used_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}) + +export const couponApplicableUsers = sqliteTable('coupon_applicable_users', { + id: integer('id').primaryKey({ autoIncrement: true }), + couponId: integer('coupon_id').notNull().references(() => coupons.id), + userId: integer('user_id').notNull().references(() => users.id), +}, (t) => ({ + unq_coupon_user: unique('unique_coupon_user').on(t.couponId, t.userId), +})) + +export const couponApplicableProducts = sqliteTable('coupon_applicable_products', { + id: integer('id').primaryKey({ autoIncrement: true }), + couponId: integer('coupon_id').notNull().references(() => coupons.id), + productId: integer('product_id').notNull().references(() => productInfo.id), +}, (t) => ({ + unq_coupon_product: unique('unique_coupon_product').on(t.couponId, t.productId), +})) + +export const userIncidents = sqliteTable('user_incidents', { + id: integer('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').notNull().references(() => users.id), + orderId: integer('order_id').references(() => orders.id), + dateAdded: integer('date_added', { mode: 'timestamp' }).notNull().default(epochSeconds), + adminComment: text('admin_comment'), + addedBy: integer('added_by').references(() => staffUsers.id), + negativityScore: integer('negativity_score'), +}) + +export const reservedCoupons = sqliteTable('reserved_coupons', { + id: integer('id').primaryKey({ autoIncrement: true }), + secretCode: text('secret_code').notNull().unique(), + couponCode: text('coupon_code').notNull(), + discountPercent: text('discount_percent'), + flatDiscount: text('flat_discount'), + minOrder: text('min_order'), + productIds: text('product_ids'), + maxValue: text('max_value'), + validTill: integer('valid_till', { mode: 'timestamp' }), + maxLimitForUser: integer('max_limit_for_user'), + exclusiveApply: integer('exclusive_apply', { mode: 'boolean' }).notNull().default(false), + isRedeemed: integer('is_redeemed', { mode: 'boolean' }).notNull().default(false), + redeemedBy: integer('redeemed_by').references(() => users.id), + redeemedAt: integer('redeemed_at', { mode: 'timestamp' }), + createdBy: integer('created_by').notNull().references(() => staffUsers.id), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), +}, (t) => ({ + unq_secret_code: unique('unique_secret_code').on(t.secretCode), +})) + +export const notifCreds = sqliteTable('notif_creds', { + id: integer('id').primaryKey({ autoIncrement: true }), + token: text('token').notNull().unique(), + addedAt: integer('added_at', { mode: 'timestamp' }).notNull().default(epochSeconds), + userId: integer('user_id').notNull().references(() => users.id), + lastVerified: integer('last_verified', { mode: 'timestamp' }), +}) + +export const unloggedUserTokens = sqliteTable('unlogged_user_tokens', { + id: integer('id').primaryKey({ autoIncrement: true }), + token: text('token').notNull().unique(), + addedAt: integer('added_at', { mode: 'timestamp' }).notNull().default(epochSeconds), + lastVerified: integer('last_verified', { mode: 'timestamp' }), +}) + +export const userNotifications = sqliteTable('user_notifications', { + id: integer('id').primaryKey({ autoIncrement: true }), + title: text('title').notNull(), + imageUrl: text('image_url'), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(epochSeconds), + body: text('body').notNull(), + applicableUsers: text('applicable_users'), +}) + +export const uploadUrlStatusRelations = relations(uploadUrlStatus, ({}) => ({})) + +export const userCredsRelations = relations(userCreds, ({ one }) => ({ + user: one(users, { fields: [userCreds.userId], references: [users.id] }), +})) + +export const staffUsersRelations = relations(staffUsers, ({ one, many }) => ({ + role: one(staffRoles, { fields: [staffUsers.staffRoleId], references: [staffRoles.id] }), + coupons: many(coupons), + stores: many(storeInfo), +})) + +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 }) => ({ + products: many(productInfo), +})) + +export const productInfoRelations = relations(productInfo, ({ one, many }) => ({ + unit: one(units, { fields: [productInfo.unitId], references: [units.id] }), + store: one(storeInfo, { fields: [productInfo.storeId], references: [storeInfo.id] }), + productSlots: many(productSlots), + specialDeals: many(specialDeals), + orderItems: many(orderItems), + cartItems: many(cartItems), + tags: many(productTags), + applicableCoupons: many(couponApplicableProducts), + reviews: many(productReviews), + groups: many(productGroupMembership), +})) + +export const productTagInfoRelations = relations(productTagInfo, ({ many }) => ({ + products: many(productTags), +})) + +export const productTagsRelations = relations(productTags, ({ one }) => ({ + product: one(productInfo, { fields: [productTags.productId], references: [productInfo.id] }), + tag: one(productTagInfo, { fields: [productTags.tagId], references: [productTagInfo.id] }), +})) + +export const deliverySlotInfoRelations = relations(deliverySlotInfo, ({ many }) => ({ + productSlots: many(productSlots), + orders: many(orders), + vendorSnippets: many(vendorSnippets), +})) + +export const vendorSnippetsRelations = relations(vendorSnippets, ({ one }) => ({ + slot: one(deliverySlotInfo, { fields: [vendorSnippets.slotId], references: [deliverySlotInfo.id] }), +})) + +export const productSlotsRelations = relations(productSlots, ({ one }) => ({ + product: one(productInfo, { fields: [productSlots.productId], references: [productInfo.id] }), + slot: one(deliverySlotInfo, { fields: [productSlots.slotId], references: [deliverySlotInfo.id] }), +})) + +export const specialDealsRelations = relations(specialDeals, ({ one }) => ({ + product: one(productInfo, { fields: [specialDeals.productId], references: [productInfo.id] }), +})) + +export const ordersRelations = relations(orders, ({ one, many }) => ({ + user: one(users, { fields: [orders.userId], references: [users.id] }), + address: one(addresses, { fields: [orders.addressId], references: [addresses.id] }), + slot: one(deliverySlotInfo, { fields: [orders.slotId], references: [deliverySlotInfo.id] }), + orderItems: many(orderItems), + payment: one(payments), + paymentInfo: one(paymentInfoTable, { fields: [orders.paymentInfoId], references: [paymentInfoTable.id] }), + orderStatus: many(orderStatus), + refunds: many(refunds), + couponUsages: many(couponUsage), + userIncidents: many(userIncidents), +})) + +export const orderItemsRelations = relations(orderItems, ({ one }) => ({ + order: one(orders, { fields: [orderItems.orderId], references: [orders.id] }), + product: one(productInfo, { fields: [orderItems.productId], references: [productInfo.id] }), +})) + +export const orderStatusRelations = relations(orderStatus, ({ one }) => ({ + order: one(orders, { fields: [orderStatus.orderId], references: [orders.id] }), + user: one(users, { fields: [orderStatus.userId], references: [users.id] }), + refundCoupon: one(coupons, { fields: [orderStatus.refundCouponId], references: [coupons.id] }), +})) + +export const paymentInfoRelations = relations(paymentInfoTable, ({ one }) => ({ + order: one(orders, { fields: [paymentInfoTable.id], references: [orders.paymentInfoId] }), +})) + +export const paymentsRelations = relations(payments, ({ one }) => ({ + order: one(orders, { fields: [payments.orderId], references: [orders.id] }), +})) + +export const refundsRelations = relations(refunds, ({ one }) => ({ + order: one(orders, { fields: [refunds.orderId], references: [orders.id] }), +})) + +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] }), +})) + +export const complaintsRelations = relations(complaints, ({ one }) => ({ + user: one(users, { fields: [complaints.userId], references: [users.id] }), + order: one(orders, { fields: [complaints.orderId], references: [orders.id] }), +})) + +export const couponsRelations = relations(coupons, ({ one, many }) => ({ + creator: one(staffUsers, { fields: [coupons.createdBy], references: [staffUsers.id] }), + usages: many(couponUsage), + applicableUsers: many(couponApplicableUsers), + applicableProducts: many(couponApplicableProducts), +})) + +export const couponUsageRelations = relations(couponUsage, ({ one }) => ({ + user: one(users, { fields: [couponUsage.userId], references: [users.id] }), + coupon: one(coupons, { fields: [couponUsage.couponId], references: [coupons.id] }), + order: one(orders, { fields: [couponUsage.orderId], references: [orders.id] }), + orderItem: one(orderItems, { fields: [couponUsage.orderItemId], references: [orderItems.id] }), +})) + +export const userDetailsRelations = relations(userDetails, ({ one }) => ({ + user: one(users, { fields: [userDetails.userId], references: [users.id] }), +})) + +export const notifCredsRelations = relations(notifCreds, ({ one }) => ({ + user: one(users, { fields: [notifCreds.userId], references: [users.id] }), +})) + +export const userNotificationsRelations = relations(userNotifications, ({}) => ({})) + +export const storeInfoRelations = relations(storeInfo, ({ one, many }) => ({ + owner: one(staffUsers, { fields: [storeInfo.owner], references: [staffUsers.id] }), + products: many(productInfo), +})) + +export const couponApplicableUsersRelations = relations(couponApplicableUsers, ({ one }) => ({ + coupon: one(coupons, { fields: [couponApplicableUsers.couponId], references: [coupons.id] }), + user: one(users, { fields: [couponApplicableUsers.userId], references: [users.id] }), +})) + +export const couponApplicableProductsRelations = relations(couponApplicableProducts, ({ one }) => ({ + coupon: one(coupons, { fields: [couponApplicableProducts.couponId], references: [coupons.id] }), + product: one(productInfo, { fields: [couponApplicableProducts.productId], references: [productInfo.id] }), +})) + +export const reservedCouponsRelations = relations(reservedCoupons, ({ one }) => ({ + redeemedUser: one(users, { fields: [reservedCoupons.redeemedBy], references: [users.id] }), + creator: one(staffUsers, { fields: [reservedCoupons.createdBy], references: [staffUsers.id] }), +})) + +export const productReviewsRelations = relations(productReviews, ({ one }) => ({ + user: one(users, { fields: [productReviews.userId], references: [users.id] }), + 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), +})) + +export const productGroupMembershipRelations = relations(productGroupMembership, ({ one }) => ({ + product: one(productInfo, { fields: [productGroupMembership.productId], references: [productInfo.id] }), + group: one(productGroupInfo, { fields: [productGroupMembership.groupId], references: [productGroupInfo.id] }), +})) + +export const homeBannersRelations = relations(homeBanners, ({}) => ({})) + +export const staffRolesRelations = relations(staffRoles, ({ many }) => ({ + staffUsers: many(staffUsers), + rolePermissions: many(staffRolePermissions), +})) + +export const staffPermissionsRelations = relations(staffPermissions, ({ many }) => ({ + rolePermissions: many(staffRolePermissions), +})) + +export const staffRolePermissionsRelations = relations(staffRolePermissions, ({ one }) => ({ + role: one(staffRoles, { fields: [staffRolePermissions.staffRoleId], references: [staffRoles.id] }), + permission: one(staffPermissions, { fields: [staffRolePermissions.staffPermissionId], references: [staffPermissions.id] }), +})) + +export const userIncidentsRelations = relations(userIncidents, ({ one }) => ({ + user: one(users, { fields: [userIncidents.userId], references: [users.id] }), + order: one(orders, { fields: [userIncidents.orderId], references: [orders.id] }), + addedBy: one(staffUsers, { fields: [userIncidents.addedBy], references: [staffUsers.id] }), +})) + +export const productAvailabilitySchedulesRelations = relations( + productAvailabilitySchedules, + ({}) => ({}) +) + +export const usersRelations = relations(users, ({ many, one }) => ({ + addresses: many(addresses), + orders: many(orders), + notifications: many(notifications), + cartItems: many(cartItems), + userCreds: one(userCreds), + coupons: many(coupons), + couponUsages: many(couponUsage), + applicableCoupons: many(couponApplicableUsers), + userDetails: one(userDetails), + notifCreds: many(notifCreds), + userIncidents: many(userIncidents), +})) diff --git a/apps/backend/src/db/schema.ts b/apps/backend/src/db/schema.ts old mode 100755 new mode 100644 index 939e04d..36f5ca8 --- a/apps/backend/src/db/schema.ts +++ b/apps/backend/src/db/schema.ts @@ -1,706 +1 @@ -import { pgTable, pgSchema, integer, varchar, date, boolean, timestamp, numeric, jsonb, pgEnum, unique, real, text, check, decimal } from "drizzle-orm/pg-core"; -import { relations, sql } from "drizzle-orm"; - -const mf = pgSchema('mf'); - - - -export const users = mf.table('users', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - name: varchar({ length: 255 }), - email: varchar({ length: 255 }), - mobile: varchar({ length: 255 }), - createdAt: timestamp('created_at').notNull().defaultNow(), -}, (t) => ({ - unq_email: unique('unique_email').on(t.email), -})); - -export const userDetails = mf.table('user_details', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - userId: integer('user_id').notNull().references(() => users.id).unique(), - bio: varchar('bio', { length: 500 }), - dateOfBirth: date('date_of_birth'), - gender: varchar('gender', { length: 20 }), - occupation: varchar('occupation', { length: 100 }), - profileImage: varchar('profile_image', { length: 500 }), - isSuspended: boolean('is_suspended').notNull().default(false), - createdAt: timestamp('created_at').notNull().defaultNow(), - updatedAt: timestamp('updated_at').notNull().defaultNow(), -}); - -export const userCreds = mf.table('user_creds', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - userId: integer('user_id').notNull().references(() => users.id), - userPassword: varchar('user_password', { length: 255 }).notNull(), - createdAt: timestamp('created_at').notNull().defaultNow(), -}); - -export const addresses = mf.table('addresses', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - userId: integer('user_id').notNull().references(() => users.id), - name: varchar('name', { length: 255 }).notNull(), - phone: varchar('phone', { length: 15 }).notNull(), - addressLine1: varchar('address_line1', { length: 255 }).notNull(), - addressLine2: varchar('address_line2', { length: 255 }), - city: varchar('city', { length: 100 }).notNull(), - state: varchar('state', { length: 100 }).notNull(), - pincode: varchar('pincode', { length: 10 }).notNull(), - isDefault: boolean('is_default').notNull().default(false), - latitude: real('latitude'), - longitude: real('longitude'), - 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), - createdAt: timestamp('created_at').notNull().defaultNow(), -}); - -export const staffUsers = mf.table('staff_users', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - name: varchar({ length: 255 }).notNull(), - password: varchar({ length: 255 }).notNull(), - staffRoleId: integer('staff_role_id').references(() => staffRoles.id), - createdAt: timestamp('created_at').notNull().defaultNow(), -}); - -export const storeInfo = mf.table('store_info', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - name: varchar({ length: 255 }).notNull(), - description: varchar({ length: 500 }), - imageUrl: varchar('image_url', { length: 500 }), - createdAt: timestamp('created_at').notNull().defaultNow(), - owner: integer('owner').notNull().references(() => staffUsers.id), -}); - -export const units = mf.table('units', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - shortNotation: varchar('short_notation', { length: 50 }).notNull(), - fullName: varchar('full_name', { length: 100 }).notNull(), -}, (t) => ({ - unq_short_notation: unique('unique_short_notation').on(t.shortNotation), -})); - -export const productAvailabilityActionEnum = pgEnum('product_availability_action', ['in', 'out']); - -export const productInfo = mf.table('product_info', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - name: varchar({ length: 255 }).notNull(), - shortDescription: varchar('short_description', { length: 500 }), - longDescription: varchar('long_description', { length: 1000 }), - unitId: integer('unit_id').notNull().references(() => units.id), - price: numeric({ precision: 10, scale: 2 }).notNull(), - marketPrice: numeric('market_price', { precision: 10, scale: 2 }), - images: jsonb('images'), - isOutOfStock: boolean('is_out_of_stock').notNull().default(false), - isSuspended: boolean('is_suspended').notNull().default(false), - isFlashAvailable: boolean('is_flash_available').notNull().default(false), - flashPrice: numeric('flash_price', { precision: 10, scale: 2 }), - createdAt: timestamp('created_at').notNull().defaultNow(), - incrementStep: real('increment_step').notNull().default(1), - productQuantity: real('product_quantity').notNull().default(1), - storeId: integer('store_id').references(() => storeInfo.id), - scheduledAvailability: boolean('scheduled_availability').notNull().default(true), -}); - -export const productAvailabilitySchedules = mf.table('product_availability_schedules', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - time: varchar('time', { length: 10 }).notNull(), - scheduleName: varchar('schedule_name', { length: 255 }).notNull().unique(), - action: productAvailabilityActionEnum('action').notNull(), - productIds: integer('product_ids').array().notNull().default([]), - groupIds: integer('group_ids').array().notNull().default([]), - createdAt: timestamp('created_at').notNull().defaultNow(), - lastUpdated: timestamp('last_updated').notNull().defaultNow(), -}); - -export const productGroupInfo = mf.table('product_group_info', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - groupName: varchar('group_name', { length: 255 }).notNull(), - description: varchar({ length: 500 }), - createdAt: timestamp('created_at').notNull().defaultNow(), -}); - -export const productGroupMembership = mf.table('product_group_membership', { - productId: integer('product_id').notNull().references(() => productInfo.id), - groupId: integer('group_id').notNull().references(() => productGroupInfo.id), - addedAt: timestamp('added_at').notNull().defaultNow(), -}, (t) => ({ - pk: unique('product_group_membership_pk').on(t.productId, t.groupId), -})); - -export const homeBanners = mf.table('home_banners', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - name: varchar('name', { length: 255 }).notNull(), - imageUrl: varchar('image_url', { length: 500 }).notNull(), - description: varchar('description', { length: 500 }), - productIds: integer('product_ids').array(), - redirectUrl: varchar('redirect_url', { length: 500 }), - serialNum: integer('serial_num'), - isActive: boolean('is_active').notNull().default(false), - createdAt: timestamp('created_at').notNull().defaultNow(), - lastUpdated: timestamp('last_updated').notNull().defaultNow(), -}); - -export const productReviews = mf.table('product_reviews', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - userId: integer('user_id').notNull().references(() => users.id), - productId: integer('product_id').notNull().references(() => productInfo.id), - reviewBody: text('review_body').notNull(), - imageUrls: jsonb('image_urls').$defaultFn(() => []), - reviewTime: timestamp('review_time').notNull().defaultNow(), - ratings: real('ratings').notNull(), - adminResponse: text('admin_response'), - adminResponseImages: jsonb('admin_response_images').$defaultFn(() => []), -}, (t) => ({ - ratingCheck: check('rating_check', sql`${t.ratings} >= 1 AND ${t.ratings} <= 5`), -})); - -export const uploadStatusEnum = pgEnum('upload_status', ['pending', 'claimed']); - -export const staffRoleEnum = pgEnum('staff_role', ['super_admin', 'admin', 'marketer', 'delivery_staff']); - -export const staffPermissionEnum = pgEnum('staff_permission', ['crud_product', 'make_coupon', 'crud_staff_users']); - -export const uploadUrlStatus = mf.table('upload_url_status', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - createdAt: timestamp('created_at').notNull().defaultNow(), - key: varchar('key', { length: 500 }).notNull(), - status: uploadStatusEnum('status').notNull().default('pending'), -}); - -export const productTagInfo = mf.table('product_tag_info', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - tagName: varchar('tag_name', { length: 100 }).notNull().unique(), - tagDescription: varchar('tag_description', { length: 500 }), - imageUrl: varchar('image_url', { length: 500 }), - isDashboardTag: boolean('is_dashboard_tag').notNull().default(false), - relatedStores: jsonb('related_stores').$defaultFn(() => []), - createdAt: timestamp('created_at').notNull().defaultNow(), -}); - -export const productTags = mf.table('product_tags', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - productId: integer('product_id').notNull().references(() => productInfo.id), - tagId: integer('tag_id').notNull().references(() => productTagInfo.id), - assignedAt: timestamp('assigned_at').notNull().defaultNow(), -}, (t) => ({ - unq_product_tag: unique('unique_product_tag').on(t.productId, t.tagId), -})); - -export const deliverySlotInfo = mf.table('delivery_slot_info', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - deliveryTime: timestamp('delivery_time').notNull(), - freezeTime: timestamp('freeze_time').notNull(), - isActive: boolean('is_active').notNull().default(true), - isFlash: boolean('is_flash').notNull().default(false), - isCapacityFull: boolean('is_capacity_full').notNull().default(false), - deliverySequence: jsonb('delivery_sequence').$defaultFn(() => {}), - groupIds: jsonb('group_ids').$defaultFn(() => []), -}); - -export const vendorSnippets = mf.table('vendor_snippets', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - snippetCode: varchar('snippet_code', { length: 255 }).notNull().unique(), - slotId: integer('slot_id').references(() => deliverySlotInfo.id), - isPermanent: boolean('is_permanent').notNull().default(false), - productIds: integer('product_ids').array().notNull(), - validTill: timestamp('valid_till'), - createdAt: timestamp('created_at').notNull().defaultNow(), -}); - -export const vendorSnippetsRelations = relations(vendorSnippets, ({ one }) => ({ - slot: one(deliverySlotInfo, { fields: [vendorSnippets.slotId], references: [deliverySlotInfo.id] }), -})); - -export const productSlots = mf.table('product_slots', { - productId: integer('product_id').notNull().references(() => productInfo.id), - slotId: integer('slot_id').notNull().references(() => deliverySlotInfo.id), -}, (t) => ({ - pk: unique('product_slot_pk').on(t.productId, t.slotId), -})); - -export const specialDeals = mf.table('special_deals', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - productId: integer('product_id').notNull().references(() => productInfo.id), - quantity: numeric({ precision: 10, scale: 2 }).notNull(), - price: numeric({ precision: 10, scale: 2 }).notNull(), - validTill: timestamp('valid_till').notNull(), -}); - -export const orders = mf.table('orders', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - userId: integer('user_id').notNull().references(() => users.id), - addressId: integer('address_id').notNull().references(() => addresses.id), - slotId: integer('slot_id').references(() => deliverySlotInfo.id), - isCod: boolean('is_cod').notNull().default(false), - isOnlinePayment: boolean('is_online_payment').notNull().default(false), - paymentInfoId: integer('payment_info_id').references(() => paymentInfoTable.id), - totalAmount: numeric('total_amount', { precision: 10, scale: 2 }).notNull(), - deliveryCharge: numeric('delivery_charge', { precision: 10, scale: 2 }).notNull().default('0'), - readableId: integer('readable_id').notNull(), - adminNotes: text('admin_notes'), - userNotes: text('user_notes'), - orderGroupId: varchar('order_group_id', { length: 255 }), - orderGroupProportion: decimal('order_group_proportion', { precision: 10, scale: 4 }), - isFlashDelivery: boolean('is_flash_delivery').notNull().default(false), - createdAt: timestamp('created_at').notNull().defaultNow(), -}); - -export const orderItems = mf.table('order_items', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - orderId: integer('order_id').notNull().references(() => orders.id), - productId: integer('product_id').notNull().references(() => productInfo.id), - quantity: varchar('quantity', { length: 50 }).notNull(), - price: numeric({ precision: 10, scale: 2 }).notNull(), - discountedPrice: numeric('discounted_price', { precision: 10, scale: 2 }), - is_packaged: boolean('is_packaged').notNull().default(false), - is_package_verified: boolean('is_package_verified').notNull().default(false), -}); - -export const paymentStatusEnum = pgEnum('payment_status', ['pending', 'success', 'cod', 'failed']); - -export const orderStatus = mf.table('order_status', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - orderTime: timestamp('order_time').notNull().defaultNow(), - userId: integer('user_id').notNull().references(() => users.id), - orderId: integer('order_id').notNull().references(() => orders.id), - isPackaged: boolean('is_packaged').notNull().default(false), - isDelivered: boolean('is_delivered').notNull().default(false), - isCancelled: boolean('is_cancelled').notNull().default(false), - cancelReason: varchar('cancel_reason', { length: 255 }), - isCancelledByAdmin: boolean('is_cancelled_by_admin'), - paymentStatus: paymentStatusEnum('payment_state').notNull().default('pending'), - cancellationUserNotes: text('cancellation_user_notes'), - cancellationAdminNotes: text('cancellation_admin_notes'), - cancellationReviewed: boolean('cancellation_reviewed').notNull().default(false), - cancellationReviewedAt: timestamp('cancellation_reviewed_at'), - refundCouponId: integer('refund_coupon_id').references(() => coupons.id), -}); - -export const paymentInfoTable = mf.table('payment_info', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - status: varchar({ length: 50 }).notNull(), - gateway: varchar({ length: 50 }).notNull(), - orderId: varchar('order_id', { length: 500 }), - token: varchar({ length: 500 }), - merchantOrderId: varchar('merchant_order_id', { length: 255 }).notNull().unique(), - payload: jsonb('payload'), -}); - -export const payments = mf.table('payments', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - status: varchar({ length: 50 }).notNull(), - gateway: varchar({ length: 50 }).notNull(), - orderId: integer('order_id').notNull().references(() => orders.id), - token: varchar({ length: 500 }), - merchantOrderId: varchar('merchant_order_id', { length: 255 }).notNull().unique(), - payload: jsonb('payload'), -}); - -export const refunds = mf.table('refunds', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - orderId: integer('order_id').notNull().references(() => orders.id), - refundAmount: numeric('refund_amount', { precision: 10, scale: 2 }), - refundStatus: varchar('refund_status', { length: 50 }).default('none'), - merchantRefundId: varchar('merchant_refund_id', { length: 255 }), - refundProcessedAt: timestamp('refund_processed_at'), - createdAt: timestamp('created_at').notNull().defaultNow(), -}); - -export const keyValStore = mf.table('key_val_store', { - key: varchar('key', { length: 255 }).primaryKey(), - value: jsonb('value'), -}); - -export const notifications = mf.table('notifications', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - userId: integer('user_id').notNull().references(() => users.id), - title: varchar({ length: 255 }).notNull(), - body: varchar({ length: 512 }).notNull(), - type: varchar({ length: 50 }), - isRead: boolean('is_read').notNull().default(false), - 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), - productId: integer('product_id').notNull().references(() => productInfo.id), - quantity: numeric({ precision: 10, scale: 2 }).notNull(), - addedAt: timestamp('added_at').notNull().defaultNow(), -}, (t) => ({ - unq_user_product: unique('unique_user_product').on(t.userId, t.productId), -})); - -export const complaints = mf.table('complaints', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - userId: integer('user_id').notNull().references(() => users.id), - orderId: integer('order_id').references(() => orders.id), - complaintBody: varchar('complaint_body', { length: 1000 }).notNull(), - images: jsonb('images'), - response: varchar('response', { length: 1000 }), - isResolved: boolean('is_resolved').notNull().default(false), - createdAt: timestamp('created_at').notNull().defaultNow(), -}); - -export const coupons = mf.table('coupons', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - couponCode: varchar('coupon_code', { length: 50 }).notNull().unique('unique_coupon_code'), - isUserBased: boolean('is_user_based').notNull().default(false), - discountPercent: numeric('discount_percent', { precision: 5, scale: 2 }), - flatDiscount: numeric('flat_discount', { precision: 10, scale: 2 }), - minOrder: numeric('min_order', { precision: 10, scale: 2 }), - productIds: jsonb('product_ids'), - createdBy: integer('created_by').references(() => staffUsers.id), - maxValue: numeric('max_value', { precision: 10, scale: 2 }), - isApplyForAll: boolean('is_apply_for_all').notNull().default(false), - validTill: timestamp('valid_till'), - maxLimitForUser: integer('max_limit_for_user'), - isInvalidated: boolean('is_invalidated').notNull().default(false), - exclusiveApply: boolean('exclusive_apply').notNull().default(false), - createdAt: timestamp('created_at').notNull().defaultNow(), -}); - -export const couponUsage = mf.table('coupon_usage', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - userId: integer('user_id').notNull().references(() => users.id), - couponId: integer('coupon_id').notNull().references(() => coupons.id), - orderId: integer('order_id').references(() => orders.id), - orderItemId: integer('order_item_id').references(() => orderItems.id), - usedAt: timestamp('used_at').notNull().defaultNow(), -}); - -export const couponApplicableUsers = mf.table('coupon_applicable_users', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - couponId: integer('coupon_id').notNull().references(() => coupons.id), - userId: integer('user_id').notNull().references(() => users.id), -}, (t) => ({ - unq_coupon_user: unique('unique_coupon_user').on(t.couponId, t.userId), -})); - -export const couponApplicableProducts = mf.table('coupon_applicable_products', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - couponId: integer('coupon_id').notNull().references(() => coupons.id), - productId: integer('product_id').notNull().references(() => productInfo.id), -}, (t) => ({ - unq_coupon_product: unique('unique_coupon_product').on(t.couponId, t.productId), -})); - -export const userIncidents = mf.table('user_incidents', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - userId: integer('user_id').notNull().references(() => users.id), - orderId: integer('order_id').references(() => orders.id), - dateAdded: timestamp('date_added').notNull().defaultNow(), - adminComment: text('admin_comment'), - addedBy: integer('added_by').references(() => staffUsers.id), - negativityScore: integer('negativity_score'), -}); - -export const reservedCoupons = mf.table('reserved_coupons', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - secretCode: varchar('secret_code', { length: 50 }).notNull().unique(), - couponCode: varchar('coupon_code', { length: 50 }).notNull(), - discountPercent: numeric('discount_percent', { precision: 5, scale: 2 }), - flatDiscount: numeric('flat_discount', { precision: 10, scale: 2 }), - minOrder: numeric('min_order', { precision: 10, scale: 2 }), - productIds: jsonb('product_ids'), - maxValue: numeric('max_value', { precision: 10, scale: 2 }), - validTill: timestamp('valid_till'), - maxLimitForUser: integer('max_limit_for_user'), - exclusiveApply: boolean('exclusive_apply').notNull().default(false), - isRedeemed: boolean('is_redeemed').notNull().default(false), - redeemedBy: integer('redeemed_by').references(() => users.id), - redeemedAt: timestamp('redeemed_at'), - createdBy: integer('created_by').notNull().references(() => staffUsers.id), - createdAt: timestamp('created_at').notNull().defaultNow(), -}, (t) => ({ - unq_secret_code: unique('unique_secret_code').on(t.secretCode), -})); - -export const notifCreds = mf.table('notif_creds', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - token: varchar({ length: 500 }).notNull().unique(), - addedAt: timestamp('added_at').notNull().defaultNow(), - userId: integer('user_id').notNull().references(() => users.id), - lastVerified: timestamp('last_verified'), -}); - -export const unloggedUserTokens = mf.table('unlogged_user_tokens', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - token: varchar({ length: 500 }).notNull().unique(), - addedAt: timestamp('added_at').notNull().defaultNow(), - 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(), - createdAt: timestamp('created_at').notNull().defaultNow(), -}, (t) => ({ - unq_role_name: unique('unique_role_name').on(t.roleName), -})); - -export const staffPermissions = mf.table('staff_permissions', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - permissionName: staffPermissionEnum('permission_name').notNull(), - createdAt: timestamp('created_at').notNull().defaultNow(), -}, (t) => ({ - unq_permission_name: unique('unique_permission_name').on(t.permissionName), -})); - -export const staffRolePermissions = mf.table('staff_role_permissions', { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - staffRoleId: integer('staff_role_id').notNull().references(() => staffRoles.id), - staffPermissionId: integer('staff_permission_id').notNull().references(() => staffPermissions.id), - createdAt: timestamp('created_at').notNull().defaultNow(), -}, (t) => ({ - unq_role_permission: unique('unique_role_permission').on(t.staffRoleId, t.staffPermissionId), -})); - -// Relations -export const usersRelations = relations(users, ({ many, one }) => ({ - addresses: many(addresses), - orders: many(orders), - notifications: many(notifications), - cartItems: many(cartItems), - userCreds: one(userCreds), - coupons: many(coupons), - couponUsages: many(couponUsage), - applicableCoupons: many(couponApplicableUsers), - userDetails: one(userDetails), - notifCreds: many(notifCreds), - userIncidents: many(userIncidents), -})); - -export const userCredsRelations = relations(userCreds, ({ one }) => ({ - user: one(users, { fields: [userCreds.userId], references: [users.id] }), -})); - -export const staffUsersRelations = relations(staffUsers, ({ one, many }) => ({ - role: one(staffRoles, { fields: [staffUsers.staffRoleId], references: [staffRoles.id] }), - coupons: many(coupons), - stores: many(storeInfo), -})); - -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 }) => ({ - products: many(productInfo), -})); - -export const productInfoRelations = relations(productInfo, ({ one, many }) => ({ - unit: one(units, { fields: [productInfo.unitId], references: [units.id] }), - store: one(storeInfo, { fields: [productInfo.storeId], references: [storeInfo.id] }), - productSlots: many(productSlots), - specialDeals: many(specialDeals), - orderItems: many(orderItems), - cartItems: many(cartItems), - tags: many(productTags), - applicableCoupons: many(couponApplicableProducts), - reviews: many(productReviews), - groups: many(productGroupMembership), -})); - -export const productTagInfoRelations = relations(productTagInfo, ({ many }) => ({ - products: many(productTags), -})); - -export const productTagsRelations = relations(productTags, ({ one }) => ({ - product: one(productInfo, { fields: [productTags.productId], references: [productInfo.id] }), - tag: one(productTagInfo, { fields: [productTags.tagId], references: [productTagInfo.id] }), -})); - -export const deliverySlotInfoRelations = relations(deliverySlotInfo, ({ many }) => ({ - productSlots: many(productSlots), - orders: many(orders), - vendorSnippets: many(vendorSnippets), -})); - -export const productSlotsRelations = relations(productSlots, ({ one }) => ({ - product: one(productInfo, { fields: [productSlots.productId], references: [productInfo.id] }), - slot: one(deliverySlotInfo, { fields: [productSlots.slotId], references: [deliverySlotInfo.id] }), -})); - -export const specialDealsRelations = relations(specialDeals, ({ one }) => ({ - product: one(productInfo, { fields: [specialDeals.productId], references: [productInfo.id] }), -})); - -export const ordersRelations = relations(orders, ({ one, many }) => ({ - user: one(users, { fields: [orders.userId], references: [users.id] }), - address: one(addresses, { fields: [orders.addressId], references: [addresses.id] }), - slot: one(deliverySlotInfo, { fields: [orders.slotId], references: [deliverySlotInfo.id] }), - orderItems: many(orderItems), - payment: one(payments), - paymentInfo: one(paymentInfoTable, { fields: [orders.paymentInfoId], references: [paymentInfoTable.id] }), - orderStatus: many(orderStatus), - refunds: many(refunds), - couponUsages: many(couponUsage), - userIncidents: many(userIncidents), -})); - -export const orderItemsRelations = relations(orderItems, ({ one }) => ({ - order: one(orders, { fields: [orderItems.orderId], references: [orders.id] }), - product: one(productInfo, { fields: [orderItems.productId], references: [productInfo.id] }), -})); - -export const orderStatusRelations = relations(orderStatus, ({ one }) => ({ - order: one(orders, { fields: [orderStatus.orderId], references: [orders.id] }), - user: one(users, { fields: [orderStatus.userId], references: [users.id] }), - refundCoupon: one(coupons, { fields: [orderStatus.refundCouponId], references: [coupons.id] }), -})); - -export const paymentInfoRelations = relations(paymentInfoTable, ({ one }) => ({ - order: one(orders, { fields: [paymentInfoTable.id], references: [orders.paymentInfoId] }), -})); - -export const paymentsRelations = relations(payments, ({ one }) => ({ - order: one(orders, { fields: [payments.orderId], references: [orders.id] }), -})); - -export const refundsRelations = relations(refunds, ({ one }) => ({ - order: one(orders, { fields: [refunds.orderId], references: [orders.id] }), -})); - -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] }), -})); - -export const complaintsRelations = relations(complaints, ({ one }) => ({ - user: one(users, { fields: [complaints.userId], references: [users.id] }), - order: one(orders, { fields: [complaints.orderId], references: [orders.id] }), -})); - -export const couponsRelations = relations(coupons, ({ one, many }) => ({ - creator: one(staffUsers, { fields: [coupons.createdBy], references: [staffUsers.id] }), - usages: many(couponUsage), - applicableUsers: many(couponApplicableUsers), - applicableProducts: many(couponApplicableProducts), -})); - -export const couponUsageRelations = relations(couponUsage, ({ one }) => ({ - user: one(users, { fields: [couponUsage.userId], references: [users.id] }), - coupon: one(coupons, { fields: [couponUsage.couponId], references: [coupons.id] }), - order: one(orders, { fields: [couponUsage.orderId], references: [orders.id] }), - orderItem: one(orderItems, { fields: [couponUsage.orderItemId], references: [orderItems.id] }), -})); - -export const userDetailsRelations = relations(userDetails, ({ one }) => ({ - user: one(users, { fields: [userDetails.userId], references: [users.id] }), -})); - -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), -})); - -export const couponApplicableUsersRelations = relations(couponApplicableUsers, ({ one }) => ({ - coupon: one(coupons, { fields: [couponApplicableUsers.couponId], references: [coupons.id] }), - user: one(users, { fields: [couponApplicableUsers.userId], references: [users.id] }), -})); - -export const couponApplicableProductsRelations = relations(couponApplicableProducts, ({ one }) => ({ - coupon: one(coupons, { fields: [couponApplicableProducts.couponId], references: [coupons.id] }), - product: one(productInfo, { fields: [couponApplicableProducts.productId], references: [productInfo.id] }), -})); - -export const reservedCouponsRelations = relations(reservedCoupons, ({ one }) => ({ - redeemedUser: one(users, { fields: [reservedCoupons.redeemedBy], references: [users.id] }), - creator: one(staffUsers, { fields: [reservedCoupons.createdBy], references: [staffUsers.id] }), -})); - -export const productReviewsRelations = relations(productReviews, ({ one }) => ({ - user: one(users, { fields: [productReviews.userId], references: [users.id] }), - 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), -})); - -export const productGroupMembershipRelations = relations(productGroupMembership, ({ one }) => ({ - product: one(productInfo, { fields: [productGroupMembership.productId], references: [productInfo.id] }), - group: one(productGroupInfo, { fields: [productGroupMembership.groupId], references: [productGroupInfo.id] }), -})); - -export const homeBannersRelations = relations(homeBanners, ({}) => ({ - // Relations for productIds array would be more complex, skipping for now -})); - -export const staffRolesRelations = relations(staffRoles, ({ many }) => ({ - staffUsers: many(staffUsers), - rolePermissions: many(staffRolePermissions), -})); - -export const staffPermissionsRelations = relations(staffPermissions, ({ many }) => ({ - rolePermissions: many(staffRolePermissions), -})); - -export const staffRolePermissionsRelations = relations(staffRolePermissions, ({ one }) => ({ - role: one(staffRoles, { fields: [staffRolePermissions.staffRoleId], references: [staffRoles.id] }), - permission: one(staffPermissions, { fields: [staffRolePermissions.staffPermissionId], references: [staffPermissions.id] }), -})); - -export const userIncidentsRelations = relations(userIncidents, ({ one }) => ({ - user: one(users, { fields: [userIncidents.userId], references: [users.id] }), - order: one(orders, { fields: [userIncidents.orderId], references: [orders.id] }), - addedBy: one(staffUsers, { fields: [userIncidents.addedBy], references: [staffUsers.id] }), -})); - -export const productAvailabilitySchedulesRelations = relations(productAvailabilitySchedules, ({}) => ({ -})); +export * from './schema-sqlite' diff --git a/apps/backend/src/db/sqlite-casts.ts b/apps/backend/src/db/sqlite-casts.ts new file mode 100644 index 0000000..e59d9cf --- /dev/null +++ b/apps/backend/src/db/sqlite-casts.ts @@ -0,0 +1,34 @@ +export const parseJsonValue = (value: unknown, fallback: T): T => { + if (value === null || value === undefined) return fallback + if (typeof value === 'string') { + try { + return JSON.parse(value) as T + } catch { + return fallback + } + } + return value as T +} + +export const parseNumberArray = (value: unknown): number[] => { + const parsed = parseJsonValue(value, []) + return parsed + .map((item) => Number(item)) + .filter((item) => !Number.isNaN(item)) +} + +export const toJsonString = (value: unknown, fallback: string): string => { + if (value === null || value === undefined) return fallback + if (typeof value === 'string') return value + return JSON.stringify(value) +} + +export const toEpochSeconds = (value: Date | number): number => { + if (typeof value === 'number') return value + return Math.floor(value.getTime() / 1000) +} + +export const fromEpochSeconds = (value: number | null | undefined): Date | null => { + if (value === null || value === undefined) return null + return new Date(value * 1000) +} diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/main.ts b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/main.ts index 1c91892..0705d1c 100644 --- a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/main.ts +++ b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/main.ts @@ -1,41 +1,55 @@ export type { IBannerDbService, Banner, NewBanner } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/banner-db-service.interface' -export { bannerDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/banner-queries' +// export { bannerDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/banner-queries' +export { bannerDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/sqlite/banner-queries' export type { IComplaintDbService, Complaint, NewComplaint } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/complaint-db-service.interface' -export { complaintDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/complaint-queries' +// export { complaintDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/complaint-queries' +export { complaintDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/sqlite/complaint-queries' export type { IConstantDbService, Constant, NewConstant } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/constant-db-service.interface' -export { constantDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/constant-queries' +// export { constantDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/constant-queries' +export { constantDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/sqlite/constant-queries' export type { ICouponDbService, Coupon, NewCoupon, ReservedCoupon, NewReservedCoupon, CouponWithRelations } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/coupon-db-service.interface' -export { couponDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/coupon-queries' +// export { couponDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/coupon-queries' +export { couponDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/sqlite/coupon-queries' export type { IOrderDbService, Order, OrderItem, OrderStatus, OrderWithRelations, OrderWithStatus, OrderWithCouponUsages } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/order-db-service.interface' -export { orderDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/order-queries' +// export { orderDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/order-queries' +export { orderDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/sqlite/order-queries' export type { IProductDbService, Product, NewProduct, ProductGroup, NewProductGroup } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/product-db-service.interface' -export { productDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/product-queries' +// export { productDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/product-queries' +export { productDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/sqlite/product-queries' export type { IRefundDbService, Refund, NewRefund } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/refund-db-service.interface' -export { refundDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/refund-queries' +// export { refundDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/refund-queries' +export { refundDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/sqlite/refund-queries' export type { IScheduleDbService, Schedule, NewSchedule } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/schedule-db-service.interface' -export { scheduleDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/schedule-queries' +// export { scheduleDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/schedule-queries' +export { scheduleDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/sqlite/schedule-queries' export type { ISlotDbService, Slot, NewSlot, ProductSlot, NewProductSlot, SlotWithRelations } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/slot-db-service.interface' -export { slotDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/slot-queries' +// export { slotDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/slot-queries' +export { slotDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/sqlite/slot-queries' export type { IStaffUserDbService, StaffUser, NewStaffUser, StaffRole, StaffUserWithRole } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/staff-user-db-service.interface' -export { staffUserDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/staff-user-queries' +// export { staffUserDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/staff-user-queries' +export { staffUserDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/sqlite/staff-user-queries' export type { IStoreDbService, Store, NewStore } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/store-db-service.interface' -export { storeDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/store-queries' +// export { storeDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/store-queries' +export { storeDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/sqlite/store-queries' export type { ITagDbService, Tag, NewTag } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/tag-db-service.interface' -export { tagDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/tag-queries' +// export { tagDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/tag-queries' +export { tagDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/sqlite/tag-queries' export type { IUserDbService, User, NewUser, UserDetail } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/user-db-service.interface' -export { userDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/user-queries' +// export { userDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/user-queries' +export { userDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/sqlite/user-queries' export type { IVendorSnippetDbService, VendorSnippet, NewVendorSnippet } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/vendor-snippet-db-service.interface' -export { vendorSnippetDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/vendor-snippets-queries' +// export { vendorSnippetDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/postgres/vendor-snippets-queries' +export { vendorSnippetDbService } from '@/src/trpc/apis/admin-apis/dataAccessors/sqlite/vendor-snippets-queries' diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/banner-queries.ts b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/banner-queries.ts new file mode 100644 index 0000000..9b62365 --- /dev/null +++ b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/banner-queries.ts @@ -0,0 +1,48 @@ +import { db } from '@/src/db/db_index_sqlite' +import { homeBanners } from '@/src/db/schema' +import { eq, desc } from 'drizzle-orm' +import { IBannerDbService, Banner, NewBanner } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/banner-db-service.interface' +import { toJsonString } from '@/src/db/sqlite-casts' + +export class BannerDbService implements IBannerDbService { + async getAllBanners(): Promise { + return db.query.homeBanners.findMany({ + orderBy: desc(homeBanners.createdAt), + }) + } + + async getBannerById(id: number): Promise { + return db.query.homeBanners.findFirst({ + where: eq(homeBanners.id, id), + }) + } + + async createBanner(data: NewBanner): Promise { + const normalized = { + ...data, + productIds: data.productIds ? toJsonString(data.productIds, '[]') : data.productIds, + } + const [banner] = await db.insert(homeBanners).values(normalized).returning() + return banner + } + + async updateBannerById(id: number, data: Partial): Promise { + const normalized = { + ...data, + productIds: data.productIds ? toJsonString(data.productIds, '[]') : data.productIds, + lastUpdated: new Date(), + } + const [banner] = await db + .update(homeBanners) + .set(normalized) + .where(eq(homeBanners.id, id)) + .returning() + return banner + } + + async deleteBannerById(id: number): Promise { + await db.delete(homeBanners).where(eq(homeBanners.id, id)) + } +} + +export const bannerDbService: IBannerDbService = new BannerDbService() diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/complaint-queries.ts b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/complaint-queries.ts new file mode 100644 index 0000000..b792066 --- /dev/null +++ b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/complaint-queries.ts @@ -0,0 +1,43 @@ +import { db } from '@/src/db/db_index_sqlite' +import { complaints, users } from '@/src/db/schema' +import { eq, desc, lt } from 'drizzle-orm' +import { IComplaintDbService, Complaint, NewComplaint } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/complaint-db-service.interface' + +export class ComplaintDbService implements IComplaintDbService { + async getComplaints( + cursor?: number, + limit: number = 20 + ): Promise> { + 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, + createdAt: complaints.createdAt, + response: complaints.response, + 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) + + return complaintsData + } + + async resolveComplaint(id: number, response?: string): Promise { + await db + .update(complaints) + .set({ isResolved: true, response }) + .where(eq(complaints.id, id)) + } +} + +export const complaintDbService: IComplaintDbService = new ComplaintDbService() diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/constant-queries.ts b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/constant-queries.ts new file mode 100644 index 0000000..d12a84b --- /dev/null +++ b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/constant-queries.ts @@ -0,0 +1,26 @@ +import { db } from '@/src/db/db_index_sqlite' +import { keyValStore } from '@/src/db/schema' +import { IConstantDbService, Constant, NewConstant } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/constant-db-service.interface' +import { toJsonString } from '@/src/db/sqlite-casts' + +export class ConstantDbService implements IConstantDbService { + async getAllConstants(): Promise { + return db.select().from(keyValStore) + } + + async upsertConstants(constants: { key: string; value: any }[]): Promise { + await db.transaction(async (tx) => { + for (const { key, value } of constants) { + await tx.insert(keyValStore) + .values({ key, value: toJsonString(value, 'null') }) + .onConflictDoUpdate({ + target: keyValStore.key, + set: { value: toJsonString(value, 'null') }, + }) + } + }) + return constants.length + } +} + +export const constantDbService: IConstantDbService = new ConstantDbService() diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/coupon-queries.ts b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/coupon-queries.ts new file mode 100644 index 0000000..60d8d44 --- /dev/null +++ b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/coupon-queries.ts @@ -0,0 +1,204 @@ +import { db } from '@/src/db/db_index_sqlite' +import { coupons, couponApplicableUsers, couponApplicableProducts, reservedCoupons, users, orders, orderStatus } from '@/src/db/schema' +import { eq, and, like, or, inArray, lt, asc } from 'drizzle-orm' +import { ICouponDbService, Coupon, NewCoupon, ReservedCoupon, NewReservedCoupon, CouponWithRelations } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/coupon-db-service.interface' +import { parseNumberArray, toJsonString } from '@/src/db/sqlite-casts' + +export class CouponDbService implements ICouponDbService { + async createCoupon(data: NewCoupon): Promise { + const normalized = { + ...data, + productIds: data.productIds ? toJsonString(data.productIds, '[]') : data.productIds, + } + const [coupon] = await db.insert(coupons).values(normalized).returning() + return coupon + } + + async getCouponById(id: number): Promise { + const result = await db.query.coupons.findFirst({ + where: eq(coupons.id, id), + with: { + creator: true, + applicableUsers: { with: { user: true } }, + applicableProducts: { with: { product: true } }, + }, + }) + if (!result) return undefined + return { + ...result, + productIds: parseNumberArray(result.productIds), + } as CouponWithRelations + } + + async getCouponByCode(code: string): Promise { + return db.query.coupons.findFirst({ + where: eq(coupons.couponCode, code), + }) + } + + async getAllCoupons(options: { cursor?: number; limit: number; search?: string }): Promise { + const { cursor, limit, search } = options + + 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: (couponsRef, { desc }) => [desc(couponsRef.createdAt)], + limit: limit + 1, + }) + + return result.map((coupon) => ({ + ...coupon, + productIds: parseNumberArray(coupon.productIds), + })) as CouponWithRelations[] + } + + async updateCoupon(id: number, data: Partial): Promise { + const normalized = { + ...data, + productIds: data.productIds ? toJsonString(data.productIds, '[]') : data.productIds, + } + const [coupon] = await db.update(coupons).set(normalized).where(eq(coupons.id, id)).returning() + return coupon + } + + async invalidateCoupon(id: number): Promise { + const [coupon] = await db.update(coupons).set({ isInvalidated: true }).where(eq(coupons.id, id)).returning() + return coupon + } + + async addApplicableUsers(couponId: number, userIds: number[]): Promise { + await db.insert(couponApplicableUsers).values( + userIds.map(userId => ({ couponId, userId })) + ) + } + + async addApplicableProducts(couponId: number, productIds: number[]): Promise { + await db.insert(couponApplicableProducts).values( + productIds.map(productId => ({ couponId, productId })) + ) + } + + async removeAllApplicableUsers(couponId: number): Promise { + await db.delete(couponApplicableUsers).where(eq(couponApplicableUsers.couponId, couponId)) + } + + async removeAllApplicableProducts(couponId: number): Promise { + await db.delete(couponApplicableProducts).where(eq(couponApplicableProducts.couponId, couponId)) + } + + async countApplicableUsers(couponId: number): Promise { + return db.$count(couponApplicableUsers, eq(couponApplicableUsers.couponId, couponId)) + } + + async createReservedCoupon(data: NewReservedCoupon): Promise { + const normalized = { + ...data, + productIds: data.productIds ? toJsonString(data.productIds, '[]') : data.productIds, + } + const [coupon] = await db.insert(reservedCoupons).values(normalized).returning() + return coupon + } + + async getReservedCoupons(options: { cursor?: number; limit: number; search?: string }): Promise { + const { cursor, limit, search } = options + + 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) + } + + return db.query.reservedCoupons.findMany({ + where: whereCondition, + with: { redeemedUser: true, creator: true }, + orderBy: (reservedCouponsRef, { desc }) => [desc(reservedCouponsRef.createdAt)], + limit: limit + 1, + }) + } + + async getUsersByIds(ids: number[]): Promise> { + return db.query.users.findMany({ + where: inArray(users.id, ids), + columns: { id: true, name: true, mobile: true }, + }) + } + + async getUsersBySearch(search: string, limit: number, offset: number): Promise> { + const whereCondition = or( + like(users.name, `%${search}%`), + like(users.mobile, `%${search}%`) + ) + + return db.query.users.findMany({ + where: whereCondition, + columns: { id: true, name: true, mobile: true }, + limit, + offset, + orderBy: (usersRef, { asc }) => [asc(usersRef.name)], + }) + } + + async createUser(data: Partial): Promise { + const [user] = await db.insert(users).values(data).returning() + return user + } + + async getUserByMobile(mobile: string): Promise { + return db.query.users.findFirst({ + where: eq(users.mobile, mobile), + }) + } + + async getOrderByIdWithUserAndStatus(id: number): Promise { + return db.query.orders.findFirst({ + where: eq(orders.id, id), + with: { + user: true, + orderStatus: true, + }, + }) + } + + async updateOrderStatusRefundCoupon(orderId: number, couponId: number): Promise { + await db.update(orderStatus) + .set({ refundCouponId: couponId }) + .where(eq(orderStatus.orderId, orderId)) + } + + async withTransaction(fn: (tx: any) => Promise): Promise { + return db.transaction(fn) + } +} + +export const couponDbService: ICouponDbService = new CouponDbService() diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/order-queries.ts b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/order-queries.ts new file mode 100644 index 0000000..0bc7798 --- /dev/null +++ b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/order-queries.ts @@ -0,0 +1,332 @@ +import { db } from '@/src/db/db_index_sqlite' +import { + orders, + orderItems, + orderStatus, + users, + addresses, + refunds, + coupons, + couponUsage, + complaints, + payments, + deliverySlotInfo, + productInfo, + units, + paymentInfoTable, +} from '@/src/db/schema' +import { eq, and, gte, lt, desc, inArray, SQL } from 'drizzle-orm' +import { + IOrderDbService, + Order, + OrderItem, + OrderStatus, + Address, + Refund, + OrderWithRelations, + OrderWithStatus, + OrderWithCouponUsages, +} from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/order-db-service.interface' + +export class OrderDbService implements IOrderDbService { + async updateOrderNotes(orderId: number, adminNotes: string | null): Promise { + const [updated] = await db + .update(orders) + .set({ adminNotes }) + .where(eq(orders.id, orderId)) + .returning() + return updated + } + + async removeDeliveryCharge(orderId: number, totalAmount: string): Promise { + await db + .update(orders) + .set({ deliveryCharge: '0', totalAmount }) + .where(eq(orders.id, orderId)) + } + + async getOrderById(orderId: number): Promise { + return db.query.orders.findFirst({ + where: eq(orders.id, orderId), + }) + } + + async getOrderWithRelations(orderId: number): Promise { + return db.query.orders.findFirst({ + where: eq(orders.id, orderId), + with: { + user: true, + address: true, + slot: true, + orderItems: { + with: { + product: { + with: { unit: true }, + }, + }, + }, + payment: true, + paymentInfo: true, + }, + }) as Promise + } + + async getOrderWithDetails(orderId: number): Promise { + return db.query.orders.findFirst({ + where: eq(orders.id, orderId), + with: { + user: true, + address: true, + slot: true, + orderItems: { + with: { + product: { + with: { unit: true }, + }, + }, + }, + payment: true, + paymentInfo: true, + orderStatus: true, + refunds: true, + }, + }) as Promise + } + + async getOrderWithStatus(orderId: number): Promise { + return db.query.orders.findFirst({ + where: eq(orders.id, orderId), + with: { + orderStatus: true, + }, + }) as Promise + } + + async getOrderStatusByOrderId(orderId: number): Promise { + return db.query.orderStatus.findFirst({ + where: eq(orderStatus.orderId, orderId), + }) + } + + async updateOrderStatusPackaged(orderId: number, isPackaged: boolean, isDelivered: boolean): Promise { + await db + .update(orderStatus) + .set({ isPackaged, isDelivered }) + .where(eq(orderStatus.orderId, orderId)) + } + + async updateOrderStatusDelivered(orderId: number, isDelivered: boolean): Promise { + await db + .update(orderStatus) + .set({ isDelivered }) + .where(eq(orderStatus.orderId, orderId)) + } + + async cancelOrderStatus(statusId: number, reason: string): Promise { + await db + .update(orderStatus) + .set({ + isCancelled: true, + isCancelledByAdmin: true, + cancelReason: reason, + cancellationAdminNotes: reason, + cancellationReviewed: true, + cancellationReviewedAt: new Date(), + }) + .where(eq(orderStatus.id, statusId)) + } + + async getRefundByOrderId(orderId: number): Promise { + return db.query.refunds.findFirst({ + where: eq(refunds.orderId, orderId), + }) + } + + async createRefund(orderId: number, refundStatus: string): Promise { + await db.insert(refunds).values({ orderId, refundStatus }) + } + + async getCouponUsageByOrderId(orderId: number): Promise> { + return db.query.couponUsage.findMany({ + where: eq(couponUsage.orderId, orderId), + with: { coupon: true }, + }) + } + + async getOrderItemById(orderItemId: number): Promise { + return db.query.orderItems.findFirst({ + where: eq(orderItems.id, orderItemId), + }) + } + + async updateOrderItem(orderItemId: number, data: Partial): Promise { + await db + .update(orderItems) + .set(data) + .where(eq(orderItems.id, orderItemId)) + } + + async updateOrderItemsPackaged(orderId: number, isPackaged: boolean): Promise { + await db + .update(orderItems) + .set({ is_packaged: isPackaged }) + .where(eq(orderItems.orderId, orderId)) + } + + async updateAddressCoords(addressId: number, latitude: number, longitude: number): Promise
{ + const [updated] = await db + .update(addresses) + .set({ adminLatitude: latitude, adminLongitude: longitude }) + .where(eq(addresses.id, addressId)) + .returning() + return updated + } + + async getOrdersBySlotId(slotId: number): Promise { + return db.query.orders.findMany({ + where: eq(orders.slotId, slotId), + with: { + user: true, + address: true, + slot: true, + orderItems: { + with: { + product: { with: { unit: true } }, + }, + }, + orderStatus: true, + }, + }) as Promise + } + + async getOrdersBySlotIds(slotIds: number[]): Promise { + return db.query.orders.findMany({ + where: inArray(orders.slotId, slotIds), + with: { + orderItems: { with: { product: true } }, + couponUsages: { with: { coupon: true } }, + }, + }) as Promise + } + + async getOrdersByDateRange(start: Date, end: Date, slotId?: number): Promise { + const conditions: Array = [ + gte(orders.createdAt, start), + lt(orders.createdAt, end), + ] + + if (slotId !== undefined) { + conditions.push(eq(orders.slotId, slotId)) + } + + return db.query.orders.findMany({ + where: and(...conditions), + with: { + user: true, + address: true, + slot: true, + orderItems: { + with: { + product: { with: { unit: true } }, + }, + }, + orderStatus: true, + }, + }) as Promise + } + + async getAllOrdersWithFilters(options: { + cursor?: number + limit: number + slotId?: number | null + packagedFilter: 'all' | 'packaged' | 'not_packaged' + deliveredFilter: 'all' | 'delivered' | 'not_delivered' + cancellationFilter: 'all' | 'cancelled' | 'not_cancelled' + flashDeliveryFilter: 'all' | 'flash' | 'regular' + }): Promise { + const { cursor, limit, slotId, packagedFilter, deliveredFilter, cancellationFilter, flashDeliveryFilter } = options + + const conditions: Array = [] + + if (cursor) { + conditions.push(lt(orders.id, cursor)) + } + + if (slotId !== undefined && slotId !== null) { + conditions.push(eq(orders.slotId, slotId)) + } + + if (packagedFilter === 'packaged') { + conditions.push(eq(orderStatus.isPackaged, true)) + } else if (packagedFilter === 'not_packaged') { + conditions.push(eq(orderStatus.isPackaged, false)) + } + + if (deliveredFilter === 'delivered') { + conditions.push(eq(orderStatus.isDelivered, true)) + } else if (deliveredFilter === 'not_delivered') { + conditions.push(eq(orderStatus.isDelivered, false)) + } + + if (cancellationFilter === 'cancelled') { + conditions.push(eq(orderStatus.isCancelled, true)) + } else if (cancellationFilter === 'not_cancelled') { + conditions.push(eq(orderStatus.isCancelled, false)) + } + + if (flashDeliveryFilter === 'flash') { + conditions.push(eq(orders.isFlashDelivery, true)) + } else if (flashDeliveryFilter === 'regular') { + conditions.push(eq(orders.isFlashDelivery, false)) + } + + const whereCondition = conditions.length > 0 ? and(...conditions) : undefined + + return db.query.orders.findMany({ + where: whereCondition, + with: { + user: true, + address: true, + slot: true, + orderItems: { + with: { + product: { with: { unit: true } }, + }, + }, + orderStatus: true, + }, + orderBy: (ordersRef, { desc }) => [desc(ordersRef.createdAt)], + limit: limit + 1, + }) as Promise + } + + async updateOrdersAndItemsInTransaction(data: Array<{ orderId: number; totalAmount: string; items: Array<{ id: number; price: string; discountedPrice: string }> }>): Promise { + await db.transaction(async (tx) => { + for (const order of data) { + await tx.update(orders) + .set({ totalAmount: order.totalAmount }) + .where(eq(orders.id, order.orderId)) + + for (const item of order.items) { + await tx.update(orderItems) + .set({ price: item.price, discountedPrice: item.discountedPrice }) + .where(eq(orderItems.id, item.id)) + } + } + }) + } + + async deleteOrderById(orderId: number): Promise { + await db.transaction(async (tx) => { + await tx.delete(couponUsage).where(eq(couponUsage.orderId, orderId)) + await tx.delete(orderStatus).where(eq(orderStatus.orderId, orderId)) + await tx.delete(orderItems).where(eq(orderItems.orderId, orderId)) + await tx.delete(refunds).where(eq(refunds.orderId, orderId)) + await tx.delete(payments).where(eq(payments.orderId, orderId)) + await tx.delete(complaints).where(eq(complaints.orderId, orderId)) + await tx.delete(orders).where(eq(orders.id, orderId)) + }) + } +} + +export const orderDbService: IOrderDbService = new OrderDbService() diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/product-queries.ts b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/product-queries.ts new file mode 100644 index 0000000..31bcdf1 --- /dev/null +++ b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/product-queries.ts @@ -0,0 +1,244 @@ +import { db } from '@/src/db/db_index_sqlite' +import { productInfo, units, specialDeals, productSlots, productTags, productReviews, productGroupInfo, productGroupMembership, users } from '@/src/db/schema' +import { eq, and, inArray, desc, sql } from 'drizzle-orm' +import { IProductDbService, Product, NewProduct, ProductGroup, NewProductGroup } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/product-db-service.interface' +import { toJsonString } from '@/src/db/sqlite-casts' + +export class ProductDbService implements IProductDbService { + async getAllProducts(): Promise { + return db.query.productInfo.findMany({ + orderBy: productInfo.name, + with: { + unit: true, + store: true, + }, + }) + } + + async getProductById(id: number): Promise { + return db.query.productInfo.findFirst({ + where: eq(productInfo.id, id), + with: { + unit: true, + }, + }) + } + + async createProduct(data: NewProduct): Promise { + const normalized = { + ...data, + images: data.images ? toJsonString(data.images, '[]') : data.images, + } + const [product] = await db.insert(productInfo).values(normalized).returning() + return product + } + + async updateProduct(id: number, data: Partial): Promise { + const normalized = { + ...data, + images: data.images ? toJsonString(data.images, '[]') : data.images, + } + const [product] = await db + .update(productInfo) + .set(normalized) + .where(eq(productInfo.id, id)) + .returning() + return product + } + + async deleteProduct(id: number): Promise { + const [product] = await db + .delete(productInfo) + .where(eq(productInfo.id, id)) + .returning() + return product + } + + async getDealsByProductId(productId: number): Promise { + return db.query.specialDeals.findMany({ + where: eq(specialDeals.productId, productId), + orderBy: specialDeals.quantity, + }) + } + + async createDeals(deals: Partial[]): Promise { + if (deals.length > 0) { + await db.insert(specialDeals).values(deals as any) + } + } + + async deleteDealsByProductId(productId: number): Promise { + await db.delete(specialDeals).where(eq(specialDeals.productId, productId)) + } + + async getTagsByProductId(productId: number): Promise> { + return db.query.productTags.findMany({ + where: eq(productTags.productId, productId), + with: { + tag: true, + }, + }) as any + } + + async createTagAssociations(associations: { productId: number; tagId: number }[]): Promise { + if (associations.length > 0) { + await db.insert(productTags).values(associations) + } + } + + async deleteTagAssociationsByProductId(productId: number): Promise { + await db.delete(productTags).where(eq(productTags.productId, productId)) + } + + async getProductSlotsBySlotId(slotId: number): Promise { + return db.query.productSlots.findMany({ + where: eq(productSlots.slotId, slotId), + }) + } + + async getProductSlotsBySlotIds(slotIds: number[]): Promise { + return db.query.productSlots.findMany({ + where: inArray(productSlots.slotId, slotIds), + columns: { slotId: true, productId: true }, + }) + } + + async createProductSlot(slotId: number, productId: number): Promise { + await db.insert(productSlots).values({ slotId, productId }) + } + + async deleteProductSlotsBySlotId(slotId: number): Promise { + await db.delete(productSlots).where(eq(productSlots.slotId, slotId)) + } + + async deleteProductSlot(slotId: number, productId: number): Promise { + await db + .delete(productSlots) + .where(and(eq(productSlots.slotId, slotId), eq(productSlots.productId, productId))) + } + + async getReviewsByProductId(productId: number, limit: number, offset: number): Promise<(typeof productReviews.$inferSelect & { userName: string | null })[]> { + const reviews = await db + .select({ + id: productReviews.id, + reviewBody: productReviews.reviewBody, + ratings: productReviews.ratings, + imageUrls: productReviews.imageUrls, + reviewTime: productReviews.reviewTime, + adminResponse: productReviews.adminResponse, + adminResponseImages: productReviews.adminResponseImages, + userName: users.name, + }) + .from(productReviews) + .innerJoin(users, eq(productReviews.userId, users.id)) + .where(eq(productReviews.productId, productId)) + .orderBy(desc(productReviews.reviewTime)) + .limit(limit) + .offset(offset) + + return reviews as any + } + + async getReviewCountByProductId(productId: number): Promise { + const result = await db + .select({ count: sql`count(*)` }) + .from(productReviews) + .where(eq(productReviews.productId, productId)) + return Number(result[0].count) + } + + async updateReview(reviewId: number, data: Partial): Promise { + const normalized = { + ...data, + imageUrls: data.imageUrls ? toJsonString(data.imageUrls, '[]') : data.imageUrls, + adminResponseImages: data.adminResponseImages ? toJsonString(data.adminResponseImages, '[]') : data.adminResponseImages, + } + const [review] = await db + .update(productReviews) + .set(normalized) + .where(eq(productReviews.id, reviewId)) + .returning() + return review + } + + async getAllGroups(): Promise { + return db.query.productGroupInfo.findMany({ + with: { + memberships: { + with: { + product: true, + }, + }, + }, + orderBy: desc(productGroupInfo.createdAt), + }) + } + + async getGroupById(id: number): Promise { + return db.query.productGroupInfo.findFirst({ + where: eq(productGroupInfo.id, id), + }) + } + + async createGroup(data: NewProductGroup): Promise { + const [group] = await db.insert(productGroupInfo).values(data).returning() + return group + } + + async updateGroup(id: number, data: Partial): Promise { + const [group] = await db + .update(productGroupInfo) + .set(data) + .where(eq(productGroupInfo.id, id)) + .returning() + return group + } + + async deleteGroup(id: number): Promise { + const [group] = await db + .delete(productGroupInfo) + .where(eq(productGroupInfo.id, id)) + .returning() + return group + } + + async deleteGroupMembershipsByGroupId(groupId: number): Promise { + await db.delete(productGroupMembership).where(eq(productGroupMembership.groupId, groupId)) + } + + async createGroupMemberships(memberships: { productId: number; groupId: number }[]): Promise { + if (memberships.length > 0) { + await db.insert(productGroupMembership).values(memberships) + } + } + + async getUnitById(id: number): Promise { + return db.query.units.findFirst({ + where: eq(units.id, id), + }) + } + + async validateProductIdsExist(productIds: number[]): Promise { + const products = await db.query.productInfo.findMany({ + where: inArray(productInfo.id, productIds), + columns: { id: true }, + }) + return products.length === productIds.length + } + + async batchUpdateProducts(updates: { productId: number; data: Partial }[]): Promise { + const promises = updates.map(update => { + const normalized = { + ...update.data, + images: update.data.images ? toJsonString(update.data.images, '[]') : update.data.images, + } + return db + .update(productInfo) + .set(normalized) + .where(eq(productInfo.id, update.productId)) + }) + await Promise.all(promises) + } +} + +export const productDbService: IProductDbService = new ProductDbService() diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/refund-queries.ts b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/refund-queries.ts new file mode 100644 index 0000000..7ea1257 --- /dev/null +++ b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/refund-queries.ts @@ -0,0 +1,49 @@ +import { db } from '@/src/db/db_index_sqlite' +import { refunds, orders, orderStatus, payments } from '@/src/db/schema' +import { eq, and } from 'drizzle-orm' +import { IRefundDbService, Refund, NewRefund } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/refund-db-service.interface' + +export class RefundDbService implements IRefundDbService { + async createRefund(data: NewRefund): Promise { + const [refund] = await db.insert(refunds).values(data).returning() + return refund + } + + async updateRefund(id: number, data: Partial): Promise { + const [refund] = await db + .update(refunds) + .set(data) + .where(eq(refunds.id, id)) + .returning() + return refund + } + + async getRefundByOrderId(orderId: number): Promise { + return db.query.refunds.findFirst({ + where: eq(refunds.orderId, orderId), + }) + } + + async getOrderById(id: number): Promise { + return db.query.orders.findFirst({ + where: eq(orders.id, id), + }) + } + + async getOrderStatusByOrderId(orderId: number): Promise { + return db.query.orderStatus.findFirst({ + where: eq(orderStatus.orderId, orderId), + }) + } + + async getSuccessfulPaymentByOrderId(orderId: number): Promise { + return db.query.payments.findFirst({ + where: and( + eq(payments.orderId, orderId), + eq(payments.status, 'success') + ), + }) + } +} + +export const refundDbService: IRefundDbService = new RefundDbService() diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/schedule-queries.ts b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/schedule-queries.ts new file mode 100644 index 0000000..5f88f17 --- /dev/null +++ b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/schedule-queries.ts @@ -0,0 +1,60 @@ +import { db } from '@/src/db/db_index_sqlite' +import { productAvailabilitySchedules } from '@/src/db/schema' +import { eq, desc } from 'drizzle-orm' +import { IScheduleDbService, Schedule, NewSchedule } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/schedule-db-service.interface' +import { toJsonString } from '@/src/db/sqlite-casts' + +export class ScheduleDbService implements IScheduleDbService { + async createSchedule(data: NewSchedule): Promise { + const normalized = { + ...data, + productIds: data.productIds ? toJsonString(data.productIds, '[]') : data.productIds, + groupIds: data.groupIds ? toJsonString(data.groupIds, '[]') : data.groupIds, + } + const [schedule] = await db.insert(productAvailabilitySchedules).values(normalized).returning() + return schedule + } + + async getAllSchedules(): Promise { + return db.query.productAvailabilitySchedules.findMany({ + orderBy: desc(productAvailabilitySchedules.createdAt), + }) + } + + async getScheduleById(id: number): Promise { + return db.query.productAvailabilitySchedules.findFirst({ + where: eq(productAvailabilitySchedules.id, id), + }) + } + + async getScheduleByName(name: string): Promise { + return db.query.productAvailabilitySchedules.findFirst({ + where: eq(productAvailabilitySchedules.scheduleName, name), + }) + } + + async updateSchedule(id: number, data: Partial): Promise { + const normalized = { + ...data, + productIds: data.productIds ? toJsonString(data.productIds, '[]') : data.productIds, + groupIds: data.groupIds ? toJsonString(data.groupIds, '[]') : data.groupIds, + lastUpdated: new Date(), + } + const [schedule] = await db + .update(productAvailabilitySchedules) + .set(normalized) + .where(eq(productAvailabilitySchedules.id, id)) + .returning() + return schedule + } + + async deleteSchedule(id: number): Promise { + const [schedule] = await db + .delete(productAvailabilitySchedules) + .where(eq(productAvailabilitySchedules.id, id)) + .returning() + return schedule + } +} + +export const scheduleDbService: IScheduleDbService = new ScheduleDbService() diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/slot-queries.ts b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/slot-queries.ts new file mode 100644 index 0000000..c56ffdc --- /dev/null +++ b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/slot-queries.ts @@ -0,0 +1,161 @@ +import { db } from '@/src/db/db_index_sqlite' +import { deliverySlotInfo, productSlots, vendorSnippets, productInfo, productGroupInfo } from '@/src/db/schema' +import { eq, inArray, and, desc } from 'drizzle-orm' +import { ISlotDbService, Slot, NewSlot, ProductSlot, SlotWithRelations } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/slot-db-service.interface' +import { parseNumberArray, toJsonString } from '@/src/db/sqlite-casts' + +export class SlotDbService implements ISlotDbService { + async getAllSlots(): Promise { + return db.query.deliverySlotInfo.findMany({ + orderBy: desc(deliverySlotInfo.deliveryTime), + with: { + productSlots: { + with: { + product: { + columns: { id: true, name: true, images: true }, + }, + }, + }, + }, + }) as Promise + } + + async getActiveSlots(): Promise { + return db.query.deliverySlotInfo.findMany({ + where: eq(deliverySlotInfo.isActive, true), + orderBy: desc(deliverySlotInfo.deliveryTime), + }) + } + + async getSlotById(id: number): Promise { + return db.query.deliverySlotInfo.findFirst({ + where: eq(deliverySlotInfo.id, id), + with: { + productSlots: { + with: { + product: { + columns: { id: true, name: true, images: true }, + }, + }, + }, + vendorSnippets: true, + }, + }) as Promise + } + + async createSlot(data: NewSlot): Promise { + const normalized = { + ...data, + deliverySequence: data.deliverySequence ? toJsonString(data.deliverySequence, '{}') : data.deliverySequence, + groupIds: data.groupIds ? toJsonString(data.groupIds, '[]') : data.groupIds, + } + const [slot] = await db.insert(deliverySlotInfo).values(normalized).returning() + return slot + } + + async updateSlot(id: number, data: Partial): Promise { + const normalized = { + ...data, + deliverySequence: data.deliverySequence ? toJsonString(data.deliverySequence, '{}') : data.deliverySequence, + groupIds: data.groupIds ? toJsonString(data.groupIds, '[]') : data.groupIds, + } + const [slot] = await db + .update(deliverySlotInfo) + .set(normalized) + .where(eq(deliverySlotInfo.id, id)) + .returning() + return slot + } + + async deactivateSlot(id: number): Promise { + const [slot] = await db + .update(deliverySlotInfo) + .set({ isActive: false }) + .where(eq(deliverySlotInfo.id, id)) + .returning() + return slot + } + + async getProductSlotsBySlotId(slotId: number): Promise { + return db.query.productSlots.findMany({ + where: eq(productSlots.slotId, slotId), + }) + } + + async getProductSlotsBySlotIds(slotIds: number[]): Promise { + return db.query.productSlots.findMany({ + where: inArray(productSlots.slotId, slotIds), + columns: { slotId: true, productId: true }, + }) + } + + async createProductSlot(slotId: number, productId: number): Promise { + await db.insert(productSlots).values({ slotId, productId }) + } + + async deleteProductSlot(slotId: number, productId: number): Promise { + await db + .delete(productSlots) + .where(and(eq(productSlots.slotId, slotId), eq(productSlots.productId, productId))) + } + + async deleteProductSlotsBySlotId(slotId: number): Promise { + await db.delete(productSlots).where(eq(productSlots.slotId, slotId)) + } + + async getVendorSnippetsBySlotId(slotId: number): Promise> { + const snippets = await db.query.vendorSnippets.findMany({ + where: eq(vendorSnippets.slotId, slotId), + }) + + return snippets.map((snippet) => ({ + ...snippet, + productIds: parseNumberArray(snippet.productIds), + })) as Array<{ id: number; snippetCode: string; slotId: number | null; productIds: number[]; validTill: Date | null; createdAt: Date; isPermanent: boolean | null }> + } + + async createVendorSnippet(data: { snippetCode: string; slotId: number; productIds: number[]; validTill?: Date }): Promise<{ id: number; snippetCode: string; slotId: number | null; productIds: number[]; validTill: Date | null; createdAt: Date; isPermanent: boolean | null }> { + const [snippet] = await db.insert(vendorSnippets).values({ + snippetCode: data.snippetCode, + slotId: data.slotId, + productIds: toJsonString(data.productIds, '[]'), + validTill: data.validTill || null, + }).returning() + return { + ...snippet, + productIds: parseNumberArray(snippet.productIds), + } + } + + async checkSnippetCodeExists(code: string): Promise { + const existing = await db.query.vendorSnippets.findFirst({ + where: eq(vendorSnippets.snippetCode, code), + }) + return !!existing + } + + async validateProductsExist(productIds: number[]): Promise { + const products = await db.query.productInfo.findMany({ + where: inArray(productInfo.id, productIds), + }) + return products.length === productIds.length + } + + async getProductsByIds(productIds: number[]): Promise { + return db.query.productInfo.findMany({ + where: inArray(productInfo.id, productIds), + }) + } + + async getGroupsByIds(groupIds: number[]): Promise> { + return db.query.productGroupInfo.findMany({ + where: inArray(productGroupInfo.id, groupIds), + }) + } + + async withTransaction(fn: (tx: any) => Promise): Promise { + return db.transaction(fn) + } +} + +export const slotDbService: ISlotDbService = new SlotDbService() diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/staff-user-queries.ts b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/staff-user-queries.ts new file mode 100644 index 0000000..7179861 --- /dev/null +++ b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/staff-user-queries.ts @@ -0,0 +1,104 @@ +import { db } from '@/src/db/db_index_sqlite' +import { staffUsers, staffRoles, users, userDetails, orders } from '@/src/db/schema' +import { eq, or, like, and, lt, desc } from 'drizzle-orm' +import { IStaffUserDbService, StaffUser, NewStaffUser, StaffRole, StaffUserWithRole } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/staff-user-db-service.interface' + +export class StaffUserDbService implements IStaffUserDbService { + async getStaffUserByName(name: string): Promise { + return db.query.staffUsers.findFirst({ + where: eq(staffUsers.name, name), + }) + } + + async getAllStaff(): Promise { + return db.query.staffUsers.findMany({ + columns: { id: true, name: true }, + with: { + role: { + with: { + rolePermissions: { + with: { permission: true }, + }, + }, + }, + }, + }) + } + + async createStaffUser(data: NewStaffUser): Promise { + const [user] = await db.insert(staffUsers).values(data).returning() + return user + } + + async getRoleById(id: number): Promise { + return db.query.staffRoles.findFirst({ + where: eq(staffRoles.id, id), + }) + } + + async getAllRoles(): Promise> { + return db.query.staffRoles.findMany({ + columns: { id: true, roleName: true }, + }) + } + + async getUsers(options: { cursor?: number; limit: number; search?: string }): Promise { + const { cursor, limit, search } = options + + 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 + } + + return db.query.users.findMany({ + where: whereCondition, + with: { userDetails: true }, + orderBy: desc(users.id), + limit: limit + 1, + }) + } + + async getUserById(id: number): Promise { + return db.query.users.findFirst({ + where: eq(users.id, id), + with: { + userDetails: true, + orders: { + orderBy: desc(orders.createdAt), + limit: 1, + }, + }, + }) + } + + async upsertUserDetails(data: Partial & { userId: number }): Promise { + await db + .insert(userDetails) + .values(data) + .onConflictDoUpdate({ + target: userDetails.userId, + set: data, + }) + } + + async getLastOrderByUserId(userId: number): Promise { + const userOrders = await db.query.orders.findMany({ + where: eq(orders.userId, userId), + orderBy: desc(orders.createdAt), + limit: 1, + }) + return userOrders[0] + } +} + +export const staffUserDbService: IStaffUserDbService = new StaffUserDbService() diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/store-queries.ts b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/store-queries.ts new file mode 100644 index 0000000..8c5579d --- /dev/null +++ b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/store-queries.ts @@ -0,0 +1,53 @@ +import { db } from '@/src/db/db_index_sqlite' +import { storeInfo, productInfo } from '@/src/db/schema' +import { eq, inArray } from 'drizzle-orm' +import { IStoreDbService, Store, NewStore } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/store-db-service.interface' + +export class StoreDbService implements IStoreDbService { + async getAllStores(): Promise { + return db.query.storeInfo.findMany({ + with: { owner: true }, + }) + } + + async getStoreById(id: number): Promise { + return db.query.storeInfo.findFirst({ + where: eq(storeInfo.id, id), + with: { owner: true }, + }) + } + + async createStore(data: NewStore): Promise { + const [store] = await db.insert(storeInfo).values(data).returning() + return store + } + + async updateStore(id: number, data: Partial): Promise { + const [store] = await db + .update(storeInfo) + .set(data) + .where(eq(storeInfo.id, id)) + .returning() + return store + } + + async deleteStore(id: number): Promise { + await db.delete(storeInfo).where(eq(storeInfo.id, id)) + } + + async assignProductsToStore(storeId: number, productIds: number[]): Promise { + await db + .update(productInfo) + .set({ storeId }) + .where(inArray(productInfo.id, productIds)) + } + + async removeProductsFromStore(storeId: number): Promise { + await db + .update(productInfo) + .set({ storeId: null }) + .where(eq(productInfo.storeId, storeId)) + } +} + +export const storeDbService: IStoreDbService = new StoreDbService() diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/tag-queries.ts b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/tag-queries.ts new file mode 100644 index 0000000..a90ce4e --- /dev/null +++ b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/tag-queries.ts @@ -0,0 +1,51 @@ +import { db } from '@/src/db/db_index_sqlite' +import { productTagInfo } from '@/src/db/schema' +import { eq } from 'drizzle-orm' +import { ITagDbService, Tag, NewTag } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/tag-db-service.interface' +import { toJsonString } from '@/src/db/sqlite-casts' + +export class TagDbService implements ITagDbService { + async getAllTags(): Promise { + return db.select().from(productTagInfo).orderBy(productTagInfo.tagName) + } + + async getTagById(id: number): Promise { + return db.query.productTagInfo.findFirst({ + where: eq(productTagInfo.id, id), + }) + } + + async getTagByName(name: string): Promise { + return db.query.productTagInfo.findFirst({ + where: eq(productTagInfo.tagName, name.trim()), + }) + } + + async createTag(data: NewTag): Promise { + const normalized = { + ...data, + relatedStores: data.relatedStores ? toJsonString(data.relatedStores, '[]') : data.relatedStores, + } + const [tag] = await db.insert(productTagInfo).values(normalized).returning() + return tag + } + + async updateTag(id: number, data: Partial): Promise { + const normalized = { + ...data, + relatedStores: data.relatedStores ? toJsonString(data.relatedStores, '[]') : data.relatedStores, + } + const [tag] = await db + .update(productTagInfo) + .set(normalized) + .where(eq(productTagInfo.id, id)) + .returning() + return tag + } + + async deleteTag(id: number): Promise { + await db.delete(productTagInfo).where(eq(productTagInfo.id, id)) + } +} + +export const tagDbService: ITagDbService = new TagDbService() diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/user-queries.ts b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/user-queries.ts new file mode 100644 index 0000000..e9c4072 --- /dev/null +++ b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/user-queries.ts @@ -0,0 +1,172 @@ +import { db } from '@/src/db/db_index_sqlite' +import { users, userDetails, orders, orderItems, orderStatus, complaints, notifCreds, unloggedUserTokens, userIncidents } from '@/src/db/schema' +import { eq, desc, asc, count, max, inArray, and, like, gt } from 'drizzle-orm' +import { IUserDbService, User, NewUser, UserDetail } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/user-db-service.interface' + +export class UserDbService implements IUserDbService { + async getUserById(id: number): Promise { + return db.query.users.findFirst({ + where: eq(users.id, id), + }) + } + + async getUserByMobile(mobile: string): Promise { + return db.query.users.findFirst({ + where: eq(users.mobile, mobile), + }) + } + + async getUsers(options: { limit: number; cursor?: number; search?: string }): Promise { + const { limit, cursor, search } = options + + const conditions = [] + + if (search && search.trim()) { + conditions.push(like(users.mobile, `%${search.trim()}%`)) + } + + if (cursor) { + conditions.push(gt(users.id, cursor)) + } + + const whereCondition = conditions.length > 0 ? and(...conditions) : undefined + + return db + .select() + .from(users) + .where(whereCondition) + .orderBy(asc(users.id)) + .limit(limit + 1) + } + + async createUser(data: NewUser): Promise { + const [user] = await db.insert(users).values(data).returning() + return user + } + + async getUserDetailsByUserId(userId: number): Promise { + return db.query.userDetails.findFirst({ + where: eq(userDetails.userId, userId), + }) + } + + async upsertUserDetails(data: Partial & { userId: number }): Promise { + await db + .insert(userDetails) + .values(data) + .onConflictDoUpdate({ + target: userDetails.userId, + set: data, + }) + } + + async getOrdersByUserId(userId: number): Promise { + return db + .select() + .from(orders) + .where(eq(orders.userId, userId)) + .orderBy(desc(orders.createdAt)) + } + + async getLastOrderByUserId(userId: number): Promise { + const userOrders = await db + .select() + .from(orders) + .where(eq(orders.userId, userId)) + .orderBy(desc(orders.createdAt)) + .limit(1) + return userOrders[0] + } + + async getOrderCountByUserIds(userIds: number[]): Promise<{ userId: number; totalOrders: number }[]> { + if (userIds.length === 0) return [] + return db + .select({ + userId: orders.userId, + totalOrders: count(orders.id), + }) + .from(orders) + .where(inArray(orders.userId, userIds)) + .groupBy(orders.userId) + } + + async getLastOrderDateByUserIds(userIds: number[]): Promise<{ userId: number; lastOrderDate: Date | null }[]> { + if (userIds.length === 0) return [] + return db + .select({ + userId: orders.userId, + lastOrderDate: max(orders.createdAt), + }) + .from(orders) + .where(inArray(orders.userId, userIds)) + .groupBy(orders.userId) + } + + async getOrderStatusByOrderIds(orderIds: number[]): Promise<{ orderId: number; isDelivered: boolean; isCancelled: boolean }[]> { + if (orderIds.length === 0) return [] + return db + .select({ + orderId: orderStatus.orderId, + isDelivered: orderStatus.isDelivered, + isCancelled: orderStatus.isCancelled, + }) + .from(orderStatus) + .where(inArray(orderStatus.orderId, orderIds)) + } + + async getOrderItemCountByOrderIds(orderIds: number[]): Promise<{ orderId: number; itemCount: number }[]> { + if (orderIds.length === 0) return [] + return db + .select({ + orderId: orderItems.orderId, + itemCount: count(orderItems.id), + }) + .from(orderItems) + .where(inArray(orderItems.orderId, orderIds)) + .groupBy(orderItems.orderId) + } + + async getUnresolvedComplaintCount(): Promise { + return db.$count(complaints, eq(complaints.isResolved, false)) + } + + async getAllNotifTokens(): Promise { + const tokens = await db.select({ token: notifCreds.token }).from(notifCreds) + return tokens.map(t => t.token) + } + + async getNotifTokensByUserIds(userIds: number[]): Promise { + const tokens = await db + .select({ token: notifCreds.token }) + .from(notifCreds) + .where(inArray(notifCreds.userId, userIds)) + return tokens.map(t => t.token) + } + + async getUnloggedTokens(): Promise { + const tokens = await db.select({ token: unloggedUserTokens.token }).from(unloggedUserTokens) + return tokens.map(t => t.token) + } + + async getUserIncidentsByUserId(userId: number): Promise } | null; addedBy?: { name: string | null } | null }>> { + return db.query.userIncidents.findMany({ + where: eq(userIncidents.userId, userId), + with: { + order: { + with: { + orderStatus: true, + }, + }, + addedBy: true, + }, + orderBy: desc(userIncidents.dateAdded), + }) + } + + async createUserIncident(data: { userId: number; orderId?: number | null; adminComment?: string | null; addedBy: number; negativityScore?: number | null }): Promise { + const [incident] = await db.insert(userIncidents).values(data).returning() + return incident + } +} + +export const userDbService: IUserDbService = new UserDbService() diff --git a/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/vendor-snippets-queries.ts b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/vendor-snippets-queries.ts new file mode 100644 index 0000000..b13886a --- /dev/null +++ b/apps/backend/src/trpc/apis/admin-apis/dataAccessors/sqlite/vendor-snippets-queries.ts @@ -0,0 +1,141 @@ +import { db } from '@/src/db/db_index_sqlite' +import { vendorSnippets, deliverySlotInfo, orders, orderItems, productInfo } from '@/src/db/schema' +import { eq, and, inArray, gt, asc, desc } from 'drizzle-orm' +import { IVendorSnippetDbService, VendorSnippet, NewVendorSnippet } from '@/src/trpc/apis/admin-apis/dataAccessors/interfaces/vendor-snippet-db-service.interface' +import { toJsonString } from '@/src/db/sqlite-casts' + +export class VendorSnippetDbService implements IVendorSnippetDbService { + async createSnippet(data: NewVendorSnippet): Promise { + const normalized = { + ...data, + productIds: data.productIds ? toJsonString(data.productIds, '[]') : data.productIds, + } + const [snippet] = await db.insert(vendorSnippets).values(normalized).returning() + return snippet + } + + async getAllSnippets(): Promise { + return db.query.vendorSnippets.findMany({ + with: { slot: true }, + orderBy: desc(vendorSnippets.createdAt), + }) + } + + async getSnippetById(id: number): Promise { + return db.query.vendorSnippets.findFirst({ + where: eq(vendorSnippets.id, id), + with: { slot: true }, + }) + } + + async getSnippetByCode(code: string): Promise { + return db.query.vendorSnippets.findFirst({ + where: eq(vendorSnippets.snippetCode, code), + }) + } + + async updateSnippet(id: number, data: Partial): Promise { + const normalized = { + ...data, + productIds: data.productIds ? toJsonString(data.productIds, '[]') : data.productIds, + } + const [snippet] = await db + .update(vendorSnippets) + .set(normalized) + .where(eq(vendorSnippets.id, id)) + .returning() + return snippet + } + + async deleteSnippet(id: number): Promise { + const [snippet] = await db + .delete(vendorSnippets) + .where(eq(vendorSnippets.id, id)) + .returning() + return snippet + } + + async checkSnippetCodeExists(code: string): Promise { + const existing = await db.query.vendorSnippets.findFirst({ + where: eq(vendorSnippets.snippetCode, code), + }) + return !!existing + } + + async getSlotById(id: number): Promise { + return db.query.deliverySlotInfo.findFirst({ + where: eq(deliverySlotInfo.id, id), + }) + } + + async getUpcomingSlots(since: Date): Promise { + return db.query.deliverySlotInfo.findMany({ + where: and( + eq(deliverySlotInfo.isActive, true), + gt(deliverySlotInfo.deliveryTime, since) + ), + orderBy: asc(deliverySlotInfo.deliveryTime), + }) + } + + async getProductsByIds(ids: number[]): Promise> { + return db.query.productInfo.findMany({ + where: inArray(productInfo.id, ids), + columns: { id: true, name: true }, + }) + } + + async validateProductsExist(ids: number[]): Promise { + const products = await db.query.productInfo.findMany({ + where: inArray(productInfo.id, ids), + }) + return products.length === ids.length + } + + async getOrdersBySlotId(slotId: number): Promise { + return 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), + }) + } + + async getOrderItemsByOrderIds(orderIds: number[]): Promise { + return db.query.orderItems.findMany({ + where: inArray(orderItems.orderId, orderIds), + }) + } + + async getOrderItemById(id: number): Promise { + return db.query.orderItems.findFirst({ + where: eq(orderItems.id, id), + }) + } + + async updateOrderItemPackaging(id: number, is_packaged: boolean): Promise { + await db + .update(orderItems) + .set({ is_packaged }) + .where(eq(orderItems.id, id)) + } + + async hasSnippetForSlot(slotId: number): Promise { + const snippet = await db.query.vendorSnippets.findFirst({ + where: eq(vendorSnippets.slotId, slotId), + }) + return !!snippet + } +} + +export const vendorSnippetDbService: IVendorSnippetDbService = new VendorSnippetDbService() diff --git a/apps/backend/src/trpc/apis/user-apis/dataAccessors/main.ts b/apps/backend/src/trpc/apis/user-apis/dataAccessors/main.ts index 56d4a3d..b878f1f 100644 --- a/apps/backend/src/trpc/apis/user-apis/dataAccessors/main.ts +++ b/apps/backend/src/trpc/apis/user-apis/dataAccessors/main.ts @@ -1,32 +1,43 @@ export type { IUserBannerDbService, UserBanner } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-banner-db-service.interface' -export { userBannerDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-banner-queries' +// export { userBannerDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-banner-queries' +export { userBannerDbService } from '@/src/trpc/apis/user-apis/dataAccessors/sqlite/user-banner-queries' export type { IUserStoreDbService, Store as UserStore, StoreBasic } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-store-db-service.interface' -export { userStoreDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-store-queries' +// export { userStoreDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-store-queries' +export { userStoreDbService } from '@/src/trpc/apis/user-apis/dataAccessors/sqlite/user-store-queries' export type { IUserAddressDbService, Address, NewAddress } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-address-db-service.interface' -export { userAddressDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-address-queries' +// export { userAddressDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-address-queries' +export { userAddressDbService } from '@/src/trpc/apis/user-apis/dataAccessors/sqlite/user-address-queries' export type { IUserCartDbService, CartItem } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-cart-db-service.interface' -export { userCartDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-cart-queries' +// export { userCartDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-cart-queries' +export { userCartDbService } from '@/src/trpc/apis/user-apis/dataAccessors/sqlite/user-cart-queries' export type { IUserComplaintDbService, Complaint, NewComplaint } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-complaint-db-service.interface' -export { userComplaintDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-complaint-queries' +// export { userComplaintDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-complaint-queries' +export { userComplaintDbService } from '@/src/trpc/apis/user-apis/dataAccessors/sqlite/user-complaint-queries' export type { IUserProductDbService, Product, Store as ProductStore, Review, ProductWithUnit } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-product-db-service.interface' -export { userProductDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-product-queries' +// export { userProductDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-product-queries' +export { userProductDbService } from '@/src/trpc/apis/user-apis/dataAccessors/sqlite/user-product-queries' export type { IUserAuthDbService, User, UserCred, UserDetail } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-auth-db-service.interface' -export { userAuthDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-auth-queries' +// export { userAuthDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-auth-queries' +export { userAuthDbService } from '@/src/trpc/apis/user-apis/dataAccessors/sqlite/user-auth-queries' export type { IUserProfileDbService, User as ProfileUser, UserDetail as ProfileUserDetail, UserCred as ProfileUserCred, NotifCred, UnloggedToken } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-profile-db-service.interface' -export { userProfileDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-profile-queries' +// export { userProfileDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-profile-queries' +export { userProfileDbService } from '@/src/trpc/apis/user-apis/dataAccessors/sqlite/user-profile-queries' export type { IUserSlotDbService, Slot } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-slot-db-service.interface' -export { userSlotDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-slot-queries' +// export { userSlotDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-slot-queries' +export { userSlotDbService } from '@/src/trpc/apis/user-apis/dataAccessors/sqlite/user-slot-queries' export type { IUserCouponDbService, Coupon, CouponWithRelations, ReservedCoupon } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-coupon-db-service.interface' -export { userCouponDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-coupon-queries' +// export { userCouponDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-coupon-queries' +export { userCouponDbService } from '@/src/trpc/apis/user-apis/dataAccessors/sqlite/user-coupon-queries' export type { IUserOrderDbService, Order, OrderInsert, OrderItemInsert, OrderStatusInsert, Coupon as OrderCoupon } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-order-db-service.interface' -export { userOrderDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-order-queries' +// export { userOrderDbService } from '@/src/trpc/apis/user-apis/dataAccessors/postgres/user-order-queries' +export { userOrderDbService } from '@/src/trpc/apis/user-apis/dataAccessors/sqlite/user-order-queries' diff --git a/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-address-queries.ts b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-address-queries.ts new file mode 100644 index 0000000..bafdcdf --- /dev/null +++ b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-address-queries.ts @@ -0,0 +1,71 @@ +import { db } from '@/src/db/db_index_sqlite' +import { addresses, orders, orderStatus, deliverySlotInfo } from '@/src/db/schema' +import { eq, and, gte } from 'drizzle-orm' +import { IUserAddressDbService, Address, NewAddress } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-address-db-service.interface' + +export class UserAddressDbService implements IUserAddressDbService { + async getDefaultAddress(userId: number): Promise
{ + const [defaultAddress] = await db + .select() + .from(addresses) + .where(and(eq(addresses.userId, userId), eq(addresses.isDefault, true))) + .limit(1) + return defaultAddress + } + + async getUserAddresses(userId: number): Promise { + return db.select().from(addresses).where(eq(addresses.userId, userId)) + } + + async unsetDefaultForUser(userId: number): Promise { + await db.update(addresses).set({ isDefault: false }).where(eq(addresses.userId, userId)) + } + + async createAddress(data: NewAddress): Promise
{ + const [newAddress] = await db.insert(addresses).values(data).returning() + return newAddress + } + + async getAddressByIdForUser(addressId: number, userId: number): Promise
{ + const [address] = await db + .select() + .from(addresses) + .where(and(eq(addresses.id, addressId), eq(addresses.userId, userId))) + .limit(1) + return address + } + + async updateAddressForUser(addressId: number, userId: number, data: Partial): Promise
{ + const [updated] = await db + .update(addresses) + .set(data) + .where(and(eq(addresses.id, addressId), eq(addresses.userId, userId))) + .returning() + return updated + } + + async deleteAddressForUser(addressId: number, userId: number): Promise { + await db.delete(addresses).where(and(eq(addresses.id, addressId), eq(addresses.userId, userId))) + } + + async hasOngoingOrdersForAddress(addressId: number): Promise { + const ongoingOrders = await db + .select({ + orderId: orders.id, + }) + .from(orders) + .innerJoin(orderStatus, eq(orders.id, orderStatus.orderId)) + .innerJoin(deliverySlotInfo, eq(orders.slotId, deliverySlotInfo.id)) + .where( + and( + eq(orders.addressId, addressId), + eq(orderStatus.isCancelled, false), + gte(deliverySlotInfo.deliveryTime, new Date()) + ) + ) + .limit(1) + return ongoingOrders.length > 0 + } +} + +export const userAddressDbService: IUserAddressDbService = new UserAddressDbService() diff --git a/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-auth-queries.ts b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-auth-queries.ts new file mode 100644 index 0000000..ecf7393 --- /dev/null +++ b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-auth-queries.ts @@ -0,0 +1,122 @@ +import { db } from '@/src/db/db_index_sqlite' +import { users, userCreds, userDetails, addresses, cartItems, complaints, couponApplicableUsers, couponUsage, notifCreds, notifications, orderItems, orderStatus, orders, payments, refunds, productReviews, reservedCoupons } from '@/src/db/schema' +import { eq } from 'drizzle-orm' +import { IUserAuthDbService, User, UserCred, UserDetail } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-auth-db-service.interface' + +export class UserAuthDbService implements IUserAuthDbService { + async getUserByEmail(email: string): Promise { + const [user] = await db.select().from(users).where(eq(users.email, email)).limit(1) + return user + } + + async getUserByMobile(mobile: string): Promise { + const [user] = await db.select().from(users).where(eq(users.mobile, mobile)).limit(1) + return user + } + + async getUserById(userId: number): Promise { + const [user] = await db.select().from(users).where(eq(users.id, userId)).limit(1) + return user + } + + async getUserCredsByUserId(userId: number): Promise { + const [creds] = await db.select().from(userCreds).where(eq(userCreds.userId, userId)).limit(1) + return creds + } + + async getUserDetailsByUserId(userId: number): Promise { + const [detail] = await db.select().from(userDetails).where(eq(userDetails.userId, userId)).limit(1) + return detail + } + + async createUserWithCredsAndDetails(data: { name: string | null; email: string | null; mobile: string; passwordHash: string; imageKey?: string | null }): Promise { + const { name, email, mobile, passwordHash, imageKey } = data + return db.transaction(async (tx) => { + const [user] = await tx + .insert(users) + .values({ name, email, mobile }) + .returning() + + await tx + .insert(userCreds) + .values({ userId: user.id, userPassword: passwordHash }) + + if (imageKey) { + await tx.insert(userDetails).values({ userId: user.id, profileImage: imageKey }) + } + + return user + }) + } + + async createUser(data: { name: string | null; email: string | null; mobile: string }): Promise { + const [user] = await db.insert(users).values(data).returning() + return user + } + + async upsertUserCreds(userId: number, passwordHash: string): Promise { + await db + .insert(userCreds) + .values({ userId, userPassword: passwordHash }) + .onConflictDoUpdate({ + target: userCreds.userId, + set: { userPassword: passwordHash }, + }) + } + + async updateUserName(userId: number, name: string): Promise { + await db.update(users).set({ name }).where(eq(users.id, userId)) + } + + async updateUserEmail(userId: number, email: string): Promise { + await db.update(users).set({ email }).where(eq(users.id, userId)) + } + + async upsertUserDetails(userId: number, data: Partial): Promise { + await db + .insert(userDetails) + .values({ userId, ...data }) + .onConflictDoUpdate({ + target: userDetails.userId, + set: data, + }) + } + + async deleteAccountByUserId(userId: number): Promise { + await db.transaction(async (tx) => { + await tx.delete(notifCreds).where(eq(notifCreds.userId, userId)) + await tx.delete(couponApplicableUsers).where(eq(couponApplicableUsers.userId, userId)) + await tx.delete(couponUsage).where(eq(couponUsage.userId, userId)) + await tx.delete(complaints).where(eq(complaints.userId, userId)) + await tx.delete(cartItems).where(eq(cartItems.userId, userId)) + await tx.delete(notifications).where(eq(notifications.userId, userId)) + await tx.delete(productReviews).where(eq(productReviews.userId, userId)) + + await tx.update(reservedCoupons) + .set({ redeemedBy: null }) + .where(eq(reservedCoupons.redeemedBy, userId)) + + const userOrders = await tx + .select({ id: orders.id }) + .from(orders) + .where(eq(orders.userId, userId)) + + for (const order of userOrders) { + await tx.delete(orderItems).where(eq(orderItems.orderId, order.id)) + await tx.delete(orderStatus).where(eq(orderStatus.orderId, order.id)) + await tx.delete(payments).where(eq(payments.orderId, order.id)) + await tx.delete(refunds).where(eq(refunds.orderId, order.id)) + await tx.delete(couponUsage).where(eq(couponUsage.orderId, order.id)) + await tx.delete(complaints).where(eq(complaints.orderId, order.id)) + } + + await tx.delete(orders).where(eq(orders.userId, userId)) + await tx.delete(addresses).where(eq(addresses.userId, userId)) + await tx.delete(userDetails).where(eq(userDetails.userId, userId)) + await tx.delete(userCreds).where(eq(userCreds.userId, userId)) + await tx.delete(users).where(eq(users.id, userId)) + }) + } +} + +export const userAuthDbService: IUserAuthDbService = new UserAuthDbService() diff --git a/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-banner-queries.ts b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-banner-queries.ts new file mode 100644 index 0000000..a323a9e --- /dev/null +++ b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-banner-queries.ts @@ -0,0 +1,15 @@ +import { db } from '@/src/db/db_index_sqlite' +import { homeBanners } from '@/src/db/schema' +import { isNotNull, asc } from 'drizzle-orm' +import { IUserBannerDbService, UserBanner } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-banner-db-service.interface' + +export class UserBannerDbService implements IUserBannerDbService { + async getActiveBanners(): Promise { + return db.query.homeBanners.findMany({ + where: isNotNull(homeBanners.serialNum), + orderBy: asc(homeBanners.serialNum), + }) + } +} + +export const userBannerDbService: IUserBannerDbService = new UserBannerDbService() diff --git a/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-cart-queries.ts b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-cart-queries.ts new file mode 100644 index 0000000..c7e2dda --- /dev/null +++ b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-cart-queries.ts @@ -0,0 +1,75 @@ +import { db } from '@/src/db/db_index_sqlite' +import { cartItems, productInfo, units } from '@/src/db/schema' +import { eq, and, sql } from 'drizzle-orm' +import { IUserCartDbService, CartItem } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-cart-db-service.interface' + +export class UserCartDbService implements IUserCartDbService { + async getCartItemsWithProducts(userId: number) { + return db + .select({ + cartId: cartItems.id, + productId: productInfo.id, + productName: productInfo.name, + productPrice: productInfo.price, + productImages: productInfo.images, + productQuantity: productInfo.productQuantity, + isOutOfStock: productInfo.isOutOfStock, + unitShortNotation: units.shortNotation, + quantity: cartItems.quantity, + addedAt: cartItems.addedAt, + }) + .from(cartItems) + .innerJoin(productInfo, eq(cartItems.productId, productInfo.id)) + .innerJoin(units, eq(productInfo.unitId, units.id)) + .where(eq(cartItems.userId, userId)) + } + + async getProductById(productId: number) { + return db.query.productInfo.findFirst({ + where: eq(productInfo.id, productId), + }) + } + + async getCartItemByUserAndProduct(userId: number, productId: number): Promise { + return db.query.cartItems.findFirst({ + where: and(eq(cartItems.userId, userId), eq(cartItems.productId, productId)), + }) + } + + async incrementCartItemQuantity(cartItemId: number, quantity: number): Promise { + await db.update(cartItems) + .set({ + quantity: sql`${cartItems.quantity} + ${quantity}`, + }) + .where(eq(cartItems.id, cartItemId)) + } + + async createCartItem(userId: number, productId: number, quantity: number): Promise { + await db.insert(cartItems).values({ + userId, + productId, + quantity: quantity.toString(), + }) + } + + async updateCartItemQuantity(itemId: number, userId: number, quantity: number): Promise { + const [updatedItem] = await db.update(cartItems) + .set({ quantity: quantity.toString() }) + .where(and(eq(cartItems.id, itemId), eq(cartItems.userId, userId))) + .returning() + return updatedItem + } + + async deleteCartItem(itemId: number, userId: number): Promise { + const [deletedItem] = await db.delete(cartItems) + .where(and(eq(cartItems.id, itemId), eq(cartItems.userId, userId))) + .returning() + return deletedItem + } + + async clearCart(userId: number): Promise { + await db.delete(cartItems).where(eq(cartItems.userId, userId)) + } +} + +export const userCartDbService: IUserCartDbService = new UserCartDbService() diff --git a/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-complaint-queries.ts b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-complaint-queries.ts new file mode 100644 index 0000000..f3f4945 --- /dev/null +++ b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-complaint-queries.ts @@ -0,0 +1,28 @@ +import { db } from '@/src/db/db_index_sqlite' +import { complaints } from '@/src/db/schema' +import { eq, asc } from 'drizzle-orm' +import { IUserComplaintDbService } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-complaint-db-service.interface' + +export class UserComplaintDbService implements IUserComplaintDbService { + async getComplaintsByUserId(userId: number) { + return db + .select({ + id: complaints.id, + complaintBody: complaints.complaintBody, + response: complaints.response, + isResolved: complaints.isResolved, + createdAt: complaints.createdAt, + orderId: complaints.orderId, + images: complaints.images, + }) + .from(complaints) + .where(eq(complaints.userId, userId)) + .orderBy(asc(complaints.createdAt)) + } + + async createComplaint(data: { userId: number; orderId?: number | null; complaintBody: string; images: string[] }) { + await db.insert(complaints).values(data) + } +} + +export const userComplaintDbService: IUserComplaintDbService = new UserComplaintDbService() diff --git a/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-coupon-queries.ts b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-coupon-queries.ts new file mode 100644 index 0000000..bcdcff6 --- /dev/null +++ b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-coupon-queries.ts @@ -0,0 +1,88 @@ +import { db } from '@/src/db/db_index_sqlite' +import { coupons, couponUsage, couponApplicableUsers, couponApplicableProducts, reservedCoupons } from '@/src/db/schema' +import { eq, and, or, gt, isNull } from 'drizzle-orm' +import { IUserCouponDbService, Coupon, ReservedCoupon, CouponWithRelations } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-coupon-db-service.interface' + +export class UserCouponDbService implements IUserCouponDbService { + async getActiveCouponsForUser(userId: number): Promise { + return db.query.coupons.findMany({ + where: and( + eq(coupons.isInvalidated, false), + or( + isNull(coupons.validTill), + gt(coupons.validTill, new Date()) + ) + ), + with: { + usages: { + where: eq(couponUsage.userId, userId), + }, + applicableUsers: { + with: { user: true }, + }, + applicableProducts: { + with: { product: true }, + }, + }, + }) as Promise + } + + async getAllCouponsForUser(userId: number): Promise { + return db.query.coupons.findMany({ + with: { + usages: { + where: eq(couponUsage.userId, userId), + }, + applicableUsers: { + with: { user: true }, + }, + applicableProducts: { + with: { product: true }, + }, + }, + }) as Promise + } + + async getReservedCouponBySecretCode(secretCode: string): Promise { + return db.query.reservedCoupons.findFirst({ + where: and( + eq(reservedCoupons.secretCode, secretCode.toUpperCase()), + eq(reservedCoupons.isRedeemed, false) + ), + }) + } + + async redeemReservedCoupon(userId: number, reservedCoupon: ReservedCoupon): Promise { + return db.transaction(async (tx) => { + const [coupon] = await tx.insert(coupons).values({ + couponCode: reservedCoupon.couponCode, + isUserBased: true, + discountPercent: reservedCoupon.discountPercent, + flatDiscount: reservedCoupon.flatDiscount, + minOrder: reservedCoupon.minOrder, + productIds: reservedCoupon.productIds, + maxValue: reservedCoupon.maxValue, + isApplyForAll: false, + validTill: reservedCoupon.validTill, + maxLimitForUser: reservedCoupon.maxLimitForUser, + exclusiveApply: reservedCoupon.exclusiveApply, + createdBy: reservedCoupon.createdBy, + }).returning() + + await tx.insert(couponApplicableUsers).values({ + couponId: coupon.id, + userId, + }) + + await tx.update(reservedCoupons).set({ + isRedeemed: true, + redeemedBy: userId, + redeemedAt: new Date(), + }).where(eq(reservedCoupons.id, reservedCoupon.id)) + + return coupon + }) + } +} + +export const userCouponDbService: IUserCouponDbService = new UserCouponDbService() diff --git a/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-order-queries.ts b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-order-queries.ts new file mode 100644 index 0000000..54c0af0 --- /dev/null +++ b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-order-queries.ts @@ -0,0 +1,265 @@ +import { db } from '@/src/db/db_index_sqlite' +import { + orders, + orderItems, + orderStatus, + addresses, + productInfo, + paymentInfoTable, + coupons, + couponUsage, + cartItems, + refunds, + units, + userDetails, +} from '@/src/db/schema' +import { and, desc, eq, gte, inArray } from 'drizzle-orm' +import { + IUserOrderDbService, + Order, +} from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-order-db-service.interface' + +export class UserOrderDbService implements IUserOrderDbService { + async getUserDetailByUserId(userId: number) { + return db.query.userDetails.findFirst({ + where: eq(userDetails.userId, userId), + }) + } + + async getAddressByUserId(userId: number, addressId: number) { + return db.query.addresses.findFirst({ + where: and(eq(addresses.userId, userId), eq(addresses.id, addressId)), + }) + } + + async getProductById(productId: number) { + return db.query.productInfo.findFirst({ + where: eq(productInfo.id, productId), + }) + } + + async getCouponWithUsage(couponId: number, userId: number) { + return db.query.coupons.findFirst({ + where: eq(coupons.id, couponId), + with: { + usages: { where: eq(couponUsage.userId, userId) }, + }, + }) + } + + async createOrdersWithItems(params: { + ordersData: Array<{ + order: Omit + orderItems: Omit[] + orderStatus: Omit + }> + paymentMethod: 'online' | 'cod' + }): Promise { + const { ordersData, paymentMethod } = params + return db.transaction(async (tx) => { + let sharedPaymentInfoId: number | null = null + if (paymentMethod === 'online') { + const [paymentInfo] = await tx + .insert(paymentInfoTable) + .values({ + status: 'pending', + gateway: 'razorpay', + merchantOrderId: `multi_order_${Date.now()}`, + }) + .returning() + sharedPaymentInfoId = paymentInfo.id + } + + const ordersToInsert: Omit[] = ordersData.map( + (od) => ({ + ...od.order, + paymentInfoId: sharedPaymentInfoId, + }) + ) + + const insertedOrders = await tx.insert(orders).values(ordersToInsert).returning() + + const allOrderItems: Omit[] = [] + const allOrderStatuses: Omit[] = [] + + insertedOrders.forEach((order, index) => { + const od = ordersData[index] + od.orderItems.forEach((item) => { + allOrderItems.push({ ...item, orderId: order.id as number }) + }) + allOrderStatuses.push({ + ...od.orderStatus, + orderId: order.id as number, + }) + }) + + await tx.insert(orderItems).values(allOrderItems) + await tx.insert(orderStatus).values(allOrderStatuses) + + return insertedOrders + }) + } + + async deleteCartItemsByUserAndProductIds(userId: number, productIds: number[]) { + await db.delete(cartItems).where( + and(eq(cartItems.userId, userId), inArray(cartItems.productId, productIds)) + ) + } + + async createCouponUsage(params: { userId: number; couponId: number; orderId: number }) { + const { userId, couponId, orderId } = params + await db.insert(couponUsage).values({ + userId, + couponId, + orderId, + orderItemId: null, + usedAt: new Date(), + }) + } + + async getOrdersCount(userId: number) { + return db.$count(orders, eq(orders.userId, userId)) + } + + async getOrdersWithRelations(userId: number, limit: number, offset: number) { + return db.query.orders.findMany({ + where: eq(orders.userId, userId), + with: { + orderItems: { + with: { + product: true, + }, + }, + slot: true, + paymentInfo: true, + orderStatus: true, + refunds: true, + }, + orderBy: (ordersRef, { desc }) => [desc(ordersRef.createdAt)], + limit, + offset, + }) + } + + async getOrderWithDetailsById(orderId: number, userId: number) { + return db.query.orders.findFirst({ + where: and(eq(orders.id, orderId), eq(orders.userId, userId)), + with: { + orderItems: { + with: { + product: true, + }, + }, + slot: true, + paymentInfo: true, + orderStatus: { + with: { + refundCoupon: true, + }, + }, + refunds: true, + }, + }) + } + + async getCouponUsagesByOrderId(orderId: number) { + return db.query.couponUsage.findMany({ + where: eq(couponUsage.orderId, orderId), + with: { + coupon: true, + }, + }) + } + + async getOrderWithStatus(orderId: number) { + return db.query.orders.findFirst({ + where: eq(orders.id, orderId), + with: { + orderStatus: true, + }, + }) + } + + async cancelOrderTransaction(params: { + statusId: number + reason: string + orderId: number + refundStatus: string + }) { + const { statusId, reason, orderId, refundStatus } = params + await db.transaction(async (tx) => { + await tx + .update(orderStatus) + .set({ + isCancelled: true, + cancelReason: reason, + cancellationUserNotes: reason, + cancellationReviewed: false, + }) + .where(eq(orderStatus.id, statusId)) + + await tx.insert(refunds).values({ + orderId, + refundStatus, + }) + }) + } + + async updateOrderNotes(orderId: number, userNotes: string | null) { + await db + .update(orders) + .set({ + userNotes, + }) + .where(eq(orders.id, orderId)) + } + + async getRecentDeliveredOrderIds(userId: number, since: Date, limit: number) { + return db + .select({ id: orders.id }) + .from(orders) + .innerJoin(orderStatus, eq(orders.id, orderStatus.orderId)) + .where( + and( + eq(orders.userId, userId), + eq(orderStatus.isDelivered, true), + gte(orders.createdAt, since) + ) + ) + .orderBy(desc(orders.createdAt)) + .limit(limit) + } + + async getProductIdsByOrderIds(orderIds: number[]) { + return db + .select({ productId: orderItems.productId }) + .from(orderItems) + .where(inArray(orderItems.orderId, orderIds)) + } + + async getProductsWithUnitsByIds(productIds: number[], limit: number) { + return db + .select({ + id: productInfo.id, + name: productInfo.name, + shortDescription: productInfo.shortDescription, + price: productInfo.price, + images: productInfo.images, + isOutOfStock: productInfo.isOutOfStock, + unitShortNotation: units.shortNotation, + incrementStep: productInfo.incrementStep, + }) + .from(productInfo) + .innerJoin(units, eq(productInfo.unitId, units.id)) + .where( + and( + inArray(productInfo.id, productIds), + eq(productInfo.isSuspended, false) + ) + ) + .orderBy(desc(productInfo.createdAt)) + .limit(limit) + } +} + +export const userOrderDbService: IUserOrderDbService = new UserOrderDbService() diff --git a/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-product-queries.ts b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-product-queries.ts new file mode 100644 index 0000000..a18748e --- /dev/null +++ b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-product-queries.ts @@ -0,0 +1,111 @@ +import { db } from '@/src/db/db_index_sqlite' +import { productInfo, units, storeInfo, productSlots, deliverySlotInfo, specialDeals, productReviews, users } from '@/src/db/schema' +import { eq, and, gt, sql, desc } from 'drizzle-orm' +import { IUserProductDbService, Review } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-product-db-service.interface' + +export class UserProductDbService implements IUserProductDbService { + async getProductById(productId: number) { + const result = await db + .select({ + id: productInfo.id, + name: productInfo.name, + shortDescription: productInfo.shortDescription, + longDescription: productInfo.longDescription, + price: productInfo.price, + marketPrice: productInfo.marketPrice, + images: productInfo.images, + isOutOfStock: productInfo.isOutOfStock, + storeId: productInfo.storeId, + unitShortNotation: units.shortNotation, + incrementStep: productInfo.incrementStep, + productQuantity: productInfo.productQuantity, + isFlashAvailable: productInfo.isFlashAvailable, + flashPrice: productInfo.flashPrice, + }) + .from(productInfo) + .innerJoin(units, eq(productInfo.unitId, units.id)) + .where(eq(productInfo.id, productId)) + .limit(1) + return result[0] + } + + async getStoreBasicById(storeId: number) { + return db.query.storeInfo.findFirst({ + where: eq(storeInfo.id, storeId), + columns: { id: true, name: true, description: true }, + }) + } + + async getDeliverySlotsForProduct(productId: number) { + const now = new Date() + return db + .select({ + id: deliverySlotInfo.id, + deliveryTime: deliverySlotInfo.deliveryTime, + freezeTime: deliverySlotInfo.freezeTime, + }) + .from(productSlots) + .innerJoin(deliverySlotInfo, eq(productSlots.slotId, deliverySlotInfo.id)) + .where( + and( + eq(productSlots.productId, productId), + eq(deliverySlotInfo.isActive, true), + eq(deliverySlotInfo.isCapacityFull, false), + gt(deliverySlotInfo.deliveryTime, now), + gt(deliverySlotInfo.freezeTime, now) + ) + ) + .orderBy(deliverySlotInfo.deliveryTime) + } + + async getSpecialDealsForProduct(productId: number) { + const now = new Date() + return db + .select({ + quantity: specialDeals.quantity, + price: specialDeals.price, + validTill: specialDeals.validTill, + }) + .from(specialDeals) + .where( + and( + eq(specialDeals.productId, productId), + gt(specialDeals.validTill, now) + ) + ) + .orderBy(specialDeals.quantity) + } + + async getProductReviews(productId: number, limit: number, offset: number) { + return db + .select({ + id: productReviews.id, + reviewBody: productReviews.reviewBody, + ratings: productReviews.ratings, + imageUrls: productReviews.imageUrls, + reviewTime: productReviews.reviewTime, + userName: users.name, + }) + .from(productReviews) + .innerJoin(users, eq(productReviews.userId, users.id)) + .where(eq(productReviews.productId, productId)) + .orderBy(desc(productReviews.reviewTime)) + .limit(limit) + .offset(offset) + } + + async getReviewCount(productId: number): Promise { + const result = await db + .select({ count: sql`count(*)` }) + .from(productReviews) + .where(eq(productReviews.productId, productId)) + return Number(result[0].count) + } + + async createReview(data: { userId: number; productId: number; reviewBody: string; ratings: number; imageUrls: string[] }): Promise { + const [newReview] = await db.insert(productReviews).values(data).returning() + return newReview + } +} + +export const userProductDbService: IUserProductDbService = new UserProductDbService() diff --git a/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-profile-queries.ts b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-profile-queries.ts new file mode 100644 index 0000000..53f42b7 --- /dev/null +++ b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-profile-queries.ts @@ -0,0 +1,74 @@ +import { db } from '@/src/db/db_index_sqlite' +import { users, userDetails, userCreds, notifCreds, unloggedUserTokens } from '@/src/db/schema' +import { eq, and } from 'drizzle-orm' +import { IUserProfileDbService, User, UserDetail, UserCred, NotifCred, UnloggedToken } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-profile-db-service.interface' + +export class UserProfileDbService implements IUserProfileDbService { + async getUserById(userId: number): Promise { + const [user] = await db.select().from(users).where(eq(users.id, userId)).limit(1) + return user + } + + async getUserDetailByUserId(userId: number): Promise { + const [detail] = await db.select().from(userDetails).where(eq(userDetails.userId, userId)).limit(1) + return detail + } + + async getUserWithCreds(userId: number): Promise<{ user: User; creds: UserCred | null } | undefined> { + 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 undefined + + const row = result[0] as any + return { + user: row.users, + creds: row.user_creds || null, + } + } + + async getNotifCredByUserAndToken(userId: number, token: string): Promise { + return db.query.notifCreds.findFirst({ + where: and(eq(notifCreds.userId, userId), eq(notifCreds.token, token)), + }) + } + + async updateNotifCredLastVerified(id: number): Promise { + await db.update(notifCreds).set({ lastVerified: new Date() }).where(eq(notifCreds.id, id)) + } + + async insertNotifCred(userId: number, token: string): Promise { + await db.insert(notifCreds).values({ + userId, + token, + lastVerified: new Date(), + }) + } + + async deleteUnloggedToken(token: string): Promise { + await db.delete(unloggedUserTokens).where(eq(unloggedUserTokens.token, token)) + } + + async getUnloggedToken(token: string): Promise { + return db.query.unloggedUserTokens.findFirst({ + where: eq(unloggedUserTokens.token, token), + }) + } + + async updateUnloggedTokenLastVerified(id: number): Promise { + await db.update(unloggedUserTokens).set({ lastVerified: new Date() }).where(eq(unloggedUserTokens.id, id)) + } + + async insertUnloggedToken(token: string): Promise { + await db.insert(unloggedUserTokens).values({ + token, + lastVerified: new Date(), + }) + } +} + +export const userProfileDbService: IUserProfileDbService = new UserProfileDbService() diff --git a/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-slot-queries.ts b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-slot-queries.ts new file mode 100644 index 0000000..dc60891 --- /dev/null +++ b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-slot-queries.ts @@ -0,0 +1,26 @@ +import { db } from '@/src/db/db_index_sqlite' +import { deliverySlotInfo, productInfo } from '@/src/db/schema' +import { eq } from 'drizzle-orm' +import { IUserSlotDbService, Slot } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-slot-db-service.interface' + +export class UserSlotDbService implements IUserSlotDbService { + async getActiveSlots(): Promise { + return db.query.deliverySlotInfo.findMany({ + where: eq(deliverySlotInfo.isActive, true), + }) + } + + async getProductAvailability(): Promise> { + return db + .select({ + id: productInfo.id, + name: productInfo.name, + isOutOfStock: productInfo.isOutOfStock, + isFlashAvailable: productInfo.isFlashAvailable, + }) + .from(productInfo) + .where(eq(productInfo.isSuspended, false)) + } +} + +export const userSlotDbService: IUserSlotDbService = new UserSlotDbService() diff --git a/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-store-queries.ts b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-store-queries.ts new file mode 100644 index 0000000..af30ed5 --- /dev/null +++ b/apps/backend/src/trpc/apis/user-apis/dataAccessors/sqlite/user-store-queries.ts @@ -0,0 +1,69 @@ +import { db } from '@/src/db/db_index_sqlite' +import { storeInfo, productInfo, units } from '@/src/db/schema' +import { eq, and, sql } from 'drizzle-orm' +import { IUserStoreDbService } from '@/src/trpc/apis/user-apis/dataAccessors/interfaces/user-store-db-service.interface' + +export class UserStoreDbService implements IUserStoreDbService { + async getStoresWithProductCount(): Promise> { + return db + .select({ + id: storeInfo.id, + name: storeInfo.name, + description: storeInfo.description, + imageUrl: storeInfo.imageUrl, + productCount: sql`count(${productInfo.id})`.as('productCount'), + }) + .from(storeInfo) + .leftJoin( + productInfo, + and(eq(productInfo.storeId, storeInfo.id), eq(productInfo.isSuspended, false)) + ) + .groupBy(storeInfo.id) + } + + async getStoreById(storeId: number) { + return db.query.storeInfo.findFirst({ + where: eq(storeInfo.id, storeId), + columns: { + id: true, + name: true, + description: true, + imageUrl: true, + }, + }) + } + + async getSampleProductsByStoreId(storeId: number, limit: number) { + return db + .select({ + id: productInfo.id, + name: productInfo.name, + images: productInfo.images, + }) + .from(productInfo) + .where(and(eq(productInfo.storeId, storeId), eq(productInfo.isSuspended, false))) + .limit(limit) + } + + async getStoreProductsWithUnits(storeId: number) { + return db + .select({ + id: productInfo.id, + name: productInfo.name, + shortDescription: productInfo.shortDescription, + price: productInfo.price, + marketPrice: productInfo.marketPrice, + images: productInfo.images, + isOutOfStock: productInfo.isOutOfStock, + incrementStep: productInfo.incrementStep, + unitShortNotation: units.shortNotation, + unitNotation: units.shortNotation, + productQuantity: productInfo.productQuantity, + }) + .from(productInfo) + .innerJoin(units, eq(productInfo.unitId, units.id)) + .where(and(eq(productInfo.storeId, storeId), eq(productInfo.isSuspended, false))) + } +} + +export const userStoreDbService: IUserStoreDbService = new UserStoreDbService()