# DailyMeals

A full-stack meal-plan web application. Browse recipes by meal category, view full
recipes with macros, ingredients and preparation steps, and export a selection of
recipes to a PDF that includes an aggregated shopping list.

- **Frontend:** React 18 + TypeScript, Vite, React Router v6, Tailwind CSS v3,
  TanStack Query v5, React Hook Form + Zod, jsPDF + html2canvas, Lucide icons, Axios.
- **Backend:** ASP.NET Core 8 Web API, EF Core 8 (Npgsql), JWT auth with refresh-token
  rotation, rate limiting, Serilog, full security hardening.
- **Database:** an **existing, externally hosted PostgreSQL 16** database. The app only
  connects to it — it never creates, seeds, or migrates the schema.
- **Infrastructure:** Docker Compose runs the API and the frontend only (no database
  container). nginx serves the SPA, terminates TLS, and reverse-proxies `/api`.

---

## Architecture

```
Browser ──HTTPS──> nginx (frontend container) ──> static SPA
                         │
                         └── /api ──HTTP──> ASP.NET Core API (api container) ──> external PostgreSQL
```

- nginx redirects HTTP → HTTPS, sets security headers, gzips responses and proxies
  `/api` to the API container.
- The API validates JWTs, enforces login rate limiting, and talks to PostgreSQL via
  EF Core (parameterized queries only).

---

## Prerequisites

