# 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 ```bash npm install ``` ### 2. Környezeti változók konfigurálása Másold le a `.env.example` fájlt `.env` néven: ```bash cp .env.example .env ``` Állítsd be az `.env` fájlban: ```env 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) ```bash npm run docker:up ``` ### 4. Prisma migrációk futtatása ```bash npm run prisma:migrate npm run prisma:generate ``` ### 5. Szerver indítása ```bash npm run dev ``` A szerver elindul: `http://localhost:3000` --- ## 🔐 Kész API Endpoints (Teszteléshez) ### ⚠️ FONTOS: Cookie-Based Authentication 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) #### 1. Regisztráció (Cookie-t ad vissza) ```bash POST http://localhost:3000/api/auth/register Content-Type: application/json { "name": "John Doe", "email": "john@example.com", "password": "securePassword123" } ``` **Válasz:** ```http HTTP/1.1 201 Created Set-Cookie: auth_token=; 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! #### 2. Bejelentkezés (Cookie-t ad vissza) ```bash POST http://localhost:3000/api/auth/login Content-Type: application/json { "email": "john@example.com", "password": "securePassword123" } ``` **Válasz:** ```http HTTP/1.1 200 OK Set-Cookie: auth_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=604800 { "message": "Login successful", "data": { "user": { "id": 1, "name": "John Doe", "email": "john@example.com" } } } ``` #### 3. Kijelentkezés (Cookie törlése) ```bash POST http://localhost:3000/api/auth/logout Cookie: auth_token= ``` **Válasz:** ```http HTTP/1.1 200 OK Set-Cookie: auth_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0 { "message": "Logout successful" } ``` ### Védett endpointok (JWT cookie szükséges!) 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 ```bash # 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 ```bash PUT http://localhost:3000/api/users/me Cookie: auth_token= Content-Type: application/json { "name": "John Updated" } ``` #### 6. Összes felhasználó lekérdezése ```bash GET http://localhost:3000/api/users Cookie: auth_token= ``` #### 7. Egy felhasználó lekérdezése ID alapján ```bash GET http://localhost:3000/api/users/2 Cookie: auth_token= ``` --- ## 📋 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:** ```javascript /** * 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: ```javascript 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: ```bash 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. ```javascript 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:** ```env 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`: ```javascript // A middleware chain-ben (körülbelül 130. sor) app.use(cookieParser()); app.use(corsMiddleware); // <-- UNCOMMENT THIS LINE! ``` **Tesztelés cURL-lel:** ```bash # 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):** ```javascript 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! ```javascript 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" ', // 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` ```html

🎉 Üdvözlünk, {{name}}!

Sikeres regisztráció az alkalmazásunkban!

Regisztráció dátuma: {{date}}

Mostantól hozzáférsz az összes funkciónkhoz.

``` **.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: ```env ETHEREAL_USER=your-generated-user@ethereal.email ETHEREAL_PASS=your-generated-password ``` **Tesztelés:** Regisztrálj egy új felhasználót: ```bash 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 ```bash 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) ``` ### 2. Regisztráció (cURL cookie példa) ```bash 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 ```bash 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 ### 4. Védett endpoint hívása (Cookie-val) ```bash 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 ```bash 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:** ```bash 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 ```bash docker-compose down docker-compose up -d docker-compose logs postgres ``` ### Prisma migration hiba ```bash npm run prisma:migrate:reset npm run prisma:migrate npm run prisma:generate ``` ### JWT token invalid hiba (Cookie-based) - ✅ 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](SEGÍTSÉG.md) - Részletes példakódok és megoldások - 📖 [ARCHITECTURE.md](ARCHITECTURE.md) - Clean Architecture dokumentáció - 📖 [COOKIE_AUTH_MIGRATION.md](COOKIE_AUTH_MIGRATION.md) - Cookie-based JWT migration guide - 🌐 [Ethereal Email](https://ethereal.email) - Test SMTP service - 🌐 [Handlebars](https://handlebarsjs.com/) - Template engine - 🌐 [Prisma Docs](https://www.prisma.io/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) ```javascript 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. ```javascript 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:** ```env ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173,https://myfrontend.com ``` **Tesztelés:** ```bash 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:** ```javascript // 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. ```javascript 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" '', // 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):** ```html

Üdvözlünk, {{name}}!

Sikeres regisztráció az alkalmazásunkban!

Regisztráció dátuma: {{date}}

Mostantól hozzáférsz az összes funkciónkhoz.

``` **.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: ```env ETHEREAL_USER=your-generated-user@ethereal.email ETHEREAL_PASS=your-generated-password ``` **Tesztelés:** Regisztrálj egy új felhasználót: ```bash 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 ```bash npm run dev ``` ### 2. Regisztráció ```bash 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 ```bash 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 ```bash GET http://localhost:3000/api/users/me Authorization: Bearer ``` **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 ```bash docker-compose down docker-compose up -d docker-compose logs postgres ``` ### Prisma migration hiba ```bash 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 ` 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!