From 0bb77c45c355145d1d536def06d682e507cb84c9 Mon Sep 17 00:00:00 2001 From: Leonardo Lodi Date: Fri, 21 Jun 2024 15:06:30 +0100 Subject: [PATCH 1/6] add db pool, get, post, put, delete http methods for books and pets tables --- db/index.js | 25 --------- src/routers/books.js | 88 ++++++++++++++++++++++++++++++- src/routers/pets.js | 95 ++++++++++++++++++++++++++++++++++ src/server.js | 3 ++ test/database-cleaner/index.js | 2 +- test/helpers/createBook.js | 2 +- test/helpers/createPet.js | 2 +- utils/dbConnection.js | 16 ++++++ 8 files changed, 204 insertions(+), 29 deletions(-) delete mode 100644 db/index.js create mode 100644 src/routers/pets.js create mode 100644 utils/dbConnection.js diff --git a/db/index.js b/db/index.js deleted file mode 100644 index af723442..00000000 --- a/db/index.js +++ /dev/null @@ -1,25 +0,0 @@ -// Load our .env file -require('dotenv').config() - -// Require Client obj from the postgres node module -const { Client } = require("pg"); - -const client = { - query: async (str, values) => { - // Get the connection string from process.env - - // the dotenv library sets this variable based - // on the contents of our env file - // Create a new connection to the database using the Client - // object provided by the postgres node module - const dbClient = new Client(process.env.PGURL) - // connect a connection - await dbClient.connect() - // execute the query - const result = await dbClient.query(str, values) - // close the connection - await dbClient.end() - return result - } -} - -module.exports = client; diff --git a/src/routers/books.js b/src/routers/books.js index 1551dd87..6780024d 100644 --- a/src/routers/books.js +++ b/src/routers/books.js @@ -1,9 +1,95 @@ const express = require('express') const router = express.Router() -const db = require("../../db"); +const dbConnection = require("../../utils/dbConnection.js") router.get('/', async (req, res) => { + const db = await dbConnection.connect() + try { + const sqlQuery = 'select * from books' + const result = await db.query(sqlQuery) + + res.json({ + books: result.rows + }) + } catch (error) { + console.log(error) + } finally { + db.release() + } +}) + +router.post('/', async (req, res) => { + const db = await dbConnection.connect() + const { title, type, author, topic, publication_date, pages } = req.body + + try { + const sqlQuery = 'insert into books (title, type, author, topic, publication_date, pages) values ($1, $2, $3, $4, $5, $6) returning *' + const result = await db.query(sqlQuery, [title, type, author, topic, publication_date, pages]) + + res.status(201).json({ + book: result.rows[0] + }) + } catch (error) { + console.log(error) + } finally { + db.release() + } +}) + +router.get('/:id', async (req, res) => { + const db = await dbConnection.connect() + const id = Number(req.params.id) + + try { + const sqlQuery = 'select * from books where id = $1' + const result = await db.query(sqlQuery, [id]) + + res.json({ + book: result.rows[0] + }) + } catch (error) { + console.log(error) + } finally { + db.release() + } +}) + +router.put('/:id', async (req, res) => { + const db = await dbConnection.connect() + const { title, type, author, topic, publication_date, pages } = req.body + const id = Number(req.params.id) + + try { + const sqlQuery = 'update books set title = $1, type = $2, author = $3, topic = $4, publication_date = $5, pages = $6 where id = $7 returning *' + const result = await db.query(sqlQuery, [title, type, author, topic, publication_date, pages, id]) + + res.status(201).json({ + book: result.rows[0] + }) + } catch (error) { + console.log(error) + } finally { + db.release() + } +}) + +router.delete('/:id', async (req, res) => { + const db = await dbConnection.connect() + const id = Number(req.params.id) + + try { + const sqlQuery = 'delete from books where id = $1 returning *' + const result = await db.query(sqlQuery, [id]) + + res.status(201).json({ + book: result.rows[0] + }) + } catch (error) { + console.log(error) + } finally { + db.release() + } }) module.exports = router diff --git a/src/routers/pets.js b/src/routers/pets.js new file mode 100644 index 00000000..ee18c49d --- /dev/null +++ b/src/routers/pets.js @@ -0,0 +1,95 @@ +const express = require("express") +const router = express.Router() +const dbConnection = require("../../utils/dbConnection.js") + +router.get('/', async (req, res) => { + const db = await dbConnection.connect() + + try { + const sqlQuery = 'select * from pets' + const result = await db.query(sqlQuery) + + res.json({ + pets: result.rows + }) + } catch (error) { + console.log(error) + } finally { + db.release() + } +}) + +router.post('/', async (req, res) => { + const db = await dbConnection.connect() + const { name, age, type, breed, has_microchip } = req.body + + try { + const sqlQuery = 'insert into pets (name, age, type, breed, has_microchip) values ($1, $2, $3, $4, $5) returning *' + const result = await db.query(sqlQuery, [name, age, type, breed, has_microchip]) + + res.status(201).json({ + pet: result.rows[0] + }) + } catch (error) { + console.log(error) + } finally { + db.release() + } +}) + +router.get('/:id', async (req, res) => { + const db = await dbConnection.connect() + const id = Number(req.params.id) + + try { + const sqlQuery = 'select * from pets where id = $1' + const result = await db.query(sqlQuery, [id]) + + res.json({ + pet: result.rows[0] + }) + } catch (error) { + console.log(error) + } finally { + db.release() + } +}) + +router.put('/:id', async (req, res) => { + const db = await dbConnection.connect() + const { name, age, type, breed, has_microchip } = req.body + const id = Number(req.params.id) + + try { + const sqlQuery = 'update pets set name = $1, age = $2, type = $3, breed = $4, has_microchip = $5 where id = $6 returning *' + const result = await db.query(sqlQuery, [name, age, type, breed, has_microchip, id]) + + res.status(201).json({ + pet: result.rows[0] + }) + } catch (error) { + console.log(error) + } finally { + db.release() + } +}) + +router.delete('/:id', async (req, res) => { + const db = await dbConnection.connect() + const id = Number(req.params.id) + + try { + const sqlQuery = 'delete from pets where id = $1 returning *' + const result = await db.query(sqlQuery, [id]) + + res.status(201).json({ + pet: result.rows[0] + }) + } catch (error) { + console.log(error) + } finally { + db.release() + } +}) + +module.exports = router diff --git a/src/server.js b/src/server.js index dac55e5d..e9834489 100644 --- a/src/server.js +++ b/src/server.js @@ -1,3 +1,4 @@ +require("dotenv").config() const express = require("express"); const morgan = require("morgan"); const cors = require("cors"); @@ -10,7 +11,9 @@ app.use(express.json()); //TODO: Implement books and pets APIs using Express Modular Routers const booksRouter = require('./routers/books.js') +const petsRouter = require('./routers/pets.js') app.use('/books', booksRouter) +app.use('/pets', petsRouter) module.exports = app diff --git a/test/database-cleaner/index.js b/test/database-cleaner/index.js index 13a4e464..d4964cdf 100644 --- a/test/database-cleaner/index.js +++ b/test/database-cleaner/index.js @@ -1,5 +1,5 @@ const fs = require('fs/promises') -const client = require("../../db"); +const client = require("../../utils/dbConnection.js"); global.beforeEach(async() => { const sqlDataForBooks = await fs.readFile('./sql/create-books.sql') diff --git a/test/helpers/createBook.js b/test/helpers/createBook.js index 5f7e9c76..6e45030e 100644 --- a/test/helpers/createBook.js +++ b/test/helpers/createBook.js @@ -1,4 +1,4 @@ -const client = require("../../db"); +const client = require("../../utils/dbConnection.js"); const createBook = async (values) => { const sqlString = `INSERT INTO "books" (title, type, author, topic, publication_date, pages) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *;` diff --git a/test/helpers/createPet.js b/test/helpers/createPet.js index 8dfca845..6746959f 100644 --- a/test/helpers/createPet.js +++ b/test/helpers/createPet.js @@ -1,4 +1,4 @@ -const client = require("../../db"); +const client = require("../../utils/dbConnection.js"); const createPet = async (values) => { const sqlString = `INSERT INTO "pets" (name, age, type, breed, has_microchip) VALUES ($1, $2, $3, $4, $5) RETURNING *;` diff --git a/utils/dbConnection.js b/utils/dbConnection.js new file mode 100644 index 00000000..ad5b5c94 --- /dev/null +++ b/utils/dbConnection.js @@ -0,0 +1,16 @@ +const { Pool } = require("pg") + +const { PGHOST, PGDATABASE, PGUSER, PGPASSWORD } = process.env + +const dbConnection = new Pool({ + host: PGHOST, + database: PGDATABASE, + username: PGUSER, + password: PGPASSWORD, + port: 5432, + ssl: { + require: true, + }, +}) + +module.exports = dbConnection From bc4c4a2d25133c0db7a0227cfedd24b9fdb6a62f Mon Sep 17 00:00:00 2001 From: Leonardo Lodi Date: Fri, 21 Jun 2024 20:01:20 +0100 Subject: [PATCH 2/6] add error handling, controllers and dal directories --- src/controllers/booksControllers.js | 71 ++++++++++++++ src/controllers/petsControllers.js | 75 +++++++++++++++ src/dal/booksRepo.js | 138 +++++++++++++++++++++++++++ src/dal/petsRepo.js | 124 ++++++++++++++++++++++++ src/errors/ConflictError.js | 3 + src/errors/MissingFieldError.js | 3 + src/errors/NotFoundError.js | 3 + src/routers/books.js | 100 +++---------------- src/routers/pets.js | 98 +++---------------- src/server.js | 44 ++++++--- {utils => src/utils}/dbConnection.js | 0 test/database-cleaner/index.js | 2 +- test/helpers/createBook.js | 2 +- test/helpers/createPet.js | 2 +- test/helpers/insertBooks.js | 2 +- test/helpers/insertPets.js | 2 +- 16 files changed, 480 insertions(+), 189 deletions(-) create mode 100644 src/controllers/booksControllers.js create mode 100644 src/controllers/petsControllers.js create mode 100644 src/dal/booksRepo.js create mode 100644 src/dal/petsRepo.js create mode 100644 src/errors/ConflictError.js create mode 100644 src/errors/MissingFieldError.js create mode 100644 src/errors/NotFoundError.js rename {utils => src/utils}/dbConnection.js (100%) diff --git a/src/controllers/booksControllers.js b/src/controllers/booksControllers.js new file mode 100644 index 00000000..377676f9 --- /dev/null +++ b/src/controllers/booksControllers.js @@ -0,0 +1,71 @@ +const { + getAllBooks, + postNewBook, + getBookById, + updateBookById, + deleteBookById, +} = require("../dal/booksRepo.js") + +const getBooks = async (req, res) => { + const books = await getAllBooks() + + res.json({ + books, + }) +} + +const postBook = async (req, res, next) => { + try { + const book = await postNewBook(req) + + res.status(201).json({ + book, + }) + } catch (error) { + next(error) + } +} + +const getBook = async (req, res, next) => { + try { + const book = await getBookById(req) + + res.json({ + book, + }) + } catch (error) { + next(error) + } +} + +const updateBook = async (req, res, next) => { + try { + const book = await updateBookById(req) + + res.status(201).json({ + book, + }) + } catch (error) { + next(error) + } +} + +const deleteBook = async (req, res, next) => { + try { + const book = await deleteBookById(req) + + res.status(201).json({ + book, + }) + } catch (error) { + next(error) + } +} + +module.exports = { + getBooks, + postBook, + getBook, + updateBook, + deleteBook, +} diff --git a/src/controllers/petsControllers.js b/src/controllers/petsControllers.js new file mode 100644 index 00000000..391cd329 --- /dev/null +++ b/src/controllers/petsControllers.js @@ -0,0 +1,75 @@ +const { + getAllPets, + postNewPet, + getPetById, + updatePetById, + deletePetById, +} = require("../dal/petsRepo.js") + +const getPets = async (req, res, next) => { + try { + const pets = await getAllPets() + + res.json({ + pets, + }) + } catch (error) { + next(error) + } +} + +const postPet = async (req, res, next) => { + try { + const pet = await postNewPet(req) + + res.status(201).json({ + pet, + }) + } catch (error) { + next(error) + } +} + +const getPet = async (req, res, next) => { + try { + const pet = await getPetById(req) + + res.json({ + pet, + }) + } catch (error) { + next(error) + } +} + +const updatePet = async (req, res, next) => { + try { + const pet = await updatePetById(req) + + res.status(201).json({ + pet, + }) + } catch (error) { + next(error) + } +} + +const deletePet = async (req, res, next) => { + try { + const pet = await deletePetById(req) + + res.status(201).json({ + pet, + }) + } catch (error) { + next(error) + } +} + +module.exports = { + getPets, + postPet, + getPet, + updatePet, + deletePet, +} diff --git a/src/dal/booksRepo.js b/src/dal/booksRepo.js new file mode 100644 index 00000000..01e140b4 --- /dev/null +++ b/src/dal/booksRepo.js @@ -0,0 +1,138 @@ +const ConflictError = require("../errors/ConflictError.js") +const MissingFieldError = require("../errors/MissingFieldError.js") +const NotFoundError = require("../errors/NotFoundError.js") +const dbConnection = require("../utils/dbConnection.js") + +const getAllBooks = async () => { + const db = await dbConnection.connect() + + try { + const sqlQuery = "select * from books" + const result = await db.query(sqlQuery) + + return result.rows + } catch (error) { + throw error + } finally { + db.release() + } +} + +const postNewBook = async (req) => { + const db = await dbConnection.connect() + const { title, type, author, topic, publication_date, pages } = req.body + + if ( + [title, type, author, topic, publication_date, pages].some( + (prop) => prop === undefined + ) + ) { + throw new MissingFieldError("Missing fields in the request body") + } + + try { + const sqlQuery = + "insert into books (title, type, author, topic, publication_date, pages) values ($1, $2, $3, $4, $5, $6) returning *" + const result = await db.query(sqlQuery, [ + title, + type, + author, + topic, + publication_date, + pages, + ]) + + return result.rows[0] + } catch (error) { + throw error + } finally { + db.release() + } +} + +const getBookById = async (req) => { + const db = await dbConnection.connect() + const id = Number(req.params.id) + + try { + const sqlQuery = "select * from books where id = $1" + const result = await db.query(sqlQuery, [id]) + + if (result.rows.length === 0) { + throw new NotFoundError(`no book with id: ${id}`) + } + + return result.rows[0] + } catch (error) { + throw error + } finally { + db.release() + } +} + +const updateBookById = async (req) => { + const db = await dbConnection.connect() + const { title, type, author, topic, publication_date, pages } = req.body + const id = Number(req.params.id) + + try { + const sqlQuery = + "update books set title = $1, type = $2, author = $3, topic = $4, publication_date = $5, pages = $6 where id = $7 returning *" + const result = await db.query(sqlQuery, [ + title, + type, + author, + topic, + publication_date, + pages, + id, + ]) + + if (result.rows.length === 0) { + throw new NotFoundError(`no book with id: ${id}`) + } + + const booksDb = await getAllBooks(req) + const titleFound = booksDb.find( + (book) => book.title.toLowerCase() === title.toLowerCase() + ) + + if (titleFound) { + throw new ConflictError(`A book with the title: ${title} already exists`) + } + + return result.rows[0] + } catch (error) { + throw error + } finally { + db.release() + } +} + +const deleteBookById = async (req) => { + const db = await dbConnection.connect() + const id = Number(req.params.id) + + try { + const sqlQuery = "delete from books where id = $1 returning *" + const result = await db.query(sqlQuery, [id]) + + if (result.rows.length === 0) { + throw new NotFoundError(`no book with id: ${id}`) + } + + return result.rows[0] + } catch (error) { + throw error + } finally { + db.release() + } +} + +module.exports = { + getAllBooks, + postNewBook, + getBookById, + updateBookById, + deleteBookById, +} diff --git a/src/dal/petsRepo.js b/src/dal/petsRepo.js new file mode 100644 index 00000000..ac40d3b0 --- /dev/null +++ b/src/dal/petsRepo.js @@ -0,0 +1,124 @@ +const MissingFieldError = require("../errors/MissingFieldError.js") +const NotFoundError = require("../errors/NotFoundError.js") +const dbConnection = require("../utils/dbConnection.js") + +const getAllPets = async () => { + const db = await dbConnection.connect() + + try { + const sqlQuery = "select * from pets" + const result = await db.query(sqlQuery) + + return result.rows + } catch (error) { + throw error + } finally { + db.release() + } +} + +const postNewPet = async (req) => { + const db = await dbConnection.connect() + const { name, age, type, breed, has_microchip } = req.body + + try { + const sqlQuery = + "insert into pets (name, age, type, breed, has_microchip) values ($1, $2, $3, $4, $5) returning *" + const result = await db.query(sqlQuery, [ + name, + age, + type, + breed, + has_microchip, + ]) + + if ( + [name, age, type, breed, has_microchip].some((prop) => prop === undefined) + ) { + throw new MissingFieldError("Missing fields in the request body") + } + + return result.rows[0] + } catch (error) { + throw error + } finally { + db.release() + } +} + +const getPetById = async (req) => { + const db = await dbConnection.connect() + const id = Number(req.params.id) + + try { + const sqlQuery = "select * from pets where id = $1" + const result = await db.query(sqlQuery, [id]) + + if (result.rows.length === 0) { + throw new NotFoundError(`no pet with id: ${id}`) + } + + return result.rows[0] + } catch (error) { + throw error + } finally { + db.release() + } +} + +const updatePetById = async (req) => { + const db = await dbConnection.connect() + const { name, age, type, breed, has_microchip } = req.body + const id = Number(req.params.id) + + try { + const sqlQuery = + "update pets set name = $1, age = $2, type = $3, breed = $4, has_microchip = $5 where id = $6 returning *" + const result = await db.query(sqlQuery, [ + name, + age, + type, + breed, + has_microchip, + id, + ]) + + if (result.rows.length === 0) { + throw new NotFoundError(`no pet with id: ${id}`) + } + + return result.rows[0] + } catch (error) { + throw error + } finally { + db.release() + } +} + +const deletePetById = async (req) => { + const db = await dbConnection.connect() + const id = Number(req.params.id) + + try { + const sqlQuery = "delete from pets where id = $1 returning *" + const result = await db.query(sqlQuery, [id]) + + if (result.rows.length === 0) { + throw new NotFoundError(`no pet with id: ${id}`) + } + + return result.rows[0] + } catch (error) { + throw error + } finally { + db.release() + } +} + +module.exports = { + getAllPets, + postNewPet, + getPetById, + updatePetById, + deletePetById, +} diff --git a/src/errors/ConflictError.js b/src/errors/ConflictError.js new file mode 100644 index 00000000..5bae3340 --- /dev/null +++ b/src/errors/ConflictError.js @@ -0,0 +1,3 @@ +class ConflictError extends Error {} + +module.exports = ConflictError diff --git a/src/errors/MissingFieldError.js b/src/errors/MissingFieldError.js new file mode 100644 index 00000000..3e3c7e24 --- /dev/null +++ b/src/errors/MissingFieldError.js @@ -0,0 +1,3 @@ +class MissingFieldError extends Error {} + +module.exports = MissingFieldError diff --git a/src/errors/NotFoundError.js b/src/errors/NotFoundError.js new file mode 100644 index 00000000..db590d5f --- /dev/null +++ b/src/errors/NotFoundError.js @@ -0,0 +1,3 @@ +class NotFoundError extends Error {} + +module.exports = NotFoundError diff --git a/src/routers/books.js b/src/routers/books.js index 6780024d..c25d61e1 100644 --- a/src/routers/books.js +++ b/src/routers/books.js @@ -1,95 +1,21 @@ -const express = require('express') +const express = require("express") const router = express.Router() -const dbConnection = require("../../utils/dbConnection.js") +const { + getBooks, + postBook, + getBook, + updateBook, + deleteBook, +} = require("../controllers/booksControllers.js") -router.get('/', async (req, res) => { - const db = await dbConnection.connect() +router.get("/", getBooks) - try { - const sqlQuery = 'select * from books' - const result = await db.query(sqlQuery) +router.post("/", postBook) - res.json({ - books: result.rows - }) - } catch (error) { - console.log(error) - } finally { - db.release() - } -}) +router.get("/:id", getBook) -router.post('/', async (req, res) => { - const db = await dbConnection.connect() - const { title, type, author, topic, publication_date, pages } = req.body +router.put("/:id", updateBook) - try { - const sqlQuery = 'insert into books (title, type, author, topic, publication_date, pages) values ($1, $2, $3, $4, $5, $6) returning *' - const result = await db.query(sqlQuery, [title, type, author, topic, publication_date, pages]) - - res.status(201).json({ - book: result.rows[0] - }) - } catch (error) { - console.log(error) - } finally { - db.release() - } -}) - -router.get('/:id', async (req, res) => { - const db = await dbConnection.connect() - const id = Number(req.params.id) - - try { - const sqlQuery = 'select * from books where id = $1' - const result = await db.query(sqlQuery, [id]) - - res.json({ - book: result.rows[0] - }) - } catch (error) { - console.log(error) - } finally { - db.release() - } -}) - -router.put('/:id', async (req, res) => { - const db = await dbConnection.connect() - const { title, type, author, topic, publication_date, pages } = req.body - const id = Number(req.params.id) - - try { - const sqlQuery = 'update books set title = $1, type = $2, author = $3, topic = $4, publication_date = $5, pages = $6 where id = $7 returning *' - const result = await db.query(sqlQuery, [title, type, author, topic, publication_date, pages, id]) - - res.status(201).json({ - book: result.rows[0] - }) - } catch (error) { - console.log(error) - } finally { - db.release() - } -}) - -router.delete('/:id', async (req, res) => { - const db = await dbConnection.connect() - const id = Number(req.params.id) - - try { - const sqlQuery = 'delete from books where id = $1 returning *' - const result = await db.query(sqlQuery, [id]) - - res.status(201).json({ - book: result.rows[0] - }) - } catch (error) { - console.log(error) - } finally { - db.release() - } -}) +router.delete("/:id", deleteBook) module.exports = router diff --git a/src/routers/pets.js b/src/routers/pets.js index ee18c49d..5dac779c 100644 --- a/src/routers/pets.js +++ b/src/routers/pets.js @@ -1,95 +1,21 @@ const express = require("express") +const { + getPets, + postPet, + getPet, + updatePet, + deletePet, +} = require("../controllers/petsControllers") const router = express.Router() -const dbConnection = require("../../utils/dbConnection.js") -router.get('/', async (req, res) => { - const db = await dbConnection.connect() +router.get("/", getPets) - try { - const sqlQuery = 'select * from pets' - const result = await db.query(sqlQuery) +router.post("/", postPet) - res.json({ - pets: result.rows - }) - } catch (error) { - console.log(error) - } finally { - db.release() - } -}) +router.get("/:id", getPet) -router.post('/', async (req, res) => { - const db = await dbConnection.connect() - const { name, age, type, breed, has_microchip } = req.body +router.put("/:id", updatePet) - try { - const sqlQuery = 'insert into pets (name, age, type, breed, has_microchip) values ($1, $2, $3, $4, $5) returning *' - const result = await db.query(sqlQuery, [name, age, type, breed, has_microchip]) - - res.status(201).json({ - pet: result.rows[0] - }) - } catch (error) { - console.log(error) - } finally { - db.release() - } -}) - -router.get('/:id', async (req, res) => { - const db = await dbConnection.connect() - const id = Number(req.params.id) - - try { - const sqlQuery = 'select * from pets where id = $1' - const result = await db.query(sqlQuery, [id]) - - res.json({ - pet: result.rows[0] - }) - } catch (error) { - console.log(error) - } finally { - db.release() - } -}) - -router.put('/:id', async (req, res) => { - const db = await dbConnection.connect() - const { name, age, type, breed, has_microchip } = req.body - const id = Number(req.params.id) - - try { - const sqlQuery = 'update pets set name = $1, age = $2, type = $3, breed = $4, has_microchip = $5 where id = $6 returning *' - const result = await db.query(sqlQuery, [name, age, type, breed, has_microchip, id]) - - res.status(201).json({ - pet: result.rows[0] - }) - } catch (error) { - console.log(error) - } finally { - db.release() - } -}) - -router.delete('/:id', async (req, res) => { - const db = await dbConnection.connect() - const id = Number(req.params.id) - - try { - const sqlQuery = 'delete from pets where id = $1 returning *' - const result = await db.query(sqlQuery, [id]) - - res.status(201).json({ - pet: result.rows[0] - }) - } catch (error) { - console.log(error) - } finally { - db.release() - } -}) +router.delete("/:id", deletePet) module.exports = router diff --git a/src/server.js b/src/server.js index e9834489..aa95a21b 100644 --- a/src/server.js +++ b/src/server.js @@ -1,19 +1,41 @@ require("dotenv").config() -const express = require("express"); -const morgan = require("morgan"); -const cors = require("cors"); +const express = require("express") +const morgan = require("morgan") +const cors = require("cors") -const app = express(); +const app = express() -app.use(morgan("dev")); -app.use(cors()); -app.use(express.json()); +app.use(morgan("dev")) +app.use(cors()) +app.use(express.json()) //TODO: Implement books and pets APIs using Express Modular Routers -const booksRouter = require('./routers/books.js') -const petsRouter = require('./routers/pets.js') +const booksRouter = require("./routers/books.js") +const petsRouter = require("./routers/pets.js") +const MissingFieldError = require("./errors/MissingFieldError.js") +const NotFoundError = require("./errors/NotFoundError.js") +const ConflictError = require("./errors/ConflictError.js") -app.use('/books', booksRouter) -app.use('/pets', petsRouter) +app.use("/books", booksRouter) +app.use("/pets", petsRouter) +app.use((error, req, res, next) => { + if (error instanceof MissingFieldError) { + return res.status(400).json({ + error: error.message, + }) + } + + if (error instanceof NotFoundError) { + return res.status(404).json({ + error: error.message, + }) + } + + if (error instanceof ConflictError) { + return res.status(409).json({ + error: error.message, + }) + } +}) module.exports = app diff --git a/utils/dbConnection.js b/src/utils/dbConnection.js similarity index 100% rename from utils/dbConnection.js rename to src/utils/dbConnection.js diff --git a/test/database-cleaner/index.js b/test/database-cleaner/index.js index d4964cdf..7d3291ac 100644 --- a/test/database-cleaner/index.js +++ b/test/database-cleaner/index.js @@ -1,5 +1,5 @@ const fs = require('fs/promises') -const client = require("../../utils/dbConnection.js"); +const client = require("../../src/utils/dbConnection.js"); global.beforeEach(async() => { const sqlDataForBooks = await fs.readFile('./sql/create-books.sql') diff --git a/test/helpers/createBook.js b/test/helpers/createBook.js index 6e45030e..f8cae53d 100644 --- a/test/helpers/createBook.js +++ b/test/helpers/createBook.js @@ -1,4 +1,4 @@ -const client = require("../../utils/dbConnection.js"); +const client = require("../../src/utils/dbConnection.js"); const createBook = async (values) => { const sqlString = `INSERT INTO "books" (title, type, author, topic, publication_date, pages) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *;` diff --git a/test/helpers/createPet.js b/test/helpers/createPet.js index 6746959f..54bb5b32 100644 --- a/test/helpers/createPet.js +++ b/test/helpers/createPet.js @@ -1,4 +1,4 @@ -const client = require("../../utils/dbConnection.js"); +const client = require("../../src/utils/dbConnection.js"); const createPet = async (values) => { const sqlString = `INSERT INTO "pets" (name, age, type, breed, has_microchip) VALUES ($1, $2, $3, $4, $5) RETURNING *;` diff --git a/test/helpers/insertBooks.js b/test/helpers/insertBooks.js index 54e720ca..3bab2201 100644 --- a/test/helpers/insertBooks.js +++ b/test/helpers/insertBooks.js @@ -1,5 +1,5 @@ const fs = require('fs/promises') -const client = require("../../db"); +const client = require("../../src/utils/dbConnection.js"); const insertBooks = async () => { const sqlDataForBooks = await fs.readFile('./sql/insert-books.sql') diff --git a/test/helpers/insertPets.js b/test/helpers/insertPets.js index eadddec2..2eed6b86 100644 --- a/test/helpers/insertPets.js +++ b/test/helpers/insertPets.js @@ -1,5 +1,5 @@ const fs = require('fs/promises') -const client = require("../../db"); +const client = require("../../src/utils/dbConnection.js"); const insertPets = async () => { const sqlDataForPets = await fs.readFile('./sql/insert-pets.sql') From a157f67e43e65ba2c7a6641eb21db1a986a52ffc Mon Sep 17 00:00:00 2001 From: Leonardo Lodi Date: Sun, 23 Jun 2024 12:44:19 +0100 Subject: [PATCH 3/6] adjust getAllBooks query --- src/controllers/booksControllers.js | 14 +++--- src/dal/booksRepo.js | 67 +++++++++++++++++++++++------ 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/src/controllers/booksControllers.js b/src/controllers/booksControllers.js index 377676f9..f989fb78 100644 --- a/src/controllers/booksControllers.js +++ b/src/controllers/booksControllers.js @@ -6,12 +6,16 @@ const { deleteBookById, } = require("../dal/booksRepo.js") -const getBooks = async (req, res) => { - const books = await getAllBooks() +const getBooks = async (req, res, next) => { + try { + const books = await getAllBooks(req) - res.json({ - books, - }) + res.json({ + books, + }) + } catch (error) { + next(error) + } } const postBook = async (req, res, next) => { diff --git a/src/dal/booksRepo.js b/src/dal/booksRepo.js index 01e140b4..c7550faf 100644 --- a/src/dal/booksRepo.js +++ b/src/dal/booksRepo.js @@ -3,12 +3,57 @@ const MissingFieldError = require("../errors/MissingFieldError.js") const NotFoundError = require("../errors/NotFoundError.js") const dbConnection = require("../utils/dbConnection.js") -const getAllBooks = async () => { +const getAllBooks = async (req) => { const db = await dbConnection.connect() + const author = req.query.author + let page = 1 + let perPage = 20 try { - const sqlQuery = "select * from books" - const result = await db.query(sqlQuery) + let sqlQuery = "select * from books" + let result = await db.query(sqlQuery) + + if (author) { + sqlQuery = "select * from books where author = $1" + result = await db.query(sqlQuery, [author]) + } + + if (req.query.page && req.query.perPage) { + page = Number(req.query.page) + perPage = Number(req.query.perPage) + + if (perPage > 50 || perPage < 10 || page < 1) { + throw new MissingFieldError(`parameter invalid perPage: ${perPage} not valid. Accepted range is 10 - 50`) + } + + const calculateOffset = () => { + if (page === 1) { + return 0 + } else if (page === 2) { + return perPage + } else { + return perPage * page + } + } + + sqlQuery = "select * from books limit $1 offset $2" + result = await db.query(sqlQuery, [perPage, calculateOffset()]) + + if (author) { + sqlQuery = "select * from books where author = $1 limit $2 offset $3" + result = await db.query(sqlQuery, [author, perPage, calculateOffset()]) + } + + if (result.rows.length === 0) { + throw new MissingFieldError("The number of books per page exceeds the total") + } + + return { + ...result.rows, + perPage, + page + } + } return result.rows } catch (error) { @@ -76,6 +121,13 @@ const updateBookById = async (req) => { const id = Number(req.params.id) try { + const conflictQuery = "select * from books where title = $1 and id != $2" + const conflictResult = await db.query(conflictQuery, [title, id]) + + if (conflictResult.rows.length > 0) { + throw new ConflictError(`A book with the title: ${title} already exists`) + } + const sqlQuery = "update books set title = $1, type = $2, author = $3, topic = $4, publication_date = $5, pages = $6 where id = $7 returning *" const result = await db.query(sqlQuery, [ @@ -92,15 +144,6 @@ const updateBookById = async (req) => { throw new NotFoundError(`no book with id: ${id}`) } - const booksDb = await getAllBooks(req) - const titleFound = booksDb.find( - (book) => book.title.toLowerCase() === title.toLowerCase() - ) - - if (titleFound) { - throw new ConflictError(`A book with the title: ${title} already exists`) - } - return result.rows[0] } catch (error) { throw error From aa85314c2771e9696e27951bc539ec3bcec8a5ee Mon Sep 17 00:00:00 2001 From: Leonardo Lodi Date: Sun, 23 Jun 2024 13:10:02 +0100 Subject: [PATCH 4/6] add breeds router, controllers and get http method --- src/controllers/breedsControllers.js | 21 +++++++++++++++++++++ src/routers/breeds.js | 7 +++++++ src/server.js | 2 ++ 3 files changed, 30 insertions(+) create mode 100644 src/controllers/breedsControllers.js create mode 100644 src/routers/breeds.js diff --git a/src/controllers/breedsControllers.js b/src/controllers/breedsControllers.js new file mode 100644 index 00000000..9d1a15dc --- /dev/null +++ b/src/controllers/breedsControllers.js @@ -0,0 +1,21 @@ +const dbConnection = require("../utils/dbConnection.js") + +const getAllBreeds = async (req, res) => { + const db = await dbConnection.connect() + const type = req.query.type + + try { + const sqlQuery = "select distinct breed from pets where type = $1" + const result = await db.query(sqlQuery, [type]) + + res.json({ + breeds: result.rows + }) + } catch (error) { + throw error + } finally { + db.release() + } +} + +module.exports = getAllBreeds \ No newline at end of file diff --git a/src/routers/breeds.js b/src/routers/breeds.js new file mode 100644 index 00000000..95990643 --- /dev/null +++ b/src/routers/breeds.js @@ -0,0 +1,7 @@ +const express = require("express") +const router = express.Router() +const getAllBreeds = require("../controllers/breedsControllers.js") + +router.get("/", getAllBreeds) + +module.exports = router diff --git a/src/server.js b/src/server.js index aa95a21b..cdc97825 100644 --- a/src/server.js +++ b/src/server.js @@ -12,12 +12,14 @@ app.use(express.json()) //TODO: Implement books and pets APIs using Express Modular Routers const booksRouter = require("./routers/books.js") const petsRouter = require("./routers/pets.js") +const breedsRouter = require("./routers/breeds.js") const MissingFieldError = require("./errors/MissingFieldError.js") const NotFoundError = require("./errors/NotFoundError.js") const ConflictError = require("./errors/ConflictError.js") app.use("/books", booksRouter) app.use("/pets", petsRouter) +app.use("/breeds", breedsRouter) app.use((error, req, res, next) => { if (error instanceof MissingFieldError) { return res.status(400).json({ From deb347669c27717d5c17b9d3711ead19796fbed1 Mon Sep 17 00:00:00 2001 From: Leonardo Lodi Date: Sun, 23 Jun 2024 13:58:23 +0100 Subject: [PATCH 5/6] add pagination to pets --- src/controllers/petsControllers.js | 2 +- src/dal/petsRepo.js | 58 +++++++++++++++++++++++++----- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/controllers/petsControllers.js b/src/controllers/petsControllers.js index 391cd329..a30dae4b 100644 --- a/src/controllers/petsControllers.js +++ b/src/controllers/petsControllers.js @@ -8,7 +8,7 @@ const { const getPets = async (req, res, next) => { try { - const pets = await getAllPets() + const pets = await getAllPets(req) res.json({ pets, diff --git a/src/dal/petsRepo.js b/src/dal/petsRepo.js index ac40d3b0..5d51da70 100644 --- a/src/dal/petsRepo.js +++ b/src/dal/petsRepo.js @@ -2,12 +2,46 @@ const MissingFieldError = require("../errors/MissingFieldError.js") const NotFoundError = require("../errors/NotFoundError.js") const dbConnection = require("../utils/dbConnection.js") -const getAllPets = async () => { +const getAllPets = async (req) => { const db = await dbConnection.connect() + let page = 1 + let perPage = 20 try { - const sqlQuery = "select * from pets" - const result = await db.query(sqlQuery) + let sqlQuery = "select * from pets" + let result = await db.query(sqlQuery) + + if (req.query.page && req.query.perPage) { + page = Number(req.query.page) + perPage = Number(req.query.perPage) + + if (perPage > 50 || perPage < 10 || page < 1) { + throw new MissingFieldError(`parameter invalid perPage: ${perPage} not valid. Accepted range is 10 - 50`) + } + + const calculateOffset = () => { + if (page === 1) { + return 0 + } else if (page === 2) { + return perPage + } else { + return perPage * page + } + } + + sqlQuery = "select * from pets limit $1 offset $2" + result = await db.query(sqlQuery, [perPage, calculateOffset()]) + + if (result.rows.length === 0) { + throw new MissingFieldError("The number of pets per page exceeds the total") + } + + return { + ...result.rows, + perPage, + page + } + } return result.rows } catch (error) { @@ -20,8 +54,20 @@ const getAllPets = async () => { const postNewPet = async (req) => { const db = await dbConnection.connect() const { name, age, type, breed, has_microchip } = req.body + const props = { name, age, type, breed, has_microchip } + const undefinedProps = [] try { + Object.keys(props).forEach(prop => { + if (props[prop] === undefined) { + undefinedProps.push(prop) + } + }) + + if (undefinedProps.length > 0) { + throw new MissingFieldError(`missing fields: ${undefinedProps.join(', ')}`) + } + const sqlQuery = "insert into pets (name, age, type, breed, has_microchip) values ($1, $2, $3, $4, $5) returning *" const result = await db.query(sqlQuery, [ @@ -32,12 +78,6 @@ const postNewPet = async (req) => { has_microchip, ]) - if ( - [name, age, type, breed, has_microchip].some((prop) => prop === undefined) - ) { - throw new MissingFieldError("Missing fields in the request body") - } - return result.rows[0] } catch (error) { throw error From 677ede283be3da12b2b07bfbd81dac71eff30b4b Mon Sep 17 00:00:00 2001 From: Leonardo Lodi Date: Mon, 24 Jun 2024 12:29:18 +0100 Subject: [PATCH 6/6] add express-async-errors and adjust pagination --- package-lock.json | 9 ++ package.json | 1 + src/controllers/booksControllers.js | 74 ++++------ src/controllers/breedsControllers.js | 20 +-- src/controllers/petsControllers.js | 74 ++++------ src/dal/booksRepo.js | 196 +++++++++------------------ src/dal/petsRepo.js | 181 ++++++++----------------- src/routers/books.js | 4 - src/routers/pets.js | 6 +- src/server.js | 9 +- src/utils/pagination.js | 16 +++ 11 files changed, 218 insertions(+), 372 deletions(-) create mode 100644 src/utils/pagination.js diff --git a/package-lock.json b/package-lock.json index 37ccdff8..b886cb18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", + "express-async-errors": "^3.1.1", "faker": "^5.5.3", "morgan": "1.10.0", "pg": "8.6.0", @@ -2064,6 +2065,14 @@ "node": ">= 0.10.0" } }, + "node_modules/express-async-errors": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/express-async-errors/-/express-async-errors-3.1.1.tgz", + "integrity": "sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==", + "peerDependencies": { + "express": "^4.16.2" + } + }, "node_modules/express/node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", diff --git a/package.json b/package.json index 57d8b4fb..21dc9ad5 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", + "express-async-errors": "^3.1.1", "faker": "^5.5.3", "morgan": "1.10.0", "pg": "8.6.0", diff --git a/src/controllers/booksControllers.js b/src/controllers/booksControllers.js index f989fb78..aa284117 100644 --- a/src/controllers/booksControllers.js +++ b/src/controllers/booksControllers.js @@ -5,65 +5,49 @@ const { updateBookById, deleteBookById, } = require("../dal/booksRepo.js") +const getPaginationParams = require("../utils/pagination.js") -const getBooks = async (req, res, next) => { - try { - const books = await getAllBooks(req) +const getBooks = async (req, res) => { + const { page, per_page } = getPaginationParams(req) + const books = await getAllBooks(req) - res.json({ - books, - }) - } catch (error) { - next(error) - } + res.json({ + books, + per_page, + page + }) } -const postBook = async (req, res, next) => { - try { - const book = await postNewBook(req) +const postBook = async (req, res) => { + const book = await postNewBook(req) - res.status(201).json({ - book, - }) - } catch (error) { - next(error) - } + res.status(201).json({ + book, + }) } -const getBook = async (req, res, next) => { - try { - const book = await getBookById(req) +const getBook = async (req, res) => { + const book = await getBookById(req) - res.json({ - book, - }) - } catch (error) { - next(error) - } + res.json({ + book, + }) } -const updateBook = async (req, res, next) => { - try { - const book = await updateBookById(req) +const updateBook = async (req, res) => { + const book = await updateBookById(req) - res.status(201).json({ - book, - }) - } catch (error) { - next(error) - } + res.status(201).json({ + book, + }) } -const deleteBook = async (req, res, next) => { - try { - const book = await deleteBookById(req) +const deleteBook = async (req, res) => { + const book = await deleteBookById(req) - res.status(201).json({ - book, - }) - } catch (error) { - next(error) - } + res.status(201).json({ + book, + }) } module.exports = { diff --git a/src/controllers/breedsControllers.js b/src/controllers/breedsControllers.js index 9d1a15dc..3144d940 100644 --- a/src/controllers/breedsControllers.js +++ b/src/controllers/breedsControllers.js @@ -1,21 +1,13 @@ const dbConnection = require("../utils/dbConnection.js") const getAllBreeds = async (req, res) => { - const db = await dbConnection.connect() const type = req.query.type + const sqlQuery = "select distinct breed from pets where type = $1" + const result = await dbConnection.query(sqlQuery, [type]) - try { - const sqlQuery = "select distinct breed from pets where type = $1" - const result = await db.query(sqlQuery, [type]) - - res.json({ - breeds: result.rows - }) - } catch (error) { - throw error - } finally { - db.release() - } + res.json({ + breeds: result.rows + }) } -module.exports = getAllBreeds \ No newline at end of file +module.exports = getAllBreeds diff --git a/src/controllers/petsControllers.js b/src/controllers/petsControllers.js index a30dae4b..ec7b6112 100644 --- a/src/controllers/petsControllers.js +++ b/src/controllers/petsControllers.js @@ -5,65 +5,49 @@ const { updatePetById, deletePetById, } = require("../dal/petsRepo.js") +const getPaginationParams = require("../utils/pagination.js") -const getPets = async (req, res, next) => { - try { - const pets = await getAllPets(req) +const getPets = async (req, res) => { + const { page, per_page } = getPaginationParams(req) + const pets = await getAllPets(req) - res.json({ - pets, - }) - } catch (error) { - next(error) - } + res.json({ + pets, + per_page, + page + }) } -const postPet = async (req, res, next) => { - try { - const pet = await postNewPet(req) +const postPet = async (req, res) => { + const pet = await postNewPet(req) - res.status(201).json({ - pet, - }) - } catch (error) { - next(error) - } + res.status(201).json({ + pet, + }) } -const getPet = async (req, res, next) => { - try { - const pet = await getPetById(req) +const getPet = async (req, res) => { + const pet = await getPetById(req) - res.json({ - pet, - }) - } catch (error) { - next(error) - } + res.json({ + pet, + }) } -const updatePet = async (req, res, next) => { - try { - const pet = await updatePetById(req) +const updatePet = async (req, res) => { + const pet = await updatePetById(req) - res.status(201).json({ - pet, - }) - } catch (error) { - next(error) - } + res.status(201).json({ + pet, + }) } -const deletePet = async (req, res, next) => { - try { - const pet = await deletePetById(req) +const deletePet = async (req, res) => { + const pet = await deletePetById(req) - res.status(201).json({ - pet, - }) - } catch (error) { - next(error) - } + res.status(201).json({ + pet, + }) } module.exports = { diff --git a/src/dal/booksRepo.js b/src/dal/booksRepo.js index c7550faf..9d412e45 100644 --- a/src/dal/booksRepo.js +++ b/src/dal/booksRepo.js @@ -2,69 +2,33 @@ const ConflictError = require("../errors/ConflictError.js") const MissingFieldError = require("../errors/MissingFieldError.js") const NotFoundError = require("../errors/NotFoundError.js") const dbConnection = require("../utils/dbConnection.js") +const getPaginationParams = require("../utils/pagination.js") const getAllBooks = async (req) => { - const db = await dbConnection.connect() + const { page, per_page } = getPaginationParams(req) const author = req.query.author - let page = 1 - let perPage = 20 - - try { - let sqlQuery = "select * from books" - let result = await db.query(sqlQuery) - - if (author) { - sqlQuery = "select * from books where author = $1" - result = await db.query(sqlQuery, [author]) - } - - if (req.query.page && req.query.perPage) { - page = Number(req.query.page) - perPage = Number(req.query.perPage) - - if (perPage > 50 || perPage < 10 || page < 1) { - throw new MissingFieldError(`parameter invalid perPage: ${perPage} not valid. Accepted range is 10 - 50`) - } - - const calculateOffset = () => { - if (page === 1) { - return 0 - } else if (page === 2) { - return perPage - } else { - return perPage * page - } - } - - sqlQuery = "select * from books limit $1 offset $2" - result = await db.query(sqlQuery, [perPage, calculateOffset()]) - - if (author) { - sqlQuery = "select * from books where author = $1 limit $2 offset $3" - result = await db.query(sqlQuery, [author, perPage, calculateOffset()]) - } - - if (result.rows.length === 0) { - throw new MissingFieldError("The number of books per page exceeds the total") - } - - return { - ...result.rows, - perPage, - page - } - } - - return result.rows - } catch (error) { - throw error - } finally { - db.release() + + let sqlQuery = "select * from books" + let result = await dbConnection.query(sqlQuery) + + const calculateOffset = () => (page - 1) * per_page + + if (author) { + sqlQuery = "select * from books where author = $1 limit $2 offset $3" + result = await dbConnection.query(sqlQuery, [ + author, + per_page, + calculateOffset(), + ]) + } else { + sqlQuery = "select * from books limit $1 offset $2" + result = await dbConnection.query(sqlQuery, [per_page, calculateOffset()]) } + + return result.rows } const postNewBook = async (req) => { - const db = await dbConnection.connect() const { title, type, author, topic, publication_date, pages } = req.body if ( @@ -75,101 +39,71 @@ const postNewBook = async (req) => { throw new MissingFieldError("Missing fields in the request body") } - try { - const sqlQuery = - "insert into books (title, type, author, topic, publication_date, pages) values ($1, $2, $3, $4, $5, $6) returning *" - const result = await db.query(sqlQuery, [ - title, - type, - author, - topic, - publication_date, - pages, - ]) - - return result.rows[0] - } catch (error) { - throw error - } finally { - db.release() - } + const sqlQuery = + "insert into books (title, type, author, topic, publication_date, pages) values ($1, $2, $3, $4, $5, $6) returning *" + const result = await dbConnection.query(sqlQuery, [ + title, + type, + author, + topic, + publication_date, + pages, + ]) + + return result.rows[0] } const getBookById = async (req) => { - const db = await dbConnection.connect() const id = Number(req.params.id) + const sqlQuery = "select * from books where id = $1" + const result = await dbConnection.query(sqlQuery, [id]) - try { - const sqlQuery = "select * from books where id = $1" - const result = await db.query(sqlQuery, [id]) - - if (result.rows.length === 0) { - throw new NotFoundError(`no book with id: ${id}`) - } - - return result.rows[0] - } catch (error) { - throw error - } finally { - db.release() + if (result.rows.length === 0) { + throw new NotFoundError(`no book with id: ${id}`) } + + return result.rows[0] } const updateBookById = async (req) => { - const db = await dbConnection.connect() const { title, type, author, topic, publication_date, pages } = req.body const id = Number(req.params.id) + const conflictQuery = "select * from books where title = $1 and id != $2" + const conflictResult = await dbConnection.query(conflictQuery, [title, id]) - try { - const conflictQuery = "select * from books where title = $1 and id != $2" - const conflictResult = await db.query(conflictQuery, [title, id]) - - if (conflictResult.rows.length > 0) { - throw new ConflictError(`A book with the title: ${title} already exists`) - } - - const sqlQuery = - "update books set title = $1, type = $2, author = $3, topic = $4, publication_date = $5, pages = $6 where id = $7 returning *" - const result = await db.query(sqlQuery, [ - title, - type, - author, - topic, - publication_date, - pages, - id, - ]) - - if (result.rows.length === 0) { - throw new NotFoundError(`no book with id: ${id}`) - } + if (conflictResult.rows.length > 0) { + throw new ConflictError(`A book with the title: ${title} already exists`) + } - return result.rows[0] - } catch (error) { - throw error - } finally { - db.release() + const sqlQuery = + "update books set title = $1, type = $2, author = $3, topic = $4, publication_date = $5, pages = $6 where id = $7 returning *" + const result = await dbConnection.query(sqlQuery, [ + title, + type, + author, + topic, + publication_date, + pages, + id, + ]) + + if (result.rows.length === 0) { + throw new NotFoundError(`no book with id: ${id}`) } + + return result.rows[0] } const deleteBookById = async (req) => { - const db = await dbConnection.connect() const id = Number(req.params.id) + const sqlQuery = "delete from books where id = $1 returning *" + const result = await dbConnection.query(sqlQuery, [id]) - try { - const sqlQuery = "delete from books where id = $1 returning *" - const result = await db.query(sqlQuery, [id]) - - if (result.rows.length === 0) { - throw new NotFoundError(`no book with id: ${id}`) - } - - return result.rows[0] - } catch (error) { - throw error - } finally { - db.release() + if (result.rows.length === 0) { + throw new NotFoundError(`no book with id: ${id}`) } + + return result.rows[0] } module.exports = { diff --git a/src/dal/petsRepo.js b/src/dal/petsRepo.js index 5d51da70..b4f8e6d3 100644 --- a/src/dal/petsRepo.js +++ b/src/dal/petsRepo.js @@ -1,158 +1,91 @@ const MissingFieldError = require("../errors/MissingFieldError.js") const NotFoundError = require("../errors/NotFoundError.js") const dbConnection = require("../utils/dbConnection.js") +const getPaginationParams = require("../utils/pagination.js") const getAllPets = async (req) => { - const db = await dbConnection.connect() - let page = 1 - let perPage = 20 - - try { - let sqlQuery = "select * from pets" - let result = await db.query(sqlQuery) - - if (req.query.page && req.query.perPage) { - page = Number(req.query.page) - perPage = Number(req.query.perPage) - - if (perPage > 50 || perPage < 10 || page < 1) { - throw new MissingFieldError(`parameter invalid perPage: ${perPage} not valid. Accepted range is 10 - 50`) - } - - const calculateOffset = () => { - if (page === 1) { - return 0 - } else if (page === 2) { - return perPage - } else { - return perPage * page - } - } - - sqlQuery = "select * from pets limit $1 offset $2" - result = await db.query(sqlQuery, [perPage, calculateOffset()]) - - if (result.rows.length === 0) { - throw new MissingFieldError("The number of pets per page exceeds the total") - } - - return { - ...result.rows, - perPage, - page - } - } - - return result.rows - } catch (error) { - throw error - } finally { - db.release() - } + const { page, per_page } = getPaginationParams(req) + + let sqlQuery = "select * from pets" + let result = await dbConnection.query(sqlQuery) + + const calculateOffset = () => (page - 1) * per_page + + sqlQuery = "select * from pets limit $1 offset $2" + result = await dbConnection.query(sqlQuery, [per_page, calculateOffset()]) + + return result.rows } const postNewPet = async (req) => { - const db = await dbConnection.connect() const { name, age, type, breed, has_microchip } = req.body - const props = { name, age, type, breed, has_microchip } - const undefinedProps = [] - - try { - Object.keys(props).forEach(prop => { - if (props[prop] === undefined) { - undefinedProps.push(prop) - } - }) - - if (undefinedProps.length > 0) { - throw new MissingFieldError(`missing fields: ${undefinedProps.join(', ')}`) - } - - const sqlQuery = - "insert into pets (name, age, type, breed, has_microchip) values ($1, $2, $3, $4, $5) returning *" - const result = await db.query(sqlQuery, [ - name, - age, - type, - breed, - has_microchip, - ]) - - return result.rows[0] - } catch (error) { - throw error - } finally { - db.release() + const undefinedProps = ['name', 'age', 'type', 'breed', 'has_microchip'].filter( + (prop) => req.body[prop] === undefined + ) + + if (undefinedProps.length > 0) { + throw new MissingFieldError(`missing fields: ${undefinedProps.join(", ")}`) } + + const sqlQuery = + "insert into pets (name, age, type, breed, has_microchip) values ($1, $2, $3, $4, $5) returning *" + const result = await dbConnection.query(sqlQuery, [ + name, + age, + type, + breed, + has_microchip, + ]) + + return result.rows[0] } const getPetById = async (req) => { - const db = await dbConnection.connect() const id = Number(req.params.id) - try { - const sqlQuery = "select * from pets where id = $1" - const result = await db.query(sqlQuery, [id]) - - if (result.rows.length === 0) { - throw new NotFoundError(`no pet with id: ${id}`) - } + const sqlQuery = "select * from pets where id = $1" + const result = await dbConnection.query(sqlQuery, [id]) - return result.rows[0] - } catch (error) { - throw error - } finally { - db.release() + if (result.rows.length === 0) { + throw new NotFoundError(`no pet with id: ${id}`) } + + return result.rows[0] } const updatePetById = async (req) => { - const db = await dbConnection.connect() const { name, age, type, breed, has_microchip } = req.body const id = Number(req.params.id) - try { - const sqlQuery = - "update pets set name = $1, age = $2, type = $3, breed = $4, has_microchip = $5 where id = $6 returning *" - const result = await db.query(sqlQuery, [ - name, - age, - type, - breed, - has_microchip, - id, - ]) - - if (result.rows.length === 0) { - throw new NotFoundError(`no pet with id: ${id}`) - } - - return result.rows[0] - } catch (error) { - throw error - } finally { - db.release() + const sqlQuery = + "update pets set name = $1, age = $2, type = $3, breed = $4, has_microchip = $5 where id = $6 returning *" + const result = await dbConnection.query(sqlQuery, [ + name, + age, + type, + breed, + has_microchip, + id, + ]) + + if (result.rows.length === 0) { + throw new NotFoundError(`no pet with id: ${id}`) } + + return result.rows[0] } const deletePetById = async (req) => { - const db = await dbConnection.connect() const id = Number(req.params.id) - try { - const sqlQuery = "delete from pets where id = $1 returning *" - const result = await db.query(sqlQuery, [id]) - - if (result.rows.length === 0) { - throw new NotFoundError(`no pet with id: ${id}`) - } + const sqlQuery = "delete from pets where id = $1 returning *" + const result = await dbConnection.query(sqlQuery, [id]) - return result.rows[0] - } catch (error) { - throw error - } finally { - db.release() + if (result.rows.length === 0) { + throw new NotFoundError(`no pet with id: ${id}`) } + + return result.rows[0] } module.exports = { diff --git a/src/routers/books.js b/src/routers/books.js index c25d61e1..7b525aed 100644 --- a/src/routers/books.js +++ b/src/routers/books.js @@ -9,13 +9,9 @@ const { } = require("../controllers/booksControllers.js") router.get("/", getBooks) - router.post("/", postBook) - router.get("/:id", getBook) - router.put("/:id", updateBook) - router.delete("/:id", deleteBook) module.exports = router diff --git a/src/routers/pets.js b/src/routers/pets.js index 5dac779c..f078ab7c 100644 --- a/src/routers/pets.js +++ b/src/routers/pets.js @@ -1,4 +1,5 @@ const express = require("express") +const router = express.Router() const { getPets, postPet, @@ -6,16 +7,11 @@ const { updatePet, deletePet, } = require("../controllers/petsControllers") -const router = express.Router() router.get("/", getPets) - router.post("/", postPet) - router.get("/:id", getPet) - router.put("/:id", updatePet) - router.delete("/:id", deletePet) module.exports = router diff --git a/src/server.js b/src/server.js index cdc97825..49bb9b60 100644 --- a/src/server.js +++ b/src/server.js @@ -1,5 +1,6 @@ require("dotenv").config() const express = require("express") +require("express-async-errors") const morgan = require("morgan") const cors = require("cors") @@ -9,7 +10,6 @@ app.use(morgan("dev")) app.use(cors()) app.use(express.json()) -//TODO: Implement books and pets APIs using Express Modular Routers const booksRouter = require("./routers/books.js") const petsRouter = require("./routers/pets.js") const breedsRouter = require("./routers/breeds.js") @@ -20,21 +20,22 @@ const ConflictError = require("./errors/ConflictError.js") app.use("/books", booksRouter) app.use("/pets", petsRouter) app.use("/breeds", breedsRouter) + app.use((error, req, res, next) => { if (error instanceof MissingFieldError) { - return res.status(400).json({ + res.status(400).json({ error: error.message, }) } if (error instanceof NotFoundError) { - return res.status(404).json({ + res.status(404).json({ error: error.message, }) } if (error instanceof ConflictError) { - return res.status(409).json({ + res.status(409).json({ error: error.message, }) } diff --git a/src/utils/pagination.js b/src/utils/pagination.js new file mode 100644 index 00000000..085d6436 --- /dev/null +++ b/src/utils/pagination.js @@ -0,0 +1,16 @@ +const MissingFieldError = require("../errors/MissingFieldError") + +const getPaginationParams = (req) => { + const page = req.query.page ? Number(req.query.page) : 1 + const per_page = req.query.perPage ? Number(req.query.perPage) : 20 + + if (per_page > 50 || per_page < 10) { + throw new MissingFieldError( + `parameter invalid perPage: ${per_page} not valid. Accepted range is 10 - 50` + ) + } + + return { page, per_page } +} + +module.exports = getPaginationParams