Files
GKNB_MSTM071/Backend/negyedik gyakorlat/FELADAT.md
T
2026-03-04 20:02:39 +01:00

36 KiB

FELADAT: CORS + Manuális DI Container + Email Notification

🎯 Cél

Fejlett backend fejlesztési technikák elsajátítása Node.js-ben Clean Architecture és CQRS mintával:

  • CORS biztonságos konfiguráció whitelist alapú origin ellenőrzéssel
  • Saját Dependency Injection (DI) container különböző lifecycle-okkal (singleton, transient, scoped)
  • Email küldés Nodemailer + Handlebars template alapú értesítésekkel

Amit már kész kaptál:

  • Clean Architecture struktúra (api, application, domain, infrastructure)
  • CQRS Pattern (Commands/Handlers, Queries/Handlers)
  • Prisma ORM PostgreSQL adatbáziskezeléssel
  • Cookie-based JWT autentikáció (regisztráció, login, logout, védett endpointok)
  • bcrypt jelszó hashelés
  • Repository Pattern (Domain interfaces + Infrastructure implementations)
  • JwtService (cookie-based token management)
  • Jest testing framework

📐 Architektúra

Ez a projekt Clean Architecture mintát követi CQRS (Command Query Responsibility Segregation) pattern-nel:

🏛️ Layer Struktúra

src/
├── api/                          # Presentation Layer
│   ├── controllers/              # HTTP request handlers
│   ├── middlewares/              # Express middlewares (auth, cors, scope)
│   ├── routers/                  # Route definitions
│   └── server.js                 # Express app setup + DI configuration
│
├── application/                  # Application Layer
│   ├── auth/commands/            # Authentication commands & handlers
│   ├── user/                     # User commands & queries
│   │   ├── commands/             # Write operations
│   │   └── queries/              # Read operations
│   ├── services/                 # Application services
│   │   ├── JwtService.js         ✅ KÉSZ (Cookie-based JWT)
│   │   └── EmailService.js       ❌ TODO (FELADAT 3)
│   └── Container.js              ❌ TODO (FELADAT 1)
│
├── domain/                       # Domain Layer
│   ├── models/                   # Business entities
│   │   └── User.js               ✅ KÉSZ (Domain model)
│   └── irepositories/            # Repository interfaces
│       └── IUserRepository.js    ✅ KÉSZ
│
└── infrastructure/               # Infrastructure Layer
    ├── db/                       # Database connection
    │   └── DatabaseConnection.js ✅ KÉSZ (Prisma wrapper)
    └── repositories/             # Repository implementations
        └── UserRepository.js     ✅ KÉSZ (Prisma-based)

Key Concepts:

  • Domain: Üzleti entitások és szabályok (User model, IUserRepository interface)
  • Application: Business logic (Commands, Queries, Services, DI Container)
  • Infrastructure: Külső rendszerekhez kapcsolódás (Prisma, Database)
  • API: HTTP layer (controllers, routes, middlewares)

🚀 Projekt Indítása

1. Függőségek telepítése

npm install

2. Környezeti változók konfigurálása

Másold le a .env.example fájlt .env néven:

cp .env.example .env

Állítsd be az .env fájlban:

DATABASE_URL="postgresql://postgres:postgres@localhost:5432/cors_di_app?schema=public"
JWT_SECRET="titkos-kulcs-jwt-hez-változtasd-meg"
JWT_EXPIRES_IN="7d"
PORT=3000
NODE_ENV=development

# Ethereal Email (test SMTP)
ETHEREAL_USER=your-ethereal-user@ethereal.email
ETHEREAL_PASS=your-ethereal-password

# CORS (comma-separated origins)
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173

3. Docker indítása (PostgreSQL)

npm run docker:up

4. Prisma migrációk futtatása

npm run prisma:migrate
npm run prisma:generate

5. Szerver indítása

npm run dev

A szerver elindul: http://localhost:3000


🔐 Kész API Endpoints (Teszteléshez)

Ez a projekt cookie-based JWT autentikációt használ (nem localStorage!):

  • JWT token httpOnly cookie-ban tárolódik
  • Authorization header nem szükséges
  • Böngésző automatikusan küldi a cookie-t

Publikus endpointok (JWT nem kell)

POST http://localhost:3000/api/auth/register
Content-Type: application/json

{
  "name": "John Doe",
  "email": "john@example.com",
  "password": "securePassword123"
}

Válasz:

HTTP/1.1 201 Created
Set-Cookie: auth_token=<JWT>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=604800

