Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 2 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Includes Prisma ORM with SQLite, Swagger docs, linting, formatting, and Docker s

---

## 🛠 Tech Stack
## Tech Stack

- **Node.js**
- **Express**
Expand Down Expand Up @@ -237,18 +237,7 @@ Creates a new book with multiple authors and stores it in the database.
"name": "Atomic Habits",
"price": 100,
"publisher": "Humdard Publisher",
"authors": [{ "name": "Zain" }, { "name": "Chris" }]
}
```

---

#### 400 - Bad Request

```json
{
"error": "Validation failed",
"details": "Invalid input data"
"authors": [{ "name": "Anyone" }, { "name": "Chris" }]
}
```

Expand All @@ -260,10 +249,3 @@ You can test the API using:

- Swagger UI → `http://localhost:3000/api-docs`
- Postman / Thunder Client

---

## Notes

- Ensure `express.json()` middleware is enabled
- Request body must match validation schema
File renamed without changes.
17 changes: 17 additions & 0 deletions src/controllers/update.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { bookService } from '../services/book.service.js';

export const updateBook = async (req, res) => {
try {
const { id } = req.params;
const result = await bookService.updateBook(id, req.body);
return res.status(200).json({
message: 'Book updated successfully',
data: result,
});
} catch (error) {
if (error.message === 'BOOK_NOT_FOUND') {
return res.status(404).json({ message: 'Book not found' });
}
return res.status(400).json({ message: error.message });
}
};
10 changes: 8 additions & 2 deletions src/routes/book.routes.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { validate } from '../middlewares/validate.middleware.js';
import { bookSchema } from '../validations/book.validation.js';
import { createBook } from '../controllers/book.controllers.js';
import {
bookSchema,
updateBookSchema,
} from '../validations/book.validation.js';
import { createBook } from '../controllers/book.controller.js';
import { updateBook } from '../controllers/update.controller.js';
import express from 'express';
const router = express.Router();

router.post('/', validate(bookSchema), createBook);
router.put('/:id', validate(updateBookSchema), updateBook);

export default router;
42 changes: 39 additions & 3 deletions src/services/book.service.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// src/services/book.service.js
import { prisma } from '../lib/prisma.js';

export const bookService = {
createBook: async (data) => {
const { name, authors, price, publisher } = data;
Expand All @@ -14,11 +15,46 @@ export const bookService = {
price,
publisher,
authors: {
create: authors.map((authorName) => ({
name: authorName,
})),
create: authors.map((authorName) => ({ name: authorName })),
},
},
});
},

updateBook: async (id, data) => {
const bookId = Number(id);

if (!bookId || isNaN(bookId)) {
throw new Error('Invalid book id');
}

const existingBook = await prisma.book.findUnique({
where: { id: bookId },
});

if (!existingBook) {
throw new Error('BOOK_NOT_FOUND');
}

const { name, authors, price, publisher } = data;

if (price !== undefined && price < 0) {
throw new Error('Price cannot be negative');
}

return await prisma.book.update({
where: { id: bookId },
data: {
...(name && { name }),
...(price !== undefined && { price }),
...(publisher && { publisher }),
authors: authors
? {
deleteMany: {},
create: authors.map((authorName) => ({ name: authorName })),
}
: undefined,
},
});
},
};
7 changes: 7 additions & 0 deletions src/validations/book.validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,10 @@ export const bookSchema = z.object({
price: z.number().min(0),
publisher: z.string().optional(),
});

export const updateBookSchema = z.object({
name: z.string().optional(),
authors: z.array(z.string()).optional(),
price: z.number().min(0).optional(),
publisher: z.string().optional(),
});
110 changes: 110 additions & 0 deletions swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,116 @@
}
}
}
},
"/books/{id}": {
"put": {
"summary": "Update an existing book",
"description": "Updates book details by ID. If book is not found, returns 404.",

"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "ID of the book to update",
"schema": {
"type": "integer",
"example": 1
}
}
],

"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "Atomic Habits Updated"
},
"authors": {
"type": "array",
"items": {
"type": "string"
},
"example": ["Zain", "James Clear"]
},
"price": {
"type": "number",
"example": 120
},
"publisher": {
"type": "string",
"example": "Updated Publisher"
}
}
}
}
}
},

"responses": {
"200": {
"description": "Book updated successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "Book updated successfully"
},
"data": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"publisher": {
"type": "string"
}
}
}
}
}
}
}
},

"400": {
"description": "Invalid request data",
"content": {
"application/json": {
"example": {
"message": "Invalid input or price cannot be negative"
}
}
}
},

"404": {
"description": "Book not found",
"content": {
"application/json": {
"example": {
"message": "Book not found"
}
}
}
}
}
}
}
}
}