diff --git a/week-1/mandatory/2-classes-db/task.md b/week-1/mandatory/2-classes-db/task.md index 735de6ae..77896f0b 100644 --- a/week-1/mandatory/2-classes-db/task.md +++ b/week-1/mandatory/2-classes-db/task.md @@ -7,6 +7,58 @@ Below you will find a set of tasks for you to complete to consolidate and extend To submit this homework write the correct commands for each question here: ```sql +1. SELECT * from room_types WHERE def_rate > 100; + +2. SELECT * from reservations WHERE checkin_date BETWEEN '2020-09-01' AND'2020-09-30' AND (checkout_date - checkin_date ) > 3; + +3. SELECT * from customers WHERE city LIKE 'M%'; + +4. INSERT into room_types (room_type,def_rate) VALUES ('PENTHOUSE',185.00); + +5. INSERT into rooms (room_no, rate, room_type) VALUES + (501,185.00,'PENTHOUSE'), + (502,185.00,'PENTHOUSE'); + +6. INSERT into rooms (room_no, rate, room_type) VALUES + (503,143.00,'PREMIER PLUS'); + +7. SELECT count(id) from reservations + WHERE checkin_date >= '2020-08-01' + AND checkout_date <= '2020-08-31'; + +8. SELECT SUM(checkout_date - checkin_date) from + reservations WHERE room_no between 201 AND 299; + +9. SELECT COUNT(*) from invoices + WHERE total > 300.00; + + *Grand Total* + SELECT SUM(total) from invoices + WHERE total > 300.00; + + *Average* + SELECT AVG(total) from invoices + WHERE total > 300.00; + +10. SELECT SUM(checkout_date - checkin_date) from reservations + WHERE room_no between 101 AND 112; + (54) + + SELECT SUM(checkout_date - checkin_date) from reservations + WHERE room_no between 201 AND 212; + (63) + + SELECT SUM(checkout_date - checkin_date) from reservations + WHERE room_no between 301 AND 312; + (46) + + SELECT SUM(checkout_date - checkin_date) from reservations + WHERE room_no between 401 AND 412; + (40) + + SELECT SUM(checkout_date - checkin_date) from reservations + WHERE room_no between 501 AND 512; + (none) ``` diff --git a/week-2/mandatory/2-ecommerce-db/task.md b/week-2/mandatory/2-ecommerce-db/task.md index c48d2286..7d09efa0 100644 --- a/week-2/mandatory/2-ecommerce-db/task.md +++ b/week-2/mandatory/2-ecommerce-db/task.md @@ -8,6 +8,36 @@ Below you will find a set of tasks for you to complete to set up a database for To submit this homework write the correct commands for each question here: ```sql +1. select name, address from customers where country ='United States'; + +2. select name from customers order by name asc; + +3.select * from products where product_name like '%socks%'; + +4. select p.product_name, pa.* from products p join product_availability pa on (p.id = pa.prod_id) where pa.unit_price > 100; + +5. select p.product_name, pa.unit_price from products p join product_availability pa on (p.id = pa.prod_id) order by pa.unit_price limit 5; + +6. select p.product_name, pa.unit_price, s.suuplier_name from products p join product_availability pa on (p.id = pa.prod_id) join suppliers s on (pa.supp_id = s.id); + +7.select p.product_name , s.supplier_name from products p join product_availability pa on (p.id = pa.prod_id) join suppliers s on (pa.supp_id = s.id) where s.country ='United Kingdom'; + +8. select o.id, o.order_reference, o.order_date, (oi.quantity * pa.unit_price) as total_cost from orders o join order_items oi on (o.id =oi.order_id) join product_availability pa on (oi.product_id = pa.prod_id) where o.customer_id = 1; + +9. select * from order_items oi join orders o on (oi.order_id = o.id) join customers c on (o.custmer_id = c.id) where c.name ='Hope Crosby'; + +10. select p.product_name, pa.unit_price, oi.quantity from products p join product_availability pa on (p.id = pa.prod_id) join order_items oi on (pa.prod_id = oi.product_id) join orders o on (oi.order_id = o.id) where o.order_reference = 'ORD006'; + +11. select c.name, p.product_name, o.order_reference, o.order_date, s.supplier_name, oi.quantity from customers c join orders o on (c.id = o.customers_id) join order_items oi on (o.id = oi.order_id) join suppliers s on (oi.supplier_id = s.id) join product_availability pa on (s.id = pa.supp.id) join products p on (pa.prod_id = p.id); + +12. select distinct c.name from customers c join orders o on (c.id = o.customer_id) join order_items oi on (o.id = oi.order_id) join suppliers s on (oi.supplier_id = s.id) where s.country ='China'; + +13. select c.name, o.order_reference, o.order_date, (oi.quantity * pa.unit_price) as total_cost from customers c join orders o on (c.id = o.customer_id) join order_items oi on (o.id = oi.order_id) join product_availalbility pa on (oi.product_id = pa.prod_id) order by total_cost desc; + + + + + ``` diff --git a/week-2/mandatory/3-api/server.js b/week-2/mandatory/3-api/server.js new file mode 100644 index 00000000..a51fcb45 --- /dev/null +++ b/week-2/mandatory/3-api/server.js @@ -0,0 +1,43 @@ +const express = require("express"); +const app = express(); + +app.listen(3000, function(){ + console.log("Server is listening on port 3000. Ready to accept requests! ") +}); + +const {Pool} = require ('pg'); + +const db = new Pool({ + user: 'farhana', + host:'localhost', + database: 'cyf_ecommerce', + pasword: '', + port: 5432 +}); + +//get all customers + +app.get("/customers", function(req, res){ + db.query('SELECT id, name, address,city, country FROM customers', + (error, result) => { + res.json(result.rows); + }); +}); + +//get all suppliers + +app.get("/suppliers", function(re,res){ + db.query('SELECT id, supplier_name, country FROM suppliers', + (error, result) => { + res.json(result.rows); + }); +}); + +// get product names, their prices and suppliers +app.get("/products", function(req, res){ + db.query('SELECT p.product_name, pa.unit_price, s.supplier_name FROM products p JOIN product_availability pa ON (p.id = pa.prod_id) JOIN suppliers s ON(pa.supp_id = s.id)', + (error, result) => { + res.json(result.rows); + }); +}); + diff --git a/week-3/mandatory/2-api/server.js b/week-3/mandatory/2-api/server.js new file mode 100644 index 00000000..6ffd6cbf --- /dev/null +++ b/week-3/mandatory/2-api/server.js @@ -0,0 +1,243 @@ +const express = require('express'); +const bodyParser = require('body-parser'); +const app = express(); +// app.use(bodyParser.urlencoded({ extended: true })); +app.use(bodyParser.json()); + + + +app.listen(3000, function(){ + console.log("Server is listening on port 3000. Ready to accept requests!"); +} ); + +const {Pool} = require ('pg'); + +const db = new Pool({ + user: 'farhana', + host:'localhost', + database: 'cyf_ecommerce', + pasword: '', + port: 5432 +}); + +// // get product names, their prices and suppliers + query paramaters +app.get("/products", function(req, res){ + let name = req.query.name; + + if(name === undefined){ + db.query('SELECT p.product_name, pa.unit_price, s.supplier_name FROM products p JOIN product_availability pa ON (p.id = pa.prod_id) JOIN suppliers s ON(pa.supp_id = s.id)', + (error, result) => { + if(error === undefined){ + res.json(result.rows); + } else{ + res.status(400).send("Bad request "+ error); + } + + }); + } else{ + let newName = name.toLowerCase(); + db.query("SELECT * FROM products WHERE Lower(product_name) LIKE '%'||$1||'%'",[newName], (error, result)=>{ + if(error === undefined){ + res.json(result.rows); + } else{ + res.status(400).send("Bad request " + error); + } + + }); + } + +}); + +//get customer by ID +app.get("/customers/:customerid", function(req,res){ + const customerId = parseInt(req.params.customerid); + if(customerId !== undefined){ + db.query("SELECT * from customers WHERE id =$1",[customerId], (error, result) =>{ + if(error === undefined){ + res.json(result.rows); + } else{ + res.status(400).send("Bad Request " + error ); + } + + }); + + + } else{ + res.send("Customer doesnt exist"); + + } + +}); + + //get all customers + +app.get("/customers", function(req, res){ + db.query('SELECT id, name, address,city, country FROM customers', + (error, result) => { + if(error === undefined){ + res.json(result.rows); + } else{ + res.status(400).send("Bad Request "+ error); + } + + }); +}); + +//POST new customer + +app.post("/customers", function(req,res){ + const newName = req.body.name; + const newAddress = req.body.address; + const newCity = req.body.city + const newCountry = req.body.country; + + db.query("INSERT INTO customers (name,address,city,country) VALUES ($1,$2,$3,$4) returning id", + [newName,newAddress,newCity,newCountry], (error, result)=>{ + if(error == undefined){ + res.send("New Customer Added" + result.rows[0].id); + }else { + res.status(400).send("Bad request" + error); + console.log(error); + } + + + }); +}); + + +//add new product + +app.post("/products", function(req,res){ + const newName = req.body.name; + + db.query("INSERT INTO products (product_name) VALUES ($1) returning id", + [newName], (error, result)=>{ + if(error == undefined){ + res.send("New Product Added" + result.rows[0].id); + }else { + res.status(400).send("Bad request" + error); + console.log(error); + } + + }); +}); + + +//Post product availability +app.post("/availability", function(req,res){ + const newProdId = req.body.prodId; + const newSuppId = req.body.suppId; + const newUnitPrice = req.body.unitPrice; + + + if(newUnitPrice > 0){ + db.query("SELECT 1 FROM products WHERE id = $1",[newProdId], (error, result)=>{ + if(result.rowCount < 0){ + res.status(400).send("Product doesn't exist"); + + } else{ + + db.query("INSERT INTO product_availability (prod_id, unit_price) VALUES ($2,$3)",[newProdId, newUnitPrice], (error)=>{ + db.query("SELECT 1 FROM suppliers WHERE id = $4",[newSuppId], (error,result)=>{ + if(result.rowCount < 0){ + res.status(400).send("Supplier doesn't exist"); + }else{ + db.query("INSERT INTO product_availability (supp_id) VALUES ($5)",[newSuppId], (error)=>{ + res.send("Product info added"); + }) + } + }) + }); + } + }); + }else{ + res.send("Enter a positive value"); + } +}); + +//customers/:customerId/orders +app.post("/customers/:id/orders", function(req,res){ + const customerId = req.params.id; + const orderDate = req.body.orderDate; + const orderReference = req.body.orderReference; + + db.query("SELECT 1 FROM customers WHERE id = $1",[customerId], (error, result)=>{ + if(error == undefined){ + if(result.rowCount < 0){ + res.send("Customer doesn't exist"); + } else{ + db.query("INSERT INTO orders (order_date, order_reference, customer_id)"+ + "VALUES ($2,$3,$1)",[orderDate, orderReference, customerId],(error)=>{ + if(error === undefined){ + res.send("New order added"); + }else{ + res.status(400).send("Bad request" + error); + console.log(error); + } + + }); + } + }else{ + res.status(400).send("Bad request" + error); + console.log(error); + } + }) + +}) + +app.put("/customers/:id", function (req, res){ + const customerId = req.params.id; + const newName = req.body.name; + const newAddress = req.body.address; + const newCity = req.body.city; + const newCountry = req.body.country; + + db.query("UPDATE customers SET name=$2, address = $3, city = $4, country = $5 WHERE id=$1", + [customerId, newName, newAddress, newCity, newCountry], (error)=>{ + if(error == undefined){ + res.send(`Customer ${customerId} updated!`); + } else{ + console.log(error); + res.status(500).send("Bad Request"); + + } + }); + +}); + +app.delete("/orders/:orderId", function(req,res){ + const orderId = req.params.orderId; + + db.query("DELETE FROM orders WHERE id = $1",[orderId], (error)=>{ + if(error==undefined){ + res.send(`Order ${orderId} is deleted!`); + }else{ + res.status(400).send("Bad Request " + error); + } + }); +}); + +//delete an existing customer only if this customer doesn't have orders. +app.delete("/customers/:id", function(req,res){ + const customerId = req.params.id; + db.query("SELECT 1 from orders WHERE customer_id = $1",[customerId], (error, result)=>{ + if(error == undefined){ + if(result.rowCount === 0){ + db.query("DELETE from customers WHERE id = $1",[customerId], (error)=>{ + if(error == undefined){ + res.send(`Customer ${customerId} deleted`) + }else{ + res.status(400).send("Bad request " + error); + console.log(error) + } + }) + }else{ + res.send(`Customer ${customerId} has order pending`); + } + }else{ + res.status(400).send("Bad Request " + error); + console.log(error); + } + }); +}); + diff --git a/week-3/mandatory/2-api/task.md b/week-3/mandatory/2-api/task.md index 924f54de..dcb14d66 100644 --- a/week-3/mandatory/2-api/task.md +++ b/week-3/mandatory/2-api/task.md @@ -10,21 +10,21 @@ To submit you should open a pull request with all of your code in this folder. In the following homework, you will create new API endpoints in the NodeJS application `cyf-ecommerce-api` that you created for last week's homework for the Database 2 class. -- If you don't have it already, add a new GET endpoint `/products` to load all the product names along with their prices and supplier names. +*- If you don't have it already, add a new GET endpoint `/products` to load all the product names along with their prices and supplier names. -- Update the previous GET endpoint `/products` to filter the list of products by name using a query parameter, for example `/products?name=Cup`. This endpoint should still work even if you don't use the `name` query parameter! +*- Update the previous GET endpoint `/products` to filter the list of products by name using a query parameter, for example `/products?name=Cup`. This endpoint should still work even if you don't use the `name` query parameter! -- Add a new GET endpoint `/customers/:customerId` to load a single customer by ID. +*- Add a new GET endpoint `/customers/:customerId` to load a single customer by ID. -- Add a new POST endpoint `/customers` to create a new customer with name, address, city and country. +*- Add a new POST endpoint `/customers` to create a new customer with name, address, city and country. -- Add a new POST endpoint `/products` to create a new product. +*- Add a new POST endpoint `/products` to create a new product. - Add a new POST endpoint `/availability` to create a new product availability (with a price and a supplier id). Check that the price is a positive integer and that both the product and supplier ID's exist in the database, otherwise return an error. -- Add a new POST endpoint `/customers/:customerId/orders` to create a new order (including an order date, and an order reference) for a customer. Check that the customerId corresponds to an existing customer or return an error. +*- Add a new POST endpoint `/customers/:customerId/orders` to create a new order (including an order date, and an order reference) for a customer. Check that the customerId corresponds to an existing customer or return an error. -- Add a new PUT endpoint `/customers/:customerId` to update an existing customer (name, address, city and country). +*- Add a new PUT endpoint `/customers/:customerId` to update an existing customer (name, address, city and country). - Add a new DELETE endpoint `/orders/:orderId` to delete an existing order along with all the associated order items. diff --git a/week-3/mandatory/2-api/uri2sql.js b/week-3/mandatory/2-api/uri2sql.js new file mode 100644 index 00000000..fe8b2a28 --- /dev/null +++ b/week-3/mandatory/2-api/uri2sql.js @@ -0,0 +1,257 @@ +//Module uri2sql.js +// +// The purpose of this module is to take an HTTP query string and +// convert it into a piece of SQL comprising a WHERE clause along +// with optional ORDER BY and other clauses. +// +// Once converted the SQL piece can be appended to an SQL query +// to filter the result set according to client requiremnents. +// +// Author: Keith Bremer +// License: LGPL-3.0 (GNU Lesser General Public License, version 3) +// Release 0.1 (Alpha) +// +var subs = "$"; // set for PostgreSQL, use : for Oracle + +function setsubs(symbol) { + // + // Function to change the bind variable placeholder symbol + // + subs = symbol; +} + +function uri2sql(query, columns) { + // + // Function to generate an SQL predicate (WHERE clause) from an HTTP query + // as supplied as part of the URI for an endpoint. + // e.g. http://localhost:3000/customers?name[like]=%Jones&city=Paris + // should produce: + // WHERE name LIKE '%Jones' AND city = 'Paris' + // + // Syntax of query (following ? in the URI): + // column=value + // column[operator]=value + // column[operator]=value:value:value... + // column[-operator]=value... + // + // column = name of the column in the table + // operator = text form of a comparison operator from the following: + // '=' '!=' '<' '<=' '>' '>=' 'in' 'like' 'tween' 'is' + // the operator may be negated by prefixing with - (minus) + // value = a string or number or the text NULL (in upper case) + // for operators 'twixt' and 'in' there may be a colon-separated + // list of values (two for 'tween' and one or more for 'in') + // + // The query argument to this function is the result of using body-parser + // to convert the URI query into JSON, so the above example would be supplied + // as: + // { name: {like: "%Jones"}, city: "Paris" } + // + + function getValue(param) { + // + // Get the value part of an object's attribute in a form suitable for SQL + // (Only NULL, strings (including date/time) and numbers are recognised at present) + // String values are minimally sanitised (refusing any that contain ' or ;) + // + if (param.length == 0) { + throw "ERROR: missing value parameter" // no value supplied so error + }; + + var n = new Number(param); // try a number? + if (n instanceof Number && !isNaN(n)) { // warning - double negative! + return n; // it is a number so return that + } else if (param == "NULL") { + return param // it is NULL + } else if (typeof(param) == "string") { // if it's a string then... + if (param.includes("'") || param.includes(";")) { // sanitise it... + throw "ERROR: invalid value in query: " + param; // and reject if invalid + } + return param; // just return the value + } + } // end of getValue function + + function checkCol(colName) { + // + // Check column name is valid in the array of column names provided. + // If no column names are supplied then ignore these checks, otherwise + // throw an exception if not found. + // + if (columns.length > 0) { // if the column array is provided check the name + if (columns.find(o => o.column_name == colName) == undefined) { + throw "ERROR: invalid column name: " + colName; + }; + } + } + + // + // The valid operators allowed in http queries (within [...] after the column name) + // + var operators = ['eq', 'ne', 'lt', 'gte', 'gt', 'lte', 'in', 'like', 'tween', 'is']; + // + // The SQL operators that correspond to the http query operators above, as two arrays + // within an array. The first [0] is the normal translation, the second [1] is the + // negated translation + // + var sqlop = [ + // normal translation... + ['= ', '!= ', '< ', '>= ', '> ', '<= ', 'IN (', 'LIKE ', 'BETWEEN ', 'IS '], + // negated translation... + ['!= ', '= ', '>= ', '< ', '<= ', '> ', 'NOT IN (', 'NOT LIKE ', 'NOT BETWEEN ', 'IS NOT '] + ]; + + var sql = ""; // initial sql string + var sort = ""; // initial order by string + var valueArray = []; // initial value array + var substitution = 1; // number for $n bind placeholder + + // + // Loop through each query parameter (separated in URI by &) and translate + // into corresponding SQL predicate syntax. Each new predicate is appended + // to the previous ones with AND. + // + for (var col in query) { // for each query parameter (introduced by column name) + + var typ = typeof(query[col]); // get the type of the value + + if (col.charAt(0) == "$") { // $ = special symbol, not a columns name + // + // Special symbol processing: + // + if (col == "$sort") { + // + // Syntax: + // $sort=col sort results by column + // $sort=col1:col2:... sort results by col1, col2, etc. + // $sort=-col1:col2 sort results by col1 desc, col2 asc + // + if (typ != "string") { + throw "ERROR: incorrect $sort parameter value: " + query[col]; + } + // + // get an array of sort columns & process each... + // + var sortCols = getValue(query[col]).split(":"); + + sortCols.forEach( (sortCol) => { + let sortDir = ""; // sort direction: initially default + if (sortCol.charAt(0) == "-") { // is sort negated? + sortDir = " DESC"; // if so then direction = DESC + sortCol = sortCol.substring(1); // remove leading "-" + } + checkCol(sortCol); // check the column name + // + // Construct the ORDER BY clause + // + if (sort == "") { + sort = "ORDER BY "; // start with ORDER BY + } else { + sort = sort + ", "; // add more after comma + } + sort = sort + sortCol + sortDir; // add column & direction + }); // end of sortCols.foreach... + } // end of 'if (col == "$sort") ...' + } else { + // + // Filter condition processing + // + checkCol(col); + + var negate = 0; // initially assume un-negated operator + // + // Handle the appending of code to the sql and deal with the case + // of no operator (assumes 'eq' be default) + // + if (sql == "") { + sql = sql + "WHERE "; // this must be the 1st parameter + } else { + sql = sql + "AND "; // if not 1st parameter add AND keyword + } + sql = sql + col + " "; // append column name + + // + // If the datatype of the value isn't an object then treat it as a + // simple value. Add a bind variable to the sql (e.g. $3) and push + // the value onto the array. + // + if (typ != "object") { // not an object so no operator provided + sql = sql + "= " + subs + (substitution++) + " "; // so assume eq (=) + val = getValue(query[col]); // get the parameter value + valueArray.push(val); // and append it to the array + } else { + // + // Process the parameter value as an object of the form: + // { operator: value } + // with separate code for 'is', 'in' and 'tween' operators + // + for (var op in query[col]) { // else traverse sub-object + o = op; // copy the operator so it can be isolated from - prefix + if (op.charAt(0) == "-") { // if - prefix then + negate = 1; // set negate flag + o = op.substring(1); // strip prefix + } + // + // Select the corresponding SQL operator for the one from the URI + // + if (operators.includes(o)) { + sql = sql + sqlop[negate][operators.indexOf(o)]; + } else { + sql = sql + "= "; // default to = if not found + } + if (o == "is") { + // + // Code for the 'is' operator + // + sql = sql + "NULL "; // use literal NULL & ignore value + } else if (o == "tween") { + // + // Code for the 'tween' operator + // + sql = sql + subs + (substitution++) + " AND " + subs + (substitution++) + " "; + val = getValue(query[col][op]); + var varr = val.split(":"); + if (varr[0] != undefined) { + valueArray.push(varr[0]); + if (varr[1] != undefined) { + valueArray.push(varr[1]) + } else { + throw "ERROR: missing value parameter"; + } + } else { + throw "ERROR: missing value parameter"; + }; + } else if (o == "in") { + // + // Code for the 'in' operator + // + let first = true; + val = getValue(query[col][op]); + val.split(":").forEach(function(value) { + sql = sql + (first?"":", ") + subs + (substitution++); + valueArray.push(value); + first = false; + }); + sql = sql + ") "; + } else { + // + // Code for all other operators (=, !=, <, >, etc.) + // + sql = sql + subs + (substitution++) + " "; + val = getValue(query[col][op]); + valueArray.push(val); + } + } + }; + } + } + // + // Append any sort clause then return + // + sql = sql + sort; + return {'sql': sql, 'values': valueArray}; + } + + module.exports = { + uri2sql, + setsubs + } \ No newline at end of file