{
  "message": "User registered successfully",
  "data": {
    "user": {
      "id": 1,
      "name": "John Doe",
      "email": "john@example.com",
      "createdAt": "2026-03-04T10:00:00.000Z",
      "updatedAt": "2026-03-04T10:00:00.000Z"
    }
  }
}

Figyelem: Token nincs a response body-ban, csak cookie-ban!

POST http://localhost:3000/api/auth/login
Content-Type: application/json

{
  "email": "john@example.com",
  "password": "securePassword123"
}

Válasz:

HTTP/1.1 200 OK
Set-Cookie: auth_token=<JWT>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=604800

{
  "message": "Login successful",
  "data": {
    "user": {
      "id": 1,
      "name": "John Doe",
      "email": "john@example.com"
    }
  }
}
POST http://localhost:3000/api/auth/logout
Cookie: auth_token=<JWT>

Válasz:

HTTP/1.1 200 OK
Set-Cookie: auth_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0

{
  "message": "Logout successful"
}

A böngésző automatikusan küldi a cookie-t. cURL-nél használd a -b és -c flageket.

4. Aktuális felhasználó lekérdezése

# cURL
curl -b cookies.txt http://localhost:3000/api/users/me

# JavaScript (fetch)
fetch('http://localhost:3000/api/users/me', {
  credentials: 'include' // Cookie automatikusan küldődik
})

5. Aktuális felhasználó frissítése

PUT http://localhost:3000/api/users/me
Cookie: auth_token=<JWT>
Content-Type: application/json

{
  "name": "John Updated"
}

6. Összes felhasználó lekérdezése

GET http://localhost:3000/api/users
Cookie: auth_token=<JWT>

7. Egy felhasználó lekérdezése ID alapján

GET http://localhost:3000/api/users/2
Cookie: auth_token=<JWT>

📋 MIT KELL IMPLEMENTÁLNI - 3 FELADAT

⚙️ FELADAT 1: DI Container (src/application/Container.js)

⚙️ FELADAT 1: DI Container (src/application/Container.js)

Cél: Dependency Injection konténer implementálása különböző lifecycle-okkal

⚠️ FONTOS: A Container.js fájl src/application/ mappában van (nem core-ban)!

A Container három lifecycle típust támogat:

  • Singleton: Egyszer jön létre, osztva van minden request között
  • Transient: Minden resolve()-nál új példány jön létre
  • Scoped: Request szinten megosztott a példány (req.scope-on keresztül)

Implementálandó metódusok:

/**
 * Dependency Injection Container
 * src/application/Container.js
 */
class Container {
  constructor() {
    // TODO 1: Inicializáld a services Map-et (singleton instance-ok tárolása)
    // TODO 2: Inicializáld a factories Map-et (factory függvények tárolása)
    // TODO 3: Inicializáld a lifetimes Map-et (lifecycle típusok tárolása)
    
    // Példa inicializálás:
    // this.services = new Map();
    // this.factories = new Map();
    // this.lifetimes = new Map();
  }

  register(name, factory, lifetime = 'singleton') {
    // TODO 4: Tárold el a factory függvényt (this.factories.set(name, factory))
    // TODO 5: Tárold el a lifetime típust (this.lifetimes.set(name, lifetime))
    // TODO 6: Ha a lifetime === 'singleton', azonnal példányosítsd:
    //         - Hívd meg a factory-t: const instance = factory();
    //         - Tárold el: this.services.set(name, instance);
  }

  resolve(name, scope = null) {
    // TODO 7: Ha a service regisztrálva van mint 'scoped' ÉS van scope paraméter:
    //         - Ellenőrizd: if (scope && scope.has(name)) return scope.get(name);
    //         - Ha nincs még a scope-ban, példányosítsd és tárold: 
    //           const instance = this.factories.get(name)();
    //           scope.set(name, instance);
    //           return instance;
    
    // TODO 8: Ha singleton, add vissza a services-ből:
    //         if (this.lifetimes.get(name) === 'singleton') {
    //           return this.services.get(name);
    //         }
    
    // TODO 9: Ha transient, minden alkalommal hívj egy új factory-t:
    //         if (this.lifetimes.get(name) === 'transient') {
    //           return this.factories.get(name)();
    //         }
    
    // TODO 10: Ha nem regisztrált a service, dobj hibát:
    //          throw new Error(`Service '${name}' is not registered`);
  }

  createScope() {
    // TODO 11: Hozz létre egy új Map-et az scoped instance-oknak
    // TODO 12: Térj vissza egy objektummal ami tartalmaz egy resolve metódust:
    //          const scopeMap = new Map();
    //          return {
    //            resolve: (name) => this.resolve(name, scopeMap)
    //          };
  }
}

