email és dic
This commit is contained in:
@@ -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}
|
||||||
|
|
||||||
@@ -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<any>;
|
||||||
|
find(id: string): Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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}
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -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}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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]
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{{subject}}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hello {{username}}!</h1>
|
||||||
|
<p>Your email is: {{email}}</p>
|
||||||
|
\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}}
|
||||||
|
<p>Thanks for being a premium member!</p>
|
||||||
|
{{else}}
|
||||||
|
<p>Upgrade to premium for more features.</p>
|
||||||
|
{{/if}}
|
||||||
|
\end{lstlisting}
|
||||||
|
\end{exampleblock}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Handlebars alapok (3/3)}
|
||||||
|
\begin{exampleblock}{Template szintaxis - Loops}
|
||||||
|
\small
|
||||||
|
\begin{lstlisting}[language=HTML]
|
||||||
|
<ul>
|
||||||
|
{{#each items}}
|
||||||
|
<li>{{this.name}} - ${{this.price}}</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
\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]
|
||||||
|
<p>Order date: {{formatDate orderDate}}</p>
|
||||||
|
<p>Total: {{formatCurrency total 'EUR'}}</p>
|
||||||
|
|
||||||
|
{{#ifEquals status 'completed'}}
|
||||||
|
<p>Order is completed!</p>
|
||||||
|
{{else}}
|
||||||
|
<p>Order is pending.</p>
|
||||||
|
{{/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]
|
||||||
|
<div style="background: #333;
|
||||||
|
color: white;
|
||||||
|
padding: 20px;">
|
||||||
|
<img src="{{logoUrl}}"
|
||||||
|
alt="Logo"/>
|
||||||
|
<h1>{{companyName}}</h1>
|
||||||
|
</div>
|
||||||
|
\end{lstlisting}
|
||||||
|
\end{column}
|
||||||
|
\begin{column}{0.48\textwidth}
|
||||||
|
\textbf{footer.hbs}
|
||||||
|
\begin{lstlisting}[language=HTML]
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<p>© {{year}}</p>
|
||||||
|
</div>
|
||||||
|
\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]
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
{{> header}}
|
||||||
|
<div class="content">
|
||||||
|
{{body}}
|
||||||
|
</div>
|
||||||
|
{{> footer}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
\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]
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{> 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]
|
||||||
|
<div class="container">
|
||||||
|
<h1>Welcome, {{username}}!</h1>
|
||||||
|
<p>Thank you for joining MyApp.
|
||||||
|
We're excited to have you on board!</p>
|
||||||
|
|
||||||
|
<p>To get started, please verify your email address
|
||||||
|
by clicking the button below:</p>
|
||||||
|
|
||||||
|
{{> 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]
|
||||||
|
<p>If you didn't create an account,
|
||||||
|
please ignore this email.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{> footer year=year}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
\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]
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0"
|
||||||
|
border="0" align="center" style="margin: 20px 0;">
|
||||||
|
<tr>
|
||||||
|
<td style="border-radius: 4px; background: {{color}};">
|
||||||
|
<a href="{{url}}"
|
||||||
|
style="border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 24px;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
\end{lstlisting}
|
||||||
|
\end{exampleblock}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Reusable button partial (2/2)}
|
||||||
|
\begin{exampleblock}{emails/\_partials/button.hbs - Link styling}
|
||||||
|
\small
|
||||||
|
\begin{lstlisting}[language=HTML]
|
||||||
|
style="...
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;">
|
||||||
|
{{text}}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
\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{<style>} tag-et
|
||||||
|
\item Biztonságos megoldás: inline style attribute-ok
|
||||||
|
\item Automatikus: \texttt{juice} library végzi
|
||||||
|
\end{itemize}
|
||||||
|
\end{block}
|
||||||
|
|
||||||
|
\begin{columns}[T]
|
||||||
|
\begin{column}{0.48\textwidth}
|
||||||
|
\textbf{Előtte (style.css)}
|
||||||
|
\begin{lstlisting}[language=CSS]
|
||||||
|
.button {
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 24px;
|
||||||
|
}
|
||||||
|
\end{lstlisting}
|
||||||
|
\end{column}
|
||||||
|
\begin{column}{0.48\textwidth}
|
||||||
|
\textbf{Utána (inline)}
|
||||||
|
\begin{lstlisting}[language=HTML]
|
||||||
|
<a style="background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 24px;">
|
||||||
|
Button
|
||||||
|
</a>
|
||||||
|
\end{lstlisting}
|
||||||
|
\end{column}
|
||||||
|
\end{columns}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{CSS inlining - Fontos beállítások}
|
||||||
|
\begin{exampleblock}{Email-templates automatikusan végzi}
|
||||||
|
\small
|
||||||
|
\begin{lstlisting}[language=JavaScript]
|
||||||
|
const email = new Email({
|
||||||
|
// ...
|
||||||
|
juice: true, // CSS inlining engedélyezése
|
||||||
|
juiceResources: {
|
||||||
|
preserveImportant: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
\end{lstlisting}
|
||||||
|
\end{exampleblock}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}{Összefoglalás (1/2)}
|
||||||
|
\begin{block}{Email templating lényege}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Szeparált, karbantartható email template-ek
|
||||||
|
\item Dinamikus adatok injektálása
|
||||||
|
\item Újrafelhasználható komponensek (partials)
|
||||||
|
\item HTML és plain text verziók
|
||||||
|
\end{itemize}
|
||||||
|
\end{block}
|
||||||
|
|
||||||
|
\begin{block}{Eszközök}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Template engine-ek: Handlebars, EJS, Pug
|
||||||
|
\item Email-specific: MJML, Foundation for Emails
|
||||||
|
\item Node.js library: email-templates csomag
|
||||||
|
\item CSS inlining: juice library
|
||||||
|
\end{itemize}
|
||||||
|
\end{block}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}{Összefoglalás (2/2)}
|
||||||
|
\begin{block}{Kulcsfontosságú szempontok}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Email client kompatibilitás (table layout, inline CSS)
|
||||||
|
\item Responsive design mobil eszközökre
|
||||||
|
\item Tesztelés és preview development-ben
|
||||||
|
\item Clean architecture: EmailService osztály
|
||||||
|
\end{itemize}
|
||||||
|
\end{block}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
Reference in New Issue
Block a user