\section{DTO}

\begin{frame}{Mi az a DTO?}
  \begin{block}{Data Transfer Object}
    \begin{itemize}
      \item Adatok szállítására szolgáló objektum
      \item Elválasztja az adatbázis sémát\\
      a kliens felőli interfésztől
      \item Kontroll az átadott adatok felett
      \item Validáció és transzformáció helye
    \end{itemize}
  \end{block}
  
  \begin{exampleblock}{Példa}
    Felhasználó entitás tartalmaz jelszó hash-t,\\
    de a DTO nem adja ki.
  \end{exampleblock}
\end{frame}

\begin{frame}{Miért használjunk DTO-t?}
  \begin{block}{Előnyök}
    \begin{itemize}
      \item \textbf{Biztonság} - Érzékeny mezők elrejtése (pl. jelszó)
      \item \textbf{Verziókezelés} - API változhat a DB séma változtatása nélkül
      \item \textbf{Validáció} - Bejövő adatok ellenőrzése
      \item \textbf{Dokumentáció} - Világos szerződés a kliens és szerver között
      \item \textbf{Transzformáció} - Adatok átalakítása (pl. dátum formázás)
    \end{itemize}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Prisma modell vs DTO}
  \begin{columns}
    \begin{column}{0.48\textwidth}
      \begin{block}{Prisma Model}
        \begin{lstlisting}[language=JavaScript]
model User {
  id        Int      @id 
                @default(autoincrement())
  email     String   @unique
  password  String
  name      String?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
        \end{lstlisting}
      \end{block}
    \end{column}
    \begin{column}{0.48\textwidth}
      \begin{block}{UserDTO (kimenet)}
        \begin{lstlisting}[language=JavaScript]
{
  id: 1,
  email: "john@example.com",
  name: "John Doe"
  // password nincs benne!
  // createdAt, updatedAt 
  // opcionális
}
        \end{lstlisting}
      \end{block}
    \end{column}
  \end{columns}
\end{frame}

\begin{frame}[fragile]{Input DTO - Létrehozás}
  \begin{block}{CreateUserDTO - Constructor}
    \begin{lstlisting}[language=JavaScript]
class CreateUserDTO {
  constructor(data) {
    this.email = data.email;
    this.password = data.password;
    this.name = data.name;
  }
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Input DTO - Validáció}
  \begin{block}{CreateUserDTO - Validate módszer}
    \begin{lstlisting}[language=JavaScript]
class CreateUserDTO {
  // ...
  
  validate() {
    if (!this.email || !this.email.includes('@')) {
      throw new Error('Érvénytelen email cím');
    }
    if (!this.password || this.password.length < 8) {
      throw new Error('Jelszó túl rövid (min. 8 karakter)');
    }
    return true;
  }
}
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Output DTO - Válasz}
  \begin{block}{UserResponseDTO}
    \begin{lstlisting}[language=JavaScript]
class UserResponseDTO {
  constructor(user) {
    this.id = user.id;
    this.email = user.email;
    this.name = user.name;
    // password nincs másolva!
  }
  static fromPrismaUser(user) {
    return new UserResponseDTO(user);
  }
  static fromManyPrismaUsers(users) {
    return users.map(u => new UserResponseDTO(u));
  }
}
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{DTO használata service-ben}
  \begin{block}{UserService példa (1)}
    \begin{lstlisting}[language=JavaScript]
const prisma = require('./prisma');
const bcrypt = require('bcrypt');
class UserService {
  async createUser(createUserDTO) {
    createUserDTO.validate(); // Validálás
    const hashedPassword = await bcrypt.hash(
      createUserDTO.password, 10
    ); // Jelszó hash-elés
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{DTO használata service-ben (folyt.)}
  \begin{block}{UserService példa (2)}
    \begin{lstlisting}[language=JavaScript]
    const user = await prisma.user.create({
      data: {
        email: createUserDTO.email,
        password: hashedPassword,
        name: createUserDTO.name
      } // Mentés Prisma-val
    });
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{DTO használata service-ben (folyt.)}
  \begin{block}{UserService példa (3)}
    \begin{lstlisting}[language=JavaScript]
    // DTO-vá alakítás visszaadás előtt
    return UserResponseDTO.fromPrismaUser(user);
  }
  async getAllUsers() {
    const users = await prisma.user.findMany();
    return UserResponseDTO.fromManyPrismaUsers(users);
  }
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{DTO használata service-ben (folyt.)}
  \begin{block}{UserService példa (4)}
    \begin{lstlisting}[language=JavaScript]
  async getUserById(id) {
    const user = await prisma.user.findUnique({
      where: { id }
    });
    if (!user) return null;
    return UserResponseDTO.fromPrismaUser(user);
  }
}
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Express integráció DTO-val}
  \begin{block}{REST endpoint (POST)}
    \begin{lstlisting}[language=JavaScript]
