From 16f4dd737aabc5524bedf392b2a702e19d7a9f0f Mon Sep 17 00:00:00 2001 From: Soustab Haldar Date: Sun, 24 Jul 2022 00:11:36 +0530 Subject: [PATCH] all tasks done --- .gitignore | 4 + app.js | 37 ++++++-- config/keys.js | 3 + controllers/auth.js | 44 ++++++++- controllers/store.js | 181 +++++++++++++++++++++++++++++++------ middleware/index.js | 15 ++- models/book.js | 21 ++++- models/bookCopy.js | 33 +++++-- models/user.js | 23 ++--- package-lock.json | 14 +++ package.json | 1 + views/book_detail.ejs | 15 ++- views/book_list.ejs | 4 +- views/index.ejs | 2 +- views/loaned_books.ejs | 82 +++++++++++------ views/login.ejs | 22 +++++ views/partials/sidebar.ejs | 1 + views/register.ejs | 34 +++++++ 18 files changed, 436 insertions(+), 100 deletions(-) create mode 100644 config/keys.js diff --git a/.gitignore b/.gitignore index 5ce804c..426bf25 100644 --- a/.gitignore +++ b/.gitignore @@ -117,3 +117,7 @@ dist .yarn/install-state.gz .pnp.* {"mode":"full","isActive":false} + +#env +.env +.env.test diff --git a/app.js b/app.js index dd1196e..a9f26aa 100644 --- a/app.js +++ b/app.js @@ -6,11 +6,13 @@ var auth = require("./controllers/auth"); var store = require("./controllers/store"); var User = require("./models/user"); var localStrategy = require("passport-local"); + //importing the middleware object to use its functions var middleware = require("./middleware"); //no need of writing index.js as directory always calls index.js by default var port = process.env.PORT || 3000; app.use(express.static("public")); +app.use(express.static("favicon.ico")); /* CONFIGURE WITH PASSPORT */ app.use( @@ -20,11 +22,16 @@ app.use( saveUninitialized: false, }) ); +passport.serializeUser(function (user, done) { + done(null, user); +}); +passport.deserializeUser(function (user, done) { + done(null, user); +}); app.use(passport.initialize()); //middleware that initialises Passport. app.use(passport.session()); -passport.use(new localStrategy(User.authenticate())); //used to authenticate User model with passport -passport.serializeUser(User.serializeUser()); //used to serialize the user for the session +passport.use(new localStrategy(User.authenticate())); //used to authenticate User model with passportpassport.serializeUser(User.serializeUser()); //used to serialize the user for the session passport.deserializeUser(User.deserializeUser()); // used to deserialize the user app.use(express.urlencoded({ extended: true })); //parses incoming url encoded data from forms to json objects @@ -37,6 +44,13 @@ app.use(function (req, res, next) { }); /* TODO: CONNECT MONGOOSE WITH OUR MONGO DB */ +const db = require("./config/keys").MONGOURL; +mongoose.set("useCreateIndex", true); +mongoose.set("useUnifiedTopology", true); +mongoose + .connect(db, { useNewUrlParser: true }) + .then(() => console.log("Database connected successfully")) + .catch((err) => console.log(err)); app.get("/", (req, res) => { res.render("index", { title: "Library" }); @@ -52,13 +66,19 @@ app.get("/books", store.getAllBooks); app.get("/book/:id", store.getBook); -app.get("/books/loaned", -//TODO: call a function from middleware object to check if logged in (use the middleware object imported) - store.getLoanedBooks); +app.get( + "/books/loaned", + //TODO: call a function from middleware object to check if logged in (use the middleware object imported) + middleware.isLoggedIn, + store.getLoanedBooks +); -app.post("/books/issue", -//TODO: call a function from middleware object to check if logged in (use the middleware object imported) -store.issueBook); +app.post( + "/books/issue", + //TODO: call a function from middleware object to check if logged in (use the middleware object imported) + middleware.isLoggedIn, + store.issueBook +); app.post("/books/search-book", store.searchBooks); @@ -69,6 +89,7 @@ TODO: Your task is to complete below controllers in controllers/auth.js If you need to add any new route add it here and define its controller controllers folder. */ +app.post("/books/return", middleware.isLoggedIn, store.returnBook); app.get("/login", auth.getLogin); diff --git a/config/keys.js b/config/keys.js new file mode 100644 index 0000000..c8c81b5 --- /dev/null +++ b/config/keys.js @@ -0,0 +1,3 @@ +module.exports = { + MONGOURL: "mongodb://localhost:27017", +}; diff --git a/controllers/auth.js b/controllers/auth.js index 4d35ef9..79b1b25 100644 --- a/controllers/auth.js +++ b/controllers/auth.js @@ -1,23 +1,65 @@ +const { application } = require("express"); +const passport = require("passport"); +const Book = require("../models/book"); +const User = require("../models/user"); + var getLogin = (req, res) => { //TODO: render login page + if (!req.isAuthenticated()) { + res.render("login", { title: "Login" }); + } else { + res.status(200).redirect("/"); + } }; var postLogin = (req, res) => { // TODO: authenticate using passport //On successful authentication, redirect to next page + const user = new User({ + username: req.body.username, + password: req.body.password, + }); + + req.login(user, function (err) { + if (err) { + res.status(500).render(`Error: ${err}`); + } else { + passport.authenticate("local")(req, res, function () { + res.status(200).redirect("/"); + }); + } + }); }; var logout = (req, res) => { // TODO: write code to logout user and redirect back to the page + try { + req.logout(); + res.status(200).redirect("/login"); + } catch (err) { + res.status(500).send("Sorry, some error occurred!"); + } }; var getRegister = (req, res) => { - // TODO: render register page + // TODO: render register pages + res.status(200).render("register", { title: "Register" }); }; var postRegister = (req, res) => { // TODO: Register user to User db using passport //On successful authentication, redirect to next page + try { + User.register({ username: req.body.username }, req.body.password, (err) => { + if (!err) { + passport.authenticate("local")(req, res, () => { + res.status(200).redirect("/"); + }); + } + }); + } catch (err) { + res.status(500).send("Internal Server Error"); + } }; module.exports = { diff --git a/controllers/store.js b/controllers/store.js index 82c72eb..9f53062 100644 --- a/controllers/store.js +++ b/controllers/store.js @@ -1,34 +1,161 @@ -var getAllBooks = (req, res) => { - //TODO: access all books from the book model and render book list page - res.render("book_list", { books: [], title: "Books | Library" }); -} +const Book = require("../models/book"); +const User = require("../models/user"); +const Bookcopy = require("../models/bookCopy"); +const { default: mongoose } = require("mongoose"); -var getBook = (req, res) => { - //TODO: access the book with a given id and render book detail page -} +var getAllBooks = async (req, res) => { + //TODO: access all books from the book model and render book list page + try { + const allBooks = await Book.find(); + res + .status(200) + .render("book_list", { books: allBooks, title: "Library Books" }); + } catch (err) { + res.status(500).render("Sorry, some error occurred!"); + } +}; -var getLoanedBooks = (req, res) => { +var getBook = async (req, res) => { + //TODO: access the book with a given id and render book detail page + const returnBookId = req.params.id; + try { + const book = await Book.findById(returnBookId); + if (book) { + res + .status(200) + .render("book_detail", { book: book, title: "Books | " + book.title }); + } else res.status(404).send("No such book"); + } catch (err) { + res.status(500).send("Sorry, some error occurred!"); + } +}; - //TODO: access the books loaned for this user and render loaned books page -} +var getLoanedBooks = (req, res) => { + //TODO: access the books loaned for this user and render loaned books page + User.findOne({ username: req.user.username }, function (err, user) { + if (!err) { + if (user) { + Bookcopy.find({ borrower: user._id }) + .populate("book") + .populate("borrower") + .exec((err, userBooks) => { + if (!err) { + if (userBooks) { + res.status(200).render("loaned_books", { + books: userBooks, + title: "Books Loaned", + }); + } + } + }); + } + } + }); +}; var issueBook = (req, res) => { - - // TODO: Extract necessary book details from request - // return with appropriate status - // Optionally redirect to page or display on same -} - -var searchBooks = (req, res) => { - // TODO: extract search details - // query book model on these details - // render page with the above details -} + // TODO: Extract necessary book details from request + // return with appropriate status + // Optionally redirect to page or display on same + Book.findOne({ _id: req.body.bid }, function (err, bookFound) { + if (!err) { + if (bookFound) { + if (bookFound.available_copies) { + var quantity = bookFound.available_copies - 1; + Book.updateOne( + { _id: req.body.bid }, + { available_copies: quantity }, + (err) => { + if (err) res.status(500).render(`Error: ${err}`); + } + ); + + let isAvailable = quantity ? true : false; + const issuedBook = new Bookcopy({ + book: bookFound._id, + status: isAvailable, + borrower: req.user._id, + }); + issuedBook.save(); + User.findOneAndUpdate( + { username: req.user.username }, + { $push: { loaned_books: issuedBook } }, + { safe: true, upsert: true, new: true }, + (err, found) => { + if (err) res.status(500).render(`Error: ${err}`); + } + ); + + Bookcopy.update( + { book: bookFound }, + { status: isAvailable }, + (err) => { + res.status(500).render(`Error: ${err}`); + } + ); + res.status(200).redirect("/books/loaned"); + } else { + res.send("No copy of the book available to loan!"); + } + } + } + }); +}; + +var searchBooks = async (req, res) => { + // TODO: extract search details + // query book model on these details + // render page with the above details + + const matchingBooks = await Book.find( + // { title: req.body.title, author: req.body.author, genre: req.body.genre }, + { + title: { $regex: `.*${req.body.title}.*`, $options: "i" }, + author: { $regex: `.*${req.body.author}.*`, $options: "i" }, + genre: { $regex: `.*${req.body.genre}.*`, $options: "i" }, + } + ); + + res.render("book_list", { books: matchingBooks }); +}; + +var returnBook = (req, res) => { + const returnBookId = req.body.bid; + + Book.findById(returnBookId).then((book) => { + if (book) { + new_available_copies = book.available_copies + 1; + Book.updateOne( + { _id: returnBookId }, + { available_copies: new_available_copies }, + (err) => { + if (err) res.status(500).render(`Error: ${err}`); + } + ); + Bookcopy.findOneAndDelete({ book: book }).then((foundBook) => { + User.findOneAndUpdate( + { username: req.user.username }, + { $pull: { loaned_books: foundBook._id } }, + (err) => { + if (err) res.status(500).render(`Error: ${err}`); + } + ); + }); + Bookcopy.updateOne({ book: book }, { status: true }, (err) => { + res.status(500).render(`Error: ${err}`); + }); + res.redirect("/books/loaned"); + } else { + console.log("Sorry, some error occurred!"); + } + }); +}; module.exports = { - getAllBooks, - getBook, - getLoanedBooks, - issueBook, - searchBooks -} \ No newline at end of file + getAllBooks, + getBook, + getLoanedBooks, + issueBook, + returnBook, + searchBooks, +}; diff --git a/middleware/index.js b/middleware/index.js index 0ad6b1c..409a7f5 100644 --- a/middleware/index.js +++ b/middleware/index.js @@ -1,11 +1,16 @@ -var middlewareObj={}; +var middlewareObj = {}; //middleware object to check if logged in -middlewareObj.isLoggedIn=function(req,res,next){ - /* +middlewareObj.isLoggedIn = function (req, res, next) { + /* TODO: Write function to check if user is logged in. If user is logged in: Redirect to next page else, redirect to login page */ - } + if (!req.isAuthenticated()) { + res.redirect("/login"); + } else { + next(); //process function //check! + } +}; - module.exports=middlewareObj; \ No newline at end of file +module.exports = middlewareObj; diff --git a/models/book.js b/models/book.js index 7deabcd..fc34b60 100644 --- a/models/book.js +++ b/models/book.js @@ -1,8 +1,19 @@ -var mongoose=require("mongoose"); +var mongoose = require("mongoose"); //DEFINING THE BOOK MODEL -var bookSchema=new mongoose.Schema({ - /*TODO: DEFINE the following attributes- +var bookSchema = new mongoose.Schema({ + /*TODO: DEFINE the following attributes- title, genre, author, description, rating (out of 5), mrp, available_copies(instances). */ -}) -module.exports=mongoose.model("Book",bookSchema); \ No newline at end of file + title: String, + genre: String, + author: String, + description: String, + rating: { + type: Number, + min: 0, + max: 5, + }, + mrp: Number, + available_copies: Number, +}); +module.exports = mongoose.model("Book", bookSchema); diff --git a/models/bookCopy.js b/models/bookCopy.js index 45d7ea5..06050dd 100644 --- a/models/bookCopy.js +++ b/models/bookCopy.js @@ -1,10 +1,25 @@ -var mongoose=require("mongoose"); +var mongoose = require("mongoose"); +const User = require("./user"); +// //DEFINING THE BOOK COPIES MODEL +// var bookCopySchema=new mongoose.Schema({ +// //TODO: DEFINE the following attributes- +// book: //embed reference to id of book of which its a copy +// status: //TRUE IF AVAILABLE TO BE ISSUED, ELSE FALSE +// borrow_data: //date when book was borrowed +// borrower: //embed reference to id of user who has borrowed it +// }) +// module.exports=mongoose.model("Bookcopy",bookCopySchema); + //DEFINING THE BOOK COPIES MODEL -var bookCopySchema=new mongoose.Schema({ -//TODO: DEFINE the following attributes- - book: //embed reference to id of book of which its a copy - status: //TRUE IF AVAILABLE TO BE ISSUED, ELSE FALSE - borrow_data: //date when book was borrowed - borrower: //embed reference to id of user who has borrowed it -}) -module.exports=mongoose.model("Bookcopy",bookCopySchema); \ No newline at end of file +var bookCopySchema = new mongoose.Schema({ + //TODO: DEFINE the following attributes- + //book: //embed reference to id of book of which its a copy + //status: //TRUE IF AVAILABLE TO BE ISSUED, ELSE FALSE + //borrow_data: //date when book was borrowed + //borrower: //embed reference to id of user who has borrowed it + book: { type: mongoose.Schema.Types.ObjectId, ref: "Book" }, + status: Boolean, + borrow_date: { type: Date, default: () => Date.now() }, + borrower: { type: mongoose.Schema.Types.ObjectId, ref: "User" }, +}); +module.exports = mongoose.model("Bookcopy", bookCopySchema); diff --git a/models/user.js b/models/user.js index 27a1d59..4631789 100644 --- a/models/user.js +++ b/models/user.js @@ -1,14 +1,15 @@ -var mongoose=require("mongoose"); -var passportLocal=require("passport-local-mongoose"); +var mongoose = require("mongoose"); +var passportLocal = require("passport-local-mongoose"); //DEFINING THE USER MODEL -var userSchema=new mongoose.Schema({ +var userSchema = new mongoose.Schema({ + //TODO: DEFINE USERNAME AND PASSSWORD ATTRIBUTES + username: String, + password: String, - //TODO: DEFINE USERNAME AND PASSSWORD ATTRIBUTES - - - loaned_books:[ - //TODO: embed reference to id's of book copies loaned by this particular user in this array - ] -}) + loaned_books: [ + //TODO: embed reference to id's of book copies loaned by this particular user in this array + { type: mongoose.Schema.Types.ObjectId, ref: "Bookcopy" }, + ], +}); userSchema.plugin(passportLocal); -module.exports=mongoose.model("User",userSchema); \ No newline at end of file +module.exports = mongoose.model("User", userSchema); diff --git a/package-lock.json b/package-lock.json index 6c0a37f..625a63d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "dotenv": "^16.0.1", "ejs": "^3.1.6", "express": "^4.17.1", "express-session": "^1.17.2", @@ -331,6 +332,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dotenv": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", + "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1733,6 +1742,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "dotenv": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", + "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", diff --git a/package.json b/package.json index b4408ab..845a094 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "author": "", "license": "ISC", "dependencies": { + "dotenv": "^16.0.1", "ejs": "^3.1.6", "express": "^4.17.1", "express-session": "^1.17.2", diff --git a/views/book_detail.ejs b/views/book_detail.ejs index 40049c1..2980eed 100644 --- a/views/book_detail.ejs +++ b/views/book_detail.ejs @@ -1,7 +1,7 @@ <%- include ("./partials/header.ejs") %> - +
<%- include ("./partials/sidebar.ejs") %> @@ -20,13 +20,24 @@
MRP:
Rs. <%= book.mrp %>
Available Copies:
-
<%= num_available %>
+
<%= book.available_copies %>
+ <% if(book.available_copies > 0){ %> + <% } else{ %> + + <% } %>
diff --git a/views/book_list.ejs b/views/book_list.ejs index 6f15b75..742d187 100644 --- a/views/book_list.ejs +++ b/views/book_list.ejs @@ -1,7 +1,7 @@ - <%- include ("./partials/header.ejs") %> + <%- include ("./partials/header.ejs", {title: 'List of Books'}) %> - +
<%- include ("./partials/sidebar.ejs") %> diff --git a/views/index.ejs b/views/index.ejs index c73be1e..d2dafbe 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -1,7 +1,7 @@ <%- include ("./partials/header.ejs") %> - + <%- include ("./partials/sidebar.ejs") %>