module.exports = Container;

Tesztelés: A src/api/server.js már használja a Container-t:

const container = new Container();

// Singleton regisztráció
container.register('PrismaClient', () => {
  return databaseConnection.getClient();
}, 'singleton');

// Használat
const prisma = container.resolve('PrismaClient');

Indítsd el a szervert és nézd meg, hogy működik-e a DI:

npm run dev
# ✅ DI Container configured (CQRS pattern)

Részletes segítség: Nézd meg a SEGÍTSÉG.md fájlt teljes megoldással!


🌐 FELADAT 2: CORS Middleware (src/api/middlewares/corsMiddleware.js)

Cél: Whitelist alapú CORS konfiguráció implementálása

A CORS middleware csak bizonyos origin-ekről engedélyezi a hozzáférést.

const cors = require('cors');

// Engedélyezett origin-ek whitelist-je (környezeti változóból vagy default)
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [
  'http://localhost:3000',
  'http://localhost:5173'
];

const corsOptions = {
  origin: function (origin, callback) {
    // TODO 1: Ha nincs origin (pl. backend-to-backend, Postman, curl):
    //         if (!origin) {
    //           return callback(null, true);
    //         }
    
    // TODO 2: Ha az origin benne van az allowedOrigins listában:
    //         if (allowedOrigins.includes(origin)) {
    //           return callback(null, true);
    //         }
    
    // TODO 3: Egyébként tiltsd le CORS hibával:
    //         callback(new Error('Not allowed by CORS'));
  },
  credentials: true, // Cookie és Authorization header engedélyezése
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization']
};

module.exports = cors(corsOptions);

.env beállítás:

ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173,https://myfrontend.com

Aktiválás a server.js-ben:

Uncomment this line in src/api/server.js:

// A middleware chain-ben (körülbelül 130. sor)
app.use(cookieParser());
app.use(corsMiddleware);  // <-- UNCOMMENT THIS LINE!

Tesztelés cURL-lel:

# Engedélyezett origin - SIKERES
curl -H "Origin: http://localhost:3000" http://localhost:3000/api/users
# Access-Control-Allow-Origin: http://localhost:3000

# Tiltott origin - HIBA
curl -H "Origin: http://malicious-site.com" http://localhost:3000/api/users
# Error: Not allowed by CORS

Frontend tesztelés (React/Vue):

fetch('http://localhost:3000/api/auth/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include', // FONTOS: Cookie küldés/fogadás engedélyezése
  body: JSON.stringify({ email, password })
})
.then(res => res.json())
.then(data => console.log('Login sikeres:', data))
.catch(err => console.error('CORS hiba:', err));

Részletes segítség: Nézd meg a SEGÍTSÉG.md fájlt teljes megoldással!


📧 FELADAT 3: Email Service (src/application/services/EmailService.js)

Cél: Nodemailer + Handlebars template alapú email küldés implementálása

Az EmailService welcome emailt küld regisztráció után.

⚠️ FONTOS: Az EmailService src/application/services/ mappában van!

const nodemailer = require('nodemailer');
const handlebars = require('handlebars');
const fs = require('fs');
const path = require('path');

class EmailService {
  constructor() {
    // TODO 1: Hozz létre Nodemailer transportot Ethereal tesztelő SMTP-vel
    //         this.transporter = nodemailer.createTransport({
    //           host: 'smtp.ethereal.email',
    //           port: 587,
    //           secure: false, // TLS
    //           auth: {
    //             user: process.env.ETHEREAL_USER || 'your-test-email@ethereal.email',
    //             pass: process.env.ETHEREAL_PASS || 'your-test-password'
    //           }
    //         });
    
    console.log('📧 EmailService initialized');
  }

  async sendWelcomeEmail(userEmail, userName) {
    try {
      // TODO 2: Olvasd be a welcome.hbs template fájlt
      //         const templatePath = path.join(__dirname, 'templates', 'welcome.hbs');
      //         const templateSource = fs.readFileSync(templatePath, 'utf-8');
      
      // TODO 3: Compile-old a template-et Handlebars-szal
      //         const template = handlebars.compile(templateSource);
      
      // TODO 4: Generáld a HTML-t a felhasználói adatokkal
      //         const html = template({ 
      //           name: userName,
      //           date: new Date().toLocaleDateString('hu-HU')
      //         });
      
      // TODO 5: Küld el az emailt
      //         const info = await this.transporter.sendMail({
      //           from: '"Clean Architecture App" <noreply@cleanarch.com>',
      //           to: userEmail,
      //           subject: 'Üdvözlünk az alkalmazásban!',
      //           html: html
      //         });
      
      // TODO 6: Logold ki az Ethereal preview URL-t
      //         const previewUrl = nodemailer.getTestMessageUrl(info);
      //         console.log('📧 Email elküldve:', previewUrl);
      
      return true;
    } catch (error) {
      console.error('❌ Email küldési hiba:', error.message);
      return false;
    }
  }
}

