masodik_gyak
This commit is contained in:
@@ -0,0 +1,423 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user