diff --git a/Backend_ppt/dic_cors_emailsending/cors.tex b/Backend_ppt/dic_cors_emailsending/cors.tex new file mode 100644 index 0000000..6d239e4 --- /dev/null +++ b/Backend_ppt/dic_cors_emailsending/cors.tex @@ -0,0 +1,486 @@ +\section{CORS} + +\begin{frame}[fragile]{Mi az a CORS?} + \begin{block}{Cross-Origin Resource Sharing} + \begin{itemize} + \item Biztonsági mechanizmus, amely szabályozza a kereszt-eredet HTTP kéréseket + \item A böngészők alapértelmezés szerint blokkolják a különböző eredetről (origin) érkező kéréseket + \item Az \textbf{origin} = protokoll + domain + port + \item CORS policy lehetővé teszi biztonságos erőforrás-megosztást különböző domainek között + \end{itemize} + \end{block} + + \begin{exampleblock}{Példák különböző originekre} + \begin{itemize} + \item \texttt{https://example.com:443} $\neq$ \texttt{http://example.com:80} (protokoll) + \item \texttt{https://example.com} $\neq$ \texttt{https://api.example.com} (subdomain) + \item \texttt{https://example.com:3000} $\neq$ \texttt{https://example.com:4000} (port) + \end{itemize} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Miért van szükség CORS-ra?} + \begin{block}{Same-Origin Policy (SOP)} + \begin{itemize} + \item Böngészők biztonsági mechanizmusa + \item Megakadályozza, hogy rosszindulatú scriptek más domainek adataihoz férjenek hozzá + \item Alapértelmezés szerint csak azonos origin-ről tölthet le erőforrásokat + \end{itemize} + \end{block} + + \begin{alertblock}{Probléma} + Modern alkalmazásoknál gyakori: + \begin{itemize} + \item Frontend: \texttt{http://localhost:3000} + \item Backend API: \texttt{http://localhost:5000} + \item Különböző origin $\rightarrow$ CORS hiba! + \end{itemize} + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{CORS működése - Simple Request} + \begin{block}{Egyszerű kérések} + Olyan kérések, amelyek nem váltanak ki preflight kérést: + \begin{itemize} + \item Metódus: GET, HEAD, vagy POST + \item Content-Type: \texttt{application/x-www-form-urlencoded}, \texttt{multipart/form-data}, \texttt{text/plain} + \item Csak standard headerek + \end{itemize} + \end{block} + + \begin{exampleblock}{Kliens oldali kérés} +\small +\begin{lstlisting}[language=JavaScript] +fetch('http://api.example.com/data') + .then(response => response.json()) + .then(data => console.log(data)); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS működése - Preflight Request} + \begin{block}{Preflight kérés} + \begin{itemize} + \item Komplex kérések előtt a böngésző automatikusan küld egy OPTIONS kérést + \item Ellenőrzi, hogy a szerver engedélyezi-e a tényleges kérést + \item PUT, DELETE, PATCH metódusok vagy custom headerek esetén + \end{itemize} + \end{block} + + \begin{columns}[T] + \begin{column}{0.48\textwidth} + \textbf{1. Preflight (OPTIONS)} + \begin{itemize} + \item Access-Control-Request-Method + \item Access-Control-Request-Headers + \end{itemize} + \end{column} + \begin{column}{0.48\textwidth} + \textbf{2. Actual Request} + \begin{itemize} + \item A tényleges HTTP kérés + \item Csak akkor megy el, ha a preflight engedélyező választ adott + \end{itemize} + \end{column} + \end{columns} +\end{frame} + +\begin{frame}[fragile]{CORS Headers - Szerver válaszok} + \begin{block}{Legfontosabb CORS headerek} + \begin{description} + \item[\texttt{Access-Control-Allow-Origin}] Megadja, mely origin-ek férhetnek hozzá + \item[\texttt{Access-Control-Allow-Methods}] Engedélyezett HTTP metódusok + \item[\texttt{Access-Control-Allow-Headers}] Engedélyezett HTTP headerek + \item[\texttt{Access-Control-Allow-Credentials}] Cookie-k küldése engedélyezett-e + \item[\texttt{Access-Control-Max-Age}] Preflight válasz cache ideje (másodperc) + \end{description} + \end{block} +\end{frame} + +\begin{frame}[fragile]{CORS engedélyezése Express.js-ben - Manuális (1/2)} + \begin{exampleblock}{Egyszerű megoldás - minden origin engedélyezése} +\small +\begin{lstlisting}[language=JavaScript] +app.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Methods', + 'GET, POST, PUT, DELETE, OPTIONS'); + res.header('Access-Control-Allow-Headers', + 'Content-Type, Authorization'); + + next(); +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS engedélyezése Express.js-ben - Manuális (2/2)} + \begin{exampleblock}{Preflight request kezelése} +\small +\begin{lstlisting}[language=JavaScript] +app.use((req, res, next) => { + // Preflight request kezelése + if (req.method === 'OPTIONS') { + return res.sendStatus(200); + } + next(); +}); +\end{lstlisting} + \end{exampleblock} + \begin{alertblock}{Figyelem!} + A \texttt{*} wildcard nem biztonságos production környezetben! + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{CORS engedélyezése Express.js-ben - CORS csomag} + \begin{block}{Telepítés} +\small +\begin{lstlisting}[language=bash] +npm install cors +\end{lstlisting} + \end{block} +\end{frame} + +\begin{frame}[fragile]{CORS csomag - Alapértelmezett használat} + \begin{exampleblock}{Minden origin engedélyezése} +\small +\begin{lstlisting}[language=JavaScript] +const express = require('express'); +const cors = require('cors'); +const app = express(); + +// Minden origin engedélyezése +app.use(cors()); + +app.get('/api/data', (req, res) => { + res.json({ message: 'CORS enabled!' }); +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS konfiguráció - Specifikus origin} + \begin{exampleblock}{Csak egy origin engedélyezése} +\small +\begin{lstlisting}[language=JavaScript] +const corsOptions = { + origin: 'https://example.com' +}; + +app.use(cors(corsOptions)); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS konfiguráció - Több origin (1/2)} + \begin{exampleblock}{Engedélyezett origin-ek listája} +\small +\begin{lstlisting}[language=JavaScript] +const allowedOrigins = [ + 'https://example.com', + 'https://app.example.com', + 'http://localhost:3000' +]; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS konfiguráció - Több origin (2/3)} + \begin{exampleblock}{Dinamikus origin validáció} +\small +\begin{lstlisting}[language=JavaScript] +const corsOptions = { + origin: (origin, callback) => { + if (!origin || allowedOrigins.includes(origin)) { + callback(null, true); + } else { + callback(new Error('Not allowed by CORS')); + } + } +}; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS konfiguráció - Több origin (3/3)} + \begin{exampleblock}{CORS middleware beállítása} +\small +\begin{lstlisting}[language=JavaScript] +// Korábban definiált corsOptions használata +app.use(cors(corsOptions)); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS konfiguráció - Credentials} + \begin{block}{Mi az a credentials?} + \begin{itemize} + \item Cookie-k, HTTP authentication, client-side SSL certificates + \item Alapértelmezés szerint a böngésző nem küldi el ezeket cross-origin kéréseknél + \end{itemize} + \end{block} + + \begin{exampleblock}{Szerver oldal - Express} +\small +\begin{lstlisting}[language=JavaScript] +const corsOptions = { + origin: 'https://example.com', + credentials: true // Cookie-k engedélyezése +}; + +app.use(cors(corsOptions)); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS Credentials - Kliens oldal} + \begin{exampleblock}{Kliens oldal - Fetch API} +\small +\begin{lstlisting}[language=JavaScript] +fetch('https://api.example.com/data', { + credentials: 'include' // Cookie-k küldése +}) + .then(response => response.json()) + .then(data => console.log(data)); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS konfiguráció - További opciók (1/2)} + \begin{exampleblock}{Részletes konfiguráció} +\small +\begin{lstlisting}[language=JavaScript] +const corsOptions = { + origin: 'https://example.com', + methods: ['GET', 'POST', 'PUT', 'DELETE'], + allowedHeaders: ['Content-Type', 'Authorization'], + exposedHeaders: ['X-Total-Count'], + credentials: true +}; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS konfiguráció - További opciók (2/2)} + \begin{exampleblock}{Cache és preflight beállítások} +\small +\begin{lstlisting}[language=JavaScript] +const corsOptions = { + // ...előző beállítások + maxAge: 3600, // Preflight cache 1 órára + preflightContinue: false, + optionsSuccessStatus: 204 +}; + +app.use(cors(corsOptions)); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS-specifikus route-okhoz (1/3)} + \begin{exampleblock}{Public endpoint CORS-szal} +\small +\begin{lstlisting}[language=JavaScript] +const express = require('express'); +const cors = require('cors'); +const app = express(); + +// Csak public API-hoz engedélyezve +app.get('/api/public', cors(), (req, res) => { + res.json({ message: 'Public data' }); +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS-specifikus route-okhoz (2/3)} + \begin{exampleblock}{Protected endpoint CORS nélkül} +\small +\begin{lstlisting}[language=JavaScript] +// Protected endpoint CORS nélkül +app.get('/api/private', (req, res) => { + res.json({ message: 'Private data' }); +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS-specifikus route-okhoz (3/3)} + \begin{exampleblock}{Specifikus CORS konfig egy route-hoz} +\small +\begin{lstlisting}[language=JavaScript] +const corsOptionsRestricted = { + origin: 'https://trusted.example.com' +}; + +app.post('/api/sensitive', + cors(corsOptionsRestricted), + (req, res) => { + res.json({ message: 'Sensitive operation' }); + } +); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS hibakezelés (1/2)} + \begin{exampleblock}{Engedélyezett origin-ek} +\small +\begin{lstlisting}[language=JavaScript] +const allowedOrigins = [ + 'https://example.com', + 'http://localhost:3000' +]; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS hibakezelés (2/3)} + \begin{exampleblock}{Dinamikus origin ellenőrzés hibakezeléssel} +\small +\begin{lstlisting}[language=JavaScript] +const corsOptions = { + origin: (origin, callback) => { + if (!origin) return callback(null, true); + if (allowedOrigins.includes(origin)) { + callback(null, true); + } else { + const msg = `Origin ${origin} not allowed`; + callback(new Error(msg), false); + } + }, +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS hibakezelés (3/3)} + \begin{exampleblock}{CORS konfiguráció befejezése} +\small +\begin{lstlisting}[language=JavaScript] + // ...folytatás + credentials: true +}; +app.use(cors(corsOptions)); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Gyakori CORS hibák és megoldások (1/2)} + \begin{alertblock}{1. "No 'Access-Control-Allow-Origin' header"} + \textbf{Ok:} A szerver nem küldi vissza a CORS headereket\\ + \textbf{Megoldás:} CORS middleware beállítása + \end{alertblock} + + \begin{alertblock}{2. "Credentials + wildcard origin"} + \textbf{Ok:} \texttt{credentials: true} és \texttt{origin: '*'} együttes használata\\ + \textbf{Megoldás:} Specifikus origin megadása credentials használatakor + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Gyakori CORS hibák és megoldások (2/2)} + \begin{alertblock}{3. "Preflight request failed"} + \textbf{Ok:} OPTIONS kérés nem megfelelő válasza\\ + \textbf{Megoldás:} OPTIONS metódus explicit kezelése, megfelelő headerek + \end{alertblock} + + \begin{alertblock}{4. "Custom headers blokkolva"} + \textbf{Ok:} Custom header nincs az \texttt{allowedHeaders} listában\\ + \textbf{Megoldás:} Header hozzáadása az \texttt{allowedHeaders}-hoz + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Best Practices} + \begin{block}{Biztonsági ajánlások} + \footnotesize + \begin{enumerate} + \item \textbf{Soha ne használj} \texttt{origin: '*'} production környezetben, főleg credentials esetén + \item \textbf{Whitelist megközelítés}: csak konkrét, megbízható origin-ek engedélyezése + \item \textbf{Minimális jogosultságok}: csak a szükséges metódusok és headerek engedélyezése + \item \textbf{Credentials óvatos kezelése}: csak akkor engedélyezd, ha feltétlenül szükséges + \item \textbf{maxAge beállítás}: csökkenti a preflight kérések számát + \end{enumerate} + \end{block} +\end{frame} + +\begin{frame}[fragile]{Production konfiguráció} + \begin{exampleblock}{Ajánlott production konfiguráció} +\small +\begin{lstlisting}[language=JavaScript] +const corsOptions = { + origin: process.env.ALLOWED_ORIGINS?.split(',') + || [], + methods: ['GET', 'POST', 'PUT', 'DELETE'], + allowedHeaders: ['Content-Type', 'Authorization'], + credentials: true, + maxAge: 86400 // 24 óra +}; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS proxy fejlesztési környezetben (1/2)} + \begin{block}{Development server proxy} + Frontend fejlesztési szerverekben (pl. Vite, Create React App) konfigurálható proxy + \end{block} + + \begin{exampleblock}{Vite proxy alapkonfiguráció (vite.config.js)} +\small +\begin{lstlisting}[language=JavaScript] +export default { + server: { + proxy: { + '/api': { + target: 'http://localhost:5000', + changeOrigin: true + } + } + } +}; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{CORS proxy fejlesztési környezetben (2/2)} + \begin{exampleblock}{Rewrite funkció hozzáadása} +\small +\begin{lstlisting}[language=JavaScript] +// Proxy konfigurációban: +proxy: { + '/api': { + rewrite: (path) => + path.replace(/^\/api/, '') + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}{Proxy működése} + \begin{itemize} + \item Kliens \texttt{http://localhost:3000} $\rightarrow$ \texttt{/api/users} + \item Proxy továbbítja \texttt{http://localhost:5000} $\rightarrow$ \texttt{/users} + \item Nincs CORS probléma, mert a böngésző szempontjából azonos origin + \item Csak development használatra, production esetén backend CORS konfiguráció szükséges + \end{itemize} +\end{frame} + +\begin{frame}{Összefoglalás} + \begin{block}{CORS lényege} + \begin{itemize} + \item Biztonsági mechanizmus a cross-origin kérések szabályozására + \item Same-Origin Policy kiegészítése, nem helyettesítése + \item Preflight requests komplex kérések előtt + \item Explicit szerver-oldali engedélyezés szükséges + \end{itemize} + \end{block} + + \begin{block}{Implementáció} + \begin{itemize} + \item Express.js-ben: \texttt{cors} middleware csomag + \item Konfiguráció: origin, methods, headers, credentials + \item Fejlesztés: proxy használata, production: explicit whitelist + \item Biztonsági szempontok elsődlegesek! + \end{itemize} + \end{block} +\end{frame} + diff --git a/Backend_ppt/dic_cors_emailsending/dependency_injection.tex b/Backend_ppt/dic_cors_emailsending/dependency_injection.tex new file mode 100644 index 0000000..2bb58dd --- /dev/null +++ b/Backend_ppt/dic_cors_emailsending/dependency_injection.tex @@ -0,0 +1,816 @@ +\section{Dependency Injection} + +\begin{frame}{Mi az a Dependency Injection (DI)?} + \begin{block}{Definíció} + A \textbf{Dependency Injection} egy tervezési minta, ahol az objektumok nem maguk hozzák létre a függőségeiket, hanem kívülről kapják meg azokat. + \end{block} +\end{frame} + +\begin{frame}{DI előnyei és hátrányai} + \begin{columns}[T] + \begin{column}{0.48\textwidth} + \begin{alertblock}{Nélküle (rossz)} + \begin{itemize} + \item Szoros kapcsolás (tight coupling) + \item Nehéz tesztelni + \item Nehéz karbantartani + \item Függőségek rejtettek + \end{itemize} + \end{alertblock} + \end{column} + \begin{column}{0.48\textwidth} + \begin{exampleblock}{Vele (jó)} + \begin{itemize} + \item Laza kapcsolás (loose coupling) + \item Könnyű tesztelni + \item Könnyen karbantartható + \item Függőségek explicit módon láthatók + \end{itemize} + \end{exampleblock} + \end{column} + \end{columns} +\end{frame} + +\begin{frame}[fragile]{Probléma DI nélkül (1/3)} + \begin{alertblock}{Szoros kapcsolás példa - Osztály} +\small +\begin{lstlisting}[language=JavaScript] +class UserService { + constructor() { + // A UserService közvetlenül létrehozza + // a függőségeit + this.database = new MySQLDatabase(); + this.emailService = new EmailService(); + this.logger = new Logger(); + } +} +\end{lstlisting} + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Probléma DI nélkül (2/3)} + \begin{alertblock}{Használat} +\small +\begin{lstlisting}[language=JavaScript] +const userService = new UserService(); + +async createUser(userData) { + this.logger.log('Creating user...'); + const user = await this.database.save(userData); + await this.emailService.sendWelcomeEmail( + user.email + ); + return user; +} +\end{lstlisting} + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Probléma DI nélkül (3/3)} + \end{alertblock} + \begin{alertblock}{Problémák} + \begin{itemize} + \item Nem lehet könnyen PostgreSQL-re váltani + \item Teszteléskor valódi email-t küldene + \item Nem lehet mock objektumokat használni + \end{itemize} + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Megoldás Dependency Injection-nel (1/3)} + \begin{exampleblock}{Laza kapcsolás DI-val - Konstruktor} +\small +\begin{lstlisting}[language=JavaScript] +class UserService { + constructor(database, emailService, logger) { + // Függőségek kívülről érkeznek + this.database = database; + this.emailService = emailService; + this.logger = logger; + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Megoldás Dependency Injection-nel (2/3)} + \begin{exampleblock}{createUser metódus} +\small +\begin{lstlisting}[language=JavaScript] + async createUser(userData) { + this.logger.log('Creating user...'); + const user = await this.database.save(userData); + await this.emailService.sendWelcomeEmail( + user.email); + return user; + } +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Megoldás Dependency Injection-nel (3/3)} + \begin{exampleblock}{Használat - függőségek kívülről} +\small +\begin{lstlisting}[language=JavaScript] +const database = new MySQLDatabase(); +const emailService = new EmailService(); +const logger = new Logger(); + +const userService = new UserService( + database, + emailService, + logger +); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}{DI előnyei} + \begin{block}{Miért jó a Dependency Injection?} + \footnotesize + \begin{enumerate} + \item \textbf{Tesztelhetőség} + \begin{itemize} + \item Mock és stub objektumok használata + \item Izolált unit tesztek + \end{itemize} + \item \textbf{Rugalmasság} + \begin{itemize} + \item Könnyen cserélhető implementációk + \item Különböző konfigurációk más környezetekben + \end{itemize} + \item \textbf{Karbantarthatóság} + \begin{itemize} + \item Egyértelmű függőségek + \item Single Responsibility Principle + \end{itemize} + \item \textbf{Újrafelhasználhatóság} + \begin{itemize} + \item Komponensek függetlenek + \item Könnyű más projektekben használni + \end{itemize} + \end{enumerate} + \end{block} +\end{frame} + +\begin{frame}[fragile]{DI típusok - Constructor Injection (1/2)} + \begin{block}{Constructor Injection (Ajánlott)} + Függőségek átadása a konstruktorban + \end{block} + + \begin{exampleblock}{Osztály konstruktor} +\small +\begin{lstlisting}[language=JavaScript] +class OrderService { + constructor(paymentGateway, inventoryService) { + this.paymentGateway = paymentGateway; + this.inventoryService = inventoryService; + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI típusok - Constructor Injection (2/2)} + \begin{exampleblock}{processOrder metódus} +\small +\begin{lstlisting}[language=JavaScript] + async processOrder(order) { + await this.inventoryService.reserveItems( + order.items); + await this.paymentGateway.charge(order.total); + return { success: true, orderId: order.id }; + } +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Constructor Injection - Használat} + \begin{exampleblock}{Dependency injection} +\small +\begin{lstlisting}[language=JavaScript] +const paymentGateway = new StripeGateway(); +const inventoryService = new InventoryService(); + +const orderService = new OrderService( + paymentGateway, + inventoryService +); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI típusok - Setter Injection (1/3)} + \begin{block}{Setter Injection} + Függőségek beállítása setter metódusokon keresztül + \end{block} + + \begin{exampleblock}{Setter metódusok} +\small +\begin{lstlisting}[language=JavaScript] +class ReportService { + setDatabase(database) { + this.database = database; + } + + setEmailService(emailService) { + this.emailService = emailService; + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI típusok - Setter Injection (2/3)} + \begin{exampleblock}{generateAndSendReport metódus} +\small +\begin{lstlisting}[language=JavaScript] + async generateAndSendReport() { + const data = await this.database.fetchData(); + const report = this.createReport(data); + await this.emailService.send(report); + } +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI típusok - Setter Injection (3/3)} + \begin{exampleblock}{Használat} +\small +\begin{lstlisting}[language=JavaScript] +const reportService = new ReportService(); +reportService.setDatabase(new MySQLDatabase()); +reportService.setEmailService(new EmailService()); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}{Setter Injection - Hátrányok} + \begin{alertblock}{Hátrány} + Opcionális függőségek $\rightarrow$ könnyebb hibázni + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{DI típusok - Property Injection (1/2)} + \begin{block}{Property Injection} + Közvetlen property beállítás (ritkábban használt) + \end{block} + + \begin{exampleblock}{Osztály definíció} +\small +\begin{lstlisting}[language=JavaScript] +class NotificationService { + async sendNotification(message) { + if (this.emailSender) { + await this.emailSender.send(message); + } + if (this.smsSender) { + await this.smsSender.send(message); + } + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI típusok - Property Injection (2/2)} + \begin{exampleblock}{Használat} +\small +\begin{lstlisting}[language=JavaScript] +const notificationService = + new NotificationService(); + +notificationService.emailSender = + new EmailSender(); +notificationService.smsSender = + new SMSSender(); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}{Property Injection - Figyelmeztetés} + \begin{alertblock}{Figyelem} + Kevésbé explicit, nehezebb követni a függőségeket + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Dependency Injection TypeScript-tel (1/3)} + \begin{exampleblock}{Interfészek definiálása} +\small +\begin{lstlisting}[language=JavaScript] +interface IDatabase { + save(data: any): Promise; + find(id: string): Promise; +} + +interface ILogger { + log(message: string): void; + error(message: string): void; +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Dependency Injection TypeScript-tel (2/4)} + \begin{exampleblock}{Service osztály konstruktor} +\small +\begin{lstlisting}[language=JavaScript] +class UserService { + constructor( + private database: IDatabase, + private logger: ILogger + ) {} +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Dependency Injection TypeScript-tel (3/4)} + \begin{exampleblock}{createUser metódus} +\small +\begin{lstlisting}[language=JavaScript] + async createUser(userData: any) { + this.logger.log('Creating user'); + return await this.database.save(userData); + } +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Dependency Injection TypeScript-tel (4/4)} + \begin{exampleblock}{Implementációk és használat} +\small +\begin{lstlisting}[language=JavaScript] +const db: IDatabase = new MongoDatabase(); +const logger: ILogger = new ConsoleLogger(); +const service = new UserService(db, logger); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI gyakorlatban - Tesztelés (1/4)} + \begin{exampleblock}{Eredeti service konstruktor} +\small +\begin{lstlisting}[language=JavaScript] +class AuthService { + constructor(database, jwtService) { + this.database = database; + this.jwtService = jwtService; + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI gyakorlatban - Tesztelés (2/4)} + \begin{exampleblock}{login metódus} +\small +\begin{lstlisting}[language=JavaScript] + async login(email, password) { + const user = await this.database.findByEmail( + email); + if (user && user.password === password) { + return this.jwtService.generateToken(user); + } + throw new Error('Invalid credentials'); + } +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI gyakorlatban - Tesztelés (3/4)} + \begin{exampleblock}{Unit teszt - Mock objektumok (1/2)} +\small +\begin{lstlisting}[language=JavaScript] +test('login with valid credentials', async () => { + const mockDatabase = { + findByEmail: jest.fn().mockResolvedValue({ + email: 'test@test.com', + password: 'pass123' + }) + }; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI gyakorlatban - Tesztelés (4/4)} + \begin{exampleblock}{Unit teszt - Mock objektumok (2/2)} +\small +\begin{lstlisting}[language=JavaScript] + const mockJwtService = { + generateToken: jest.fn() + .mockReturnValue('token123') + }; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI gyakorlatban - Tesztelés (5/5)} + \begin{exampleblock}{Unit teszt - Érvényesítés} +\small +\begin{lstlisting}[language=JavaScript] + const authService = new AuthService( + mockDatabase, mockJwtService); + const token = await authService.login( + 'test@test.com', 'pass123'); + + expect(token).toBe('token123'); + expect(mockDatabase.findByEmail) + .toHaveBeenCalledWith('test@test.com'); +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI gyakorlatban - Környezeti különbségek (1/3)} + \begin{exampleblock}{Development környezet} +\small +\begin{lstlisting}[language=JavaScript] +if (process.env.NODE_ENV === 'development') { + const database = new MockDatabase(); + const emailService = new ConsoleEmailService(); + const logger = new VerboseLogger(); + + app.locals.userService = new UserService( + database, emailService, logger + ); +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI gyakorlatban - Környezeti különbségek (2/3)} + \begin{exampleblock}{Production környezet} +\small +\begin{lstlisting}[language=JavaScript] +if (process.env.NODE_ENV === 'production') { + const database = new PostgreSQLDatabase(); + const emailService = new SendGridEmailService(); + const logger = new CloudLogger(); + + app.locals.userService = new UserService( + database, emailService, logger + ); +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI gyakorlatban - Környezeti különbségek (3/3)} + \begin{exampleblock}{Használat route-okban} +\small +\begin{lstlisting}[language=JavaScript] +app.post('/users', async (req, res) => { + const user = await app.locals.userService + .createUser(req.body); + res.json(user); +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Manuális DI - Factory Pattern (1/4)} + \begin{exampleblock}{Factory függvény - Alapok (1/2)} +\small +\begin{lstlisting}[language=JavaScript] +function createServices(config) { + // Alapvető szolgáltatások + const logger = new Logger(config.logLevel); + const database = new Database(config.dbUrl); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Manuális DI - Factory Pattern (2/4)} + \begin{exampleblock}{Factory függvény - Alapok (2/2)} +\small +\begin{lstlisting}[language=JavaScript] + // Összetett szolgáltatások + const userRepository = + new UserRepository(database); + const emailService = + new EmailService(config.emailConfig); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Manuális DI - Factory Pattern (3/5)} + \begin{exampleblock}{Service-ek létrehozása (1/2)} +\small +\begin{lstlisting}[language=JavaScript] + const userService = new UserService( + userRepository, emailService, logger + ); + + const authService = new AuthService( + userRepository, config.jwtSecret, logger + ); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Manuális DI - Factory Pattern (4/5)} + \begin{exampleblock}{Service-ek létrehozása (2/2)} +\small +\begin{lstlisting}[language=JavaScript] + return { + userService, authService, + logger, database + }; +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Manuális DI - Factory Pattern (5/5)} + \begin{exampleblock}{Használat} +\small +\begin{lstlisting}[language=JavaScript] +const config = loadConfig(); +const services = createServices(config); + +export default services; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Service Locator Pattern (1/4)} + \begin{exampleblock}{Service container konstruktor} +\small +\begin{lstlisting}[language=JavaScript] +class ServiceContainer { + constructor() { + this.services = new Map(); + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Service Locator Pattern (2/4)} + \begin{exampleblock}{register és get metódusok} +\small +\begin{lstlisting}[language=JavaScript] + register(name, instance) { + this.services.set(name, instance); + } + + get(name) { + if (!this.services.has(name)) { + throw new Error(`Service ${name} not found`); + } + return this.services.get(name); + } +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Service Locator Pattern (3/4)} + \begin{exampleblock}{Service-ek regisztrálása (1/2)} +\small +\begin{lstlisting}[language=JavaScript] +const container = new ServiceContainer(); + +container.register('database', + new MySQLDatabase()); +container.register('logger', new Logger()); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Service Locator Pattern (4/4)} + \begin{exampleblock}{Service-ek regisztrálása (2/2)} +\small +\begin{lstlisting}[language=JavaScript] +container.register('userService', + new UserService( + container.get('database'), + container.get('logger') + ) +); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Service Locator Pattern (5/5)} + \begin{exampleblock}{Használat} +\small +\begin{lstlisting}[language=JavaScript] +const userService = container.get('userService'); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI az Express middleware-ekben (1/4)} + \begin{exampleblock}{Service-ek létrehozása} +\small +\begin{lstlisting}[language=JavaScript] +const database = new Database(); +const userService = new UserService(database); +const authService = new AuthService(database); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI az Express middleware-ekben (2/6)} + \begin{exampleblock}{Middleware factory - Setup} +\small +\begin{lstlisting}[language=JavaScript] +function createAuthMiddleware(authService) { + return async (req, res, next) => { + const token = req.headers.authorization + ?.split(' ')[1]; + if (!token) { + return res.status(401) + .json({ error: 'No token' }); + } +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI az Express middleware-ekben (3/6)} + \begin{exampleblock}{Middleware factory - Verifikáció} +\small +\begin{lstlisting}[language=JavaScript] + try { + req.user = await authService.verifyToken( + token); + next(); + } catch (error) { + res.status(401) + .json({ error: 'Invalid token' }); + } + }; +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI az Express middleware-ekben (4/6)} + \begin{exampleblock}{Route handler factory} +\small +\begin{lstlisting}[language=JavaScript] +function createUserController(userService) { + return { + async createUser(req, res) { + const user = await userService.createUser( + req.body); + res.json(user); + } + }; +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI az Express middleware-ekben (5/6)} + \begin{exampleblock}{Setup (1/2)} +\small +\begin{lstlisting}[language=JavaScript] +const authMiddleware = + createAuthMiddleware(authService); +const userController = + createUserController(userService); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI az Express middleware-ekben (6/6)} + \begin{exampleblock}{Setup (2/2)} +\small +\begin{lstlisting}[language=JavaScript] +app.post('/users', + authMiddleware, + userController.createUser +); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}{SOLID elvek és DI} + \begin{block}{DI kapcsolata a SOLID elvekkel} + \begin{description} + \item[S - Single Responsibility] Osztályok egy dolgot csinálnak, nem hozzák létre saját függőségeiket + \item[O - Open/Closed] Új implementációk hozzáadhatók a meglévő kód módosítása nélkül + \item[L - Liskov Substitution] Interfészek lehetővé teszik a helyettesítést + \item[I - Interface Segregation] Kis, specifikus interfészek használata + \item[D - Dependency Inversion] \textbf{Függőség absztrakciókra, nem konkrét implementációkra} + \end{description} + \end{block} + + \begin{alertblock}{Dependency Inversion Principle} + \begin{itemize} + \item High-level modulok nem függnek low-level moduloktól + \item Mindkettő absztrakcióktól (interfészektől) függ + \item DI ezt a célt szolgálja + \end{itemize} + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Best Practices} + \begin{block}{Ajánlások} + \footnotesize + \begin{enumerate} + \item \textbf{Constructor Injection előnyben részesítése} + \begin{itemize} + \item Kötelező függőségek explicit módon láthatók + \item Immutábilis objektumok + \end{itemize} + \item \textbf{Interfészekre programozás} + \begin{itemize} + \item TypeScript interfészek használata + \item Laza kapcsolás + \end{itemize} + \item \textbf{Ne használj Service Locator az üzleti logikában} + \begin{itemize} + \item Rejtett függőségek + \item Csak konfigurációs szinten + \end{itemize} + \item \textbf{Kerüld a túl sok függőséget} + \begin{itemize} + \item Ha sok függőség van, valószínűleg túl sok felelőssége van az osztálynak + \item Refaktorálás szükséges + \end{itemize} + \end{enumerate} + \end{block} +\end{frame} + +\begin{frame}[fragile]{Antipattern - Service Locator visszaélés (1/3)} + \begin{alertblock}{Rossz gyakorlat - Konstruktor} +\small +\begin{lstlisting}[language=JavaScript] +class UserService { + constructor(container) { + // Teljes container injektálása + this.container = container; + } +} +\end{lstlisting} + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Antipattern - Service Locator visszaélés (2/3)} + \begin{alertblock}{Rossz gyakorlat - createUser metódus} +\small +\begin{lstlisting}[language=JavaScript] + async createUser(userData) { + // Függőségek rejtettek! + const db = this.container.get('database'); + const email = this.container.get('emailService'); + const logger = this.container.get('logger'); + logger.log('Creating user'); + const user = await db.save(userData); + await email.sendWelcome(user.email); + return user; + } +\end{lstlisting} + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Antipattern - Service Locator visszaélés (3/3)} + + \begin{exampleblock}{Jó gyakorlat} +\small +\begin{lstlisting}[language=JavaScript] +class UserService { + constructor(database, emailService, logger) { + this.database = database; + this.emailService = emailService; + this.logger = logger; + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}{Összefoglalás (1/2)} + \begin{block}{Dependency Injection lényege} + \begin{itemize} + \item Függőségek kívülről történő átadása + \item Laza kapcsolás objektumok között + \item Tesztelhetőség növelése + \item SOLID elvek támogatása + \end{itemize} + \end{block} + + \begin{block}{Implementációs módszerek} + \begin{itemize} + \item Constructor Injection (preferált) + \item Setter Injection (opcionális függőségekhez) + \item Manuális DI factory pattern-nel + \item Service Container (konfigurációs szinten) + \end{itemize} + \end{block} +\end{frame} + +\begin{frame}{Összefoglalás (2/2)} + \begin{block}{Előnyök} + \begin{itemize} + \item Könnyű tesztelés mock objektumokkal + \item Rugalmas, cserélhető implementációk + \item Tisztább, karbantarthatóbb kód + \item Explicit függőségek + \end{itemize} + \end{block} +\end{frame} + diff --git a/Backend_ppt/dic_cors_emailsending/dic.tex b/Backend_ppt/dic_cors_emailsending/dic.tex new file mode 100644 index 0000000..7ea690c --- /dev/null +++ b/Backend_ppt/dic_cors_emailsending/dic.tex @@ -0,0 +1,1170 @@ +\section{DIC} + +\begin{frame}{Mi az a Dependency Injection Container?} + \begin{block}{DI Container (DIC) / IoC Container} + \begin{itemize} + \item Automatizálja a dependency injection-t + \item Központi hely a függőségek kezelésére + \item Automatikus dependency resolution + \item Lifecycle management (singleton, transient, scoped) + \item Inversion of Control (IoC) megvalósítása + \end{itemize} + \end{block} + + \begin{block}{Miért használjunk DI Container-t?} + \begin{itemize} + \item \textbf{Manuális DI problémái:} sok függőség esetén bonyolult az instanciálás + \item \textbf{Automatizálás:} a container automatikusan feloldja a függőségeket + \item \textbf{Konfiguráció:} egy helyen konfiguráljuk az összes service-t + \item \textbf{Tesztelhetőség:} könnyű mock service-eket injektálni + \end{itemize} + \end{block} +\end{frame} + +\begin{frame}[fragile]{Probléma manuális DI-val (1/3)} + \begin{alertblock}{Dependency hell - Alapvető szolgáltatások} +\small +\begin{lstlisting}[language=JavaScript] +const database = new Database(config.db); +const logger = new Logger(config.log); +const cache = new RedisCache(config.redis); + +const userRepository = + new UserRepository(database, logger); +const productRepository = + new ProductRepository(database, logger); +const orderRepository = + new OrderRepository(database, logger); +\end{lstlisting} + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Probléma manuális DI-val (2/3)} + \begin{alertblock}{Dependency hell - Service-ek} +\small +\begin{lstlisting}[language=JavaScript] +const emailService = + new EmailService(config.email, logger); +const paymentService = + new PaymentService(config.payment, logger); + +const userService = new UserService( + userRepository, emailService, logger); +const productService = new ProductService( + productRepository, cache, logger); +\end{lstlisting} + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Probléma manuális DI-val (3/3)} + \begin{alertblock}{Dependency hell - Komplex függőségek} +\small +\begin{lstlisting}[language=JavaScript] +const orderService = new OrderService( + orderRepository, + productService, + userService, + paymentService, + emailService, + logger +); +// És még tovább... +\end{lstlisting} + \end{alertblock} + \begin{alertblock}{Problémák} + Bonyolult, hibára hajlamos, nehéz karbantartani, függőségi sorrend fontos + \end{alertblock} +\end{frame} + +\begin{frame}{DI Container életciklusok} + \begin{block}{Service Lifetimes} + \begin{description} + \item[Transient] Minden kérésnél új instance + \begin{itemize} + \item Lightweight, stateless service-ek + \item Példa: validators, utilities + \end{itemize} + \item[Singleton] Egy instance az egész alkalmazásra + \begin{itemize} + \item Shared state, resource-intenzív + \item Példa: database connection pool, logger, config + \end{itemize} + \item[Scoped] Egy instance per request/scope + \begin{itemize} + \item HTTP request cycle alatt ugyanaz + \item Példa: database transaction, user context + \end{itemize} + \end{description} + \end{block} +\end{frame} + +\begin{frame}[fragile]{Népszerű DI Container-ek Node.js-hez} + \begin{block}{JavaScript/TypeScript DI library-k} + \begin{description} + \item[InversifyJS] TypeScript-friendly, decorator-alapú + \item[Awilix] Lightweight, flexible, Node.js-specifikus + \item[TSyringe] Microsoft TypeScript DI container + \item[TypeDI] Decorator-alapú, TypeScript support + \item[BottleJS] Egyszerű DI container pure JS-hez + \end{description} + \end{block} + + \begin{exampleblock}{Választás} + TypeScript projekt: \textbf{InversifyJS} vagy \textbf{TSyringe}\\ + JavaScript projekt: \textbf{Awilix}\\ + Egyszerű projektek: manuális DI vagy \textbf{BottleJS} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix - Lightweight DI Container} + \begin{block}{Telepítés} +\small +\begin{lstlisting}[language=bash] +npm install awilix +\end{lstlisting} + \end{block} +\end{frame} + +\begin{frame}[fragile]{Awilix - Alapvető használat (1/2)} + \begin{exampleblock}{Container létrehozása és singleton-ok} +\small +\begin{lstlisting}[language=JavaScript] +const { createContainer, asClass, + asFunction, asValue } = require('awilix'); + +const container = createContainer(); + +container.register({ + // Singleton - egy instance + database: asClass(Database).singleton(), + logger: asClass(Logger).singleton() +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix - Alapvető használat (2/3)} + \begin{exampleblock}{Transient és Scoped} +\small +\begin{lstlisting}[language=JavaScript] +container.register({ + // Transient - minden resolve-nál új instance + userService: asClass(UserService).transient(), + + // Scoped - scope-on belül ugyanaz + orderService: asClass(OrderService).scoped() +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix - Alapvető használat (3/3)} + \begin{exampleblock}{Value injection} +\small +\begin{lstlisting}[language=JavaScript] +container.register({ + // Value injection - konstans értékek + config: asValue({ + dbUrl: process.env.DATABASE_URL, + port: process.env.PORT || 3000 + }) +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix - Service-ek használata (1/4)} + \begin{exampleblock}{Osztály definíciók - Repository rész 1} +\small +\begin{lstlisting}[language=JavaScript] +class UserRepository { + constructor({ database, logger }) { + this.database = database; + this.logger = logger; + } +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix - Service-ek használata (2/4)} + \begin{exampleblock}{Osztály definíciók - Repository rész 2} +\small +\begin{lstlisting}[language=JavaScript] + async findById(id) { + this.logger.log(`Finding user ${id}`); + return await this.database.query( + 'SELECT * FROM users WHERE id = ?', [id] + ); + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix - Service-ek használata (3/5)} + \begin{exampleblock}{Osztály definíciók - Service rész 1} +\small +\begin{lstlisting}[language=JavaScript] +class UserService { + constructor({ userRepository, emailService, + logger }) { + this.userRepository = userRepository; + this.emailService = emailService; + this.logger = logger; + } +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix - Service-ek használata (4/5)} + \begin{exampleblock}{Osztály definíciók - Service rész 2} +\small +\begin{lstlisting}[language=JavaScript] + async createUser(userData) { + this.logger.log('Creating user'); + const user = await this.userRepository + .create(userData); + await this.emailService.sendWelcomeEmail( + user); + return user; + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix - Service-ek használata (5/5)} + \begin{exampleblock}{Dependency resolution} +\small +\begin{lstlisting}[language=JavaScript] +// Awilix automatikusan feloldja +// a függőségeket +const userService = + container.resolve('userService'); +// userRepository, emailService, logger +// automatikusan injektálva +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix - Auto-loading (1/3)} + \begin{exampleblock}{Setup} +\small +\begin{lstlisting}[language=JavaScript] +const { createContainer, asClass } = + require('awilix'); + +const container = createContainer(); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix - Auto-loading (2/4)} + \begin{exampleblock}{Automatikus service regisztráció} +\small +\begin{lstlisting}[language=JavaScript] +container.loadModules([ + 'services/**/*.js', + 'repositories/**/*.js', + 'controllers/**/*.js' +], { + formatName: 'camelCase', +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix - Auto-loading (3/4)} + \begin{exampleblock}{Resolver options} +\small +\begin{lstlisting}[language=JavaScript] + resolverOptions: { + lifetime: 'SINGLETON', + register: asClass + } +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix - Auto-loading (3/4)} + \begin{exampleblock}{Egyedi konfiguráció} +\small +\begin{lstlisting}[language=JavaScript] +container.loadModules([ + ['services/**/*.js', + { register: asClass, lifetime: 'SINGLETON' }], + ['repositories/**/*.js', + { register: asClass, lifetime: 'SCOPED' }], + ['utils/**/*.js', + { register: asFunction, lifetime: 'SINGLETON' }] +]); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix - Auto-loading (4/4)} + \begin{alertblock}{Fájl struktúra} + \texttt{services/userService.js} $\rightarrow$ \texttt{userService}\\ + \texttt{repositories/userRepository.js} $\rightarrow$ \texttt{userRepository} + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Awilix Express integráció (1/2)} + \begin{exampleblock}{Setup és container} +\small +\begin{lstlisting}[language=JavaScript] +const express = require('express'); +const { createContainer, asClass } = + require('awilix'); +const { loadControllers, scopePerRequest } = + require('awilix-express'); + +const app = express(); +const container = createContainer(); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix Express integráció (2/2)} + \begin{exampleblock}{Service regisztráció} +\small +\begin{lstlisting}[language=JavaScript] +container.register({ + userService: asClass(UserService).singleton(), + orderService: asClass(OrderService) + .singleton() +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix Express - Middleware} + \begin{exampleblock}{Scope és controller betöltés} +\small +\begin{lstlisting}[language=JavaScript] +// Scope per request middleware +app.use(scopePerRequest(container)); + +// Controller-ek automatikus betöltése +app.use(loadControllers('controllers/*.js', + { cwd: __dirname })); + +app.listen(3000); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix Controller példa (1/4)} + \begin{exampleblock}{controllers/userController.js - Import} +\small +\begin{lstlisting}[language=JavaScript] +const { createController } = + require('awilix-express'); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix Controller példa (2/4)} + \begin{exampleblock}{Osztály definíció} +\small +\begin{lstlisting}[language=JavaScript] +class UserController { + constructor({ userService, logger }) { + // Awilix automatikusan injektálja + this.userService = userService; + this.logger = logger; + } +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix Controller példa (3/7)} + \begin{exampleblock}{GetUser metódus - try block} +\small +\begin{lstlisting}[language=JavaScript] + async getUser(req, res) { + try { + const user = await this.userService + .getUserById(req.params.id); + res.json(user); + } catch (error) { + this.logger.error(error); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix Controller példa (4/7)} + \begin{exampleblock}{GetUser metódus - error handling} +\small +\begin{lstlisting}[language=JavaScript] + res.status(500).json({ + error: 'Internal server error' }); + } + } +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix Controller példa (5/7)} + \begin{exampleblock}{CreateUser metódus - rész 1} +\small +\begin{lstlisting}[language=JavaScript] + async createUser(req, res) { + try { + const user = await this.userService + .createUser(req.body); + res.status(201).json(user); + } catch (error) { + this.logger.error(error); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix Controller példa (6/7)} + \begin{exampleblock}{CreateUser metódus - rész 2} +\small +\begin{lstlisting}[language=JavaScript] + res.status(500).json({ + error: 'Internal server error' }); + } + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Awilix Controller példa (7/7)} + \begin{exampleblock}{Route mapping} +\small +\begin{lstlisting}[language=JavaScript] +module.exports = createController(UserController) + .prefix('/users') + .get('/:id', 'getUser') + .post('/', 'createUser'); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - TypeScript DI Container} + \begin{block}{Telepítés} +\small +\begin{lstlisting}[language=bash] +npm install inversify reflect-metadata +\end{lstlisting} + \end{block} + + \begin{exampleblock}{tsconfig.json beállítások} +\small +\begin{lstlisting}[language=JSON] +{ + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Alapvető setup} + \begin{exampleblock}{Setup} +\small +\begin{lstlisting}[language=JavaScript] +import 'reflect-metadata'; +import { Container } from 'inversify'; + +const container = new Container(); + +// Az app belépési pontján +// import 'reflect-metadata' kell lennie legelső sorban +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Decorator-alapú DI (1/4)} + \begin{exampleblock}{Symbol-ok definíciója} +\small +\begin{lstlisting}[language=JavaScript] +import { injectable, inject } from 'inversify'; +import 'reflect-metadata'; + +const TYPES = { + Database: Symbol.for('Database'), + Logger: Symbol.for('Logger'), + UserRepository: Symbol.for('UserRepository'), + UserService: Symbol.for('UserService') +}; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Decorator-alapú DI (2/4)} + \begin{exampleblock}{Database osztály} +\small +\begin{lstlisting}[language=JavaScript] +@injectable() +class Database { + query(sql: string) { /* ... */ } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Decorator-alapú DI (2/3)} + \begin{exampleblock}{Logger osztály} +\small +\begin{lstlisting}[language=JavaScript] +@injectable() +class Logger { + log(message: string) { + console.log(message); + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Decorator-alapú DI (3/4)} + \begin{exampleblock}{UserRepository constructor} +\small +\begin{lstlisting}[language=JavaScript] +@injectable() +class UserRepository { + constructor( + @inject(TYPES.Database) + private database: Database, + @inject(TYPES.Logger) + private logger: Logger + ) {} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Decorator-alapú DI (4/4)} + \begin{exampleblock}{UserRepository metódusok} +\small +\begin{lstlisting}[language=JavaScript] + async findById(id: string) { + this.logger.log(`Finding user ${id}`); + return await this.database.query( + `SELECT * FROM users WHERE id = ${id}`); + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Container konfiguráció (1/3)} + \begin{exampleblock}{Setup} +\small +\begin{lstlisting}[language=JavaScript] +import { Container } from 'inversify'; + +const container = new Container(); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Container konfiguráció (2/3)} + \begin{exampleblock}{Singleton és Transient binding} +\small +\begin{lstlisting}[language=JavaScript] +// Singleton binding +container.bind(TYPES.Database) + .to(Database).inSingletonScope(); +container.bind(TYPES.Logger) + .to(Logger).inSingletonScope(); + +// Transient binding (default) +container.bind( + TYPES.UserRepository).to(UserRepository); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Container konfiguráció (3/4)} + \begin{exampleblock}{Scoped és Constant binding} +\small +\begin{lstlisting}[language=JavaScript] +// Scoped binding (request scope) +container.bind(TYPES.UserService) + .to(UserService).inRequestScope(); + +// Constant value +container.bind(TYPES.Config) + .toConstantValue({ + dbUrl: process.env.DATABASE_URL, + port: 3000 + }); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Container konfiguráció (4/4)} + \begin{exampleblock}{Factory binding} +\small +\begin{lstlisting}[language=JavaScript] +// Factory binding +container.bind(TYPES.EmailService) + .toFactory((context) => { + return () => new EmailService( + context.container.get(TYPES.Config)); + }); + +export { container, TYPES }; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Service használat (1/3)} + \begin{exampleblock}{Service lekérése} +\small +\begin{lstlisting}[language=JavaScript] +import { container, TYPES } + from './container'; + +// Service lekérése +const userService = + container.get(TYPES.UserService); +await userService.createUser({ + name: 'John', email: 'john@example.com' +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Service használat (2/3)} + \begin{exampleblock}{Express route-okban} +\small +\begin{lstlisting}[language=JavaScript] +app.get('/users/:id', async (req, res) => { + const userService = + container.get(TYPES.UserService); + const user = await userService.getUserById( + req.params.id); + res.json(user); +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Service használat (3/4)} + \begin{exampleblock}{Middleware factory} +\small +\begin{lstlisting}[language=JavaScript] +function injectService(type: symbol) { + return (req, res, next) => { + req.service = container.get(type); + next(); + }; +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Service használat (4/4)} + \begin{exampleblock}{Middleware használat} +\small +\begin{lstlisting}[language=JavaScript] +app.get('/users/:id', + injectService(TYPES.UserService), + async (req, res) => { + const user = await req.service.getUserById( + req.params.id); + res.json(user); + } +); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Interface binding (1/3)} + \begin{exampleblock}{Interface definíció} +\small +\begin{lstlisting}[language=JavaScript] +interface ILogger { + log(message: string): void; + error(message: string): void; +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Interface binding (2/4)} + \begin{exampleblock}{ConsoleLogger implementáció} +\small +\begin{lstlisting}[language=JavaScript] +@injectable() +class ConsoleLogger implements ILogger { + log(message: string) { + console.log(message); + } + error(message: string) { + console.error(message); + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Interface binding (3/4)} + \begin{exampleblock}{FileLogger implementáció} +\small +\begin{lstlisting}[language=JavaScript] +@injectable() +class FileLogger implements ILogger { + log(message: string) { + /* write to file */ + } + error(message: string) { + /* write to file */ + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Interface binding (4/5)} + \begin{exampleblock}{Container konfiguráció} +\small +\begin{lstlisting}[language=JavaScript] +const TYPES = { ILogger: Symbol.for('ILogger') }; + +// Development környezet +if (process.env.NODE_ENV === 'development') { + container.bind(TYPES.ILogger) + .to(ConsoleLogger).inSingletonScope(); +} else { + container.bind(TYPES.ILogger) + .to(FileLogger).inSingletonScope(); +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{InversifyJS - Interface binding (5/5)} + \begin{exampleblock}{Használat} +\small +\begin{lstlisting}[language=JavaScript] +@injectable() +class UserService { + constructor(@inject(TYPES.ILogger) + private logger: ILogger) {} +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Container scope és lifecycle (1/2)} + \begin{exampleblock}{Container setup} +\small +\begin{lstlisting}[language=JavaScript] +const { createContainer, asClass } = + require('awilix'); +const { scopePerRequest } = + require('awilix-express'); + +const container = createContainer(); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Container scope és lifecycle (2/2)} + \begin{exampleblock}{Lifetime regisztráció} +\small +\begin{lstlisting}[language=JavaScript] +container.register({ + // Singleton - egy instance az egész app-ra + database: asClass(Database).singleton(), + // Scoped - egy instance per HTTP request + userContext: asClass(UserContext).scoped(), + // Transient - mindig új instance + validator: asClass(Validator).transient() +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Request scope - Használat (1/2)} + \begin{exampleblock}{Middleware setup} +\small +\begin{lstlisting}[language=JavaScript] +app.use(scopePerRequest(container)); + +// Minden request új scope-ot kap +app.get('/users', (req, res) => { + // req.container - scoped container + const userContext = + req.container.resolve('userContext'); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Request scope - Használat (2/2)} + \begin{exampleblock}{Scope életciklus} +\small +\begin{lstlisting}[language=JavaScript] + // Ugyanaz a userContext instance + // a request során +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Testing DI Container-rel (1/5)} + \begin{exampleblock}{Import és describe} +\small +\begin{lstlisting}[language=JavaScript] +const { createContainer, asClass, asValue } = + require('awilix'); + +describe('UserService', () => { + let container; + let userService; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Testing DI Container-rel (2/5)} + \begin{exampleblock}{BeforeEach setup} +\small +\begin{lstlisting}[language=JavaScript] + beforeEach(() => { + container = createContainer(); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Testing DI Container-rel (3/5)} + \begin{exampleblock}{Mock repository} +\small +\begin{lstlisting}[language=JavaScript] + const mockRepository = { + findById: jest.fn().mockResolvedValue( + { id: 1, name: 'John' }), + create: jest.fn().mockResolvedValue( + { id: 2, name: 'Jane' }) + }; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Testing DI Container-rel (4/5)} + \begin{exampleblock}{Mock email és logger} +\small +\begin{lstlisting}[language=JavaScript] + const mockEmailService = { + sendWelcomeEmail: jest.fn() + .mockResolvedValue(true) + }; + const mockLogger = { + log: jest.fn(), error: jest.fn() + }; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Testing DI Container-rel (5/6)} + \begin{exampleblock}{Container regisztráció} +\small +\begin{lstlisting}[language=JavaScript] + container.register({ + userRepository: asValue(mockRepository), + emailService: asValue(mockEmailService), + logger: asValue(mockLogger), + userService: asClass(UserService) + }); + userService = container.resolve('userService'); + }); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Testing DI Container-rel (6/6)} + \begin{exampleblock}{Teszt} +\small +\begin{lstlisting}[language=JavaScript] + test('createUser sends welcome email', + async () => { + await userService.createUser( + { name: 'Jane', email: 'jane@ex.com' }); + expect(container.resolve('emailService') + .sendWelcomeEmail).toHaveBeenCalled(); + }); +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI Container projekt struktúra (1/3)} + \begin{exampleblock}{Config és services} +\small +\begin{lstlisting}[language=bash] +src/ + ├── config/ + │ ├── database.js + │ └── email.js + ├── services/ + │ ├── userService.js + │ ├── emailService.js + │ └── authService.js +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI Container projekt struktúra (2/3)} + \begin{exampleblock}{Repositories és controllers} +\small +\begin{lstlisting}[language=bash] + ├── repositories/ + │ ├── userRepository.js + │ └── orderRepository.js + ├── controllers/ + │ ├── userController.js + │ └── orderController.js + ├── container.js + └── app.js +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI Container projekt struktúra (3/4)} + \begin{exampleblock}{container.js - Setup} +\small +\begin{lstlisting}[language=JavaScript] +const { createContainer, asClass, asValue } = + require('awilix'); +const config = require('./config'); + +function setupContainer() { + const container = createContainer(); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI Container projekt struktúra (4/5)} + \begin{exampleblock}{container.js - LoadModules} +\small +\begin{lstlisting}[language=JavaScript] + container.loadModules([ + 'services/**/*.js', + 'repositories/**/*.js' + ], { + formatName: 'camelCase', + resolverOptions: { + lifetime: 'SINGLETON', + register: asClass + } + }); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{DI Container projekt struktúra (5/5)} + \begin{exampleblock}{container.js - Export} +\small +\begin{lstlisting}[language=JavaScript] + container.register({ config: asValue(config) }); + return container; +} +module.exports = setupContainer; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Advanced: Multi-tenancy DI (1/3)} + \begin{exampleblock}{Setup} +\small +\begin{lstlisting}[language=JavaScript] +const { createContainer, asClass } = + require('awilix'); + +function tenantMiddleware(req, res, next) { + const tenantId = req.headers['x-tenant-id']; + + // Tenant-specifikus scope + req.tenantScope = req.container.createScope(); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Advanced: Multi-tenancy DI (2/3)} + \begin{exampleblock}{Tenant regisztráció} +\small +\begin{lstlisting}[language=JavaScript] + req.tenantScope.register({ + tenantId: asValue(tenantId), + tenantDatabase: asClass(TenantDatabase) + .scoped(), + tenantConfig: asClass(TenantConfig).scoped() + }); + next(); +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Advanced: Multi-tenancy DI (3/4)} + \begin{exampleblock}{Middleware setup} +\small +\begin{lstlisting}[language=JavaScript] +app.use(scopePerRequest(container)); +app.use(tenantMiddleware); + +app.get('/data', async (req, res) => { + // Tenant-specifikus database + const db = req.tenantScope.resolve( + 'tenantDatabase'); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Advanced: Multi-tenancy DI (4/4)} + \begin{exampleblock}{Query használat} +\small +\begin{lstlisting}[language=JavaScript] + const data = await db.query( + 'SELECT * FROM tenant_data'); + res.json(data); +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}{DI Container Best Practices} + \begin{block}{Ajánlások} + \footnotesize + \begin{enumerate} + \item \textbf{Egy container per alkalmazás} - singleton pattern + \item \textbf{Centralizált konfiguráció} - egy helyen regisztráld a service-eket + \item \textbf{Helyes lifetime választás} + \begin{itemize} + \item Singleton: stateless, expensive resources + \item Scoped: request-specific state + \item Transient: lightweight, stateless + \end{itemize} + \item \textbf{Ne add tovább a container-t} - ne injektáld service-ekbe (service locator antipattern) + \item \textbf{Explicit függőségek} - konstruktor injection + \item \textbf{Interface-alapú programozás} - TypeScript interface-ek + \item \textbf{Testing} - mock service-ek könnyű injektálása + \end{enumerate} + \end{block} +\end{frame} + +\begin{frame}[fragile]{Antipattern - Service Locator (1/3)} + \begin{alertblock}{Rossz: Container injection - Constructor} +\small +\begin{lstlisting}[language=JavaScript] +class UserService { + constructor({ container }) { + this.container = container; // NE! + } +\end{lstlisting} + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Antipattern - Service Locator (2/3)} + \begin{alertblock}{Rossz: Rejtett függőségek} +\small +\begin{lstlisting}[language=JavaScript] + async createUser(userData) { + // Rejtett függőségek + const repo = this.container.resolve( + 'userRepository'); + const email = this.container.resolve( + 'emailService'); + // ... + } +} +\end{lstlisting} + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Antipattern - Service Locator (3/4)} + \begin{exampleblock}{Jó: Explicit constructor} +\small +\begin{lstlisting}[language=JavaScript] +class UserService { + constructor({ userRepository, emailService, + logger }) { + this.userRepository = userRepository; + this.emailService = emailService; + this.logger = logger; + } +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Antipattern - Service Locator (4/4)} + \begin{exampleblock}{Jó: Explicit függőségek} +\small +\begin{lstlisting}[language=JavaScript] + async createUser(userData) { + // Explicit függőségek + const user = await this.userRepository + .create(userData); + await this.emailService.sendWelcome(user); + return user; + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}{Mikor használj DI Container-t?} + \begin{block}{Használd, ha:} + \begin{itemize} + \item Közepes vagy nagy projekt sok függőséggel + \item TypeScript projektek (InversifyJS, TSyringe) + \item Komplex service lifecycle management szükséges + \item Enterprise alkalmazások + \item Microservices architektúra + \end{itemize} + \end{block} + + \begin{block}{Ne használd, ha:} + \begin{itemize} + \item Kis projektek, kevés függőséggel + \item Egyszerű CRUD alkalmazások + \item Learning project - manuális DI jobb a megértéshez + \item Overhead túl nagy a project méretéhez képest + \end{itemize} + \end{block} +\end{frame} + +\begin{frame}{DI Container - Alternatívák} + \begin{alertblock}{Alternatíva} + Kis projektekhez: manuális DI factory pattern-nel vagy egyszerű service registry + \end{alertblock} +\end{frame} + +\begin{frame}{Összefoglalás (1/2)} + \begin{block}{DI Container lényege} + \begin{itemize} + \item Automatizált dependency injection + \item Központi konfiguráció + \item Lifecycle management (singleton, scoped, transient) + \item Inversion of Control (IoC) + \end{itemize} + \end{block} + + \begin{block}{Népszerű library-k} + \begin{itemize} + \item \textbf{Awilix} - Lightweight, JavaScript-friendly + \item \textbf{InversifyJS} - TypeScript, decorator-alapú + \item \textbf{TSyringe} - Microsoft TypeScript DI + \end{itemize} + \end{block} +\end{frame} + +\begin{frame}{Összefoglalás (2/2)} + \begin{block}{Előnyök} + \begin{itemize} + \item Automatikus dependency resolution + \item Könnyű tesztelhetőség + \item Tiszta architektúra + \item Skálázható kódbázis + \end{itemize} + \end{block} +\end{frame} + diff --git a/Backend_ppt/dic_cors_emailsending/dic_cors_eamilsending.pdf b/Backend_ppt/dic_cors_emailsending/dic_cors_eamilsending.pdf new file mode 100644 index 0000000..b89cebc Binary files /dev/null and b/Backend_ppt/dic_cors_emailsending/dic_cors_eamilsending.pdf differ diff --git a/Backend_ppt/dic_cors_emailsending/dic_cors_eamilsending.tex b/Backend_ppt/dic_cors_emailsending/dic_cors_eamilsending.tex new file mode 100644 index 0000000..93215d5 --- /dev/null +++ b/Backend_ppt/dic_cors_emailsending/dic_cors_eamilsending.tex @@ -0,0 +1,23 @@ +\documentclass[usenames,dvipsnames,aspectratio=169]{beamer} +\usepackage{../common/webfejl} + +% Automatikus frame törés engedélyezése túl hosszú tartalomnál +\setbeamertemplate{frametitle continuation}[from second][\insertcontinuationcountroman] + +\title[Backend fejlesztés - CORS, DI, Email]{Webtechnológia és webalkalmazás-fejlesztés} +\subtitle{CORS, Dependency Injection, Email Sending} + +\begin{document} + +\begin{frame}[plain] + \titlepage + \logoalul +\end{frame} + +\input{cors.tex} +\input{dependency_injection.tex} +\input{dic.tex} +\input{email_sending.tex} +\input{email_templating.tex} + +\end{document} \ No newline at end of file diff --git a/Backend_ppt/dic_cors_emailsending/email_sending.tex b/Backend_ppt/dic_cors_emailsending/email_sending.tex new file mode 100644 index 0000000..668f5d5 --- /dev/null +++ b/Backend_ppt/dic_cors_emailsending/email_sending.tex @@ -0,0 +1,1197 @@ +\section{Email Küldés Backend-ből} + +\begin{frame}{Miért kell email küldés a backend-ből?} + \begin{block}{Gyakori felhasználási esetek} + \begin{itemize} + \item \textbf{Regisztráció megerősítés} - Verification email + \item \textbf{Jelszó visszaállítás} - Password reset link + \item \textbf{Értesítések} - Rendelés visszaigazolás, státusz változás + \item \textbf{Marketing emailek} - Newsletter, promóciók + \item \textbf{Rendszer értesítések/ Jelentések} - Hibák, figyelmeztetések - Napi/heti összesítők + \end{itemize} + \end{block} + + \begin{alertblock}{Miért backend-ből?} + \begin{itemize} + \item Biztonság: SMTP hitelesítési adatok nem kerülnek a kliens oldalra + \item Megbízhatóság: szerver oldali retry logika + \item Komplexitás: template renderelés, csatolmányok + \item Skálázhatóság: külön email queue kezelés + \end{itemize} + \end{alertblock} +\end{frame} + +\begin{frame}{Email protokollok} + \begin{block}{SMTP - Simple Mail Transfer Protocol} + \begin{itemize} + \item Email \textbf{küldésére} használt standard protokoll + \item Port: 25 (alap), 587 (TLS), 465 (SSL) + \item Autentikáció: username + password vagy API key + \end{itemize} + \end{block} + + \begin{block}{Egyéb protokollok} + \begin{description} + \item[IMAP] Email fogadás és szinkronizálás (Port 143/993) + \item[POP3] Email letöltés (Port 110/995) + \item[HTTP API] Modern email szolgáltatók (SendGrid, Mailgun) + \end{description} + \end{block} +\end{frame} + +\begin{frame}{Email protokollok - Backend használat} + \begin{exampleblock}{Backend használat} + Backend fejlesztésben jellemzően csak \textbf{SMTP}-t vagy \textbf{HTTP API}-t használunk email küldésre + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Nodemailer - A legnépszerűbb Node.js email könyvtár} + \begin{block}{Telepítés} +\small +\begin{lstlisting}[language=bash] +npm install nodemailer +\end{lstlisting} + \end{block} + + \begin{block}{Jellemzők} + \begin{itemize} + \item SMTP támogatás + \item HTML és plain text emailek + \item Csatolmányok (attachments) + \item Beágyazott képek (inline images) + \item Unicode támogatás + \item Stream támogatás nagyméretű csatolmányokhoz + \item OAuth2 autentikáció + \end{itemize} + \end{block} +\end{frame} + +\begin{frame}[fragile]{Nodemailer alapok - Transporter létrehozása (1/3)} + \begin{exampleblock}{Import és konfiguráció Object} +\small +\begin{lstlisting}[language=JavaScript] +const nodemailer = require('nodemailer'); + +const config = { + host: 'smtp.example.com', + port: 587, + secure: false, + auth: { +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Nodemailer alapok - Transporter létrehozása (2/4)} + \begin{exampleblock}{Auth és zárás} +\small +\begin{lstlisting}[language=JavaScript] + user: 'your-email@example.com', + pass: 'your-password' + } +}; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Nodemailer alapok - Transporter létrehozása (3/4)} + \begin{exampleblock}{Transporter létrehozása} +\small +\begin{lstlisting}[language=JavaScript] +// Transporter létrehozása SMTP-vel +const transporter = nodemailer.createTransport(config); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Nodemailer alapok - Transporter létrehozása (4/4)} + \begin{exampleblock}{Transporter ellenőrzés} +\small +\begin{lstlisting}[language=JavaScript] +// Ellenőrzés +transporter.verify((error, success) => { + if (error) { + console.log('SMTP connection error:', error); + } else { + console.log('Server is ready to send emails'); + } +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Egyszerű email küldés (1/3)} + \begin{exampleblock}{Mail options megadása} +\small +\begin{lstlisting}[language=JavaScript] +const mailOptions = { + from: '"Sender Name" ', + to: 'recipient@example.com', + subject: 'Test Email', + text: 'This is a plain text email message.' +}; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Egyszerű email küldés (2/3)} + \begin{exampleblock}{Email küldés callback-kel} +\small +\begin{lstlisting}[language=JavaScript] +transporter.sendMail(mailOptions, (error, info) => { + if (error) { + console.log('Error sending email:', error); + } else { + console.log('Email sent:', info.messageId); + } +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Egyszerű email küldés (3/3)} + \begin{exampleblock}{Async/await használattal} +\small +\begin{lstlisting}[language=JavaScript] +async function sendEmail() { + try { + const info = await transporter.sendMail(mailOptions); + console.log('Email sent:', info.messageId); + return info; + } catch (error) { + console.error('Error sending email:', error); + throw error; + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{HTML email küldés (1/3)} + \begin{exampleblock}{HTML mail options - Alapadatok} +\small +\begin{lstlisting}[language=JavaScript] +const mailOptions = { + from: '"Company Name" ', + to: 'user@example.com', + subject: 'Welcome to Our Service!', + html: ` +

Welcome!

+

Thank you for registering.

+

Click below to verify your email:

+\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{HTML email küldés (2/4)} + \begin{exampleblock}{HTML mail options - Gomb stílusok} +\small +\begin{lstlisting}[language=JavaScript] + + Verify Email + +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{HTML email küldés (3/4)} + \begin{exampleblock}{HTML mail options - Lezárás} +\small +\begin{lstlisting}[language=JavaScript] +

Best regards,
The Team

+ `, +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{HTML email küldés (4/5)} + \begin{exampleblock}{Plain text alternatíva} +\small +\begin{lstlisting}[language=JavaScript] + text: 'Welcome! Verify your email: \n + https://example.com/verify?token=abc123' +}; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{HTML email küldés (5/5)} + \begin{exampleblock}{Email küldés} +\small +\begin{lstlisting}[language=JavaScript] +await transporter.sendMail(mailOptions); +\end{lstlisting} + \end{exampleblock} + + \begin{alertblock}{Fontos} + Mindig adj meg \texttt{text} alternatívát a \texttt{html} mellett! + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Több címzett és CC/BCC (1/3)} + \begin{exampleblock}{Több címzett kezelése} +\small +\begin{lstlisting}[language=JavaScript] +const mailOptions = { + from: 'sender@example.com', + // Több címzett vesszővel + to: 'user1@example.com, user2@example.com', + // Vagy tömbként + to: ['user1@example.com', 'user2@example.com'], +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Több címzett és CC/BCC (2/3)} + \begin{exampleblock}{CC és BCC} +\small +\begin{lstlisting}[language=JavaScript] + // CC - Carbon Copy (látható másolat) + cc: 'manager@example.com', + // BCC - Blind Carbon Copy (rejtett másolat) + bcc: ['admin@example.com', 'audit@example.com'], +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Több címzett és CC/BCC (3/3)} + \begin{exampleblock}{Email küldés} +\small +\begin{lstlisting}[language=JavaScript] + subject: 'Team Notification', + text: 'This message is sent to multiple \n + recipients' +}; + +await transporter.sendMail(mailOptions); +\end{lstlisting} + \end{exampleblock} + + \begin{alertblock}{Tömeges email küldés} + Sok címzetthez NE egy emailben küld! Használj email service provider-t vagy queue-t! + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Csatolmányok (Attachments) (1/3)} + \begin{exampleblock}{Fájl csatolása - Mail options} +\small +\begin{lstlisting}[language=JavaScript] +const mailOptions = { + from: 'sender@example.com', + to: 'recipient@example.com', + subject: 'Invoice', + text: 'Please find your invoice attached.', + attachments: [ +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Csatolmányok (Attachments) (2/3)} + \begin{exampleblock}{Fájl csatolása - Fájlok} +\small +\begin{lstlisting}[language=JavaScript] + { + filename: 'invoice.pdf', + path: '/path/to/invoice.pdf' + }, + { + filename: 'report.xlsx', +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Csatolmányok (Attachments) (3/4)} + \begin{exampleblock}{Buffer és String csatolmányok} +\small +\begin{lstlisting}[language=JavaScript] + path: '/path/to/report.xlsx' + }, + { + // Buffer-ből + filename: 'data.txt', + content: Buffer.from('File content here') + }, +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Csatolmányok (Attachments) (4/4)} + \begin{exampleblock}{String tartalom és küldés} +\small +\begin{lstlisting}[language=JavaScript] + { + // String-ből + filename: 'info.txt', + content: 'Text content' + } + ] +}; + +await transporter.sendMail(mailOptions); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Beágyazott képek (Inline Images) (1/3)} + \begin{exampleblock}{HTML struktúra CID-vel} +\small +\begin{lstlisting}[language=JavaScript] +const mailOptions = { + from: 'sender@example.com', + to: 'recipient@example.com', + subject: 'Email with Image', + html: ` +

Company Logo

+ Logo +

Email content here...

+\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Beágyazott képek (Inline Images) (2/3)} + \begin{exampleblock}{HTML és attachments} +\small +\begin{lstlisting}[language=JavaScript] + Banner + `, +\end{lstlisting} + \end{exampleblock} + \begin{exampleblock}{Attachments list} +\small +\begin{lstlisting}[language=JavaScript] + attachments: [ + { + filename: 'logo.png', + path: '/path/to/logo.png', + cid: 'logo' // Content-ID referencia + }, +\end{lstlisting} + \end{exampleblock} +\end{frame} + + +\begin{frame}[fragile]{Beágyazott képek (Inline Images) (3/3)} + \begin{exampleblock}{Banner és küldés} +\small +\begin{lstlisting}[language=JavaScript] + { + filename: 'banner.jpg', + path: '/path/to/banner.jpg', + cid: 'banner' + } + ] +}; + +await transporter.sendMail(mailOptions); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Gmail használata SMTP-vel} + \begin{exampleblock}{Gmail konfiguráció} +\small +\begin{lstlisting}[language=JavaScript] +const transporter = nodemailer.createTransport({ + service: 'gmail', // Előre konfigurált service + auth: { + user: 'your-email@gmail.com', + pass: 'your-app-password' // NEM a normál jelszó!} + }); +\end{lstlisting} + \end{exampleblock} + + \begin{alertblock}{App Password szükséges!} + \begin{enumerate} + \item Gmail Account Settings $\rightarrow$ Security + \item 2-Step Verification bekapcsolása + \item App passwords létrehozása + \item Az generált app password használata + \end{enumerate} + \end{alertblock} +\end{frame} + +\begin{frame}{Gmail korlátozások} + \begin{alertblock}{Korlátozások} + \begin{itemize} + \item Gmail: max 500 email/nap + \item Production környezetben NE használj Gmail-t! + \end{itemize} + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Email Service Provider - SendGrid példa} + \begin{block}{Miért használj ESP-t?} + \begin{itemize} + \item Magas kézbesítési arány (deliverability) + \item Spam szűrők elkerülése + \item Analytics és tracking + \item Skálázhatóság + \item Email validáció és bounce kezelés + \end{itemize} + \end{block} + + \begin{exampleblock}{SendGrid telepítés} +\small +\begin{lstlisting}[language=bash] +npm install @sendgrid/mail +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{SendGrid használat (1/2)} + \begin{exampleblock}{SendGrid inicializálás} +\small +\begin{lstlisting}[language=JavaScript] +const sgMail = require('@sendgrid/mail'); +sgMail.setApiKey(process.env.SENDGRID_API_KEY); + +const msg = { + to: 'recipient@example.com', + from: 'sender@verified-domain.com', + subject: 'Hello from SendGrid', +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{SendGrid használat (2/2)} + \begin{exampleblock}{SendGrid email küldés} +\small +\begin{lstlisting}[language=JavaScript] + text: 'Plain text content', + html: 'HTML content' +}; + +await sgMail.send(msg); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Nodemailer SendGrid-del (1/2)} + \begin{exampleblock}{SendGrid SMTP-n keresztül Nodemailer-rel} +\small +\begin{lstlisting}[language=JavaScript] +const nodemailer = require('nodemailer'); + +const transporter = nodemailer.createTransport({ + host: 'smtp.sendgrid.net', + port: 587, + auth: { + user: 'apikey', +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Nodemailer SendGrid-del (2/4)} + \begin{exampleblock}{Auth konfiguráció} +\small +\begin{lstlisting}[language=JavaScript] + pass: process.env.SENDGRID_API_KEY + } +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Nodemailer SendGrid-del (3/4)} + \begin{exampleblock}{Mail options} +\small +\begin{lstlisting}[language=JavaScript] +const mailOptions = { + from: 'verified-sender@yourdomain.com', + to: 'recipient@example.com', + subject: 'Email via SendGrid SMTP', + html: '

Hello from SendGrid!

' +}; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Nodemailer SendGrid-del (4/4)} + \begin{exampleblock}{Email küldés} +\small +\begin{lstlisting}[language=JavaScript] +try { + const info = await transporter.sendMail(mailOptions); + console.log('Email sent:', info.messageId); +} catch (error) { + console.error('Error:', error); +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}{Email Service Providers összehasonlítás} + \begin{table} + \small + \begin{tabular}{l|l|l|l} + \textbf{Szolgáltató} & \textbf{Ingyenes limit} & \textbf{Jellemzők} & \textbf{Ár} \\ + \hline + SendGrid & 100/nap & Kiváló docs, sablonok & \$19.95/hó \\ + Mailgun & 5000/hó & Fejlesztő-barát API & \$35/hó \\ + Amazon SES & 62000/hó & AWS integráció & \$0.10/1000 \\ + Postmark & 100/hó & Transactional spec. & \$15/hó \\ + Resend & 3000/hó & Modern, egyszerű & \$20/hó \\ + \end{tabular} + \end{table} + + \begin{block}{Választási szempontok} + \begin{itemize} + \item \textbf{Mennyiség}: hány email/hó szükséges? + \item \textbf{Típus}: transactional vs marketing? + \item \textbf{Funkciók}: analytics, webhooks, sablonok? + \item \textbf{Ár}: költségvetés + \item \textbf{Kézbesíthetőség}: deliverability rate + \end{itemize} + \end{block} +\end{frame} + +\begin{frame}[fragile]{Email küldés Express route-ban (1/4)} + \begin{exampleblock}{Express setup} +\small +\begin{lstlisting}[language=JavaScript] +const express = require('express'); +const nodemailer = require('nodemailer'); +const app = express(); + +app.use(express.json()); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email küldés Express route-ban (2/4)} + \begin{exampleblock}{Transporter konfiguráció} +\small +\begin{lstlisting}[language=JavaScript] +const transporter = nodemailer.createTransport({ + host: process.env.SMTP_HOST, + port: process.env.SMTP_PORT, + auth: { + user: process.env.SMTP_USER, +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email küldés Express route-ban (3/4)} + \begin{exampleblock}{Auth és route} +\small +\begin{lstlisting}[language=JavaScript] + pass: process.env.SMTP_PASS + } +}); + +app.post('/api/register', async (req, res) => { + const { email, username } = req.body; + + try { +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email küldés Express route-ban (4/5)} + \begin{exampleblock}{User mentés és token} +\small +\begin{lstlisting}[language=JavaScript] + // User mentése adatbázisba (példa) + // const user = await saveUser({ email, username }); + const verificationToken = generateToken(); + +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email küldés Express route-ban (5/6)} + \begin{exampleblock}{Email küldés - sendMail} +\small +\begin{lstlisting}[language=JavaScript] + // Email küldés + await transporter.sendMail({ + from: '"MyApp" ', + to: email, + subject: 'Verify your email', + html: `