const express = require('express');
const userService = new UserService();
const app = express();
app.post('/api/users', async (req, res) => {
  try {
    const createDTO = new CreateUserDTO(req.body);
    const userDTO = await userService.createUser(createDTO);
    res.status(201).json(userDTO);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Express integráció DTO-val (folyt.)}
  \begin{block}{REST endpoint (GET)}
    \begin{lstlisting}[language=JavaScript]
app.get('/api/users', async (req, res) => {
  const usersDTO = await userService.getAllUsers();
  res.json(usersDTO);
});
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Update DTO - Constructor}
  \begin{block}{UpdateUserDTO}
    \begin{lstlisting}[language=JavaScript]
class UpdateUserDTO {
  constructor(data) {
    // Csak a megadott mezők kerülnek be
    if (data.email !== undefined) 
      this.email = data.email;
    if (data.name !== undefined) 
      this.name = data.name;
    if (data.password !== undefined)
      this.password = data.password;
  }
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Update DTO - Validáció}
  \begin{block}{UpdateUserDTO - Validate}
    \begin{lstlisting}[language=JavaScript]
class UpdateUserDTO {
  // ...
  
  validate() {
    if (this.email && !this.email.includes('@')) {
      throw new Error('Érvénytelen email');
    }
    if (this.password && this.password.length < 8) {
      throw new Error('Jelszó túl rövid');
    }
    return true;
  }
}
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Nested DTO - Relációkkal}
  \begin{block}{PostWithAuthorDTO}
    \begin{lstlisting}[language=JavaScript]
class PostWithAuthorDTO {
  constructor(post) {
    this.id = post.id;
    this.title = post.title;
    this.content = post.content;
    this.createdAt = post.createdAt;
    // Nested DTO az author-hoz
    if (post.author) {
      this.author = new UserResponseDTO(post.author);
    }
  }
  static fromPrismaPost(post) {
    return new PostWithAuthorDTO(post);
  }
}
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Nested DTO használata}
  \begin{block}{Service metódus}
    \begin{lstlisting}[language=JavaScript]
class PostService {
  async getPostWithAuthor(postId) {
    const post = await prisma.post.findUnique({
      where: { id: postId },
      include: {
        author: true
      }
    });
    
    if (!post) return null;
    
    return PostWithAuthorDTO.fromPrismaPost(post);
  }
}
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Validációs könyvtár - Joi}
  \begin{block}{Joi telepítése}
    \begin{lstlisting}[language=bash]
npm install joi
    \end{lstlisting}
  \end{block}
  
  \begin{block}{Validációs séma}
    \begin{lstlisting}[language=JavaScript]
const Joi = require('joi');

const createUserSchema = Joi.object({
  email: Joi.string().email().required(),
  password: Joi.string().min(8).required(),
  name: Joi.string().optional()
});
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{DTO Joi validációval}
  \begin{block}{CreateUserDTO sémával}
    \begin{lstlisting}[language=JavaScript]
class CreateUserDTO {
  constructor(data) {
    const { error, value } = 
      createUserSchema.validate(data);
    if (error) 
      throw new Error(error.details[0].message);
    Object.assign(this, value);
  }
}
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Pagináció DTO}
  \begin{block}{PaginatedResponseDTO}
    \begin{lstlisting}[language=JavaScript]
class PaginatedResponseDTO {
  constructor(data, total, page, pageSize) {
    this.data = data;
    this.pagination = {
      total, page, pageSize,
      totalPages: Math.ceil(total / pageSize)
    };
  }
}
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Pagináció DTO használata}
  \begin{block}{PaginatedResponseDTOUsage}
    \begin{lstlisting}[language=JavaScript]
// Használat
const users = await prisma.user.findMany({
  skip: (page - 1) * pageSize, take: pageSize
});
const total = await prisma.user.count();
return new PaginatedResponseDTO(
  UserResponseDTO.fromManyPrismaUsers(users),
  total, page, pageSize
);
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Szűrési DTO - Constructor}
  \begin{block}{UserFilterDTO}
    \begin{lstlisting}[language=JavaScript]
class UserFilterDTO {
  constructor(query) {
    this.email = query.email;
    this.name = query.name;
    this.page = parseInt(query.page) || 1;
    this.pageSize = parseInt(query.pageSize) || 10;
  }
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Szűrési DTO - Prisma konverzió}
  \begin{block}{toPrismaWhere módszer}
    \begin{lstlisting}[language=JavaScript]
