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

1215 lines
36 KiB
Markdown

# 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=<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!
#### 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=<JWT>; 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=<JWT>
```
**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=<JWT>
Content-Type: application/json
{
"name": "John Updated"
}
```
#### 6. Összes felhasználó lekérdezése
```bash
GET http://localhost:3000/api/users
Cookie: auth_token=<JWT>
```
#### 7. Egy felhasználó lekérdezése ID alapján
```bash
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:**
```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" <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`
```html
<!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:
```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" <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):**
```html
<!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:
```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 <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
```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 <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!