Welcome ${username}!

+

Click to verify: +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email küldés Express route-ban (6/6)} + \begin{exampleblock}{Email küldés - zárás és válasz} +\small +\begin{lstlisting}[language=JavaScript] + + Verify Email

` + }); + res.json({ message: 'Registration successful, check email' }); + } catch (error) { + res.status(500).json({ error: 'Registration failed' }); + } +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email Service osztály - Clean Architecture (1/4)} + \begin{exampleblock}{EmailService class} +\small +\begin{lstlisting}[language=JavaScript] +class EmailService { + constructor(transporter) { + this.transporter = transporter; + } + + async sendWelcomeEmail(user) { + const mailOptions = { + from: '"MyApp" ', + to: user.email, +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email Service osztály - Clean Architecture (2/4)} + \begin{exampleblock}{EmailService sendWelcomeEmail} +\small +\begin{lstlisting}[language=JavaScript] + subject: 'Welcome to MyApp!', + html: `

Welcome ${user.username}!

` + }; + return await this.transporter.sendMail(mailOptions); + } +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email Service osztály - Clean Architecture (3/4)} + \begin{exampleblock}{EmailService sendPasswordReset} +\small +\begin{lstlisting}[language=JavaScript] + async sendPasswordReset(email, resetToken) { + const resetUrl = `${process.env.APP_URL}/reset?token=${resetToken}`; + return await this.transporter.sendMail({ + from: '"MyApp" ', + to: email, +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email Service osztály - Clean Architecture (4/5)} + \begin{exampleblock}{sendPasswordReset (folytatás)} +\small +\begin{lstlisting}[language=JavaScript] + subject: 'Password Reset Request', + html: `