Welcome to the library. This is your homepage.

diff --git a/views/loaned_books.ejs b/views/loaned_books.ejs index ad20b79..d330ca3 100644 --- a/views/loaned_books.ejs +++ b/views/loaned_books.ejs @@ -1,33 +1,57 @@ - <%- include ("./partials/header.ejs") %> - -

Loaned Books list

+ <%- include ("./partials/header.ejs", {title: 'Loaned Books'}) %> + +
+
+ <%- include ("./partials/sidebar.ejs") %> +
+

Loaned Books list

+ + + + + + + + + + + + + <% books.forEach(function(copy,i){%> + + + + + + + + + <%})%> + +
#TitleAuthorGenreBorrow DateReturn
<%= i %> + <%= copy.book.title %> + <%= copy.book.author %><%= copy.book.genre %><%= copy.borrow_date %> + +
+ - - - - - - - - - - - - - <% books.forEach(function(copy,i){%> - - - - - - - - - <%})%> - -
#TitleAuthorGenreBorrow DateReturn Buttons
<%= i %><%= copy.book.title %><%= copy.book.author %><%= copy.book.genre %><%= copy.borrow_date %> - -
+ +
+
+
+
+
diff --git a/views/login.ejs b/views/login.ejs index e69de29..8885dc1 100644 --- a/views/login.ejs +++ b/views/login.ejs @@ -0,0 +1,22 @@ + + <%- include ("./partials/header.ejs") %> + + +
<%- include ("./partials/sidebar.ejs") %>
+

Login

+
+ + +
+ + +
+ +
+ + diff --git a/views/partials/sidebar.ejs b/views/partials/sidebar.ejs index 07ca39c..eb5cee7 100644 --- a/views/partials/sidebar.ejs +++ b/views/partials/sidebar.ejs @@ -11,6 +11,7 @@
  • Logout
  • <% } else { %>
  • Login
  • +
  • Register
  • <% } %>
    diff --git a/views/register.ejs b/views/register.ejs index e69de29..e52ebbf 100644 --- a/views/register.ejs +++ b/views/register.ejs @@ -0,0 +1,34 @@ + + <%- include ("./partials/header.ejs") %> + +
    <%- include ("./partials/sidebar.ejs") %>
    +

    Register:

    +
    + + +
    + + + +
    + + +
    + + +
    + +
    +
    Already have an account? Login
    + +