hoangsonww / hoangsonww/MERN-Stack-Ecommerce-App
Feature: Wishlists + “Notify Me” (Restock & Price-Drop Alerts)
- Dominant language
- JavaScript
- Stars
- 126
- Forks
- 66
- PR merge metrics
- No merged PRs in 30d
Description
### Summary
Add a customer-facing **Wishlist** plus a **“Notify me”** alert system for **restock** and **price-drop** events. Shoppers can save products to a wishlist, subscribe to alerts per product/variant, and receive notifications when (a) an out-of-stock item is back or (b) the price falls below a user-selected threshold or percentage.
---
#### Motivation
* Increases engagement and return visits without requiring paid vector DB credits.
* Complements current recommendations (Weaviate/FAISS/LangChain) by keeping users in the loop for items they actually want.
* Common e-commerce expectations (wishlists + restock emails) make the demo feel production-grade.
---
#### Scope (v1)
1. **Wishlist**
* Add/remove product (and optional variant) to user wishlist.
* Wishlist page & mini-widget (header icon + count).
2. **Alerts**
* “Notify me” for **restock** (when `inventory > 0`) and **price-drop** (below target/percent).
* Single alert per user/product/variant per alert-type (idempotent).
* Delivery: email (dev: console logger; prod: pluggable provider).
3. **Backoffice hooks**
* Trigger checks when products are updated (price/inventory changes).
* Daily sweep job to re-evaluate alerts (safety net).
**Non-Goals (v1):** SMS/mobile push, complex targeting, multi-currency handling, bulk imports.
---
#### UX Notes
* Product detail page:
* If OOS → show **“Notify me when back in stock”**.
* If in stock → show **“Add to wishlist”** and a **“Price-drop alert”** dialog (target price or % drop).
* Wishlist page:
* Grid of saved items (name, price, stock badge, remove, go to product).
* If OOS item has an active subscription, show “Subscribed • Manage”.
* Toasts for add/remove/success; form validation for target price.
---
#### Data Model (MongoDB)
New collections:
```js
// wishlists
{
_id, userId, items: [
{ productId, variantId: String|null, addedAt }
],
updatedAt, createdAt
}
// alert_subscriptions
{
_id,
userId,
productId,
variantId: String|null,
type: "RESTOCK" | "PRICE_DROP",
// Exactly one of the below for price alerts
targetPrice: Number|null,
dropPercent: Number|null, // e.g., 15 => notify when price <= price_at_subscribe * 0.85
status: "ACTIVE" | "CANCELLED" | "TRIGGERED",
lastNotifiedAt: Date|null,
createdAt
}
```
Indexes:
* `wishlists`: `{ userId: 1 }`
* `alert_subscriptions`: `{ userId: 1, productId: 1, variantId: 1, type: 1, status: 1 }` (unique on `{userId, productId, variantId, type, status: ACTIVE}`)
---
#### API (Express)
**Wishlist**
```
POST /api/wishlist body: { productId, variantId? }
DELETE /api/wishlist body: { productId, variantId? }
GET /api/wishlist -> { items: [...] }
```
**Alerts**
```
POST /api/alerts/subscribe body: { productId, variantId?, type: "RESTOCK" | "PRICE_DROP", targetPrice?, dropPercent? }
POST /api/alerts/cancel body: { subscriptionId }
GET /api/alerts/mine -> { subscriptions: [...] }
```
**Admin/Hook (internal)**
```
POST /api/admin/products/:id/reindex // optional: trigger recompute after price/stock update
```
Validation rules:
* `PRICE_DROP`: exactly one of `targetPrice` or `dropPercent` must be provided.
* Prevent duplicate ACTIVE subscription by (userId, productId, variantId, type).
Update **openapi.yaml** + Swagger docs with new schemas and endpoints.
---
#### Triggering Logic
* On product update (price or inventory), run `evaluateSubscriptions(productId)`:
* **RESTOCK**: notify if `inventory > 0`.
* **PRICE\_DROP**:
* If `targetPrice`: notify if `currentPrice <= targetPrice`.
* If `dropPercent`: compute threshold from **current price at subscribe time** or store `basePrice` in doc (recommended field: `basePriceAtSubscribe`).
* Mark subscription as `TRIGGERED` and set `lastNotifiedAt`.
* Idempotency: keep a small Redis/Memory TTL key (fallback to Mongo query) `{subId}:{productPrice}:{inventory}` to avoid double-send in bursty updates.
---
#### Email Delivery (pluggable)
* **Dev**: console transport (logs JSON of “email”).
* **Prod option**: environment-driven provider (`SENDGRID_API_KEY` or `RESEND_API_KEY` or `MAILGUN_*`).
* Templated subject lines:
* Restock: `“Back in stock: {{productName}}”`
* Price drop: `“Price dropped to {{price}}: {{productName}}”`
---
#### Background Job
* Add a small scheduler:
* **Option A (simple):** `node-cron` daily at 02:00 server time → re-evaluate all ACTIVE subs for safety.
* **Option B (if Redis available):** BullMQ queue `alerts:eval` to process in batches.
---
#### Security
* All endpoints require JWT (already used in app).
* Verify user ownership on wishlist and subscriptions.
* Rate limit write endpoints (e.g., 10/min/user) to prevent abuse.
---
#### Observability
* Logs: `alerts.triggered`, `alerts.skipped_reason`, `wishlist.add/remove` with userId, productId.
* Counters: `alerts_triggered_total{type}`, `subscriptions_active_total`.
* Add basic unit tests for:
* Subscription validation.
* Threshold evaluation.
* Idempotent trigger on repeated product updates.
---
#### Acceptance Criteria
* A logged-in user can:
* Add/remove products from wishlist; wishlist persists and renders on its page.
* Subscribe to **RESTOCK** for an OOS product; when stock becomes available, an email (or console log in dev) is produced and subscription becomes `TRIGGERED`.
* Subscribe to **PRICE\_DROP** (target price **or** percent); on qualifying price change, receive notification and subscription becomes `TRIGGERED`.
* API and Swagger/OpenAPI are updated and pass validation.
* Unit tests cover rule evaluation and API validators.
* No duplicate notifications are sent for the same event/version.
* CI passes and feature is behind env flag `FEATURE_ALERTS=true`.
---
#### Tasks
**Backend**
* [ ] Mongo models for `wishlists` and `alert_subscriptions` (+ indexes).
* [ ] POST/DELETE/GET wishlist endpoints with auth + validation.
* [ ] Subscribe/cancel/list alert endpoints with validation.
* [ ] Evaluation service `evaluateSubscriptions(product)` with idempotency.
* [ ] Hook in product update path (price/stock) to call evaluator.
* [ ] Email provider abstraction + console transport (dev) + env-based provider (prod).
* [ ] `node-cron` (or BullMQ) daily sweep.
* [ ] Unit tests (Jest) for validators & evaluator.
**Frontend (React)**
* [ ] Wishlist icon in header + page `/wishlist`.
* [ ] Product detail: “Add to wishlist” / “Remove from wishlist”.
* [ ] OOS state: “Notify me when back in stock” button (requires auth).
* [ ] Price-drop dialog (target or %), client-side validation.
* [ ] Toasts and loading states.
**Docs & CI**
* [ ] Update `README.md` (feature overview + envs).
* [ ] Update `openapi.yaml` + Swagger UI.
* [ ] Add `.env.example` keys for email provider + `FEATURE_ALERTS`.
* [ ] Extend GitHub Actions to run new tests.
---
#### Open Questions
* Should `PRICE_DROP` alerts auto-rearm (stay ACTIVE) until user cancels, or single-shot (v1 uses single-shot `TRIGGERED`)?
* Variant-level stock/price: do we already store variant prices/inventory separately? If yes, require `variantId` to avoid false alerts.
* Retention: auto-clean `TRIGGERED` subscriptions older than 90 days?
---
Contributor guide
Assessment
This issue has not been assessed yet.