module.exports = EmailService;

Email Template létrehozása:

Hozd létre: src/application/services/templates/welcome.hbs

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    body { font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px; }
    .container { background: white; padding: 30px; border-radius: 10px; max-width: 600px; margin: 0 auto; }
    h1 { color: #333; }
    .footer { margin-top: 30px; font-size: 12px; color: #999; }
  </style>
</head>
<body>
  <div class="container">
    <h1>🎉 Üdvözlünk, {{name}}!</h1>
    <p>Sikeres regisztráció az alkalmazásunkban!</p>
    <p>Regisztráció dátuma: <strong>{{date}}</strong></p>
    <p>Mostantól hozzáférsz az összes funkciónkhoz.</p>
    <div class="footer">
      <p>Ez egy automatikus üzenet, kérjük ne válaszolj rá.</p>
    </div>
  </div>
</body>
</html>

.env konfigurálás Ethereal SMTP-vel:

  1. Menj a https://ethereal.email oldalra
  2. Kattints a "Create Ethereal Account" gombra
  3. Másold ki a kapott user és password értékeket
  4. Állítsd be a .env fájlban:
ETHEREAL_USER=your-generated-user@ethereal.email
ETHEREAL_PASS=your-generated-password

Tesztelés:

Regisztrálj egy új felhasználót:

curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Test User",
    "email": "test@example.com",
    "password": "password123"
  }'

Console output:

📧 Email elküldve: https://ethereal.email/message/XXXXXX

Nyisd meg a böngészőben a linket és látni fogod az elküldött emailt!

Részletes segítség: Nézd meg a SEGÍTSÉG.md fájlt teljes megoldással és példákkal!


📁 Projekt Struktúra (Frissített)

src/
├── api/                          # Presentation Layer
│   ├── controllers/
│   │   ├── AuthController.js     ✅ KÉSZ (Cookie-based JWT)
│   │   └── UserController.js     ✅ KÉSZ (CQRS queries/commands)
│   ├── middlewares/
│   │   ├── authMiddleware.js     ✅ KÉSZ (Cookie-based auth)
│   │   ├── corsMiddleware.js     ❌ TODO (FELADAT 2)
│   │   └── scopeMiddleware.js    📚 Opcionális
│   ├── routers/
│   │   ├── authRoutes.js         ✅ KÉSZ (register, login, logout)
│   │   └── userRoutes.js         ✅ KÉSZ (protected endpoints)
│   └── server.js                 ✅ KÉSZ (Express + DI setup)
│
├── application/                  # Application Layer
│   ├── auth/commands/            ✅ KÉSZ (CQRS)
│   │   ├── RegisterUserCommand.js
│   │   ├── RegisterUserCommandHandler.js
│   │   ├── LoginUserCommand.js
│   │   └── LoginUserCommandHandler.js
│   ├── user/                     ✅ KÉSZ (CQRS)
│   │   ├── commands/             # Write operations
│   │   └── queries/              # Read operations
│   ├── services/
│   │   ├── JwtService.js         ✅ KÉSZ (Cookie-based JWT)
│   │   ├── EmailService.js       ❌ TODO (FELADAT 3)
│   │   └── templates/
│   │       └── welcome.hbs       ❌ TODO (Email template)
│   └── Container.js              ❌ TODO (FELADAT 1)
│
├── domain/                       # Domain Layer
│   ├── models/
│   │   └── User.js               ✅ KÉSZ (Domain entity)
│   └── irepositories/
│       └── IUserRepository.js    ✅ KÉSZ (Repository interface)
│
├── infrastructure/               # Infrastructure Layer
│   ├── db/
│   │   └── DatabaseConnection.js ✅ KÉSZ (Prisma wrapper)
│   └── repositories/
│       └── UserRepository.js     ✅ KÉSZ (Prisma implementation)
│
├── prisma/
│   ├── schema.prisma             ✅ KÉSZ (User model)
│   └── migrations/               ✅ KÉSZ
│
└── tests/                        # Testing
    └── unit/
        ├── commands/             ✅ KÉSZ (Command handler tests)
        ├── queries/              ✅ KÉSZ (Query handler tests)
        ├── controllers/          ✅ KÉSZ (Controller tests)
        ├── middlewares/          ✅ KÉSZ (Middleware tests)
        └── services/             ✅ KÉSZ (JwtService tests)

🎯 Tanulási Célok

CORS (Cross-Origin Resource Sharing)

  • Origin alapú hozzáférés-vezérlés implementálása
  • Whitelist konfiguráció production környezetben
  • Preflight request-ek kezelése (OPTIONS)
  • Credential (cookie, auth header) kezelés

DI Container (Dependency Injection)

  • Lifecycle management (Singleton, Transient, Scoped)
  • Loose coupling az alkalmazásban
  • Tesztelhetőség javítása (dependency injection)
  • Service registry pattern

Email Notification

  • SMTP konfiguráció (Ethereal Email)
  • Template-alapú email generálás (Handlebars)
  • Async email küldés (nem blokkolja a request-et)
  • Test email preview Ethereal-lel

Clean Architecture + CQRS

  • Layer separation (Domain, Application, Infrastructure, API)
  • CQRS Pattern (Commands for writes, Queries for reads)
  • Repository Pattern (interface + implementation)
  • Cookie-based JWT authentication (httpOnly, secure, sameSite)

🧪 Teljes Tesztelési Flow

1. Szerver indítása

npm run dev

Expected output:

✅ Prisma connected to PostgreSQL
✅ DI Container configured (CQRS pattern)
🚀 Server running on http://localhost:3000
📧 Email Service configured (implement EmailService!)
🔐 JWT Authentication enabled (cookie-based)
🍪 Cookies: httpOnly, secure (production), sameSite=strict

📍 Endpoints:
   POST   /api/auth/register
   POST   /api/auth/login
   POST   /api/auth/logout
   GET    /api/users/me (protected)
   PUT    /api/users/me (protected)
   GET    /api/users (protected)
   GET    /api/users/:id (protected)
curl -c cookies.txt -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Test User",
    "email": "test@example.com",
    "password": "password123"
  }'

