hoangsonww / hoangsonww/MERN-Stack-Ecommerce-App

Feature: Wishlists + “Notify Me” (Restock & Price-Drop Alerts)

Open
#21 0 comments 0 reactions 1 assignee Claimed by @hoangsonww View on GitHub
dependencies documentation enhancement good first issue help wanted
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.