2026-06-24 22:58:18 +02:00
2026-06-04 06:24:56 +02:00
2026-06-04 06:24:56 +02:00
2026-06-24 22:56:40 +02:00
2026-06-24 22:58:18 +02:00
2026-06-04 06:24:56 +02:00
2026-06-04 06:24:56 +02:00
2026-06-04 06:24:56 +02:00
2026-06-04 06:24:56 +02:00
2026-06-04 06:24:56 +02:00
2026-06-04 06:24:56 +02:00
2026-06-04 06:24:56 +02:00
2026-06-04 06:24:56 +02:00
2026-06-24 21:43:40 +02:00

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 and Docker Compose
  • Network access from the API container to your external PostgreSQL server
  • For local development without Docker: .NET 8 SDK and Node.js 20+

Production deployment (Linux server)

See docs/DEPLOYMENT.md for a full guide (Docker, firewall, PostgreSQL access, Lets Encrypt HTTPS).

Quick version:

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:

    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:

    docker compose up -d --build
    
  3. Open the application:

    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):

    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):

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

    Angular frontend (recommended rewrite):

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

    Or React frontend:

    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:

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:

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:

  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.
Description
No description provided
Readme 1.3 MiB
Languages
TypeScript 67.3%
C# 22.9%
PLpgSQL 8.7%
CSS 0.5%
Dockerfile 0.2%
Other 0.3%