Password Reset

+

Click here to reset your password:

+ Reset Password +

This link expires in 1 hour.

` + }); + } +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email Service osztály - Clean Architecture (5/5)} + \begin{exampleblock}{EmailService sendOrderConfirmation} +\small +\begin{lstlisting}[language=JavaScript] + async sendOrderConfirmation(order) { + return await this.transporter.sendMail({ + from: '"Shop" ', + to: order.customerEmail, + subject: `Order Confirmation #${order.id}`, + html: this.renderOrderTemplate(order) }); + } +} +module.exports = EmailService; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{EmailService használata DI-val (1/4)} + \begin{exampleblock}{Transporter setup} +\small +\begin{lstlisting}[language=JavaScript] +// app.js - Setup +const nodemailer = require('nodemailer'); +const EmailService = require('./services/EmailService'); + +const transporter = nodemailer.createTransport({ + host: process.env.SMTP_HOST, + port: process.env.SMTP_PORT, +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{EmailService használata DI-val (2/5)} + \begin{exampleblock}{Auth} +\small +\begin{lstlisting}[language=JavaScript] + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASS + } +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{EmailService használata DI-val (3/5)} + \begin{exampleblock}{EmailService inicializálás} +\small +\begin{lstlisting}[language=JavaScript] +const emailService = new EmailService(transporter); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{EmailService használata DI-val (4/5)} + \begin{exampleblock}{Route használat} +\small +\begin{lstlisting}[language=JavaScript] +// Route-okban használat +app.post('/api/register', async (req, res) => { + try { + const user = await userService.createUser(req.body); + await emailService.sendWelcomeEmail(user); + +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{EmailService használata DI-val (5/5)} + \begin{exampleblock}{Register route - válasz} +\small +\begin{lstlisting}[language=JavaScript] + res.json({ message: 'Registration successful' }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{EmailService használata DI-val (6/6)} + \begin{exampleblock}{Forgot password route} +\small +\begin{lstlisting}[language=JavaScript] +app.post('/api/forgot-password', async (req, res) => { + try { + const resetToken = await userService.generateResetToken(req.body.email); + await emailService.sendPasswordReset(req.body.email, resetToken); + res.json({ message: 'Password reset email sent' }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Hibakezelés és retry logika (1/4)} + \begin{exampleblock}{Retry mechanizmus - Setup} +\small +\begin{lstlisting}[language=JavaScript] +class EmailService { + async sendEmailWithRetry(mailOptions, maxRetries = 3) { + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + const info = await this.transporter.sendMail(mailOptions); + console.log(`Email sent on attempt ${attempt}:`, info.messageId); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Hibakezelés és retry logika (2/4)} + \begin{exampleblock}{Retry mechanizmus - Return és error} +\small +\begin{lstlisting}[language=JavaScript] + return info; + } catch (error) { +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Hibakezelés és retry logika (3/4)} + \begin{exampleblock}{Error handling és backoff} +\small +\begin{lstlisting}[language=JavaScript] + console.error(`Attempt ${attempt} failed:`, error.message); + if (attempt === maxRetries) { + // Utolsó próbálkozás sikertelen + throw new Error(`Failed to send email after ${maxRetries} attempts`); + } + + // Exponential backoff: 1s, 2s, 4s... + const delay = Math.pow(2, attempt - 1) * 1000; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Hibakezelés és retry logika (4/5)} + \begin{exampleblock}{Exponential backoff} +\small +\begin{lstlisting}[language=JavaScript] + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + } +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Hibakezelés és retry logika (5/6)} + \begin{exampleblock}{sendWelcomeEmail setup} +\small +\begin{lstlisting}[language=JavaScript] + async sendWelcomeEmail(user) { + const mailOptions = { + from: '"MyApp" ', + to: user.email, + subject: 'Welcome!', + html: `

