From 5e0fd422e880687419cef25a99744337bfa58263 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Sat, 22 Jun 2024 10:59:27 +0100 Subject: [PATCH 01/20] Get all books functionality --- .env.example | 1 - src/repos/bookRepository.js | 17 +++++++++++++++++ src/routers/books.js | 4 +++- src/server.js | 1 + utils/dbConnection.js | 16 ++++++++++++++++ 5 files changed, 37 insertions(+), 2 deletions(-) delete mode 100644 .env.example create mode 100644 src/repos/bookRepository.js create mode 100644 utils/dbConnection.js diff --git a/.env.example b/.env.example deleted file mode 100644 index 6d05b4ad..00000000 --- a/.env.example +++ /dev/null @@ -1 +0,0 @@ -PGURL="postgres://[user]:[password]@[host]/[dbname]" diff --git a/src/repos/bookRepository.js b/src/repos/bookRepository.js new file mode 100644 index 00000000..8eabeda6 --- /dev/null +++ b/src/repos/bookRepository.js @@ -0,0 +1,17 @@ +const dbConnection = require('../../utils/dbConnection') + +async function getAllBooks() { + const db = await dbConnection.connect() + + try { + const sqlQuery = 'SELECT * FROM BOOKS' + const result = await db.query(sqlQuery) + return result.rows + } catch (e) { + console.log(e) + } finally { + db.release() + } +} + +module.exports = getAllBooks \ No newline at end of file diff --git a/src/routers/books.js b/src/routers/books.js index 1551dd87..e5338be3 100644 --- a/src/routers/books.js +++ b/src/routers/books.js @@ -1,9 +1,11 @@ const express = require('express') const router = express.Router() -const db = require("../../db"); +const getAllBooks = require('../repos/bookRepository') router.get('/', async (req, res) => { + const books = await getAllBooks() + res.status(200).json({ books }) }) module.exports = router diff --git a/src/server.js b/src/server.js index dac55e5d..a99383e1 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"); diff --git a/utils/dbConnection.js b/utils/dbConnection.js new file mode 100644 index 00000000..28dc327d --- /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 \ No newline at end of file From 45e5185006e6d9a5a6056b17ea9f9d64f587dd22 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Sat, 22 Jun 2024 17:49:33 +0100 Subject: [PATCH 02/20] Post books to database with error handling --- src/controllers/booksControllers.js | 33 +++++++++++++++++++++++++++++ src/errors/errors.js | 4 ++++ src/repos/bookRepository.js | 22 +++++++++++++++---- src/routers/books.js | 9 ++++---- src/server.js | 14 +++++++++++- 5 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 src/controllers/booksControllers.js create mode 100644 src/errors/errors.js diff --git a/src/controllers/booksControllers.js b/src/controllers/booksControllers.js new file mode 100644 index 00000000..4c51b56b --- /dev/null +++ b/src/controllers/booksControllers.js @@ -0,0 +1,33 @@ +const { MissingFieldsError } = require("../errors/errors"); +const { fetchBooks, postBook } = require('../repos/bookRepository') + +async function getAllBooks(req, res, next) { + const books = await fetchBooks() + res.status(200).json({ books }) +} + +async function addBook(req, res, next) { + const book = req.body; + const requiredProperties = [ + "title", + "type", + "author", + "topic", + "publication_date", + "pages", + ]; + + try { + const allFieldsExist = requiredProperties.every((property) => book[property]); + if (!allFieldsExist) { + throw new MissingFieldsError('Books require a title, type, author, topic, publication year, and number of pages') + } + postBook(book) + res.status(201).json({ book }) + } catch(e) { + console.log('Error creating book', e) + next(e) + } +} + +module.exports = { addBook, getAllBooks }; diff --git a/src/errors/errors.js b/src/errors/errors.js new file mode 100644 index 00000000..1b3c9bbc --- /dev/null +++ b/src/errors/errors.js @@ -0,0 +1,4 @@ +class MissingFieldsError extends Error { +} + +module.exports = { MissingFieldsError } \ No newline at end of file diff --git a/src/repos/bookRepository.js b/src/repos/bookRepository.js index 8eabeda6..0951ffd5 100644 --- a/src/repos/bookRepository.js +++ b/src/repos/bookRepository.js @@ -1,10 +1,9 @@ const dbConnection = require('../../utils/dbConnection') -async function getAllBooks() { +async function fetchBooks() { const db = await dbConnection.connect() - try { - const sqlQuery = 'SELECT * FROM BOOKS' + const sqlQuery = 'SELECT * FROM books' const result = await db.query(sqlQuery) return result.rows } catch (e) { @@ -14,4 +13,19 @@ async function getAllBooks() { } } -module.exports = getAllBooks \ No newline at end of file +async function postBook(book) { + const db = await dbConnection.connect() + 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, [book.title, book.type, book.author, book.topic, book.publication_date, book.pages]) + return result.rows + } catch (e) { + console.log(e) + } finally { + db.release() + } +} + + +module.exports = { fetchBooks, postBook } \ No newline at end of file diff --git a/src/routers/books.js b/src/routers/books.js index e5338be3..c11b541b 100644 --- a/src/routers/books.js +++ b/src/routers/books.js @@ -1,11 +1,10 @@ const express = require('express') const router = express.Router() -const getAllBooks = require('../repos/bookRepository') -router.get('/', async (req, res) => { +const { addBook, getAllBooks } = require('../controllers/booksControllers') - const books = await getAllBooks() - res.status(200).json({ books }) -}) +router.get('/', getAllBooks) + +router.post('/', addBook) module.exports = router diff --git a/src/server.js b/src/server.js index a99383e1..8a19298e 100644 --- a/src/server.js +++ b/src/server.js @@ -11,7 +11,19 @@ app.use(express.json()); //TODO: Implement books and pets APIs using Express Modular Routers const booksRouter = require('./routers/books.js') - app.use('/books', booksRouter) +//Error handling +const { MissingFieldsError } = require('./errors/errors.js') + +app.use((error, req, res, next) => { + if (error instanceof MissingFieldsError) { + return res.status(400).json({error: error.message}) + } + + res.status(500).json({ + message: 'Something went wrong' + }) +}) + module.exports = app From d6052eb0edf7fdedcb1eafba9de600c48f57a176 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Sat, 22 Jun 2024 18:22:05 +0100 Subject: [PATCH 03/20] Get book by ID --- src/controllers/booksControllers.js | 23 +++++++++++++++++++---- src/{repos => dal}/bookRepository.js | 17 ++++++++++++++++- src/errors/errors.js | 6 +++++- src/routers/books.js | 4 +++- src/server.js | 5 ++++- 5 files changed, 47 insertions(+), 8 deletions(-) rename src/{repos => dal}/bookRepository.js (67%) diff --git a/src/controllers/booksControllers.js b/src/controllers/booksControllers.js index 4c51b56b..951c656a 100644 --- a/src/controllers/booksControllers.js +++ b/src/controllers/booksControllers.js @@ -1,11 +1,12 @@ -const { MissingFieldsError } = require("../errors/errors"); -const { fetchBooks, postBook } = require('../repos/bookRepository') +const { MissingFieldsError, NoDataError } = require("../errors/errors"); +const { fetchBooks, postBook, fetchBookById } = require('../dal/bookRepository') async function getAllBooks(req, res, next) { const books = await fetchBooks() res.status(200).json({ books }) } + async function addBook(req, res, next) { const book = req.body; const requiredProperties = [ @@ -25,9 +26,23 @@ async function addBook(req, res, next) { postBook(book) res.status(201).json({ book }) } catch(e) { - console.log('Error creating book', e) + console.log(e) next(e) } } -module.exports = { addBook, getAllBooks }; +async function getBookById(req, res, next) { + targetBookId = Number(req.params.id) + try { + const book = await fetchBookById(targetBookId) + if (book.length === 0) { + throw new NoDataError('A book with the provided ID does not exist') + } + res.status(200).json({ book }) + } catch(e) { + console.log(e) + next(e) + } +} + +module.exports = { addBook, getAllBooks, getBookById }; diff --git a/src/repos/bookRepository.js b/src/dal/bookRepository.js similarity index 67% rename from src/repos/bookRepository.js rename to src/dal/bookRepository.js index 0951ffd5..57fce251 100644 --- a/src/repos/bookRepository.js +++ b/src/dal/bookRepository.js @@ -28,4 +28,19 @@ async function postBook(book) { } -module.exports = { fetchBooks, postBook } \ No newline at end of file +async function fetchBookById(id) { + const db = await dbConnection.connect() + try { + const sqlQuery = 'SELECT * FROM books WHERE id = $1' + const result = await db.query(sqlQuery, [id]) + console.log(result) + return result.rows + } catch (e) { + console.log(e) + } finally { + db.release() + } +} + + +module.exports = { fetchBooks, postBook, fetchBookById } \ No newline at end of file diff --git a/src/errors/errors.js b/src/errors/errors.js index 1b3c9bbc..d3be92e8 100644 --- a/src/errors/errors.js +++ b/src/errors/errors.js @@ -1,4 +1,8 @@ class MissingFieldsError extends Error { } -module.exports = { MissingFieldsError } \ No newline at end of file +class NoDataError extends Error { + +} + +module.exports = { MissingFieldsError, NoDataError } \ No newline at end of file diff --git a/src/routers/books.js b/src/routers/books.js index c11b541b..12edff20 100644 --- a/src/routers/books.js +++ b/src/routers/books.js @@ -1,10 +1,12 @@ const express = require('express') const router = express.Router() -const { addBook, getAllBooks } = require('../controllers/booksControllers') +const { addBook, getAllBooks, getBookById } = require('../controllers/booksControllers') router.get('/', getAllBooks) router.post('/', addBook) +router.get('/:id', getBookById) + module.exports = router diff --git a/src/server.js b/src/server.js index 8a19298e..3d991d47 100644 --- a/src/server.js +++ b/src/server.js @@ -14,12 +14,15 @@ const booksRouter = require('./routers/books.js') app.use('/books', booksRouter) //Error handling -const { MissingFieldsError } = require('./errors/errors.js') +const { MissingFieldsError, NoDataError } = require('./errors/errors.js') app.use((error, req, res, next) => { if (error instanceof MissingFieldsError) { return res.status(400).json({error: error.message}) } + if (error instanceof NoDataError) { + return res.status(404).json({error: error.message}) + } res.status(500).json({ message: 'Something went wrong' From 5513a2f99db2825c2f02eadfdaafffbe0a4b33f8 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Sun, 23 Jun 2024 12:28:00 +0100 Subject: [PATCH 04/20] Update book by ID --- src/controllers/booksControllers.js | 28 +++++++++++++++++++++++----- src/dal/bookRepository.js | 21 ++++++++++++++++----- src/routers/books.js | 10 ++++++---- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/controllers/booksControllers.js b/src/controllers/booksControllers.js index 951c656a..6c46417b 100644 --- a/src/controllers/booksControllers.js +++ b/src/controllers/booksControllers.js @@ -1,13 +1,13 @@ const { MissingFieldsError, NoDataError } = require("../errors/errors"); -const { fetchBooks, postBook, fetchBookById } = require('../dal/bookRepository') +const { fetchBooks, postBook, fetchBookById, updateBookById } = require('../dal/bookRepository') -async function getAllBooks(req, res, next) { +async function getAllBooksController(req, res, next) { const books = await fetchBooks() res.status(200).json({ books }) } -async function addBook(req, res, next) { +async function addBookController(req, res, next) { const book = req.body; const requiredProperties = [ "title", @@ -31,7 +31,7 @@ async function addBook(req, res, next) { } } -async function getBookById(req, res, next) { +async function getBookByIdController(req, res, next) { targetBookId = Number(req.params.id) try { const book = await fetchBookById(targetBookId) @@ -45,4 +45,22 @@ async function getBookById(req, res, next) { } } -module.exports = { addBook, getAllBooks, getBookById }; +async function putBookByIdController(req, res, next) { + const targetBookId = Number(req.params.id) + const newParams = req.body + + try { + const book = await fetchBookById(targetBookId) + if (book.length === 0) { + throw new NoDataError('A book with the provided ID does not exist') + } + const updatedBook = await updateBookById(targetBookId, newParams) + res.status(201).json( {book: updatedBook} ) + } catch(e) { + console.log(e) + next(e) + } + +} + +module.exports = { addBookController, getAllBooksController, getBookByIdController, putBookByIdController }; diff --git a/src/dal/bookRepository.js b/src/dal/bookRepository.js index 57fce251..55f3f812 100644 --- a/src/dal/bookRepository.js +++ b/src/dal/bookRepository.js @@ -3,7 +3,7 @@ const dbConnection = require('../../utils/dbConnection') async function fetchBooks() { const db = await dbConnection.connect() try { - const sqlQuery = 'SELECT * FROM books' + const sqlQuery = 'SELECT * FROM books;' const result = await db.query(sqlQuery) return result.rows } catch (e) { @@ -27,13 +27,24 @@ async function postBook(book) { } } - async function fetchBookById(id) { const db = await dbConnection.connect() try { - const sqlQuery = 'SELECT * FROM books WHERE id = $1' + const sqlQuery = 'SELECT * FROM books WHERE id = $1;' const result = await db.query(sqlQuery, [id]) - console.log(result) + return result.rows + } catch (e) { + console.log(e) + } finally { + db.release() + } +} + +async function updateBookById(id, newParams) { + const db = await dbConnection.connect() + try { + const sqlQuery = 'UPDATE books SET title = $2, type = $3, author = $4, topic = $5, publication_date = $6, pages = $7 WHERE id = $1 RETURNING *;' + const result = await db.query(sqlQuery, [id, newParams.title, newParams.type, newParams.author, newParams.topic, newParams.publication_date, newParams.pages]) return result.rows } catch (e) { console.log(e) @@ -43,4 +54,4 @@ async function fetchBookById(id) { } -module.exports = { fetchBooks, postBook, fetchBookById } \ No newline at end of file +module.exports = { fetchBooks, postBook, fetchBookById, updateBookById } \ No newline at end of file diff --git a/src/routers/books.js b/src/routers/books.js index 12edff20..f6415bc9 100644 --- a/src/routers/books.js +++ b/src/routers/books.js @@ -1,12 +1,14 @@ const express = require('express') const router = express.Router() -const { addBook, getAllBooks, getBookById } = require('../controllers/booksControllers') +const { addBookController, getAllBooksController, getBookByIdController, putBookByIdController } = require('../controllers/booksControllers') -router.get('/', getAllBooks) +router.get('/', getAllBooksController) -router.post('/', addBook) +router.post('/', addBookController) -router.get('/:id', getBookById) +router.get('/:id', getBookByIdController) + +router.put('/:id', putBookByIdController) module.exports = router From e2b168befe31dc96ed3bdd7a2f636fff415ad50f Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Sun, 23 Jun 2024 12:40:00 +0100 Subject: [PATCH 05/20] Delete book by ID --- src/controllers/booksControllers.js | 100 ++++++++++++++++++---------- src/dal/bookRepository.js | 15 ++++- src/routers/books.js | 4 +- 3 files changed, 82 insertions(+), 37 deletions(-) diff --git a/src/controllers/booksControllers.js b/src/controllers/booksControllers.js index 6c46417b..6f930678 100644 --- a/src/controllers/booksControllers.js +++ b/src/controllers/booksControllers.js @@ -1,12 +1,17 @@ const { MissingFieldsError, NoDataError } = require("../errors/errors"); -const { fetchBooks, postBook, fetchBookById, updateBookById } = require('../dal/bookRepository') +const { + fetchBooks, + postBook, + fetchBookById, + updateBookById, + deleteBookById +} = require("../dal/bookRepository"); async function getAllBooksController(req, res, next) { - const books = await fetchBooks() - res.status(200).json({ books }) + const books = await fetchBooks(); + res.status(200).json({ books }); } - async function addBookController(req, res, next) { const book = req.body; const requiredProperties = [ @@ -17,50 +22,75 @@ async function addBookController(req, res, next) { "publication_date", "pages", ]; - + try { - const allFieldsExist = requiredProperties.every((property) => book[property]); + const allFieldsExist = requiredProperties.every( + (property) => book[property] + ); if (!allFieldsExist) { - throw new MissingFieldsError('Books require a title, type, author, topic, publication year, and number of pages') + throw new MissingFieldsError( + "Books require a title, type, author, topic, publication year, and number of pages" + ); } - postBook(book) - res.status(201).json({ book }) - } catch(e) { - console.log(e) - next(e) + postBook(book); + res.status(201).json({ book }); + } catch (e) { + console.log(e); + next(e); } } async function getBookByIdController(req, res, next) { - targetBookId = Number(req.params.id) - try { - const book = await fetchBookById(targetBookId) - if (book.length === 0) { - throw new NoDataError('A book with the provided ID does not exist') - } - res.status(200).json({ book }) - } catch(e) { - console.log(e) - next(e) + targetBookId = Number(req.params.id); + try { + const book = await fetchBookById(targetBookId); + if (book.length === 0) { + throw new NoDataError("A book with the provided ID does not exist"); } + res.status(200).json({ book }); + } catch (e) { + console.log(e); + next(e); + } } async function putBookByIdController(req, res, next) { - const targetBookId = Number(req.params.id) - const newParams = req.body + const targetBookId = Number(req.params.id); + const newParams = req.body; - try { - const book = await fetchBookById(targetBookId) - if (book.length === 0) { - throw new NoDataError('A book with the provided ID does not exist') - } - const updatedBook = await updateBookById(targetBookId, newParams) - res.status(201).json( {book: updatedBook} ) - } catch(e) { - console.log(e) - next(e) + try { + const book = await fetchBookById(targetBookId); + if (book.length === 0) { + throw new NoDataError("A book with the provided ID does not exist"); } + const updatedBook = await updateBookById(targetBookId, newParams); + res.status(201).json({ book: updatedBook }); + } catch (e) { + console.log(e); + next(e); + } +} + +async function deleteBookByIdController(req, res, next) { + const targetBookId = Number(req.params.id); + try { + const book = await fetchBookById(targetBookId); + if (book.length === 0) { + throw new NoDataError("A book with the provided ID does not exist"); + } + const deletedBook = await deleteBookById(targetBookId) + res.status(201).json({ book: deletedBook}) + } catch (e) { + console.log(e); + next(e); + } } -module.exports = { addBookController, getAllBooksController, getBookByIdController, putBookByIdController }; +module.exports = { + addBookController, + getAllBooksController, + getBookByIdController, + putBookByIdController, + deleteBookByIdController +}; diff --git a/src/dal/bookRepository.js b/src/dal/bookRepository.js index 55f3f812..25bd4634 100644 --- a/src/dal/bookRepository.js +++ b/src/dal/bookRepository.js @@ -53,5 +53,18 @@ async function updateBookById(id, newParams) { } } +async function deleteBookById(id) { + const db = await dbConnection.connect() + try { + const sqlQuery = 'DELETE FROM books WHERE id = $1 RETURNING *' + const result = await db.query(sqlQuery, [id]) + return result.rows + } catch (e) { + console.log(e) + } finally { + db.release() + } +} + -module.exports = { fetchBooks, postBook, fetchBookById, updateBookById } \ No newline at end of file +module.exports = { fetchBooks, postBook, fetchBookById, updateBookById, deleteBookById } \ No newline at end of file diff --git a/src/routers/books.js b/src/routers/books.js index f6415bc9..19c56a39 100644 --- a/src/routers/books.js +++ b/src/routers/books.js @@ -1,7 +1,7 @@ const express = require('express') const router = express.Router() -const { addBookController, getAllBooksController, getBookByIdController, putBookByIdController } = require('../controllers/booksControllers') +const { addBookController, getAllBooksController, getBookByIdController, putBookByIdController, deleteBookByIdController } = require('../controllers/booksControllers') router.get('/', getAllBooksController) @@ -11,4 +11,6 @@ router.get('/:id', getBookByIdController) router.put('/:id', putBookByIdController) +router.delete('/:id', deleteBookByIdController) + module.exports = router From e16668e001d433712ffcaceee2f7a5710f715c48 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Sun, 23 Jun 2024 13:19:28 +0100 Subject: [PATCH 06/20] Get books by type and topic --- src/controllers/booksControllers.js | 4 ++-- src/dal/bookRepository.js | 26 +++++++++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/controllers/booksControllers.js b/src/controllers/booksControllers.js index 6f930678..c3b301e7 100644 --- a/src/controllers/booksControllers.js +++ b/src/controllers/booksControllers.js @@ -7,8 +7,8 @@ const { deleteBookById } = require("../dal/bookRepository"); -async function getAllBooksController(req, res, next) { - const books = await fetchBooks(); +async function getAllBooksController(req, res) { + const books = await fetchBooks(req.query); res.status(200).json({ books }); } diff --git a/src/dal/bookRepository.js b/src/dal/bookRepository.js index 25bd4634..a8b2dd99 100644 --- a/src/dal/bookRepository.js +++ b/src/dal/bookRepository.js @@ -1,10 +1,26 @@ const dbConnection = require('../../utils/dbConnection') -async function fetchBooks() { +async function fetchBooks(reqQuery) { const db = await dbConnection.connect() + const params = [] + let sqlQuery = "SELECT * FROM books" + + if (reqQuery.type) { + params.push(reqQuery.type) + sqlQuery += ` WHERE type = $${params.length}` + } + + if (reqQuery.topic) { + params.push(reqQuery.topic) + if (params.length ===1) { + sqlQuery += ` WHERE topic = $${params.length}` + } else { + sqlQuery += ` AND topic = $${params.length}` + } + } + try { - const sqlQuery = 'SELECT * FROM books;' - const result = await db.query(sqlQuery) + const result = await db.query(sqlQuery, params) return result.rows } catch (e) { console.log(e) @@ -17,7 +33,7 @@ async function postBook(book) { const db = await dbConnection.connect() try { const sqlQuery = `INSERT INTO books -(title, type, author, topic, publication_date, pages) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *` +(title, type, author, topic, publication_date, pages) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *;` const result = await db.query(sqlQuery, [book.title, book.type, book.author, book.topic, book.publication_date, book.pages]) return result.rows } catch (e) { @@ -56,7 +72,7 @@ async function updateBookById(id, newParams) { async function deleteBookById(id) { const db = await dbConnection.connect() try { - const sqlQuery = 'DELETE FROM books WHERE id = $1 RETURNING *' + const sqlQuery = 'DELETE FROM books WHERE id = $1 RETURNING *;' const result = await db.query(sqlQuery, [id]) return result.rows } catch (e) { From 59e3aac61df9827690f5d7c619818b29399b435f Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 24 Jun 2024 11:38:09 +0100 Subject: [PATCH 07/20] Basic book tests all passing --- db/index.js | 38 ++++++++---------- package-lock.json | 9 +++++ package.json | 4 +- src/controllers/booksControllers.js | 60 ++++++++++------------------- src/dal/bookRepository.js | 36 ++++++----------- src/server.js | 1 + 6 files changed, 61 insertions(+), 87 deletions(-) diff --git a/db/index.js b/db/index.js index af723442..2577751a 100644 --- a/db/index.js +++ b/db/index.js @@ -1,25 +1,19 @@ -// Load our .env file -require('dotenv').config() -// Require Client obj from the postgres node module -const { Client } = require("pg"); +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 -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/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..b47a87ba 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,15 @@ "scripts": { "start": "npx nodemon src/index.js", "test": "npx jest -i test/api/routes --forceExit", - "test-extensions": "npx jest -i test/api/extensions --forceExit" + "test-extensions": "npx jest -i test/api/extensions --forceExit", + "test-books": "npx jest -i test/api/routes/books.spec.js --forceExit" }, "dependencies": { "body-parser": "^1.20.2", "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 c3b301e7..4614c1da 100644 --- a/src/controllers/booksControllers.js +++ b/src/controllers/booksControllers.js @@ -4,7 +4,7 @@ const { postBook, fetchBookById, updateBookById, - deleteBookById + deleteBookById, } = require("../dal/bookRepository"); async function getAllBooksController(req, res) { @@ -12,8 +12,8 @@ async function getAllBooksController(req, res) { res.status(200).json({ books }); } -async function addBookController(req, res, next) { - const book = req.body; +async function addBookController(req, res) { + const newBook = req.body; const requiredProperties = [ "title", "type", @@ -22,69 +22,51 @@ async function addBookController(req, res, next) { "publication_date", "pages", ]; - - try { - const allFieldsExist = requiredProperties.every( - (property) => book[property] + const allFieldsExist = requiredProperties.every( + (property) => newBook[property] + ); + if (!allFieldsExist) { + throw new MissingFieldsError( + "Books require a title, type, author, topic, publication year, and number of pages" ); - if (!allFieldsExist) { - throw new MissingFieldsError( - "Books require a title, type, author, topic, publication year, and number of pages" - ); - } - postBook(book); - res.status(201).json({ book }); - } catch (e) { - console.log(e); - next(e); } + const book = await postBook(newBook); + res.status(201).json({ book }); } async function getBookByIdController(req, res, next) { targetBookId = Number(req.params.id); - try { - const book = await fetchBookById(targetBookId); - if (book.length === 0) { - throw new NoDataError("A book with the provided ID does not exist"); - } - res.status(200).json({ book }); - } catch (e) { - console.log(e); - next(e); + + const book = await fetchBookById(targetBookId); + if (book.length === 0) { + throw new NoDataError("A book with the provided ID does not exist"); } + res.status(200).json({ book }); } async function putBookByIdController(req, res, next) { const targetBookId = Number(req.params.id); const newParams = req.body; - try { const book = await fetchBookById(targetBookId); if (book.length === 0) { throw new NoDataError("A book with the provided ID does not exist"); } + const updatedBook = await updateBookById(targetBookId, newParams); res.status(201).json({ book: updatedBook }); - } catch (e) { - console.log(e); - next(e); - } } async function deleteBookByIdController(req, res, next) { const targetBookId = Number(req.params.id); - try { const book = await fetchBookById(targetBookId); if (book.length === 0) { throw new NoDataError("A book with the provided ID does not exist"); } - const deletedBook = await deleteBookById(targetBookId) - res.status(201).json({ book: deletedBook}) - } catch (e) { - console.log(e); - next(e); - } + const deletedBook = await deleteBookById(targetBookId); + res.status(201).json({ book: deletedBook }); + } module.exports = { @@ -92,5 +74,5 @@ module.exports = { getAllBooksController, getBookByIdController, putBookByIdController, - deleteBookByIdController + deleteBookByIdController, }; diff --git a/src/dal/bookRepository.js b/src/dal/bookRepository.js index a8b2dd99..0bcdd510 100644 --- a/src/dal/bookRepository.js +++ b/src/dal/bookRepository.js @@ -1,7 +1,6 @@ -const dbConnection = require('../../utils/dbConnection') +const db = require('../../db/index.js') async function fetchBooks(reqQuery) { - const db = await dbConnection.connect() const params = [] let sqlQuery = "SELECT * FROM books" @@ -24,62 +23,49 @@ async function fetchBooks(reqQuery) { return result.rows } catch (e) { console.log(e) - } finally { - db.release() - } + } } async function postBook(book) { - const db = await dbConnection.connect() 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, [book.title, book.type, book.author, book.topic, book.publication_date, book.pages]) - return result.rows + return result.rows[0] } catch (e) { console.log(e) - } finally { - db.release() - } + } } async function fetchBookById(id) { - const db = await dbConnection.connect() + try { const sqlQuery = 'SELECT * FROM books WHERE id = $1;' const result = await db.query(sqlQuery, [id]) - return result.rows + return result.rows[0] } catch (e) { console.log(e) - } finally { - db.release() - } + } } async function updateBookById(id, newParams) { - const db = await dbConnection.connect() try { const sqlQuery = 'UPDATE books SET title = $2, type = $3, author = $4, topic = $5, publication_date = $6, pages = $7 WHERE id = $1 RETURNING *;' const result = await db.query(sqlQuery, [id, newParams.title, newParams.type, newParams.author, newParams.topic, newParams.publication_date, newParams.pages]) - return result.rows + return result.rows[0] } catch (e) { console.log(e) - } finally { - db.release() - } + } } async function deleteBookById(id) { - const db = await dbConnection.connect() try { const sqlQuery = 'DELETE FROM books WHERE id = $1 RETURNING *;' const result = await db.query(sqlQuery, [id]) - return result.rows + return result.rows[0] } catch (e) { console.log(e) - } finally { - db.release() - } + } } diff --git a/src/server.js b/src/server.js index 3d991d47..e038dbc8 100644 --- a/src/server.js +++ b/src/server.js @@ -1,4 +1,5 @@ require('dotenv').config() +require('express-async-errors') const express = require("express"); const morgan = require("morgan"); const cors = require("cors"); From cb125372ab7645b94d23be50c61d8826c1eac820 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 24 Jun 2024 11:49:42 +0100 Subject: [PATCH 08/20] Getting non-existant book by id returns expected error --- package.json | 3 ++- src/controllers/booksControllers.js | 3 +-- src/dal/bookRepository.js | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index b47a87ba..c32a2a11 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "start": "npx nodemon src/index.js", "test": "npx jest -i test/api/routes --forceExit", "test-extensions": "npx jest -i test/api/extensions --forceExit", - "test-books": "npx jest -i test/api/routes/books.spec.js --forceExit" + "test-books": "npx jest -i test/api/routes/books.spec.js --forceExit", + "test-books-extensions": "npx jest -i test/api/extensions/books.spec.js --forceExit" }, "dependencies": { "body-parser": "^1.20.2", diff --git a/src/controllers/booksControllers.js b/src/controllers/booksControllers.js index 4614c1da..9105ee02 100644 --- a/src/controllers/booksControllers.js +++ b/src/controllers/booksControllers.js @@ -39,7 +39,7 @@ async function getBookByIdController(req, res, next) { const book = await fetchBookById(targetBookId); if (book.length === 0) { - throw new NoDataError("A book with the provided ID does not exist"); + throw new NoDataError(`no book with id: ${targetBookId}`); } res.status(200).json({ book }); } @@ -52,7 +52,6 @@ async function putBookByIdController(req, res, next) { if (book.length === 0) { throw new NoDataError("A book with the provided ID does not exist"); } - const updatedBook = await updateBookById(targetBookId, newParams); res.status(201).json({ book: updatedBook }); } diff --git a/src/dal/bookRepository.js b/src/dal/bookRepository.js index 0bcdd510..9064b158 100644 --- a/src/dal/bookRepository.js +++ b/src/dal/bookRepository.js @@ -38,11 +38,10 @@ async function postBook(book) { } async function fetchBookById(id) { - try { const sqlQuery = 'SELECT * FROM books WHERE id = $1;' const result = await db.query(sqlQuery, [id]) - return result.rows[0] + return result.rows } catch (e) { console.log(e) } From 77969f58ad4b472148d66b8abafe93a67a3c1a85 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 24 Jun 2024 12:04:19 +0100 Subject: [PATCH 09/20] Get book by author --- src/controllers/booksControllers.js | 4 ++-- src/dal/bookRepository.js | 31 ++++++++++++++++++----------- src/routers/books.js | 4 ++-- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/controllers/booksControllers.js b/src/controllers/booksControllers.js index 9105ee02..7474f9e5 100644 --- a/src/controllers/booksControllers.js +++ b/src/controllers/booksControllers.js @@ -7,7 +7,7 @@ const { deleteBookById, } = require("../dal/bookRepository"); -async function getAllBooksController(req, res) { +async function getBooksController(req, res) { const books = await fetchBooks(req.query); res.status(200).json({ books }); } @@ -70,7 +70,7 @@ async function deleteBookByIdController(req, res, next) { module.exports = { addBookController, - getAllBooksController, + getBooksController, getBookByIdController, putBookByIdController, deleteBookByIdController, diff --git a/src/dal/bookRepository.js b/src/dal/bookRepository.js index 9064b158..6945c25e 100644 --- a/src/dal/bookRepository.js +++ b/src/dal/bookRepository.js @@ -1,25 +1,32 @@ const db = require('../../db/index.js') async function fetchBooks(reqQuery) { - const params = [] let sqlQuery = "SELECT * FROM books" + const params = [] + // if (reqQuery.type) { + // params.push(reqQuery.type) + // sqlQuery += ` WHERE type = $${params.length}` + // } - if (reqQuery.type) { - params.push(reqQuery.type) - sqlQuery += ` WHERE type = $${params.length}` - } + // if (reqQuery.topic) { + // params.push(reqQuery.topic) + // if (params.length ===1) { + // sqlQuery += ` WHERE topic = $${params.length}` + // } else { + // sqlQuery += ` AND topic = $${params.length}` + // } + // } - if (reqQuery.topic) { - params.push(reqQuery.topic) - if (params.length ===1) { - sqlQuery += ` WHERE topic = $${params.length}` - } else { - sqlQuery += ` AND topic = $${params.length}` - } + if(reqQuery.author) { + params.push(reqQuery.author) + sqlQuery += ` WHERE author = $${params.length}` } try { const result = await db.query(sqlQuery, params) + if (result.rows.length === 1) { + return result.rows[0] + } return result.rows } catch (e) { console.log(e) diff --git a/src/routers/books.js b/src/routers/books.js index 19c56a39..23971ba9 100644 --- a/src/routers/books.js +++ b/src/routers/books.js @@ -1,9 +1,9 @@ const express = require('express') const router = express.Router() -const { addBookController, getAllBooksController, getBookByIdController, putBookByIdController, deleteBookByIdController } = require('../controllers/booksControllers') +const { addBookController, getBooksController, getBookByIdController, putBookByIdController, deleteBookByIdController } = require('../controllers/booksControllers') -router.get('/', getAllBooksController) +router.get('/', getBooksController) router.post('/', addBookController) From e23122ebaabd21953e1c00a5db7bb5e93af63dab Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 24 Jun 2024 14:39:30 +0100 Subject: [PATCH 10/20] Pagination for books --- src/controllers/booksControllers.js | 22 +++++++++--- src/dal/bookRepository.js | 52 ++++++++++++++++------------- 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/src/controllers/booksControllers.js b/src/controllers/booksControllers.js index 7474f9e5..90f48b26 100644 --- a/src/controllers/booksControllers.js +++ b/src/controllers/booksControllers.js @@ -1,15 +1,27 @@ const { MissingFieldsError, NoDataError } = require("../errors/errors"); const { - fetchBooks, + fetchAllBooks, postBook, fetchBookById, updateBookById, deleteBookById, + fetchBookByQuery } = require("../dal/bookRepository"); async function getBooksController(req, res) { - const books = await fetchBooks(req.query); - res.status(200).json({ books }); + let books; + const query = req.query + + if (query) { + books = await fetchBookByQuery(query) + } else { + books = await fetchAllBooks(); + } + + res.status(200).json({ books, + page: Number(query.page), + per_page: Number(query.perPage) + }); } async function addBookController(req, res) { @@ -34,11 +46,11 @@ async function addBookController(req, res) { res.status(201).json({ book }); } -async function getBookByIdController(req, res, next) { +async function getBookByIdController(req, res) { targetBookId = Number(req.params.id); const book = await fetchBookById(targetBookId); - if (book.length === 0) { + if (!book) { throw new NoDataError(`no book with id: ${targetBookId}`); } res.status(200).json({ book }); diff --git a/src/dal/bookRepository.js b/src/dal/bookRepository.js index 6945c25e..4dfad6b5 100644 --- a/src/dal/bookRepository.js +++ b/src/dal/bookRepository.js @@ -1,36 +1,40 @@ const db = require('../../db/index.js') -async function fetchBooks(reqQuery) { +async function fetchAllBooks() { + try { + const result = await db.query("SELECT * FROM books") + return result.rows + } catch (e) { + console.log(e) + } +} + +async function fetchBookByQuery(query) { let sqlQuery = "SELECT * FROM books" const params = [] - // if (reqQuery.type) { - // params.push(reqQuery.type) - // sqlQuery += ` WHERE type = $${params.length}` - // } - - // if (reqQuery.topic) { - // params.push(reqQuery.topic) - // if (params.length ===1) { - // sqlQuery += ` WHERE topic = $${params.length}` - // } else { - // sqlQuery += ` AND topic = $${params.length}` - // } - // } - if(reqQuery.author) { - params.push(reqQuery.author) + if(query.author) { + params.push(query.author) sqlQuery += ` WHERE author = $${params.length}` } + if(query.perPage) { + params.push(query.perPage) + sqlQuery += ` LIMIT $${params.length}` + } + + if(query.page) { + params.push((query.page - 1) * query.perPage) + sqlQuery += ` OFFSET $${params.length}` + } + + try { - const result = await db.query(sqlQuery, params) - if (result.rows.length === 1) { - return result.rows[0] - } - return result.rows + const result = await db.query(sqlQuery, params) + return result.rows } catch (e) { console.log(e) - } + } } async function postBook(book) { @@ -48,7 +52,7 @@ async function fetchBookById(id) { try { const sqlQuery = 'SELECT * FROM books WHERE id = $1;' const result = await db.query(sqlQuery, [id]) - return result.rows + return result.rows[0] } catch (e) { console.log(e) } @@ -75,4 +79,4 @@ async function deleteBookById(id) { } -module.exports = { fetchBooks, postBook, fetchBookById, updateBookById, deleteBookById } \ No newline at end of file +module.exports = { fetchAllBooks, postBook, fetchBookById, updateBookById, deleteBookById, fetchBookByQuery } \ No newline at end of file From 2bbeb497680a4b15668524e4c8cdc754c4854dc4 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 24 Jun 2024 15:05:07 +0100 Subject: [PATCH 11/20] All pagination tests pass --- src/controllers/booksControllers.js | 5 +- src/dal/bookRepository.js | 140 ++++++++++++++++------------ src/errors/errors.js | 6 +- src/server.js | 6 +- 4 files changed, 94 insertions(+), 63 deletions(-) diff --git a/src/controllers/booksControllers.js b/src/controllers/booksControllers.js index 90f48b26..f3cbb0f7 100644 --- a/src/controllers/booksControllers.js +++ b/src/controllers/booksControllers.js @@ -1,4 +1,4 @@ -const { MissingFieldsError, NoDataError } = require("../errors/errors"); +const { MissingFieldsError, NoDataError, InvalidParameterError } = require("../errors/errors"); const { fetchAllBooks, postBook, @@ -13,6 +13,9 @@ async function getBooksController(req, res) { const query = req.query if (query) { + if (query.perPage < 10 || query.perPage > 50) { + throw new InvalidParameterError(`parameter invalid perPage: ${query.perPage} not valid. Accepted range is 10 - 50`) + } books = await fetchBookByQuery(query) } else { books = await fetchAllBooks(); diff --git a/src/dal/bookRepository.js b/src/dal/bookRepository.js index 4dfad6b5..4a8400cf 100644 --- a/src/dal/bookRepository.js +++ b/src/dal/bookRepository.js @@ -1,82 +1,102 @@ -const db = require('../../db/index.js') +const db = require("../../db/index.js"); async function fetchAllBooks() { - try { - const result = await db.query("SELECT * FROM books") - return result.rows - } catch (e) { - console.log(e) - } + try { + const result = await db.query("SELECT * FROM books"); + return result.rows; + } catch (e) { + console.log(e); + } } async function fetchBookByQuery(query) { - let sqlQuery = "SELECT * FROM books" - const params = [] + let sqlQuery = "SELECT * FROM books"; + const params = []; + const perPage = query.perPage || 20; - if(query.author) { - params.push(query.author) - sqlQuery += ` WHERE author = $${params.length}` - } + if (query.author) { + params.push(query.author); + sqlQuery += ` WHERE author = $${params.length}`; + } - if(query.perPage) { - params.push(query.perPage) - sqlQuery += ` LIMIT $${params.length}` - } + params.push(perPage); + sqlQuery += ` LIMIT $${params.length}`; - if(query.page) { - params.push((query.page - 1) * query.perPage) - sqlQuery += ` OFFSET $${params.length}` - } - - - try { - const result = await db.query(sqlQuery, params) - return result.rows - } catch (e) { - console.log(e) - } + if (query.page) { + params.push((query.page - 1) * query.perPage); + sqlQuery += ` OFFSET $${params.length}`; + } + + try { + const result = await db.query(sqlQuery, params); + return result.rows; + } catch (e) { + console.log(e); + } } async function postBook(book) { - 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, [book.title, book.type, book.author, book.topic, book.publication_date, book.pages]) - return result.rows[0] - } catch (e) { - console.log(e) - } + 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, [ + book.title, + book.type, + book.author, + book.topic, + book.publication_date, + book.pages, + ]); + return result.rows[0]; + } catch (e) { + console.log(e); + } } async function fetchBookById(id) { - try { - const sqlQuery = 'SELECT * FROM books WHERE id = $1;' - const result = await db.query(sqlQuery, [id]) - return result.rows[0] - } catch (e) { - console.log(e) - } + try { + const sqlQuery = "SELECT * FROM books WHERE id = $1;"; + const result = await db.query(sqlQuery, [id]); + return result.rows[0]; + } catch (e) { + console.log(e); + } } async function updateBookById(id, newParams) { - try { - const sqlQuery = 'UPDATE books SET title = $2, type = $3, author = $4, topic = $5, publication_date = $6, pages = $7 WHERE id = $1 RETURNING *;' - const result = await db.query(sqlQuery, [id, newParams.title, newParams.type, newParams.author, newParams.topic, newParams.publication_date, newParams.pages]) - return result.rows[0] - } catch (e) { - console.log(e) - } + try { + const sqlQuery = + "UPDATE books SET title = $2, type = $3, author = $4, topic = $5, publication_date = $6, pages = $7 WHERE id = $1 RETURNING *;"; + const result = await db.query(sqlQuery, [ + id, + newParams.title, + newParams.type, + newParams.author, + newParams.topic, + newParams.publication_date, + newParams.pages, + ]); + return result.rows[0]; + } catch (e) { + console.log(e); + } } async function deleteBookById(id) { - try { - const sqlQuery = 'DELETE FROM books WHERE id = $1 RETURNING *;' - const result = await db.query(sqlQuery, [id]) - return result.rows[0] - } catch (e) { - console.log(e) - } + try { + const sqlQuery = "DELETE FROM books WHERE id = $1 RETURNING *;"; + const result = await db.query(sqlQuery, [id]); + return result.rows[0]; + } catch (e) { + console.log(e); + } } - -module.exports = { fetchAllBooks, postBook, fetchBookById, updateBookById, deleteBookById, fetchBookByQuery } \ No newline at end of file +module.exports = { + fetchAllBooks, + postBook, + fetchBookById, + updateBookById, + deleteBookById, + fetchBookByQuery, +}; diff --git a/src/errors/errors.js b/src/errors/errors.js index d3be92e8..6703a180 100644 --- a/src/errors/errors.js +++ b/src/errors/errors.js @@ -5,4 +5,8 @@ class NoDataError extends Error { } -module.exports = { MissingFieldsError, NoDataError } \ No newline at end of file +class InvalidParameterError extends Error { + +} + +module.exports = { MissingFieldsError, NoDataError, InvalidParameterError } \ No newline at end of file diff --git a/src/server.js b/src/server.js index e038dbc8..ddff83e8 100644 --- a/src/server.js +++ b/src/server.js @@ -15,7 +15,7 @@ const booksRouter = require('./routers/books.js') app.use('/books', booksRouter) //Error handling -const { MissingFieldsError, NoDataError } = require('./errors/errors.js') +const { MissingFieldsError, NoDataError, InvalidParameterError } = require('./errors/errors.js') app.use((error, req, res, next) => { if (error instanceof MissingFieldsError) { @@ -25,6 +25,10 @@ app.use((error, req, res, next) => { return res.status(404).json({error: error.message}) } + if (error instanceof InvalidParameterError) { + return res.status(400).json({error: error.message}) + } + res.status(500).json({ message: 'Something went wrong' }) From 69906e09962d350da80435813065505b049282af Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 24 Jun 2024 15:25:55 +0100 Subject: [PATCH 12/20] All book functions and tests --- src/controllers/booksControllers.js | 59 ++++++++++++++++++----------- src/dal/bookRepository.js | 1 + src/errors/errors.js | 6 ++- src/server.js | 6 ++- 4 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/controllers/booksControllers.js b/src/controllers/booksControllers.js index f3cbb0f7..0e9d45a7 100644 --- a/src/controllers/booksControllers.js +++ b/src/controllers/booksControllers.js @@ -1,30 +1,37 @@ -const { MissingFieldsError, NoDataError, InvalidParameterError } = require("../errors/errors"); +const { + MissingFieldsError, + NoDataError, + InvalidParameterError, + DataAlreadyExistsError, +} = require("../errors/errors"); const { fetchAllBooks, postBook, fetchBookById, updateBookById, deleteBookById, - fetchBookByQuery + fetchBookByQuery, } = require("../dal/bookRepository"); +const { book1 } = require("../../test/fixtures/bookData"); async function getBooksController(req, res) { let books; - const query = req.query + const query = req.query; if (query) { if (query.perPage < 10 || query.perPage > 50) { - throw new InvalidParameterError(`parameter invalid perPage: ${query.perPage} not valid. Accepted range is 10 - 50`) + throw new InvalidParameterError( + `parameter invalid perPage: ${query.perPage} not valid. Accepted range is 10 - 50` + ); } - books = await fetchBookByQuery(query) + books = await fetchBookByQuery(query); } else { books = await fetchAllBooks(); } - - res.status(200).json({ books, - page: Number(query.page), - per_page: Number(query.perPage) - }); + + res + .status(200) + .json({ books, page: Number(query.page), per_page: Number(query.perPage) }); } async function addBookController(req, res) { @@ -63,24 +70,30 @@ async function putBookByIdController(req, res, next) { const targetBookId = Number(req.params.id); const newParams = req.body; - const book = await fetchBookById(targetBookId); - if (book.length === 0) { - throw new NoDataError("A book with the provided ID does not exist"); - } - const updatedBook = await updateBookById(targetBookId, newParams); - res.status(201).json({ book: updatedBook }); + const allBooks = await fetchAllBooks(); + if (allBooks.find((book) => book.title === newParams.title)) { + throw new DataAlreadyExistsError( + `A book with the title: ${newParams.title} already exists` + ); + } + + const book = await fetchBookById(targetBookId); + if (!book) { + throw new NoDataError(`no book with id: ${targetBookId}`); + } + const updatedBook = await updateBookById(targetBookId, newParams); + res.status(201).json({ book: updatedBook }); } async function deleteBookByIdController(req, res, next) { const targetBookId = Number(req.params.id); - const book = await fetchBookById(targetBookId); - if (book.length === 0) { - throw new NoDataError("A book with the provided ID does not exist"); - } - const deletedBook = await deleteBookById(targetBookId); - res.status(201).json({ book: deletedBook }); - + const book = await fetchBookById(targetBookId); + if (!book) { + throw new NoDataError(`no book with id: ${targetBookId}`); + } + const deletedBook = await deleteBookById(targetBookId); + res.status(201).json({ book: deletedBook }); } module.exports = { diff --git a/src/dal/bookRepository.js b/src/dal/bookRepository.js index 4a8400cf..9e14971c 100644 --- a/src/dal/bookRepository.js +++ b/src/dal/bookRepository.js @@ -64,6 +64,7 @@ async function fetchBookById(id) { } async function updateBookById(id, newParams) { + console.log(id, newParams) try { const sqlQuery = "UPDATE books SET title = $2, type = $3, author = $4, topic = $5, publication_date = $6, pages = $7 WHERE id = $1 RETURNING *;"; diff --git a/src/errors/errors.js b/src/errors/errors.js index 6703a180..09d80724 100644 --- a/src/errors/errors.js +++ b/src/errors/errors.js @@ -9,4 +9,8 @@ class InvalidParameterError extends Error { } -module.exports = { MissingFieldsError, NoDataError, InvalidParameterError } \ No newline at end of file +class DataAlreadyExistsError extends Error { + +} + +module.exports = { MissingFieldsError, NoDataError, InvalidParameterError, DataAlreadyExistsError } \ No newline at end of file diff --git a/src/server.js b/src/server.js index ddff83e8..fc20646c 100644 --- a/src/server.js +++ b/src/server.js @@ -15,7 +15,7 @@ const booksRouter = require('./routers/books.js') app.use('/books', booksRouter) //Error handling -const { MissingFieldsError, NoDataError, InvalidParameterError } = require('./errors/errors.js') +const { MissingFieldsError, NoDataError, InvalidParameterError, DataAlreadyExistsError } = require('./errors/errors.js') app.use((error, req, res, next) => { if (error instanceof MissingFieldsError) { @@ -29,6 +29,10 @@ app.use((error, req, res, next) => { return res.status(400).json({error: error.message}) } + if (error instanceof DataAlreadyExistsError) { + return res.status(409).json({error: error.message}) + } + res.status(500).json({ message: 'Something went wrong' }) From d7f93533e99fff8e5c7b970a0b00bafa836fb35f Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 24 Jun 2024 17:02:36 +0100 Subject: [PATCH 13/20] Get all pets and pet by ID --- package.json | 3 ++- src/controllers/booksControllers.js | 1 - src/controllers/petsControllers.js | 14 ++++++++++++++ src/dal/petsRepository.js | 26 ++++++++++++++++++++++++++ src/routers/pets.js | 10 ++++++++++ src/server.js | 4 +++- 6 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 src/controllers/petsControllers.js create mode 100644 src/dal/petsRepository.js create mode 100644 src/routers/pets.js diff --git a/package.json b/package.json index c32a2a11..b76bca64 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "test": "npx jest -i test/api/routes --forceExit", "test-extensions": "npx jest -i test/api/extensions --forceExit", "test-books": "npx jest -i test/api/routes/books.spec.js --forceExit", - "test-books-extensions": "npx jest -i test/api/extensions/books.spec.js --forceExit" + "test-books-extensions": "npx jest -i test/api/extensions/books.spec.js --forceExit", + "test-pets": "npx jest -i test/api/routes/pets.spec.js --forceExit" }, "dependencies": { "body-parser": "^1.20.2", diff --git a/src/controllers/booksControllers.js b/src/controllers/booksControllers.js index 0e9d45a7..34f1088d 100644 --- a/src/controllers/booksControllers.js +++ b/src/controllers/booksControllers.js @@ -12,7 +12,6 @@ const { deleteBookById, fetchBookByQuery, } = require("../dal/bookRepository"); -const { book1 } = require("../../test/fixtures/bookData"); async function getBooksController(req, res) { let books; diff --git a/src/controllers/petsControllers.js b/src/controllers/petsControllers.js new file mode 100644 index 00000000..fb6a17d1 --- /dev/null +++ b/src/controllers/petsControllers.js @@ -0,0 +1,14 @@ +const { fetchAllPets, fetchPetById } = require("../dal/petsRepository"); + +async function getPetsController(req, res) { + const pets = await fetchAllPets(); + res.status(200).json({ pets }); +} + +async function getPetsByIdController(req, res) { + const id = Number(req.params.id); + const pet = await fetchPetById(id); + res.status(200).json({ pet }); +} + +module.exports = { getPetsController, getPetsByIdController }; diff --git a/src/dal/petsRepository.js b/src/dal/petsRepository.js new file mode 100644 index 00000000..ee9b8726 --- /dev/null +++ b/src/dal/petsRepository.js @@ -0,0 +1,26 @@ +const db = require("../../db/index.js"); + +async function fetchAllPets() { + const sqlQuery = 'SELECT * FROM pets' + + try { + const pets = await db.query(sqlQuery) + return pets.rows + } catch (e) { + console.log(e) + } +} + +async function fetchPetById(id) { + const sqlQuery = 'SELECT * FROM pets WHERE id = $1' + + try { + const pets = await db.query(sqlQuery, [id]) + return pets.rows[0] + } catch (e) { + console.log(e) + } +} + +module.exports = { fetchAllPets, fetchPetById } + diff --git a/src/routers/pets.js b/src/routers/pets.js new file mode 100644 index 00000000..bdff130b --- /dev/null +++ b/src/routers/pets.js @@ -0,0 +1,10 @@ +const express = require('express') +const router = express.Router() + +const { getPetsController, getPetsByIdController } = require('../controllers/petsControllers') + +router.get('/', getPetsController) + +router.get('/:id', getPetsByIdController) + +module.exports = router \ No newline at end of file diff --git a/src/server.js b/src/server.js index fc20646c..b0506771 100644 --- a/src/server.js +++ b/src/server.js @@ -12,7 +12,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) //Error handling const { MissingFieldsError, NoDataError, InvalidParameterError, DataAlreadyExistsError } = require('./errors/errors.js') @@ -32,7 +34,7 @@ app.use((error, req, res, next) => { if (error instanceof DataAlreadyExistsError) { return res.status(409).json({error: error.message}) } - + console.log(`Unhandled error`, error) res.status(500).json({ message: 'Something went wrong' }) From 0c7b790b6e08c4dc000c859cffc21931d502fb71 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 24 Jun 2024 17:20:38 +0100 Subject: [PATCH 14/20] Update pet --- src/controllers/petsControllers.js | 12 ++++++++++-- src/dal/petsRepository.js | 13 ++++++++++++- src/routers/pets.js | 4 +++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/controllers/petsControllers.js b/src/controllers/petsControllers.js index fb6a17d1..e26343a8 100644 --- a/src/controllers/petsControllers.js +++ b/src/controllers/petsControllers.js @@ -1,4 +1,4 @@ -const { fetchAllPets, fetchPetById } = require("../dal/petsRepository"); +const { fetchAllPets, fetchPetById, updatePetById } = require("../dal/petsRepository"); async function getPetsController(req, res) { const pets = await fetchAllPets(); @@ -11,4 +11,12 @@ async function getPetsByIdController(req, res) { res.status(200).json({ pet }); } -module.exports = { getPetsController, getPetsByIdController }; +async function updatePetByIdController(req, res) { + console.log('in') + const id = Number(req.params.id); + const updatedParams = req.body + const pet = await updatePetById(id, updatedParams); + res.status(201).json({ pet }) +} + +module.exports = { getPetsController, getPetsByIdController, updatePetByIdController }; diff --git a/src/dal/petsRepository.js b/src/dal/petsRepository.js index ee9b8726..2cef1dcd 100644 --- a/src/dal/petsRepository.js +++ b/src/dal/petsRepository.js @@ -22,5 +22,16 @@ async function fetchPetById(id) { } } -module.exports = { fetchAllPets, fetchPetById } +async function updatePetById(id, updatedParams) { + const sqlQuery = 'UPDATE pets SET name = $1, age = $2, type = $3, breed = $4, has_microchip = $5 RETURNING *;' + + try { + const result = await db.query(sqlQuery, [updatedParams.name, updatedParams.age, updatedParams.type, updatedParams.breed, updatedParams.has_microchip]) + return result.rows[0] + } catch (e) { + console.log(e) + } +} + +module.exports = { fetchAllPets, fetchPetById, updatePetById } diff --git a/src/routers/pets.js b/src/routers/pets.js index bdff130b..32a48f71 100644 --- a/src/routers/pets.js +++ b/src/routers/pets.js @@ -1,10 +1,12 @@ const express = require('express') const router = express.Router() -const { getPetsController, getPetsByIdController } = require('../controllers/petsControllers') +const { getPetsController, getPetsByIdController, updatePetByIdController } = require('../controllers/petsControllers') router.get('/', getPetsController) router.get('/:id', getPetsByIdController) +router.put('/:id', updatePetByIdController) + module.exports = router \ No newline at end of file From ec315d4b933d5187cf4261a9b449dab24df513f3 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 24 Jun 2024 17:45:33 +0100 Subject: [PATCH 15/20] Add and delete pets --- src/controllers/petsControllers.js | 18 ++++++-- src/dal/bookRepository.js | 3 +- src/dal/petsRepository.js | 73 ++++++++++++++++++++++-------- src/routers/pets.js | 6 ++- 4 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/controllers/petsControllers.js b/src/controllers/petsControllers.js index e26343a8..07433d5c 100644 --- a/src/controllers/petsControllers.js +++ b/src/controllers/petsControllers.js @@ -1,4 +1,4 @@ -const { fetchAllPets, fetchPetById, updatePetById } = require("../dal/petsRepository"); +const { fetchAllPets, fetchPetById, updatePetById, addPet, deletePet } = require("../dal/petsRepository"); async function getPetsController(req, res) { const pets = await fetchAllPets(); @@ -12,11 +12,23 @@ async function getPetsByIdController(req, res) { } async function updatePetByIdController(req, res) { - console.log('in') const id = Number(req.params.id); const updatedParams = req.body const pet = await updatePetById(id, updatedParams); res.status(201).json({ pet }) } -module.exports = { getPetsController, getPetsByIdController, updatePetByIdController }; +async function addPetController(req, res) { + const newPet = req.body + const pet = await addPet(newPet) + res.status(201).json({ pet }) +} + +async function deletePetController(req, res) { + const id = Number(req.params.id) + + const pet = await deletePet(id) + res.status(201).json({ pet }) +} + +module.exports = { getPetsController, getPetsByIdController, updatePetByIdController, addPetController, deletePetController }; diff --git a/src/dal/bookRepository.js b/src/dal/bookRepository.js index 9e14971c..1000e9a0 100644 --- a/src/dal/bookRepository.js +++ b/src/dal/bookRepository.js @@ -37,8 +37,7 @@ async function fetchBookByQuery(query) { async function postBook(book) { try { - const sqlQuery = `INSERT INTO books -(title, type, author, topic, publication_date, pages) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *;`; + 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, [ book.title, book.type, diff --git a/src/dal/petsRepository.js b/src/dal/petsRepository.js index 2cef1dcd..53197524 100644 --- a/src/dal/petsRepository.js +++ b/src/dal/petsRepository.js @@ -1,37 +1,72 @@ const db = require("../../db/index.js"); async function fetchAllPets() { - const sqlQuery = 'SELECT * FROM pets' - - try { - const pets = await db.query(sqlQuery) - return pets.rows - } catch (e) { - console.log(e) - } + const sqlQuery = "SELECT * FROM pets"; + + try { + const pets = await db.query(sqlQuery); + return pets.rows; + } catch (e) { + console.log(e); + } } async function fetchPetById(id) { - const sqlQuery = 'SELECT * FROM pets WHERE id = $1' + const sqlQuery = "SELECT * FROM pets WHERE id = $1"; - try { - const pets = await db.query(sqlQuery, [id]) - return pets.rows[0] - } catch (e) { - console.log(e) - } + try { + const pets = await db.query(sqlQuery, [id]); + return pets.rows[0]; + } catch (e) { + console.log(e); + } } async function updatePetById(id, updatedParams) { - const sqlQuery = 'UPDATE pets SET name = $1, age = $2, type = $3, breed = $4, has_microchip = $5 RETURNING *;' + const sqlQuery = + "UPDATE pets SET name = $1, age = $2, type = $3, breed = $4, has_microchip = $5 RETURNING *;"; + + try { + const result = await db.query(sqlQuery, [ + updatedParams.name, + updatedParams.age, + updatedParams.type, + updatedParams.breed, + updatedParams.has_microchip, + ]); + return result.rows[0]; + } catch (e) { + console.log(e); + } +} + +async function addPet(newPet) { + const sqlQuery = + "INSERT INTO pets (name, age, type, breed, has_microchip) VALUES ($1, $2, $3, $4, $5) RETURNING *"; + + try { + const result = await db.query(sqlQuery, [ + newPet.name, + newPet.age, + newPet.type, + newPet.breed, + newPet.has_microchip, + ]); + return result.rows[0] + } catch (e) { + console.log(e); + } +} + +async function deletePet(petId) { + const sqlQuery = 'DELETE FROM pets WHERE id = $1 RETURNING *' try { - const result = await db.query(sqlQuery, [updatedParams.name, updatedParams.age, updatedParams.type, updatedParams.breed, updatedParams.has_microchip]) + const result = await db.query(sqlQuery, [petId]) return result.rows[0] } catch (e) { console.log(e) } } -module.exports = { fetchAllPets, fetchPetById, updatePetById } - +module.exports = { fetchAllPets, fetchPetById, updatePetById, addPet, deletePet }; diff --git a/src/routers/pets.js b/src/routers/pets.js index 32a48f71..56930e62 100644 --- a/src/routers/pets.js +++ b/src/routers/pets.js @@ -1,7 +1,7 @@ const express = require('express') const router = express.Router() -const { getPetsController, getPetsByIdController, updatePetByIdController } = require('../controllers/petsControllers') +const { getPetsController, getPetsByIdController, updatePetByIdController, addPetController, deletePetController } = require('../controllers/petsControllers') router.get('/', getPetsController) @@ -9,4 +9,8 @@ router.get('/:id', getPetsByIdController) router.put('/:id', updatePetByIdController) +router.post('/', addPetController) + +router.delete('/:id', deletePetController) + module.exports = router \ No newline at end of file From 9bd3860c57f491289bedcd61e9c6752f74ea293a Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Wed, 26 Jun 2024 17:05:24 +0100 Subject: [PATCH 16/20] Error handling for missing field when adding new pet --- package.json | 3 +- src/controllers/petsControllers.js | 52 +++++++++++++++++++++++------- src/dal/bookRepository.js | 1 - 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index b76bca64..c52f0061 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "test-extensions": "npx jest -i test/api/extensions --forceExit", "test-books": "npx jest -i test/api/routes/books.spec.js --forceExit", "test-books-extensions": "npx jest -i test/api/extensions/books.spec.js --forceExit", - "test-pets": "npx jest -i test/api/routes/pets.spec.js --forceExit" + "test-pets": "npx jest -i test/api/routes/pets.spec.js --forceExit", + "test-pets-extensions": "npx jest -i test/api/extensions/pets.spec.js --forceExit" }, "dependencies": { "body-parser": "^1.20.2", diff --git a/src/controllers/petsControllers.js b/src/controllers/petsControllers.js index 07433d5c..004052b1 100644 --- a/src/controllers/petsControllers.js +++ b/src/controllers/petsControllers.js @@ -1,4 +1,12 @@ -const { fetchAllPets, fetchPetById, updatePetById, addPet, deletePet } = require("../dal/petsRepository"); +const { MissingFieldsError } = require("../errors/errors"); + +const { + fetchAllPets, + fetchPetById, + updatePetById, + addPet, + deletePet, +} = require("../dal/petsRepository"); async function getPetsController(req, res) { const pets = await fetchAllPets(); @@ -12,23 +20,43 @@ async function getPetsByIdController(req, res) { } async function updatePetByIdController(req, res) { - const id = Number(req.params.id); - const updatedParams = req.body - const pet = await updatePetById(id, updatedParams); - res.status(201).json({ pet }) + const id = Number(req.params.id); + const updatedParams = req.body; + const pet = await updatePetById(id, updatedParams); + res.status(201).json({ pet }); } async function addPetController(req, res) { - const newPet = req.body - const pet = await addPet(newPet) - res.status(201).json({ pet }) + const newPet = req.body; + + const requiredFields = ['name', 'age', 'type', 'breed', 'has_microchip'] + const missingFields = [] + + requiredFields.forEach((field) => { + if (!newPet[field]) { + missingFields.push(field) + } + }) + + if (missingFields.length > 0) { + throw new MissingFieldsError(`missing fields: ${missingFields.toString().replaceAll(',', ', ')}`); + } + + const pet = await addPet(newPet); + res.status(201).json({ pet }); } async function deletePetController(req, res) { - const id = Number(req.params.id) + const id = Number(req.params.id); - const pet = await deletePet(id) - res.status(201).json({ pet }) + const pet = await deletePet(id); + res.status(201).json({ pet }); } -module.exports = { getPetsController, getPetsByIdController, updatePetByIdController, addPetController, deletePetController }; +module.exports = { + getPetsController, + getPetsByIdController, + updatePetByIdController, + addPetController, + deletePetController, +}; diff --git a/src/dal/bookRepository.js b/src/dal/bookRepository.js index 1000e9a0..93d08473 100644 --- a/src/dal/bookRepository.js +++ b/src/dal/bookRepository.js @@ -63,7 +63,6 @@ async function fetchBookById(id) { } async function updateBookById(id, newParams) { - console.log(id, newParams) try { const sqlQuery = "UPDATE books SET title = $2, type = $3, author = $4, topic = $5, publication_date = $6, pages = $7 WHERE id = $1 RETURNING *;"; From 25ac0f9b0326a5e6b35acaa10c81ab13142e6c93 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Wed, 26 Jun 2024 17:30:14 +0100 Subject: [PATCH 17/20] Returns error when fetching non-existant pet --- src/controllers/petsControllers.js | 14 +++++++++++--- src/dal/petsRepository.js | 6 +++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/controllers/petsControllers.js b/src/controllers/petsControllers.js index 004052b1..9469a04d 100644 --- a/src/controllers/petsControllers.js +++ b/src/controllers/petsControllers.js @@ -1,4 +1,4 @@ -const { MissingFieldsError } = require("../errors/errors"); +const { MissingFieldsError, NoDataError } = require("../errors/errors"); const { fetchAllPets, @@ -6,9 +6,14 @@ const { updatePetById, addPet, deletePet, + fetchPetsWithQuery } = require("../dal/petsRepository"); async function getPetsController(req, res) { + if (req.query) { + fetchPetsWithQuery() + } + const pets = await fetchAllPets(); res.status(200).json({ pets }); } @@ -16,6 +21,11 @@ async function getPetsController(req, res) { async function getPetsByIdController(req, res) { const id = Number(req.params.id); const pet = await fetchPetById(id); + + if(!pet) { + throw new NoDataError(`no pet with id: ${id}`) + } + res.status(200).json({ pet }); } @@ -31,13 +41,11 @@ async function addPetController(req, res) { const requiredFields = ['name', 'age', 'type', 'breed', 'has_microchip'] const missingFields = [] - requiredFields.forEach((field) => { if (!newPet[field]) { missingFields.push(field) } }) - if (missingFields.length > 0) { throw new MissingFieldsError(`missing fields: ${missingFields.toString().replaceAll(',', ', ')}`); } diff --git a/src/dal/petsRepository.js b/src/dal/petsRepository.js index 53197524..232cd98c 100644 --- a/src/dal/petsRepository.js +++ b/src/dal/petsRepository.js @@ -11,6 +11,10 @@ async function fetchAllPets() { } } +async function fetchPetsWithQuery(query) { +console.log(query) +} + async function fetchPetById(id) { const sqlQuery = "SELECT * FROM pets WHERE id = $1"; @@ -69,4 +73,4 @@ async function deletePet(petId) { } } -module.exports = { fetchAllPets, fetchPetById, updatePetById, addPet, deletePet }; +module.exports = { fetchAllPets, fetchPetById, updatePetById, addPet, deletePet, fetchPetsWithQuery }; From afe1d159c3ed9f4e2974fac0c61c8340a077c181 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Wed, 26 Jun 2024 18:46:55 +0100 Subject: [PATCH 18/20] All pet tests pass --- src/controllers/petsControllers.js | 52 ++++++++++++++++++++++-------- src/dal/petsRepository.js | 46 +++++++++++++++++++------- 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/controllers/petsControllers.js b/src/controllers/petsControllers.js index 9469a04d..1d6ba062 100644 --- a/src/controllers/petsControllers.js +++ b/src/controllers/petsControllers.js @@ -1,4 +1,4 @@ -const { MissingFieldsError, NoDataError } = require("../errors/errors"); +const { MissingFieldsError, NoDataError, InvalidParameterError } = require("../errors/errors"); const { fetchAllPets, @@ -6,32 +6,49 @@ const { updatePetById, addPet, deletePet, - fetchPetsWithQuery + fetchPetsWithQuery, } = require("../dal/petsRepository"); async function getPetsController(req, res) { + let pets; + if (req.query) { - fetchPetsWithQuery() + if (req.query.perPage < 10 || req.query.perPage > 50) { + throw new InvalidParameterError(`parameter invalid perPage: ${req.query.perPage} not valid. Accepted range is 10 - 50`) + } + pets = await fetchPetsWithQuery(req.query); + } else { + pets = await fetchAllPets(); } - const pets = await fetchAllPets(); - res.status(200).json({ pets }); + res + .status(200) + .json({ + pets, + page: Number(req.query.page), + per_page: Number(req.query.perPage), + }); } async function getPetsByIdController(req, res) { const id = Number(req.params.id); const pet = await fetchPetById(id); - if(!pet) { - throw new NoDataError(`no pet with id: ${id}`) + if (!pet) { + throw new NoDataError(`no pet with id: ${id}`); } - - res.status(200).json({ pet }); + res.status(200).json({ pets }); } async function updatePetByIdController(req, res) { const id = Number(req.params.id); const updatedParams = req.body; + + const found = await fetchPetById(id) + if (!found) { + throw new NoDataError(`no pet with id: ${id}`); + } + const pet = await updatePetById(id, updatedParams); res.status(201).json({ pet }); } @@ -39,15 +56,17 @@ async function updatePetByIdController(req, res) { async function addPetController(req, res) { const newPet = req.body; - const requiredFields = ['name', 'age', 'type', 'breed', 'has_microchip'] - const missingFields = [] + const requiredFields = ["name", "age", "type", "breed", "has_microchip"]; + const missingFields = []; requiredFields.forEach((field) => { if (!newPet[field]) { - missingFields.push(field) + missingFields.push(field); } - }) + }); if (missingFields.length > 0) { - throw new MissingFieldsError(`missing fields: ${missingFields.toString().replaceAll(',', ', ')}`); + throw new MissingFieldsError( + `missing fields: ${missingFields.toString().replaceAll(",", ", ")}` + ); } const pet = await addPet(newPet); @@ -57,6 +76,11 @@ async function addPetController(req, res) { async function deletePetController(req, res) { const id = Number(req.params.id); + const found = await fetchPetById(id) + if (!found) { + throw new NoDataError(`no pet with id: ${id}`); + } + const pet = await deletePet(id); res.status(201).json({ pet }); } diff --git a/src/dal/petsRepository.js b/src/dal/petsRepository.js index 232cd98c..00caa6fe 100644 --- a/src/dal/petsRepository.js +++ b/src/dal/petsRepository.js @@ -1,7 +1,7 @@ const db = require("../../db/index.js"); async function fetchAllPets() { - const sqlQuery = "SELECT * FROM pets"; + const sqlQuery = "SELECT * FROM pets LIMIT 20"; try { const pets = await db.query(sqlQuery); @@ -12,7 +12,24 @@ async function fetchAllPets() { } async function fetchPetsWithQuery(query) { -console.log(query) + let sqlQuery = `SELECT * FROM pets`; + const params = []; + const perPage = query.perPage || 20; + + params.push(perPage); + sqlQuery += ` LIMIT $${params.length}`; + + if (query.page) { + params.push((query.page - 1) * query.perPage); + sqlQuery += ` OFFSET $${params.length}`; + } + + try { + const result = await db.query(sqlQuery, params); + return result.rows; + } catch (e) { + console.log(e); + } } async function fetchPetById(id) { @@ -56,21 +73,28 @@ async function addPet(newPet) { newPet.breed, newPet.has_microchip, ]); - return result.rows[0] + return result.rows[0]; } catch (e) { console.log(e); } } async function deletePet(petId) { - const sqlQuery = 'DELETE FROM pets WHERE id = $1 RETURNING *' + const sqlQuery = "DELETE FROM pets WHERE id = $1 RETURNING *"; - try { - const result = await db.query(sqlQuery, [petId]) - return result.rows[0] - } catch (e) { - console.log(e) - } + try { + const result = await db.query(sqlQuery, [petId]); + return result.rows[0]; + } catch (e) { + console.log(e); + } } -module.exports = { fetchAllPets, fetchPetById, updatePetById, addPet, deletePet, fetchPetsWithQuery }; +module.exports = { + fetchAllPets, + fetchPetById, + updatePetById, + addPet, + deletePet, + fetchPetsWithQuery, +}; From 84f99bde1ec4a4b47b0011b335c0fc996134b22b Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Wed, 26 Jun 2024 18:48:24 +0100 Subject: [PATCH 19/20] Refactored --- src/controllers/petsControllers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/petsControllers.js b/src/controllers/petsControllers.js index 1d6ba062..8d4ec709 100644 --- a/src/controllers/petsControllers.js +++ b/src/controllers/petsControllers.js @@ -37,7 +37,7 @@ async function getPetsByIdController(req, res) { if (!pet) { throw new NoDataError(`no pet with id: ${id}`); } - res.status(200).json({ pets }); + res.status(200).json({ pet }); } async function updatePetByIdController(req, res) { @@ -48,7 +48,7 @@ async function updatePetByIdController(req, res) { if (!found) { throw new NoDataError(`no pet with id: ${id}`); } - + const pet = await updatePetById(id, updatedParams); res.status(201).json({ pet }); } From 41ae5c63ed1e939ba5005062c76e3c43e3040d05 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Wed, 26 Jun 2024 19:58:41 +0100 Subject: [PATCH 20/20] Breeds functionality added - test passing --- package.json | 3 ++- src/controllers/breedsControllers.js | 9 +++++++++ src/dal/breedsRepository.js | 10 ++++++++++ src/routers/breeds.js | 8 ++++++++ src/server.js | 2 ++ 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/controllers/breedsControllers.js create mode 100644 src/dal/breedsRepository.js create mode 100644 src/routers/breeds.js diff --git a/package.json b/package.json index c52f0061..831dbdf6 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "test-books": "npx jest -i test/api/routes/books.spec.js --forceExit", "test-books-extensions": "npx jest -i test/api/extensions/books.spec.js --forceExit", "test-pets": "npx jest -i test/api/routes/pets.spec.js --forceExit", - "test-pets-extensions": "npx jest -i test/api/extensions/pets.spec.js --forceExit" + "test-pets-extensions": "npx jest -i test/api/extensions/pets.spec.js --forceExit", + "test-breeds-extensions": "npx jest -i test/api/extensions/breeds.spec.js --forceExit" }, "dependencies": { "body-parser": "^1.20.2", diff --git a/src/controllers/breedsControllers.js b/src/controllers/breedsControllers.js new file mode 100644 index 00000000..12c898b5 --- /dev/null +++ b/src/controllers/breedsControllers.js @@ -0,0 +1,9 @@ +const { fetchBreeds } = require("../dal/breedsRepository"); + +async function getAllBreedsController(req, res) { + const breeds = await fetchBreeds(req.query); + console.log({ breeds }) + res.status(200).json({ breeds }); +} + +module.exports = { getAllBreedsController }; diff --git a/src/dal/breedsRepository.js b/src/dal/breedsRepository.js new file mode 100644 index 00000000..d9888489 --- /dev/null +++ b/src/dal/breedsRepository.js @@ -0,0 +1,10 @@ +const db = require("../../db/index.js"); + +async function fetchBreeds(query) { + + sqlQuery = 'SELECT breed FROM pets WHERE type = $1 GROUP BY breed' + const result = await db.query(sqlQuery, [query.type]) + return result.rows +} + +module.exports = { fetchBreeds } \ No newline at end of file diff --git a/src/routers/breeds.js b/src/routers/breeds.js new file mode 100644 index 00000000..c27834ee --- /dev/null +++ b/src/routers/breeds.js @@ -0,0 +1,8 @@ +const express = require('express') +const router = express.Router() + +const { getAllBreedsController } = require('../controllers/breedsControllers') + +router.use('/', getAllBreedsController) + +module.exports = router \ No newline at end of file diff --git a/src/server.js b/src/server.js index b0506771..24a3fc33 100644 --- a/src/server.js +++ b/src/server.js @@ -13,8 +13,10 @@ 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') app.use('/books', booksRouter) app.use('/pets', petsRouter) +app.use('/breeds', breedsRouter) //Error handling const { MissingFieldsError, NoDataError, InvalidParameterError, DataAlreadyExistsError } = require('./errors/errors.js')