- [Docker](https://docs.docker.com/get-docker/) and Docker Compose
- Network access from the API container to your external PostgreSQL server
- For local development without Docker: [.NET 8 SDK](https://dotnet.microsoft.com/) and
  [Node.js 20+](https://nodejs.org/)

---

## Production deployment (Linux server)

See **[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)** for a full guide (Docker, firewall, PostgreSQL access, Let’s Encrypt HTTPS).

Quick version:

```bash
cp .env.example .env && nano .env
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
```

---

## Quick start (Docker)

1. **Copy the environment template and fill in your values:**

   ```bash
   cp .env.example .env
   # then edit .env
   ```

   Required variables (see `.env.example` for descriptions):

   | Variable               | Description                                                        |
   | ---------------------- | ------------------------------------------------------------------ |
   | `DB_CONNECTION_STRING` | Npgsql connection string to the existing PostgreSQL database       |
   | `JWT_SECRET`           | Signing key for JWTs (≥ 32 chars; `openssl rand -base64 48`)       |
   | `JWT_ISSUER`           | JWT issuer identifier (e.g. `DailyMeals`)                          |
   | `JWT_AUDIENCE`         | JWT audience identifier (e.g. `DailyMeals`)                        |
   | `CORS_ALLOWED_ORIGIN`  | Public origin of the frontend (e.g. `https://localhost`)           |

2. **Start the stack:**

   ```bash
   docker compose up -d --build
   ```

3. **Open the application:**

   - <http://localhost> — automatically redirects to **<https://localhost>**

   > The frontend container ships with a **self-signed** TLS certificate so HTTPS works
   > out of the box, so your browser will warn about the certificate on first visit.
   > For production, replace it with a real certificate (see below).

Sign in with a user that already exists in the `AspNetUsers` table of your database.
Passwords are verified with ASP.NET Core Identity's password hasher, so the app is
compatible with hashes produced by ASP.NET Core Identity.

---

## JetBrains Rider

1. Open **`DailyMeals.sln`** in Rider (open the **solution root**, not the `meal-plan-frontend` folder alone).
2. Copy local API settings (connection string is not committed):

   ```bash
   cp MealPlan.Api/appsettings.Local.json.example MealPlan.Api/appsettings.Local.json
   # Edit appsettings.Local.json with your PostgreSQL connection string
   ```

3. Install Node + frontend dependencies (no Homebrew `npm` required):

   ```bash
   cd DailyMeals          # repository root (where DailyMeals.sln lives)
   ./scripts/install-node.sh
   ```

   **Angular frontend (recommended rewrite):**

   ```bash
   cd meal-plan-frontend-angular
   npm install
   npm start          # http://localhost:4200, proxies /api → localhost:5000
   ```

   **Or React frontend:**

   ```bash
   cd meal-plan-frontend
   ../scripts/npm install
   ../scripts/npm run dev   # http://localhost:5173
   ```

4. In Rider, open the run configuration dropdown (top toolbar). You should see:
   - **DailyMeals (API + Frontend)** — API (`http://localhost:5000`) + Vite (`http://localhost:5173`) (default).
   - **MealPlan.Api (http)** — API only (Swagger at `/swagger`).
   - **meal-plan-frontend (vite dev)** — frontend only.

   If configs are missing: **File → Reload All from Disk**, or close Rider and reopen `DailyMeals.sln`.

5. Node is provided by **`.toolchain/`** (same approach as FA_WEB). Rider run configs point at that interpreter automatically.

---

## Local development (without Docker)

**Backend:**

```bash
cd MealPlan.Api
export ConnectionStrings__DefaultConnection="Host=...;Port=5432;Database=...;Username=...;Password=..."
export Jwt__Secret="dev-secret-at-least-32-characters-long-xxxxx"
dotnet run
# API on http://localhost:5000 (Swagger UI at /swagger in Development)
```

**Frontend:**

```bash
cd meal-plan-frontend
npm install
npm run dev
# App on http://localhost:5173 — /api is proxied to http://localhost:5000
```

---

## API reference

All `/api/recipes` endpoints require a valid `Authorization: Bearer <accessToken>` header.

| Method | Endpoint                  | Description                                            |
| ------ | ------------------------- | ----------------------------------------------------- |
| POST   | `/api/auth/register`      | Create account → access + refresh token (rate-limited) |
| POST   | `/api/auth/login`         | Login → access + refresh token (rate-limited 5/min/IP)|
| POST   | `/api/auth/refresh`       | Rotate refresh token → new token pair                 |
| POST   | `/api/auth/logout`        | Revoke a refresh token                                |
| GET    | `/api/auth/me`            | Current user info                                     |
| GET    | `/api/recipes`            | List recipes — `?category=`, `?search=`, `?ingredient=` |
| GET    | `/api/recipes/{id}`       | Full recipe detail (ingredients + steps)              |
| GET    | `/api/recipes/categories` | Categories with active recipe counts                  |
| POST   | `/api/recipes`            | Create a recipe (ingredients + steps)                 |
| PUT    | `/api/recipes/{id}`       | Update a recipe                                     |
| GET    | `/api/recipes/import/template` | Download Excel import template (.xlsx)           |
| POST   | `/api/recipes/import/excel`    | Import recipes from uploaded .xlsx file          |

Meal categories: `0 = Breakfast`, `1 = SecondBreakfast`, `2 = Lunch`, `3 = Dinner`.

---

## Security

- **JWT** access tokens expire after **15 minutes**; refresh tokens after **7 days**.
- **Refresh-token rotation:** every refresh invalidates the presented token and issues a
  new one. (Refresh tokens are tracked in memory to honor the "do not modify the database"
  constraint — for multi-instance deployments, swap `InMemoryRefreshTokenStore` for a
  shared store such as Redis.)
- **Rate limiting:** `POST /api/auth/login` is limited to 5 attempts per minute per IP.
- **CORS** is restricted to `CORS__AllowedOrigin`.
- **Security headers** are set both in the API and at the nginx edge:
  `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`,
  `Referrer-Policy: no-referrer`, `Content-Security-Policy`, and HSTS.
- **HTTPS** is enforced: nginx redirects all HTTP traffic to HTTPS.
- **No raw SQL** — all data access uses EF Core parameterized queries.
- **All secrets** are provided via environment variables; nothing is hardcoded.
- **Structured logging** with Serilog to console and rolling daily files
  (`MealPlan.Api/logs/`).

---

## PDF export

On the **All Meals** page, select one or more recipes and click **Generate PDF**. The PDF
contains:

1. **One page per recipe** — name, category badge, calories + macros, prep time,
   ingredients and numbered steps.
2. **A final shopping-list page** — ingredients aggregated across all selected recipes:
   - gram amounts of the same ingredient are summed into one line;
   - non-gram units (pcs, tbsp, …) are summed separately per unit;
   - "to taste" / "for serving" / "optional" items are listed once without a quantity;
   - a parenthetical breakdown shows the per-recipe contributions when an ingredient
     appears in more than one recipe;
   - a footer lists every recipe included in the export with its category and calories.

---

## Using a real TLS certificate (production)

The frontend image generates a self-signed certificate at build time. To use a real
certificate (e.g. from Let's Encrypt), mount your cert/key over the defaults in
`docker-compose.yml`:

```yaml
  frontend:
    volumes:
      - /path/to/fullchain.pem:/etc/nginx/certs/server.crt:ro
      - /path/to/privkey.pem:/etc/nginx/certs/server.key:ro
```

---

## Project layout

```
DailyMeals/
├── MealPlan.Api/            ASP.NET Core 8 Web API
│   ├── Controllers/         AuthController, RecipesController
│   ├── Data/                AppDbContext (database-first, no migrations)
│   ├── Models/              Recipe, Ingredient, RecipeStep, ApplicationUser, ...
│   ├── DTOs/                Auth + Recipe DTOs
│   ├── Services/            AuthService, RecipeService, TokenService, token store
│   ├── Middleware/          ExceptionMiddleware
│   ├── Program.cs           Composition root / pipeline
│   └── Dockerfile
├── meal-plan-frontend/      React + TypeScript SPA (original)
│   ├── src/                 api, components, pages, hooks, store, types, utils
│   ├── nginx.conf           TLS, gzip, security headers, /api proxy, SPA fallback
│   └── Dockerfile
├── meal-plan-frontend-angular/  Angular 19 SPA (rewrite of React frontend)
│   ├── src/app/             core, layout, features, shared
│   ├── public/i18n/         EN + PL translations
│   ├── nginx.conf
│   └── Dockerfile
├── meal-plan-frontend-mobile/   Expo (React Native) iOS + Android app
│   ├── app/                 Expo Router screens
│   ├── src/                 api, context, i18n, theme, components
│   └── README.md            Run with `npm start` (see mobile README)
├── docker-compose.yml       API + frontend (no database container)
└── .env.example
```

---

## Notes

- Designed for **Linux hosting** — no Windows-specific APIs or paths.
- **Dark mode** follows `prefers-color-scheme` by default and can be toggled manually;
  the preference is kept in memory only (no `localStorage`), so it resets on reload.
- Because auth tokens are held in memory only, a full page reload returns you to the
  login screen — this is intentional given the in-memory storage requirement.
```
