# User Management Backend - Gyakorlat ## Projekt Célja User kezelő backend implementálása CQRS pattern és layered architecture használatával. ## Technológiák - Node.js + Express - Prisma ORM + SQLite - JavaScript (nem TypeScript) - Layered Architecture - CQRS Pattern ## Projekt Struktúra ``` ├── prisma/ │ └── schema.prisma # Adatbázis séma (TODO) ├── src/ │ ├── api/ # API réteg │ │ ├── controllers/ │ │ │ └── UserController.js # Kész │ │ └── routes/ │ │ └── user.routes.js # Kész │ ├── application/ # Alkalmazási logika │ │ └── user/ │ │ ├── command/ │ │ │ ├── CreateUserCommand.js # Kész │ │ │ ├── CreateUserHandler.js # TODO │ │ │ ├── UpdateUserCommand.js # Kész │ │ │ ├── UpdateUserHandler.js # TODO │ │ │ ├── DeleteUserCommand.js # Kész │ │ │ └── DeleteUserHandler.js # TODO │ │ └── query/ │ │ ├── GetUserQuery.js # Kész │ │ ├── GetUserHandler.js # TODO │ │ ├── GetAllUsersQuery.js # Kész │ │ └── GetAllUsersHandler.js # TODO │ ├── domain/ # Domain réteg │ │ ├── interfaces/ │ │ │ └── IUserRepository.js # Kész (interfész definíció) │ │ └── models/ │ │ └── User.js # TODO │ └── infrastructure/ # Infrastruktúra réteg │ ├── database/ │ │ └── prisma.js # Kész │ └── repositories/ │ └── UserRepository.js # TODO (extends IUserRepository) ├── .env # Kész ├── package.json # Kész └── server.js # Kész ``` ## Indítás ```bash npm install npx prisma generate npx prisma migrate dev --name init npm run dev ``` Szerver: http://localhost:3000 ## Feladatok ### 1. Prisma Schema (10 perc) Fájl: `prisma/schema.prisma` Definiáld a User modelt a TODO komment helyén: ```prisma model User { id Int @id @default(autoincrement()) email String @unique name String password String role String @default("user") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } ``` Migráció után ellenőrizd: `npx prisma studio` ### 2. UserRepository Implementáció (20 perc) Fájl: `src/infrastructure/repositories/UserRepository.js` A UserRepository már extends IUserRepository-t. Implementáld a metódusokat: **create(userData)** ```javascript return await prisma.user.create({ data: userData }); ``` **findById(id)** ```javascript return await prisma.user.findUnique({ where: { id: parseInt(id) } }); ``` **findByEmail(email)** ```javascript return await prisma.user.findUnique({ where: { email } }); ``` **findAll()** ```javascript return await prisma.user.findMany({ orderBy: { createdAt: 'desc' } }); ``` **update(id, userData)** ```javascript return await prisma.user.update({ where: { id: parseInt(id) }, data: userData }); ``` **delete(id)** ```javascript return await prisma.user.delete({ where: { id: parseInt(id) } }); ``` ### 3. Domain Model (10 perc) Fájl: `src/domain/models/User.js` ```javascript class User { constructor(id, email, name, password, role, createdAt, updatedAt) { this.id = id; this.email = email; this.name = name; this.password = password; this.role = role; this.createdAt = createdAt; this.updatedAt = updatedAt; } isAdmin() { return this.role === 'admin'; } canEdit(targetUser) { return this.isAdmin() || this.id === targetUser.id; } validate() { if (!this.email || !this.name || !this.password) { throw new Error('Email, name és password kötelező'); } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(this.email)) { throw new Error('Hibás email formátum'); } if (this.password.length < 6) { throw new Error('Jelszó min. 6 karakter'); } } } ``` ### 4. Command Handlers (25 perc) **CreateUserHandler** (`src/application/user/command/CreateUserHandler.js`) ```javascript const UserRepository = require('../../../infrastructure/repositories/UserRepository'); class CreateUserHandler { constructor() { this.userRepository = new UserRepository(); } async handle(command) { // 1. Validáció if (!command.email || !command.name || !command.password) { throw new Error('Email, name és password kötelező'); } // 2. Email formátum const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(command.email)) { throw new Error('Hibás email formátum'); } // 3. Duplikáció ellenőrzés const existing = await this.userRepository.findByEmail(command.email); if (existing) { throw new Error('Email már használatban'); } // 4. Létrehozás return await this.userRepository.create({ email: command.email, name: command.name, password: command.password, role: command.role || 'user' }); } } module.exports = CreateUserHandler; ``` **UpdateUserHandler** (`src/application/user/command/UpdateUserHandler.js`) ```javascript const UserRepository = require('../../../infrastructure/repositories/UserRepository'); class UpdateUserHandler { constructor() { this.userRepository = new UserRepository(); } async handle(command) { // 1. Létezés ellenőrzés const user = await this.userRepository.findById(command.id); if (!user) { throw new Error('User nem található'); } // 2. Email egyediség (ha változott) if (command.email && command.email !== user.email) { const existing = await this.userRepository.findByEmail(command.email); if (existing) { throw new Error('Email már használatban'); } } // 3. Frissítendő adatok const updateData = {}; if (command.email) updateData.email = command.email; if (command.name) updateData.name = command.name; if (command.role) updateData.role = command.role; // 4. Frissítés return await this.userRepository.update(command.id, updateData); } } module.exports = UpdateUserHandler; ``` **DeleteUserHandler** (`src/application/user/command/DeleteUserHandler.js`) ```javascript const UserRepository = require('../../../infrastructure/repositories/UserRepository'); class DeleteUserHandler { constructor() { this.userRepository = new UserRepository(); } async handle(command) { const user = await this.userRepository.findById(command.id); if (!user) { throw new Error('User nem található'); } await this.userRepository.delete(command.id); return { message: 'User törölve', id: command.id }; } } module.exports = DeleteUserHandler; ``` ### 5. Query Handlers (10 perc) **GetUserHandler** (`src/application/user/query/GetUserHandler.js`) ```javascript const UserRepository = require('../../../infrastructure/repositories/UserRepository'); class GetUserHandler { constructor() { this.userRepository = new UserRepository(); } async handle(query) { const user = await this.userRepository.findById(query.id); if (!user) { throw new Error('User nem található'); } return user; } } module.exports = GetUserHandler; ``` **GetAllUsersHandler** (`src/application/user/query/GetAllUsersHandler.js`) ```javascript const UserRepository = require('../../../infrastructure/repositories/UserRepository'); class GetAllUsersHandler { constructor() { this.userRepository = new UserRepository(); } async handle(query) { return await this.userRepository.findAll(); } } module.exports = GetAllUsersHandler; ``` ## Tesztelés ### Endpoints **Create User** ```bash POST http://localhost:3000/api/users Content-Type: application/json { "email": "test@test.com", "name": "Test User", "password": "password123" } ``` **Get All Users** ```bash GET http://localhost:3000/api/users ``` **Get User by ID** ```bash GET http://localhost:3000/api/users/1 ``` **Update User** ```bash PUT http://localhost:3000/api/users/1 Content-Type: application/json { "name": "Updated Name" } ``` **Delete User** ```bash DELETE http://localhost:3000/api/users/1 ``` ### Hibakezelés Tesztelése - Hiányzó email/name/password - Duplikált email - Nem létező user ID - Hibás email formátum ## Architektúra ### Layered Architecture ``` API Layer (routes, controllers) ↓ Application Layer (commands, queries, handlers) ↓ Domain Layer (models, interfaces) ↓ Infrastructure Layer (database, repositories) ``` ### CQRS Pattern **Command** - Írás (Create, Update, Delete) **Query** - Olvasás (Get, GetAll) Előnyök: - Szeparált felelősségek - Könnyebb skálázhatóság - Tiszta intent ## Parancsok ```bash npm run dev # fejlesztői szerver npm start # produkciós szerver npx prisma generate # Prisma client generálás npx prisma migrate dev # migráció npx prisma studio # adatbázis GUI ``` ## Gyakori Hibák **Cannot find module '@prisma/client'** → `npx prisma generate` **Port already in use** → `.env`-ben módosítsd a PORT értékét **Not implemented** → Implementáld a TODO-kat ## Bónusz 1. bcrypt jelszó hash-elés 2. JWT authentikáció 3. Request logging middleware 4. Joi validáció 5. Error handling middleware