Kimenet:

  • JWT token cookie-ban (Set-Cookie header)
  • bcrypt-tel hashelt jelszó az adatbázisban
  • Email elküldve (ha implementáltad a FELADAT 3-at)
  • Cookie mentve a cookies.txt fájlba

3. Bejelentkezés

curl -c cookies.txt -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "password": "password123"
  }'

Kimenet:

  • JWT token cookie-ban
  • bcrypt.compare jelszó ellenőrzés
curl -b cookies.txt http://localhost:3000/api/users/me

Kimenet:

  • authMiddleware ellenőrzi a JWT token-t cookie-ból
  • req.user.userId beállítva
  • GetMeQueryHandler lekéri az aktuális user-t Prisma-val

5. Kijelentkezés

curl -b cookies.txt -c cookies.txt -X POST http://localhost:3000/api/auth/logout

Kimenet:

  • Cookie törlése (Max-Age=0)
  • Következő request-nél 401 Unauthorized

🧪 Unit Testing

Futtatás:

npm test                # Összes teszt + coverage
npm run test:watch      # Watch mode
npm run test:unit       # Csak unit tesztek

Expected output:

PASS  tests/unit/commands/RegisterUserCommandHandler.test.js
PASS  tests/unit/commands/LoginUserCommandHandler.test.js
PASS  tests/unit/queries/GetMeQueryHandler.test.js
PASS  tests/unit/controllers/AuthController.test.js
PASS  tests/unit/middlewares/authMiddleware.test.js
PASS  tests/unit/services/JwtService.test.js

Test Suites: 10 passed, 10 total
Tests:       75 passed, 75 total

Hibaelhárítás

Docker PostgreSQL nem indul

docker-compose down
docker-compose up -d
docker-compose logs postgres

Prisma migration hiba

npm run prisma:migrate:reset
npm run prisma:migrate
npm run prisma:generate
  • Ellenőrizd, hogy a .env fájlban van-e JWT_SECRET
  • cURL-nél használd a -b cookies.txt flag-et
  • Frontend-en használd a credentials: 'include' opciót

Email nem megy ki

  • Ellenőrizd az Ethereal SMTP credentials-t (.env)
  • Nézd meg a console-t az Ethereal preview URL-ért
  • Hozd létre a templates/welcome.hbs fájlt
  • Ellenőrizd, hogy implementáltad-e az EmailService-t