Welcome ${user.username}!

` + }; + +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Hibakezelés és retry logika (6/6)} + \begin{exampleblock}{sendEmailWithRetry call} +\small +\begin{lstlisting}[language=JavaScript] + return await this.sendEmailWithRetry(mailOptions); + } +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email queue - Bull használatával} + \begin{block}{Miért kell queue?} + \begin{itemize} + \item Email küldés lassú (network I/O) + \item User ne várjon a válaszra + \item Megbízhatóság: retry failed jobs + \item Rate limiting kezelése + \end{itemize} + \end{block} + + \begin{exampleblock}{Bull telepítés} +\small +\begin{lstlisting}[language=bash] +npm install bull redis +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email queue - Queue létrehozás} + \begin{exampleblock}{Queue létrehozás} +\small +\begin{lstlisting}[language=JavaScript] +const Queue = require('bull'); + +const emailQueue = new Queue('email', { + redis: { + host: process.env.REDIS_HOST || 'localhost', + port: process.env.REDIS_PORT || 6379 + } +}); + +module.exports = emailQueue; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email queue - Job feldolgozás (1/3)} + \begin{exampleblock}{Worker létrehozása} +\small +\begin{lstlisting}[language=JavaScript] +const emailQueue = require('./emailQueue'); +const EmailService = require('./services/EmailService'); + +const emailService = new EmailService(transporter); + +// Job processor +emailQueue.process(async (job) => { + const { type, data } = job.data; + console.log(`Processing email job: ${type}`); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email queue - Job feldolgozás (2/3)} + \begin{exampleblock}{Switch case processing} +\small +\begin{lstlisting}[language=JavaScript] + switch (type) { + case 'welcome': + return await emailService.sendWelcomeEmail(data.user); + case 'order-confirmation': + return await emailService.sendOrderConfirmation(data.order); + default: + throw new Error(`Unknown email type: ${type}`); + } +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email queue - Job feldolgozás (3/3)} + \begin{exampleblock}{Event handlers} +\small +\begin{lstlisting}[language=JavaScript] +// Event handlers +emailQueue.on('completed', (job, result) => { + console.log(`Job ${job.id} completed:`, result.messageId); +}); +emailQueue.on('failed', (job, err) => { + console.error(`Job ${job.id} failed:`, err.message); +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email queue - Job hozzáadása (1/4)} + \begin{exampleblock}{Register route setup} +\small +\begin{lstlisting}[language=JavaScript] +const emailQueue = require('./emailQueue'); + +app.post('/api/register', async (req, res) => { + try { + const user = await userService.createUser(req.body); + // Email job hozzáadása a queue-hoz + await emailQueue.add('welcome', { +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email queue - Job hozzáadása (2/4)} + \begin{exampleblock}{Job type és data} +\small +\begin{lstlisting}[language=JavaScript] + type: 'welcome', + data: { user } + }, { + removeOnComplete: true, + removeOnFail: false + }); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email queue - Job hozzáadása (3/4)} + \begin{exampleblock}{Job configuration és response} +\small +\begin{lstlisting}[language=JavaScript] + // Azonnal válaszolunk, email küldés háttérben történik + res.json({ + message: 'Registration successful', + userId: user.id + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email queue - Job hozzáadása (4/4)} + \begin{exampleblock}{Forgot password route} +\small +\begin{lstlisting}[language=JavaScript] +app.post('/api/forgot-password', async (req, res) => { + const resetToken = await userService.generateResetToken(req.body.email); + await emailQueue.add('password-reset', { + type: 'password-reset', + data: { email: req.body.email, token: resetToken } }); + res.json({ message: 'If email exists, reset link was sent' }); +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Környezeti változók kezelése (1/3)} + \begin{exampleblock}{.env fájl - SMTP} +\small +\begin{lstlisting}[language=bash] +# SMTP Configuration +SMTP_HOST=smtp.example.com +SMTP_PORT=587 +SMTP_USER=your-email@example.com +SMTP_PASS=your-password +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Környezeti változók kezelése (2/3)} + \begin{exampleblock}{.env fájl - Email settings} +\small +\begin{lstlisting}[language=bash] +# Email settings +EMAIL_FROM_NAME=MyApp +EMAIL_FROM_ADDRESS=noreply@myapp.com +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Környezeti változók kezelése (3/4)} + \begin{exampleblock}{.env fájl (folytatás)} +\small +\begin{lstlisting}[language=bash] +# SendGrid (alternatíva) +SENDGRID_API_KEY=SG.xxxxxx + +# Application +APP_URL=https://myapp.com +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Környezeti változók kezelése (4/5)} + \begin{exampleblock}{Config betöltése} +\small +\begin{lstlisting}[language=JavaScript] +require('dotenv').config(); + +const emailConfig = { + host: process.env.SMTP_HOST, + port: parseInt(process.env.SMTP_PORT), +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Környezeti változók kezelése (5/5)} + \begin{exampleblock}{Auth és transporter} +\small +\begin{lstlisting}[language=JavaScript] + auth: { + user: process.env.SMTP_USER, +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Környezeti változók kezelése (6/6)} + \begin{exampleblock}{Transporter létrehozás} +\small +\begin{lstlisting}[language=JavaScript] + pass: process.env.SMTP_PASS + } +}; + +const transporter = nodemailer.createTransport(emailConfig); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}{Best Practices} + \begin{block}{Biztonsági és fejlesztési ajánlások} + \footnotesize + \begin{enumerate} + \item \textbf{Soha ne hard-code-old credentials}-t - használj környezeti változókat + \item \textbf{Használj queue-t} production környezetben - ne blokkolj HTTP requesteket + \item \textbf{Rate limiting} - védd meg magad spam-től + \item \textbf{Email validáció} - ellenőrizd az email formátumot + \item \textbf{Unsubscribe link} - marketing emailekhez kötelező + \item \textbf{SPF, DKIM, DMARC} - domain autentikáció + \item \textbf{Teszt környezet} - használj Mailtrap/Ethereal development-hez + \item \textbf{Template-ek} - külön fájlokban tartsd az email template-eket + \item \textbf{Error logging} - monitorozd a sikertelen küldéseket + \item \textbf{Bounce handling} - kezeld a visszapattanó email-eket + \end{enumerate} + \end{block} +\end{frame} + +\begin{frame}[fragile]{Development email teszt - Ethereal Email (1/3)} + \begin{block}{Ethereal Email} + Fake SMTP service teszteléshez - https://ethereal.email + \end{block} + + \begin{exampleblock}{Teszt account és config} +\small +\begin{lstlisting}[language=JavaScript] +const nodemailer = require('nodemailer'); + +// Teszt account létrehozása +const testAccount = await nodemailer.createTestAccount(); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Development email teszt - Ethereal Email (2/4)} + \begin{exampleblock}{Transporter létrehozása} +\small +\begin{lstlisting}[language=JavaScript] +const transporter = nodemailer.createTransport({ + host: 'smtp.ethereal.email', + port: 587, +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Development email teszt - Ethereal Email (3/4)} + \begin{exampleblock}{Transporter konfiguráció} +\small +\begin{lstlisting}[language=JavaScript] + secure: false, + auth: { + user: testAccount.user, + pass: testAccount.pass + } +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Development email teszt - Ethereal Email (4/4)} + \begin{exampleblock}{Email küldés és preview} +\small +\begin{lstlisting}[language=JavaScript] +const info = await transporter.sendMail({ + from: '"Test" ', + to: 'recipient@example.com', + subject: 'Test Email', + html: '

This is a test

' +}); +// Preview URL - böngészőben megnézhető +console.log('Preview URL:', nodemailer.getTestMessageUrl(info)); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}{Összefoglalás (1/2)} + \begin{block}{Email küldés backend-ből} + \begin{itemize} + \item SMTP protokoll használata + \item Nodemailer - de facto standard Node.js-hez + \item HTML, plain text, attachments támogatása + \item Email Service Providers (SendGrid, Mailgun, SES) + \end{itemize} + \end{block} + + \begin{block}{Production best practices} + \begin{itemize} + \item Queue használata (Bull + Redis) + \item Retry logika hibák kezelésére + \item EmailService osztály clean architecture-höz + \item Környezeti változók biztonságos kezelése + \item Error logging és monitoring + \end{itemize} + \end{block} +\end{frame} + +\begin{frame}{Összefoglalás (2/2)} + \begin{block}{Development} + \begin{itemize} + \item Ethereal Email vagy Mailtrap teszteléshez + \item Ne használj production SMTP-t development-ben + \item Template-ek külön kezelése (következő téma) + \end{itemize} + \end{block} +\end{frame} + + diff --git a/Backend_ppt/dic_cors_emailsending/email_templating.tex b/Backend_ppt/dic_cors_emailsending/email_templating.tex new file mode 100644 index 0000000..8dad3e2 --- /dev/null +++ b/Backend_ppt/dic_cors_emailsending/email_templating.tex @@ -0,0 +1,658 @@ +\section{Templating} + +\begin{frame}{Miért használjunk email template-eket? (1/2)} + \begin{block}{Problémák template nélkül} + \begin{itemize} + \item HTML string-ek a kódban $\rightarrow$ nehéz karbantartani + \item Duplikált kód különböző email típusokhoz + \item Nehéz változtatni a design-t + \item Frontend és backend keveredik + \item Nincs újrafelhasználhatóság + \end{itemize} + \end{block} +\end{frame} + +\begin{frame}{Miért használjunk email template-eket? (2/2)} + \begin{exampleblock}{Template-ekkel} + \begin{itemize} + \item Szeparált template fájlok + \item Dinamikus adatok injektálása + \item Újrafelhasználható komponensek (header, footer) + \item Könnyű módosítás és tesztelés + \item Designer-ek is módosíthatják + \item Verziókövetés + \end{itemize} + \end{exampleblock} +\end{frame} + +\begin{frame}{Template Engine-ek} + \begin{block}{Népszerű template engine-ek Node.js-hez} + \begin{description} + \item[Handlebars] Logika-mentes, egyszerű szintaxis, blokk helper-ek + \item[EJS] Embedded JavaScript, ismerős szintaxis + \item[Pug (Jade)] Tömör, whitespace-based szintaxis + \item[Nunjucks] Jinja2-szerű, gazdag funkciók + \item[Mustache] Minimális, logika-mentes + \end{description} + \end{block} + + \begin{block}{Email-specifikus eszközök} + \begin{description} + \item[MJML] Responsive email framework - JSON/XML-szerű szintaxis + \item[Foundation for Emails] Responsive email framework + \item[Email-templates] Nodemailer integráció többféle engine-hez + \end{description} + \end{block} +\end{frame} + +\begin{frame}[fragile]{Handlebars alapok} + \begin{block}{Telepítés} +\small +\begin{lstlisting}[language=bash] +npm install handlebars +\end{lstlisting} + \end{block} + + \begin{exampleblock}{Template szintaxis (1/2)} +\small +\begin{lstlisting}[language=HTML] + + + + {{subject}} + + +

Hello {{username}}!

+

Your email is: {{email}}

+\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Handlebars alapok (2/3)} + \begin{exampleblock}{Template szintaxis - Conditionals} +\small +\begin{lstlisting}[language=HTML] + {{#if isPremium}} +

Thanks for being a premium member!

+ {{else}} +

Upgrade to premium for more features.

+ {{/if}} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Handlebars alapok (3/3)} + \begin{exampleblock}{Template szintaxis - Loops} +\small +\begin{lstlisting}[language=HTML] +
    + {{#each items}} +
  • {{this.name}} - ${{this.price}}
  • + {{/each}} +
+ + +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Handlebars használata Node.js-ben (1/3)} + \begin{exampleblock}{Setup és template beolvasás} +\small +\begin{lstlisting}[language=JavaScript] +const handlebars = require('handlebars'); +const fs = require('fs').promises; + +async function renderTemplate(templateName, data) { + // Template fájl beolvasása + const templatePath = `./templates/${templateName}.hbs`; + const templateSource = await fs.readFile(templatePath, 'utf-8'); + + // Template compile + const template = handlebars.compile(templateSource); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Handlebars használata Node.js-ben (2/3)} + \begin{exampleblock}{Render adatokkal} +\small +\begin{lstlisting}[language=JavaScript] +async function renderTemplate(templateName, data) { + // ... template beolvasás és compile + + // Render adatokkal + const html = template(data); + + return html; +} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Handlebars használata Node.js-ben (3/4)} + \begin{exampleblock}{Használat példa - Data} +\small +\begin{lstlisting}[language=JavaScript] +const data = { + username: 'John Doe', + email: 'john@example.com', + isPremium: true, + items: [ + { name: 'Product 1', price: 29.99 }, + { name: 'Product 2', price: 49.99 } + ] +}; +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Handlebars használata Node.js-ben (4/4)} + \begin{exampleblock}{Használat példa - Render} +\small +\begin{lstlisting}[language=JavaScript] +const html = await renderTemplate('welcome', data); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Handlebars custom helper-ek (1/3)} + \begin{exampleblock}{Dátum helper} +\small +\begin{lstlisting}[language=JavaScript] +const handlebars = require('handlebars'); +// Egyszerű helper - dátum formázás +handlebars.registerHelper('formatDate', (date) => { + return new Date(date).toLocaleDateString('hu-HU');}); +\end{lstlisting} + \end{exampleblock} + \begin{exampleblock}{Currency helper} +\small +\begin{lstlisting}[language=JavaScript] +// Helper paraméterekkel +handlebars.registerHelper('formatCurrency', + (amount, currency = 'USD') => { + return new Intl.NumberFormat('en-US', { + style: 'currency', currency: currency}).format(amount);}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Handlebars custom helper-ek (2/3)} + \begin{exampleblock}{Block helper - feltételes logika} + \small + \begin{lstlisting}[language=JavaScript] + // Block helper - feltételes logika + handlebars.registerHelper('ifEquals', + function(arg1, arg2, options) { + return (arg1 === arg2) + ? options.fn(this) + : options.inverse(this); + }); + \end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Handlebars custom helper-ek (3/3)} + \begin{exampleblock}{Template használat} +\small +\begin{lstlisting}[language=HTML] +

Order date: {{formatDate orderDate}}

+

Total: {{formatCurrency total 'EUR'}}

+ +{{#ifEquals status 'completed'}} +

Order is completed!

+{{else}} +

Order is pending.

+{{/ifEquals}} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Handlebars partials - újrafelhasználható részek (1/3)} + \begin{exampleblock}{Setup} +\small +\begin{lstlisting}[language=JavaScript] +const handlebars = require('handlebars'); +const fs = require('fs').promises; + +// Partial-ok betöltése +async function registerPartials() { + const header = await fs.readFile( + './templates/partials/header.hbs', 'utf-8'); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Handlebars partials - újrafelhasználható részek (2/3)} + \begin{exampleblock}{Partial fájlok beolvasása} +\small +\begin{lstlisting}[language=JavaScript] + const footer = await fs.readFile( + './templates/partials/footer.hbs', 'utf-8'); + const button = await fs.readFile( + './templates/partials/button.hbs', 'utf-8'); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Handlebars partials - újrafelhasználható részek (3/3)} + \begin{exampleblock}{Partial regisztráció} +\small +\begin{lstlisting}[language=JavaScript] + // Partials regisztrálása + handlebars.registerPartial('header', header); + handlebars.registerPartial('footer', footer); + handlebars.registerPartial('button', button); +} + +await registerPartials(); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Partial példák (1/2)} + \begin{columns}[T] + \begin{column}{0.48\textwidth} + \textbf{header.hbs} +\begin{lstlisting}[language=HTML] +
+ Logo +

{{companyName}}

+
+\end{lstlisting} + \end{column} + \begin{column}{0.48\textwidth} + \textbf{footer.hbs} +\begin{lstlisting}[language=HTML] +
+

© {{year}}

+
+\end{lstlisting} + \end{column} + \end{columns} +\end{frame} + +\begin{frame}[fragile]{Partial példák (2/2)} + \begin{exampleblock}{main.hbs - Partials használata} +\small +\begin{lstlisting}[language=HTML] + + + + {{> header}} +
+ {{body}} +
+ {{> footer}} + + +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email-templates csomag - All-in-one megoldás} + \begin{block}{Telep\u00edt\u00e9s} +\small +\begin{lstlisting}[language=bash] +npm install email-templates +\end{lstlisting} + \end{block} + + \begin{block}{Jellemz\u0151k} + \begin{itemize} + \item Automatikus HTML \u00e9s text verzi\u00f3 gener\u00e1l\u00e1s + \item CSS inlining (email client compatibility) + \item Template engine v\u00e1laszt\u00e9k (Pug, Handlebars, EJS, etc.) + \item Preview server fejleszt\u00e9shez + \item Nodemailer integr\u00e1ci\u00f3 + \end{itemize} + \end{block} +\end{frame} + +\begin{frame}[fragile]{Email-templates haszn\u00e1lata (1/3)} + \begin{exampleblock}{Transporter setup} +\small +\begin{lstlisting}[language=JavaScript] +const Email = require('email-templates'); +const nodemailer = require('nodemailer'); +const transporter = nodemailer.createTransport({ + host: process.env.SMTP_HOST, + port: process.env.SMTP_PORT, + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASS + } +});\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email-templates haszn\u00e1lata (2/3)} + \begin{exampleblock}{Email konfigur\u00e1ci\u00f3} +\small +\begin{lstlisting}[language=JavaScript] +const email = new Email({ + message: { from: 'noreply@example.com' }, + transport: transporter, + views: { + root: './emails', // Template könyvtár + options: { + extension: 'hbs' // Handlebars + } + }, +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email-templates haszn\u00e1lata (3/3)} + \begin{exampleblock}{CSS inlining config} +\small +\begin{lstlisting}[language=JavaScript] + // ... folytat\u00e1s + juice: true, // CSS inlining + juiceResources: { + preserveImportant: true, + webResources: { + relativeTo: './emails' + } + } +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email-templates - Email küldés (1/2)} + \begin{exampleblock}{Welcome email példa} +\small +\begin{lstlisting}[language=JavaScript] +// Email küldés template-tel +await email.send({ + template: 'welcome', // ./emails/welcome könyvtár + message: { to: 'user@example.com' }, + locals: { + username: 'John Doe', + verifyUrl: 'https://example.com/verify?token=abc123', + year: new Date().getFullYear() + } +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Email-templates - Email küldés (2/2)} + \begin{exampleblock}{Order confirmation példa} +\small +\begin{lstlisting}[language=JavaScript] +// Több template eset +await email.send({ + template: 'order-confirmation', + message: { to: order.customerEmail }, + locals: { + orderNumber: order.id, items: order.items, + total: order.total, shippingAddress: order.address + } +}); +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Template könyvtár struktúra (1/2)} + \begin{exampleblock}{Fájl struktúra - Layouts és Partials} +\small +\begin{lstlisting}[language=bash] +emails/ + ├── _layouts/ + │ └── default.hbs # Alap layout + ├── _partials/ + │ ├── header.hbs + │ ├── footer.hbs + │ └── button.hbs +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Template könyvtár struktúra (2/3)} + \begin{exampleblock}{Fájl struktúra - Welcome template} +\small +\begin{lstlisting}[language=bash] + ├── welcome/ + │ ├── html.hbs # HTML verzió + │ ├── text.hbs # Plain text + │ ├── subject.hbs # Subject + │ └── style.css # CSS (inline-olva lesz) +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Template könyvtár struktúra (3/3)} + \begin{exampleblock}{Fájl struktúra - További templates} +\small +\begin{lstlisting}[language=bash] + ├── password-reset/ + │ ├── html.hbs + │ ├── text.hbs + │ └── subject.hbs + └── order-confirmation/ + ├── html.hbs + ├── text.hbs + └── subject.hbs +\end{lstlisting} + \end{exampleblock} + + \begin{alertblock}{Fontos} + Minden template-hez készíts \texttt{text.hbs} verziót is (accessibility)! + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Welcome template példa - HTML (1/3)} + \begin{exampleblock}{emails/welcome/html.hbs - Head} +\small +\begin{lstlisting}[language=HTML] + + + + + + + + + {{> header companyName="MyApp" logoUrl="/logo.png"}} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Welcome template példa - HTML (2/3)} + \begin{exampleblock}{emails/welcome/html.hbs - Content} +\small +\begin{lstlisting}[language=HTML] +
+

Welcome, {{username}}!

+

Thank you for joining MyApp. + We're excited to have you on board!

+ +

To get started, please verify your email address + by clicking the button below:

+ + {{> button text="Verify Email" url=verifyUrl color="#4CAF50"}} +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Welcome template példa - HTML (3/3)} + \begin{exampleblock}{emails/welcome/html.hbs - Footer} +\small +\begin{lstlisting}[language=HTML] +

If you didn't create an account, + please ignore this email.

+
+ + {{> footer year=year}} + + +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Welcome template példa - Text (1/2)} + \begin{exampleblock}{emails/welcome/text.hbs - Content} +\small +\begin{lstlisting}[language=plaintext] +Welcome, {{username}}! +Thank you for joining MyApp. +We're excited to have you on board! + +To get started, please verify your email address +by visiting: +{{verifyUrl}} + +If you didn't create an account, +please ignore this email. +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile]{Welcome template példa - Text (2/2)} + \begin{exampleblock}{emails/welcome/text.hbs - Signature} +\small +\begin{lstlisting}[language=plaintext] +Best regards, +The MyApp Team + +© {{year}} MyApp. All rights reserved. +\end{lstlisting} + \end{exampleblock} + + \begin{exampleblock}{emails/welcome/subject.hbs} +\small +\begin{lstlisting}[language=plaintext] +Welcome to MyApp, {{username}}! +\end{lstlisting} + \end{exampleblock} +\end{frame} + +\begin{frame}{Template subject - Megjegyzés} + \begin{alertblock}{Megjegyzés} + A \texttt{subject.hbs} is lehet dinamikus template! + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{Reusable button partial (1/2)} + \begin{exampleblock}{emails/\_partials/button.hbs - Table structure} +\small +\begin{lstlisting}[language=HTML] + + + + +
+ + {{text}} + +
+\end{lstlisting} + \end{exampleblock} + \begin{alertblock}{Miért table layout?} + Email client-ek (Outlook, Gmail) rosszul támogatják a modern CSS-t. + Table-based layout a legkompatibilisebb megoldás. + \end{alertblock} +\end{frame} + +\begin{frame}[fragile]{CSS inlining} + \begin{block}{Miért kell CSS inlining?} + \begin{itemize} + \item Gmail és sok email client eltávolítja a \texttt{