From a70226ba6e754661236e03168bfaa202581c1fef Mon Sep 17 00:00:00 2001 From: Angus Townsley Date: Mon, 24 Jun 2024 00:55:02 +0100 Subject: [PATCH 1/4] Inital endpoint construction --- db/index.js | 34 +++++------- src/controllers/books.js | 113 +++++++++++++++++++++++++++++++++++++++ src/controllers/pets.js | 111 ++++++++++++++++++++++++++++++++++++++ src/routers/books.js | 12 +++-- src/routers/pets.js | 15 ++++++ src/server.js | 2 + 6 files changed, 263 insertions(+), 24 deletions(-) create mode 100644 src/controllers/books.js create mode 100644 src/controllers/pets.js create mode 100644 src/routers/pets.js diff --git a/db/index.js b/db/index.js index af723442..cc127fad 100644 --- a/db/index.js +++ b/db/index.js @@ -1,25 +1,17 @@ -// Load our .env file require('dotenv').config() +const {Pool} = require('pg') -// 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 - } -} +const { PGHOST, PGDATABASE, PGUSER, PGPASSWORD } = process.env +const pool = new Pool({ + host: PGHOST, + database: PGDATABASE, + username: PGUSER, + password: PGPASSWORD, + port: 5432, + ssl: { + require: true, + }, +}) -module.exports = client; +module.exports = pool; diff --git a/src/controllers/books.js b/src/controllers/books.js new file mode 100644 index 00000000..d57fd494 --- /dev/null +++ b/src/controllers/books.js @@ -0,0 +1,113 @@ +const pool = require('../../db') + +const getBooks = async (req, res) => { + const db = await pool.connect() + + const sqlQuery = 'select * from books' + const result = await db.query(sqlQuery) + + db.release() + + res.send({ books: result.rows }) +} + +const createBook = async (req, res) => { + const db = await pool.connect() + + const { title, type, author, topic, publication_date, pages } = req.body + + if ( + !title || + !type || + !author || + !topic || + !publication_date || + !Number.isInteger(pages) + ) { + res.status(400).json({ + error: 'All fields are required and pages must be an integer.', + }) + } + + try { + const result = await db.query( + `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] + ) + + return res.status(201).json({ book: result.rows[0] }) + } catch (err) { + console.error('Error inserting book:', err) + return res.status(500).json({ error: 'Internal Server Error' }) + } finally { + db.release() + } +} + +const getBookById = async (req, res) => { + const db = await pool.connect() + + const sqlQuery = 'select * from books where id = $1' + const result = await db.query(sqlQuery, [Number(req.params.id)]) + + db.release() + + res.send({ book: result.rows[0] }) +} + +const updateBookById = async (req, res) => { + const db = await pool.connect() + + const { title, type, author, topic, publication_date, pages } = req.body + + if ( + !title || + !type || + !author || + !topic || + !publication_date || + !Number.isInteger(pages) + ) { + res.status(400).json({ + error: 'All fields are required and pages must be an integer.', + }) + } + + try { + const result = await db.query( + `Update books + Set title = $1 , type = $2 , author = $3 , topic = $4 , publication_date = $5 , pages = $6 + where id = $7 + RETURNING *`, + [title, type, author, topic, publication_date, pages, req.params.id] + ) + + return res.status(201).json({ book: result.rows[0] }) + } catch (err) { + console.error('Error inserting book:', err) + return res.status(500).json({ error: 'Internal Server Error' }) + } finally { + db.release() + } +} + +const deleteBookById = async (req, res) => { + const db = await pool.connect() + + const sqlQuery = 'delete from books where id = $1 RETURNING *' + const result = await db.query(sqlQuery, [Number(req.params.id)]) + + db.release() + + res.status(201).send({ book: result.rows[0] }) +} + +module.exports = { + getBooks, + createBook, + getBookById, + deleteBookById, + updateBookById, +} diff --git a/src/controllers/pets.js b/src/controllers/pets.js new file mode 100644 index 00000000..23cce5f4 --- /dev/null +++ b/src/controllers/pets.js @@ -0,0 +1,111 @@ +const pool = require('../../db') + +const getPets = async (req, res) => { + const db = await pool.connect() + + const sqlQuery = 'select * from pets' + const result = await db.query(sqlQuery) + + db.release() + + res.send({ pets: result.rows }) +} + +const createPet = async (req, res) => { + const db = await pool.connect() + + const { name, age, type, breed, has_microchip} = req.body + + if ( + !name || + !age || + !type || + !breed || + !typeof has_microchip === "boolean" + ) { + res.status(400).json({ + error: 'All fields are required and has_microchip must be a boolean', + }) + } + + try { + const result = await db.query( + `INSERT INTO pets (name, age, type, breed, has_microchip) + VALUES ($1, $2, $3, $4, $5) + RETURNING *`, + [name, age, type, breed, has_microchip, ] + ) + + return res.status(201).json({ pet: result.rows[0] }) + } catch (err) { + console.error('Error inserting pet:', err) + return res.status(500).json({ error: 'Internal Server Error' }) + } finally { + db.release() + } +} + +const getPetById = async (req, res) => { + const db = await pool.connect() + + const sqlQuery = 'select * from pets where id = $1' + const result = await db.query(sqlQuery, [Number(req.params.id)]) + + db.release() + + res.send({ pet: result.rows[0] }) +} + +const updatePetById = async (req, res) => { + const db = await pool.connect() + + const { name, age, type, breed, has_microchip} = req.body + + if ( + !name || + !age || + !type || + !breed || + !typeof has_microchip === "boolean" + ) { + res.status(400).json({ + error: 'All fields are required and has_microchip must be a boolean', + }) + } + + try { + const result = await db.query( + `Update pets + Set name = $1 , age = $2 , type = $3 , breed = $4 , has_microchip = $5 + where id = $6 + RETURNING *`, + [name, age, type, breed, has_microchip, req.params.id] + ) + + return res.status(201).json({ pet: result.rows[0] }) + } catch (err) { + console.error('Error inserting pet:', err) + return res.status(500).json({ error: 'Internal Server Error' }) + } finally { + db.release() + } +} + +const deletePetById = async (req, res) => { + const db = await pool.connect() + + const sqlQuery = 'delete from pets where id = $1 RETURNING *' + const result = await db.query(sqlQuery, [Number(req.params.id)]) + + db.release() + + res.status(201).send({ pet: result.rows[0] }) +} + +module.exports = { + getPets, + createPet, + getPetById, + deletePetById, + updatePetById, +} diff --git a/src/routers/books.js b/src/routers/books.js index 1551dd87..97136642 100644 --- a/src/routers/books.js +++ b/src/routers/books.js @@ -1,9 +1,15 @@ const express = require('express') const router = express.Router() -const db = require("../../db"); +const { getBooks, createBook, getBookById , deleteBookById, updateBookById} = require('../controllers/books') -router.get('/', async (req, res) => { +router.get('/', getBooks) -}) +router.post('/', createBook) + +router.get('/:id', getBookById) + +router.delete('/:id', deleteBookById) + +router.put('/:id', updateBookById) module.exports = router diff --git a/src/routers/pets.js b/src/routers/pets.js new file mode 100644 index 00000000..5736d76d --- /dev/null +++ b/src/routers/pets.js @@ -0,0 +1,15 @@ +const express = require('express') +const router = express.Router() +const { getPets, createPet, getPetById , deletePetById, updatePetById} = require('../controllers/pets') + +router.get('/', getPets) + +router.post('/', createPet) + +router.get('/:id', getPetById) + +router.delete('/:id', deletePetById) + +router.put('/:id', updatePetById) + +module.exports = router diff --git a/src/server.js b/src/server.js index dac55e5d..233ceb88 100644 --- a/src/server.js +++ b/src/server.js @@ -10,7 +10,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 From 2d39c8639a569d69cd419d7c9d547710cb1ce0ab Mon Sep 17 00:00:00 2001 From: Angus Townsley Date: Mon, 24 Jun 2024 01:37:59 +0100 Subject: [PATCH 2/4] Implement query by type and topic --- src/controllers/books.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/controllers/books.js b/src/controllers/books.js index d57fd494..6d12e5ce 100644 --- a/src/controllers/books.js +++ b/src/controllers/books.js @@ -1,10 +1,29 @@ const pool = require('../../db') +const types = new Map([['Fiction', "type = 'Fiction'"], ['Non-Fiction', "type = 'Non-Fiction'"]]) + const getBooks = async (req, res) => { const db = await pool.connect() + const {type, topic } = req.query + + const queryContents = [] + + let sqlQuery = "select * from books where 1 = 1" + + + if(types.has(type)) { + queryContents.push(type) + sqlQuery += ` and type = $${queryContents.length}` + console.log(sqlQuery) + } + + if(topic) { + queryContents.push(topic) + sqlQuery += ` and topic = $${queryContents.length}` + console.log(sqlQuery) + } - const sqlQuery = 'select * from books' - const result = await db.query(sqlQuery) + const result = await db.query(sqlQuery, queryContents) db.release() From 57971a997bc86ed6c83a4109ee5a04bdec61b0ce Mon Sep 17 00:00:00 2001 From: Angus Townsley Date: Mon, 24 Jun 2024 10:11:41 +0100 Subject: [PATCH 3/4] Refactor books to include a repository --- src/controllers/books.js | 71 +++------------------- src/repositories/booksRepository.js | 94 +++++++++++++++++++++++++++++ src/repositories/petsRepository.js | 1 + 3 files changed, 103 insertions(+), 63 deletions(-) create mode 100644 src/repositories/booksRepository.js create mode 100644 src/repositories/petsRepository.js diff --git a/src/controllers/books.js b/src/controllers/books.js index 6d12e5ce..70cde177 100644 --- a/src/controllers/books.js +++ b/src/controllers/books.js @@ -1,31 +1,11 @@ const pool = require('../../db') +const booksRepository = require('../repositories/booksRepository') const types = new Map([['Fiction', "type = 'Fiction'"], ['Non-Fiction', "type = 'Non-Fiction'"]]) const getBooks = async (req, res) => { - const db = await pool.connect() - const {type, topic } = req.query - - const queryContents = [] - - let sqlQuery = "select * from books where 1 = 1" - - - if(types.has(type)) { - queryContents.push(type) - sqlQuery += ` and type = $${queryContents.length}` - console.log(sqlQuery) - } - - if(topic) { - queryContents.push(topic) - sqlQuery += ` and topic = $${queryContents.length}` - console.log(sqlQuery) - } - const result = await db.query(sqlQuery, queryContents) - - db.release() + const result = await booksRepository.getBooks(req) res.send({ books: result.rows }) } @@ -48,30 +28,13 @@ const createBook = async (req, res) => { }) } - try { - const result = await db.query( - `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] - ) + const result = await booksRepository.createBook(req) - return res.status(201).json({ book: result.rows[0] }) - } catch (err) { - console.error('Error inserting book:', err) - return res.status(500).json({ error: 'Internal Server Error' }) - } finally { - db.release() - } + res.status(201).json({ book: result.rows[0] }) } const getBookById = async (req, res) => { - const db = await pool.connect() - - const sqlQuery = 'select * from books where id = $1' - const result = await db.query(sqlQuery, [Number(req.params.id)]) - - db.release() + const result = await booksRepository.getBookById(req) res.send({ book: result.rows[0] }) } @@ -93,32 +56,14 @@ const updateBookById = async (req, res) => { error: 'All fields are required and pages must be an integer.', }) } - - try { - const result = await db.query( - `Update books - Set title = $1 , type = $2 , author = $3 , topic = $4 , publication_date = $5 , pages = $6 - where id = $7 - RETURNING *`, - [title, type, author, topic, publication_date, pages, req.params.id] - ) - + const result = await booksRepository.updateBookById(req) return res.status(201).json({ book: result.rows[0] }) - } catch (err) { - console.error('Error inserting book:', err) - return res.status(500).json({ error: 'Internal Server Error' }) - } finally { - db.release() - } + } const deleteBookById = async (req, res) => { - const db = await pool.connect() - - const sqlQuery = 'delete from books where id = $1 RETURNING *' - const result = await db.query(sqlQuery, [Number(req.params.id)]) - db.release() + const result = await booksRepository.deleteBookById(req) res.status(201).send({ book: result.rows[0] }) } diff --git a/src/repositories/booksRepository.js b/src/repositories/booksRepository.js new file mode 100644 index 00000000..cc3d8f62 --- /dev/null +++ b/src/repositories/booksRepository.js @@ -0,0 +1,94 @@ +const pool = require('../../db') + +const types = new Map([ + ['Fiction', "type = 'Fiction'"], + ['Non-Fiction', "type = 'Non-Fiction'"], +]) + +async function getBooks(req) { + const db = await pool.connect() + const { type, topic } = req.query + + const queryContents = [] + + let sqlQuery = 'select * from books where 1 = 1' + + if (types.has(type)) { + queryContents.push(type) + sqlQuery += ` and type = $${queryContents.length}` + console.log(sqlQuery) + } + + if (topic) { + queryContents.push(topic) + sqlQuery += ` and topic = $${queryContents.length}` + console.log(sqlQuery) + } + + const result = await db.query(sqlQuery, queryContents) + + db.release() + + return result +} + +async function createBook(req) { + const db = await pool.connect() + const { title, type, author, topic, publication_date, pages } = req.body + + try { + const result = await db.query( + `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] + ) + + return result + } catch (err) { + console.error('Error inserting book:', err) + } finally { + db.release() + } +} + +async function getBookById(req) { + const db = await pool.connect() + + const sqlQuery = 'select * from books where id = $1' + const result = await db.query(sqlQuery, [Number(req.params.id)]) + + db.release() + + return result +} + +async function updateBookById(req) { + const db = await pool.connect() + const { title, type, author, topic, publication_date, pages } = req.body + + const result = await db.query( + `Update books + Set title = $1 , type = $2 , author = $3 , topic = $4 , publication_date = $5 , pages = $6 + where id = $7 + RETURNING *`, + [title, type, author, topic, publication_date, pages, req.params.id] + ) + + db.release() + + return result +} + +async function deleteBookById(req) { + const db = await pool.connect() + + const sqlQuery = 'delete from books where id = $1 RETURNING *' + const result = await db.query(sqlQuery, [Number(req.params.id)]) + + db.release() + + return result +} + +module.exports = { getBooks, createBook, getBookById , updateBookById, deleteBookById} diff --git a/src/repositories/petsRepository.js b/src/repositories/petsRepository.js new file mode 100644 index 00000000..0233084b --- /dev/null +++ b/src/repositories/petsRepository.js @@ -0,0 +1 @@ +const pool = require('../../db') \ No newline at end of file From 9e648d2b89b21694b2cb6ce2e130550a63694f67 Mon Sep 17 00:00:00 2001 From: Angus Townsley Date: Mon, 24 Jun 2024 10:37:46 +0100 Subject: [PATCH 4/4] Refactor pets endpoints to use repository --- src/controllers/books.js | 4 +- src/controllers/pets.js | 71 ++++++--------------------- src/repositories/petsRepository.js | 78 +++++++++++++++++++++++++++++- 3 files changed, 93 insertions(+), 60 deletions(-) diff --git a/src/controllers/books.js b/src/controllers/books.js index 70cde177..29737f4f 100644 --- a/src/controllers/books.js +++ b/src/controllers/books.js @@ -40,8 +40,6 @@ const getBookById = async (req, res) => { } const updateBookById = async (req, res) => { - const db = await pool.connect() - const { title, type, author, topic, publication_date, pages } = req.body if ( @@ -52,7 +50,7 @@ const updateBookById = async (req, res) => { !publication_date || !Number.isInteger(pages) ) { - res.status(400).json({ + return res.status(400).json({ error: 'All fields are required and pages must be an integer.', }) } diff --git a/src/controllers/pets.js b/src/controllers/pets.js index 23cce5f4..08f7dd70 100644 --- a/src/controllers/pets.js +++ b/src/controllers/pets.js @@ -1,103 +1,62 @@ const pool = require('../../db') +const petsRepository = require('../repositories/petsRepository') const getPets = async (req, res) => { - const db = await pool.connect() - - const sqlQuery = 'select * from pets' - const result = await db.query(sqlQuery) - - db.release() + const result = await petsRepository.getPets() res.send({ pets: result.rows }) } const createPet = async (req, res) => { - const db = await pool.connect() - - const { name, age, type, breed, has_microchip} = req.body + const { name, age, type, breed, has_microchip } = req.body if ( !name || !age || !type || !breed || - !typeof has_microchip === "boolean" + !typeof has_microchip === 'boolean' ) { res.status(400).json({ error: 'All fields are required and has_microchip must be a boolean', }) } - try { - const result = await db.query( - `INSERT INTO pets (name, age, type, breed, has_microchip) - VALUES ($1, $2, $3, $4, $5) - RETURNING *`, - [name, age, type, breed, has_microchip, ] - ) - - return res.status(201).json({ pet: result.rows[0] }) - } catch (err) { - console.error('Error inserting pet:', err) - return res.status(500).json({ error: 'Internal Server Error' }) - } finally { - db.release() - } + const result = await petsRepository.createPet(req) + + res.status(201).json({ pet: result.rows[0] }) } const getPetById = async (req, res) => { - const db = await pool.connect() - - const sqlQuery = 'select * from pets where id = $1' - const result = await db.query(sqlQuery, [Number(req.params.id)]) - db.release() + const result = await petsRepository.getPetById(req) res.send({ pet: result.rows[0] }) } const updatePetById = async (req, res) => { - const db = await pool.connect() - - const { name, age, type, breed, has_microchip} = req.body + const { name, age, type, breed, has_microchip } = req.body if ( !name || !age || !type || !breed || - !typeof has_microchip === "boolean" + !typeof has_microchip === 'boolean' ) { res.status(400).json({ error: 'All fields are required and has_microchip must be a boolean', }) } - try { - const result = await db.query( - `Update pets - Set name = $1 , age = $2 , type = $3 , breed = $4 , has_microchip = $5 - where id = $6 - RETURNING *`, - [name, age, type, breed, has_microchip, req.params.id] - ) - - return res.status(201).json({ pet: result.rows[0] }) - } catch (err) { - console.error('Error inserting pet:', err) - return res.status(500).json({ error: 'Internal Server Error' }) - } finally { - db.release() - } -} + const result = await petsRepository.updatePetById(req) -const deletePetById = async (req, res) => { - const db = await pool.connect() + res.status(201).json({ pet: result.rows[0] }) - const sqlQuery = 'delete from pets where id = $1 RETURNING *' - const result = await db.query(sqlQuery, [Number(req.params.id)]) +} - db.release() +const deletePetById = async (req, res) => { + const result = await petsRepository.deletePetById(req) res.status(201).send({ pet: result.rows[0] }) } diff --git a/src/repositories/petsRepository.js b/src/repositories/petsRepository.js index 0233084b..312a6c0d 100644 --- a/src/repositories/petsRepository.js +++ b/src/repositories/petsRepository.js @@ -1 +1,77 @@ -const pool = require('../../db') \ No newline at end of file +const pool = require('../../db') + +const getPets = async () => { + const db = await pool.connect() + + const sqlQuery = 'select * from pets' + const result = await db.query(sqlQuery) + + db.release() + + return result +} + +const createPet = async (req) => { + const db = await pool.connect() + + const { name, age, type, breed, has_microchip } = req.body + + const result = await db.query( + `INSERT INTO pets (name, age, type, breed, has_microchip) + VALUES ($1, $2, $3, $4, $5) + RETURNING *`, + [name, age, type, breed, has_microchip] + ) + + db.release() + + return result +} + +const getPetById = async (req) => { + const db = await pool.connect() + + const sqlQuery = 'select * from pets where id = $1' + const result = await db.query(sqlQuery, [Number(req.params.id)]) + + db.release() + + return result +} + +const updatePetById = async (req) => { + const db = await pool.connect() + + const { name, age, type, breed, has_microchip } = req.body + + const result = await db.query( + `Update pets + Set name = $1 , age = $2 , type = $3 , breed = $4 , has_microchip = $5 + where id = $6 + RETURNING *`, + [name, age, type, breed, has_microchip, req.params.id] + ) + + db.release() + + return result +} + +const deletePetById = async (req, res) => { + const db = await pool.connect() + + const sqlQuery = 'delete from pets where id = $1 RETURNING *' + const result = await db.query(sqlQuery, [Number(req.params.id)]) + + db.release() + + return result +} + +module.exports = { + getPets, + createPet, + getPetById, + deletePetById, + updatePetById, +}