CORS hiba frontend-ből

  • Add meg a frontend origin-t az .env fájlban: ALLOWED_ORIGINS=http://localhost:5173
  • Uncomment a app.use(corsMiddleware); sort a server.js-ben
  • Implementáld a CORS middleware-t (FELADAT 2)

📊 Értékelési Szempontok

Funkcionális követelmények:

  • DI Container működik (register, resolve, createScope)
  • Singleton, Transient, Scoped lifecycle-ok implementálva
  • CORS whitelist működik (engedélyezett és tiltott origin-ek)
  • Email küldés funkcionális (Handlebars template + Ethereal SMTP)
  • Template fájl létrehozva és használva

Kód minőség:

  • Kód tisztaság és SOLID elvek követése
  • Hibakezelés (try-catch, validation)
  • Environment változók használata (.env)
  • Kommentek és dokumentáció
  • Clean Architecture layering betartása

Testing:

  • Unit tesztek írása (Container, CORS, EmailService)
  • Tesztek lefutnak és zöldek
  • Megfelelő coverage (>70%)

📚 Hasznos Források

  • 📖 SEGÍTSÉG.md - Részletes példakódok és megoldások
  • 📖 ARCHITECTURE.md - Clean Architecture dokumentáció
  • 📖 COOKIE_AUTH_MIGRATION.md - Cookie-based JWT migration guide
  • 🌐 Ethereal Email - Test SMTP service
  • 🌐 Handlebars - Template engine
  • 🌐 Prisma Docs - ORM documentation

Sok sikert a feladatokhoz! 🚀

Ha elakadtál, nézd meg a SEGÍTSÉG.md fájlt teljes megoldásokkal és magyarázatokkal!

Cél: Dependency Injection konténer implementálása különböző lifecycle-okkal

A Container három lifecycle típust támogat:

  • Singleton: Egyszer jön létre, osztva van minden request között
  • Transient: Minden resolve()-nál új példány jön létre
  • Scoped: Request szinten megosztott a példány (req.scope-on keresztül)
class Container {
  constructor() {
    // TODO 1: Inicializáld a services Map-et (singleton instance-ok tárolása)
    // TODO 2: Inicializáld a factories Map-et (factory függvények tárolása)
    // TODO 3: Inicializáld a lifetimes Map-et (lifecycle típusok tárolása)
    
    // Példa inicializálás:
    // this.services = new Map();
    // this.factories = new Map();
    // this.lifetimes = new Map();
  }

  register(name, factory, lifetime = 'singleton') {
    // TODO 4: Tárold el a factory függvényt (this.factories.set(name, factory))
    // TODO 5: Tárold el a lifetime típust (this.lifetimes.set(name, lifetime))
    // TODO 6: Ha a lifetime === 'singleton', azonnal példányosítsd:
    //         - Hívd meg a factory-t: const instance = factory();
    //         - Tárold el: this.services.set(name, instance);
  }

  resolve(name, scope = null) {
    // TODO 7: Ha a service regisztrálva van mint 'scoped' ÉS van scope paraméter:
    //         - Ellenőrizd: if (scope && scope.has(name)) return scope.get(name);
    //         - Ha nincs még a scope-ban, példányosítsd és tárold: 
    //           const instance = this.factories.get(name)();
    //           scope.set(name, instance);
    //           return instance;
    
    // TODO 8: Ha singleton, add vissza a services-ből:
    //         if (this.lifetimes.get(name) === 'singleton') {
    //           return this.services.get(name);
    //         }
    
    // TODO 9: Ha transient, minden alkalommal hívj egy új factory-t:
    //         if (this.lifetimes.get(name) === 'transient') {
    //           return this.factories.get(name)();
    //         }
    
    // TODO 10: Ha nem regisztrált a service, dobj hibát:
    //          throw new Error(`Service ''${name}'' is not registered`);
  }

  createScope() {
    // TODO 11: Hozz létre egy új Map-et az scoped instance-oknak
    // TODO 12: Térj vissza egy objektummal ami tartalmaz egy resolve metódust:
    //          const scopeMap = new Map();
    //          return {
    //            resolve: (name) => this.resolve(name, scopeMap)
    //          };
  }
}

module.exports = Container;

Tesztelés: A src/api/server.js már használja a Container-t. Indítsd el a szervert és nézd meg, hogy működik-e a DI.


FELADAT 2: CORS Middleware (src/api/middlewares/corsMiddleware.js)

Cél: Whitelist alapú CORS konfiguráció implementálása

A CORS middleware csak bizonyos origin-ekről engedélyezi a hozzáférést.