class UserFilterDTO {
  // ...
  
  toPrismaWhere() {
    const where = {};
    if (this.email)
      where.email = { contains: this.email };
    if (this.name)
      where.name = { contains: this.name };
    return where;
  }
}
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Szűrési DTO használata}
  \begin{block}{Express endpoint szűréssel (1)}
    \begin{lstlisting}[language=JavaScript]
app.get('/api/users', async (req, res) => {
  const filterDTO = new UserFilterDTO(req.query);
  const where = filterDTO.toPrismaWhere();
  const users = await prisma.user.findMany({
    where,
    skip: (filterDTO.page - 1) * filterDTO.pageSize,
    take: filterDTO.pageSize
  });
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Szűrési DTO használata (folyt.)}
  \begin{block}{Express endpoint szűréssel (2)}
    \begin{lstlisting}[language=JavaScript]
  const total = await prisma.user.count({ where });
  const result = new PaginatedResponseDTO(
    UserResponseDTO.fromManyPrismaUsers(users),
    total, filterDTO.page, filterDTO.pageSize
  );
  res.json(result);
});
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Mapper függvények}
  \begin{block}{Alternatív megközelítés (1)}
    \begin{lstlisting}[language=JavaScript]
function toUserResponseDTO(user) {
  return {
    id: user.id,
    email: user.email,
    name: user.name
  };
}
function toUserResponseDTOs(users) {
  return users.map(toUserResponseDTO);
}
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Mapper függvények (folyt.)}
  \begin{block}{Alternatív megközelítés (2)}
    \begin{lstlisting}[language=JavaScript]
module.exports = {
  toUserResponseDTO,
  toUserResponseDTOs
};
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Mapper használata}
  \begin{block}{Service-ben (1)}
    \begin{lstlisting}[language=JavaScript]
const { toUserResponseDTO, toUserResponseDTOs }
  = require('./dto/user.mapper');
class UserService {
  async getAllUsers() {
    const users = await prisma.user.findMany();
    return toUserResponseDTOs(users);
  }
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Mapper használata (folyt.)}
  \begin{block}{Service-ben (2)}
    \begin{lstlisting}[language=JavaScript]
  async getUserById(id) {
    const user = await prisma.user.findUnique({
      where: { id }
    });
    if (!user) return null;
    return toUserResponseDTO(user);
  }
}
    \end{lstlisting}
  \end{block}
\end{frame}

\begin{frame}{DTO Best Practices}
  \begin{block}{Ajánlások}
    \begin{itemize}
      \item Külön DTO minden use case-hez (Create, Update, Response)
      \item Ne add vissza közvetlenül a Prisma modellt
      \item Validálj minden bejövő adatot
      \item Használj validációs könyvtárat (Joi, Yup, Zod)
      \item Tartsd egyszerűnek - ne túlbonyolítsd
      \item Dokumentáld a DTO mezőket
    \end{itemize}
  \end{block}
\end{frame}

\begin{frame}{DTO vs Prisma Select}
  \begin{columns}
    \begin{column}{0.48\textwidth}
      \begin{block}{Prisma Select}
        \begin{itemize}
          \item Egyszerű mezők kiválasztása
          \item Kevesebb kód
          \item Nincs validáció
          \item DB szintű szűrés
        \end{itemize}
      \end{block}
    \end{column}
    \begin{column}{0.48\textwidth}
      \begin{block}{DTO}
        \begin{itemize}
          \item Komplex transzformációk
          \item Validáció és logika
          \item Típusbiztonság
          \item API verziókezelés
        \end{itemize}
      \end{block}
    \end{column}
  \end{columns}
  
  \vspace{0.5cm}
  
  \begin{alertblock}{Kombináld őket!}
    Használd a Prisma select-et a DB szinten, és DTO-t a transzformációhoz.
  \end{alertblock}
\end{frame}

\begin{frame}{Összefoglalás}
  \begin{block}{DTO előnyei}
    \begin{itemize}
      \item \textbf{Biztonság} - Érzékeny adatok elrejtése
      \item \textbf{Validáció} - Adatok ellenőrzése
      \item \textbf{Transzformáció} - Adatok átalakítása
      \item \textbf{Dokumentáció} - Világos API szerződés
      \item \textbf{Karbantarthatóság} - Könnyebb változtatások
      \item \textbf{Tesztelhetőség} - Egyszerűbb unit tesztek
    \end{itemize}
  \end{block}
\end{frame}
