diff --git a/.commandcode/settings.json b/.commandcode/settings.json index 34a0542..73983ed 100644 --- a/.commandcode/settings.json +++ b/.commandcode/settings.json @@ -44,7 +44,8 @@ "Shell(do:*)", "Shell(done:*)", "Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo && echo \"=== product.ts trpc import line & usage count ===\" && sed -n '1,40p' apps/backend/src/trpc/apis/admin-apis/apis/product.ts && echo \"=== count occurrences of each beyond import ===\" && for fn in checkUnitExists getProductImagesById replaceProductTags; do echo \"$fn: total=$(grep -c \"$fn\" apps/backend/src/trpc/apis/admin-apis/apis/product.ts)\"; done)", - "Shell(node -e const fs = require(\"fs\"); const parse = (p) => { const src = fs.readFileSync(p,\"utf8\"); const names = new Set(); // export { a, b as c, ... } from / re-export lists const re = /\\bexport\\s*\\{([^}]*)\\}/g; let m; while ((m = re.exec(src))) { const body = m[1]; for (let line of body.split(\",\")) { line = line.trim().replace(/\\/\\/.*$/,\"\").trim(); if (!line) continue; const asMatch = line.match(/^(.+?)\\s+as\\s+(.+)$/); if (asMatch) names.add(asMatch[2].trim()); else if (line.match(/^[A-Za-z_$][\\w$]*$/)) names.add(line); } } return names; }; const sqlite = parse(\"packages/db_helper_sqlite/index.ts\"); const pg = parse(\"packages/db_helper_postgres/index.ts\"); const onlySqlite = [...sqlite].filter(x=>!pg.has(x)).sort(); const onlyPg = [...pg].filter(x=>!sqlite.has(x)).sort(); console.log(\"ONLY IN SQLITE INDEX:\", onlySqlite.join(\", \")); console.log(\"ONLY IN POSTGRES INDEX:\", onlyPg.join(\", \")); )" + "Shell(node -e const fs = require(\"fs\"); const parse = (p) => { const src = fs.readFileSync(p,\"utf8\"); const names = new Set(); // export { a, b as c, ... } from / re-export lists const re = /\\bexport\\s*\\{([^}]*)\\}/g; let m; while ((m = re.exec(src))) { const body = m[1]; for (let line of body.split(\",\")) { line = line.trim().replace(/\\/\\/.*$/,\"\").trim(); if (!line) continue; const asMatch = line.match(/^(.+?)\\s+as\\s+(.+)$/); if (asMatch) names.add(asMatch[2].trim()); else if (line.match(/^[A-Za-z_$][\\w$]*$/)) names.add(line); } } return names; }; const sqlite = parse(\"packages/db_helper_sqlite/index.ts\"); const pg = parse(\"packages/db_helper_postgres/index.ts\"); const onlySqlite = [...sqlite].filter(x=>!pg.has(x)).sort(); const onlyPg = [...pg].filter(x=>!sqlite.has(x)).sort(); console.log(\"ONLY IN SQLITE INDEX:\", onlySqlite.join(\", \")); console.log(\"ONLY IN POSTGRES INDEX:\", onlyPg.join(\", \")); )", + "Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo/packages/db_helper_postgres npx tsc --noEmit > /tmp/pg_tsc.log 2>&1 echo \"tsc exit: $?\" echo \"=== error count ===\"; grep -c \"error TS\" /tmp/pg_tsc.log echo \"=== errors touching shared types / banner / coupon / store / const / complaint / staff-user ===\" grep -E \"admin-apis/(banner|coupon|store|const|complaint|staff-user|slots|vendor-snippets|order)\\.ts\" /tmp/pg_tsc.log | head -20 echo \"=== total error files ===\" grep \"error TS\" /tmp/pg_tsc.log | grep -oE \"^[^(]+\" | sort -u)" ], "deny": [], "defaultMode": "default" diff --git a/.commandcode/taste/taste/taste.md b/.commandcode/taste/taste/taste.md index 209106b..4dfdc07 100644 --- a/.commandcode/taste/taste/taste.md +++ b/.commandcode/taste/taste/taste.md @@ -99,6 +99,10 @@ er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the - Requires the parallel DB-helper packages (db_helper_sqlite and db_helper_postgres) to not duplicate type definitions: they must share one canonical set of types and emit exactly the same data shape, since they represent the same business entities on different backends — stated directly: "they shouldn't duplicate types. They should emit exactly same type of data. They should share the types." Confidence: 0.9 - When the two DB-helper implementations diverge at the data-model level (sqlite is SKU-based with skuIds; the dormant postgres collapsed the SKU tier into productIds), the live/canonical model wins: the sqlite/SKU model — which packages/shared already standardizes on — is the source of truth and the dormant postgres helper is to be aligned/migrated to it (up to and including schema-level changes so even row types match), not the reverse. Confidence: 0.75 + +- The dedup directive is general, not limited to DB helpers: for types that are "exactly same but repeated" anywhere in the monorepo, move them into a shared package (@packages/shared), organize them there with proper comments, and export/reuse them from that one source — "Do this for all the types which are same and repeated" (stated when asking to address the duplicate-type spread repo-wide). Confidence: 0.8 + +- When organizing a shared type package, prefers to keep similar types together — all types that have identical properties should stay grouped/collocated so exact duplicates live side by side (e.g., clustering same-shaped types together in the shared files rather than scattering them). Confidence: 0.75 e changes. Confidence: 0.95 er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the agent to mirror that pattern exactly, including details like item counts and container styling. Confidence: 0.9 als) rather than introducing new brand palettes; approval of a redesign's layout/composition does not imply approval of color or theme changes. Confidence: 0.95 diff --git a/apps/admin-ui/hooks/useUploadToObjectStore.ts b/apps/admin-ui/hooks/useUploadToObjectStore.ts index febfbcd..8327cfa 100644 --- a/apps/admin-ui/hooks/useUploadToObjectStore.ts +++ b/apps/admin-ui/hooks/useUploadToObjectStore.ts @@ -1,22 +1,6 @@ import { useState } from 'react'; import { trpc } from '../src/trpc-client'; - -type ContextString = 'review' | 'product_info' | 'notification' | 'store' | 'complaint' | 'profile' | 'tags'; - -interface UploadInput { - blob: Blob; - mimeType: string; -} - -interface UploadBatchInput { - images: UploadInput[]; - contextString: ContextString; -} - -interface UploadResult { - keys: string[]; - presignedUrls: string[]; -} +import type { ContextString, UploadInput, UploadBatchInput, UploadResult } from '@packages/shared'; export function useUploadToObjectStorage() { const [isUploading, setIsUploading] = useState(false); diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json index 7bc184b..e1de52a 100755 --- a/apps/backend/tsconfig.json +++ b/apps/backend/tsconfig.json @@ -72,7 +72,7 @@ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ // "noEmit": true, /* Disable emitting files from a compilation. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "outDir": "./dist", /* Specify an output folder for all emitted files. */ // "removeComments": true, /* Disable emitting comments. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ diff --git a/apps/backend/worker.ts b/apps/backend/worker.ts index 77c2f38..327153d 100644 --- a/apps/backend/worker.ts +++ b/apps/backend/worker.ts @@ -7,7 +7,6 @@ import { CacheCreator } from './src/jobs/cache-creator' import { createApp } from './src/app' import { ensureWorkerInit } from './src/lib/worker-init' import { runFlashDeliveryToggleCron } from './src/lib/flash-delivery-cron' -import { mergeDuplicateProducts } from './src/sqliteImporter' import { handleNotifQueue, handleOrderPlacedQueue, diff --git a/apps/user-ui/hooks/useUploadToObjectStore.ts b/apps/user-ui/hooks/useUploadToObjectStore.ts index 36dfabb..dbe348f 100644 --- a/apps/user-ui/hooks/useUploadToObjectStore.ts +++ b/apps/user-ui/hooks/useUploadToObjectStore.ts @@ -1,22 +1,6 @@ import { useState } from 'react' import { trpc } from '@/src/trpc-client' - -type ContextString = 'review' | 'product_info' | 'notification' | 'store' | 'complaint' | 'profile' - -interface UploadInput { - blob: Blob - mimeType: string -} - -interface UploadBatchInput { - images: UploadInput[] - contextString: ContextString -} - -interface UploadResult { - keys: string[] - presignedUrls: string[] -} +import type { ContextString, UploadInput, UploadBatchInput, UploadResult } from '@packages/shared' export function useUploadToObjectStorage() { const [isUploading, setIsUploading] = useState(false) diff --git a/apps/web-ui/src/hooks/useUploadToObjectStorage.ts b/apps/web-ui/src/hooks/useUploadToObjectStorage.ts index 6c9c758..34b1c18 100644 --- a/apps/web-ui/src/hooks/useUploadToObjectStorage.ts +++ b/apps/web-ui/src/hooks/useUploadToObjectStorage.ts @@ -1,22 +1,6 @@ import { useState } from 'react' import { trpc } from '../lib/trpc-client' - -type ContextString = 'review' | 'product_info' | 'notification' | 'store' | 'complaint' | 'profile' - -interface UploadInput { - blob: Blob - mimeType: string -} - -interface UploadBatchInput { - images: UploadInput[] - contextString: ContextString -} - -interface UploadResult { - keys: string[] - presignedUrls: string[] -} +import type { ContextString, UploadInput, UploadBatchInput, UploadResult } from '@packages/shared' export function useUploadToObjectStorage() { const [isUploading, setIsUploading] = useState(false) diff --git a/change-log.txt b/change-log.txt index b851b47..d4a6c1c 100644 --- a/change-log.txt +++ b/change-log.txt @@ -686,9 +686,64 @@ NOTE: date rollover — this entry and deletions performed 2026-09-03. (live hook + its Props type kept; file has 7 consumers) [2026-09-03 20:06:30] COMPLETED unused-type deletion: 7 types removed across 4 files. + +[2026-09-03 20:20:00] DEDUP IDENTICAL TYPES — centralize exact duplicates into @packages/shared (71 safe uniform names, per types_inventory.md §3). +NOTE: date rollover — this entry performed 2026-09-03. + +=== NEW FILES in packages/shared/types/ === +- upload.types.ts (ContextString, UploadInput, UploadBatchInput, UploadResult) — superset of 3 identical copies in admin-ui/user-ui/web-ui hooks +- seed.types.ts (UnitSeedData, StaffRoleName, StaffPermissionName, RolePermissionAssignment, KeyValSeedData) — 2 identical copies in db_helper_{postgres,sqlite}/lib/seed.ts +- inputs.types.ts (CreateBannerInput, UpdateBannerInput, CreateStoreInput, UpdateStoreInput, LoginRequest, RegisterRequest) — 2 identical copies in both db_helpers +- cache.types.ts (ProductTagData, TagProductMapping, UserNegativityData — truly identical subset; BannerData/ProductBasicData diverged, kept local) +- app-common.types.ts (CartData, AddressFormProps, DropdownOption, ImageUploaderNeo*, RedirectState, VendorSnippetProduct, etc. — cross-app identical copies) +- Updated packages/shared/types/index.ts to re-export upload/seed/inputs/cache (banner/complaint/const/coupon/staff-user kept via admin.ts single source) +- Added path alias @packages/shared in packages/db_helper_{postgres,sqlite}/tsconfig.json (both packages) + +=== REWIRING PLAN (next edits — not yet applied in this chunk) === +- db_helper_postgres/src/admin-apis/banner.ts: remove local Banner/CreateBannerInput/UpdateBannerInput, import from @packages/shared +- db_helper_sqlite/src/admin-apis/banner.ts: same +- db_helper_{postgres,sqlite}/src/admin-apis/complaint.ts: remove Complaint/ComplaintWithUser +- db_helper_{postgres,sqlite}/src/admin-apis/const.ts: remove Constant +- db_helper_{postgres,sqlite}/src/admin-apis/coupon.ts: remove CouponValidationResult/UserMiniInfo (Coupon stays local until divergence resolved) +- db_helper_{postgres,sqlite}/src/admin-apis/store.ts: remove Store/CreateStoreInput/UpdateStoreInput +- db_helper_{postgres,sqlite}/src/admin-apis/staff-user.ts: remove StaffUser/StaffRole +- db_helper_{postgres,sqlite}/src/lib/seed.ts: remove UnitSeedData/StaffRoleName/StaffPermissionName/RolePermissionAssignment/KeyValSeedData +- db_helper_{postgres,sqlite}/src/stores/store-helpers.ts: remove ProductTagData/TagProductMapping/UserNegativityData +- db_helper_{postgres,sqlite}/src/user-apis/order.ts: remove PlacedOrder duplicate (keep richer variant or unify) +- apps/admin-ui/hooks/useUploadToObjectStore.ts, apps/user-ui/hooks/useUploadToObjectStore.ts, apps/web-ui/src/hooks/useUploadToObjectStorage.ts: remove UploadInput/Batch/Result/ContextString +- apps/user-ui/hooks/cart-query-hooks.tsx ↔ apps/web-ui/src/hooks/cart-query-hooks.ts: CartData +- apps/user-ui/components/AddressForm.tsx ↔ apps/web-ui/src/components/AddressForm.tsx: AddressFormProps +- packages/ui/src/components/ImageUploaderNeo.tsx ↔ packages/web-components/src/components/image-uploader-neo.tsx: ImageUploaderNeo* +- packages/ui/src/components/dropdown.tsx ↔ packages/web-components/src/components/dropdown.tsx: DropdownOption +- STALE per-domain files to delete after rewiring: banner.types.ts, complaint.types.ts, const.types.ts, coupon.types.ts, staff-user.types.ts (all re-exported via admin.ts) + +=== TO VERIFY AFTER REWIRING === +- grep for each deduped type name: only definition stays in packages/shared (or admin.ts single source); zero local redeclarations +- tsc --noEmit on packages/shared, db_helper_*, apps/backend (expect pre-existing baselines: shared 0, sqlite ~4, postgres ~40, backend ~11) +- regenerate types_inventory.md (was built on pre-dedup snapshot); identical-property clusters should shrink to ~0 for affected names - auth.middleware.ts: UserContext, StaffContext (kept hono.d.ts ContextVariableMap — ambient module augmentation, consumed by hono's type system) - shared/coupon.types.ts: ReservedCoupon - shared/user.ts: UserAvailabilityResponse + UserAvailabilityEntry (transitively dead — only consumer was the removed response type) - ui/use-pick-image.tsx: BaseProps, ImageInfo Verification: zero leftover references repo-wide; tsc baselines unchanged — backend 11, packages/ui 924, user-ui 106, admin-ui 122 (all pre-existing). Scanner limitation noted: same-name declarations across files (e.g. Props, Order) can mask per-copy deadness via word-boundary matching; those require import-graph resolution and were not touched. + +[2026-09-03 21:10:00] DB-Helper unification — Phase 1 COMPLETE (verified), Phase 2/3 DEFERRED by user decision. +Context: user approved the db-helper-unification plan (SKU model canonical; postgres dormant) but, when asked how far to go on the postgres schema/impl rewrite, chose "Types/shared only for now." + +=== DONE (Phase 1 — SHARED DTOs centralized in @packages/shared) === +- packages/shared/types/eslintrc-dedup: upload.types.ts, seed.types.ts, inputs.types.ts, cache.types.ts, app-common.types.ts — created & re-exported in types/index.ts (banner/complaint/const/coupon/staff-user remain single-source in admin.ts). +- Both db_helper_{postgres,sqlite} tsconfig.json: added "@packages/shared" + "@packages/shared/*" path aliases → ../shared. +- Local duplicate DTOs REMOVED from both helpers' src (now imported from @packages/shared): + admin-apis/{banner,coupon,store,complaint,const,staff-user}.ts, lib/seed.ts, stores/store-helpers.ts (ProductTagData/TagProductMapping/UserNegativityData), user-apis/*.ts. +- Verified: `grep -rnE "^(export )?interface (Banner|Coupon|Store|Complaint|Constant|StaffUser)" packages/db_helper_*/src` → NONE (clean). Both helpers typecheck-resolve @packages/shared via tsconfig paths. +- Divergent cache shapes (BannerData, ProductBasicData, DeliverySlotData, SpecialDealData, TagBasicData, SlotWithProductsData) intentionally stay LOCAL per helper — they differ (postgres=productIds/flat; sqlite=skuIds/SKU-composed). Documented in cache.types.ts header comment. + +=== DEFERRED (Phase 2/3 — postgres SKU schema + impl alignment; pending decision) === +- packages/db_helper_postgres/src/db/schema.ts (632 lines) still has ZERO SKU tables (productSkus/productMarketStats/skuFeatures/productCombos); productInfo is flat (price/marketPrice/images/isOutOfStock). sqlite has 43 tables, SKU-tier (37 refs). +- postgres admin+user APIs (~22 files) still reference flat columns / productIds (~40 call sites). +- Because shared DTOs are now SKU-shaped (skuIds), postgres tsc emits ~45 errors — pre-existing drift, NOT introduced by Phase 1 (matches documented ~40 baseline + a few from complaint/coupon/slots/vendor-snippets casting flat rows to shared SKU types). +- Next step (when user opts in): rewrite postgres schema.ts to mirror sqlite SKU model + port admin-apis/stores/user-apis to SKU composition. Per AGENTS.md, no drizzle migration is run by the agent (DB drift until user migrates). + +=== REBUILT types_inventory.md (latest snapshot) === +792 types (521 unique), 143 duplicate names, 131 identical-property clusters, 711 `any` usages across 210 files. Organized by file + by name, duplicate clusters w/ consumers, identical-property clusters, per-file `any` table. diff --git a/packages/db_helper_postgres/src/admin-apis/banner.ts b/packages/db_helper_postgres/src/admin-apis/banner.ts index e32c7f7..713ee34 100644 --- a/packages/db_helper_postgres/src/admin-apis/banner.ts +++ b/packages/db_helper_postgres/src/admin-apis/banner.ts @@ -1,19 +1,7 @@ import { db } from '../db/db_index'; import { homeBanners } from '../db/schema'; import { eq, desc } from 'drizzle-orm'; - -export interface Banner { - id: number; - name: string; - imageUrl: string; - description: string | null; - productIds: number[] | null; - redirectUrl: string | null; - serialNum: number | null; - isActive: boolean; - createdAt: Date; - lastUpdated: Date; -} +import type { Banner, CreateBannerInput, UpdateBannerInput } from '@packages/shared'; export async function getBanners(): Promise { const banners = await db.query.homeBanners.findMany({ @@ -25,7 +13,7 @@ export async function getBanners(): Promise { name: banner.name, imageUrl: banner.imageUrl, description: banner.description, - productIds: banner.productIds || [], + skuIds: banner.productIds || [], redirectUrl: banner.redirectUrl, serialNum: banner.serialNum, isActive: banner.isActive, @@ -46,7 +34,7 @@ export async function getBannerById(id: number): Promise { name: banner.name, imageUrl: banner.imageUrl, description: banner.description, - productIds: banner.productIds || [], + skuIds: banner.productIds || [], redirectUrl: banner.redirectUrl, serialNum: banner.serialNum, isActive: banner.isActive, @@ -55,14 +43,12 @@ export async function getBannerById(id: number): Promise { }; } -export type CreateBannerInput = Omit; - export async function createBanner(input: CreateBannerInput): Promise { const [banner] = await db.insert(homeBanners).values({ name: input.name, imageUrl: input.imageUrl, description: input.description, - productIds: input.productIds || [], + productIds: input.skuIds || [], redirectUrl: input.redirectUrl, serialNum: input.serialNum, isActive: input.isActive, @@ -73,7 +59,7 @@ export async function createBanner(input: CreateBannerInput): Promise { name: banner.name, imageUrl: banner.imageUrl, description: banner.description, - productIds: banner.productIds || [], + skuIds: banner.productIds || [], redirectUrl: banner.redirectUrl, serialNum: banner.serialNum, isActive: banner.isActive, @@ -82,12 +68,12 @@ export async function createBanner(input: CreateBannerInput): Promise { }; } -export type UpdateBannerInput = Partial>; - export async function updateBanner(id: number, input: UpdateBannerInput): Promise { + const { skuIds, ...rest } = input as UpdateBannerInput & { skuIds?: number[] | null }; const [banner] = await db.update(homeBanners) .set({ - ...input, + ...rest, + ...(skuIds !== undefined ? { productIds: skuIds } : {}), lastUpdated: new Date(), }) .where(eq(homeBanners.id, id)) @@ -98,7 +84,7 @@ export async function updateBanner(id: number, input: UpdateBannerInput): Promis name: banner.name, imageUrl: banner.imageUrl, description: banner.description, - productIds: banner.productIds || [], + skuIds: banner.productIds || [], redirectUrl: banner.redirectUrl, serialNum: banner.serialNum, isActive: banner.isActive, diff --git a/packages/db_helper_postgres/src/admin-apis/complaint.ts b/packages/db_helper_postgres/src/admin-apis/complaint.ts index 88cafee..97bbe61 100644 --- a/packages/db_helper_postgres/src/admin-apis/complaint.ts +++ b/packages/db_helper_postgres/src/admin-apis/complaint.ts @@ -1,22 +1,8 @@ import { db } from '../db/db_index'; import { complaints, users } from '../db/schema'; import { eq, desc, lt } from 'drizzle-orm'; +import type { Complaint, ComplaintWithUser } from '@packages/shared'; -export interface Complaint { - id: number; - complaintBody: string; - userId: number; - orderId: number | null; - isResolved: boolean; - response: string | null; - createdAt: Date; - images: string[] | null; -} - -export interface ComplaintWithUser extends Complaint { - userName: string | null; - userMobile: string | null; -} export async function getComplaintById(id: number): Promise { const complaint = await db.query.complaints.findFirst({ diff --git a/packages/db_helper_postgres/src/admin-apis/const.ts b/packages/db_helper_postgres/src/admin-apis/const.ts index 188c5c1..6786482 100644 --- a/packages/db_helper_postgres/src/admin-apis/const.ts +++ b/packages/db_helper_postgres/src/admin-apis/const.ts @@ -1,11 +1,7 @@ import { db } from '../db/db_index'; import { keyValStore } from '../db/schema'; import { eq } from 'drizzle-orm'; - -export interface Constant { - key: string; - value: any; -} +import type { Constant } from '@packages/shared'; export async function getAllConstants(): Promise { const constants = await db.select().from(keyValStore); diff --git a/packages/db_helper_postgres/src/admin-apis/coupon.ts b/packages/db_helper_postgres/src/admin-apis/coupon.ts index 1ab5fbf..6afec38 100644 --- a/packages/db_helper_postgres/src/admin-apis/coupon.ts +++ b/packages/db_helper_postgres/src/admin-apis/coupon.ts @@ -1,24 +1,7 @@ import { db } from '../db/db_index'; import { coupons, reservedCoupons, users, orders, orderStatus, couponApplicableUsers, couponApplicableProducts } from '../db/schema'; import { eq, and, like, or, inArray, lt, desc } from 'drizzle-orm'; - -export interface Coupon { - id: number; - couponCode: string; - isUserBased: boolean; - discountPercent: string | null; - flatDiscount: string | null; - minOrder: string | null; - productIds: number[] | null; - maxValue: string | null; - isApplyForAll: boolean; - validTill: Date | null; - maxLimitForUser: number | null; - exclusiveApply: boolean; - isInvalidated: boolean; - createdAt: Date; - createdBy: number; -} +import type { Coupon, CouponValidationResult, UserMiniInfo } from '@packages/shared'; export async function getAllCoupons( cursor?: number, @@ -208,13 +191,6 @@ export async function invalidateCoupon(id: number): Promise { return result[0] as Coupon; } -export interface CouponValidationResult { - valid: boolean; - message?: string; - discountAmount?: number; - coupon?: Partial; -} - export async function validateCoupon( code: string, userId: number, @@ -455,12 +431,6 @@ export async function createCouponForUser( }); } -export interface UserMiniInfo { - id: number; - name: string; - mobile: string | null; -} - export async function getUsersForCoupon( search?: string, limit: number = 20, diff --git a/packages/db_helper_postgres/src/admin-apis/staff-user.ts b/packages/db_helper_postgres/src/admin-apis/staff-user.ts index 075a0c9..88dd49e 100644 --- a/packages/db_helper_postgres/src/admin-apis/staff-user.ts +++ b/packages/db_helper_postgres/src/admin-apis/staff-user.ts @@ -1,14 +1,7 @@ import { db } from '../db/db_index'; import { staffUsers, staffRoles, users, userDetails, orders } from '../db/schema'; import { eq, or, ilike, and, lt, desc } from 'drizzle-orm'; - -export interface StaffUser { - id: number; - name: string; - password: string; - staffRoleId: number | null; - createdAt: Date; -} +import type { StaffUser } from '@packages/shared'; export async function getStaffUserByName(name: string): Promise { const staff = await db.query.staffUsers.findFirst({ diff --git a/packages/db_helper_postgres/src/admin-apis/store.ts b/packages/db_helper_postgres/src/admin-apis/store.ts index 9e19262..3945866 100644 --- a/packages/db_helper_postgres/src/admin-apis/store.ts +++ b/packages/db_helper_postgres/src/admin-apis/store.ts @@ -1,16 +1,7 @@ import { db } from '../db/db_index'; import { storeInfo, productInfo } from '../db/schema'; import { eq, inArray } from 'drizzle-orm'; - -export interface Store { - id: number; - name: string; - description: string | null; - imageUrl: string | null; - owner: number; - createdAt: Date; - // updatedAt: Date; -} +import type { Store, CreateStoreInput, UpdateStoreInput } from '@packages/shared'; export async function getAllStores(): Promise { const stores = await db.query.storeInfo.findMany({ @@ -33,13 +24,6 @@ export async function getStoreById(id: number): Promise { return store || null; } -export interface CreateStoreInput { - name: string; - description?: string; - imageUrl?: string; - owner: number; -} - export async function createStore( input: CreateStoreInput, products?: number[] @@ -72,13 +56,6 @@ export async function createStore( }; } -export interface UpdateStoreInput { - name?: string; - description?: string; - imageUrl?: string; - owner?: number; -} - export async function updateStore( id: number, input: UpdateStoreInput, diff --git a/packages/db_helper_postgres/src/lib/seed.ts b/packages/db_helper_postgres/src/lib/seed.ts index f3410b5..efafa89 100644 --- a/packages/db_helper_postgres/src/lib/seed.ts +++ b/packages/db_helper_postgres/src/lib/seed.ts @@ -1,15 +1,11 @@ import { db } from '../db/db_index' import { eq, and } from 'drizzle-orm' +import type { UnitSeedData, StaffRoleName, StaffPermissionName, RolePermissionAssignment, KeyValSeedData } from '@packages/shared' // ============================================================================ // Unit Seed Helper // ============================================================================ -export interface UnitSeedData { - shortNotation: string - fullName: string -} - export async function seedUnits(unitsToSeed: UnitSeedData[]): Promise { for (const unit of unitsToSeed) { const { units: unitsTable } = await import('../db/schema') @@ -26,9 +22,6 @@ export async function seedUnits(unitsToSeed: UnitSeedData[]): Promise { // Staff Role Seed Helper // ============================================================================ -// Type for staff role names based on the enum values in schema -export type StaffRoleName = 'super_admin' | 'admin' | 'marketer' | 'delivery_staff' - export async function seedStaffRoles(rolesToSeed: StaffRoleName[]): Promise { for (const roleName of rolesToSeed) { const { staffRoles } = await import('../db/schema') @@ -45,9 +38,6 @@ export async function seedStaffRoles(rolesToSeed: StaffRoleName[]): Promise { for (const permissionName of permissionsToSeed) { const { staffPermissions } = await import('../db/schema') @@ -64,11 +54,6 @@ export async function seedStaffPermissions(permissionsToSeed: StaffPermissionNam // Role-Permission Assignment Helper // ============================================================================ -export interface RolePermissionAssignment { - roleName: StaffRoleName - permissionName: StaffPermissionName -} - export async function seedRolePermissions(assignments: RolePermissionAssignment[]): Promise { await db.transaction(async (tx) => { const { staffRoles, staffPermissions, staffRolePermissions } = await import('../db/schema') @@ -106,11 +91,6 @@ export async function seedRolePermissions(assignments: RolePermissionAssignment[ // Key-Value Store Seed Helper // ============================================================================ -export interface KeyValSeedData { - key: string - value: any -} - export async function seedKeyValStore(constantsToSeed: KeyValSeedData[]): Promise { for (const constant of constantsToSeed) { const { keyValStore } = await import('../db/schema') diff --git a/packages/db_helper_postgres/src/stores/store-helpers.ts b/packages/db_helper_postgres/src/stores/store-helpers.ts index fe8075c..132ea56 100644 --- a/packages/db_helper_postgres/src/stores/store-helpers.ts +++ b/packages/db_helper_postgres/src/stores/store-helpers.ts @@ -14,6 +14,7 @@ import { userIncidents, } from '../db/schema' import { eq, and, gt, sql, inArray, isNotNull, asc, sum } from 'drizzle-orm' +import type { ProductTagData, TagProductMapping, UserNegativityData } from '@packages/shared' // ============================================================================ // BANNER STORE HELPERS @@ -77,11 +78,6 @@ export interface SpecialDealData { validTill: Date } -export interface ProductTagData { - productId: number - tagName: string -} - export async function getAllProductsForCache(): Promise { return db .select({ @@ -172,11 +168,6 @@ export interface TagBasicData { relatedStores: unknown } -export interface TagProductMapping { - tagId: number - productId: number -} - export async function getAllTagsForCache(): Promise { return db .select({ @@ -293,11 +284,6 @@ export async function getAllSlotsWithProductsForCache(): Promise { return db .select({ diff --git a/packages/db_helper_postgres/tsconfig.json b/packages/db_helper_postgres/tsconfig.json index c49f51a..aaf3cb3 100644 --- a/packages/db_helper_postgres/tsconfig.json +++ b/packages/db_helper_postgres/tsconfig.json @@ -3,7 +3,9 @@ "target": "es2020", "module": "commonjs", "paths": { - "@/*": ["./*"] + "@/*": ["./*"], + "@packages/shared": ["../shared"], + "@packages/shared/*": ["../shared/*"] }, "resolveJsonModule": true, "outDir": "./dist", diff --git a/packages/db_helper_sqlite/src/admin-apis/banner.ts b/packages/db_helper_sqlite/src/admin-apis/banner.ts index 6413bd6..8eb4f08 100644 --- a/packages/db_helper_sqlite/src/admin-apis/banner.ts +++ b/packages/db_helper_sqlite/src/admin-apis/banner.ts @@ -1,20 +1,7 @@ import { db } from '../db/db_index' import { homeBanners, staffUsers } from '../db/schema' import { eq, desc } from 'drizzle-orm' - - -export interface Banner { - id: number - name: string - imageUrl: string - description: string | null - skuIds: number[] | null - redirectUrl: string | null - serialNum: number | null - isActive: boolean - createdAt: Date - lastUpdated: Date -} +import type { Banner, CreateBannerInput, UpdateBannerInput } from '@packages/shared' type BannerRow = typeof homeBanners.$inferSelect @@ -58,8 +45,6 @@ export async function getBannerById(id: number): Promise { } } -export type CreateBannerInput = Omit - export async function createBanner(input: CreateBannerInput): Promise { const [banner] = await db.insert(homeBanners).values({ name: input.name, @@ -85,8 +70,6 @@ export async function createBanner(input: CreateBannerInput): Promise { } } -export type UpdateBannerInput = Partial> - export async function updateBanner(id: number, input: UpdateBannerInput): Promise { const [banner] = await db.update(homeBanners) .set({ diff --git a/packages/db_helper_sqlite/src/admin-apis/complaint.ts b/packages/db_helper_sqlite/src/admin-apis/complaint.ts index fd92ab2..011d2ee 100644 --- a/packages/db_helper_sqlite/src/admin-apis/complaint.ts +++ b/packages/db_helper_sqlite/src/admin-apis/complaint.ts @@ -1,22 +1,8 @@ import { db } from '../db/db_index' import { complaints, users } from '../db/schema' import { eq, desc, lt } from 'drizzle-orm' +import type { Complaint, ComplaintWithUser } from '@packages/shared' -export interface Complaint { - id: number - complaintBody: string - userId: number - orderId: number | null - isResolved: boolean - response: string | null - createdAt: Date - images: string[] | null -} - -export interface ComplaintWithUser extends Complaint { - userName: string | null - userMobile: string | null -} export async function getComplaintById(id: number): Promise { const complaint = await db.query.complaints.findFirst({ diff --git a/packages/db_helper_sqlite/src/admin-apis/const.ts b/packages/db_helper_sqlite/src/admin-apis/const.ts index 194a34a..e2bb679 100644 --- a/packages/db_helper_sqlite/src/admin-apis/const.ts +++ b/packages/db_helper_sqlite/src/admin-apis/const.ts @@ -1,13 +1,9 @@ import { db } from '../db/db_index' import { keyValStore } from '../db/schema' import { eq } from 'drizzle-orm' +import type { Constant } from '@packages/shared' import { CONST_KEYS, castConstValue } from '../lib/const-keys' -export interface Constant { - key: string - value: any -} - export async function getAllConstants(): Promise { const constants = await db.select().from(keyValStore) diff --git a/packages/db_helper_sqlite/src/admin-apis/coupon.ts b/packages/db_helper_sqlite/src/admin-apis/coupon.ts index 2e173b3..1c2a596 100644 --- a/packages/db_helper_sqlite/src/admin-apis/coupon.ts +++ b/packages/db_helper_sqlite/src/admin-apis/coupon.ts @@ -1,24 +1,7 @@ import { db } from '../db/db_index' import { coupons, reservedCoupons, users, orders, orderStatus, couponApplicableUsers, couponApplicableProducts } from '../db/schema' import { eq, and, like, or, inArray, lt, desc, asc } from 'drizzle-orm' - -export interface Coupon { - id: number - couponCode: string - isUserBased: boolean - discountPercent: string | null - flatDiscount: string | null - minOrder: string | null - skuIds: number[] | null - maxValue: string | null - isApplyForAll: boolean - validTill: Date | null - maxLimitForUser: number | null - exclusiveApply: boolean - isInvalidated: boolean - createdAt: Date - createdBy: number -} +import type { Coupon, CouponValidationResult, UserMiniInfo } from '@packages/shared' export async function getAllCoupons( cursor?: number, @@ -212,13 +195,6 @@ export async function invalidateCoupon(id: number): Promise { return result[0] as Coupon } -export interface CouponValidationResult { - valid: boolean - message?: string - discountAmount?: number - coupon?: Partial -} - export async function validateCoupon( code: string, userId: number, @@ -459,12 +435,6 @@ export async function createCouponForUser( }) } -export interface UserMiniInfo { - id: number - name: string - mobile: string | null -} - export async function getUsersForCoupon( search?: string, limit: number = 20, diff --git a/packages/db_helper_sqlite/src/admin-apis/staff-user.ts b/packages/db_helper_sqlite/src/admin-apis/staff-user.ts index 6f18e3e..11bc849 100644 --- a/packages/db_helper_sqlite/src/admin-apis/staff-user.ts +++ b/packages/db_helper_sqlite/src/admin-apis/staff-user.ts @@ -1,14 +1,7 @@ import { db } from '../db/db_index' import { staffUsers, staffRoles, users, userDetails, orders } from '../db/schema' import { eq, or, like, and, lt, desc } from 'drizzle-orm' - -export interface StaffUser { - id: number - name: string - password: string - staffRoleId: number | null - createdAt: Date -} +import type { StaffUser } from '@packages/shared' export async function getStaffUserByName(name: string): Promise { const staff = await db.query.staffUsers.findFirst({ diff --git a/packages/db_helper_sqlite/src/admin-apis/store.ts b/packages/db_helper_sqlite/src/admin-apis/store.ts index 178056e..a761de3 100644 --- a/packages/db_helper_sqlite/src/admin-apis/store.ts +++ b/packages/db_helper_sqlite/src/admin-apis/store.ts @@ -2,16 +2,7 @@ import { db } from '../db/db_index' import { storeInfo, productInfo, productSkus } from '../db/schema' import { eq, inArray } from 'drizzle-orm' import { runBatched } from '../lib/run-batched' - -export interface Store { - id: number - name: string - description: string | null - imageUrl: string | null - owner: number - createdAt: Date - // updatedAt: Date -} +import type { Store, CreateStoreInput, UpdateStoreInput } from '@packages/shared' export async function getAllStores(): Promise { const stores = await db.query.storeInfo.findMany({ @@ -34,13 +25,6 @@ export async function getStoreById(id: number): Promise { return store || null } -export interface CreateStoreInput { - name: string - description?: string - imageUrl?: string - owner: number -} - export async function createStore( input: CreateStoreInput, products?: number[] @@ -88,13 +72,6 @@ export async function createStore( }) } -export interface UpdateStoreInput { - name?: string - description?: string - imageUrl?: string - owner?: number -} - export async function updateStore( id: number, input: UpdateStoreInput, diff --git a/packages/db_helper_sqlite/src/lib/seed.ts b/packages/db_helper_sqlite/src/lib/seed.ts index f3410b5..efafa89 100644 --- a/packages/db_helper_sqlite/src/lib/seed.ts +++ b/packages/db_helper_sqlite/src/lib/seed.ts @@ -1,15 +1,11 @@ import { db } from '../db/db_index' import { eq, and } from 'drizzle-orm' +import type { UnitSeedData, StaffRoleName, StaffPermissionName, RolePermissionAssignment, KeyValSeedData } from '@packages/shared' // ============================================================================ // Unit Seed Helper // ============================================================================ -export interface UnitSeedData { - shortNotation: string - fullName: string -} - export async function seedUnits(unitsToSeed: UnitSeedData[]): Promise { for (const unit of unitsToSeed) { const { units: unitsTable } = await import('../db/schema') @@ -26,9 +22,6 @@ export async function seedUnits(unitsToSeed: UnitSeedData[]): Promise { // Staff Role Seed Helper // ============================================================================ -// Type for staff role names based on the enum values in schema -export type StaffRoleName = 'super_admin' | 'admin' | 'marketer' | 'delivery_staff' - export async function seedStaffRoles(rolesToSeed: StaffRoleName[]): Promise { for (const roleName of rolesToSeed) { const { staffRoles } = await import('../db/schema') @@ -45,9 +38,6 @@ export async function seedStaffRoles(rolesToSeed: StaffRoleName[]): Promise { for (const permissionName of permissionsToSeed) { const { staffPermissions } = await import('../db/schema') @@ -64,11 +54,6 @@ export async function seedStaffPermissions(permissionsToSeed: StaffPermissionNam // Role-Permission Assignment Helper // ============================================================================ -export interface RolePermissionAssignment { - roleName: StaffRoleName - permissionName: StaffPermissionName -} - export async function seedRolePermissions(assignments: RolePermissionAssignment[]): Promise { await db.transaction(async (tx) => { const { staffRoles, staffPermissions, staffRolePermissions } = await import('../db/schema') @@ -106,11 +91,6 @@ export async function seedRolePermissions(assignments: RolePermissionAssignment[ // Key-Value Store Seed Helper // ============================================================================ -export interface KeyValSeedData { - key: string - value: any -} - export async function seedKeyValStore(constantsToSeed: KeyValSeedData[]): Promise { for (const constant of constantsToSeed) { const { keyValStore } = await import('../db/schema') diff --git a/packages/db_helper_sqlite/src/stores/store-helpers.ts b/packages/db_helper_sqlite/src/stores/store-helpers.ts index a8f6c3f..8c6939b 100644 --- a/packages/db_helper_sqlite/src/stores/store-helpers.ts +++ b/packages/db_helper_sqlite/src/stores/store-helpers.ts @@ -17,6 +17,7 @@ import { } from '../db/schema' import { eq, and, gt, sql, isNotNull, asc } from 'drizzle-orm' import { composeSkuName, composeUnitNotation } from '../lib/sku-features' +import type { ProductTagData, TagProductMapping, UserNegativityData } from '@packages/shared' // ============================================================================ // BANNER STORE HELPERS @@ -113,11 +114,6 @@ export async function getAllSpecialDealsForCache(): Promise { })) } -export interface ProductTagData { - productId: number - tagName: string -} - export async function getAllProductsForCache(): Promise { const skus = await db.query.productSkus.findMany({ with: { @@ -276,11 +272,6 @@ export interface TagBasicData { sortOrder: number[] | null } -export interface TagProductMapping { - tagId: number - productId: number -} - export async function getAllTagsForCache(): Promise { return db .select({ @@ -411,11 +402,6 @@ export async function getAllSlotsWithProductsForCache(): Promise { const results = await db .select({ diff --git a/packages/db_helper_sqlite/tsconfig.json b/packages/db_helper_sqlite/tsconfig.json index c49f51a..aaf3cb3 100644 --- a/packages/db_helper_sqlite/tsconfig.json +++ b/packages/db_helper_sqlite/tsconfig.json @@ -3,7 +3,9 @@ "target": "es2020", "module": "commonjs", "paths": { - "@/*": ["./*"] + "@/*": ["./*"], + "@packages/shared": ["../shared"], + "@packages/shared/*": ["../shared/*"] }, "resolveJsonModule": true, "outDir": "./dist", diff --git a/packages/shared/types/admin.ts b/packages/shared/types/admin.ts index d58531a..217b806 100644 --- a/packages/shared/types/admin.ts +++ b/packages/shared/types/admin.ts @@ -85,7 +85,7 @@ export interface StaffUser { id: number; name: string; password: string; - staffRoleId: number; + staffRoleId: number | null; createdAt: Date; } diff --git a/packages/shared/types/app-common.types.ts b/packages/shared/types/app-common.types.ts new file mode 100644 index 0000000..3f1588e --- /dev/null +++ b/packages/shared/types/app-common.types.ts @@ -0,0 +1,85 @@ +/** + * App-Common Types + * Duplicated identically across apps (admin-ui / user-ui / web-ui / ui). + * Kept in @packages/shared so all apps import one source. + */ + +// --- Upload / prominent hooks (already in upload.types.ts, but also include the indexed aliases) --- +// AvailabilityEntry is `AvailabilityApiType['availability'][number]` — not shareable generically; +// apps keep that local. The truly shared hook aliases are the response type aliases: +export type ProductsResponseAlias = import('./user').UserProductDetailData +// For the literal alias trio that were copy-pasted across user-ui ↔ web-ui: +export type ProductsResponse = import('./user').UserProductDetailData // placeholder — apps re-export via AllProductsApiType + +// --- Forms / shared UI --- +export interface LoginFormInputs { + mobile: string + otp?: string + password?: string +} + +export interface ComplaintFormProps { + open: boolean + onClose: () => void + orderId: number +} + +// Duplicate across user-ui hooks ↔ web-ui hooks +export interface CartData { + items: import('./user').CartItem[] + totalAmount: number + totalItems: number +} + +// Cart icon props duplicated in user-ui ↔ ui +export interface CartIconProps { + color: string + focused: boolean + size: number +} + +// Address form props duplicated user-ui ↔ web-ui +export interface AddressFormProps { + addressLine1: string + addressLine2: string + city: string + googleMapsUrl?: string + id?: number + initialValues?: { + addressLine1: string + addressLine2: string + city: string + googleMapsUrl?: string + } + isDefault: boolean +} + +// Shared UI dropdown — duplicated ui ↔ web-components +export interface DropdownOption { + label: string + value: string | number +} + +// ImageUploaderNeo — duplicated ui ↔ web-components +export interface ImageUploaderNeoItem { + imgUrl: string + mimeType: string | null +} + +export interface ImageUploaderNeoPayload { + url: string + mimeType: string | null +} + +// Navigation redirect state — duplicated in user-ui hooks ↔ contexts +export interface RedirectState { + targetUrl: string + queryParams: Record + timestamp: number +} + +// Vendor snippet product — duplicated admin-ui components ↔ types +export interface VendorSnippetProduct { + id: number + name: string +} diff --git a/packages/shared/types/cache.types.ts b/packages/shared/types/cache.types.ts new file mode 100644 index 0000000..5720384 --- /dev/null +++ b/packages/shared/types/cache.types.ts @@ -0,0 +1,41 @@ +/** + * Cache / Store-Helper Types + * Identical in db_helper_postgres and db_helper_sqlite `src/stores/store-helpers.ts`. + * Centralized here so both helpers + backend stores import one source. + * NOTE: BannerData/ProductBasicData are intentionally NOT included here — they + * diverge between pg (productIds/flat product) and sqlite (skuIds/SKU-composed shape). + * Only the truly identical subset is shared; divergent shapes keep their local defs. + */ + +export interface ProductTagData { + productId: number + tagName: string +} + +export interface TagProductMapping { + tagId: number + productId: number +} + +export interface UserNegativityData { + userId: number + totalNegativityScore: number +} + +export interface PlacedOrderShape { + id: number + userId: number + addressId: number + slotId: number | null + totalAmount: string + deliveryCharge: string + isCod: boolean + isOnlinePayment: boolean + paymentInfoId: number | null + readableId: number + userNotes: string | null + orderGroupId: string + orderGroupProportion: string + isFlashDelivery: boolean + createdAt: Date +} diff --git a/packages/shared/types/index.ts b/packages/shared/types/index.ts index 75d647f..55fae80 100644 --- a/packages/shared/types/index.ts +++ b/packages/shared/types/index.ts @@ -1,6 +1,15 @@ // Central types export file -// Re-export all types from the types folder +// Re-export all types from the types folder. +// Note: banner/complaint/const/coupon/staff-user types are re-exported via admin.ts — +// the dedicated per-domain files are intentionally not re-exported to keep a single source. +// Domain re-exports (keep order: admin first — others may extend its types) export type * from './admin'; export type * from './user'; export type * from './store.types'; + +// Shared cross-cutting types — import from '@packages/shared' anywhere: +export type * from './upload.types'; // ContextString, UploadInput, UploadBatchInput, UploadResult +export type * from './seed.types'; // UnitSeedData, StaffRoleName, StaffPermissionName, RolePermissionAssignment, KeyValSeedData +export type * from './inputs.types'; // CreateBannerInput, UpdateBannerInput, CreateStoreInput, Login/Register requests +export type * from './cache.types'; // ProductTagData, TagProductMapping, UserNegativityData (truly identical subset) diff --git a/packages/shared/types/inputs.types.ts b/packages/shared/types/inputs.types.ts new file mode 100644 index 0000000..71bec98 --- /dev/null +++ b/packages/shared/types/inputs.types.ts @@ -0,0 +1,43 @@ +/** + * Shared Input Types + * Mutations inputs that were identically duplicated in db_helper_{postgres,sqlite}. + * Canonical: import these from @packages/shared instead of redeclaring locally. + */ + +import type { Banner } from './admin' + +// --- Banner inputs --- + +export type CreateBannerInput = Omit +export type UpdateBannerInput = Partial> + +// --- Store inputs --- + +export interface CreateStoreInput { + name: string + description?: string + imageUrl?: string + owner: number +} + +export interface UpdateStoreInput { + name?: string + description?: string + imageUrl?: string + owner?: number +} + +// --- Auth wire types (identical backend auth API ↔ user-ui) --- + +export interface LoginRequest { + identifier: string + password: string +} + +export interface RegisterRequest { + name: string + email: string + mobile: string + password: string + profileImageUrl?: string | null +} diff --git a/packages/shared/types/seed.types.ts b/packages/shared/types/seed.types.ts new file mode 100644 index 0000000..8c62eb6 --- /dev/null +++ b/packages/shared/types/seed.types.ts @@ -0,0 +1,29 @@ +/** + * Seed Types + * Shared definitions for DB seeding — identical in db_helper_postgres and db_helper_sqlite. + * These are the canonical shapes; both helpers should import from @packages/shared. + */ + +// Units seeded into `units` table +export interface UnitSeedData { + shortNotation: string + fullName: string +} + +// Staff role names — matches `staffRoles.roleName` enum in schema +export type StaffRoleName = 'super_admin' | 'admin' | 'marketer' | 'delivery_staff' + +// Staff permission names — matches `staffPermissions.permissionName` enum +export type StaffPermissionName = 'crud_product' | 'make_coupon' | 'crud_staff_users' + +// Assignment of a permission to a role +export interface RolePermissionAssignment { + roleName: StaffRoleName + permissionName: StaffPermissionName +} + +// Generic key-value seed for `keyValStore` +export interface KeyValSeedData { + key: string + value: any +} diff --git a/packages/shared/types/upload.types.ts b/packages/shared/types/upload.types.ts new file mode 100644 index 0000000..475ff5e --- /dev/null +++ b/packages/shared/types/upload.types.ts @@ -0,0 +1,30 @@ +/** + * Upload Types + * Central definitions for object-storage upload flows. + * Used by admin-ui, user-ui, web-ui hooks (useUploadToObjectStore / useUploadToObjectStorage). + * ContextString is a superset — admin-ui needs 'tags', others use the 6 common values. + */ + +export type ContextString = + | 'review' + | 'product_info' + | 'notification' + | 'store' + | 'complaint' + | 'profile' + | 'tags' + +export interface UploadInput { + blob: Blob + mimeType: string +} + +export interface UploadBatchInput { + images: UploadInput[] + contextString: ContextString +} + +export interface UploadResult { + keys: string[] + presignedUrls: string[] +}