negyedik gyakorlat + megoldasok
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
.env
|
||||
*.log
|
||||
@@ -0,0 +1,119 @@
|
||||
# Első Gyakorlat - MINTA Megoldás
|
||||
|
||||
Ez a mappa tartalmazza az első gyakorlat **teljes, működő megoldását**.
|
||||
|
||||
## Amit implementáltunk:
|
||||
|
||||
### 1. **Layered Architecture** (Rétegezett Architektúra)
|
||||
- **API Layer**: Controllers, Routers - HTTP kérések kezelése
|
||||
- **Application Layer**: Commands, Queries, Handlers - Üzleti logika
|
||||
- **Domain Layer**: Interfaces (Repository pattern)
|
||||
- **Infrastructure Layer**: Repositories - Adatkezelés (JSON file-ok)
|
||||
|
||||
### 2. **CQRS Pattern** (Command Query Responsibility Segregation)
|
||||
- **Commands**: Írási műveletek (Create, Update, Delete)
|
||||
- **Queries**: Olvasási műveletek (GetAll, GetById)
|
||||
- **Handlers**: Command és Query végrehajtók
|
||||
|
||||
### 3. **Repository Pattern**
|
||||
- `IUserRepository` és `IPostRepository` interfészek
|
||||
- `UserRepository` és `PostRepository` implementációk
|
||||
- Adatok tárolása JSON file-okban
|
||||
|
||||
## Struktúra
|
||||
|
||||
```
|
||||
src/
|
||||
├── API/
|
||||
│ ├── server.js # Express szerver
|
||||
│ ├── controllers/
|
||||
│ │ ├── userController.js # User endpoint logika
|
||||
│ │ └── postController.js # Post endpoint logika
|
||||
│ └── routers/
|
||||
│ ├── userRouter.js # User route-ok
|
||||
│ └── postRouter.js # Post route-ok
|
||||
├── Application/
|
||||
│ ├── users/
|
||||
│ │ ├── command/ # User írási műveletek
|
||||
│ │ │ ├── createUserCommand.js
|
||||
│ │ │ ├── createUserCommandHandler.js
|
||||
│ │ │ ├── updateUserCommand.js
|
||||
│ │ │ ├── updateUserCommandHandler.js
|
||||
│ │ │ ├── deleteUserCommand.js
|
||||
│ │ │ └── deleteUserCommandHandler.js
|
||||
│ │ └── query/ # User olvasási műveletek
|
||||
│ │ ├── getAllUsersQuery.js
|
||||
│ │ ├── getAllUsersQueryHandler.js
|
||||
│ │ ├── getUserByIdQuery.js
|
||||
│ │ └── getUserByIdQueryHandler.js
|
||||
│ └── blogs/
|
||||
│ ├── command/ # Post írási műveletek
|
||||
│ └── query/ # Post olvasási műveletek
|
||||
├── Domain/
|
||||
│ ├── IUserRepository.js # User repository interface
|
||||
│ └── IPostRepository.js # Post repository interface
|
||||
└── Infrastructure/
|
||||
├── userRepository.js # User repository implementáció
|
||||
├── postRepository.js # Post repository implementáció
|
||||
├── user.json # User adatok
|
||||
└── post.json # Post adatok
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Users
|
||||
- `GET /api/users` - Összes user lekérése
|
||||
- `GET /api/users/:id` - Egy user lekérése
|
||||
- `POST /api/users` - Új user létrehozása
|
||||
- `PUT /api/users/:id` - User módosítása
|
||||
- `DELETE /api/users/:id` - User törlése
|
||||
|
||||
### Posts
|
||||
- `GET /api/posts` - Összes post lekérése
|
||||
- `GET /api/posts/:id` - Egy post lekérése
|
||||
- `GET /api/posts/user/:userId` - User összes postja
|
||||
- `POST /api/posts` - Új post létrehozása
|
||||
- `PUT /api/posts/:id` - Post módosítása
|
||||
- `DELETE /api/posts/:id` - Post törlése
|
||||
|
||||
## Indítás
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
A szerver elindul a `http://localhost:3000` címen.
|
||||
|
||||
## Példa Használat
|
||||
|
||||
### User létrehozása
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/users \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"age": 25
|
||||
}'
|
||||
```
|
||||
|
||||
### Post létrehozása
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/posts \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "My First Post",
|
||||
"content": "Hello World!",
|
||||
"author": "John Doe"
|
||||
}'
|
||||
```
|
||||
|
||||
## Tanulási Pontok
|
||||
|
||||
1. **Separation of Concerns**: Minden réteg saját felelősséggel rendelkezik
|
||||
2. **CQRS**: Írási és olvasási műveletek szétválasztása
|
||||
3. **Repository Pattern**: Adatkezelés absztrakciója
|
||||
4. **Dependency Injection**: Handler-ek megkapják a repository-t
|
||||
5. **Express.js**: RESTful API építése
|
||||
6. **File-based Storage**: JSON file-ok használata adatbázis helyett
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "elso-gyakorlat-minta",
|
||||
"version": "1.0.0",
|
||||
"description": "Complete reference implementation - First Exercise",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "nodemon src/API/server.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^5.2.1",
|
||||
"nodemon": "^3.1.11"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { PostRepository } from '../../Infrastructure/postRepository.js';
|
||||
import { GetAllPostsQuery } from '../../Application/blogs/query/getAllPostsQuery.js';
|
||||
import { GetAllPostsQueryHandler } from '../../Application/blogs/query/getAllPostsQueryHandler.js';
|
||||
import { GetPostByIdQuery } from '../../Application/blogs/query/getPostByIdQuery.js';
|
||||
import { GetPostByIdQueryHandler } from '../../Application/blogs/query/getPostByIdQueryHandler.js';
|
||||
import { GetPostsByUserIdQuery } from '../../Application/blogs/query/getPostsByUserIdQuery.js';
|
||||
import { GetPostsByUserIdQueryHandler } from '../../Application/blogs/query/getPostsByUserIdQueryHandler.js';
|
||||
import { CreatePostCommand } from '../../Application/blogs/command/createPostCommand.js';
|
||||
import { CreatePostCommandHandler } from '../../Application/blogs/command/createPostCommandHandler.js';
|
||||
import { UpdatePostCommand } from '../../Application/blogs/command/updatePostCommand.js';
|
||||
import { UpdatePostCommandHandler } from '../../Application/blogs/command/updatePostCommandHandler.js';
|
||||
import { DeletePostCommand } from '../../Application/blogs/command/deletePostCommand.js';
|
||||
import { DeletePostCommandHandler } from '../../Application/blogs/command/deletePostCommandHandler.js';
|
||||
|
||||
const postRepository = new PostRepository();
|
||||
const getAllPostsQueryHandler = new GetAllPostsQueryHandler(postRepository);
|
||||
const getPostByIdQueryHandler = new GetPostByIdQueryHandler(postRepository);
|
||||
const getPostsByUserIdQueryHandler = new GetPostsByUserIdQueryHandler(postRepository);
|
||||
const createPostCommandHandler = new CreatePostCommandHandler(postRepository);
|
||||
const updatePostCommandHandler = new UpdatePostCommandHandler(postRepository);
|
||||
const deletePostCommandHandler = new DeletePostCommandHandler(postRepository);
|
||||
|
||||
export class PostController {
|
||||
async getAll(req, res) {
|
||||
try {
|
||||
const query = new GetAllPostsQuery();
|
||||
const posts = await getAllPostsQueryHandler.handle(query);
|
||||
res.json(posts);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getById(req, res) {
|
||||
try {
|
||||
const query = new GetPostByIdQuery(req.params.id);
|
||||
const post = await getPostByIdQueryHandler.handle(query);
|
||||
res.json(post);
|
||||
} catch (error) {
|
||||
res.status(404).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getByUserId(req, res) {
|
||||
try {
|
||||
const query = new GetPostsByUserIdQuery(req.params.userId);
|
||||
const posts = await getPostsByUserIdQueryHandler.handle(query);
|
||||
res.json(posts);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async create(req, res) {
|
||||
try {
|
||||
const command = new CreatePostCommand(
|
||||
req.body.title,
|
||||
req.body.content,
|
||||
req.body.author
|
||||
);
|
||||
const post = await createPostCommandHandler.handle(command);
|
||||
res.status(201).json(post);
|
||||
} catch (error) {
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async update(req, res) {
|
||||
try {
|
||||
const command = new UpdatePostCommand(
|
||||
req.params.id,
|
||||
req.body.title,
|
||||
req.body.content,
|
||||
req.body.author
|
||||
);
|
||||
const post = await updatePostCommandHandler.handle(command);
|
||||
res.json(post);
|
||||
} catch (error) {
|
||||
res.status(404).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async delete(req, res) {
|
||||
try {
|
||||
const command = new DeletePostCommand(req.params.id);
|
||||
await deletePostCommandHandler.handle(command);
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
res.status(404).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { UserRepository } from '../../Infrastructure/userRepository.js';
|
||||
import { GetAllUsersQuery } from '../../Application/users/query/getAllUsersQuery.js';
|
||||
import { GetAllUsersQueryHandler } from '../../Application/users/query/getAllUsersQueryHandler.js';
|
||||
import { GetUserByIdQuery } from '../../Application/users/query/getUserByIdQuery.js';
|
||||
import { GetUserByIdQueryHandler } from '../../Application/users/query/getUserByIdQueryHandler.js';
|
||||
import { CreateUserCommand } from '../../Application/users/command/createUserCommand.js';
|
||||
import { CreateUserCommandHandler } from '../../Application/users/command/createUserCommandHandler.js';
|
||||
import { UpdateUserCommand } from '../../Application/users/command/updateUserCommand.js';
|
||||
import { UpdateUserCommandHandler } from '../../Application/users/command/updateUserCommandHandler.js';
|
||||
import { DeleteUserCommand } from '../../Application/users/command/deleteUserCommand.js';
|
||||
import { DeleteUserCommandHandler } from '../../Application/users/command/deleteUserCommandHandler.js';
|
||||
|
||||
const userRepository = new UserRepository();
|
||||
const getAllUsersQueryHandler = new GetAllUsersQueryHandler(userRepository);
|
||||
const getUserByIdQueryHandler = new GetUserByIdQueryHandler(userRepository);
|
||||
const createUserCommandHandler = new CreateUserCommandHandler(userRepository);
|
||||
const updateUserCommandHandler = new UpdateUserCommandHandler(userRepository);
|
||||
const deleteUserCommandHandler = new DeleteUserCommandHandler(userRepository);
|
||||
|
||||
export class UserController {
|
||||
async getAll(req, res) {
|
||||
try {
|
||||
const query = new GetAllUsersQuery();
|
||||
const users = await getAllUsersQueryHandler.handle(query);
|
||||
res.json(users);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getById(req, res) {
|
||||
try {
|
||||
const query = new GetUserByIdQuery(req.params.id);
|
||||
const user = await getUserByIdQueryHandler.handle(query);
|
||||
res.json(user);
|
||||
} catch (error) {
|
||||
res.status(404).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async create(req, res) {
|
||||
try {
|
||||
const command = new CreateUserCommand(
|
||||
req.body.name,
|
||||
req.body.email,
|
||||
req.body.age
|
||||
);
|
||||
const user = await createUserCommandHandler.handle(command);
|
||||
res.status(201).json(user);
|
||||
} catch (error) {
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async update(req, res) {
|
||||
try {
|
||||
const command = new UpdateUserCommand(
|
||||
req.params.id,
|
||||
req.body.name,
|
||||
req.body.email,
|
||||
req.body.age
|
||||
);
|
||||
const user = await updateUserCommandHandler.handle(command);
|
||||
res.json(user);
|
||||
} catch (error) {
|
||||
res.status(404).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async delete(req, res) {
|
||||
try {
|
||||
const command = new DeleteUserCommand(req.params.id);
|
||||
await deleteUserCommandHandler.handle(command);
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
res.status(404).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import express from 'express';
|
||||
import { PostController } from '../controllers/postController.js';
|
||||
|
||||
const router = express.Router();
|
||||
const postController = new PostController();
|
||||
|
||||
router.get('/', (req, res) => postController.getAll(req, res));
|
||||
router.get('/:id', (req, res) => postController.getById(req, res));
|
||||
router.get('/user/:userId', (req, res) => postController.getByUserId(req, res));
|
||||
router.post('/', (req, res) => postController.create(req, res));
|
||||
router.put('/:id', (req, res) => postController.update(req, res));
|
||||
router.delete('/:id', (req, res) => postController.delete(req, res));
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,13 @@
|
||||
import express from 'express';
|
||||
import { UserController } from '../controllers/userController.js';
|
||||
|
||||
const router = express.Router();
|
||||
const userController = new UserController();
|
||||
|
||||
router.get('/', (req, res) => userController.getAll(req, res));
|
||||
router.get('/:id', (req, res) => userController.getById(req, res));
|
||||
router.post('/', (req, res) => userController.create(req, res));
|
||||
router.put('/:id', (req, res) => userController.update(req, res));
|
||||
router.delete('/:id', (req, res) => userController.delete(req, res));
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,36 @@
|
||||
import express from 'express';
|
||||
import postRouter from './routers/postRouter.js';
|
||||
import userRouter from './routers/userRouter.js';
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// Middleware
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// Routes
|
||||
app.use('/api/posts', postRouter);
|
||||
app.use('/api/users', userRouter);
|
||||
|
||||
// Root endpoint
|
||||
app.get('/', (req, res) => {
|
||||
res.json({
|
||||
message: 'Welcome to the Blog API - MINTA Megoldás',
|
||||
endpoints: {
|
||||
posts: '/api/posts',
|
||||
users: '/api/users'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Error handling middleware
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
res.status(500).json({ error: 'Something went wrong!' });
|
||||
});
|
||||
|
||||
// Start server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on http://localhost:${PORT}`);
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
export class CreatePostCommand {
|
||||
constructor(title, content, author) {
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
this.author = author;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
export class CreatePostCommandHandler {
|
||||
constructor(postRepository) {
|
||||
this.postRepository = postRepository;
|
||||
}
|
||||
|
||||
async handle(command) {
|
||||
if (!command.title || !command.content) {
|
||||
throw new Error('Title and content are required');
|
||||
}
|
||||
|
||||
const postData = {
|
||||
title: command.title,
|
||||
content: command.content,
|
||||
author: command.author
|
||||
};
|
||||
|
||||
return await this.postRepository.create(postData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class DeletePostCommand {
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
export class DeletePostCommandHandler {
|
||||
constructor(postRepository) {
|
||||
this.postRepository = postRepository;
|
||||
}
|
||||
|
||||
async handle(command) {
|
||||
const result = await this.postRepository.delete(command.id);
|
||||
if (!result) {
|
||||
throw new Error('Post not found');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export class UpdatePostCommand {
|
||||
constructor(id, title, content, author) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
this.author = author;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
export class UpdatePostCommandHandler {
|
||||
constructor(postRepository) {
|
||||
this.postRepository = postRepository;
|
||||
}
|
||||
|
||||
async handle(command) {
|
||||
const postData = {
|
||||
title: command.title,
|
||||
content: command.content,
|
||||
author: command.author
|
||||
};
|
||||
|
||||
const post = await this.postRepository.update(command.id, postData);
|
||||
if (!post) {
|
||||
throw new Error('Post not found');
|
||||
}
|
||||
|
||||
return post;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class GetAllPostsQuery {
|
||||
constructor() {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export class GetAllPostsQueryHandler {
|
||||
constructor(postRepository) {
|
||||
this.postRepository = postRepository;
|
||||
}
|
||||
|
||||
async handle(query) {
|
||||
return await this.postRepository.getAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class GetPostByIdQuery {
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export class GetPostByIdQueryHandler {
|
||||
constructor(postRepository) {
|
||||
this.postRepository = postRepository;
|
||||
}
|
||||
|
||||
async handle(query) {
|
||||
const post = await this.postRepository.getById(query.id);
|
||||
if (!post) {
|
||||
throw new Error('Post not found');
|
||||
}
|
||||
return post;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class GetPostsByUserIdQuery {
|
||||
constructor(userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
export class GetPostsByUserIdQueryHandler {
|
||||
constructor(postRepository) {
|
||||
this.postRepository = postRepository;
|
||||
}
|
||||
|
||||
async handle(query) {
|
||||
return await this.postRepository.getByUserId(query.userId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export class CreateUserCommand {
|
||||
constructor(name, email, age) {
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
export class CreateUserCommandHandler {
|
||||
constructor(userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
async handle(command) {
|
||||
if (!command.name || !command.email) {
|
||||
throw new Error('Name and email are required');
|
||||
}
|
||||
|
||||
const userData = {
|
||||
name: command.name,
|
||||
email: command.email,
|
||||
age: command.age
|
||||
};
|
||||
|
||||
return await this.userRepository.create(userData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class DeleteUserCommand {
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
export class DeleteUserCommandHandler {
|
||||
constructor(userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
async handle(command) {
|
||||
const result = await this.userRepository.delete(command.id);
|
||||
if (!result) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export class UpdateUserCommand {
|
||||
constructor(id, name, email, age) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
export class UpdateUserCommandHandler {
|
||||
constructor(userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
async handle(command) {
|
||||
const userData = {
|
||||
name: command.name,
|
||||
email: command.email,
|
||||
age: command.age
|
||||
};
|
||||
|
||||
const user = await this.userRepository.update(command.id, userData);
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class GetAllUsersQuery {
|
||||
constructor() {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export class GetAllUsersQueryHandler {
|
||||
constructor(userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
async handle(query) {
|
||||
return await this.userRepository.getAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class GetUserByIdQuery {
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export class GetUserByIdQueryHandler {
|
||||
constructor(userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
async handle(query) {
|
||||
const user = await this.userRepository.getById(query.id);
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
export class IPostRepository {
|
||||
async getAll() {
|
||||
throw new Error('Method not implemented');
|
||||
}
|
||||
|
||||
async getById(id) {
|
||||
throw new Error('Method not implemented');
|
||||
}
|
||||
|
||||
async getByUserId(userId) {
|
||||
throw new Error('Method not implemented');
|
||||
}
|
||||
|
||||
async create(post) {
|
||||
throw new Error('Method not implemented');
|
||||
}
|
||||
|
||||
async update(id, postData) {
|
||||
throw new Error('Method not implemented');
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
throw new Error('Method not implemented');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
export class IUserRepository {
|
||||
async getAll() {
|
||||
throw new Error('Method not implemented');
|
||||
}
|
||||
|
||||
async getById(id) {
|
||||
throw new Error('Method not implemented');
|
||||
}
|
||||
|
||||
async create(user) {
|
||||
throw new Error('Method not implemented');
|
||||
}
|
||||
|
||||
async update(id, userData) {
|
||||
throw new Error('Method not implemented');
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
throw new Error('Method not implemented');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,65 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { IPostRepository } from '../Domain/IPostRepository.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const POST_FILE = path.join(__dirname, 'post.json');
|
||||
|
||||
export class PostRepository extends IPostRepository {
|
||||
async getAll() {
|
||||
try {
|
||||
const data = await fs.readFile(POST_FILE, 'utf-8');
|
||||
return JSON.parse(data);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getById(id) {
|
||||
const posts = await this.getAll();
|
||||
return posts.find(post => post.id === id);
|
||||
}
|
||||
|
||||
async getByUserId(userId) {
|
||||
const posts = await this.getAll();
|
||||
return posts.filter(post => post.userId === userId);
|
||||
}
|
||||
|
||||
async create(post) {
|
||||
const posts = await this.getAll();
|
||||
const newPost = {
|
||||
id: Date.now().toString(),
|
||||
...post,
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
posts.push(newPost);
|
||||
await fs.writeFile(POST_FILE, JSON.stringify(posts, null, 2));
|
||||
return newPost;
|
||||
}
|
||||
|
||||
async update(id, postData) {
|
||||
const posts = await this.getAll();
|
||||
const index = posts.findIndex(post => post.id === id);
|
||||
if (index === -1) return null;
|
||||
|
||||
posts[index] = {
|
||||
...posts[index],
|
||||
...postData,
|
||||
id: posts[index].id,
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
await fs.writeFile(POST_FILE, JSON.stringify(posts, null, 2));
|
||||
return posts[index];
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
const posts = await this.getAll();
|
||||
const filteredPosts = posts.filter(post => post.id !== id);
|
||||
if (posts.length === filteredPosts.length) return false;
|
||||
|
||||
await fs.writeFile(POST_FILE, JSON.stringify(filteredPosts, null, 2));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,60 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { IUserRepository } from '../Domain/IUserRepository.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const USER_FILE = path.join(__dirname, 'user.json');
|
||||
|
||||
export class UserRepository extends IUserRepository {
|
||||
async getAll() {
|
||||
try {
|
||||
const data = await fs.readFile(USER_FILE, 'utf-8');
|
||||
return JSON.parse(data);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getById(id) {
|
||||
const users = await this.getAll();
|
||||
return users.find(user => user.id === id);
|
||||
}
|
||||
|
||||
async create(user) {
|
||||
const users = await this.getAll();
|
||||
const newUser = {
|
||||
id: Date.now().toString(),
|
||||
...user,
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
users.push(newUser);
|
||||
await fs.writeFile(USER_FILE, JSON.stringify(users, null, 2));
|
||||
return newUser;
|
||||
}
|
||||
|
||||
async update(id, userData) {
|
||||
const users = await this.getAll();
|
||||
const index = users.findIndex(user => user.id === id);
|
||||
if (index === -1) return null;
|
||||
|
||||
users[index] = {
|
||||
...users[index],
|
||||
...userData,
|
||||
id: users[index].id,
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
await fs.writeFile(USER_FILE, JSON.stringify(users, null, 2));
|
||||
return users[index];
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
const users = await this.getAll();
|
||||
const filteredUsers = users.filter(user => user.id !== id);
|
||||
if (users.length === filteredUsers.length) return false;
|
||||
|
||||
await fs.writeFile(USER_FILE, JSON.stringify(filteredUsers, null, 2));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# Database
|
||||
DATABASE_URL="postgresql://postgres:postgres@localhost:5433/blog_db?schema=public"
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6380
|
||||
|
||||
# JWT
|
||||
JWT_SECRET=your-secret-key-change-this-in-production
|
||||
JWT_EXPIRES_IN=7d
|
||||
JWT_REFRESH_SECRET=your-refresh-secret-key-change-this
|
||||
JWT_REFRESH_EXPIRES_IN=30d
|
||||
|
||||
# Server
|
||||
PORT=3001
|
||||
NODE_ENV=development
|
||||
|
||||
# Cookie
|
||||
COOKIE_SECRET=your-cookie-secret-change-this
|
||||
@@ -0,0 +1,8 @@
|
||||
node_modules/
|
||||
.env
|
||||
dist/
|
||||
.DS_Store
|
||||
*.log
|
||||
coverage/
|
||||
.vscode/
|
||||
.idea/
|
||||
@@ -0,0 +1,89 @@
|
||||
# Harmadik Gyakorlat - MINTA Megoldás
|
||||
|
||||
Ez a mappa tartalmazza a harmadik gyakorlat **teljes, működő megoldását**.
|
||||
|
||||
## Amit implementáltunk:
|
||||
|
||||
### 1. **Authentication (Hitelesítés)**
|
||||
- User regisztráció jelszó hash-eléssel (bcrypt)
|
||||
- User bejelentkezés JWT tokenekkel
|
||||
- Cookie-based session kezelés
|
||||
- Token frissítés (refresh token)
|
||||
- Kijelentkezés
|
||||
|
||||
### 2. **Authorization (Jogosultságkezelés)**
|
||||
- JWT token validálás middleware
|
||||
- Role-based access control (RBAC)
|
||||
- Resource ownership ellenőrzés
|
||||
- Védett endpoint-ok
|
||||
|
||||
### 3. **Security Best Practices**
|
||||
- HttpOnly cookie-k
|
||||
- Secure cookie-k (production)
|
||||
- JWT token expiry
|
||||
- Password hashing (bcrypt)
|
||||
- Input validáció
|
||||
|
||||
## Indítás
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run docker:up
|
||||
npx prisma generate
|
||||
npx prisma migrate dev
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Auth Endpoints
|
||||
- `POST /api/auth/register` - Regisztráció
|
||||
- `POST /api/auth/login` - Bejelentkezés
|
||||
- `POST /api/auth/logout` - Kijelentkezés (védett)
|
||||
- `POST /api/auth/refresh` - Token frissítés
|
||||
- `GET /api/auth/me` - Aktuális user (védett)
|
||||
|
||||
### Blog Endpoints
|
||||
- `GET /api/blogs` - Összes blog (publikus)
|
||||
- `GET /api/blogs/:id` - Egy blog (publikus)
|
||||
- `POST /api/blogs` - Blog létrehozás (védett)
|
||||
- `PUT /api/blogs/:id` - Blog módosítás (védett + ownership)
|
||||
- `DELETE /api/blogs/:id` - Blog törlés (védett + ownership)
|
||||
|
||||
## Példa Használat
|
||||
|
||||
```bash
|
||||
# Regisztráció
|
||||
curl -X POST http://localhost:3000/api/auth/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "test@test.com",
|
||||
"username": "testuser",
|
||||
"password": "Test1234"
|
||||
}'
|
||||
|
||||
# Login
|
||||
curl -X POST http://localhost:3000/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "test@test.com",
|
||||
"password": "Test1234"
|
||||
}'
|
||||
|
||||
# Blog létrehozás (védett)
|
||||
curl -X POST http://localhost:3000/api/blogs \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "My Blog",
|
||||
"content": "Blog content..."
|
||||
}'
|
||||
```
|
||||
|
||||
## Tanulási Pontok
|
||||
|
||||
1. **JWT Tokens**: Access és Refresh tokenek használata
|
||||
2. **Bcrypt**: Jelszó hash-elés és validálás
|
||||
3. **Cookie-based Auth**: HttpOnly, Secure cookie-k
|
||||
4. **Authorization Middleware**: Token validálás, role check, ownership
|
||||
5. **Security**: Best practices implementálása
|
||||
@@ -0,0 +1,38 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: blog_postgres_minta
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "5433:5432"
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: blog_db
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: blog_redis_minta
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "6380:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "blog-auth-practice-minta",
|
||||
"version": "1.0.0",
|
||||
"description": "MINTA - Backend gyakorló feladat - Authentication & Authorization",
|
||||
"main": "src/index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node --watch src/index.js",
|
||||
"start": "node src/index.js",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate": "prisma migrate dev",
|
||||
"prisma:studio": "prisma studio",
|
||||
"docker:up": "docker-compose up -d",
|
||||
"docker:down": "docker-compose down",
|
||||
"setup": "npm install && npm run docker:up && npm run prisma:migrate && npm run prisma:generate"
|
||||
},
|
||||
"keywords": ["backend", "authentication", "authorization", "jwt", "prisma", "cqrs"],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.9.1",
|
||||
"bcrypt": "^5.1.1",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"dotenv": "^16.4.1",
|
||||
"express": "^4.18.2",
|
||||
"ioredis": "^5.3.2",
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prisma": "^5.9.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
email String @unique
|
||||
username String @unique
|
||||
password String
|
||||
role Role @default(USER)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
blogs Blog[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model Blog {
|
||||
id String @id @default(uuid())
|
||||
title String
|
||||
content String
|
||||
published Boolean @default(false)
|
||||
authorId String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("blogs")
|
||||
}
|
||||
|
||||
enum Role {
|
||||
USER
|
||||
ADMIN
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
import { UserRepository } from '../../infrastructure/repositories/UserRepository.js';
|
||||
import {
|
||||
hashPassword,
|
||||
verifyPassword,
|
||||
generateAccessToken,
|
||||
generateRefreshToken,
|
||||
setAuthCookies,
|
||||
clearAuthCookies,
|
||||
validateRegisterInput,
|
||||
validateLoginInput,
|
||||
verifyRefreshToken,
|
||||
createSession,
|
||||
deleteSession
|
||||
} from '../../infrastructure/auth/authUtils.js';
|
||||
|
||||
export class AuthController {
|
||||
constructor(userRepository) {
|
||||
this.userRepository = userRepository || new UserRepository();
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/register
|
||||
* Új felhasználó regisztrációja
|
||||
*/
|
||||
async register(req, res) {
|
||||
try {
|
||||
const { email, username, password } = req.body;
|
||||
|
||||
// 1. Input validáció
|
||||
const validation = validateRegisterInput(req.body);
|
||||
if (!validation.isValid) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
errors: validation.errors
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Email uniqueness ellenőrzés
|
||||
const existingUserByEmail = await this.userRepository.findByEmail(email);
|
||||
if (existingUserByEmail) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Email already in use'
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Username uniqueness ellenőrzés
|
||||
const existingUserByUsername = await this.userRepository.findByUsername(username);
|
||||
if (existingUserByUsername) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Username already taken'
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Jelszó hash-elés
|
||||
const hashedPassword = await hashPassword(password);
|
||||
|
||||
// 5. User létrehozása
|
||||
const user = await this.userRepository.create({
|
||||
email,
|
||||
username,
|
||||
password: hashedPassword,
|
||||
role: 'USER'
|
||||
});
|
||||
|
||||
// 6. JWT tokenek generálása
|
||||
const accessToken = generateAccessToken(user);
|
||||
const refreshToken = generateRefreshToken(user);
|
||||
|
||||
// 7. Cookie-k beállítása
|
||||
setAuthCookies(res, accessToken, refreshToken);
|
||||
|
||||
// 8. Redis session (opcionális)
|
||||
await createSession(user.id, { email: user.email, username: user.username });
|
||||
|
||||
// 9. Válasz (jelszó nélkül!)
|
||||
const { password: _, ...userWithoutPassword } = user;
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: 'User registered successfully',
|
||||
data: {
|
||||
user: userWithoutPassword,
|
||||
accessToken,
|
||||
refreshToken
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Register error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/login
|
||||
* Bejelentkezés
|
||||
*/
|
||||
async login(req, res) {
|
||||
try {
|
||||
const { email, username, password } = req.body;
|
||||
|
||||
// 1. Input validáció
|
||||
const validation = validateLoginInput(req.body);
|
||||
if (!validation.isValid) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
errors: validation.errors
|
||||
});
|
||||
}
|
||||
|
||||
// 2. User keresése (email VAGY username alapján)
|
||||
let user;
|
||||
if (email) {
|
||||
user = await this.userRepository.findByEmail(email);
|
||||
} else if (username) {
|
||||
user = await this.userRepository.findByUsername(username);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Jelszó ellenőrzés
|
||||
const isPasswordValid = await verifyPassword(password, user.password);
|
||||
if (!isPasswordValid) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
// 4. JWT tokenek generálása
|
||||
const accessToken = generateAccessToken(user);
|
||||
const refreshToken = generateRefreshToken(user);
|
||||
|
||||
// 5. Cookie-k beállítása
|
||||
setAuthCookies(res, accessToken, refreshToken);
|
||||
|
||||
// 6. Redis session
|
||||
await createSession(user.id, { email: user.email, username: user.username });
|
||||
|
||||
// 7. Válasz (jelszó nélkül!)
|
||||
const { password: _, ...userWithoutPassword } = user;
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Login successful',
|
||||
data: {
|
||||
user: userWithoutPassword,
|
||||
accessToken,
|
||||
refreshToken
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/logout
|
||||
* Kijelentkezés
|
||||
*/
|
||||
async logout(req, res) {
|
||||
try {
|
||||
// 1. Cookie-k törlése
|
||||
clearAuthCookies(res);
|
||||
|
||||
// 2. Redis session törlése
|
||||
if (req.user && req.user.id) {
|
||||
await deleteSession(req.user.id);
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Logged out successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/refresh
|
||||
* Token frissítés
|
||||
*/
|
||||
async refreshToken(req, res) {
|
||||
try {
|
||||
// 1. Refresh token kiolvasása
|
||||
const refreshToken = req.cookies.refreshToken;
|
||||
if (!refreshToken) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Refresh token not found'
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Refresh token validálás
|
||||
const decoded = verifyRefreshToken(refreshToken);
|
||||
|
||||
// 3. User lekérése
|
||||
const user = await this.userRepository.findById(decoded.userId);
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'User not found'
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Új access token generálás
|
||||
const newAccessToken = generateAccessToken(user);
|
||||
|
||||
// 5. Cookie frissítése
|
||||
res.cookie('accessToken', newAccessToken, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'strict',
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
accessToken: newAccessToken
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Refresh token error:', error);
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
error: 'Invalid refresh token'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/auth/me
|
||||
* Bejelentkezett user adatai
|
||||
*/
|
||||
async getCurrentUser(req, res) {
|
||||
try {
|
||||
// req.user-t az authenticateToken middleware állította be
|
||||
const { password: _, ...userWithoutPassword } = req.user;
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
user: userWithoutPassword
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Get current user error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { BlogRepository } from '../../infrastructure/repositories/BlogRepository.js';
|
||||
|
||||
export class BlogController {
|
||||
constructor() {
|
||||
this.blogRepository = new BlogRepository();
|
||||
}
|
||||
|
||||
async getAll(req, res) {
|
||||
try {
|
||||
const blogs = await this.blogRepository.findAll();
|
||||
res.json({
|
||||
success: true,
|
||||
data: blogs
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getById(req, res) {
|
||||
try {
|
||||
const blog = await this.blogRepository.findById(req.params.id);
|
||||
if (!blog) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Blog not found'
|
||||
});
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
data: blog
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async create(req, res) {
|
||||
try {
|
||||
const { title, content, published } = req.body;
|
||||
|
||||
if (!title || !content) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Title and content are required'
|
||||
});
|
||||
}
|
||||
|
||||
const blog = await this.blogRepository.create({
|
||||
title,
|
||||
content,
|
||||
published: published || false,
|
||||
authorId: req.user.id
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: blog,
|
||||
message: 'Blog created successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async update(req, res) {
|
||||
try {
|
||||
const { title, content, published } = req.body;
|
||||
|
||||
const updateData = {};
|
||||
if (title) updateData.title = title;
|
||||
if (content) updateData.content = content;
|
||||
if (typeof published !== 'undefined') updateData.published = published;
|
||||
|
||||
const blog = await this.blogRepository.update(req.params.id, updateData);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: blog,
|
||||
message: 'Blog updated successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async delete(req, res) {
|
||||
try {
|
||||
await this.blogRepository.delete(req.params.id);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Blog deleted successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import {
|
||||
extractTokenFromRequest,
|
||||
verifyAccessToken
|
||||
} from '../../infrastructure/auth/authUtils.js';
|
||||
import { UserRepository } from '../../infrastructure/repositories/UserRepository.js';
|
||||
|
||||
const userRepository = new UserRepository();
|
||||
|
||||
/**
|
||||
* Authentication Middleware
|
||||
*
|
||||
* Ellenőrzi a JWT tokent és beállítja req.user-t
|
||||
*/
|
||||
export async function authenticateToken(req, res, next) {
|
||||
try {
|
||||
// 1. Token kiolvasása (cookie vagy Authorization header)
|
||||
const token = extractTokenFromRequest(req);
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Access token required'
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Token validálás
|
||||
const decoded = verifyAccessToken(token);
|
||||
|
||||
// 3. User lekérése az adatbázisból
|
||||
const user = await userRepository.findById(decoded.userId);
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'User not found'
|
||||
});
|
||||
}
|
||||
|
||||
// 4. User hozzáadása a request objektumhoz
|
||||
req.user = user;
|
||||
|
||||
// 5. Továbblépés a következő middleware-re
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Authentication error:', error);
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Invalid or expired token'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorization Middleware - Role Check
|
||||
*
|
||||
* Ellenőrzi, hogy a usernek megfelel ő role-ja van-e
|
||||
*
|
||||
* @param {string[]} allowedRoles - Engedélyezett role-ok listája
|
||||
* @returns {Function} Express middleware
|
||||
*/
|
||||
export function requireRole(allowedRoles) {
|
||||
return (req, res, next) => {
|
||||
try {
|
||||
// 1. Ellenőrzés: req.user létezik? (authenticateToken után fut)
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Authentication required'
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Role check: van-e a usernek megfelelő role-ja?
|
||||
if (!allowedRoles.includes(req.user.role)) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Insufficient permissions'
|
||||
});
|
||||
}
|
||||
|
||||
// 3. OK, továbblépés
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Authorization error:', error);
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Authorization failed'
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resource Ownership Check
|
||||
*
|
||||
* Ellenőrzi, hogy a user tulajdonosa-e az adott resource-nak
|
||||
* VAGY admin role-ja van
|
||||
*
|
||||
* @param {Function} getResourceOwnerId - Async függvény ami visszaadja a resource owner ID-t
|
||||
* @returns {Function} Express middleware
|
||||
*/
|
||||
export function checkOwnership(getResourceOwnerId) {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
// 1. User ellenőrzés
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Authentication required'
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Resource owner ID lekérése
|
||||
const ownerId = await getResourceOwnerId(req);
|
||||
|
||||
if (!ownerId) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Resource not found'
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Ownership ellenőrzés: user tulajdonos VAGY admin
|
||||
const isOwner = req.user.id === ownerId;
|
||||
const isAdmin = req.user.role === 'ADMIN';
|
||||
|
||||
if (!isOwner && !isAdmin) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'You do not have permission to access this resource'
|
||||
});
|
||||
}
|
||||
|
||||
// 4. OK, továbblépés
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Ownership check error:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: 'Ownership verification failed'
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export function errorHandler(err, req, res, next) {
|
||||
console.error('Error:', err);
|
||||
|
||||
const statusCode = err.statusCode || 500;
|
||||
const message = err.message || 'Internal Server Error';
|
||||
|
||||
res.status(statusCode).json({
|
||||
success: false,
|
||||
error: message,
|
||||
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
|
||||
});
|
||||
}
|
||||
|
||||
export function notFoundHandler(req, res) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: 'Endpoint not found'
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import express from 'express';
|
||||
import { AuthController } from '../controllers/AuthController.js';
|
||||
import { authenticateToken } from '../middlewares/authMiddleware.js';
|
||||
|
||||
const router = express.Router();
|
||||
const authController = new AuthController();
|
||||
|
||||
// Publikus route-ok
|
||||
router.post('/register', (req, res) => authController.register(req, res));
|
||||
router.post('/login', (req, res) => authController.login(req, res));
|
||||
router.post('/refresh', (req, res) => authController.refreshToken(req, res));
|
||||
|
||||
// Védett route-ok (authenticateToken middleware)
|
||||
router.post('/logout', authenticateToken, (req, res) => authController.logout(req, res));
|
||||
router.get('/me', authenticateToken, (req, res) => authController.getCurrentUser(req, res));
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,38 @@
|
||||
import express from 'express';
|
||||
import { BlogController } from '../controllers/BlogController.js';
|
||||
import { authenticateToken, checkOwnership } from '../middlewares/authMiddleware.js';
|
||||
import { BlogRepository } from '../../infrastructure/repositories/BlogRepository.js';
|
||||
|
||||
const router = express.Router();
|
||||
const blogController = new BlogController();
|
||||
const blogRepository = new BlogRepository();
|
||||
|
||||
// Publikus route-ok
|
||||
router.get('/', (req, res) => blogController.getAll(req, res));
|
||||
router.get('/:id', (req, res) => blogController.getById(req, res));
|
||||
|
||||
// Védett route-ok
|
||||
router.post('/',
|
||||
authenticateToken,
|
||||
(req, res) => blogController.create(req, res)
|
||||
);
|
||||
|
||||
router.put('/:id',
|
||||
authenticateToken,
|
||||
checkOwnership(async (req) => {
|
||||
const blog = await blogRepository.findById(parseInt(req.params.id));
|
||||
return blog?.authorId;
|
||||
}),
|
||||
(req, res) => blogController.update(req, res)
|
||||
);
|
||||
|
||||
router.delete('/:id',
|
||||
authenticateToken,
|
||||
checkOwnership(async (req) => {
|
||||
const blog = await blogRepository.findById(parseInt(req.params.id));
|
||||
return blog?.authorId;
|
||||
}),
|
||||
(req, res) => blogController.delete(req, res)
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,29 @@
|
||||
export class Blog {
|
||||
constructor(data) {
|
||||
this.id = data.id;
|
||||
this.title = data.title;
|
||||
this.content = data.content;
|
||||
this.published = data.published;
|
||||
this.authorId = data.authorId;
|
||||
this.createdAt = data.createdAt;
|
||||
this.updatedAt = data.updatedAt;
|
||||
this.author = data.author;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
content: this.content,
|
||||
published: this.published,
|
||||
authorId: this.authorId,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
author: this.author ? {
|
||||
id: this.author.id,
|
||||
username: this.author.username,
|
||||
email: this.author.email
|
||||
} : null
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export class User {
|
||||
constructor(data) {
|
||||
this.id = data.id;
|
||||
this.email = data.email;
|
||||
this.username = data.username;
|
||||
this.password = data.password;
|
||||
this.role = data.role;
|
||||
this.createdAt = data.createdAt;
|
||||
this.updatedAt = data.updatedAt;
|
||||
}
|
||||
|
||||
isAdmin() {
|
||||
return this.role === 'ADMIN';
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
const { password, ...userWithoutPassword } = this;
|
||||
return userWithoutPassword;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
export class IBlogRepository {
|
||||
async findById(id) {
|
||||
throw new Error('findById() must be implemented');
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
throw new Error('findAll() must be implemented');
|
||||
}
|
||||
|
||||
async findByAuthorId(authorId) {
|
||||
throw new Error('findByAuthorId() must be implemented');
|
||||
}
|
||||
|
||||
async create(blogData) {
|
||||
throw new Error('create() must be implemented');
|
||||
}
|
||||
|
||||
async update(id, blogData) {
|
||||
throw new Error('update() must be implemented');
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
throw new Error('delete() must be implemented');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export class IUserRepository {
|
||||
async findById(id) {
|
||||
throw new Error('findById() must be implemented');
|
||||
}
|
||||
|
||||
async findByEmail(email) {
|
||||
throw new Error('findByEmail() must be implemented');
|
||||
}
|
||||
|
||||
async findByUsername(username) {
|
||||
throw new Error('findByUsername() must be implemented');
|
||||
}
|
||||
|
||||
async create(userData) {
|
||||
throw new Error('create() must be implemented');
|
||||
}
|
||||
|
||||
async update(id,userData) {
|
||||
throw new Error('update() must be implemented');
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
throw new Error('delete() must be implemented');
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
throw new Error('findAll() must be implemented');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import express from 'express';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
import databaseClient from './infrastructure/database/prisma.js';
|
||||
import redisClient from './infrastructure/database/redis.js';
|
||||
|
||||
import authRoutes from './api/routes/authRoutes.js';
|
||||
import blogRoutes from './api/routes/blogRoutes.js';
|
||||
|
||||
import { errorHandler, notFoundHandler } from './api/middlewares/errorHandler.js';
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
// Middleware
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(cookieParser(process.env.COOKIE_SECRET));
|
||||
|
||||
// Database connection
|
||||
await databaseClient.connect();
|
||||
|
||||
// Health check
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'OK',
|
||||
timestamp: new Date().toISOString(),
|
||||
environment: process.env.NODE_ENV
|
||||
});
|
||||
});
|
||||
|
||||
// Routes
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/blogs', blogRoutes);
|
||||
|
||||
// Error handling
|
||||
app.use(notFoundHandler);
|
||||
app.use(errorHandler);
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('\n🛑 Shutting down gracefully...');
|
||||
await databaseClient.disconnect();
|
||||
await redisClient.disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
console.log('\n🛑 Shutting down gracefully...');
|
||||
await databaseClient.disconnect();
|
||||
await redisClient.disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Start server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`✅ Server running on http://localhost:${PORT}`);
|
||||
console.log(`📊 Environment: ${process.env.NODE_ENV}`);
|
||||
});
|
||||
@@ -0,0 +1,231 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { redis } from '../database/redis.js';
|
||||
|
||||
/**
|
||||
* JWT Token Generálás
|
||||
*/
|
||||
|
||||
export function generateAccessToken(user) {
|
||||
const payload = {
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
return jwt.sign(
|
||||
payload,
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
|
||||
);
|
||||
}
|
||||
|
||||
export function generateRefreshToken(user) {
|
||||
const payload = {
|
||||
userId: user.id
|
||||
};
|
||||
|
||||
return jwt.sign(
|
||||
payload,
|
||||
process.env.JWT_REFRESH_SECRET,
|
||||
{ expiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '30d' }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT Token Validálás
|
||||
*/
|
||||
|
||||
export function verifyAccessToken(token) {
|
||||
try {
|
||||
return jwt.verify(token, process.env.JWT_SECRET);
|
||||
} catch (error) {
|
||||
if (error.name === 'TokenExpiredError') {
|
||||
throw new Error('Token expired');
|
||||
}
|
||||
if (error.name === 'JsonWebTokenError') {
|
||||
throw new Error('Invalid token');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function verifyRefreshToken(token) {
|
||||
try {
|
||||
return jwt.verify(token, process.env.JWT_REFRESH_SECRET);
|
||||
} catch (error) {
|
||||
throw new Error('Invalid refresh token');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Token Kiolvasás Request-ből
|
||||
*/
|
||||
|
||||
export function extractTokenFromRequest(req) {
|
||||
// 1. Cookie-ból
|
||||
if (req.cookies && req.cookies.accessToken) {
|
||||
return req.cookies.accessToken;
|
||||
}
|
||||
|
||||
// 2. Authorization header-ből
|
||||
const authHeader = req.headers.authorization;
|
||||
if (authHeader && authHeader.startsWith('Bearer ')) {
|
||||
return authHeader.substring(7);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Jelszó Hash-elés és Validálás
|
||||
*/
|
||||
|
||||
export async function hashPassword(plainPassword) {
|
||||
const saltRounds = 10;
|
||||
return await bcrypt.hash(plainPassword, saltRounds);
|
||||
}
|
||||
|
||||
export async function verifyPassword(plainPassword, hashedPassword) {
|
||||
return await bcrypt.compare(plainPassword, hashedPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cookie Beállítás
|
||||
*/
|
||||
|
||||
export function setAuthCookies(res, accessToken, refreshToken) {
|
||||
const cookieOptions = {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'strict'
|
||||
};
|
||||
|
||||
// Access token cookie - 7 nap
|
||||
res.cookie('accessToken', accessToken, {
|
||||
...cookieOptions,
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000
|
||||
});
|
||||
|
||||
// Refresh token cookie - 30 nap
|
||||
res.cookie('refreshToken', refreshToken, {
|
||||
...cookieOptions,
|
||||
maxAge: 30 * 24 * 60 * 60 * 1000
|
||||
});
|
||||
}
|
||||
|
||||
export function clearAuthCookies(res) {
|
||||
res.clearCookie('accessToken');
|
||||
res.clearCookie('refreshToken');
|
||||
}
|
||||
|
||||
/**
|
||||
* Input Validáció
|
||||
*/
|
||||
|
||||
export function isValidEmail(email) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
export function isValidPassword(password) {
|
||||
return password && password.length >= 8;
|
||||
}
|
||||
|
||||
export function validateRegisterInput(data) {
|
||||
const errors = [];
|
||||
|
||||
if (!data.email || !isValidEmail(data.email)) {
|
||||
errors.push('Valid email is required');
|
||||
}
|
||||
|
||||
if (!data.username || data.username.length < 3) {
|
||||
errors.push('Username must be at least 3 characters');
|
||||
}
|
||||
|
||||
if (!data.password || !isValidPassword(data.password)) {
|
||||
errors.push('Password must be at least 8 characters');
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
export function validateLoginInput(data) {
|
||||
const errors = [];
|
||||
|
||||
if (!data.password) {
|
||||
errors.push('Password is required');
|
||||
}
|
||||
|
||||
if (!data.email && !data.username) {
|
||||
errors.push('Email or username is required');
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Redis Session Management (Opcionális)
|
||||
*/
|
||||
|
||||
export async function createSession(userId, sessionData) {
|
||||
try {
|
||||
const sessionKey = `session:${userId}`;
|
||||
const ttl = 7 * 24 * 60 * 60; // 7 nap másodpercben
|
||||
|
||||
await redis.set(
|
||||
sessionKey,
|
||||
JSON.stringify({
|
||||
userId,
|
||||
loginTime: new Date().toISOString(),
|
||||
...sessionData
|
||||
}),
|
||||
'EX',
|
||||
ttl
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Redis session create error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSession(userId) {
|
||||
try {
|
||||
const sessionKey = `session:${userId}`;
|
||||
const session = await redis.get(sessionKey);
|
||||
|
||||
if (!session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSON.parse(session);
|
||||
} catch (error) {
|
||||
console.error('Redis session get error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteSession(userId) {
|
||||
try {
|
||||
const sessionKey = `session:${userId}`;
|
||||
await redis.del(sessionKey);
|
||||
} catch (error) {
|
||||
console.error('Redis session delete error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function sessionExists(userId) {
|
||||
try {
|
||||
const sessionKey = `session:${userId}`;
|
||||
const exists = await redis.exists(sessionKey);
|
||||
return exists === 1;
|
||||
} catch (error) {
|
||||
console.error('Redis session exists error:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
export const prisma = new PrismaClient({
|
||||
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
||||
});
|
||||
|
||||
export default {
|
||||
client: prisma,
|
||||
connect: async () => {
|
||||
try {
|
||||
await prisma.$connect();
|
||||
console.log('✅ Database connected');
|
||||
} catch (error) {
|
||||
console.error('❌ Database connection failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
},
|
||||
disconnect: async () => {
|
||||
await prisma.$disconnect();
|
||||
console.log('Database disconnected');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import Redis from 'ioredis';
|
||||
|
||||
const redis = new Redis({
|
||||
host: process.env.REDIS_HOST || 'localhost',
|
||||
port: parseInt(process.env.REDIS_PORT) || 6380,
|
||||
retryStrategy: (times) => {
|
||||
const delay = Math.min(times * 50, 2000);
|
||||
return delay;
|
||||
}
|
||||
});
|
||||
|
||||
redis.on('connect', () => {
|
||||
console.log('✅ Redis connected');
|
||||
});
|
||||
|
||||
redis.on('error', (error) => {
|
||||
console.error('❌ Redis connection error:', error);
|
||||
});
|
||||
|
||||
export { redis };
|
||||
|
||||
export default {
|
||||
client: redis,
|
||||
disconnect: async () => {
|
||||
await redis.quit();
|
||||
console.log('Redis disconnected');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import { IBlogRepository } from '../../domain/repositories/IBlogRepository.js';
|
||||
import { Blog } from '../../domain/models/Blog.js';
|
||||
import { prisma } from '../database/prisma.js';
|
||||
|
||||
export class BlogRepository extends IBlogRepository {
|
||||
async findById(id) {
|
||||
const blog = await prisma.blog.findUnique({
|
||||
where: { id },
|
||||
include: { author: true }
|
||||
});
|
||||
return blog ? new Blog(blog) : null;
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
const blogs = await prisma.blog.findMany({
|
||||
include: { author: true },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
return blogs.map(blog => new Blog(blog));
|
||||
}
|
||||
|
||||
async findByAuthorId(authorId) {
|
||||
const blogs = await prisma.blog.findMany({
|
||||
where: { authorId },
|
||||
include: { author: true },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
return blogs.map(blog => new Blog(blog));
|
||||
}
|
||||
|
||||
async create(blogData) {
|
||||
const blog = await prisma.blog.create({
|
||||
data: blogData,
|
||||
include: { author: true }
|
||||
});
|
||||
return new Blog(blog);
|
||||
}
|
||||
|
||||
async update(id, blogData) {
|
||||
const blog = await prisma.blog.update({
|
||||
where: { id },
|
||||
data: blogData,
|
||||
include: { author: true }
|
||||
});
|
||||
return new Blog(blog);
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
await prisma.blog.delete({
|
||||
where: { id }
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { IUserRepository } from '../../domain/repositories/IUserRepository.js';
|
||||
import { User } from '../../domain/models/User.js';
|
||||
import { prisma } from '../database/prisma.js';
|
||||
|
||||
export class UserRepository extends IUserRepository {
|
||||
async findById(id) {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id }
|
||||
});
|
||||
return user ? new User(user) : null;
|
||||
}
|
||||
|
||||
async findByEmail(email) {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email }
|
||||
});
|
||||
return user ? new User(user) : null;
|
||||
}
|
||||
|
||||
async findByUsername(username) {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { username }
|
||||
});
|
||||
return user ? new User(user) : null;
|
||||
}
|
||||
|
||||
async create(userData) {
|
||||
const user = await prisma.user.create({
|
||||
data: userData
|
||||
});
|
||||
return new User(user);
|
||||
}
|
||||
|
||||
async update(id, userData) {
|
||||
const user = await prisma.user.update({
|
||||
where: { id },
|
||||
data: userData
|
||||
});
|
||||
return new User(user);
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
await prisma.user.delete({
|
||||
where: { id }
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
const users = await prisma.user.findMany({
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
return users.map(user => new User(user));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
PORT=3000
|
||||
DATABASE_URL="file:./dev.db"
|
||||
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
.env
|
||||
dev.db
|
||||
dev.db-journal
|
||||
prisma/migrations/
|
||||
@@ -0,0 +1,80 @@
|
||||
# Második Gyakorlat - MINTA Megoldás
|
||||
|
||||
Ez a mappa tartalmazza a második gyakorlat **teljes, működő megoldását**.
|
||||
|
||||
## Amit implementáltunk:
|
||||
|
||||
### 1. **Prisma ORM + SQLite**
|
||||
- Prisma schema definiálása
|
||||
- User model adatbázis szinten
|
||||
- Migrációk kezelése
|
||||
|
||||
### 2. **CQRS Pattern + Layered Architecture**
|
||||
- **API Layer**: Controllers, Routes
|
||||
- **Application Layer**: Commands, Queries, Handlers
|
||||
- **Domain Layer**: Models, Interfaces
|
||||
- **Infrastructure Layer**: Prisma, Repositories
|
||||
|
||||
### 3. **Repository Pattern**
|
||||
- `IUserRepository` interface
|
||||
- `UserRepository` Prisma implementáció
|
||||
- Minden adatbázis művelet a repository-n keresztül
|
||||
|
||||
### 4. **Domain Model**
|
||||
- User osztály üzleti logikával
|
||||
- Validációk
|
||||
- Helper metódusok (isAdmin, canEdit, validate)
|
||||
|
||||
## Indítás
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npx prisma generate
|
||||
npx prisma migrate dev --name init
|
||||
npm run dev
|
||||
```
|
||||
|
||||
A szerver elindul a `http://localhost:3000` címen.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
- `GET /api/users` - Összes user
|
||||
- `GET /api/users/:id` - Egy user
|
||||
- `POST /api/users` - User létrehozása
|
||||
- `PUT /api/users/:id` - User frissítése
|
||||
- `DELETE /api/users/:id` - User törlése
|
||||
|
||||
## Példa Használat
|
||||
|
||||
```bash
|
||||
# User létrehozása
|
||||
curl -X POST http://localhost:3000/api/users \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "test@test.com",
|
||||
"name": "Test User",
|
||||
"password": "password123"
|
||||
}'
|
||||
|
||||
# Összes user lekérése
|
||||
curl http://localhost:3000/api/users
|
||||
|
||||
# User frissítése
|
||||
curl -X PUT http://localhost:3000/api/users/1 \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Updated Name"
|
||||
}'
|
||||
|
||||
# User törlése
|
||||
curl -X DELETE http://localhost:3000/api/users/1
|
||||
```
|
||||
|
||||
## Tanulási Pontok
|
||||
|
||||
1. **Prisma ORM**: Modern ORM használata Node.js-ben
|
||||
2. **SQLite**: Egyszerű file-based adatbázis
|
||||
3. **CQRS**: Command/Query szétválasztás
|
||||
4. **Layered Architecture**: Rétegezett architektúra
|
||||
5. **Domain Model**: Üzleti logika a domain rétegben
|
||||
6. **Repository Pattern**: Adatelérés absztrakciója
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"watch": ["src"],
|
||||
"ext": "js,json",
|
||||
"ignore": ["node_modules"],
|
||||
"exec": "node ./src/api/server.js"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "user-management-backend-minta",
|
||||
"version": "1.0.0",
|
||||
"description": "MINTA - User management backend with CQRS and layered architecture",
|
||||
"main": "./src/api/server.js",
|
||||
"scripts": {
|
||||
"start": "node ./src/api/server.js",
|
||||
"dev": "nodemon ./src/api/server.js",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate": "prisma migrate dev",
|
||||
"prisma:studio": "prisma studio"
|
||||
},
|
||||
"keywords": ["express", "prisma", "sqlite", "cqrs"],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"@prisma/client": "^5.8.0",
|
||||
"dotenv": "^16.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.2",
|
||||
"prisma": "^5.8.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
const CreateUserCommand = require('../../application/user/command/CreateUserCommand');
|
||||
const CreateUserHandler = require('../../application/user/command/CreateUserHandler');
|
||||
const UpdateUserCommand = require('../../application/user/command/UpdateUserCommand');
|
||||
const UpdateUserHandler = require('../../application/user/command/UpdateUserHandler');
|
||||
const DeleteUserCommand = require('../../application/user/command/DeleteUserCommand');
|
||||
const DeleteUserHandler = require('../../application/user/command/DeleteUserHandler');
|
||||
const GetUserQuery = require('../../application/user/query/GetUserQuery');
|
||||
const GetUserHandler = require('../../application/user/query/GetUserHandler');
|
||||
const GetAllUsersQuery = require('../../application/user/query/GetAllUsersQuery');
|
||||
const GetAllUsersHandler = require('../../application/user/query/GetAllUsersHandler');
|
||||
|
||||
class UserController {
|
||||
constructor() {
|
||||
this.createUserHandler = new CreateUserHandler();
|
||||
this.updateUserHandler = new UpdateUserHandler();
|
||||
this.deleteUserHandler = new DeleteUserHandler();
|
||||
this.getUserHandler = new GetUserHandler();
|
||||
this.getAllUsersHandler = new GetAllUsersHandler();
|
||||
}
|
||||
|
||||
async getAllUsers(req, res) {
|
||||
try {
|
||||
const query = new GetAllUsersQuery();
|
||||
const users = await this.getAllUsersHandler.handle(query);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: users
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getUserById(req, res) {
|
||||
try {
|
||||
const query = new GetUserQuery(parseInt(req.params.id));
|
||||
const user = await this.getUserHandler.handle(query);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: user
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async createUser(req, res) {
|
||||
try {
|
||||
const { email, name, password, role } = req.body;
|
||||
const command = new CreateUserCommand(email, name, password, role);
|
||||
const user = await this.createUserHandler.handle(command);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: user,
|
||||
message: 'User sikeresen létrehozva'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async updateUser(req, res) {
|
||||
try {
|
||||
const { email, name, role } = req.body;
|
||||
const command = new UpdateUserCommand(
|
||||
parseInt(req.params.id),
|
||||
email,
|
||||
name,
|
||||
role
|
||||
);
|
||||
const user = await this.updateUserHandler.handle(command);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: user,
|
||||
message: 'User sikeresen frissítve'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async deleteUser(req, res) {
|
||||
try {
|
||||
const command = new DeleteUserCommand(parseInt(req.params.id));
|
||||
const result = await this.deleteUserHandler.handle(command);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: result.message
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserController;
|
||||
@@ -0,0 +1,16 @@
|
||||
const express = require('express');
|
||||
const UserController = require('../controllers/UserController');
|
||||
|
||||
const router = express.Router();
|
||||
const userController = new UserController();
|
||||
|
||||
// Query routes (olvasás)
|
||||
router.get('/', (req, res) => userController.getAllUsers(req, res));
|
||||
router.get('/:id', (req, res) => userController.getUserById(req, res));
|
||||
|
||||
// Command routes (írás)
|
||||
router.post('/', (req, res) => userController.createUser(req, res));
|
||||
router.put('/:id', (req, res) => userController.updateUser(req, res));
|
||||
router.delete('/:id', (req, res) => userController.deleteUser(req, res));
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,51 @@
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const userRoutes = require('./routes/user.routes');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// Middleware
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// Routes
|
||||
app.use('/api/users', userRoutes);
|
||||
|
||||
// Alapértelmezett route
|
||||
app.get('/', (req, res) => {
|
||||
res.json({
|
||||
message: 'User Management API - MINTA Megoldás',
|
||||
endpoints: {
|
||||
users: {
|
||||
getAll: 'GET /api/users',
|
||||
getById: 'GET /api/users/:id',
|
||||
create: 'POST /api/users',
|
||||
update: 'PUT /api/users/:id',
|
||||
delete: 'DELETE /api/users/:id'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 404 handler
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: 'Endpoint nem található!'
|
||||
});
|
||||
});
|
||||
|
||||
// Error handler
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Szerver hiba történt!'
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`✅ Szerver fut a http://localhost:${PORT} címen`);
|
||||
console.log(`📊 Prisma Studio: npx prisma studio`);
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
class CreateUserCommand {
|
||||
constructor(email, name, password, role) {
|
||||
this.email = email;
|
||||
this.name = name;
|
||||
this.password = password;
|
||||
this.role = role;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CreateUserCommand;
|
||||
@@ -0,0 +1,41 @@
|
||||
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 ellenőrzés
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(command.email)) {
|
||||
throw new Error('Hibás email formátum');
|
||||
}
|
||||
|
||||
// 3. Jelszó hossz ellenőrzés
|
||||
if (command.password.length < 6) {
|
||||
throw new Error('Jelszó minimum 6 karakter kell legyen');
|
||||
}
|
||||
|
||||
// 4. Duplikáció ellenőrzés
|
||||
const existing = await this.userRepository.findByEmail(command.email);
|
||||
if (existing) {
|
||||
throw new Error('Email már használatban van');
|
||||
}
|
||||
|
||||
// 5. User létrehozása
|
||||
return await this.userRepository.create({
|
||||
email: command.email,
|
||||
name: command.name,
|
||||
password: command.password, // Production környezetben hash-elni kell!
|
||||
role: command.role || 'user'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CreateUserHandler;
|
||||
@@ -0,0 +1,7 @@
|
||||
class DeleteUserCommand {
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DeleteUserCommand;
|
||||
@@ -0,0 +1,22 @@
|
||||
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 sikeresen törölve',
|
||||
id: command.id
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DeleteUserHandler;
|
||||
@@ -0,0 +1,10 @@
|
||||
class UpdateUserCommand {
|
||||
constructor(id, email, name, role) {
|
||||
this.id = id;
|
||||
this.email = email;
|
||||
this.name = name;
|
||||
this.role = role;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UpdateUserCommand;
|
||||
@@ -0,0 +1,34 @@
|
||||
const UserRepository = require('../../../infrastructure/repositories/UserRepository');
|
||||
|
||||
class UpdateUserHandler {
|
||||
constructor() {
|
||||
this.userRepository = new UserRepository();
|
||||
}
|
||||
|
||||
async handle(command) {
|
||||
// 1. User létezésének ellenőrzése
|
||||
const user = await this.userRepository.findById(command.id);
|
||||
if (!user) {
|
||||
throw new Error('User nem található');
|
||||
}
|
||||
|
||||
// 2. Email egyediség ellenőrzése (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 van');
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Frissítendő adatok összeállítása
|
||||
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 végrehajtása
|
||||
return await this.userRepository.update(command.id, updateData);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UpdateUserHandler;
|
||||
@@ -0,0 +1,13 @@
|
||||
const UserRepository = require('../../../infrastructure/repositories/UserRepository');
|
||||
|
||||
class GetAllUsersHandler {
|
||||
constructor() {
|
||||
this.userRepository = new UserRepository();
|
||||
}
|
||||
|
||||
async handle(query) {
|
||||
return await this.userRepository.findAll();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GetAllUsersHandler;
|
||||
@@ -0,0 +1,5 @@
|
||||
class GetAllUsersQuery {
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
module.exports = GetAllUsersQuery;
|
||||
@@ -0,0 +1,17 @@
|
||||
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;
|
||||
@@ -0,0 +1,7 @@
|
||||
class GetUserQuery {
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GetUserQuery;
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* IUserRepository Interface
|
||||
*
|
||||
* Ez definiálja a UserRepository-nak kötelező metódusokat.
|
||||
* A Repository Pattern szerint az Infrastructure réteg implementálja ezt.
|
||||
*/
|
||||
class IUserRepository {
|
||||
async create(userData) {
|
||||
throw new Error('create() metódus nincs implementálva');
|
||||
}
|
||||
|
||||
async findById(id) {
|
||||
throw new Error('findById() metódus nincs implementálva');
|
||||
}
|
||||
|
||||
async findByEmail(email) {
|
||||
throw new Error('findByEmail() metódus nincs implementálva');
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
throw new Error('findAll() metódus nincs implementálva');
|
||||
}
|
||||
|
||||
async update(id, userData) {
|
||||
throw new Error('update() metódus nincs implementálva');
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
throw new Error('delete() metódus nincs implementálva');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IUserRepository;
|
||||
@@ -0,0 +1,56 @@
|
||||
class User {
|
||||
constructor(data) {
|
||||
this.id = data.id;
|
||||
this.email = data.email;
|
||||
this.name = data.name;
|
||||
this.password = data.password;
|
||||
this.role = data.role || 'user';
|
||||
this.createdAt = data.createdAt;
|
||||
this.updatedAt = data.updatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ellenőrzi, hogy a user admin-e
|
||||
*/
|
||||
isAdmin() {
|
||||
return this.role === 'admin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Ellenőrzi, hogy a user szerkeszthet-e egy másik usert
|
||||
* @param {User} targetUser - A szerkeszteni kívánt user
|
||||
*/
|
||||
canEdit(targetUser) {
|
||||
return this.isAdmin() || this.id === targetUser.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* User adatok validálása
|
||||
*/
|
||||
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');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* User objektum JSON-ként (jelszó nélkül!)
|
||||
*/
|
||||
toJSON() {
|
||||
const { password, ...userWithoutPassword } = this;
|
||||
return userWithoutPassword;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = User;
|
||||
@@ -0,0 +1,7 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
|
||||
const prisma = new PrismaClient({
|
||||
log: ['query', 'error', 'warn'],
|
||||
});
|
||||
|
||||
module.exports = prisma;
|
||||
@@ -0,0 +1,79 @@
|
||||
const prisma = require('../database/prisma');
|
||||
const IUserRepository = require('../../domain/interfaces/IUserRepository');
|
||||
|
||||
/**
|
||||
* UserRepository - Prisma implementáció
|
||||
*
|
||||
* Ez az osztály implementálja az IUserRepository interfészt
|
||||
* és Prisma ORM-et használ az adatbázis műveletekhez.
|
||||
*/
|
||||
class UserRepository extends IUserRepository {
|
||||
/**
|
||||
* User létrehozása
|
||||
* @param {Object} userData - { email, name, password, role }
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
async create(userData) {
|
||||
return await prisma.user.create({
|
||||
data: userData
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* User keresése ID alapján
|
||||
* @param {number} id
|
||||
* @returns {Promise<User|null>}
|
||||
*/
|
||||
async findById(id) {
|
||||
return await prisma.user.findUnique({
|
||||
where: { id: parseInt(id) }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* User keresése email alapján
|
||||
* @param {string} email
|
||||
* @returns {Promise<User|null>}
|
||||
*/
|
||||
async findByEmail(email) {
|
||||
return await prisma.user.findUnique({
|
||||
where: { email }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Összes user lekérése
|
||||
* @returns {Promise<User[]>}
|
||||
*/
|
||||
async findAll() {
|
||||
return await prisma.user.findMany({
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* User frissítése
|
||||
* @param {number} id
|
||||
* @param {Object} userData - Frissítendő mezők
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
async update(id, userData) {
|
||||
return await prisma.user.update({
|
||||
where: { id: parseInt(id) },
|
||||
data: userData
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* User törlése
|
||||
* @param {number} id
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
async delete(id) {
|
||||
return await prisma.user.delete({
|
||||
where: { id: parseInt(id) }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserRepository;
|
||||
@@ -0,0 +1,17 @@
|
||||
# Server Configuration
|
||||
PORT=3000
|
||||
NODE_ENV=development
|
||||
|
||||
# Database Configuration (PostgreSQL + Prisma)
|
||||
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/cors_di_app?schema=public"
|
||||
|
||||
# JWT Configuration
|
||||
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
|
||||
JWT_EXPIRES_IN=7d
|
||||
|
||||
# Ethereal Email Configuration (https://ethereal.email/create)
|
||||
ETHEREAL_USER=your-ethereal-user@ethereal.email
|
||||
ETHEREAL_PASS=your-ethereal-password
|
||||
|
||||
# CORS Configuration (comma-separated origins)
|
||||
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001
|
||||
@@ -0,0 +1,20 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,829 @@
|
||||
# 📚 SEGÍTSÉG - Példakódok és Magyarázatok
|
||||
|
||||
## Tartalomjegyzék
|
||||
|
||||
1. [DI Container Implementálása](#1-di-container-implementálása)
|
||||
2. [CORS Middleware Implementálása](#2-cors-middleware-implementálása)
|
||||
3. [Email Service Implementálása](#3-email-service-implementálása)
|
||||
4. [Cookie-Based JWT Használata](#4-cookie-based-jwt-használata)
|
||||
5. [Tesztelési Példák](#5-tesztelési-példák)
|
||||
6. [Gyakori Hibák és Megoldások](#6-gyakori-hibák-és-megoldások)
|
||||
|
||||
---
|
||||
|
||||
## 1. DI Container Implementálása
|
||||
|
||||
### 📁 Fájl: `src/application/Container.js`
|
||||
|
||||
### Teljes megoldás magyarázattal:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Dependency Injection Container
|
||||
* Supports singleton, transient, and scoped lifetimes
|
||||
*/
|
||||
class Container {
|
||||
constructor() {
|
||||
// 1. Singleton instance-ok tárolása (egyszer létrehozott objektumok)
|
||||
this.services = new Map();
|
||||
|
||||
// 2. Factory függvények tárolása (objektumokat létrehozó függvények)
|
||||
this.factories = new Map();
|
||||
|
||||
// 3. Lifecycle típusok tárolása (singleton/transient/scoped)
|
||||
this.lifetimes = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Service regisztrálása
|
||||
* @param {string} name - Service neve
|
||||
* @param {Function} factory - Factory függvény ami a service-t létrehozza
|
||||
* @param {string} lifetime - 'singleton' | 'transient' | 'scoped'
|
||||
*/
|
||||
register(name, factory, lifetime = 'singleton') {
|
||||
// 4. Factory függvény eltárolása
|
||||
this.factories.set(name, factory);
|
||||
|
||||
// 5. Lifetime típus eltárolása
|
||||
this.lifetimes.set(name, lifetime);
|
||||
|
||||
// 6. Ha singleton, azonnal példányosítjuk és eltároljuk
|
||||
if (lifetime === 'singleton') {
|
||||
const instance = factory();
|
||||
this.services.set(name, instance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Service lekérése
|
||||
* @param {string} name - Service neve
|
||||
* @param {Map} scope - Opcionális scope Map scoped lifetime-hoz
|
||||
* @returns {any} Service instance
|
||||
*/
|
||||
resolve(name, scope = null) {
|
||||
// 7. Scoped lifecycle kezelése
|
||||
if (this.lifetimes.get(name) === 'scoped' && scope) {
|
||||
// Ha már van a scope-ban, azt adjuk vissza
|
||||
if (scope.has(name)) {
|
||||
return scope.get(name);
|
||||
}
|
||||
|
||||
// Ha nincs még, létrehozzuk és eltároljuk a scope-ban
|
||||
const instance = this.factories.get(name)();
|
||||
scope.set(name, instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
// 8. Singleton lifecycle - mindig ugyanazt az instance-t adjuk vissza
|
||||
if (this.lifetimes.get(name) === 'singleton') {
|
||||
return this.services.get(name);
|
||||
}
|
||||
|
||||
// 9. Transient lifecycle - mindig új instance-t hozunk létre
|
||||
if (this.lifetimes.get(name) === 'transient') {
|
||||
return this.factories.get(name)();
|
||||
}
|
||||
|
||||
// 10. Ha nincs regisztrálva a service, hibát dobunk
|
||||
throw new Error(`Service '${name}' is not registered`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Új scope létrehozása scoped lifecycle-hoz (pl. request-enkénti instance-ok)
|
||||
* @returns {Object} Scope objektum resolve metódussal
|
||||
*/
|
||||
createScope() {
|
||||
// 11. Új Map létrehozása a scoped instance-oknak
|
||||
const scopeMap = new Map();
|
||||
|
||||
// 12. Visszaadunk egy objektumot ami tartalmaz egy resolve metódust
|
||||
return {
|
||||
resolve: (name) => this.resolve(name, scopeMap)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Container;
|
||||
```
|
||||
|
||||
### 💡 Használati példák:
|
||||
|
||||
#### Singleton (egy instance az egész alkalmazásban)
|
||||
|
||||
```javascript
|
||||
const container = new Container();
|
||||
|
||||
// Singleton PrismaClient (egy kapcsolat az egész app-ban)
|
||||
container.register('PrismaClient', () => {
|
||||
return new PrismaClient();
|
||||
}, 'singleton');
|
||||
|
||||
// Minden resolve ugyanazt az instance-t adja vissza
|
||||
const prisma1 = container.resolve('PrismaClient');
|
||||
const prisma2 = container.resolve('PrismaClient');
|
||||
console.log(prisma1 === prisma2); // true - ugyanaz az objektum!
|
||||
```
|
||||
|
||||
#### Transient (minden resolve új instance)
|
||||
|
||||
```javascript
|
||||
// Transient logger (minden híváshoz új)
|
||||
container.register('Logger', () => {
|
||||
return {
|
||||
id: Math.random(),
|
||||
log: (msg) => console.log(`[${new Date().toISOString()}] ${msg}`)
|
||||
};
|
||||
}, 'transient');
|
||||
|
||||
const logger1 = container.resolve('Logger');
|
||||
const logger2 = container.resolve('Logger');
|
||||
console.log(logger1 === logger2); // false - különböző objektumok!
|
||||
console.log(logger1.id !== logger2.id); // true - különböző ID-k
|
||||
```
|
||||
|
||||
#### Scoped (request szinten megosztott)
|
||||
|
||||
```javascript
|
||||
// Scoped RequestContext
|
||||
container.register('RequestContext', () => {
|
||||
return {
|
||||
id: Math.random(),
|
||||
user: null,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}, 'scoped');
|
||||
|
||||
// Első request scope
|
||||
const scope1 = container.createScope();
|
||||
const ctx1a = scope1.resolve('RequestContext');
|
||||
const ctx1b = scope1.resolve('RequestContext');
|
||||
console.log(ctx1a === ctx1b); // true - ugyanaz a scope-on belül!
|
||||
|
||||
// Második request scope
|
||||
const scope2 = container.createScope();
|
||||
const ctx2 = scope2.resolve('RequestContext');
|
||||
console.log(ctx1a === ctx2); // false - különböző scope-ok!
|
||||
```
|
||||
|
||||
### 🧪 Tesztelés:
|
||||
|
||||
```javascript
|
||||
// tests/unit/application/Container.test.js
|
||||
const Container = require('../../src/application/Container');
|
||||
|
||||
describe('Container', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
container = new Container();
|
||||
});
|
||||
|
||||
test('singleton - should return same instance', () => {
|
||||
container.register('TestService', () => ({ id: Math.random() }), 'singleton');
|
||||
|
||||
const instance1 = container.resolve('TestService');
|
||||
const instance2 = container.resolve('TestService');
|
||||
|
||||
expect(instance1).toBe(instance2);
|
||||
expect(instance1.id).toBe(instance2.id);
|
||||
});
|
||||
|
||||
test('transient - should return different instances', () => {
|
||||
container.register('TestService', () => ({ id: Math.random() }), 'transient');
|
||||
|
||||
const instance1 = container.resolve('TestService');
|
||||
const instance2 = container.resolve('TestService');
|
||||
|
||||
expect(instance1).not.toBe(instance2);
|
||||
expect(instance1.id).not.toBe(instance2.id);
|
||||
});
|
||||
|
||||
test('scoped - should return same instance within scope', () => {
|
||||
container.register('TestService', () => ({ id: Math.random() }), 'scoped');
|
||||
|
||||
const scope = container.createScope();
|
||||
const instance1 = scope.resolve('TestService');
|
||||
const instance2 = scope.resolve('TestService');
|
||||
|
||||
expect(instance1).toBe(instance2);
|
||||
expect(instance1.id).toBe(instance2.id);
|
||||
});
|
||||
|
||||
test('scoped - different scopes should have different instances', () => {
|
||||
container.register('TestService', () => ({ id: Math.random() }), 'scoped');
|
||||
|
||||
const scope1 = container.createScope();
|
||||
const scope2 = container.createScope();
|
||||
|
||||
const instance1 = scope1.resolve('TestService');
|
||||
const instance2 = scope2.resolve('TestService');
|
||||
|
||||
expect(instance1).not.toBe(instance2);
|
||||
});
|
||||
|
||||
test('should throw error for unregistered service', () => {
|
||||
expect(() => container.resolve('NonExistent')).toThrow(
|
||||
"Service 'NonExistent' is not registered"
|
||||
);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. CORS Middleware Implementálása
|
||||
|
||||
### 📁 Fájl: `src/api/middlewares/corsMiddleware.js`
|
||||
|
||||
### Teljes megoldás:
|
||||
|
||||
```javascript
|
||||
const cors = require('cors');
|
||||
|
||||
// Engedélyezett origin-ek whitelist-je (környezeti változóból)
|
||||
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:5173', // Vite default port
|
||||
'http://localhost:5174',
|
||||
'http://localhost:4200' // Angular default port
|
||||
];
|
||||
|
||||
/**
|
||||
* CORS Configuration
|
||||
* Whitelist-based origin validation
|
||||
*/
|
||||
const corsOptions = {
|
||||
/**
|
||||
* Origin ellenőrzés
|
||||
* @param {string} origin - Request origin
|
||||
* @param {Function} callback - Callback(error, allowed)
|
||||
*/
|
||||
origin: function (origin, callback) {
|
||||
// 1. Ha nincs origin (backend-to-backend, Postman, curl)
|
||||
// Ezeket általában engedélyezzük development-ben
|
||||
if (!origin) {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
// 2. Ha az origin benne van az allowedOrigins listában
|
||||
if (allowedOrigins.includes(origin)) {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
// 3. Egyébként tiltjuk CORS hibával
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
},
|
||||
|
||||
// Cookie és Authorization header engedélyezése
|
||||
credentials: true,
|
||||
|
||||
// Engedélyezett HTTP metódusok
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
||||
|
||||
// Engedélyezett header-ek
|
||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||
|
||||
// Response header-ek amiket a frontend láthat
|
||||
exposedHeaders: ['Set-Cookie'],
|
||||
|
||||
// Preflight cache idő (másodpercben)
|
||||
maxAge: 600 // 10 perc
|
||||
};
|
||||
|
||||
module.exports = cors(corsOptions);
|
||||
```
|
||||
|
||||
### 💡 Használat:
|
||||
|
||||
#### `.env` konfiguráció:
|
||||
|
||||
```env
|
||||
# Development
|
||||
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
|
||||
|
||||
# Production
|
||||
ALLOWED_ORIGINS=https://myapp.com,https://www.myapp.com,https://admin.myapp.com
|
||||
```
|
||||
|
||||
#### Aktiválás `server.js`-ben:
|
||||
|
||||
```javascript
|
||||
// src/api/server.js
|
||||
const corsMiddleware = require('./middlewares/corsMiddleware');
|
||||
|
||||
// Middleware chain
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(cookieParser());
|
||||
app.use(corsMiddleware); // <-- CORS middleware hozzáadása
|
||||
```
|
||||
|
||||
### 🧪 Tesztelés cURL-lel:
|
||||
|
||||
```bash
|
||||
# Engedélyezett origin
|
||||
curl -H "Origin: http://localhost:3000" \
|
||||
-H "Access-Control-Request-Method: POST" \
|
||||
-H "Access-Control-Request-Headers: Content-Type" \
|
||||
-X OPTIONS \
|
||||
http://localhost:3000/api/users
|
||||
|
||||
# Sikeres válasz:
|
||||
# Access-Control-Allow-Origin: http://localhost:3000
|
||||
# Access-Control-Allow-Credentials: true
|
||||
|
||||
# Tiltott origin
|
||||
curl -H "Origin: http://malicious-site.com" \
|
||||
-X GET \
|
||||
http://localhost:3000/api/users
|
||||
|
||||
# Hiba válasz: "Not allowed by CORS"
|
||||
```
|
||||
|
||||
### 🧪 Frontend tesztelés:
|
||||
|
||||
```javascript
|
||||
// React/Vue/Angular frontend
|
||||
fetch('http://localhost:3000/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'include', // Cookie küldés/fogadás engedélyezése
|
||||
body: JSON.stringify({ email, password })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => console.log('Login sikeres:', data))
|
||||
.catch(err => console.error('CORS hiba:', err));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Email Service Implementálása
|
||||
|
||||
### 📁 Fájl: `src/application/services/EmailService.js`
|
||||
|
||||
### Teljes megoldás:
|
||||
|
||||
```javascript
|
||||
const nodemailer = require('nodemailer');
|
||||
const handlebars = require('handlebars');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Email Service
|
||||
* Nodemailer + Handlebars template based email sending
|
||||
*/
|
||||
class EmailService {
|
||||
constructor() {
|
||||
// 1. Nodemailer transport létrehozása Ethereal SMTP-vel
|
||||
this.transporter = nodemailer.createTransport({
|
||||
host: 'smtp.ethereal.email',
|
||||
port: 587,
|
||||
secure: false, // TLS
|
||||
auth: {
|
||||
user: process.env.ETHEREAL_USER || 'your-test-email@ethereal.email',
|
||||
pass: process.env.ETHEREAL_PASS || 'your-test-password'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('📧 EmailService initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Welcome email küldése új regisztrált felhasználónak
|
||||
* @param {string} userEmail - Felhasználó email címe
|
||||
* @param {string} userName - Felhasználó neve
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async sendWelcomeEmail(userEmail, userName) {
|
||||
try {
|
||||
// 2. Template fájl beolvasása
|
||||
const templatePath = path.join(__dirname, 'templates', 'welcome.hbs');
|
||||
const templateSource = fs.readFileSync(templatePath, 'utf-8');
|
||||
|
||||
// 3. Handlebars template compile-olása
|
||||
const template = handlebars.compile(templateSource);
|
||||
|
||||
// 4. HTML generálása az adatokkal
|
||||
const html = template({
|
||||
name: userName,
|
||||
email: userEmail,
|
||||
date: new Date().toLocaleDateString('hu-HU'),
|
||||
year: new Date().getFullYear()
|
||||
});
|
||||
|
||||
// 5. Email küldése
|
||||
const info = await this.transporter.sendMail({
|
||||
from: '"Clean Architecture App" <noreply@cleanarch.com>',
|
||||
to: userEmail,
|
||||
subject: '🎉 Üdvözlünk az alkalmazásban!',
|
||||
html: html
|
||||
});
|
||||
|
||||
// 6. Ethereal preview URL kiírása
|
||||
const previewUrl = nodemailer.getTestMessageUrl(info);
|
||||
console.log('📧 Email elküldve:', previewUrl);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ Email küldési hiba:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Password reset email (bővítési lehetőség)
|
||||
*/
|
||||
async sendPasswordResetEmail(userEmail, resetToken) {
|
||||
try {
|
||||
const templatePath = path.join(__dirname, 'templates', 'password-reset.hbs');
|
||||
const templateSource = fs.readFileSync(templatePath, 'utf-8');
|
||||
const template = handlebars.compile(templateSource);
|
||||
|
||||
const resetLink = `${process.env.FRONTEND_URL}/reset-password?token=${resetToken}`;
|
||||
|
||||
const html = template({
|
||||
resetLink,
|
||||
expiresIn: '1 óra'
|
||||
});
|
||||
|
||||
const info = await this.transporter.sendMail({
|
||||
from: '"Clean Architecture App" <noreply@cleanarch.com>',
|
||||
to: userEmail,
|
||||
subject: '🔐 Jelszó visszaállítás',
|
||||
html: html
|
||||
});
|
||||
|
||||
console.log('📧 Password reset email:', nodemailer.getTestMessageUrl(info));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ Password reset email hiba:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EmailService;
|
||||
```
|
||||
|
||||
### 📁 Email Template: `src/application/services/templates/welcome.hbs`
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="hu">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Üdvözlünk!</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Arial, sans-serif;
|
||||
background-color: #f4f7f9;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 12px;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
margin-top: 0;
|
||||
font-size: 28px;
|
||||
}
|
||||
.welcome-icon {
|
||||
font-size: 48px;
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
p {
|
||||
color: #555;
|
||||
line-height: 1.6;
|
||||
font-size: 16px;
|
||||
}
|
||||
.highlight {
|
||||
background-color: #e3f2fd;
|
||||
padding: 15px;
|
||||
border-left: 4px solid #2196f3;
|
||||
margin: 20px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.button {
|
||||
display: inline-block;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
padding: 12px 30px;
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
margin: 20px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 40px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #eee;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="welcome-icon">🎉</div>
|
||||
|
||||
<h1>Üdvözlünk, {{name}}!</h1>
|
||||
|
||||
<p>Gratulálunk! Sikeres regisztráció az alkalmazásunkban.</p>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>Regisztráció részletei:</strong><br>
|
||||
📧 Email: {{email}}<br>
|
||||
📅 Dátum: {{date}}
|
||||
</div>
|
||||
|
||||
<p>Mostantól hozzáférsz az összes funkciónkhoz:</p>
|
||||
<ul>
|
||||
<li>✅ Profil kezelés</li>
|
||||
<li>✅ Biztonságos autentikáció</li>
|
||||
<li>✅ API hozzáférés</li>
|
||||
</ul>
|
||||
|
||||
<center>
|
||||
<a href="http://localhost:3000/api/users/me" class="button">
|
||||
Profilom megtekintése
|
||||
</a>
|
||||
</center>
|
||||
|
||||
<div class="footer">
|
||||
<p>Ez egy automatikus üzenet, kérjük ne válaszolj rá.</p>
|
||||
<p>© {{year}} Clean Architecture App. Minden jog fenntartva.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 💡 Ethereal Email Beállítása:
|
||||
|
||||
1. **Menj a https://ethereal.email oldalra**
|
||||
2. **Kattints "Create Ethereal Account" gombra**
|
||||
3. **Másold ki a credentials-t:**
|
||||
|
||||
```
|
||||
Username: your-random-name@ethereal.email
|
||||
Password: your-random-password
|
||||
```
|
||||
|
||||
4. **Állítsd be a `.env` fájlban:**
|
||||
|
||||
```env
|
||||
ETHEREAL_USER=your-random-name@ethereal.email
|
||||
ETHEREAL_PASS=your-random-password
|
||||
```
|
||||
|
||||
### 🧪 Tesztelés:
|
||||
|
||||
```bash
|
||||
# Regisztráció (automatikusan küld welcome emailt)
|
||||
POST http://localhost:3000/api/auth/register
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Test User",
|
||||
"email": "test@example.com",
|
||||
"password": "password123"
|
||||
}
|
||||
|
||||
# Console output:
|
||||
# 📧 Email elküldve: https://ethereal.email/message/XXXXXX
|
||||
```
|
||||
|
||||
**Nyisd meg a böngészőben a linket és látni fogod az emailt!**
|
||||
|
||||
---
|
||||
|
||||
## 4. Cookie-Based JWT Használata
|
||||
|
||||
### 🍪 Frontend Integration (React példa)
|
||||
|
||||
```javascript
|
||||
// authService.js
|
||||
const API_URL = 'http://localhost:3000/api';
|
||||
|
||||
export const authService = {
|
||||
async register(name, email, password) {
|
||||
const response = await fetch(`${API_URL}/auth/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include', // FONTOS: Cookie küldés/fogadás
|
||||
body: JSON.stringify({ name, email, password })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Registration failed');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
// JWT automatikusan cookie-ban tárolódik!
|
||||
},
|
||||
|
||||
async login(email, password) {
|
||||
const response = await fetch(`${API_URL}/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Login failed');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
},
|
||||
|
||||
async logout() {
|
||||
const response = await fetch(`${API_URL}/auth/logout`, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
return response.json();
|
||||
},
|
||||
|
||||
async getCurrentUser() {
|
||||
const response = await fetch(`${API_URL}/users/me`, {
|
||||
credentials: 'include' // Cookie automatikusan küldődik
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 🧪 cURL Tesztelés Cookie-val:
|
||||
|
||||
```bash
|
||||
# 1. Login + Cookie mentése
|
||||
curl -c cookies.txt -X POST http://localhost:3000/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"test@example.com","password":"password123"}'
|
||||
|
||||
# 2. Protected endpoint hívása a cookie-val
|
||||
curl -b cookies.txt http://localhost:3000/api/users/me
|
||||
|
||||
# 3. Logout
|
||||
curl -b cookies.txt -c cookies.txt -X POST http://localhost:3000/api/auth/logout
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Tesztelési Példák
|
||||
|
||||
### Controller Teszt Cookie-val:
|
||||
|
||||
```javascript
|
||||
// tests/unit/controllers/AuthController.test.js
|
||||
const AuthController = require('../../src/api/controllers/AuthController');
|
||||
|
||||
describe('AuthController - Cookie-based JWT', () => {
|
||||
let authController;
|
||||
let mockRegisterHandler;
|
||||
let mockLoginHandler;
|
||||
let mockJwtService;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRegisterHandler = { handle: jest.fn() };
|
||||
mockLoginHandler = { handle: jest.fn() };
|
||||
mockJwtService = {
|
||||
getCookieName: jest.fn().mockReturnValue('auth_token'),
|
||||
getCookieOptions: jest.fn().mockReturnValue({
|
||||
httpOnly: true,
|
||||
secure: false,
|
||||
sameSite: 'strict'
|
||||
})
|
||||
};
|
||||
|
||||
authController = new AuthController(
|
||||
mockRegisterHandler,
|
||||
mockLoginHandler,
|
||||
mockJwtService
|
||||
);
|
||||
});
|
||||
|
||||
test('login should set JWT in cookie', async () => {
|
||||
const mockReq = {
|
||||
body: { email: 'test@example.com', password: 'password123' }
|
||||
};
|
||||
const mockRes = {
|
||||
cookie: jest.fn(),
|
||||
status: jest.fn().mockReturnThis(),
|
||||
json: jest.fn()
|
||||
};
|
||||
|
||||
mockLoginHandler.handle.mockResolvedValue({
|
||||
user: { id: 1, email: 'test@example.com' },
|
||||
token: 'mock_jwt_token'
|
||||
});
|
||||
|
||||
await authController.login(mockReq, mockRes);
|
||||
|
||||
// Cookie beállítás ellenőrzése
|
||||
expect(mockRes.cookie).toHaveBeenCalledWith(
|
||||
'auth_token',
|
||||
'mock_jwt_token',
|
||||
expect.objectContaining({
|
||||
httpOnly: true,
|
||||
sameSite: 'strict'
|
||||
})
|
||||
);
|
||||
|
||||
// Response csak user-t tartalmaz, token nincs a body-ban
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
message: 'Login successful',
|
||||
data: {
|
||||
user: { id: 1, email: 'test@example.com' }
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Gyakori Hibák és Megoldások
|
||||
|
||||
### ❌ Hiba: "Service 'PrismaClient' is not registered"
|
||||
|
||||
**Megoldás:**
|
||||
```javascript
|
||||
// Ellenőrizd a server.js-ben:
|
||||
container.register('PrismaClient', () => {
|
||||
return databaseConnection.getClient();
|
||||
}, 'singleton');
|
||||
```
|
||||
|
||||
### ❌ Hiba: "Not allowed by CORS"
|
||||
|
||||
**Megoldás:**
|
||||
```env
|
||||
# .env fájlban add meg a frontend origin-t:
|
||||
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
|
||||
```
|
||||
|
||||
### ❌ Hiba: "No token provided in cookies"
|
||||
|
||||
**Megoldás:**
|
||||
```javascript
|
||||
// Frontend-en használd a credentials: 'include'-ot:
|
||||
fetch('http://localhost:3000/api/users/me', {
|
||||
credentials: 'include'
|
||||
});
|
||||
```
|
||||
|
||||
### ❌ Hiba: Email nem megy ki
|
||||
|
||||
**Megoldás:**
|
||||
1. Ellenőrizd az Ethereal credentials-t (`.env`)
|
||||
2. Hozz létre új Ethereal account-ot: https://ethereal.email
|
||||
3. Ellenőrizd a template fájl elérési útját:
|
||||
```javascript
|
||||
const templatePath = path.join(__dirname, 'templates', 'welcome.hbs');
|
||||
```
|
||||
|
||||
### ❌ Hiba: Container circular dependency
|
||||
|
||||
**Megoldás:**
|
||||
```javascript
|
||||
// Használj lazy loading-ot:
|
||||
container.register('ServiceA', () => {
|
||||
const ServiceB = container.resolve('ServiceB');
|
||||
return new ServiceA(ServiceB);
|
||||
}, 'singleton');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 További Olvasnivalók
|
||||
|
||||
- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
|
||||
- [Dependency Injection Pattern](https://martinfowler.com/articles/injection.html)
|
||||
- [CORS in Express](https://expressjs.com/en/resources/middleware/cors.html)
|
||||
- [Nodemailer Documentation](https://nodemailer.com/about/)
|
||||
- [Handlebars Templates](https://handlebarsjs.com/)
|
||||
- [Cookie Security Best Practices](https://owasp.org/www-community/controls/SecureCookieAttribute)
|
||||
|
||||
---
|
||||
|
||||
**Sok sikert a feladatokhoz! 🚀**
|
||||
@@ -0,0 +1,418 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<coverage generated="1772649160943" clover="3.2.0">
|
||||
<project timestamp="1772649160943" name="All files">
|
||||
<metrics statements="301" coveredstatements="1" conditionals="103" coveredconditionals="2" methods="83" coveredmethods="4" elements="487" coveredelements="7" complexity="0" loc="301" ncloc="301" packages="11" files="26" classes="26"/>
|
||||
<package name="api.controllers">
|
||||
<metrics statements="65" coveredstatements="0" conditionals="18" coveredconditionals="0" methods="9" coveredmethods="0"/>
|
||||
<file name="AuthController.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\api\controllers\AuthController.js">
|
||||
<metrics statements="26" coveredstatements="0" conditionals="10" coveredconditionals="0" methods="4" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="2" count="0" type="stmt"/>
|
||||
<line num="10" count="0" type="stmt"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
<line num="19" count="0" type="stmt"/>
|
||||
<line num="20" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="stmt"/>
|
||||
<line num="23" count="0" type="stmt"/>
|
||||
<line num="26" count="0" type="stmt"/>
|
||||
<line num="32" count="0" type="stmt"/>
|
||||
<line num="40" count="0" type="cond" truecount="0" falsecount="6"/>
|
||||
<line num="45" count="0" type="stmt"/>
|
||||
<line num="53" count="0" type="stmt"/>
|
||||
<line num="54" count="0" type="stmt"/>
|
||||
<line num="56" count="0" type="stmt"/>
|
||||
<line num="57" count="0" type="stmt"/>
|
||||
<line num="60" count="0" type="stmt"/>
|
||||
<line num="66" count="0" type="stmt"/>
|
||||
<line num="74" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="77" count="0" type="stmt"/>
|
||||
<line num="85" count="0" type="stmt"/>
|
||||
<line num="87" count="0" type="stmt"/>
|
||||
<line num="94" count="0" type="stmt"/>
|
||||
<line num="98" count="0" type="stmt"/>
|
||||
<line num="103" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="UserController.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\api\controllers\UserController.js">
|
||||
<metrics statements="39" coveredstatements="0" conditionals="8" coveredconditionals="0" methods="5" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="2" count="0" type="stmt"/>
|
||||
<line num="3" count="0" type="stmt"/>
|
||||
<line num="4" count="0" type="stmt"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
<line num="13" count="0" type="stmt"/>
|
||||
<line num="14" count="0" type="stmt"/>
|
||||
<line num="15" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="stmt"/>
|
||||
<line num="24" count="0" type="stmt"/>
|
||||
<line num="26" count="0" type="stmt"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="29" count="0" type="stmt"/>
|
||||
<line num="34" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="35" count="0" type="stmt"/>
|
||||
<line num="43" count="0" type="stmt"/>
|
||||
<line num="44" count="0" type="stmt"/>
|
||||
<line num="45" count="0" type="stmt"/>
|
||||
<line num="47" count="0" type="stmt"/>
|
||||
<line num="53" count="0" type="stmt"/>
|
||||
<line num="61" count="0" type="stmt"/>
|
||||
<line num="62" count="0" type="stmt"/>
|
||||
<line num="63" count="0" type="stmt"/>
|
||||
<line num="65" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="66" count="0" type="stmt"/>
|
||||
<line num="69" count="0" type="stmt"/>
|
||||
<line num="70" count="0" type="stmt"/>
|
||||
<line num="72" count="0" type="stmt"/>
|
||||
<line num="77" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="78" count="0" type="stmt"/>
|
||||
<line num="86" count="0" type="stmt"/>
|
||||
<line num="87" count="0" type="stmt"/>
|
||||
<line num="88" count="0" type="stmt"/>
|
||||
<line num="90" count="0" type="stmt"/>
|
||||
<line num="91" count="0" type="stmt"/>
|
||||
<line num="93" count="0" type="stmt"/>
|
||||
<line num="98" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="99" count="0" type="stmt"/>
|
||||
<line num="104" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="api.middlewares">
|
||||
<metrics statements="17" coveredstatements="0" conditionals="4" coveredconditionals="0" methods="4" coveredmethods="0"/>
|
||||
<file name="authMiddleware.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\api\middlewares\authMiddleware.js">
|
||||
<metrics statements="11" coveredstatements="0" conditionals="2" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="3" count="0" type="stmt"/>
|
||||
<line num="14" count="0" type="stmt"/>
|
||||
<line num="16" count="0" type="stmt"/>
|
||||
<line num="18" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="19" count="0" type="stmt"/>
|
||||
<line num="26" count="0" type="stmt"/>
|
||||
<line num="29" count="0" type="stmt"/>
|
||||
<line num="35" count="0" type="stmt"/>
|
||||
<line num="37" count="0" type="stmt"/>
|
||||
<line num="44" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="corsMiddleware.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\api\middlewares\corsMiddleware.js">
|
||||
<metrics statements="4" coveredstatements="0" conditionals="2" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="4" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="6" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="scopeMiddleware.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\api\middlewares\scopeMiddleware.js">
|
||||
<metrics statements="2" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="2" coveredmethods="0"/>
|
||||
<line num="6" count="0" type="stmt"/>
|
||||
<line num="18" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="api.routers">
|
||||
<metrics statements="20" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="11" coveredmethods="0"/>
|
||||
<file name="authRoutes.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\api\routers\authRoutes.js">
|
||||
<metrics statements="9" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="5" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="10" count="0" type="stmt"/>
|
||||
<line num="13" count="0" type="stmt"/>
|
||||
<line num="20" count="0" type="stmt"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="33" count="0" type="stmt"/>
|
||||
<line num="38" count="0" type="stmt"/>
|
||||
<line num="40" count="0" type="stmt"/>
|
||||
<line num="43" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="userRoutes.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\api\routers\userRoutes.js">
|
||||
<metrics statements="11" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="6" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="2" count="0" type="stmt"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
<line num="14" count="0" type="stmt"/>
|
||||
<line num="20" count="0" type="stmt"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="33" count="0" type="stmt"/>
|
||||
<line num="39" count="0" type="stmt"/>
|
||||
<line num="44" count="0" type="stmt"/>
|
||||
<line num="46" count="0" type="stmt"/>
|
||||
<line num="49" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="application.auth.commands">
|
||||
<metrics statements="49" coveredstatements="0" conditionals="21" coveredconditionals="0" methods="7" coveredmethods="0"/>
|
||||
<file name="LoginUserCommand.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\auth\commands\LoginUserCommand.js">
|
||||
<metrics statements="3" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="8" count="0" type="stmt"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="LoginUserCommandHandler.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\auth\commands\LoginUserCommandHandler.js">
|
||||
<metrics statements="17" coveredstatements="0" conditionals="8" coveredconditionals="0" methods="2" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="2" count="0" type="stmt"/>
|
||||
<line num="4" count="0" type="stmt"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
<line num="21" count="0" type="stmt"/>
|
||||
<line num="24" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="25" count="0" type="stmt"/>
|
||||
<line num="29" count="0" type="stmt"/>
|
||||
<line num="33" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="34" count="0" type="stmt"/>
|
||||
<line num="38" count="0" type="stmt"/>
|
||||
<line num="40" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="41" count="0" type="stmt"/>
|
||||
<line num="45" count="0" type="stmt"/>
|
||||
<line num="51" count="0" type="stmt"/>
|
||||
<line num="53" count="0" type="stmt"/>
|
||||
<line num="60" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="RegisterUserCommand.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\auth\commands\RegisterUserCommand.js">
|
||||
<metrics statements="4" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="8" count="0" type="stmt"/>
|
||||
<line num="9" count="0" type="stmt"/>
|
||||
<line num="13" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="RegisterUserCommandHandler.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\auth\commands\RegisterUserCommandHandler.js">
|
||||
<metrics statements="25" coveredstatements="0" conditionals="13" coveredconditionals="0" methods="3" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="2" count="0" type="stmt"/>
|
||||
<line num="4" count="0" type="stmt"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
<line num="13" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="stmt"/>
|
||||
<line num="25" count="0" type="cond" truecount="0" falsecount="5"/>
|
||||
<line num="26" count="0" type="stmt"/>
|
||||
<line num="29" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="30" count="0" type="stmt"/>
|
||||
<line num="34" count="0" type="stmt"/>
|
||||
<line num="35" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="36" count="0" type="stmt"/>
|
||||
<line num="40" count="0" type="stmt"/>
|
||||
<line num="44" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="45" count="0" type="stmt"/>
|
||||
<line num="49" count="0" type="stmt"/>
|
||||
<line num="52" count="0" type="stmt"/>
|
||||
<line num="61" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="62" count="0" type="stmt"/>
|
||||
<line num="63" count="0" type="stmt"/>
|
||||
<line num="68" count="0" type="stmt"/>
|
||||
<line num="74" count="0" type="stmt"/>
|
||||
<line num="76" count="0" type="stmt"/>
|
||||
<line num="83" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="application.services">
|
||||
<metrics statements="34" coveredstatements="1" conditionals="22" coveredconditionals="2" methods="14" coveredmethods="4"/>
|
||||
<file name="Container.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\services\Container.js">
|
||||
<metrics statements="1" coveredstatements="1" conditionals="2" coveredconditionals="2" methods="4" coveredmethods="4"/>
|
||||
<line num="57" count="1" type="stmt"/>
|
||||
</file>
|
||||
<file name="EmailService.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\services\EmailService.js">
|
||||
<metrics statements="4" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="2" coveredmethods="0"/>
|
||||
<line num="20" count="0" type="stmt"/>
|
||||
<line num="26" count="0" type="stmt"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="31" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="JwtService.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\services\JwtService.js">
|
||||
<metrics statements="29" coveredstatements="0" conditionals="20" coveredconditionals="0" methods="8" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="9" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="10" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
<line num="20" count="0" type="stmt"/>
|
||||
<line num="29" count="0" type="stmt"/>
|
||||
<line num="30" count="0" type="stmt"/>
|
||||
<line num="32" count="0" type="stmt"/>
|
||||
<line num="42" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="43" count="0" type="stmt"/>
|
||||
<line num="46" count="0" type="stmt"/>
|
||||
<line num="48" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="49" count="0" type="stmt"/>
|
||||
<line num="52" count="0" type="stmt"/>
|
||||
<line num="61" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="62" count="0" type="stmt"/>
|
||||
<line num="65" count="0" type="stmt"/>
|
||||
<line num="73" count="0" type="stmt"/>
|
||||
<line num="75" count="0" type="stmt"/>
|
||||
<line num="91" count="0" type="stmt"/>
|
||||
<line num="93" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="94" count="0" type="stmt"/>
|
||||
<line num="95" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="96" count="0" type="stmt"/>
|
||||
<line num="97" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="98" count="0" type="stmt"/>
|
||||
<line num="102" count="0" type="stmt"/>
|
||||
<line num="110" count="0" type="stmt"/>
|
||||
<line num="114" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="application.user.commands">
|
||||
<metrics statements="11" coveredstatements="0" conditionals="2" coveredconditionals="0" methods="3" coveredmethods="0"/>
|
||||
<file name="UpdateUserProfileCommand.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\commands\UpdateUserProfileCommand.js">
|
||||
<metrics statements="3" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="8" count="0" type="stmt"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="UpdateUserProfileCommandHandler.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\commands\UpdateUserProfileCommandHandler.js">
|
||||
<metrics statements="8" coveredstatements="0" conditionals="2" coveredconditionals="0" methods="2" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="16" count="0" type="stmt"/>
|
||||
<line num="18" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="19" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="stmt"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="28" count="0" type="stmt"/>
|
||||
<line num="32" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="application.user.queries">
|
||||
<metrics statements="27" coveredstatements="0" conditionals="8" coveredconditionals="0" methods="10" coveredmethods="0"/>
|
||||
<file name="GetAllUsersQuery.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\queries\GetAllUsersQuery.js">
|
||||
<metrics statements="1" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="GetAllUsersQueryHandler.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\queries\GetAllUsersQueryHandler.js">
|
||||
<metrics statements="4" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="3" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="16" count="0" type="stmt"/>
|
||||
<line num="21" count="0" type="stmt"/>
|
||||
<line num="25" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="GetMeQuery.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\queries\GetMeQuery.js">
|
||||
<metrics statements="2" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="GetMeQueryHandler.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\queries\GetMeQueryHandler.js">
|
||||
<metrics statements="8" coveredstatements="0" conditionals="2" coveredconditionals="0" methods="2" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="16" count="0" type="stmt"/>
|
||||
<line num="18" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="23" count="0" type="stmt"/>
|
||||
<line num="26" count="0" type="stmt"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="31" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="GetUserByIdQuery.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\queries\GetUserByIdQuery.js">
|
||||
<metrics statements="2" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="1" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
</file>
|
||||
<file name="GetUserByIdQueryHandler.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\application\user\queries\GetUserByIdQueryHandler.js">
|
||||
<metrics statements="10" coveredstatements="0" conditionals="6" coveredconditionals="0" methods="2" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="16" count="0" type="stmt"/>
|
||||
<line num="18" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="19" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="stmt"/>
|
||||
<line num="26" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="30" count="0" type="stmt"/>
|
||||
<line num="31" count="0" type="stmt"/>
|
||||
<line num="35" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="domain.irepositories">
|
||||
<metrics statements="7" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="6" coveredmethods="0"/>
|
||||
<file name="IUserRepository.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\domain\irepositories\IUserRepository.js">
|
||||
<metrics statements="7" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="6" coveredmethods="0"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
<line num="21" count="0" type="stmt"/>
|
||||
<line num="29" count="0" type="stmt"/>
|
||||
<line num="38" count="0" type="stmt"/>
|
||||
<line num="47" count="0" type="stmt"/>
|
||||
<line num="56" count="0" type="stmt"/>
|
||||
<line num="60" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="domain.models">
|
||||
<metrics statements="22" coveredstatements="0" conditionals="16" coveredconditionals="0" methods="5" coveredmethods="0"/>
|
||||
<file name="User.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\domain\models\User.js">
|
||||
<metrics statements="22" coveredstatements="0" conditionals="16" coveredconditionals="0" methods="5" coveredmethods="0"/>
|
||||
<line num="7" count="0" type="stmt"/>
|
||||
<line num="8" count="0" type="stmt"/>
|
||||
<line num="9" count="0" type="stmt"/>
|
||||
<line num="10" count="0" type="stmt"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
<line num="12" count="0" type="stmt"/>
|
||||
<line num="21" count="0" type="stmt"/>
|
||||
<line num="24" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="25" count="0" type="stmt"/>
|
||||
<line num="28" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="29" count="0" type="stmt"/>
|
||||
<line num="32" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="33" count="0" type="stmt"/>
|
||||
<line num="36" count="0" type="stmt"/>
|
||||
<line num="45" count="0" type="stmt"/>
|
||||
<line num="46" count="0" type="stmt"/>
|
||||
<line num="54" count="0" type="cond" truecount="0" falsecount="4"/>
|
||||
<line num="55" count="0" type="stmt"/>
|
||||
<line num="57" count="0" type="stmt"/>
|
||||
<line num="58" count="0" type="stmt"/>
|
||||
<line num="66" count="0" type="stmt"/>
|
||||
<line num="76" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="infrastructure.db">
|
||||
<metrics statements="25" coveredstatements="0" conditionals="8" coveredconditionals="0" methods="5" coveredmethods="0"/>
|
||||
<file name="DatabaseConnection.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\infrastructure\db\DatabaseConnection.js">
|
||||
<metrics statements="25" coveredstatements="0" conditionals="8" coveredconditionals="0" methods="5" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="9" count="0" type="stmt"/>
|
||||
<line num="16" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="17" count="0" type="stmt"/>
|
||||
<line num="18" count="0" type="stmt"/>
|
||||
<line num="21" count="0" type="stmt"/>
|
||||
<line num="22" count="0" type="stmt"/>
|
||||
<line num="26" count="0" type="stmt"/>
|
||||
<line num="27" count="0" type="stmt"/>
|
||||
<line num="29" count="0" type="stmt"/>
|
||||
<line num="30" count="0" type="stmt"/>
|
||||
<line num="39" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="40" count="0" type="stmt"/>
|
||||
<line num="42" count="0" type="stmt"/>
|
||||
<line num="49" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="50" count="0" type="stmt"/>
|
||||
<line num="51" count="0" type="stmt"/>
|
||||
<line num="52" count="0" type="stmt"/>
|
||||
<line num="61" count="0" type="stmt"/>
|
||||
<line num="62" count="0" type="stmt"/>
|
||||
<line num="63" count="0" type="stmt"/>
|
||||
<line num="65" count="0" type="stmt"/>
|
||||
<line num="66" count="0" type="stmt"/>
|
||||
<line num="72" count="0" type="stmt"/>
|
||||
<line num="74" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
<package name="infrastructure.repositories">
|
||||
<metrics statements="24" coveredstatements="0" conditionals="4" coveredconditionals="0" methods="9" coveredmethods="0"/>
|
||||
<file name="UserRepository.js" path="D:\munka\Egyetem\25_26_II\GKNB_MSTM071\Backend\negyedik gyakorlat\src\infrastructure\repositories\UserRepository.js">
|
||||
<metrics statements="24" coveredstatements="0" conditionals="4" coveredconditionals="0" methods="9" coveredmethods="0"/>
|
||||
<line num="1" count="0" type="stmt"/>
|
||||
<line num="2" count="0" type="stmt"/>
|
||||
<line num="10" count="0" type="stmt"/>
|
||||
<line num="11" count="0" type="stmt"/>
|
||||
<line num="20" count="0" type="stmt"/>
|
||||
<line num="24" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="25" count="0" type="stmt"/>
|
||||
<line num="28" count="0" type="stmt"/>
|
||||
<line num="37" count="0" type="stmt"/>
|
||||
<line num="41" count="0" type="cond" truecount="0" falsecount="2"/>
|
||||
<line num="42" count="0" type="stmt"/>
|
||||
<line num="45" count="0" type="stmt"/>
|
||||
<line num="53" count="0" type="stmt"/>
|
||||
<line num="57" count="0" type="stmt"/>
|
||||
<line num="66" count="0" type="stmt"/>
|
||||
<line num="74" count="0" type="stmt"/>
|
||||
<line num="83" count="0" type="stmt"/>
|
||||
<line num="93" count="0" type="stmt"/>
|
||||
<line num="102" count="0" type="stmt"/>
|
||||
<line num="103" count="0" type="stmt"/>
|
||||
<line num="106" count="0" type="stmt"/>
|
||||
<line num="108" count="0" type="stmt"/>
|
||||
<line num="119" count="0" type="stmt"/>
|
||||
<line num="130" count="0" type="stmt"/>
|
||||
</file>
|
||||
</package>
|
||||
</project>
|
||||
</coverage>
|
||||
File diff suppressed because one or more lines are too long
+394
@@ -0,0 +1,394 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/controllers/AuthController.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">api/controllers</a> AuthController.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/26</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/10</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/26</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a>
|
||||
<a name='L58'></a><a href='#L58'>58</a>
|
||||
<a name='L59'></a><a href='#L59'>59</a>
|
||||
<a name='L60'></a><a href='#L60'>60</a>
|
||||
<a name='L61'></a><a href='#L61'>61</a>
|
||||
<a name='L62'></a><a href='#L62'>62</a>
|
||||
<a name='L63'></a><a href='#L63'>63</a>
|
||||
<a name='L64'></a><a href='#L64'>64</a>
|
||||
<a name='L65'></a><a href='#L65'>65</a>
|
||||
<a name='L66'></a><a href='#L66'>66</a>
|
||||
<a name='L67'></a><a href='#L67'>67</a>
|
||||
<a name='L68'></a><a href='#L68'>68</a>
|
||||
<a name='L69'></a><a href='#L69'>69</a>
|
||||
<a name='L70'></a><a href='#L70'>70</a>
|
||||
<a name='L71'></a><a href='#L71'>71</a>
|
||||
<a name='L72'></a><a href='#L72'>72</a>
|
||||
<a name='L73'></a><a href='#L73'>73</a>
|
||||
<a name='L74'></a><a href='#L74'>74</a>
|
||||
<a name='L75'></a><a href='#L75'>75</a>
|
||||
<a name='L76'></a><a href='#L76'>76</a>
|
||||
<a name='L77'></a><a href='#L77'>77</a>
|
||||
<a name='L78'></a><a href='#L78'>78</a>
|
||||
<a name='L79'></a><a href='#L79'>79</a>
|
||||
<a name='L80'></a><a href='#L80'>80</a>
|
||||
<a name='L81'></a><a href='#L81'>81</a>
|
||||
<a name='L82'></a><a href='#L82'>82</a>
|
||||
<a name='L83'></a><a href='#L83'>83</a>
|
||||
<a name='L84'></a><a href='#L84'>84</a>
|
||||
<a name='L85'></a><a href='#L85'>85</a>
|
||||
<a name='L86'></a><a href='#L86'>86</a>
|
||||
<a name='L87'></a><a href='#L87'>87</a>
|
||||
<a name='L88'></a><a href='#L88'>88</a>
|
||||
<a name='L89'></a><a href='#L89'>89</a>
|
||||
<a name='L90'></a><a href='#L90'>90</a>
|
||||
<a name='L91'></a><a href='#L91'>91</a>
|
||||
<a name='L92'></a><a href='#L92'>92</a>
|
||||
<a name='L93'></a><a href='#L93'>93</a>
|
||||
<a name='L94'></a><a href='#L94'>94</a>
|
||||
<a name='L95'></a><a href='#L95'>95</a>
|
||||
<a name='L96'></a><a href='#L96'>96</a>
|
||||
<a name='L97'></a><a href='#L97'>97</a>
|
||||
<a name='L98'></a><a href='#L98'>98</a>
|
||||
<a name='L99'></a><a href='#L99'>99</a>
|
||||
<a name='L100'></a><a href='#L100'>100</a>
|
||||
<a name='L101'></a><a href='#L101'>101</a>
|
||||
<a name='L102'></a><a href='#L102'>102</a>
|
||||
<a name='L103'></a><a href='#L103'>103</a>
|
||||
<a name='L104'></a><a href='#L104'>104</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const RegisterUserCommand = <span class="cstat-no" title="statement not covered" >require('../../application/auth/commands/RegisterUserCommand');</span>
|
||||
const LoginUserCommand = <span class="cstat-no" title="statement not covered" >require('../../application/auth/commands/LoginUserCommand');</span>
|
||||
|
||||
/**
|
||||
* Auth Controller
|
||||
* Authentication endpoints using CQRS Commands with cookie-based JWT
|
||||
*/
|
||||
class AuthController {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(registerUserCommandHandler, loginUserCommandHandler, jwtService) {
|
||||
<span class="cstat-no" title="statement not covered" > this.registerUserCommandHandler = registerUserCommandHandler;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.loginUserCommandHandler = loginUserCommandHandler;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.jwtService = jwtService;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/register - User regisztráció
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync register(req, res) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
const { name, email, password } = <span class="cstat-no" title="statement not covered" >req.body;</span>
|
||||
|
||||
const command = <span class="cstat-no" title="statement not covered" >new RegisterUserCommand(name, email, password);</span>
|
||||
const result = <span class="cstat-no" title="statement not covered" >await this.registerUserCommandHandler.handle(command);</span>
|
||||
|
||||
// Set JWT token in httpOnly cookie
|
||||
<span class="cstat-no" title="statement not covered" > res.cookie(</span>
|
||||
this.jwtService.getCookieName(),
|
||||
result.token,
|
||||
this.jwtService.getCookieOptions()
|
||||
);
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(201).json({</span>
|
||||
message: 'User registered successfully',
|
||||
data: {
|
||||
user: result.user
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// Validációs hibák -> 400
|
||||
const status = <span class="cstat-no" title="statement not covered" >error.message.includes('required') || </span>
|
||||
error.message.includes('already exists') ||
|
||||
error.message.includes('Invalid') ||
|
||||
error.message.includes('must be') ? 400 : 500;
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(status).json({ error: error.message });</span>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/login - User bejelentkezés
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync login(req, res) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
const { email, password } = <span class="cstat-no" title="statement not covered" >req.body;</span>
|
||||
|
||||
const command = <span class="cstat-no" title="statement not covered" >new LoginUserCommand(email, password);</span>
|
||||
const result = <span class="cstat-no" title="statement not covered" >await this.loginUserCommandHandler.handle(command);</span>
|
||||
|
||||
// Set JWT token in httpOnly cookie
|
||||
<span class="cstat-no" title="statement not covered" > res.cookie(</span>
|
||||
this.jwtService.getCookieName(),
|
||||
result.token,
|
||||
this.jwtService.getCookieOptions()
|
||||
);
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(200).json({</span>
|
||||
message: 'Login successful',
|
||||
data: {
|
||||
user: result.user
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// Validációs vagy auth hibák -> 401
|
||||
const status = <span class="cstat-no" title="statement not covered" >error.message.includes('Invalid') || </span>
|
||||
error.message.includes('required') ? 401 : 500;
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(status).json({ error: error.message });</span>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/logout - User kijelentkezés
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync logout(req, res) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
// Clear the auth cookie
|
||||
<span class="cstat-no" title="statement not covered" > res.clearCookie(this.jwtService.getCookieName(), {</span>
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'strict',
|
||||
path: '/'
|
||||
});
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(200).json({</span>
|
||||
message: 'Logout successful'
|
||||
});
|
||||
} catch (error) {
|
||||
<span class="cstat-no" title="statement not covered" > res.status(500).json({ error: error.message });</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = AuthController;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+397
@@ -0,0 +1,397 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/controllers/UserController.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">api/controllers</a> UserController.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/39</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/8</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/5</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/39</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a>
|
||||
<a name='L58'></a><a href='#L58'>58</a>
|
||||
<a name='L59'></a><a href='#L59'>59</a>
|
||||
<a name='L60'></a><a href='#L60'>60</a>
|
||||
<a name='L61'></a><a href='#L61'>61</a>
|
||||
<a name='L62'></a><a href='#L62'>62</a>
|
||||
<a name='L63'></a><a href='#L63'>63</a>
|
||||
<a name='L64'></a><a href='#L64'>64</a>
|
||||
<a name='L65'></a><a href='#L65'>65</a>
|
||||
<a name='L66'></a><a href='#L66'>66</a>
|
||||
<a name='L67'></a><a href='#L67'>67</a>
|
||||
<a name='L68'></a><a href='#L68'>68</a>
|
||||
<a name='L69'></a><a href='#L69'>69</a>
|
||||
<a name='L70'></a><a href='#L70'>70</a>
|
||||
<a name='L71'></a><a href='#L71'>71</a>
|
||||
<a name='L72'></a><a href='#L72'>72</a>
|
||||
<a name='L73'></a><a href='#L73'>73</a>
|
||||
<a name='L74'></a><a href='#L74'>74</a>
|
||||
<a name='L75'></a><a href='#L75'>75</a>
|
||||
<a name='L76'></a><a href='#L76'>76</a>
|
||||
<a name='L77'></a><a href='#L77'>77</a>
|
||||
<a name='L78'></a><a href='#L78'>78</a>
|
||||
<a name='L79'></a><a href='#L79'>79</a>
|
||||
<a name='L80'></a><a href='#L80'>80</a>
|
||||
<a name='L81'></a><a href='#L81'>81</a>
|
||||
<a name='L82'></a><a href='#L82'>82</a>
|
||||
<a name='L83'></a><a href='#L83'>83</a>
|
||||
<a name='L84'></a><a href='#L84'>84</a>
|
||||
<a name='L85'></a><a href='#L85'>85</a>
|
||||
<a name='L86'></a><a href='#L86'>86</a>
|
||||
<a name='L87'></a><a href='#L87'>87</a>
|
||||
<a name='L88'></a><a href='#L88'>88</a>
|
||||
<a name='L89'></a><a href='#L89'>89</a>
|
||||
<a name='L90'></a><a href='#L90'>90</a>
|
||||
<a name='L91'></a><a href='#L91'>91</a>
|
||||
<a name='L92'></a><a href='#L92'>92</a>
|
||||
<a name='L93'></a><a href='#L93'>93</a>
|
||||
<a name='L94'></a><a href='#L94'>94</a>
|
||||
<a name='L95'></a><a href='#L95'>95</a>
|
||||
<a name='L96'></a><a href='#L96'>96</a>
|
||||
<a name='L97'></a><a href='#L97'>97</a>
|
||||
<a name='L98'></a><a href='#L98'>98</a>
|
||||
<a name='L99'></a><a href='#L99'>99</a>
|
||||
<a name='L100'></a><a href='#L100'>100</a>
|
||||
<a name='L101'></a><a href='#L101'>101</a>
|
||||
<a name='L102'></a><a href='#L102'>102</a>
|
||||
<a name='L103'></a><a href='#L103'>103</a>
|
||||
<a name='L104'></a><a href='#L104'>104</a>
|
||||
<a name='L105'></a><a href='#L105'>105</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const GetMeQuery = <span class="cstat-no" title="statement not covered" >require('../../application/user/queries/GetMeQuery');</span>
|
||||
const GetAllUsersQuery = <span class="cstat-no" title="statement not covered" >require('../../application/user/queries/GetAllUsersQuery');</span>
|
||||
const GetUserByIdQuery = <span class="cstat-no" title="statement not covered" >require('../../application/user/queries/GetUserByIdQuery');</span>
|
||||
const UpdateUserProfileCommand = <span class="cstat-no" title="statement not covered" >require('../../application/user/commands/UpdateUserProfileCommand');</span>
|
||||
|
||||
/**
|
||||
* User Controller
|
||||
* User-related endpoints using CQRS pattern (protected by JWT)
|
||||
*/
|
||||
class UserController {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(getMeQueryHandler, getAllUsersQueryHandler, getUserByIdQueryHandler, updateUserProfileCommandHandler) {
|
||||
<span class="cstat-no" title="statement not covered" > this.getMeQueryHandler = getMeQueryHandler;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.getAllUsersQueryHandler = getAllUsersQueryHandler;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.getUserByIdQueryHandler = getUserByIdQueryHandler;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.updateUserProfileCommandHandler = updateUserProfileCommandHandler;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/users/me - Bejelentkezett user adatai (protected)
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync getMe(req, res) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
// req.user-t az authMiddleware tölti ki a JWT-ből
|
||||
const userId = <span class="cstat-no" title="statement not covered" >req.user.userId;</span>
|
||||
|
||||
const query = <span class="cstat-no" title="statement not covered" >new GetMeQuery(userId);</span>
|
||||
const user = <span class="cstat-no" title="statement not covered" >await this.getMeQueryHandler.handle(query);</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(200).json({</span>
|
||||
message: 'User retrieved successfully',
|
||||
data: user
|
||||
});
|
||||
} catch (error) {
|
||||
const status = <span class="cstat-no" title="statement not covered" >error.message.includes('not found') ? 404 : 500;</span>
|
||||
<span class="cstat-no" title="statement not covered" > res.status(status).json({ error: error.message });</span>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/users - Összes user lekérése (protected)
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync getAll(req, res) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
const query = <span class="cstat-no" title="statement not covered" >new GetAllUsersQuery();</span>
|
||||
const users = <span class="cstat-no" title="statement not covered" >await this.getAllUsersQueryHandler.handle(query);</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(200).json({</span>
|
||||
message: 'Users retrieved successfully',
|
||||
data: users,
|
||||
count: users.length
|
||||
});
|
||||
} catch (error) {
|
||||
<span class="cstat-no" title="statement not covered" > res.status(500).json({ error: error.message });</span>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/users/:id - User lekérése ID alapján (protected)
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync getById(req, res) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
const { id } = <span class="cstat-no" title="statement not covered" >req.params;</span>
|
||||
const userId = <span class="cstat-no" title="statement not covered" >parseInt(id);</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (isNaN(userId)) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return res.status(400).json({ error: 'Invalid user ID' });</span>
|
||||
}
|
||||
|
||||
const query = <span class="cstat-no" title="statement not covered" >new GetUserByIdQuery(userId);</span>
|
||||
const user = <span class="cstat-no" title="statement not covered" >await this.getUserByIdQueryHandler.handle(query);</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(200).json({</span>
|
||||
message: 'User retrieved successfully',
|
||||
data: user
|
||||
});
|
||||
} catch (error) {
|
||||
const status = <span class="cstat-no" title="statement not covered" >error.message.includes('not found') ? 404 : 500;</span>
|
||||
<span class="cstat-no" title="statement not covered" > res.status(status).json({ error: error.message });</span>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/users/me - User profil frissítése (protected)
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync updateMe(req, res) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
const userId = <span class="cstat-no" title="statement not covered" >req.user.userId;</span>
|
||||
const { name } = <span class="cstat-no" title="statement not covered" >req.body;</span>
|
||||
|
||||
const command = <span class="cstat-no" title="statement not covered" >new UpdateUserProfileCommand(userId, name);</span>
|
||||
const user = <span class="cstat-no" title="statement not covered" >await this.updateUserProfileCommandHandler.handle(command);</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > res.status(200).json({</span>
|
||||
message: 'Profile updated successfully',
|
||||
data: user
|
||||
});
|
||||
} catch (error) {
|
||||
const status = <span class="cstat-no" title="statement not covered" >error.message.includes('required') ? 400 : 500;</span>
|
||||
<span class="cstat-no" title="statement not covered" > res.status(status).json({ error: error.message });</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = UserController;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/controllers</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> api/controllers</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/65</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/18</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/9</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/65</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file low" data-value="AuthController.js"><a href="AuthController.js.html">AuthController.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="26" class="abs low">0/26</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="10" class="abs low">0/10</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="4" class="abs low">0/4</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="26" class="abs low">0/26</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="UserController.js"><a href="UserController.js.html">UserController.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="39" class="abs low">0/39</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="8" class="abs low">0/8</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="5" class="abs low">0/5</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="39" class="abs low">0/39</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/middlewares/authMiddleware.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">api/middlewares</a> authMiddleware.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/11</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/11</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const JwtService = <span class="cstat-no" title="statement not covered" >require('../../application/services/JwtService');</span>
|
||||
|
||||
const jwtService = <span class="cstat-no" title="statement not covered" >new JwtService();</span>
|
||||
|
||||
/**
|
||||
* Authentication Middleware - JWT token ellenőrzés (Cookie-based)
|
||||
*
|
||||
* Ezt a middleware-t használd protected route-okon!
|
||||
*
|
||||
* Példa használat:
|
||||
* router.get('/me', authMiddleware, userController.getMe);
|
||||
*/
|
||||
function <span class="fstat-no" title="function not covered" >authMiddleware(</span>req, res, next) {
|
||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
||||
// 1. Token kinyerése cookieból
|
||||
const token = <span class="cstat-no" title="statement not covered" >jwtService.extractTokenFromCookies(req.cookies);</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (!token) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > return res.status(401).json({ </span>
|
||||
error: 'Authentication required',
|
||||
message: 'No token provided in cookies'
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Token verifikálása
|
||||
const decoded = <span class="cstat-no" title="statement not covered" >jwtService.verifyToken(token);</span>
|
||||
|
||||
// 3. User adatok elhelyezése req.user-ben (controller-ek használhatják)
|
||||
<span class="cstat-no" title="statement not covered" > req.user = {</span>
|
||||
userId: decoded.userId,
|
||||
email: decoded.email
|
||||
};
|
||||
|
||||
// 4. Folytatás
|
||||
<span class="cstat-no" title="statement not covered" > next();</span>
|
||||
} catch (error) {
|
||||
<span class="cstat-no" title="statement not covered" > return res.status(401).json({ </span>
|
||||
error: 'Authentication failed',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = authMiddleware;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/middlewares/corsMiddleware.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">api/middlewares</a> corsMiddleware.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const cors = <span class="cstat-no" title="statement not covered" >require('cors');</span>
|
||||
|
||||
// Engedélyezett origin-ek whitelist (környezeti változóból)
|
||||
const allowedOrigins = <span class="cstat-no" title="statement not covered" >process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'];</span>
|
||||
|
||||
const corsOptions = <span class="cstat-no" title="statement not covered" >{</span>
|
||||
origin: <span class="fstat-no" title="function not covered" >fu</span>nction (origin, callback) {
|
||||
// TODO 1: Ha nincs origin (pl. Postman, curl, backend-backend hívás), engedélyezd
|
||||
// Tipp: if (!origin) return callback(null, true);
|
||||
|
||||
// TODO 2: Ha az origin benne van az allowedOrigins-ban, engedélyezd
|
||||
// Tipp: if (allowedOrigins.includes(origin)) return callback(null, true);
|
||||
|
||||
// TODO 3: Egyébként tiltsd le CORS hibával
|
||||
// Tipp: callback(new Error('Not allowed by CORS'));
|
||||
},
|
||||
credentials: true, // Cookie/Auth header engedélyezése
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization']
|
||||
};
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = cors(corsOptions);</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/middlewares</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> api/middlewares</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/17</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/17</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file low" data-value="authMiddleware.js"><a href="authMiddleware.js.html">authMiddleware.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="11" class="abs low">0/11</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="11" class="abs low">0/11</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="corsMiddleware.js"><a href="corsMiddleware.js.html">corsMiddleware.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="4" class="abs low">0/4</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="1" class="abs low">0/1</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="4" class="abs low">0/4</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="scopeMiddleware.js"><a href="scopeMiddleware.js.html">scopeMiddleware.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="2" class="abs low">0/2</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/middlewares/scopeMiddleware.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">api/middlewares</a> scopeMiddleware.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Request szintű DI scope létrehozása
|
||||
* Middleware ami minden kéréshez új DI scope-ot készít
|
||||
*/
|
||||
function <span class="fstat-no" title="function not covered" >scopeMiddleware(</span>container) {
|
||||
<span class="cstat-no" title="statement not covered" > return <span class="fstat-no" title="function not covered" >(r</span>eq, res, next) => {</span>
|
||||
// TODO 1: Hozz létre request-specifikus scope-ot
|
||||
// Tipp: const scope = container.createScope();
|
||||
|
||||
// TODO 2: Tárold el a scope-ot req.scope alatt
|
||||
// Tipp: req.scope = scope;
|
||||
|
||||
// TODO 3: Hívd meg a next()-et
|
||||
// Tipp: next();
|
||||
};
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = scopeMiddleware;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/routers/authRoutes.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">api/routers</a> authRoutes.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/13</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/5</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/9</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const express = <span class="cstat-no" title="statement not covered" >require('express');</span>
|
||||
|
||||
/**
|
||||
* Auth Routes
|
||||
* Public endpoints (nincs JWT védelem)
|
||||
*
|
||||
* @param {Container} container - DI Container
|
||||
*/
|
||||
function <span class="fstat-no" title="function not covered" >createAuthRoutes(</span>container) {
|
||||
const router = <span class="cstat-no" title="statement not covered" >express.Router();</span>
|
||||
|
||||
// AuthController lekérése a DI Container-ből
|
||||
const authController = <span class="cstat-no" title="statement not covered" >container.resolve('AuthController');</span>
|
||||
|
||||
/**
|
||||
* POST /api/auth/register - User regisztráció
|
||||
* Body: { name, email, password }
|
||||
* Response: { user, token }
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.post('/register', <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >authController.register(req, res))</span>;</span>
|
||||
|
||||
/**
|
||||
* POST /api/auth/login - User bejelentkezés
|
||||
* Body: { email, password }
|
||||
* Response: { user, token }
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.post('/login', <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >authController.login(req, res))</span>;</span>
|
||||
|
||||
/**
|
||||
* POST /api/auth/logout - User kijelentkezés
|
||||
* Clears the authentication cookie
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.post('/logout', <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >authController.logout(req, res))</span>;</span>
|
||||
|
||||
/**
|
||||
* OPTIONS /api/auth/* - CORS preflight
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.options('*', <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >res.sendStatus(204))</span>;</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return router;</span>
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = createAuthRoutes;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/routers</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> api/routers</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/29</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/11</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/20</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file low" data-value="authRoutes.js"><a href="authRoutes.js.html">authRoutes.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="13" class="abs low">0/13</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="5" class="abs low">0/5</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="9" class="abs low">0/9</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="userRoutes.js"><a href="userRoutes.js.html">userRoutes.js</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="16" class="abs low">0/16</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="6" class="abs low">0/6</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="11" class="abs low">0/11</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for api/routers/userRoutes.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../index.html">All files</a> / <a href="index.html">api/routers</a> userRoutes.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/16</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/6</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/11</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const express = <span class="cstat-no" title="statement not covered" >require('express');</span>
|
||||
const authMiddleware = <span class="cstat-no" title="statement not covered" >require('../middlewares/authMiddleware');</span>
|
||||
|
||||
/**
|
||||
* User Routes
|
||||
* Protected endpoints (JWT authentication required)
|
||||
*
|
||||
* @param {Container} container - DI Container
|
||||
*/
|
||||
function <span class="fstat-no" title="function not covered" >createUserRoutes(</span>container) {
|
||||
const router = <span class="cstat-no" title="statement not covered" >express.Router();</span>
|
||||
|
||||
// UserController lekérése a DI Container-ből
|
||||
const userController = <span class="cstat-no" title="statement not covered" >container.resolve('UserController');</span>
|
||||
|
||||
/**
|
||||
* GET /api/users/me - Bejelentkezett user adatai
|
||||
* Headers: Authorization: Bearer <token>
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.get('/me', authMiddleware, <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >userController.getMe(req, res))</span>;</span>
|
||||
|
||||
/**
|
||||
* PUT /api/users/me - User profil frissítése
|
||||
* Headers: Authorization: Bearer <token>
|
||||
* Body: { name }
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.put('/me', authMiddleware, <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >userController.updateMe(req, res))</span>;</span>
|
||||
|
||||
/**
|
||||
* GET /api/users - Összes user lekérése (protected)
|
||||
* Headers: Authorization: Bearer <token>
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.get('/', authMiddleware, <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >userController.getAll(req, res))</span>;</span>
|
||||
|
||||
/**
|
||||
* GET /api/users/:id - User lekérése ID alapján (protected)
|
||||
* Headers: Authorization: Bearer <token>
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.get('/:id', authMiddleware, <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >userController.getById(req, res))</span>;</span>
|
||||
|
||||
/**
|
||||
* OPTIONS /api/users/* - CORS preflight
|
||||
*/
|
||||
<span class="cstat-no" title="statement not covered" > router.options('*', <span class="fstat-no" title="function not covered" >(r</span>eq, res) => <span class="cstat-no" title="statement not covered" >res.sendStatus(204))</span>;</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return router;</span>
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = createUserRoutes;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../sorter.js"></script>
|
||||
<script src="../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/auth/commands/LoginUserCommand.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">application/auth/commands</a> LoginUserCommand.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/3</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/3</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Login User Command
|
||||
* Command object for user login
|
||||
*/
|
||||
class LoginUserCommand {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(email, password) {
|
||||
<span class="cstat-no" title="statement not covered" > this.email = email;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.password = password;</span>
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = LoginUserCommand;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/auth/commands/LoginUserCommandHandler.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">application/auth/commands</a> LoginUserCommandHandler.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/17</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/8</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/2</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/17</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a>
|
||||
<a name='L58'></a><a href='#L58'>58</a>
|
||||
<a name='L59'></a><a href='#L59'>59</a>
|
||||
<a name='L60'></a><a href='#L60'>60</a>
|
||||
<a name='L61'></a><a href='#L61'>61</a></td><td class="line-coverage quiet"><span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const bcrypt = <span class="cstat-no" title="statement not covered" >require('bcryptjs');</span>
|
||||
const JwtService = <span class="cstat-no" title="statement not covered" >require('../../services/JwtService');</span>
|
||||
|
||||
const jwtService = <span class="cstat-no" title="statement not covered" >new JwtService();</span>
|
||||
|
||||
/**
|
||||
* Login User Command Handler
|
||||
* Handles user login authentication
|
||||
*/
|
||||
class LoginUserCommandHandler {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(prisma) {
|
||||
<span class="cstat-no" title="statement not covered" > this.prisma = prisma;</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute login command
|
||||
* @param {LoginUserCommand} command
|
||||
* @returns {Promise<Object>} { user, token }
|
||||
*/
|
||||
<span class="fstat-no" title="function not covered" > as</span>ync handle(command) {
|
||||
const { email, password } = <span class="cstat-no" title="statement not covered" >command;</span>
|
||||
|
||||
// Validáció
|
||||
<span class="cstat-no" title="statement not covered" > if (!email || !password) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Email and password are required');</span>
|
||||
}
|
||||
|
||||
// User keresése email alapján
|
||||
const user = <span class="cstat-no" title="statement not covered" >await this.prisma.user.findUnique({</span>
|
||||
where: { email }
|
||||
});
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (!user) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Invalid email or password');</span>
|
||||
}
|
||||
|
||||
// Jelszó ellenőrzése
|
||||
const isPasswordValid = <span class="cstat-no" title="statement not covered" >await bcrypt.compare(password, user.password);</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > if (!isPasswordValid) {</span>
|
||||
<span class="cstat-no" title="statement not covered" > throw new Error('Invalid email or password');</span>
|
||||
}
|
||||
|
||||
// JWT token generálása
|
||||
const token = <span class="cstat-no" title="statement not covered" >jwtService.generateToken({</span>
|
||||
userId: user.id,
|
||||
email: user.email
|
||||
});
|
||||
|
||||
// Jelszót ne adjuk vissza
|
||||
const { password: _, ...userWithoutPassword } = <span class="cstat-no" title="statement not covered" >user;</span>
|
||||
|
||||
<span class="cstat-no" title="statement not covered" > return {</span>
|
||||
user: userWithoutPassword,
|
||||
token
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = LoginUserCommandHandler;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for application/auth/commands/RegisterUserCommand.js</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="../../../prettify.css" />
|
||||
<link rel="stylesheet" href="../../../base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(../../../sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1><a href="../../../index.html">All files</a> / <a href="index.html">application/auth/commands</a> RegisterUserCommand.js</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/1</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">0% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/4</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line low'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
||||
* Register User Command
|
||||
* Command object for user registration
|
||||
*/
|
||||
class RegisterUserCommand {
|
||||
<span class="fstat-no" title="function not covered" > co</span>nstructor(name, email, password) {
|
||||
<span class="cstat-no" title="statement not covered" > this.name = name;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.email = email;</span>
|
||||
<span class="cstat-no" title="statement not covered" > this.password = password;</span>
|
||||
}
|
||||
}
|
||||
|
||||
<span class="cstat-no" title="statement not covered" >module.exports = RegisterUserCommand;</span>
|
||||
</pre></td></tr></table></pre>
|
||||
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-03-04T18:32:40.886Z
|
||||
</div>
|
||||
<script src="../../../prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="../../../sorter.js"></script>
|
||||
<script src="../../../block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user