اذهب إلى المحتوى

السؤال

نشر

لدي مشروع قمت ب بنائه ب NestJS Next.js وهو مشروع يعمل عبر Production 

لكن اريد منكم مراجعة README.md واخباري هل يوجد اي نقاط ضعف في مشروع او ثغرات او اي شي يجب تحسينه

وللعلم انا لم استخدم Redis لان لسا مشروع جديد ولم يظهر اي ضغط حتى الان 

رابط مستودع : https://github.com/ZenZN99/DineFlow

<div align="center">

# 🍽️ QR Menu

### The QR code on the table isn't a gimmick here — it's the entire auth layer.

**A production-shaped, multi-tenant restaurant ordering platform.**
Guests order without an app or account. Kitchens see it the instant it lands, over a live socket, not a refresh button. Every write path a restaurant owner touches is gated behind a subscription state that's re-checked *on every request*, not just at login.

<p>
  <img alt="NestJS" src="https://img.shields.io/badge/Backend-NestJS%2011-E0234E?logo=nestjs&logoColor=white">
  <img alt="Next.js" src="https://img.shields.io/badge/Frontend-Next.js%2016%20(App%20Router)-000000?logo=nextdotjs&logoColor=white">
  <img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-5.7-3178C6?logo=typescript&logoColor=white">
  <img alt="MongoDB" src="https://img.shields.io/badge/MongoDB-Mongoose%209%20%2B%20Transactions-47A248?logo=mongodb&logoColor=white">
  <img alt="Socket.IO" src="https://img.shields.io/badge/Realtime-Socket.IO-010101?logo=socketdotio&logoColor=white">
  <img alt="Zustand" src="https://img.shields.io/badge/State-Zustand-2F2F2F?logo=react&logoColor=61DAFB">
  <img alt="Tailwind" src="https://img.shields.io/badge/UI-TailwindCSS%20v4-06B6D4?logo=tailwindcss&logoColor=white">
  <img alt="i18n" src="https://img.shields.io/badge/i18n-AR%20%7C%20EN%20%7C%20TR-8A2BE2">
  <img alt="Tests" src="https://img.shields.io/badge/Backend%20Tests-225%2B%20passing-C21325?logo=jest&logoColor=white">
  <img alt="License" src="https://img.shields.io/badge/License-UNLICENSED-lightgrey">
</p>

</div>

---

## Why this README exists, and why it leans hard on the backend

This is the **root of a monorepo** with two independently deployable applications  `backend/` (NestJS + MongoDB) and `frontend/` (Next.js App Router). Each has its own README. This document is not a third copy of either  it's the map that shows how they form one system, written for someone who needs to understand *the whole machine* well enough to run it, extend it, or interview the person who built it.

It leans deliberately toward the backend, because that's where this system's actual hard problems live: an atomic multi-collection order transaction, a subscription gate re-evaluated on every single request, a real-time layer that has to re-implement auth outside Nest's guard pipeline because sockets don't go through HTTP guards, and a table-availability model that's a *count*, not a boolean. The frontend is a well-organized, feature-complete Next.js client consuming all of that — and it gets a full architectural treatment here too — but the interesting engineering decisions are concentrated on the API side, so that's where this document spends most of its words.

