Fix shop product bugs: partial catalog search, premature product activation, verify badge population
- searchCatalog: use $regex partial match (like user search) instead of MongoDB $text full-word search, so typing part of a product name works - createShopProduct: new listings always start pending_shop_approval instead of auto-activating when the shop itself is already active, so incomplete wizard drafts no longer leak into the public discover feed before the owner finishes and it's approved - Populate owner.verify_badge on shop responses (getShopPublic, getShopById, getDiscoverFeed, getShopProductById, getCatalogListings) so the app can show a verified/license badge next to shop names Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -58,12 +58,13 @@ const searchCatalog = async (req, res, next) => {
|
||||
return res.json({ products: [] })
|
||||
}
|
||||
|
||||
const products = await ProductCatalogModel.find(
|
||||
{ $text: { $search: q } },
|
||||
{ score: { $meta: 'textScore' } }
|
||||
)
|
||||
.sort({ score: { $meta: 'textScore' } })
|
||||
.limit(20)
|
||||
// قبلاً $text (جستجوی متنیِ کاملِ مونگو) بود که فقط با کلمهی کامل/ریشهشده مچ میشد،
|
||||
// نه با بخشی از یه کلمه — با اینکه کاربر دقیقاً خواسته بود «قسمتی از نام کالا رو
|
||||
// وارد کنی» (عینِ جستجوی کاربر که $regex استفاده میکنه، نه $text)
|
||||
const escaped = q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
const products = await ProductCatalogModel.find({
|
||||
name: { $regex: escaped, $options: 'i' }
|
||||
}).limit(20)
|
||||
|
||||
return res.json({ products })
|
||||
} catch (error) {
|
||||
@@ -169,7 +170,12 @@ const createShopProduct = async (req, res, next) => {
|
||||
category: clonedFields.category || null,
|
||||
sub_category: clonedFields.sub_category || null,
|
||||
sub_sub_category: clonedFields.sub_sub_category || null,
|
||||
status: isCloned ? 'pending_shop_approval' : (shop.status === 'active' ? 'active' : 'pending_shop_approval')
|
||||
// قبلاً اگه خودِ فروشگاه active بود، کالای تازهساخته (همین قدمِ اول، فقط نام — بدون
|
||||
// واریانت/تصویر/دستهبندی) بلافاصله status:'active' میگرفت و توی فیدِ دیسکاور عمومی
|
||||
// نمایش داده میشد، با اینکه هنوز کاربر ویزارد رو تموم نکرده بود (کالاهای نیمهکاره/
|
||||
// آزمایشی مثل عنوانِ خالی یا فقط یه عدد). الان همیشه pending میمونه تا قدمِ آخرِ
|
||||
// ویزارد (NewProductVariantsScreen.tsx submit) صریحاً status:'active' رو ست کنه
|
||||
status: 'pending_shop_approval'
|
||||
})
|
||||
|
||||
return res.json({ message: 'کالا با موفقیت ثبت شد', listing, catalogProduct })
|
||||
@@ -312,9 +318,10 @@ const getShopProducts = async (req, res, next) => {
|
||||
|
||||
const getShopProductById = async (req, res, next) => {
|
||||
try {
|
||||
const listing = await ShopProductListingModel.findById(req.params.id).populate(
|
||||
'catalogProduct shop'
|
||||
)
|
||||
const listing = await ShopProductListingModel.findById(req.params.id).populate([
|
||||
{ path: 'catalogProduct' },
|
||||
{ path: 'shop', populate: { path: 'owner', select: 'verify_badge' } }
|
||||
])
|
||||
if (!listing) return productError(res, 404, 'کالا یافت نشد')
|
||||
return res.json({ listing })
|
||||
} catch (error) {
|
||||
@@ -337,7 +344,10 @@ const getCatalogListings = async (req, res, next) => {
|
||||
const listings = await ShopProductListingModel.find({
|
||||
catalogProduct: catalogId,
|
||||
status: 'active'
|
||||
}).populate('shop catalogProduct')
|
||||
}).populate([
|
||||
{ path: 'shop', populate: { path: 'owner', select: 'verify_badge' } },
|
||||
{ path: 'catalogProduct' }
|
||||
])
|
||||
|
||||
const visible = listings.filter((listing) => listing.shop?.status === 'active')
|
||||
visible.sort((a, b) => cheapestVariantPrice(a) - cheapestVariantPrice(b))
|
||||
@@ -449,7 +459,7 @@ const getDiscoverFeed = async (req, res, next) => {
|
||||
...validTaggedPosts.map((p) => String(p.linked_shop_product_id.shop))
|
||||
])
|
||||
]
|
||||
const shops = await ShopModel.find({ _id: { $in: shopIds } })
|
||||
const shops = await ShopModel.find({ _id: { $in: shopIds } }).populate('owner', 'verify_badge')
|
||||
const shopMap = new Map(shops.map((s) => [String(s._id), s]))
|
||||
|
||||
const catalogIds = [...new Set(docs.map((d) => String(d.catalogProduct)).filter(Boolean))]
|
||||
|
||||
@@ -176,6 +176,7 @@ const getShopById = async (req, res, next) => {
|
||||
const { shop, error } = await findOwnedShop(req.params.id, req.user._id)
|
||||
if (error === 'NOT_FOUND') return shopError(res, 404, 'فروشگاه یافت نشد')
|
||||
if (error === 'FORBIDDEN') return shopError(res, 403, 'شما مالک این فروشگاه نیستید')
|
||||
await shop.populate('owner', 'verify_badge')
|
||||
return res.json({ shop })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
@@ -202,7 +203,7 @@ const updateShopBasic = async (req, res, next) => {
|
||||
/** Public shop profile: visible to any visitor once active; owner can preview it before that. */
|
||||
const getShopPublic = async (req, res, next) => {
|
||||
try {
|
||||
const shop = await ShopModel.findById(req.params.id).populate('owner', 'user_name first_name last_name profile_image')
|
||||
const shop = await ShopModel.findById(req.params.id).populate('owner', 'user_name first_name last_name profile_image verify_badge')
|
||||
if (!shop) return shopError(res, 404, 'فروشگاه یافت نشد')
|
||||
|
||||
const currentUserId = getOptionalUserId(req)
|
||||
|
||||
Reference in New Issue
Block a user