const cors = require(''cors'');

// Engedélyezett origin-ek whitelist-je (környezeti változóból vagy default)
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split('','') || [
  ''http://localhost:3000'',
  ''http://localhost:5173''
];

const corsOptions = {
  origin: function (origin, callback) {
    // TODO 1: Ha nincs origin (pl. backend-to-backend, Postman, curl):
    //         if (!origin) {
    //           return callback(null, true);
    //         }
    
    // TODO 2: Ha az origin benne van az allowedOrigins listában:
    //         if (allowedOrigins.includes(origin)) {
    //           return callback(null, true);
    //         }
    
    // TODO 3: Egyébként tiltsd le CORS hibával:
    //         callback(new Error(''Not allowed by CORS''));
  },
  credentials: true, // Cookie és Authorization header engedélyezése
  methods: [''GET'', ''POST'', ''PUT'', ''DELETE'', ''OPTIONS''],
  allowedHeaders: [''Content-Type'', ''Authorization'']
};

module.exports = cors(corsOptions);

.env beállítás:

ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173,https://myfrontend.com

Tesztelés:

curl -H "Origin: http://localhost:3000" http://localhost:3000/api/users
# Sikeres - engedélyezett origin

curl -H "Origin: http://malicious-site.com" http://localhost:3000/api/users  
# Hiba - tiltott origin

Aktiválás a server.js-ben:

// src/api/server.js
const corsMiddleware = require(''./middlewares/corsMiddleware'');

app.use(corsMiddleware);  // <-- Uncomment this line!

FELADAT 3: Email Service (src/core/EmailService.js)

Cél: Nodemailer + Handlebars template alapú email küldés implementálása

Az EmailService welcome emailt küld regisztráció után.

const nodemailer = require(''nodemailer'');
const handlebars = require(''handlebars'');
const fs = require(''fs'');
const path = require(''path'');

class EmailService {
  constructor() {
    // TODO 1: Hozz létre Nodemailer transportot Ethereal tesztelő SMTP-vel
    //         this.transporter = nodemailer.createTransport({
    //           host: ''smtp.ethereal.email'',
    //           port: 587,
    //           secure: false, // TLS
    //           auth: {
    //             user: process.env.ETHEREAL_USER || ''your-test-email@ethereal.email'',
    //             pass: process.env.ETHEREAL_PASS || ''your-test-password''
    //           }
    //         });
    
    console.log('' EmailService initialized'');
  }

  async sendWelcomeEmail(userEmail, userName) {
    try {
      // TODO 2: Olvasd be a welcome.hbs template fájlt
      //         const templatePath = path.join(__dirname, ''templates'', ''welcome.hbs'');
      //         const templateSource = fs.readFileSync(templatePath, ''utf-8'');
      
      // TODO 3: Compile-old a template-et Handlebars-szal
      //         const template = handlebars.compile(templateSource);
      
      // TODO 4: Generáld a HTML-t a felhasználói adatokkal
      //         const html = template({ 
      //           name: userName,
      //           date: new Date().toLocaleDateString(''hu-HU'')
      //         });
      
      // TODO 5: Küld el az emailt
      //         const info = await this.transporter.sendMail({
      //           from: ''"Clean Architecture App" <noreply@cleanarch.com>'',
      //           to: userEmail,
      //           subject: ''Üdvözlünk az alkalmazásban!'',
      //           html: html
      //         });
      
      // TODO 6: Logold ki az Ethereal preview URL-t
      //         const previewUrl = nodemailer.getTestMessageUrl(info);
      //         console.log('' Email elküldve:'', previewUrl);
      
    } catch (error) {
      console.error('' Email küldési hiba:'', error.message);
    }
  }
}

module.exports = EmailService;