If you only read one section, read 6  The Order Lifecycle, End to End](#6-the-order-lifecycle-end-to-end). It's the single request that touches every layer of this stack at once.

---

## Table of Contents

1. [What This Actually Is](#1-what-this-actually-is)
2. [Monorepo Map](#2-monorepo-map)
3. [System Architecture](#3-system-architecture)
4. [Backend, In Depth](#4-backend-in-depth)
   - [4.1 Design Principles That Aren't Obvious From the Code Alone](#41-design-principles-that-arent-obvious-from-the-code-alone)
   - [4.2 Data Model](#42-data-model)
   - [4.3 State Machines](#43-state-machines)
   - [4.4 The Real-Time Layer](#44-the-real-time-layer)
   - [4.5 API Surface](#45-api-surface)
   - [4.6 Testing Strategy](#46-testing-strategy)
   - [4.7 Trade-offs & Known Limitations  An Honest Account](#47-trade-offs--known-limitations--an-honest-account)
5. [Frontend, In Depth](#5-frontend-in-depth)
6. [The Order Lifecycle, End to End](#6-the-order-lifecycle-end-to-end)
7. [The Frontend  Backend Contract](#7-the-frontend--backend-contract)
8. [Getting Started](#8-getting-started)
9. [Scripts Reference](#9-scripts-reference)
10. [Roadmap](#10-roadmap)
11. [License](#11-license)

---

## 1. What This Actually Is

**QR Menu** is the platform behind a restaurant's entire digital ordering loop, built as two cooperating apps:

- A **guest never installs anything or signs up**. They scan the QR code glued to their table, land on a menu scoped to that exact table, add items to a cart, and place an order. Behind the scenes that QR scan mints a short-lived, table-scoped session  no password, no account, but not anonymous either: it's a real, authorizable identity for the next two hours.
- The **restaurant owner** runs their entire operation  menu, tables, live order queue, staff  from a Next.js dashboard. The moment a guest's order lands, it appears on their screen over a live socket. As they move it through **Pending → Preparing → Ready → Completed**, the guest's own screen updates in the same channel, with no polling anywhere in the loop.
- An **admin** sits above all of it, approving new restaurants, watching subscriptions lapse and renew, and holding the one lever (`SubscriptionGuard`) that turns a restaurant's write access on and off.

Layered underneath all three of those journeys is a SaaS subscription gate that isn't just checked once at login — it's re-evaluated on *every single guarded request*, meaning a restaurant whose subscription lapses at 3:14 PM loses write access at 3:14 PM, mid-session, not the next time someone signs in.

---

## 2. Monorepo Map

```
Qr menu/
├── backend/                 NestJS API  the source of truth for everything
   ├── src/
      ├── controllers/     thin  DTO validation in, service call out
      ├── services/        all business logic + Mongoose queries
      ├── schemas/         Mongoose schema classes + indexes
      ├── dtos/             class-validator request shapes, one folder per resource
      ├── guards/          Auth  UserActive  Subscription  Roles
      ├── decorators/      @Roles(...) metadata
      ├── gateways/        OrderGateway, NotificationGateway (Socket.IO)
      ├── adapters/        AuthSocketAdapter  handshake-level JWT auth for sockets
      ├── token/           access/refresh JWT signing & verification
      ├── mailers/         password-reset OTP email delivery
      ├── crons/           nightly subscription-expiry sweep
      ├── libs/            response envelope, assertOwnerOrAdmin, cloudinary, messages
      ├── enums/           OrderStatus, SubscriptionStatus, TableStatus, UserRole, NotificationType
      └── specs/services/  225+ unit tests, mirroring src/services/
   └── README.md             the exhaustive backend write-up (sequence traces, full endpoint table)

├── frontend/                 Next.js 16 App Router client
   ├── app/
      ├── (auth)/           login, register, forgot-password  unauthenticated route group
      ├── (dashboards)/     dashboard/ (owner) and admin/  authenticated route groups
      ├── (pages)/          menu/[qrToken], restaurant/[id], profile, notifications, marketing pages
      ├── apis/             one fetch-wrapper module per backend resource
      ├── hooks/            one data hook per resource, + hooks/customs (useLanguage, useFormErrors)
      ├── stores/            useAuthStore, useNotificationStore (Zustand)
      ├── socket/            singleton Socket.IO client, lifecycle tied to auth state
      ├── providers/         LanguageProvider (i18n context)
      ├── locales/            ar.ts / en.ts / tr.ts  full trilingual UI copy
      ├── routes/             shared guard/fallback screens (e.g. AdminOnly)
      └── components/         landing sections, navbar, footer, shared UI primitives
   └── README.md               (stock create-next-app doc  this root README supersedes it)

└── .git/
```

**Rule of thumb for navigating the code:** if it's a decision about *what's allowed to happen*, it lives in `backend/src/guards` or `backend/src/services`. If it's a decision about *what it looks like or how it's phrased*, it lives in `frontend/app`. The frontend does not re-implement authorization logic anywhere  it reacts to `401`/`403` responses and socket events, nothing more.

---

## 3. System Architecture

```mermaid
graph TB
    subgraph Clients
        Guest["📱 Guest Device\nno account — QR scan only"]
        Owner["💻 Restaurant Dashboard\n(dashboards)/dashboard"]
        AdminUI["🛡️ Admin Console\n(dashboards)/admin"]
    end

    subgraph FE["Next.js 16 — App Router"]
        direction TB
        RouteGroups["(auth) · (dashboards) · (pages)"]
        ApiLayer["app/apis — per-resource fetch wrappers\ncredentials: 'include' on every call"]
        Hooks["app/hooks — data + mutation hooks"]
        Stores["Zustand: useAuthStore / useNotificationStore"]
        SocketClient["app/socket — singleton, connects only when authenticated"]
        I18n["LanguageProvider + ar/en/tr locales"]
    end

    subgraph BE["NestJS API"]
        direction TB
        CORS["CORS(credentials:true) + cookie-parser"]
        VPipe["Global ValidationPipe\n(whitelist + single-error-message factory)"]
        Guards["Guard Pipeline\nAuth → UserActive → Subscription → Roles"]
        Interceptor["ResponseInterceptor → { success, message, data }"]
        Controllers["Auth · Restaurant · Category/Product · Table\nOrder/OrderItem · Subscription · GuestSession · User"]
        RealTime["AuthSocketAdapter → OrderGateway + NotificationGateway"]
        Cron["SubscriptionCronService · nightly expiry sweep"]
    end

    subgraph Data["Persistence"]
        Mongo[("MongoDB\nMongoose · multi-document transactions")]
    end

    subgraph External
        Cloudinary["☁️ Cloudinary — logo/cover/product images"]
        SMTP["✉️ SMTP — password-reset OTP codes"]
    end

    Guest -->|HTTPS + WS| RouteGroups
    Owner -->|HTTPS + WS| RouteGroups
    AdminUI -->|HTTPS| RouteGroups
    RouteGroups --> ApiLayer --> CORS
    RouteGroups --> SocketClient
    ApiLayer <--> Hooks
    Hooks <--> Stores
    CORS --> VPipe --> Guards --> Controllers --> Interceptor
    Controllers <--> Mongo
    Controllers --> Cloudinary
    Controllers --> SMTP
    Controllers -.triggers.-> RealTime
    SocketClient <-.socket.io.-> RealTime
    Cron --> Mongo
    Cron -.notifies.-> RealTime
```

**Read this diagram as two arrows, not one system.** Every write from either dashboard or the guest device goes `RouteGroups  ApiLayer  CORS  Guards  Controllers  Mongo`. Every live update flows the opposite direction, outside that entire HTTP pipeline, through `RealTime  SocketClient`. The frontend never polls for order status  it either has an active socket subscription or it doesn't, and the fallback for "socket not connected yet" is a normal authenticated `GET`, not a timer.

---

## 4. Backend, In Depth

### 4.1 Design Principles That Aren't Obvious From the Code Alone

**Ownership is enforced in one function, not re-derived per query.** Rather than scoping every Mongoose query by `restaurantId` (trivially easy to forget on one query and leak cross-tenant data), sensitive mutations fetch the resource first, then call a single shared assertion:

```ts
assertOwnerOrAdmin({
  ownerId: restaurant.userId.toString(),
  authUser,
  message: messages.order.forbidden,
});
```

It costs one extra document read per mutation. In exchange, "can this user touch this row?" exists in exactly one function, unit-testable in isolation, instead of being silently re-implemented (and eventually drifting) across a dozen controllers.

**The guard pipeline runs cheapest-check-first, on purpose:**

```
AuthGuard          UserActiveGuard        SubscriptionGuard       RolesGuard
(verify JWT,        (1 DB read:            (1 DB read:              (0 DB reads 
 no DB hit)           is user blocked?)      is subscription live?)   decorator reflection)
```

A forged or expired JWT dies before a single database round trip. A blocked user is rejected before the heavier subscription lookup runs. Roles are checked last because they're free  and only matter once the requester is already a legitimate, active, subscribed actor.

**Subscription state is re-checked on every request, not cached at login.** `SubscriptionGuard` hits the `Restaurant` collection on every guarded request except for admins, who bypass it entirely. A restaurant whose subscription lapses at 3:14 PM loses write access at 3:14 PM  mid-session  because a billing gate that only matters at sign-in isn't really a gate.

**One response envelope, enforced globally, never for errors.** Every controller returns `{ data, message }`; a global `ResponseInterceptor` wraps that into `{ success: true, message, data }` for every success response in the API. Errors are deliberately *not* reshaped by the interceptor  they're re-thrown through Nest's standard exception filters, so HTTP status codes stay meaningful and the frontend can branch on status code rather than parsing a message string.

**Guests are first-class actors without being user accounts.** A `GuestSession` isn't a stripped-down `User` — it's its own schema with its own 2-hour TTL, tied to exactly one table. It's the identity an order is placed under, the room a socket subscribes to for status updates, and the thing invalidated when the table's tab closes. This keeps the guest experience genuinely account-free while still giving the backend something concrete to authorize against.

**Placing an order is one atomic unit across four collections, not four separate writes.** `Order`, `OrderItem`, `Table`, and `Notification` are all touched inside a single Mongo session (`connection.startSession()` + `withTransaction`). If a product goes unavailable mid-write, the entire order rolls back rather than leaving a half-priced, half-populated order sitting in the database. The full trace is in 6](#6-the-order-lifecycle-end-to-end).

**Price is never trusted from the client.** `unitPrice` is resolved server-side from the product's *current* `discountPrice ?? price` at the moment each item is added to the order — a stale cached price or a tampered request body can't leak through.

### 4.2 Data Model

```mermaid
erDiagram
    USER ||--o{ RESTAURANT : owns
    RESTAURANT ||--|| SUBSCRIPTION : has
    RESTAURANT ||--o{ CATEGORY : defines
    RESTAURANT ||--o{ TABLE : has
    CATEGORY ||--o{ PRODUCT : contains
    RESTAURANT ||--o{ GUEST_SESSION : hosts
    TABLE ||--o{ GUEST_SESSION : "scoped to"
    GUEST_SESSION ||--o{ ORDER : places
    TABLE ||--o{ ORDER : "occupies"
    ORDER ||--|{ ORDER_ITEM : contains
    PRODUCT ||--o{ ORDER_ITEM : "priced from"
    USER ||--o{ NOTIFICATION : receives
    GUEST_SESSION ||--o{ NOTIFICATION : receives

    USER {
        ObjectId restaurantId
        string email
        string passwordHash
        enum role "OWNER | MANAGER | ADMIN"
        boolean isActive
    }
    RESTAURANT {
        ObjectId userId
        string name
        object logo
        object cover
        enum subscriptionStatus
    }
    SUBSCRIPTION {
        ObjectId restaurantId
        enum status "PENDING | ACTIVE | EXPIRED"
        date expiresAt
    }
    CATEGORY {
        ObjectId restaurantId
        string name
        number sortOrder
    }
    PRODUCT {
        ObjectId categoryId
        ObjectId restaurantId
        number price
        number discountPrice
        boolean isAvailable
    }
    TABLE {
        ObjectId restaurantId
        string qrToken "unique, unguessable"
        enum status "AVAILABLE | OCCUPIED"
    }
    GUEST_SESSION {
        ObjectId restaurantId
        ObjectId tableId
        string sessionToken
        boolean isActive
        date expiresAt "2h TTL"
    }
    ORDER {
        ObjectId restaurantId
        ObjectId tableId
        ObjectId guestSessionId
        enum status "PENDING|PREPARING|READY|COMPLETED"
        number totalPrice
        boolean isPaid
    }
    ORDER_ITEM {
        ObjectId orderId
        ObjectId productId
        number quantity
        number unitPrice
    }
    NOTIFICATION {
        ObjectId userId
        ObjectId guestSessionId
        enum type
        boolean isRead
    }
```

The catalog today is a real two-level hierarchy  **Category  Product**, with server-resolved pricing/discounts and an `isAvailable` flag the order-creation path actually checks. A further **Product  Variant  Addon** layer (size options, extras) is on the roadmap but isn't in the current schema  worth knowing before you go looking for it.

### 4.3 State Machines

**Order**  a strict one-way graph, enforced with an explicit guard clause before every transition (`order.status !== PENDING` throws before it ever reaches `PREPARING`):

```mermaid
stateDiagram-v2
    [*] --> PENDING: guest places order
    PENDING --> PREPARING: owner accepts
    PREPARING --> READY: kitchen marks ready
    READY --> COMPLETED: owner completes + isPaid=true
    COMPLETED --> [*]: close-session
```

**Table**  availability is a *count* of non-terminal orders, not a flag flipped on a single order's completion. A table of six ordering in two separate rounds stays `OCCUPIED` until *zero* active orders remain against it:

```mermaid
stateDiagram-v2
    AVAILABLE --> OCCUPIED: first order placed
    OCCUPIED --> OCCUPIED: another order placed at same table
    OCCUPIED --> AVAILABLE: last active order reaches COMPLETED
```

**Subscription**  driven both by admin action and a nightly cron sweep:

```mermaid
stateDiagram-v2
    [*] --> PENDING: restaurant registers
    PENDING --> ACTIVE: admin approves
    ACTIVE --> EXPIRED: cron sweep finds expiresAt passed
    EXPIRED --> ACTIVE: admin re-approves
```

### 4.4 The Real-Time Layer

The subtlety worth calling out explicitly: **WebSocket connections never pass through Nest's HTTP guard pipeline.** `AuthGuard`, `UserActiveGuard`, etc. are HTTP-request constructs — a socket handshake is a different transport entirely. This is solved with a custom `IoAdapter` (`AuthSocketAdapter`) that intercepts every incoming socket connection *before* it's accepted: it reads the same `token` cookie the HTTP layer uses (falling back to an `Authorization: Bearer` header for non-browser clients), verifies it with the exact same `TokenService.verifyAccessToken`, and rejects the handshake outright on failure  the socket never even opens. One auth implementation, trusted by two transports, instead of duplicated logic that drifts apart over time.

| Gateway | Audience | Presence registry | Emits |
|---|---|---|---|
| `OrderGateway` | Restaurant dashboards *and* guest devices | In-memory, keyed by `userId` / `guestSessionId` | `new-order`  staff · `order-status-updated`  the specific guest session |
| `NotificationGateway` | Authenticated users only | In-memory, supports multiple concurrent sockets per user (multi-tab/device) | `receive-notification` |

### 4.5 API Surface

| Domain | Key endpoints | Guards | Notes |
|---|---|---|---|
| Auth | `register` · `login` · `logout` · `me` · `forgot/verify/reset-password` · `refresh` | mostly public / self | 5 failed logins  24h lockout · refresh tokens hashed at rest · OTP emailed & bcrypt-hashed, 10-min expiry |
| Guest Sessions | `POST /guest-sessions/:qrToken` | public | The entire guest onboarding step  idempotent per table within its 2h window |
| Restaurants | full CRUD | Auth, UserActive, Roles | Multipart logo + cover upload via Cloudinary |
| Menu (Category/Product) | full CRUD | Auth, UserActive, **Subscription**, Roles | A lapsed subscription blocks menu edits, not just new orders |
| Tables | full CRUD | Auth, UserActive, Subscription, Roles | `qrToken` is the unguessable key the guest flow hinges on |
| Guest Menu | `GET /guest-menu/:sessionToken` | session-token scoped | The guest-facing read path  no JWT required |
| Orders | `POST /orders` (session-token) · `accept/ready/complete/close-session` (Roles: Manager) · `me` · `admin/:restaurantId` | mixed  see table | Each transition enforces its own precondition on current status |
| Subscriptions | `active/expired/pending` · `POST /subscriptions/:restaurantId` | Roles: Admin | Approves a pending or expired restaurant |
| Notifications | full CRUD | Auth, UserActive | Scoped to the requesting user |
| Users | list/get/delete (block) · update profile | Auth, UserActive, Roles | Delete = block, not a hard delete |

Full request/response shapes and validation rules are generated automatically at **`GET /api/docs`** (Swagger). This table is the map; Swagger  and the exhaustive endpoint-by-endpoint breakdown in `backend/README.md`  is the territory.

### 4.6 Testing Strategy

The suite is **unit-first and mock-heavy by design**: every service test mocks its Mongoose models and collaborating services, so business logic (state transitions, ownership checks, price resolution) is asserted without a live MongoDB in CI.

```
backend/src/specs/services/     14 spec files · 225+ test cases
├── order.service.spec.ts              the transaction-heavy path, most heavily tested
├── order-item.service.spec.ts         price-resolution logic in isolation
├── subscription.service.spec.ts       state-machine transitions + cron sweep
├── guest-session.service.spec.ts      idempotent-session logic
└── ...one file per service
```

Controller specs mirror this one level up, asserting each controller is a *thin* delegator  correct arguments passed through, correct value returned, nothing more  because controllers here carry no business logic by design. What's deliberately **not** covered by mocks (and would need a real integration/e2e pass to be trustworthy): actual Mongo transaction rollback behavior, real index usage under load, and the socket handshake rejecting a bad cookie against a live server.

The **frontend currently ships without an automated test suite**  its correctness net today is TypeScript's type checking plus manual QA across the three route groups. That's the honest state of things, not an oversight to gloss over.

### 4.7 Trade-offs & Known Limitations  An Honest Account

- **In-memory socket presence registries don't survive a restart and don't scale past one Node process.** Both gateways keep "who's connected" in plain JS memory. Behind a load balancer with more than one instance, a guest connected to instance A never receives an event emitted from instance B. This is the actual driver behind the Redis work in 10](#10-roadmap)  not a nice-to-have, but the fix for a real horizontal-scaling ceiling.
- **The transaction wraps a socket emit.** `mongoSession.withTransaction()` retries its callback on certain transient Mongo errors. If a retry happens *after* `sendNewOrder(...)` already fired once, the kitchen could theoretically see a duplicate `new-order` event for one successful order. Rare under normal load, but a known sharp edge.
- **Cookie flags (`secure`, `sameSite`) are hardcoded for local development** rather than derived from `NODE_ENV`  called out explicitly in the source as a pending environment-based switch before a production deploy.
- **`Variant`/`Addon` are not in the current schema**, despite being part of the original product vision  the catalog today is a real Category  Product hierarchy, not a flat list, but it stops one level short of size/extras options.
- **`assertOwnerOrAdmin` costs an extra document read per protected mutation**  a conscious trade for a single, auditable source of truth over the marginally cheaper alternative of scoping every query by `restaurantId` up front.

---

## 5. Frontend, In Depth

The frontend is a **Next.js 16 App Router** client organized around three route groups that map directly onto the three actors above:

- **`app/(auth)/`**  `login`, `register`, `forgot-password`. No layout chrome, purely functional.
- **`app/(dashboards)/`**  `dashboard/` (restaurant owner: home, categories, products, tables, orders, restaurant settings, subscription status, support) and `admin/` (approve/reject restaurants, subscription lists, user blocking).
- **`app/(pages)/`**  everything else: the marketing site (`about`, `features`, `faqs`, `contact`), account pages (`profile`, `notifications`, `user/[id]`), restaurant management (`create-restaurant`, `edit-restaurant/[id]`, `restaurant/[id]`), and  the one page a guest ever sees  **`menu/[qrToken]`**.

**Data access is a two-layer split, mirroring the backend's controller/service split:** `app/apis/*.ts` holds one thin fetch-wrapper module per backend resource (`auth.ts`, `order.ts`, `table.ts`, `guest-menu.ts`, …), all funneled through a single `request()` helper that sets `credentials: 'include'` on every call and transparently retries once through `/api/auth/refresh` on a `401`. `app/hooks/*.ts` sits one layer up, giving components resource-shaped hooks (`useOrder`, `useTable`, `useGuestSession`, …) instead of raw fetch calls.

**State that needs to survive across routes lives in two Zustand stores** — `useAuthStore` (the current user, read by the socket client to decide whether to connect at all) and `useNotificationStore` (the live notification feed, written to both by REST fetches and by the `receive-notification` socket event landing in the same store).

**The socket client is a lazily-created singleton** (`app/socket/socket.ts`) that refuses to connect if `useAuthStore` has no user, and is explicitly torn down on logout — so an unauthenticated tab never even attempts the handshake the backend's `AuthSocketAdapter` would reject anyway.

**i18n is a first-class concern, not an afterthought:** `app/locales/{ar,en,tr}.ts` hold complete UI copy trees, served through `LanguageProvider` and consumed via the `useLanguage()` hook everywhere from dashboard tables to the guest menu. Arabic, English, and Turkish are treated as equally primary, not a base language with two partial translations bolted on.

**On the guest side, `menu/[qrToken]/` is its own small, self-contained app**: `Header`, `Categories`, `Search`, `Products`, a `ProductDetailsModal`, a `CartDrawer`, and explicit states for `Loading`, `Empty`, `TableNotAvailable`, `ConfirmedOrder`, and `SuccessOrder` — the full set of edge cases a real guest hits between scanning a code and watching their order move to `READY`.

**UI toolkit:** Tailwind CSS v4 for styling, Framer Motion for the marketing-page and cart-drawer motion, `lucide-react` / `react-icons` for iconography, Swiper for carousels, `react-hot-toast` for the toast layer the backend's structured error messages feed straight into, and both `qrcode` and `react-qr-code` for generating the actual per-table codes the whole system hinges on.

---

## 6. The Order Lifecycle, End to End

This is the one request worth understanding completely — it's the only path in the system that touches the guest device, both frontend layers, the full backend guard/transaction stack, and both socket gateways in a single user action.

```mermaid
sequenceDiagram
    autonumber
    actor Guest
    participant FE as Next.js (menu/[qrToken])
    participant API as NestJS API
    participant DB as MongoDB (transaction)
    participant GW as OrderGateway
    participant Owner as Dashboard (Next.js)

    Guest->>FE: Scans table QR code
    FE->>API: POST /api/guest-sessions/:qrToken
    API->>DB: find Table by qrToken (must be active)
    API->>DB: find Restaurant, check isActive + subscriptionStatus
    alt no active GuestSession for this table
        API->>DB: create GuestSession (2h TTL)
    else session already exists
        API->>API: reuse existing sessionToken (idempotent scan)
    end
    API-->>FE: { sessionToken }
    FE->>FE: store token, render menu (Categories/Products)

    Guest->>FE: builds cart in CartDrawer, taps "Place order"
    FE->>API: POST /api/orders  (header: x-session-token)
    API->>DB: begin transaction
    API->>DB: validate GuestSession is active & not expired
    API->>DB: create Order (status=PENDING, totalPrice=0)
    API->>DB: flip Table AVAILABLE → OCCUPIED
    loop for each cart line
        API->>DB: fetch Product, verify isAvailable
        API->>API: unitPrice = product.discountPrice ?? product.price
        API->>DB: create OrderItem, increment Order.totalPrice
    end
    API->>DB: create Notification (NEW_ORDER, target=owner)
    API->>DB: commit transaction
    API->>GW: sendNewOrder(restaurantId, order)
    GW-->>Owner: emit "new-order"
    API-->>FE: { success, data: order }
    FE-->>Guest: ConfirmedOrder screen

    Note over Owner: Owner taps "Accept" in the live queue
    Owner->>API: PATCH /api/orders/:id/accept
    API->>DB: guard — status must be PENDING → PREPARING
    API->>GW: sendOrderStatus(guestSessionId, PREPARING)
    GW-->>FE: emit "order-status-updated"
    FE-->>Guest: tracker updates, no refresh

    Note over Owner: repeats through Ready, then Completed
    Owner->>API: PATCH /api/orders/:id/complete
    API->>DB: status → COMPLETED, isPaid=true
    API->>DB: count remaining active orders for this table
    alt zero active orders remain
        API->>DB: flip Table back to AVAILABLE
    end
    Owner->>API: PATCH /api/orders/:id/close-session
    API->>DB: GuestSession.isActive = false
```

Two details worth internalizing from this trace: **the guest never sends a price**, and **the table's status is never a single order's problem to flip** — it's a live count evaluated fresh every time an order reaches a terminal state.

---

## 7. The Frontend ↔ Backend Contract

The backend is built expecting exactly this frontend, and the contract has three distinct shapes depending on who's calling:

| Actor | Identity carrier | How the frontend sends it |
|---|---|---|
| Restaurant owner / Admin | JWT access + refresh, `httpOnly` cookies | `credentials: 'include'` on every `fetch` — never touched by JS, never stored in `localStorage` |
| Guest | `sessionToken` from `POST /guest-sessions/:qrToken` | Sent as an `x-session-token` header, held in memory/`sessionStorage` on the guest device only |
| Socket clients (both) | Same `token` cookie, re-verified at handshake by `AuthSocketAdapter` | `io(url, { withCredentials: true })` |

Every successful REST response — regardless of actor — arrives as `{ success: true, message, data }`, which is exactly what `app/apis/request.ts` unwraps before handing `data` to a hook. A `401` anywhere except `/api/auth/refresh` itself triggers exactly one silent refresh attempt before the frontend gives up and routes to `(auth)/login`.

---

## 8. Getting Started

**Prerequisites:** Node.js 20+, a MongoDB connection string (Atlas or local), a Cloudinary account, an SMTP-capable mail account.

Run both apps from two terminals — this is two separate `npm install`s, two separate dev servers, on two separate ports.

```bash
# Terminal 1 — backend (default port 9090)
cd backend
npm install
cp .env.example .env      # fill in the values in the table below
npm run start:dev

# Terminal 2 — frontend (default port 3000)
cd frontend
npm install
npm run dev
```

**Backend environment (`backend/.env`):**

| Variable | Used by |
|---|---|
| `PORT` | `main.ts` — server listen port (defaults to `9090`) |
| `MONGO_URI` | Mongoose connection |
| `JWT_SECRET` / `JWT_REFRESH_SECRET` | Access (15m) / refresh (30d) token signing |
| `CLOUDINARY_CLOUD_NAME` / `CLOUDINARY_API_KEY` / `CLOUDINARY_API_SECRET` | Restaurant & product image uploads |
| `MAIL_HOST` / `MAIL_PORT` / `MAIL_USER` / `MAIL_PASS` / `MAIL_FROM` | Password-reset OTP emails |

**Frontend:** points at the backend via `BACKEND_URL` in `app/apis/request.ts` (defaults to `http://localhost:9090`) — update it there or promote it to a `NEXT_PUBLIC_API_URL` env var before deploying anywhere the two apps aren't both on `localhost`.

Once the backend is running:
- **Swagger docs:** `GET /api/docs`
- **Health check:** `GET /api/health`
- **Human-readable status page:** `GET /`

Once both are running, open `http://localhost:3000`, register an owner account, create a restaurant, generate a table QR from the dashboard, then open `http://localhost:3000/menu/:qrToken` in a second tab or on a phone to play the guest side live against your own dashboard.

---

## 9. Scripts Reference

| Location | Command | Does |
|---|---|---|
| `backend/` | `npm run start:dev` | Watch-mode dev server |
| `backend/` | `npm run build` / `start:prod` | Compile to `dist/` / run the compiled build |
| `backend/` | `npm run seed` | Seed the database via `src/seed.ts` |
| `backend/` | `npm run test` / `test:watch` / `test:cov` | Unit tests |
| `backend/` | `npm run test:e2e` | Supertest-driven HTTP-level tests |
| `backend/` | `npm run lint` / `format` | ESLint (autofix) / Prettier |
| `frontend/` | `npm run dev` | Next.js dev server |
| `frontend/` | `npm run build` / `start` | Production build / serve it |
| `frontend/` | `npm run lint` | ESLint |

---

## 10. Roadmap

Redis is the next infrastructure addition, and it lands in three specific places for three specific reasons — not "add Redis and cache everything":

1. **Read-through cache for the menu catalog** — category/product reads are the highest-volume, lowest-change-frequency queries in the system. `GET`-then-`SETEX`, keyed by `restaurantId`, invalidated explicitly on the corresponding `update`/`destroy` calls rather than a blind TTL, so a price change is never served stale.
2. **A Redis-backed Socket.IO adapter (`@socket.io/redis-adapter`)**, replacing the in-memory presence registries described in [§4.7](#47-trade-offs--known-limitations--an-honest-account) — the actual fix for running more than one Node process behind a load balancer without a guest missing a status update.
3. **A shared store for guest-session lookups** — `getActiveSession(sessionToken)` currently round-trips Mongo on every order-related guest request. Given a 2-hour TTL, it's an ideal Redis key with a matching expiry, removing a Mongo hit from the hottest guest-facing path in the system.

Beyond Redis, roughly in priority order: **`Product → Variant → Addon`** to complete the originally-scoped catalog depth, a real payment gateway integration (`isPaid` is currently a manual flag flipped by staff), rate limiting on the auth and guest-session endpoints, structured audit logging on admin actions, environment-derived cookie flags for production, and an automated frontend test suite to match the backend's.

---

## 11. License

UNLICENSED — private/proprietary. All rights reserved unless a license file states otherwise.

<div align="center">

*Built for a service industry that runs on tickets, tables, and timing — the stack just makes sure none of it gets lost between the QR code and the kitchen.*

</div>

 

Recommended Posts

  • 0
نشر

مشروعك ممتاز جدا ومنظم بشكل احترافي، وملف README يوضح انك تدرك تفاصيل النظام بشكل عميق وهذا شيء رائع. بخصوص التحسينات، انصحك باضافة Rate Limiting في NestJS باستخدام Throttler لحماية مسارات الدخول وجلسات الضيوف من هجمات التخمين، يمكنك البدء بتثبيت الحزمة عبر الامر التالي.
npm i --save @nestjs/throttler
ايضا تاكد من تفعيل Indexing في MongoDB على الحقول الحساسة مثل qrToken و sessionToken لضمان سرعة الاستجابة مع زيادة حجم البيانات. بالنسبة للواجهة الامامية، يفضل اضافة اختبارات E2E باستخدام Playwright لضمان عمل دورة حياة الطلب بشكل سليم دائما خاصة انها تفتقد للاختبارات حاليا. فكرة تاجيل Redis حاليا منطقية ما دام الضغط منخفضا، لكن استمر في خطتك لاستخدامه مع Socket.IO لاحقا لضمان القدرة على التوسع. اخيرا، تاكد من جعل اعدادات الكوكيز مثل Secure و SameSite تعتمد على NODE_ENV بدلا من القيم الثابتة لزيادة الامان في Production.

  • 0
نشر

بالنسبة لملف readme لا يوجد Quick Start في الأعلى أي مطور جديد يفتح المشروع يريد أن يشغله في أقل من 3 دقائق، ولديك يجب عليه قراءة 400 سطر قبل الوصول لذلك.

والأفضل تقسيم الملف إلى عدة ملفات كالتالي:

قسم المحتوى إلى:

  • README.md  الملف الرئيسي به الخارطة العامة و Quick Start.
  • ARCHITECTURE.md به لتفاصيل المعمارية من الـ README الحالي.
  • SECURITY.md به قرارات الأمان.
  • DEVELOPMENT.md به  كيفية التشغيل والـ testing.

بالنسبة للمشروع عليك بالتالي:

  • أضف Helmet.js في main.ts لحماية التطبيق من Clickjacking و XSS وهجمات أخرى.
  • ثبّت @nestjs/throttler لحماية /login و /guest-sessions من هجمات Brute Force و DoS.
  • أصلح Race Condition في Table Status باستخدام findOneAndUpdate مع شرط status: AVAILABLE في الـ query.
  • نقّل Socket.IO Auth ليكون دورياً وليس فقط عند handshake لفصل الاتصالات عند انتهاء صلاحية التوكن.
  • استخرج secure و sameSite من NODE_ENV بدل تثبيتها على false، وإلا ستُسرب التوكنات عبر HTTP.
  • أضف Idempotency Key لطلبات إنشاء الطلبات لمنع Double Orders عند الضغط المتكرر.
  • حسن getActiveSession() بإضافة expiresAt: { $gt: new Date() } في الـ query مباشرة بدل التحقق بعد الجلب.
  • أضف Global Exception Filter لمنع تسريب معلومات حساسة من stack traces في الإنتاج.
  • حل مشكلة N+1 Queries في findAll() باستخدام $lookup aggregation بدلاً من populate + map منفصل.
  • أضف Redis  إذا أمكن للتخزين المؤقت للـ Guest Sessions والـ Menu، ولتفعيل horizontal scaling عبر Socket.IO adapter.

انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أجب على هذا السؤال...

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.

  • إعلانات

  • تابعنا على



×
×
  • أضف...