Email Template (src/core/templates/welcome.hbs):

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    body { font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px; }
    .container { background: white; padding: 30px; border-radius: 10px; max-width: 600px; margin: 0 auto; }
    h1 { color: #333; }
    .footer { margin-top: 30px; font-size: 12px; color: #999; }
  </style>
</head>
<body>
  <div class="container">
    <h1>Üdvözlünk, {{name}}! </h1>
    <p>Sikeres regisztráció az alkalmazásunkban!</p>
    <p>Regisztráció dátuma: <strong>{{date}}</strong></p>
    <p>Mostantól hozzáférsz az összes funkciónkhoz.</p>
    <div class="footer">
      <p>Ez egy automatikus üzenet, kérjük ne válaszolj rá.</p>
    </div>
  </div>
</body>
</html>

.env konfigurálás Ethereal SMTP-vel:

  1. Menj a https://ethereal.email oldalra
  2. Kattints a "Create Ethereal Account" gombra
  3. Másold ki a kapott user és password értékeket
  4. Állítsd be a .env fájlban:
ETHEREAL_USER=your-generated-user@ethereal.email
ETHEREAL_PASS=your-generated-password

Tesztelés: Regisztrálj egy új felhasználót:

POST http://localhost:3000/api/auth/register
{
  "name": "Test User",
  "email": "test@example.com",
  "password": "password123"
}

Nézd meg a console-ban az Ethereal preview URL-t, ami így níz ki:

 Email elküldve: https://ethereal.email/message/XXXXXX

Nyísd meg a böngészőben és látni fogod az elküldött emailt!


Projekt Struktúra

src/
 api/
    controllers/
       AuthController.js        Kész (regisztráció, login)
       UserController.js        Kész (védett user endpointok)
    middlewares/
       authMiddleware.js        Kész (JWT token validálás)
       corsMiddleware.js        TODO - FELADAT 2
       scopeMiddleware.js       Opcionális
    routers/
       authRoutes.js            Kész (publikus auth endpointok)
       userRoutes.js            Kész (JWT védett user endpointok)
    server.js                    Kész (Express + DI setup)
 application/
    auth/
       AuthService.js           Kész (bcrypt + JWT)
    user/
        UserService.js           Kész (Prisma CRUD)
 core/
    Container.js                 TODO - FELADAT 1
    EmailService.js              TODO - FELADAT 3
    JwtUtil.js                   Kész (JWT utility)
    templates/
        welcome.hbs             Welcome email template
 domain/
    (Prisma modellekkel helyettesítve)
 infrastructure/
    (Prisma ORM kezeli)
 prisma/
     schema.prisma                Kész (User model)

Tanulási Célok

CORS (Cross-Origin Resource Sharing)

  • Origin alapú hozzáférés-vezérlés implementálása
  • Whitelist konfiguráció production környezetben
  • Preflight request-ek kezelése (OPTIONS)
  • Credential (cookie, auth header) kezelés

DI Container (Dependency Injection)

  • Lifecycle management (Singleton, Transient, Scoped)
  • Loose coupling az alkalmazásban
  • Tesztelhetőség javítása (dependency injection)
  • Service registry pattern

Email Notification

  • SMTP konfiguráció (Ethereal Email)
  • Template-alapú email generálás (Handlebars)
  • Async email küldés (nem blokkolja a request-et)
  • Test email preview Ethereal-lel

Teljes Tesztelési Flow

1. Szerver indítása

npm run dev

2. Regisztráció

POST http://localhost:3000/api/auth/register
{
  "name": "Test User",
  "email": "test@example.com",
  "password": "password123"
}

Kimenet:

  • JWT token válaszban
  • bcrypt-tel hashelt jelszó az adatbázisban
  • Email elküldve (ha implementáltad a FELADAT 3-at)

3. Bejelentkezés

POST http://localhost:3000/api/auth/login
{
  "email": "test@example.com",
  "password": "password123"
}

Kimenet:

  • JWT token válaszban
  • bcrypt.compare jelszó ellenőrzés

4. Védett endpoint hívása

GET http://localhost:3000/api/users/me
Authorization: Bearer <token>

Kimenet:

  • authMiddleware ellenőrzi a JWT token-t
  • req.user.userId beállítva
  • UserService lekéri az aktuális user-t Prisma-val

Hibaelhárítás

Docker PostgreSQL nem indul

docker-compose down
docker-compose up -d
docker-compose logs postgres

Prisma migration hiba

npm run prisma:migrate:reset
npm run prisma:migrate
npm run prisma:generate

JWT token invalid hiba

  • Ellenőrizd, hogy a .env fájlban van-e JWT_SECRET
  • Ellenőrizd, hogy a token Authorization: Bearer <token> formátumban van-e

Email nem megy ki

  • Ellenőrizd az Ethereal SMTP credentials-t (.env)
  • Nézd meg a console-t az Ethereal preview URL-ért
  • Ellenőrizd, hogy implementáltad-e az EmailService-t

Értékelési Szempontok

  • DI Container működik (register, resolve, createScope)
  • CORS whitelist működik (engedélyezett és tiltott origin-ek)
  • Email külső funkcionális (Handlebars template + Ethereal SMTP)
  • Kód tisztaság és SOLID elvek követése
  • Hibakezelés (try-catch, validation)
  • Environment változók használata (.env)

Sok sikert!