├── .gitignore
├── README.md
├── authenticate.js
├── client
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── setupProxy.js
├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── baseUrl.js
│ ├── components
│ │ ├── AddBookComponent.js
│ │ ├── BookDetailComponent.js
│ │ ├── BooksComponent.js
│ │ ├── FooterComponent.js
│ │ ├── HeaderComponent.js
│ │ ├── HistoryComponent.js
│ │ ├── HomeComponent.js
│ │ ├── IssueComponent.js
│ │ ├── LoadingComponent.js
│ │ ├── LogComponent.js
│ │ ├── MainComponent.js
│ │ ├── ProfileComponent.js
│ │ ├── ReturnComponent.js
│ │ ├── SearchComponent.js
│ │ ├── StatsComponent.js
│ │ ├── UserDetailComponent.js
│ │ └── UserListComponent.js
│ ├── images
│ │ └── walllpaper.jpg
│ ├── index.css
│ ├── index.js
│ ├── redux
│ │ ├── ActionCreators.js
│ │ ├── ActionTypes.js
│ │ ├── auth.js
│ │ ├── books.js
│ │ ├── issues.js
│ │ ├── store.js
│ │ └── users.js
│ └── serviceWorker.js
└── yarn.lock
├── config
└── keys.js
├── models
├── books.js
├── issues.js
└── users.js
├── package-lock.json
├── package.json
├── routes
├── api
│ ├── bookRouter.js
│ ├── issueRouter.js
│ └── userRouter.js
└── cors.js
└── server.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | backup
3 | config
4 | .env
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Library Management Web App
2 |
3 | A web app for the management of books, users and the Issue and Return of Books in a library.
4 |
5 | ## User Permissions
6 |
7 | ### Student
8 |
9 | A student can
10 |
11 | * register himself on the app
12 | * view and edit his profile
13 | * change his password
14 | * search for books and view availabilty
15 | * view his issue history
16 |
17 | ### Admin
18 |
19 | An admin can
20 |
21 | * view and edit his profile
22 | * search for books and view availability
23 | * view, Edit or Delete existing books
24 | * add new books
25 | * issue a book to a student
26 | * return a book issued earlier
27 | * view all stats of the library
28 | * view issue log and the profile of all the students
29 | * view the profile of all admins
30 |
31 | ## A note to the viewers
32 |
33 | 1. You can try logging in as an **admin** by entering the following credentials:
34 |
35 | * **username**: *Director*
36 | * **password**: *123pass*
37 |
38 | 2. You can also register yourself as a student and then login to view the options available to a student.
39 |
40 | ## View live App
41 |
42 | Hosted at https://lib-manage.herokuapp.com/
43 |
44 | ## Tech Stack Used
45 |
46 | ### The MERN Stack
47 |
48 | * [MongoDB](https://docs.mongodb.com/) - Document database - to store data as JSON
49 | * [Express.js](https://devdocs.io/express/) - Back-end web application framework running on top of Node.js
50 | * [React](https://reactjs.org/docs/) - Front-end web app framework used
51 | * [Node.js](https://nodejs.org/en/docs/) - JavaScript runtime environment
52 |
53 | ### Middleware
54 |
55 | * [Redux](https://redux.js.org/basics/usage-with-react) - For flux architecture, and fetching rapidly data
56 | * [Mongoose](https://mongoosejs.com/docs/guide.html) - ODM for MongoDB
57 |
58 | ## Steps followed to setup the project
59 |
60 | ### Setting up server and database
61 |
62 | 1. Initialise a package.json file by entering the following command in terminal, after getting into the project directory :
63 |
64 | ```(bash)
65 | npm init
66 | ```
67 |
68 | 2. Install npm packages required for backend side :
69 |
70 | ```(bash)
71 | npm i express body-parser mongoose concurrently
72 | npm i -D nodemon
73 | ```
74 |
75 | 3. Create a file server.js to make use of the express packages
76 |
77 | 4. Modify the package.json by adding the following scripts to it :
78 |
79 | ```(JSON)
80 | "start": "node server.js",
81 | "server": "nodemon server.js",
82 | ```
83 |
84 | 5. Create an account on MongoDB cloud Atlas, thereafter, creating a database on it and get your MongoURI exported from a file keys.js in a folder config
85 |
86 | 6. Modify server.js to get connected to the database, using the MongoURI and also add the following lines at the end of server.js :
87 |
88 | ```(JavaScript)
89 | const port = process.env.PORT || 5000;
90 | app.listen(port, ()=> console.log(`Server started running on port ${port}`));
91 | ```
92 |
93 | 7. Type the following command to get your server running on your localhost and ensure hot-reloading, when the server-side code is modified :
94 |
95 | ```(bash)
96 | npm run server
97 | ```
98 |
99 | 8. Make Schemas for various collections to be stored in database and export them from a folder models and the REST APIs for various routes in the folder routes. Change the server.js accordingly to make the use of these REST APIs. Ensure that the APIs are working correctly, by making requests using POSTMAN
100 |
101 | 9. Add JWT token based authentication and 'cors' module and use them in server.js.
102 |
103 | ### Setting up the React client
104 |
105 | 1. Create a folder 'client' in the project directory. Ensure that you have create-react-app CLI installed. Enter the following commands in terminal :
106 |
107 | ```(bash)
108 | cd client
109 | create-react-app .
110 | cd ..
111 | ```
112 |
113 | 2. In the package.json of the server, add the following scripts :
114 |
115 | ```(JSON)
116 | "client-install": "npm install --prefix client",
117 | "client": "npm start --prefix client",
118 | "dev": "concurrently \"npm run server\" \"npm run client\" ",
119 | ```
120 |
121 | 3. Remove all the additional default setup from client folder like logo, index.css, etc. Then, configure the client to make use of *bootstrap* and *reactstrap* to make the app responsive by using following commands in terminal :
122 |
123 | ```(bash)
124 | cd client
125 | npm i bootstrap reactstrap react-popper font-awesome bootstrap-social
126 | ```
127 |
128 | Add the following line to index.js :
129 |
130 | ```(JavaScript)
131 | import 'bootstrap/dist/css/bootstrap.min.css
132 | ```
133 |
134 | 4. Install Redux for maintaining the state :
135 |
136 | ```(Terminal)
137 | npm i redux react-redux redux-thunk
138 | ```
139 |
140 | 5. Create a redux store, the various actions and reducers required in a folder named redux. Make corresponding changes in the React components to map the actions and state to props
141 |
142 | ### Deployment
143 |
144 | 1. Add the following lines to server.js :
145 |
146 | ```(JavaScript)
147 | // Serve static assets if in production
148 | if (process.env.NODE_ENV === 'production') {
149 | // Set static folder
150 | app.use(express.static('client/build'));
151 |
152 | app.get('*', (req, res) => {
153 | res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
154 | });
155 | }
156 | ```
157 |
158 | 2. Add the following script to the package.json of server
159 |
160 | ```(JSON)
161 | "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client"
162 | ```
163 |
164 | 3. Install Heroku CLI and make sure you have intialised a git repository in the project directory. Enter the following commands in the terminal :
165 |
166 | ```(bash)
167 | heroku login
168 | heroku create
169 | git add .
170 | git commit -am "Deployed to Heroku"
171 | git push heroku master
172 | ```
173 |
174 | 4. Open your heroku account and click on **Open App** option in the dashboard.
--------------------------------------------------------------------------------
/authenticate.js:
--------------------------------------------------------------------------------
1 | var passport = require('passport');
2 | var LocalStrategy = require('passport-local').Strategy;
3 | var User = require('./models/users');
4 | var JwtStrategy = require('passport-jwt').Strategy;
5 | var ExtractJwt = require('passport-jwt').ExtractJwt;
6 | var jwt = require('jsonwebtoken'); // used to create, sign, and verify tokens
7 |
8 | var config = require('./config/keys.js');
9 |
10 | exports.local=passport.use(new LocalStrategy(User.authenticate()));
11 |
12 | // For sessions
13 | passport.serializeUser(User.serializeUser());
14 | passport.deserializeUser(User.deserializeUser());
15 |
16 | exports.getToken = function(user) {
17 | return jwt.sign(user, config.secretKey,
18 | {expiresIn: 3600});
19 | };
20 |
21 | var opts = {};
22 | opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
23 | opts.secretOrKey = config.secretKey;
24 |
25 | exports.jwtPassport = passport.use(new JwtStrategy(opts,
26 | (jwt_payload, done) => {
27 | console.log("JWT payload: ", jwt_payload);
28 | User.findOne({_id: jwt_payload._id}, (err, user) => {
29 | if (err) {
30 | return done(err, false);
31 | }
32 | else if (user) {
33 | return done(null, user);
34 | }
35 | else {
36 | return done(null, false);
37 | }
38 | });
39 | }));
40 |
41 | exports.verifyUser = passport.authenticate('jwt', {session: false});
42 |
43 | exports.verifyAdmin = function (req, res, next){
44 | if(req.user.admin){
45 | next();
46 | }else{
47 | var err = new Error('You are not authorized!');
48 | err.status = 403;
49 | return next(err);
50 | }
51 | };
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "bootstrap": "^4.3.1",
7 | "font-awesome": "^4.7.0",
8 | "http-proxy-middleware": "^2.0.1",
9 | "lodash": "^4.17.21",
10 | "lodash.template": "^4.5.0",
11 | "react": "^17.0.2",
12 | "react-dom": "^17.0.2",
13 | "react-popper": "^2.2.5",
14 | "react-redux": "^7.2.4",
15 | "react-redux-form": "^1.16.14",
16 | "react-router-dom": "^5.2.1",
17 | "react-scripts": "^4.0.3",
18 | "reactstrap": "^8.9.0",
19 | "redux": "^4.1.1",
20 | "redux-thunk": "^2.3.0"
21 | },
22 | "scripts": {
23 | "start": "react-scripts start",
24 | "build": "react-scripts build",
25 | "test": "react-scripts test",
26 | "eject": "react-scripts eject"
27 | },
28 | "eslintConfig": {
29 | "extends": "react-app"
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cjchirag7/lib_management/6b1aba891aee01d92749b51e0cb9002922b6b4e7/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
23 | Central Library
24 |
25 |
26 | You need to enable JavaScript to run this app.
27 |
28 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/client/setupProxy.js:
--------------------------------------------------------------------------------
1 | const proxy = require('http-proxy-middleware');
2 | module.exports = function(app) {
3 | app.use(proxy('/api',
4 | { target: 'http://localhost:5000/' ,
5 | secure: false}
6 | ));
7 | }
--------------------------------------------------------------------------------
/client/src/App.css:
--------------------------------------------------------------------------------
1 | .row-header{
2 | margin:0px auto;
3 | padding:0px auto;
4 | }
5 |
6 | body{
7 | background-color: rgba(1, 11, 20, 0.007);
8 | width: 100vw;
9 | }
10 |
11 | .row-content {
12 | margin:0px auto;
13 | padding: 30px 0px 50px 0px;
14 | }
15 |
16 | .footer{
17 | background-color: rgb(95, 148, 216);
18 | height: 25px;
19 | margin: auto;
20 | }
21 |
22 |
23 | .home{
24 | background-image: url('./images/walllpaper.jpg');
25 | min-height: 100vh;
26 | min-width: 99vw;
27 | }
28 |
29 | .darkbg{
30 | background-color: black;
31 | opacity: 0.7;
32 | color: white;
33 | size: 4rem;
34 | }
35 |
36 | table, th, td {
37 | border: 1px solid white;
38 | }
39 |
40 | table {
41 | min-width: 40%
42 | }
43 |
44 | .heading{
45 | margin-top: 70px;
46 | }
47 |
48 | .full{
49 | min-height: 100vh;
50 | }
51 |
52 | .Option{
53 | cursor: pointer;
54 | }
55 |
56 | .loading{
57 | min-height: 100vh;
58 | }
--------------------------------------------------------------------------------
/client/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Main from './components/MainComponent';
3 | import { BrowserRouter } from 'react-router-dom';
4 | import {Provider} from 'react-redux';
5 | import {ConfigureStore} from './redux/store.js'
6 |
7 | import 'bootstrap/dist/css/bootstrap.min.css';
8 | import 'font-awesome/css/font-awesome.css';
9 | import './App.css';
10 |
11 | const store= ConfigureStore();
12 |
13 | function App() {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | export default App;
26 |
--------------------------------------------------------------------------------
/client/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render( , div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/client/src/baseUrl.js:
--------------------------------------------------------------------------------
1 | export const baseUrl="https://lib-manage.herokuapp.com/api/";
2 | // export const baseUrl="http://localhost:5000/api/";
--------------------------------------------------------------------------------
/client/src/components/AddBookComponent.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import {Button, Label, Col, Row} from 'reactstrap';
3 | import { Control, LocalForm, Errors } from 'react-redux-form';
4 | import Loading from './LoadingComponent';
5 |
6 | const required = (val) => val && val.length;
7 | const requiredNum = (val) => !!(val);
8 | const maxLength = (len) => (val) => !(val) || (val.length <= len);
9 | const minLength = (len) => (val) => (val) && (val.length >= len);
10 | const maxVal = (len) => (val) => !(val) || (val<= len);
11 | const minVal = (len) => (val) => (val) && (val>= len);
12 | const isNumber = (val) => !isNaN(Number(val));
13 |
14 | class AddBook extends Component {
15 |
16 | constructor(props){
17 | super(props);
18 | this.state={
19 | }
20 |
21 | }
22 |
23 | componentDidMount() {
24 | window.scrollTo(0, 0)
25 | }
26 |
27 | render(){
28 | let uniqueIsbn=(val) =>(!this.props.books.some((book)=>(book.isbn===val)))
29 | let uniqueName=(val) =>(!this.props.books.some((book)=>(book.name===val)))
30 |
31 | if (this.props.booksLoading) {
32 | return(
33 |
38 | );
39 | }
40 | else if (this.props.booksErrMess) {
41 | return(
42 |
43 |
44 |
45 |
46 |
{this.props.booksErrMess}
47 |
48 |
49 |
50 | );
51 | }
52 | else
53 | return (
54 |
55 |
56 |
57 |
Add a book
58 |
59 |
60 |
61 | {
62 | this.props.postBook(values.name, values.author, values.description, values.isbn, values.cat, values.floor, values.shelf, values.copies);
63 | }}>
64 |
65 | Name
66 |
67 |
74 |
84 |
85 | Authors
86 |
87 |
94 |
103 |
104 |
105 |
106 | ISBN No.
107 |
108 |
116 |
128 |
129 | Copies Available
130 |
131 |
138 |
149 |
150 |
151 |
152 |
153 |
154 |
155 | Category
156 |
157 | Romance Technology
158 | Computer Science Management
159 | Electronics Physics
160 | Chemistry Mathematics
161 | Fiction Philosophy
162 | Language Arts
163 | Other
164 |
165 |
166 |
167 |
168 | Floor
169 |
171 | 0 1
172 | 2 3
173 | 4 5
174 | 6 7
175 | 8
176 |
177 |
178 |
179 |
180 |
181 | Shelf
182 |
183 |
190 |
201 |
202 |
203 |
204 |
205 |
206 | Description
207 |
208 |
212 |
213 |
214 |
215 |
216 |
217 | Submit
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 | );
226 |
227 | }
228 |
229 | }
230 |
231 | export default AddBook;
--------------------------------------------------------------------------------
/client/src/components/BookDetailComponent.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import {Row,Col, Card, CardText, CardHeader, CardFooter, CardBody,CardTitle } from 'reactstrap';
3 | import Loading from './LoadingComponent';
4 | function RenderBook({book,isAdmin,toggleEditModal,changeSelected}) {
5 | if (book != null)
6 | return(
7 |
8 |
9 | {book.name}
10 | {isAdmin?({changeSelected(book._id);toggleEditModal();}}/>):( )}
11 |
12 |
13 | - {book.author}
14 |
15 | Category: {book.cat}
16 | ISBN number: {book.isbn}
17 | Descrption: {book.description}
18 | Location: Shelf no. {book.shelf} ,
19 | {book.floor===0?' Ground ':book.floor}{(book.floor===1)?'st ':(book.floor===2)?'nd ':(book.floor===3)?'rd ':(book.floor===0)?'':'th '}
20 | Floor
21 | Copies available : {book.copies}
22 |
23 |
24 |
25 |
26 |
27 | Created at : {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit', hour: 'numeric',minute: 'numeric', hour12: true }).format(new Date( Date.parse(book.createdAt)))}
28 |
29 |
30 | Last updated at : {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit',hour: 'numeric',minute: 'numeric', hour12: true}).format(new Date( Date.parse(book.updatedAt)))}
31 |
32 |
33 |
34 |
35 | );
36 | else
37 | return(
38 |
39 | );
40 | }
41 |
42 |
43 | class BookDetail extends Component {
44 |
45 | constructor(props){
46 | super(props);
47 | this.state={
48 | }
49 | }
50 | componentDidMount() {
51 | window.scrollTo(0, 0)
52 | }
53 | render(){
54 | if (this.props.isLoading) {
55 | return(
56 |
61 | );
62 | }
63 | else if (this.props.errMess) {
64 | return(
65 |
66 |
67 |
68 |
69 |
{this.props.errMess}
70 |
71 |
72 |
73 | );
74 | }
75 | else
76 | return(
77 |
78 |
79 |
80 |
81 |
82 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | );
92 | }
93 |
94 | }
95 |
96 | export default BookDetail;
--------------------------------------------------------------------------------
/client/src/components/BooksComponent.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import { Table } from 'reactstrap';
3 | import {Link} from 'react-router-dom';
4 | import Loading from './LoadingComponent.js';
5 |
6 | // RenderBook is a functional component
7 | function RenderBook ({book, changeSelected ,isAdmin, toggleDeleteModal,toggleEditModal,i}) {
8 | return (
9 |
10 |
11 | {i}
12 |
13 |
14 |
15 | {book.name}
16 |
17 |
18 |
19 | {book.isbn}
20 |
21 |
22 | {book.author}
23 |
24 |
25 | {book.copies}
26 |
27 | {isAdmin?({changeSelected(book._id); toggleEditModal(); }} className="Option fa fa-pencil"/>
28 | {changeSelected(book._id); toggleDeleteModal();}} className="Option fa fa-trash"/>
29 | ):( )}
30 |
31 | );
32 | }
33 |
34 |
35 | class Booklist extends Component {
36 |
37 | constructor(props){
38 | super(props);
39 | this.state={
40 | }
41 | this.i=1;
42 | }
43 |
44 |
45 | componentDidMount() {
46 | window.scrollTo(0, 0)
47 | }
48 |
49 | render(){
50 | const list = this.props.books.map((book) => {
51 | return (
52 |
53 |
58 |
59 | );
60 | });
61 |
62 | if (this.props.booksLoading) {
63 | return(
64 |
69 | );
70 | }
71 | else if (this.props.booksErrMess) {
72 | return(
73 |
74 |
75 |
76 |
77 |
{this.props.booksErrMess}
78 |
79 |
80 |
81 | );
82 | }
83 | else
84 | {
85 | return(
86 |
87 |
88 |
89 |
90 |
List of All books
91 |
92 |
93 |
94 | S.No.
95 | Name of Book
96 | ISBN number
97 | Authors
98 | Copies available
99 | {this.props.isAdmin?(Edit / Delete ):( )}
100 |
101 |
102 |
103 | {list}
104 |
105 |
106 |
107 |
108 |
109 |
110 | );
111 | }
112 | }
113 |
114 | }
115 |
116 | export default Booklist;
--------------------------------------------------------------------------------
/client/src/components/FooterComponent.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 |
3 | class Footer extends Component {
4 |
5 | constructor(props){
6 | super(props);
7 | this.state={
8 | }
9 | }
10 |
11 | render(){
12 | return(
13 | © Copyright 2019 IIT(ISM)
14 | );
15 | }
16 |
17 | }
18 |
19 | export default Footer;
--------------------------------------------------------------------------------
/client/src/components/HeaderComponent.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import {Navbar, Form, FormGroup, Label, Input, Nav, NavbarToggler,Collapse,NavItem, NavbarBrand, Modal, ModalBody, ModalHeader, Button} from 'reactstrap';
3 | import {Dropdown,DropdownItem,DropdownMenu,DropdownToggle} from 'reactstrap';
4 | import { NavLink,Link } from 'react-router-dom';
5 | import { Control, LocalForm, Errors } from 'react-redux-form';
6 |
7 | const required = (val) => val && val.length;
8 | const maxLength = (len) => (val) => !(val) || (val.length <= len);
9 | const minLength = (len) => (val) => (val) && (val.length >= len);
10 | const validEmail = (val) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(val);
11 |
12 |
13 | function Registerer(props){
14 | if(props.isSignedIn===false)
15 | return (
16 |
17 |
18 |
19 | Register
20 |
21 |
22 | );
23 | else return(
24 |
25 |
26 | );
27 | }
28 |
29 |
30 | class Header extends Component{
31 |
32 | constructor(props){
33 | super(props);
34 | this.state={
35 | isNavOpen: false,
36 | isModalOpen: false,
37 | isRegisterOpen: false,
38 | dropdownOpen: false
39 | }
40 | this.toggleModal=this.toggleModal.bind(this);
41 | this.toggleNav=this.toggleNav.bind(this);
42 | this.handleLogin = this.handleLogin.bind(this);
43 | this.handleLogout = this.handleLogout.bind(this);
44 | this.toggleRegister=this.toggleRegister.bind(this);
45 | this.toggle=this.toggle.bind(this);
46 | }
47 |
48 | toggle(){
49 | this.setState({dropdownOpen: !this.state.dropdownOpen});
50 | }
51 | toggleNav(){
52 | if(window.innerWidth<1200){
53 | this.setState({
54 | isNavOpen: !this.state.isNavOpen
55 | });
56 | }
57 | }
58 |
59 | toggleRegister(event){
60 | this.setState({
61 | isRegisterOpen: !this.state.isRegisterOpen
62 | });
63 |
64 | }
65 |
66 | handleLogin(event) {
67 | this.toggleModal();
68 | this.props.loginUser({username: this.username.value, password: this.password.value});
69 | event.preventDefault();
70 | }
71 |
72 | handleLogout() {
73 | this.props.logoutUser();
74 | }
75 | toggleModal() {
76 | this.setState({
77 | isModalOpen: !this.state.isModalOpen
78 | });
79 | }
80 |
81 | render(){
82 | return (
83 |
84 |
85 |
86 |
87 |
88 | Central Library
89 |
90 |
91 |
92 |
93 |
94 | Home
95 |
96 |
97 | {this.props.auth.userinfo&&this.props.auth.userinfo.admin?(
98 |
99 |
100 |
101 |
102 | Books
103 |
104 |
105 |
106 |
107 |
108 | View / Modify books
109 |
110 | Add book
111 |
112 |
113 |
114 | ):(
115 |
116 |
117 | Books
118 |
119 |
120 |
121 | )}
122 |
123 |
124 |
125 | Search
126 |
127 |
128 | {
129 | (this.props.auth.isAuthenticated)?(
130 |
131 |
132 | My Profile
133 |
134 |
135 | ):
136 | (
)
137 | }
138 | {
139 | (this.props.auth.isAuthenticated&&!this.props.auth.userinfo.admin)?(
140 |
141 |
142 | Issue history
143 |
144 |
145 | ):
146 | (
)
147 | }
148 | {
149 | (this.props.auth.isAuthenticated&&this.props.auth.userinfo.admin)?(
150 |
151 |
152 |
153 | Issue Book
154 |
155 |
156 |
157 |
158 | Return Book
159 |
160 |
161 |
162 |
163 | Stats
164 |
165 |
166 |
167 | ):
168 | (
)
169 | }
170 |
171 |
172 |
173 | { !this.props.auth.isAuthenticated ?
174 |
175 | Login
176 | {this.props.auth.isLoading ?
177 |
178 | : null
179 | }
180 |
181 | :
182 |
183 |
{this.props.auth.user.username}
184 |
185 | Logout
186 | {this.props.auth.isLoading ?
187 |
188 | : null
189 | }
190 |
191 |
192 | }
193 |
194 |
195 | {this.toggleRegister()}}/>
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 | Sign In
204 |
205 |
206 |
219 |
220 |
221 |
222 |
223 | Register
224 |
225 |
226 | {
227 | this.toggleRegister();
228 | this.props.registerUser({
229 | username: values.username,
230 | password: values.password,
231 | email: values.email,
232 | roll: values.roll,
233 | firstname: values.firstname,
234 | lastname: values.lastname });
235 | }}>
236 |
237 | Username
238 |
240 |
242 |
243 |
244 | Password
245 |
247 |
249 |
250 |
251 | First Name
252 |
254 |
256 |
257 |
258 | Last Name
259 |
261 |
263 |
264 |
265 | Roll No.
266 |
268 |
270 |
271 |
272 | E-mail
273 |
275 |
277 |
278 | Sign Up
279 |
280 |
281 |
282 |
283 | );
284 | }
285 | }
286 |
287 | export default Header;
--------------------------------------------------------------------------------
/client/src/components/HistoryComponent.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import { Table } from 'reactstrap';
3 | import {Link} from 'react-router-dom';
4 | import Loading from './LoadingComponent.js';
5 |
6 | const fineRate=1;
7 | let totalFine=0;
8 | const allowedDays=30;
9 | function RenderIssue ({issue,i}) {
10 | const dates=[];
11 | const today= new Date();
12 | dates.push(today);
13 | const issueDate=new Date(Date.parse(issue.createdAt));
14 | const deadline = new Date( Date.parse(issue.createdAt));
15 | deadline.setDate(deadline.getDate()+30);
16 | dates.push(deadline);
17 | const returnDate=issue.returned?new Date(Date.parse((issue.updatedAt))):(new Date(Math.min.apply(null,dates)));
18 | let fine=0;
19 | if(((returnDate.getTime()-issueDate.getTime())/(1000 * 60 * 60 * 24))>allowedDays)
20 | {
21 | fine=Math.floor((returnDate.getTime()-issueDate.getTime())/(1000 * 60 * 60 * 24))*fineRate;
22 | }
23 | totalFine+=fine;
24 | return (
25 |
26 |
27 | {i}
28 |
29 |
30 |
31 | {issue.book.name}
32 |
33 |
34 |
35 | {issue.book.isbn}
36 |
37 |
38 | {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(new Date( Date.parse(issue.createdAt)))}
39 |
40 |
41 | {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(deadline)}
42 |
43 |
44 | {
45 | issue.returned?('Returned on '+(new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(new Date( Date.parse(returnDate)))))
46 | :('Not returned yet')
47 | }
48 |
49 |
50 | {
51 | fine
52 | }
53 |
54 |
55 | );
56 | }
57 |
58 | class History extends Component {
59 |
60 | constructor(props){
61 | super(props);
62 | this.state={
63 | }
64 | this.i=1;
65 | }
66 | componentDidMount() {
67 | window.scrollTo(0, 0)
68 | }
69 |
70 | render(){
71 |
72 | if (this.props.issues.isLoading) {
73 | return(
74 |
79 | );
80 | }
81 | else if (this.props.issues.errMess) {
82 | return(
83 |
84 |
85 |
86 |
87 |
{this.props.errMess}
88 |
89 |
90 |
91 | );
92 | }
93 | else if(this.props.issues.issues.length===0){
94 | return (
95 |
96 |
97 |
98 |
99 |
{'You have not issued any books.'}
100 | {'Request admin to issue a book'}
101 |
102 |
103 |
104 | );
105 | }
106 | else {
107 | const list = this.props.issues.issues.map((issue) => {
108 | return (
109 |
110 |
113 |
114 | );
115 | });
116 |
117 | return(
118 |
119 |
120 |
121 |
122 |
Issue History
123 |
124 |
125 |
126 | S.No.
127 | Name of Book
128 | ISBN number
129 | Issue Date
130 | Return Deadline
131 | Return status
132 | Fine (in Rs.)
133 |
134 |
135 |
136 | {list}
137 |
138 |
139 |
140 |
Total Fine due (if all pending books are returned today) : Rs. {totalFine}
141 |
142 |
143 |
144 |
145 | );
146 | }
147 | }
148 |
149 | }
150 |
151 | export default History;
--------------------------------------------------------------------------------
/client/src/components/HomeComponent.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 |
3 | class Home extends Component {
4 |
5 | constructor(props){
6 | super(props);
7 | this.state={
8 | }
9 | }
10 |
11 | componentDidMount() {
12 | window.scrollTo(0, 0)
13 | }
14 |
15 | render(){
16 | return(
17 |
18 |
19 |
20 |
Welcome to the Central Library
21 |
22 |
23 |
24 |
25 |
26 |
27 | The Central Library of Indian Institute of Technology (Indian School of Mines) Dhanbad is the heart of the institute providing direct academic and research supports to all departments.
28 |
29 | The Central Library is an automated library in terms of records organisation and management of all its different sections, search and discovery, information retrieval and service delivery. The whole library operations run on LIBSYS integrated library management software.
30 |
31 |
32 |
33 |
34 | Library Timings
35 | Opening Time 9.00 A.M.
36 | Closing Time 9.00 P.M.
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | );
45 | }
46 |
47 | }
48 |
49 | export default Home;
--------------------------------------------------------------------------------
/client/src/components/IssueComponent.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import { Button, Form, FormGroup, Label, Input, Col,Row } from 'reactstrap';
3 | import Loading from './LoadingComponent';
4 |
5 |
6 | class Issue extends Component {
7 |
8 | constructor(props){
9 | super(props);
10 | this.state={
11 | isbn: '',
12 | roll: ''
13 | }
14 | }
15 |
16 | componentDidMount() {
17 | window.scrollTo(0, 0)
18 | }
19 |
20 | render(){
21 | if (this.props.booksLoading||this.props.usersLoading) {
22 | return(
23 |
28 | );
29 | }
30 | else if (this.props.booksErrMess) {
31 | return(
32 |
33 |
34 |
35 |
36 |
{this.props.booksErrMess}
37 |
38 |
39 |
40 | );
41 | }
42 | else if (this.props.usersErrMess) {
43 | return(
44 |
45 |
46 |
47 |
48 |
{this.props.usersErrMess}
49 |
50 |
51 |
52 | );
53 | }
54 | else
55 | {
56 | const bookoptions= this.props.books.map((book,index)=>({book.isbn} ));
58 | const defaultBook=this.props.books[0];
59 | // To just get list of the students (not the admins)
60 | let useroptions=this.props.users.filter((user)=>(!user.admin));
61 | const defaultUser=useroptions[0];
62 | useroptions= useroptions.map((user,index)=>({user.roll} ))
64 | if(this.state.isbn==='') {
65 | this.setState({isbn: defaultBook.isbn,roll: defaultUser.roll });
66 | }
67 | return (
68 |
69 |
70 |
71 |
Issue book
72 |
73 |
74 |
75 |
139 |
140 |
141 |
142 | );
143 |
144 | }
145 | }
146 | }
147 |
148 | export default Issue;
--------------------------------------------------------------------------------
/client/src/components/LoadingComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Loading = () => {
4 | return(
5 |
6 |
7 |
8 |
9 |
10 |
11 | Loading ....
12 |
13 | );
14 | };
15 |
16 | export default Loading;
--------------------------------------------------------------------------------
/client/src/components/LogComponent.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import { Table } from 'reactstrap';
3 | import {Link} from 'react-router-dom';
4 | import Loading from './LoadingComponent.js';
5 |
6 | const fineRate=1;
7 |
8 | const allowedDays=30;
9 | function RenderIssue ({issue,i}) {
10 | const dates=[];
11 | const today= new Date();
12 | dates.push(today);
13 | const issueDate=new Date(Date.parse(issue.createdAt));
14 | const deadline = new Date( Date.parse(issue.createdAt));
15 | deadline.setDate(deadline.getDate()+30);
16 | dates.push(deadline);
17 | const returnDate=issue.returned?new Date(Date.parse((issue.updatedAt))):(new Date(Math.min.apply(null,dates)));
18 | let fine=0;
19 | if(((returnDate.getTime()-issueDate.getTime())/(1000 * 60 * 60 * 24))>allowedDays)
20 | {
21 | fine=Math.floor((returnDate.getTime()-issueDate.getTime())/(1000 * 60 * 60 * 24))*fineRate;
22 | }
23 | return (
24 |
25 |
26 | {i}
27 |
28 |
29 |
30 | {issue.student.firstname+' '+issue.student.lastname}
31 |
32 |
33 |
34 | {issue.student.roll}
35 |
36 |
37 | {issue.book==null ? "N/A":
38 | {issue.book.name}
39 | }
40 |
41 |
42 | {issue.book==null ? "N/A":issue.book.isbn}
43 |
44 |
45 | {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(new Date( Date.parse(issue.createdAt)))}
46 |
47 |
48 | {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(deadline)}
49 |
50 |
51 | {
52 | issue.returned?('Returned on '+(new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(new Date( Date.parse(returnDate)))))
53 | :('Not returned yet')
54 | }
55 |
56 |
57 | {
58 | fine
59 | }
60 |
61 |
62 | );
63 | }
64 |
65 | class Log extends Component {
66 |
67 | constructor(props){
68 | super(props);
69 | this.state={
70 | }
71 | this.i=1;
72 | }
73 | componentDidMount() {
74 | window.scrollTo(0, 0)
75 | }
76 |
77 | render(){
78 |
79 | if (this.props.issues.isLoading) {
80 | return(
81 |
86 | );
87 | }
88 | else if (this.props.issues.errMess) {
89 | return(
90 |
91 |
92 |
93 |
94 |
{this.props.errMess}
95 |
96 |
97 |
98 | );
99 | }
100 | else if(this.props.issues.issues.length===0){
101 | return (
102 |
103 |
104 |
105 |
106 |
{'You have not issued any books.'}
107 | {'Request admin to issue a book'}
108 |
109 |
110 |
111 | );
112 | }
113 | else {
114 | const list = this.props.issues.issues.map((issue) => {
115 | return (
116 |
117 |
120 |
121 | );
122 | });
123 |
124 | return(
125 |
126 |
127 |
128 |
129 |
Issue Log
130 |
131 |
132 |
133 | S.No.
134 | Name of Student
135 | Roll No.
136 | Name of Book
137 | ISBN number
138 | Issue Date
139 | Return Deadline
140 | Return status
141 | Fine (in Rs.)
142 |
143 |
144 |
145 | {list}
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 | );
155 | }
156 | }
157 |
158 | }
159 |
160 | export default Log;
--------------------------------------------------------------------------------
/client/src/components/MainComponent.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import Header from './HeaderComponent.js';
3 | import Footer from './FooterComponent.js';
4 | import Home from './HomeComponent.js';
5 | import Booklist from './BooksComponent.js';
6 | import Search from './SearchComponent.js';
7 | import BookDetail from './BookDetailComponent.js';
8 | import Profile from './ProfileComponent.js';
9 | import AddBook from './AddBookComponent.js';
10 | import History from './HistoryComponent.js';
11 | import Issue from './IssueComponent.js';
12 | import Return from './ReturnComponent.js';
13 | import UserDetail from './UserDetailComponent.js';
14 | import Stats from './StatsComponent.js';
15 | import Log from './LogComponent.js';
16 | import UserList from './UserListComponent.js';
17 |
18 | import {Switch,Route,Redirect, withRouter} from 'react-router-dom';
19 | import {connect} from 'react-redux';
20 | import {Modal,ModalBody,ModalHeader,Button, Label, Col, Row} from 'reactstrap';
21 | import { postBook, fetchBooks, editBook, deleteBook,loginUser, logoutUser,
22 | registerUser, editUser, editPassword, postIssue, returnIssue, fetchIssues, fetchUsers} from '../redux/ActionCreators';
23 | import { Control, LocalForm, Errors } from 'react-redux-form';
24 |
25 | const required = (val) => val && val.length;
26 | const requiredNum = (val) => !!(val);
27 | const maxLength = (len) => (val) => !(val) || (val.length <= len);
28 | const minLength = (len) => (val) => (val) && (val.length >= len);
29 | const maxVal = (len) => (val) => !(val) || (val<= len);
30 | const minVal = (len) => (val) => (val) && (val>= len);
31 | const isNumber = (val) => !isNaN(Number(val));
32 |
33 | const mapStateToProps= (state)=>{
34 | return{
35 | books: state.books,
36 | auth: state.auth,
37 | issues: state.issues,
38 | users: state.users
39 | };
40 | }
41 |
42 | const mapDispatchToProps = dispatch => ({
43 | fetchBooks: () => { dispatch(fetchBooks())},
44 | fetchIssues: (student) =>{ dispatch(fetchIssues(student))},
45 | fetchUsers: () => { dispatch(fetchUsers())},
46 | postBook: (name, author, description, isbn, cat, floor, shelf, copies) => dispatch(postBook(name, author, description, isbn, cat, floor, shelf, copies)),
47 | editBook: (_id, name, author, description, isbn, cat, floor, shelf, copies) => dispatch(editBook(_id, name, author, description, isbn, cat, floor, shelf, copies)),
48 | deleteBook: (_id) => dispatch(deleteBook(_id)),
49 | loginUser: (creds) => dispatch(loginUser(creds)),
50 | logoutUser: () => dispatch(logoutUser()),
51 | registerUser: (creds) => dispatch(registerUser(creds)),
52 | editUser: (_id, firstname, lastname, roll, email) => dispatch(editUser(_id, firstname, lastname, roll, email)),
53 | editPassword : (_id,username,password) => dispatch(editPassword(_id,username,password)),
54 | postIssue: (bookId,studentId) => (dispatch(postIssue(bookId,studentId))),
55 | returnIssue: (issueId) => (dispatch(returnIssue(issueId)))
56 | });
57 |
58 | class Main extends Component {
59 |
60 | componentDidMount() {
61 | this.props.fetchBooks();
62 | if(this.props.auth.isAuthenticated){
63 | this.props.fetchIssues(!this.props.auth.userinfo.admin);
64 | }
65 | if(this.props.auth.isAuthenticated&&this.props.auth.userinfo.admin){
66 | this.props.fetchUsers();
67 | }
68 | }
69 | constructor(props){
70 | super(props);
71 | this.state={
72 | isDeleteModalOpen: false,
73 | isEditModalOpen: false,
74 | selectedBook: null
75 | };
76 | this.toggleDeleteModal=this.toggleDeleteModal.bind(this);
77 | this.toggleEditModal=this.toggleEditModal.bind(this);
78 | this.changeSelected=this.changeSelected.bind(this);
79 | this.handleSubmitEdit = this.handleSubmitEdit.bind(this);
80 | }
81 |
82 | handleSubmitEdit(values) {
83 | this.toggleEditModal();
84 | this.props.editBook(this.state.selectedBook._id, values.name, values.author,
85 | values.description, values.isbn, values.cat, values.floor, values.shelf, values.copies);
86 | }
87 |
88 | changeSelected(_id){
89 | this.setState({selectedBook:this.props.books.books.filter((book)=>(book._id===_id))[0]});
90 | }
91 |
92 | toggleDeleteModal(){
93 | this.setState({isDeleteModalOpen: !this.state.isDeleteModalOpen})
94 | }
95 |
96 | toggleEditModal(){
97 | this.setState({isEditModalOpen: !this.state.isEditModalOpen});
98 | }
99 |
100 | render(){
101 | const BookWithId = ({match}) => {
102 | let selectedBook=this.props.books.books.filter((book) => (book._id)===(match.params.bookId))[0]
103 | let notFoundErr=null;
104 | if(selectedBook===undefined){
105 | notFoundErr=("\n\n Error 404 : Book not found");
106 | }
107 | return(
108 |
115 | );
116 | };
117 |
118 | const UserWithId = ({match}) => {
119 | let selectedUser=this.props.users.users.filter((user) => ((user._id)===(match.params.userId)))[0];
120 | let notFoundErr=null;
121 | if(selectedUser===undefined){
122 | notFoundErr=("\n\n Error 404 : User not found");
123 | }
124 | return(
125 |
129 | );
130 | };
131 |
132 | const PrivateRouteCommon = ({ component: Component, ...rest }) => (
133 | (
134 | this.props.auth.isAuthenticated
135 | ?
136 | :
140 | )} />
141 | );
142 |
143 | const PrivateRouteAdmin = ({ component: Component, ...rest }) => (
144 | (
145 | this.props.auth.isAuthenticated&&this.props.auth.userinfo.admin
146 | ?
147 | :
151 | )} />
152 | );
153 |
154 | const PrivateRoute = ({ component: Component, ...rest }) => (
155 | (
156 | this.props.auth.isAuthenticated&&!this.props.auth.userinfo.admin
157 | ?
158 | :
162 | )} />
163 | );
164 |
165 | let uniqueIsbn= (defaultIsbn)=> (val) =>(!this.props.books.books.some((book)=>(book.isbn===val))||(val===defaultIsbn))
166 | let uniqueName= (defaultName)=>(val) =>(!this.props.books.books.some((book)=>(book.name===val))||(val===defaultName))
167 |
168 | return (
169 |
170 |
175 |
176 | } />
177 | }
187 | />
188 |
189 | }/>
199 |
200 |
204 | }
205 | />
206 |
213 | }/>
214 | }
217 | />
218 | }
222 | />
223 | }
226 | />
227 | (!user.admin))}
229 | usersLoading={this.props.users.isLoading}
230 | usersErrMess={this.props.users.errMess}
231 | />}
232 | />
233 | (user.admin))}
235 | usersLoading={this.props.users.isLoading}
236 | usersErrMess={this.props.users.errMess}
237 | />}
238 | />
239 | } />
249 | } />
254 |
255 | }/>
264 |
265 |
266 |
267 |
268 |
269 | Confirm Deletion
270 |
271 |
272 | Book details :
273 | Name : {this.state.selectedBook?this.state.selectedBook.name:''}
274 | Authors : {this.state.selectedBook?this.state.selectedBook.author:''}
275 | ISBN Number : {this.state.selectedBook?this.state.selectedBook.isbn:''}
276 | Available Copies : {this.state.selectedBook?this.state.selectedBook.copies:''}
277 | Are you sure you wish to delete this book ?
278 | {
279 | this.props.deleteBook(this.state.selectedBook._id);
280 | this.toggleDeleteModal();}}>Yes {' '}
281 | {
282 | this.toggleDeleteModal();
283 | }}>No
284 |
285 |
286 | {this.state.selectedBook?(
287 |
288 |
289 | Edit a book
290 |
291 |
292 | this.handleSubmitEdit(values)}>
293 |
294 | Name
295 |
296 |
304 |
314 |
315 |
316 |
317 | Authors
318 |
319 |
326 |
335 |
336 |
337 |
338 | ISBN No.
339 |
340 |
348 |
360 |
361 |
362 |
363 |
364 |
365 | Category
366 |
367 | Romance Technology
368 | Computer Science Management
369 | Electronics Physics
370 | Chemistry Mathematics
371 | Fiction Philosophy
372 | Language Arts
373 | Other
374 |
375 |
376 |
377 |
378 |
379 |
380 | Copies Available
381 |
382 |
389 |
399 |
400 |
401 |
402 |
403 |
404 | Floor
405 |
406 | 0 1
407 | 2 3
408 | 4 5
409 | 6 7
410 | 8
411 |
412 |
413 |
414 |
415 |
416 | Shelf
417 |
418 |
425 |
435 |
436 |
437 |
438 |
439 |
440 | Description
441 |
442 |
446 |
447 |
448 |
449 |
450 |
451 | Submit
452 |
453 |
454 |
455 |
456 |
457 |
458 | ):(
)}
459 |
460 |
461 | );
462 | }
463 | }
464 |
465 | export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Main));
466 |
467 |
--------------------------------------------------------------------------------
/client/src/components/ProfileComponent.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import {Card,CardBody,CardHeader,Label,CardText,Button,Modal,ModalBody,ModalHeader,FormGroup} from 'reactstrap';
3 | import { Control, LocalForm, Errors } from 'react-redux-form';
4 |
5 | const required = (val) => val && val.length;
6 | const maxLength = (len) => (val) => !(val) || (val.length <= len);
7 | const minLength = (len) => (val) => (val) && (val.length >= len);
8 | const validEmail = (val) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(val);
9 | const matchcreds = (original) => (val) => (val===original);
10 |
11 | class Profile extends Component {
12 |
13 | constructor(props){
14 | super(props);
15 | this.state={
16 | isEditModalOpen: false,
17 | isPasswordModalOpen: false
18 | }
19 | this.toggleEditModal=this.toggleEditModal.bind(this);
20 | this.togglePasswordModal=this.togglePasswordModal.bind(this);
21 | }
22 |
23 | componentDidMount() {
24 | window.scrollTo(0, 0)
25 | }
26 |
27 | togglePasswordModal(){
28 | this.setState({
29 | isPasswordModalOpen: !this.state.isPasswordModalOpen
30 | });
31 | }
32 |
33 | toggleEditModal(){
34 | this.setState({isEditModalOpen: !this.state.isEditModalOpen});
35 | }
36 |
37 |
38 | render(){
39 | if(this.props.auth.userinfo===null){
40 | return (
41 |
42 | Failed to fetch. Please reload the page
43 |
44 | )
45 | }
46 | return(
47 |
48 |
49 |
50 |
51 |
52 |
53 | My Profile
54 |
55 |
56 | First Name : {' '+this.props.auth.userinfo.firstname}
57 | Last Name : {' '+this.props.auth.userinfo.lastname}
58 | {(this.props.auth.userinfo.admin)?'Admin Id : ':'Roll No.'} : {' '+this.props.auth.userinfo.roll}
59 | Email : {' '+this.props.auth.userinfo.email}
60 |
61 |
62 | Edit {' '}
63 | {' '}
64 | {this.props.auth.userinfo.admin?
: Change Password {' '} }
65 |
66 |
67 |
68 |
69 |
70 |
71 | Edit Profile
72 |
73 |
74 | {
75 | this.toggleEditModal();
76 | this.props.editUser(this.props.auth.userinfo._id, values.firstname,
77 | values.lastname, values.roll, values.email);
78 | }}>
79 |
80 | First Name
81 |
85 |
87 |
88 |
89 | Last Name
90 |
93 |
95 |
96 |
97 | Roll No.
98 |
101 |
103 |
104 |
105 | E-mail
106 |
109 |
111 |
112 | Submit
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | Change Password
121 |
122 |
123 | {
124 | if(values.newpassword===values.confirm){
125 | this.togglePasswordModal();
126 | this.props.editPassword(this.props.auth.userinfo._id, this.props.auth.user.username,
127 | values.newpassword);
128 | }
129 | else {
130 | alert("Your passwords didn't match. Please try again");
131 | }
132 | }}>
133 |
134 | Current Password
135 |
138 |
141 |
142 |
143 |
144 | New password
145 |
148 |
151 |
152 |
153 |
154 | Confirm Password
155 |
159 |
162 |
163 |
164 | Submit
165 |
166 |
167 |
168 |
169 | );
170 | }
171 |
172 | }
173 |
174 | export default Profile;
--------------------------------------------------------------------------------
/client/src/components/ReturnComponent.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import { Table,Button } from 'reactstrap';
3 | import {Link} from 'react-router-dom';
4 | import Loading from './LoadingComponent.js';
5 |
6 | const fineRate=1;
7 | let totalFine=0;
8 | const allowedDays=30;
9 | function RenderIssue ({issue,i,returnBook}) {
10 | const dates=[];
11 | const today= new Date();
12 | dates.push(today);
13 | const issueDate=new Date(Date.parse(issue.createdAt));
14 | const deadline = new Date( Date.parse(issue.createdAt));
15 | deadline.setDate(deadline.getDate()+30);
16 | dates.push(deadline);
17 | const returnDate=issue.returned?new Date(Date.parse((issue.updatedAt))):(new Date(Math.min.apply(null,dates)));
18 | let fine=0;
19 | if(((returnDate.getTime()-issueDate.getTime())/(1000 * 60 * 60 * 24))>allowedDays)
20 | {
21 | fine=Math.floor((returnDate.getTime()-issueDate.getTime())/(1000 * 60 * 60 * 24))*fineRate;
22 | }
23 | totalFine+=fine;
24 | return (
25 |
26 |
27 | {i}
28 |
29 |
30 |
31 | {issue.student.firstname+' '+issue.student.lastname}
32 |
33 |
34 |
35 | {issue.student.roll}
36 |
37 |
38 | {issue.book==null ? "N/A":
39 | {issue.book.name}
40 | }
41 |
42 |
43 | {issue.book==null ? "N/A":issue.book.isbn}
44 |
45 |
46 | {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(new Date( Date.parse(issue.createdAt)))}
47 |
48 |
49 | {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(deadline)}
50 |
51 |
52 | {
53 | fine
54 | }
55 |
56 |
57 | {
58 | returnBook(issue._id);
59 | }}>Return
60 |
61 |
62 | );
63 | }
64 |
65 | class Return extends Component {
66 |
67 | constructor(props){
68 | super(props);
69 | this.state={
70 | }
71 | this.i=1;
72 | }
73 |
74 | componentDidMount() {
75 | window.scrollTo(0, 0)
76 | }
77 |
78 | render(){
79 | console.log(this.props.issues);
80 | if (this.props.issues.isLoading) {
81 | return(
82 |
87 | );
88 | }
89 | else if (this.props.issues.errMess) {
90 | return(
91 |
92 |
93 |
94 |
95 |
{this.props.errMess}
96 |
97 |
98 |
99 | );
100 | }
101 | else if(this.props.issues.issues.length===0){
102 | return (
103 |
104 |
105 |
106 |
107 |
{'All books have been returned.'}
108 |
109 |
110 |
111 | );
112 | }
113 | else {
114 | const dueIssues = this.props.issues.issues.filter((issue)=>(!issue.returned));
115 | const list = dueIssues.map((issue) => {
116 | return (
117 |
118 |
122 |
123 | );
124 | });
125 |
126 | return(
127 |
128 |
129 |
130 |
131 |
List of books not returned
132 |
133 |
134 |
135 | S.No.
136 | Name of Student
137 | Roll No.
138 | Name of Book
139 | ISBN number
140 | Issue Date
141 | Return Deadline
142 | Fine (in Rs.)
143 | Return book
144 |
145 |
146 |
147 | {list}
148 |
149 |
150 |
151 |
Total Fine due (if all pending books are returned today) : Rs. {totalFine}
152 |
153 |
154 |
155 |
156 | );
157 | }
158 | }
159 |
160 | }
161 |
162 | export default Return;
--------------------------------------------------------------------------------
/client/src/components/SearchComponent.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import { Form,FormGroup,Row,Col,Label, Input, Badge, ListGroupItem, ListGroup } from 'reactstrap';
3 | import Loading from './LoadingComponent.js';
4 | import {Link} from 'react-router-dom';
5 |
6 | class Search extends Component {
7 |
8 | constructor(props){
9 | super(props);
10 | this.state={
11 | name: '',
12 | author: '',
13 | cat:['Romance','Technology','Computer Science','Management','Electronics','Physics','Chemistry','Mathematics','Fiction','Philosophy','Language','Arts','Other']
14 | }
15 | this.onChange=this.onChange.bind(this);
16 | }
17 |
18 | onChange(e) {
19 | const options = this.state.cat;
20 | let pos=-1;
21 |
22 | if (e.target.checked) {
23 | options.push(e.target.value)
24 | } else {
25 | let i=0;
26 | for(; i {
48 | let category_matched=this.state.cat.some((category)=>(category===book.cat));
49 | if(((book.name).search(nameRegex)!==-1)&&((book.author).search(authorRegex)!==-1)&&category_matched)
50 | return (
51 |
52 |
53 | {`${book.name} `}
54 |
55 | {book.cat}
56 | {this.props.isAdmin?(
57 | {this.props.changeSelected(book._id); this.props.toggleEditModal(); }} className="Option fa fa-pencil"/>
58 | {' '} {this.props.changeSelected(book._id); this.props.toggleDeleteModal();}} className="Option fa fa-trash"/>
59 |
60 | ):( )}
61 |
62 | Authors : {` ${book.author}`}
63 | {book.copies} Copies available
64 | Location : {` Floor- ${book.floor}, Shelf- ${book.shelf}`}
65 | );
66 | else return ( );
67 | });
68 | if (this.props.booksLoading) {
69 | return(
70 |
75 | );
76 | }
77 | else if (this.props.booksErrMess) {
78 | return(
79 |
80 |
81 |
82 |
83 |
{this.props.booksErrMess}
84 |
85 |
86 |
87 | );
88 | }
89 | else
90 | {
91 | return(
92 |
93 |
94 |
95 |
96 |
Search your book here :
97 |
209 |
210 |
211 |
212 |
213 |
214 |
215 | {list}
216 |
217 |
218 |
219 |
220 |
221 |
222 | );
223 | }
224 | }
225 |
226 | }
227 |
228 | export default Search;
--------------------------------------------------------------------------------
/client/src/components/StatsComponent.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import { Card, CardText, CardBody, CardLink,
3 | CardTitle, Button } from 'reactstrap';
4 | import {Link} from 'react-router-dom';
5 | import Loading from './LoadingComponent.js';
6 |
7 | class Stats extends Component {
8 |
9 | constructor(props){
10 | super(props);
11 | this.state={
12 | }
13 | this.i=1;
14 | }
15 | componentDidMount() {
16 | window.scrollTo(0, 0)
17 | }
18 |
19 | render(){
20 | if (this.props.issues.isLoading||this.props.booksLoading||this.props.usersLoading) {
21 | return(
22 |
27 | );
28 | }
29 | else if (this.props.issues.errMess) {
30 | return(
31 |
32 |
33 |
34 |
35 |
{this.props.issues.errMess}
36 |
37 |
38 |
39 | );
40 | }
41 | else if (this.props.usersErrMess) {
42 | return(
43 |
44 |
45 |
46 |
47 |
{this.props.usersErrMess}
48 |
49 |
50 |
51 | );
52 | }
53 | else if (this.props.booksErrMess) {
54 | return(
55 |
56 |
57 |
58 |
59 |
{this.props.booksErrMess}
60 |
61 |
62 |
63 | );
64 | }
65 | else {
66 |
67 |
68 | return(
69 |
70 |
71 |
72 |
73 |
Stats
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | {this.props.books.length}
82 |
83 | Different books available
84 |
85 |
86 | {' '} View
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | {this.props.issues.issues.length}
98 |
99 | Books Issued
100 |
101 |
102 | {' '} View
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | {this.props.issues.issues.filter((issue)=>(!issue.returned)).length}
114 |
115 |
116 | Books not yet returned
117 |
118 |
119 | {' '} View
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | {this.props.users.filter((user)=>(!user.admin)).length}
130 |
131 | Students registered
132 |
133 |
134 | {' '} View
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 | {this.props.users.filter((user)=>(user.admin)).length}
145 |
146 | Administrators in-charge
147 |
148 |
149 | {' '} View
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 | );
160 | }
161 | }
162 |
163 | }
164 |
165 | export default Stats;
--------------------------------------------------------------------------------
/client/src/components/UserDetailComponent.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import {Card,CardBody,CardHeader,CardText} from 'reactstrap';
3 | import Loading from './LoadingComponent';
4 |
5 | class UserDetail extends Component {
6 |
7 | constructor(props){
8 | super(props);
9 | this.state={
10 | }
11 | }
12 |
13 | componentDidMount() {
14 | window.scrollTo(0, 0)
15 | }
16 |
17 |
18 | render(){
19 | if (this.props.isLoading) {
20 | return(
21 |
26 | );
27 | }
28 | else if (this.props.errMess) {
29 | return(
30 |
31 |
32 |
33 |
34 |
{this.props.errMess}
35 |
36 |
37 |
38 | );
39 | }
40 | else
41 | return(
42 |
43 |
44 |
45 |
46 |
47 |
48 | User Details
49 |
50 |
51 | First Name : {' '+this.props.user.firstname}
52 | Last Name : {' '+this.props.user.lastname}
53 | {(this.props.user.admin)?'Admin Id : ':'Roll No.'} : {' '+this.props.user.roll}
54 | Email : {' '+this.props.user.email}
55 | Username : {' '+this.props.user.username}
56 |
57 |
58 |
59 |
60 |
61 | );
62 | }
63 |
64 | }
65 |
66 | export default UserDetail;
--------------------------------------------------------------------------------
/client/src/components/UserListComponent.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import { Table } from 'reactstrap';
3 | import {Link} from 'react-router-dom';
4 | import Loading from './LoadingComponent.js';
5 |
6 | function RenderUser ({user,i}) {
7 | return (
8 |
9 |
10 | {i}
11 |
12 |
13 |
14 | {user.firstname+' '+ user.lastname}
15 |
16 |
17 |
18 | {user.roll}
19 |
20 |
21 | {user.username}
22 |
23 |
24 | {user.email}
25 |
26 |
27 | );
28 | }
29 |
30 | class UserList extends Component {
31 |
32 | constructor(props){
33 | super(props);
34 | this.state={
35 | }
36 | this.i=1;
37 | }
38 | componentDidMount() {
39 | window.scrollTo(0, 0)
40 | }
41 |
42 | render(){
43 |
44 | if (this.props.usersLoading) {
45 | return(
46 |
51 | );
52 | }
53 | else if (this.props.usersErrMess) {
54 | return(
55 |
56 |
57 |
58 |
59 |
{this.props.errMess}
60 |
61 |
62 |
63 | );
64 | }
65 | else if(this.props.users.length===0){
66 | return (
67 |
68 |
69 |
70 |
71 |
{'No users found'}
72 |
73 |
74 |
75 | );
76 | }
77 | else {
78 | const list = this.props.users.map((user) => {
79 | return (
80 |
81 |
84 |
85 | );
86 | });
87 |
88 | return(
89 |
90 |
91 |
92 |
93 |
List of {this.props.users[0].admin?' admins in-charge': ' students registered'}
94 |
95 |
96 |
97 | S.No.
98 | Name of {this.props.users[0].admin?' admin': ' student'}
99 | {this.props.users[0].admin?' Admin Id': ' Roll No.'}
100 | Username
101 | Email
102 |
103 |
104 |
105 | {list}
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | );
114 | }
115 | }
116 |
117 | }
118 |
119 | export default UserList;
--------------------------------------------------------------------------------
/client/src/images/walllpaper.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cjchirag7/lib_management/6b1aba891aee01d92749b51e0cb9002922b6b4e7/client/src/images/walllpaper.jpg
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render( , document.getElementById('root'));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: https://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/client/src/redux/ActionCreators.js:
--------------------------------------------------------------------------------
1 | import * as ActionTypes from './ActionTypes';
2 | import {baseUrl} from '../baseUrl'
3 |
4 | export const addBook = (book) => ({
5 | type: ActionTypes.ADD_BOOK,
6 | payload: book
7 | });
8 |
9 | export const postBook = (name, author, description, isbn, cat, floor, shelf, copies) => (dispatch) => {
10 | const newBook = {
11 | name: name, author: author,
12 | description: description, isbn: isbn,
13 | cat: cat, floor: floor,
14 | shelf: shelf, copies: copies
15 | };
16 | const bearer = 'Bearer ' + localStorage.getItem('token');
17 | return fetch(baseUrl + 'books', {
18 | method: "POST",
19 | body: JSON.stringify(newBook),
20 | headers: {
21 | "Content-Type": "application/json",
22 | 'Authorization': bearer
23 | }
24 | // , credentials: "same-origin"
25 | })
26 | .then(response => {
27 | if (response.ok) {
28 | return response;
29 | } else {
30 | var error = new Error('Error ' + response.status + ': ' + response.statusText);
31 | error.response = response;
32 | throw error;
33 | }
34 | },
35 | error => {
36 | throw error;
37 | })
38 | .then(response => response.json())
39 | .then(response => { alert('Book added successfully');
40 | return dispatch(addBook(response));})
41 | .catch(error => { alert('Your book could not be added\nError: '+error.message); });
42 | };
43 |
44 | export const editBook = (_id, name, author, description, isbn, cat, floor, shelf, copies) => (dispatch) => {
45 |
46 | const newBook = {
47 | name: name, author: author,
48 | description: description, isbn: isbn,
49 | cat: cat, floor: floor,
50 | shelf: shelf, copies: copies
51 | };
52 | const bearer = 'Bearer ' + localStorage.getItem('token');
53 | return fetch(baseUrl + 'books/' + _id, {
54 | method: "PUT"
55 | // , credentials: 'same-origin'
56 | , body: JSON.stringify(newBook),
57 | headers: {
58 | "Content-Type": "application/json",
59 | 'Authorization': bearer
60 | } })
61 | .then(response => {
62 | if (response.ok) {
63 | return response;
64 | } else {
65 | var error = new Error('Error ' + response.status + ': ' + response.statusText);
66 | error.response = response;
67 | throw error;
68 | }
69 | },
70 | error => {
71 | throw error;
72 | })
73 | .then(response => response.json())
74 | .then(response => (dispatch(editBookdispatch(response))))
75 | .catch(error => {
76 | alert('Your book could not be edited\nError: '+error.message); });
77 | };
78 |
79 | export const editPassword = (_id,username,password) => (dispatch) => {
80 | const bearer = 'Bearer ' + localStorage.getItem('token');
81 | return fetch(baseUrl + 'users/password/' + _id, {
82 | method: "PUT"
83 | // , credentials: 'same-origin'
84 | , body: JSON.stringify({password: password}),
85 | headers: {
86 | "Content-Type": "application/json",
87 | 'Authorization': bearer
88 | } })
89 | .then(response => {
90 | if (response.ok) {
91 | return response;
92 | } else {
93 | var error = new Error('Error ' + response.status + ': ' + response.statusText+'\n ');
94 | error.response = response;
95 | throw error;
96 | }
97 | },
98 | error => {
99 | throw error;
100 | })
101 | .then(response => response.json())
102 | .then(response => {
103 | let newCreds={username: username, password: password};
104 | localStorage.removeItem('creds');
105 | localStorage.setItem('creds', JSON.stringify(newCreds));
106 | alert('Password changed successfully');
107 | return dispatch(editPasswordDispatch(newCreds));})
108 | .catch(error => {
109 | alert('Your password could not be changed\nError: '+error.message); });
110 | }
111 |
112 | export const editUser = (_id, firstname, lastname, roll, email) => (dispatch) => {
113 |
114 | const newUser = {
115 | firstname: firstname,
116 | lastname: lastname,
117 | roll: roll,
118 | email: email };
119 | const bearer = 'Bearer ' + localStorage.getItem('token');
120 | return fetch(baseUrl + 'users/' + _id, {
121 | method: "PUT"
122 | // , credentials: 'same-origin'
123 | , body: JSON.stringify(newUser),
124 | headers: {
125 | "Content-Type": "application/json",
126 | 'Authorization': bearer
127 | } })
128 | .then(response => {
129 | if (response.ok) {
130 | return response;
131 | } else {
132 | var error = new Error('Error ' + response.status + ': ' + response.statusText);
133 | error.response = response;
134 | throw error;
135 | }
136 | },
137 | error => {
138 | throw error;
139 | })
140 | .then(response => response.json())
141 | .then(response => {
142 | localStorage.removeItem('userinfo');
143 | localStorage.setItem('userinfo', JSON.stringify(response));
144 | return dispatch(editUserdispatch(response));})
145 | .catch(error => {
146 | alert('Your profile could not be edited\nError: '+error.message+'\n May be someone has already registered with that Roll No. or Email'); });
147 | };
148 |
149 | export const deleteBook = (_id) => (dispatch) => {
150 |
151 | const bearer = 'Bearer ' + localStorage.getItem('token');
152 | return fetch(baseUrl + 'books/' + _id, {
153 | method: "DELETE"
154 | // , credentials: "same-origin"
155 | , headers: {
156 | 'Authorization': bearer
157 | }
158 | })
159 | .then(response => {
160 | if (response.ok) {
161 | return response;
162 | } else {
163 | var error = new Error('Error ' + response.status + ': ' + response.statusText);
164 | error.response = response;
165 | throw error;
166 | }
167 | },
168 | error => {
169 | throw error;
170 | })
171 | .then(response => response.json())
172 | .then(response => dispatch(deleteBookdispatch(response)))
173 | .catch(error => {alert('Your book could not be deleted\nError: '+error.message); });
174 | };
175 |
176 | export const fetchBooks = () => (dispatch) => {
177 | dispatch(booksLoading(true));
178 | return fetch(baseUrl+'books')
179 | .then(response => {
180 | if (response.ok) {
181 | return response;
182 | } else {
183 | var error = new Error('Error ' + response.status + ': ' + response.statusText);
184 | error.response = response;
185 | throw error;
186 | }
187 | },
188 | error => {
189 | var errmess = new Error(error.message);
190 | throw errmess;
191 | })
192 | .then(response => response.json())
193 | .then(books => dispatch(addBooks(books)))
194 | .catch(error => dispatch(booksFailed(error.message)));
195 | }
196 |
197 |
198 | export const fetchUsers = () => (dispatch) => {
199 | const bearer = 'Bearer ' + localStorage.getItem('token');
200 | dispatch(usersLoading(true));
201 | return fetch(baseUrl+'users',{
202 | headers: {
203 | 'Authorization': bearer
204 | }
205 | })
206 | .then(response => {
207 | if (response.ok) {
208 | return response;
209 | } else {
210 | var error = new Error('Error ' + response.status + ': ' + response.statusText);
211 | error.response = response;
212 | throw error;
213 | }
214 | },
215 | error => {
216 | var errmess = new Error(error.message);
217 | throw errmess;
218 | })
219 | .then(response => response.json())
220 | .then(users => dispatch(addUsers(users)))
221 | .catch(error => dispatch(usersFailed(error.message)));
222 | }
223 |
224 |
225 | export const booksLoading = () => ({
226 | type: ActionTypes.BOOKS_LOADING
227 | });
228 |
229 | export const booksFailed = (errmess) => ({
230 | type: ActionTypes.BOOKS_FAILED,
231 | payload: errmess
232 | });
233 |
234 | export const addBooks = (books) => ({
235 | type: ActionTypes.ADD_BOOKS,
236 | payload: books
237 | });
238 |
239 | export const addUsers = (users) => ({
240 | type: ActionTypes.ADD_USERS,
241 | payload: users
242 | });
243 |
244 | export const usersLoading = () => ({
245 | type: ActionTypes.USERS_LOADING
246 | });
247 |
248 | export const editBookdispatch = (books) => ({
249 | type: ActionTypes.EDIT_BOOK,
250 | payload: books
251 | });
252 |
253 | export const returnBookdispatch = (issue) => ({
254 | type: ActionTypes.RETURN_ISSUE,
255 | payload: issue
256 | });
257 |
258 | export const editUserdispatch = (USER) => ({
259 | type: ActionTypes.EDIT_USER,
260 | payload: USER
261 | });
262 |
263 | export const editPasswordDispatch = (CREDS) => ({
264 | type: ActionTypes.EDIT_PASSWORD,
265 | payload: CREDS
266 | })
267 |
268 | export const deleteBookdispatch = (resp) => ({
269 | type: ActionTypes.DELETE_BOOK,
270 | payload: resp
271 | });
272 |
273 | export const requestLogin = (creds) => {
274 | return {
275 | type: ActionTypes.LOGIN_REQUEST,
276 | creds
277 | }
278 | }
279 |
280 | export const receiveLogin = (response) => {
281 | return {
282 | type: ActionTypes.LOGIN_SUCCESS,
283 | token: response.token,
284 | userinfo: response.userinfo
285 | }
286 | }
287 |
288 | export const loginError = (message) => {
289 | return {
290 | type: ActionTypes.LOGIN_FAILURE,
291 | message
292 | }
293 | }
294 |
295 | export const loginUser = (creds) => (dispatch) => {
296 |
297 | dispatch(requestLogin(creds));
298 | return fetch(baseUrl + 'users/login', {
299 | method: 'POST',
300 | headers: {
301 | 'Content-Type':'application/json'
302 | },
303 | body: JSON.stringify(creds)
304 | })
305 | .then(response => {
306 | if (response.ok) {
307 | return response;
308 | } else {
309 | var error = new Error('Error ' + response.status + ': ' + response.statusText);
310 | error.response = response;
311 | throw error;
312 | }
313 | },
314 | error => {
315 | throw error;
316 | })
317 | .then(response => response.json())
318 | .then(response => {
319 | if (response.success) {
320 | // If login was successful, set the token in local storage
321 | localStorage.setItem('token', response.token);
322 | localStorage.setItem('creds', JSON.stringify(creds));
323 | localStorage.setItem('userinfo', JSON.stringify(response.userinfo));
324 | dispatch(fetchIssues(!response.userinfo.admin));
325 | if(response.userinfo.admin) {
326 | dispatch(fetchUsers())
327 | }
328 | setTimeout(()=>{
329 | logoutUser();
330 | alert('Your JWT token has expired. \nPlease log in again to continue.');
331 | },3600*1000);
332 | // Dispatch the success action
333 | dispatch(receiveLogin(response));
334 |
335 | }
336 | else {
337 | var error = new Error('Error ' + response.status);
338 | error.response = response;
339 | throw error;
340 | }
341 | })
342 | .catch(error => {
343 | alert(error.message+'\n'+"Username and password didn't match");
344 | return dispatch(loginError(error.message));})
345 | };
346 |
347 | export const registerUser = (creds) => (dispatch) => {
348 |
349 |
350 | return fetch(baseUrl + 'users/signup', {
351 | method: 'POST',
352 | headers: {
353 | 'Content-Type':'application/json'
354 | },
355 | body: JSON.stringify(creds)
356 | })
357 | .then(response => {
358 | if (response.ok) {
359 | return response;
360 | } else {
361 | var error = new Error('Error ' + response.status + ': ' + response.statusText);
362 | error.response = response;
363 | throw error;
364 | }
365 | },
366 | error => {
367 | throw error;
368 | })
369 | .then(response => response.json())
370 | .then(response => {
371 | if (response.success) {
372 | // If Registration was successful, alert the user
373 | alert('Registration Successful');
374 | }
375 | else {
376 | var error = new Error('Error ' + response.status);
377 | error.response = response;
378 | throw error;
379 | }
380 | })
381 | .catch(error => alert(error.message+'\n'+
382 | 'May be someone has already registered with that username, email or Roll No.\nTry Entering a new username,email or Roll No. '))
383 | };
384 |
385 | export const addIssue = (issue) => ({
386 | type: ActionTypes.ADD_ISSUE,
387 | payload: issue
388 | });
389 |
390 | export const postIssue = (bookId,studentId) => (dispatch) => {
391 | const newIssue = {
392 | book: bookId,
393 | student: studentId
394 | };
395 | const bearer = 'Bearer ' + localStorage.getItem('token');
396 | return fetch(baseUrl + 'issues', {
397 | method: "POST",
398 | body: JSON.stringify(newIssue),
399 | headers: {
400 | "Content-Type": "application/json",
401 | 'Authorization': bearer
402 | }
403 | // , credentials: "same-origin"
404 | })
405 | .then(response => {
406 | if (response.ok) {
407 | return response;
408 | } else {
409 | var error = new Error('Error ' + response.status + ': ' + response.statusText);
410 | error.response = response;
411 | throw error;
412 | }
413 | },
414 | error => {
415 | throw error;
416 | })
417 | .then(response => response.json())
418 | .then(response => { alert('Book issued successfully');
419 | return dispatch(addIssue(response));})
420 | .catch(error => {
421 | alert('Book could not be issued\nError: '+error.message+'\n'+
422 | 'May be the student has already issued 3 books and not returned. Please return them first. \n'+
423 | 'or the book may not available. You can wait for some days, until the book is returned to library.'); });
424 | };
425 |
426 | export const returnIssue = (issueId) => (dispatch) => {
427 | const bearer = 'Bearer ' + localStorage.getItem('token');
428 | return fetch(baseUrl + 'issues/' + issueId, {
429 | method: "PUT"
430 | // , credentials: 'same-origin'
431 | , headers: {
432 | "Content-Type": "application/json",
433 | 'Authorization': bearer
434 | } })
435 | .then(response => {
436 | if (response.ok) {
437 | return response;
438 | } else {
439 | var error = new Error('Error ' + response.status + ': ' + response.statusText);
440 | error.response = response;
441 | throw error;
442 | }
443 | },
444 | error => {
445 | throw error;
446 | })
447 | .then(response => response.json())
448 | .then(response => {
449 | alert('Book returned successfully');
450 | return dispatch(returnBookdispatch(response));})
451 | .catch(error => {
452 | alert('The book could not be returned\nError: '+error.message); });
453 | };
454 |
455 | export const fetchIssues = (student) => (dispatch) => {
456 | let issueUrl;
457 | const bearer = 'Bearer ' + localStorage.getItem('token');
458 | if(student) {
459 | issueUrl='issues/student';
460 | }
461 | else {
462 | issueUrl='issues';
463 | }
464 | dispatch(issuesLoading(true));
465 | return fetch(baseUrl+issueUrl,{
466 | headers: {
467 | 'Authorization': bearer
468 | }
469 | })
470 | .then(response => {
471 | if (response.ok) {
472 | return response;
473 | } else {
474 | var error = new Error('Error ' + response.status + ': ' + response.statusText);
475 | error.response = response;
476 | throw error;
477 | }
478 | },
479 | error => {
480 | var errmess = new Error(error.message);
481 | throw errmess;
482 | })
483 | .then(response => response.json())
484 | .then(issues => dispatch(addIssues(issues)))
485 | .catch(error => dispatch(issuesFailed(error.message)));
486 | }
487 |
488 |
489 |
490 | export const issuesLoading = () => ({
491 | type: ActionTypes.ISSUES_LOADING
492 | });
493 |
494 | export const issuesFailed = (errmess) => ({
495 | type: ActionTypes.ISSUES_FAILED,
496 | payload: errmess
497 | });
498 |
499 | export const addIssues = (issues) => ({
500 | type: ActionTypes.ADD_ISSUES,
501 | payload: issues
502 | });
503 |
504 | export const usersFailed = (errmess) => ({
505 | type: ActionTypes.USERS_FAILED,
506 | payload: errmess
507 | });
508 |
509 |
510 | export const requestLogout = () => {
511 | return {
512 | type: ActionTypes.LOGOUT_REQUEST
513 | }
514 | }
515 |
516 | export const receiveLogout = () => {
517 | return {
518 | type: ActionTypes.LOGOUT_SUCCESS
519 | }
520 | }
521 |
522 |
523 | export const logoutUser = () => (dispatch) => {
524 | dispatch(requestLogout())
525 | localStorage.removeItem('token');
526 | localStorage.removeItem('creds');
527 | localStorage.removeItem('userinfo');
528 | dispatch(receiveLogout())
529 | }
530 |
--------------------------------------------------------------------------------
/client/src/redux/ActionTypes.js:
--------------------------------------------------------------------------------
1 | export const BOOKS_LOADING = 'BOOKS_LOADING';
2 | export const BOOKS_FAILED = 'BOOKS_FAILED';
3 | export const ADD_BOOKS = 'ADD_BOOKS';
4 | export const ADD_BOOK = 'ADD_BOOK';
5 | export const EDIT_BOOK = 'EDIT_BOOK';
6 | export const DELETE_BOOK = 'DELETE_BOOK';
7 | export const LOGIN_REQUEST = 'LOGIN_REQUEST';
8 | export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
9 | export const LOGIN_FAILURE = 'LOGIN_FAILURE';
10 | export const LOGOUT_REQUEST = 'LOGOUT_REQUEST';
11 | export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
12 | export const LOGOUT_FAILURE = 'LOGOUT_FAILURE';
13 | export const EDIT_USER = 'EDIT_USER';
14 | export const EDIT_PASSWORD = 'EDIT_PASSWORD';
15 | export const ISSUES_LOADING = 'ISSUES_LOADING';
16 | export const ISSUES_FAILED = 'ISSUES_FAILED';
17 | export const ADD_ISSUES = 'ADD_ISSUES';
18 | export const ADD_ISSUE = 'ADD_ISSUE';
19 | export const RETURN_ISSUE = 'RETURN_ISSUE';
20 | export const USERS_LOADING = 'USERS_LOADING';
21 | export const USERS_FAILED = 'USERS_FAILED';
22 | export const ADD_USERS = 'ADD_USERS';
--------------------------------------------------------------------------------
/client/src/redux/auth.js:
--------------------------------------------------------------------------------
1 | import * as ActionTypes from './ActionTypes';
2 |
3 | // The auth reducer. The starting state sets authentication
4 | // based on a token being in local storage. In a real app,
5 | // we would also want a util to check if the token is expired.
6 | const Auth = (state = {
7 | isLoading: false,
8 | isAuthenticated: localStorage.getItem('token') ? true : false,
9 | token: localStorage.getItem('token'),
10 | user: localStorage.getItem('creds') ? JSON.parse(localStorage.getItem('creds')) : null,
11 | userinfo: localStorage.getItem('userinfo') ? JSON.parse(localStorage.getItem('userinfo')) : null,
12 | errMess: null
13 | }, action) => {
14 | switch (action.type) {
15 | case ActionTypes.LOGIN_REQUEST:
16 | return {...state,
17 | isLoading: true,
18 | isAuthenticated: false,
19 | user: action.creds
20 | };
21 | case ActionTypes.LOGIN_SUCCESS:
22 | return {...state,
23 | isLoading: false,
24 | isAuthenticated: true,
25 | errMess: '',
26 | token: action.token,
27 | userinfo: action.userinfo
28 | };
29 | case ActionTypes.LOGIN_FAILURE:
30 | return {...state,
31 | isLoading: false,
32 | isAuthenticated: false,
33 | errMess: action.message
34 | };
35 | case ActionTypes.LOGOUT_REQUEST:
36 | return {...state,
37 | isLoading: true,
38 | isAuthenticated: true
39 | };
40 |
41 | case ActionTypes.LOGOUT_SUCCESS:
42 | return {...state,
43 | isLoading: false,
44 | isAuthenticated: false,
45 | token: '',
46 | user: null,
47 | userinfo: null
48 | };
49 |
50 | case ActionTypes.EDIT_USER:
51 | return {...state,
52 | userinfo: action.payload};
53 |
54 | case ActionTypes.EDIT_PASSWORD:
55 | return {
56 | ...state,
57 | user: action.payload
58 | }
59 | default:
60 | return state
61 | }
62 | }
63 |
64 | export default Auth;
--------------------------------------------------------------------------------
/client/src/redux/books.js:
--------------------------------------------------------------------------------
1 | import * as ActionTypes from './ActionTypes';
2 |
3 | const Books = (state = { isLoading: true,
4 | errMess: null,
5 | books:[]}, action) => {
6 | switch (action.type) {
7 | case ActionTypes.ADD_BOOKS:
8 | return {...state, isLoading: false, errMess: null, books: action.payload};
9 |
10 | case ActionTypes.BOOKS_LOADING:
11 | return {...state, isLoading: true, errMess: null, books: []}
12 |
13 | case ActionTypes.BOOKS_FAILED:
14 | return {...state, isLoading: false, errMess: action.payload};
15 |
16 | case ActionTypes.ADD_BOOK:
17 | var book = action.payload;
18 | return { ...state, books: state.books.concat(book)};
19 |
20 | case ActionTypes.EDIT_BOOK:
21 | var newbook = action.payload;
22 | return { ...state, books: state.books.map((book)=>
23 | {
24 | if(book._id===newbook._id)
25 | {
26 | return newbook;
27 | }
28 | else {
29 | return book;
30 | }
31 | })
32 | }
33 |
34 | case ActionTypes.DELETE_BOOK:
35 | var resp = action.payload;
36 | return { ...state, books: state.books.filter((book)=>{
37 | return book._id!==resp._id}) }
38 |
39 | default:
40 | return state;
41 | }
42 | };
43 | export default Books;
--------------------------------------------------------------------------------
/client/src/redux/issues.js:
--------------------------------------------------------------------------------
1 | import * as ActionTypes from './ActionTypes';
2 |
3 | const Issues = (state = { isLoading: true,
4 | errMess: null,
5 | issues:[]}, action) => {
6 | switch (action.type) {
7 | case ActionTypes.ADD_ISSUES:
8 | return {...state, isLoading: false, errMess: null, issues: action.payload};
9 |
10 | case ActionTypes.ISSUES_LOADING:
11 | return {...state, isLoading: true, errMess: null, issues: []}
12 |
13 | case ActionTypes.ISSUES_FAILED:
14 | return {...state, isLoading: false, errMess: action.payload};
15 |
16 | case ActionTypes.ADD_ISSUE:
17 | var issue = action.payload;
18 | return { ...state, issues: state.issues.concat(issue)};
19 |
20 | case ActionTypes.RETURN_ISSUE:
21 | var newissue = action.payload;
22 | return { ...state, issues: state.issues.map((issue)=>
23 | {
24 | if(issue._id===newissue._id)
25 | {
26 | return newissue;
27 | }
28 | else {
29 | return issue;
30 | }
31 | })
32 | }
33 |
34 | default:
35 | return state;
36 | }
37 | };
38 | export default Issues;
--------------------------------------------------------------------------------
/client/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import {createStore, combineReducers, applyMiddleware, compose} from 'redux';
2 | import thunk from 'redux-thunk';
3 | import Books from './books.js';
4 | import Auth from './auth.js';
5 | import Issues from './issues.js';
6 | import Users from './users.js'
7 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
8 |
9 | export const ConfigureStore = ()=>{
10 | const store=createStore(
11 | combineReducers({
12 | books: Books,
13 | auth: Auth,
14 | issues: Issues,
15 | users: Users
16 | }),
17 | composeEnhancers(applyMiddleware(thunk))
18 | );
19 | return store;
20 | }
--------------------------------------------------------------------------------
/client/src/redux/users.js:
--------------------------------------------------------------------------------
1 | import * as ActionTypes from './ActionTypes';
2 |
3 | const Users = (state = { isLoading: true,
4 | errMess: null,
5 | users:[]}, action) => {
6 | switch (action.type) {
7 | case ActionTypes.ADD_USERS:
8 | return {...state, isLoading: false, errMess: null, users: action.payload};
9 |
10 | case ActionTypes.USERS_LOADING:
11 | return {...state, isLoading: true, errMess: null, users: []}
12 |
13 | case ActionTypes.USERS_FAILED:
14 | return {...state, isLoading: false, errMess: action.payload};
15 |
16 | default:
17 | return state;
18 | }
19 | };
20 | export default Users;
--------------------------------------------------------------------------------
/client/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/config/keys.js:
--------------------------------------------------------------------------------
1 | module.exports={
2 | mongoURI: process.env.MONGO_URI,
3 | secretKey: process.env.SECRET_KEY
4 | }
5 |
--------------------------------------------------------------------------------
/models/books.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Schema = mongoose.Schema;
3 |
4 | const bookSchema = new Schema({
5 | name: {
6 | type: String,
7 | minlength: 3,
8 | required: true,
9 | unique: true
10 | },
11 | author: {
12 | type: String,
13 | required: true
14 | },
15 | description: {
16 | type: String,
17 | default: '----Not available----'
18 | },
19 | isbn: {
20 | type: String,
21 | minlength: 10,
22 | maxlength: 13,
23 | required: true,
24 | unique: true
25 | },
26 | cat: {
27 | type: String,
28 | enum: ['Romance','Technology','Computer Science','Management','Electronics','Physics','Chemistry','Mathematics','Fiction','Philosophy','Language','Arts','Other'],
29 | required: true
30 | },
31 | copies: {
32 | type: Number,
33 | min: 1,
34 | max: 1000,
35 | required: true
36 | },
37 | shelf: {
38 | type: Number,
39 | min: 1,
40 | max: 100,
41 | required: true
42 | },
43 | floor: {
44 | type: Number,
45 | min: 0,
46 | max: 8
47 | }
48 | }, {
49 | timestamps: true
50 | });
51 | var Books = mongoose.model('Book',bookSchema);
52 |
53 | module.exports=Books;
--------------------------------------------------------------------------------
/models/issues.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Schema = mongoose.Schema;
3 |
4 | const issueSchema = new Schema({
5 | student: {
6 | type: mongoose.Schema.Types.ObjectId,
7 | ref: 'User',
8 | required: true
9 | },
10 | book: {
11 | type: mongoose.Schema.Types.ObjectId,
12 | ref: 'Book',
13 | required: true
14 | },
15 | returned: {
16 | type: Boolean,
17 | default: false
18 | }
19 | }, {
20 | timestamps: true
21 | });
22 | var Issues = mongoose.model('Issue',issueSchema);
23 |
24 | module.exports=Issues;
--------------------------------------------------------------------------------
/models/users.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 | var passportLocalMongoose = require('passport-local-mongoose');
4 |
5 | var User = new Schema({
6 | firstname: {
7 | type: String,
8 | required: true,
9 | },
10 | lastname: {
11 | type: String,
12 | required: true,
13 | },
14 | email:{
15 | type: String,
16 | required: true,
17 | unique: true
18 | },
19 | roll:{
20 | type: String,
21 | required: true,
22 | unique: true
23 | },
24 | admin: {
25 | type: Boolean,
26 | default: false
27 | }
28 | });
29 |
30 | User.plugin(passportLocalMongoose);
31 | module.exports = mongoose.model('User', User);
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lib_management",
3 | "version": "1.0.0",
4 | "description": "Library Management Website using MERN stack",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "client-install": "npm install --prefix client",
9 | "start": "node server.js",
10 | "server": "nodemon server.js",
11 | "client": "npm start --prefix client",
12 | "dev": "concurrently \"npm run server\" \"npm run client\" ",
13 | "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client"
14 | },
15 | "author": "Chirag Jain",
16 | "license": "ISC",
17 | "dependencies": {
18 | "body-parser": "^1.19.0",
19 | "concurrently": "^6.2.1",
20 | "cors": "^2.8.5",
21 | "express": "^4.17.1",
22 | "font-awesome": "^4.7.0",
23 | "jsonwebtoken": "^8.5.1",
24 | "mongoose": "^6.0.4",
25 | "passport": "^0.4.1",
26 | "passport-jwt": "^4.0.0",
27 | "passport-local-mongoose": "^6.1.0"
28 | },
29 | "devDependencies": {
30 | "nodemon": "^2.0.12"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/routes/api/bookRouter.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const bodyParser = require('body-parser');
3 | const mongoose=require('mongoose');
4 | const bookRouter = express.Router();
5 | const authenticate=require('../../authenticate');
6 | const cors = require('../cors');
7 | const Books=require('../../models/books');
8 | bookRouter.use(bodyParser.json());
9 |
10 | bookRouter.route('/')
11 | .options(cors.corsWithOptions, (req, res) => { res.sendStatus(200); })
12 | .get(cors.corsWithOptions,(req,res,next) => {
13 | Books.find(req.query)
14 | .sort({name: 'asc'})
15 | .then((books)=>{
16 | res.statusCode=200;
17 | res.setHeader('Content-Type','application/json');
18 | res.json(books);
19 | },(err)=>(next(err)))
20 | .catch((err)=>(next(err)))
21 | })
22 | .post(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => {
23 | Books.create(req.body)
24 | .then((book)=>{
25 | res.statusCode=200;
26 | res.setHeader('Content-Type','application/json');
27 | res.json(book);
28 | },(err)=>(next(err)))
29 | .catch((err)=>(next(err)))
30 | })
31 | .put(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => {
32 | res.statusCode = 403;
33 | res.end('PUT operation not supported on /books');
34 | })
35 | .delete(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => {
36 | res.statusCode = 403;
37 | res.end('DELETE operation not supported on /books');
38 |
39 | /* Books.remove({})
40 | .then((resp) => {
41 | console.log("Removed All Books");
42 | res.statusCode = 200;
43 | res.setHeader('Content-Type', 'application/json');
44 | res.json(resp);
45 | }, (err) => next(err))
46 | .catch((err) => next(err));*/
47 | });
48 |
49 | bookRouter.route('/:bookId')
50 | .options(cors.corsWithOptions, (req, res) => { res.sendStatus(200);
51 | res.setHeader('Access-Control-Allow-Credentials', 'true')})
52 | .get(cors.corsWithOptions,(req,res,next) => {
53 | Books.findById(req.params.bookId)
54 | .then((book)=>{
55 | res.statusCode=200;
56 | res.setHeader('Content-Type','application/json');
57 | res.json(book);
58 | },(err)=>(next(err)))
59 | .catch((err)=>(next(err)));
60 | })
61 |
62 | .post(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => {
63 | res.statusCode = 403;
64 | res.end('POST operation not supported on /books/'+ req.params.bookId);
65 | })
66 |
67 | .put(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => {
68 | Books.findByIdAndUpdate(req.params.bookId,{
69 | $set: req.body
70 | },{new: true})
71 | .then((book) => {
72 | res.statusCode = 200;
73 | res.setHeader('Content-Type', 'application/json');
74 | res.json(book);
75 | }, (err) => next(err))
76 | .catch((err) => res.status(400).json({success: false}));
77 | })
78 |
79 | .delete(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => {
80 | Books.findByIdAndRemove(req.params.bookId)
81 | .then((resp) => {
82 | res.statusCode = 200;
83 | res.setHeader('Content-Type', 'application/json');
84 | res.json({_id: req.params.bookId,success: true});
85 | }, (err) => next(err))
86 | .catch((err) => res.status(400).json({success: false}));
87 | });
88 |
89 | module.exports = bookRouter;
--------------------------------------------------------------------------------
/routes/api/issueRouter.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | const bodyParser = require('body-parser');
3 | const issueRouter = express.Router();
4 | const mongoose=require('mongoose');
5 |
6 | var Issue = require('../../models/issues');
7 | var Books = require('../../models/books');
8 | var Users = require('../../models/users');
9 |
10 | var passport = require('passport');
11 | var authenticate = require('../../authenticate');
12 |
13 | const cors = require('../cors');
14 |
15 | issueRouter.use(bodyParser.json());
16 |
17 | issueRouter.route('/')
18 | .options(cors.corsWithOptions, (req, res) => { res.sendStatus(200); })
19 | .get(cors.corsWithOptions, authenticate.verifyUser
20 | ,authenticate.verifyAdmin,
21 | function(req, res, next) {
22 | Issue.find({})
23 | .populate('student')
24 | .populate('book')
25 | .then((issues)=>{
26 | res.statusCode=200;
27 | res.setHeader('Content-Type','application/json');
28 | res.json(issues);
29 | },(err)=>(next(err)))
30 | .catch((err)=>(next(err)))
31 | }
32 | )
33 |
34 | .post(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => {
35 | Books.findById(req.body.book)
36 | .then((requiredBook)=>{
37 | Users.findById(req.body.student)
38 | .then((requiredUser)=>{
39 |
40 | if(!requiredBook){
41 |
42 | err = new Error("Book doesn't exist");
43 | err.status = 400;
44 | return next(err);
45 | }
46 | else if(!requiredUser){
47 | err = new Error("Student doesn't exist");
48 | err.status = 400;
49 | return next(err);
50 | }
51 | else if(requiredBook._id&&requiredUser._id) {
52 | Issue.find({
53 | student: req.body.student
54 | })
55 | .then((issues)=>{
56 | notReturned=issues.filter((issue)=>(!issue.returned));
57 | if(notReturned&¬Returned.length>=3){
58 | err = new Error(`The student has already issued 3 books. Please return them first`);
59 | err.status = 400;
60 | return next(err);
61 | }
62 | else{
63 | if(requiredBook.copies>0){
64 | Issue.create(req.body, function(err, issue) {
65 | if (err) return next(err)
66 | Issue.findById(issue._id)
67 | .populate('student')
68 | .populate('book')
69 | .exec(function(err, issue) {
70 | if (err) return next(err)
71 | Books.findByIdAndUpdate(req.body.book,{
72 | $set: {copies: (requiredBook.copies-1)}
73 | },{new: true})
74 | .then((book) => {
75 | res.statusCode=200;
76 | res.setHeader('Content-Type','application/json');
77 | res.json(issue);
78 |
79 | }, (err) => next(err))
80 | .catch((err) => res.status(400).json({success: false}));
81 |
82 | })})
83 | }
84 | else {
85 | console.log(requiredBook);
86 | err = new Error(`The book is not available. You can wait for some days, until the book is returned to library.`);
87 | err.status = 400;
88 | return next(err);
89 | }
90 | }
91 | })
92 | .catch((err)=>(next(err))) ;
93 | }
94 |
95 |
96 |
97 |
98 | },(err)=>(next(err)))
99 | .catch((err)=>(next(err)))
100 |
101 | },(err)=>(next(err)))
102 | .catch((err)=>(next(err)))
103 |
104 | })
105 |
106 | .put(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => {
107 | res.statusCode = 403;
108 | res.end('PUT operation not supported on /issues');
109 | })
110 | .delete(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => {
111 | //res.statusCode = 403;
112 | //res.end('DELETE operation not supported on /issues');
113 |
114 | Issue.remove({})
115 | .then((resp) => {
116 | console.log("Removed All Issue");
117 | res.statusCode = 200;
118 | res.setHeader('Content-Type', 'application/json');
119 | res.json(resp);
120 | }, (err) => next(err))
121 | .catch((err) => next(err));
122 |
123 | })
124 | issueRouter.route('/student/')
125 | .options(cors.corsWithOptions, (req, res) => { res.sendStatus(200); })
126 | .get(cors.corsWithOptions,authenticate.verifyUser,(req,res,next) => {
127 | console.log("\n\n\n Object ID =====" +req.user._id);
128 | Issue.find({student: req.user._id})
129 | .populate('student')
130 | .populate('book')
131 | .then((issue)=>{
132 | res.statusCode=200;
133 | res.setHeader('Content-Type','application/json');
134 | res.json(issue);
135 |
136 | },(err)=>(next(err)))
137 | .catch((err)=>(next(err)))
138 | })
139 |
140 |
141 | issueRouter.route('/:issueId')
142 | .options(cors.corsWithOptions, (req, res) => { res.sendStatus(200); })
143 | .get(cors.corsWithOptions,authenticate.verifyUser,(req,res,next) => {
144 | Issue.findById(req.params.issueId)
145 | .populate('student')
146 | .populate('book')
147 | .then((issue)=>{
148 | if(issue&&(issue.student._id===req.user._id||req.user.admin))
149 | { res.statusCode=200;
150 | res.setHeader('Content-Type','application/json');
151 | res.json(issue);
152 | }
153 | else if(!issue){
154 | err = new Error(`Issue not found`);
155 | err.status = 404;
156 | return next(err);
157 |
158 | }
159 | else{
160 | err = new Error(`Unauthorised`);
161 | err.status = 401;
162 | return next(err);
163 |
164 | }
165 | },(err)=>(next(err)))
166 | .catch((err)=>(next(err)))
167 | })
168 |
169 | .post(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => {
170 | res.statusCode = 403;
171 | res.end('POST operation not supported on /issues/'+ req.params.issueId);
172 | })
173 |
174 | .delete(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => {
175 | res.statusCode = 403;
176 | res.end('POST operation not supported on /issues/'+ req.params.issueId);
177 | })
178 |
179 | .put(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => {
180 | Issue.findById(req.params.issueId)
181 | .then((issue)=>{
182 |
183 | Books.findById(issue.book)
184 | .then((requiredBook)=>{
185 | Issue.findByIdAndUpdate(req.params.issueId,{
186 | $set: {returned: true}
187 | },{new: true})
188 | .populate('student')
189 | .populate('book')
190 | .then((issue) => {
191 | Books.findByIdAndUpdate(issue.book,{
192 | $set: {copies: (requiredBook.copies+1)}
193 | },{new: true})
194 | .then((book) => {
195 | res.statusCode = 200;
196 | res.setHeader('Content-Type', 'application/json');
197 | res.json(issue);
198 | }, (err) => next(err))
199 | .catch((err) => res.status(400).json({success: false,message: "Book not updated"}));
200 |
201 | }, (err) => next(err))
202 | .catch((err) => res.status(400).json({success: false,message: "Issue not Updated"}));
203 |
204 | }, (err) => next(err))
205 | .catch((err) => res.status(400).json({success: false,message: "Book not found"}));
206 |
207 |
208 | }, (err) => next(err))
209 | .catch((err) => res.status(400).json({success: false,message: "Issue not found"}))
210 | })
211 |
212 |
213 | module.exports = issueRouter;
--------------------------------------------------------------------------------
/routes/api/userRouter.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 | const bodyParser = require('body-parser');
4 | var User = require('../../models/users');
5 | var passport = require('passport');
6 | var authenticate = require('../../authenticate');
7 |
8 | const cors = require('../cors');
9 |
10 | router.use(bodyParser.json());
11 |
12 | /* GET users listing. */
13 | router.options('*', cors.corsWithOptions, (req, res) => { res.sendStatus(200);
14 | res.setHeader('Access-Control-Allow-Credentials', 'true');} )
15 | router.get('/', cors.corsWithOptions, authenticate.verifyUser
16 | ,authenticate.verifyAdmin,
17 | function(req, res, next) {
18 | User.find({})
19 | .then((users)=>{
20 | res.statusCode=200;
21 | res.setHeader('Content-Type','application/json');
22 | res.json(users);
23 | },(err)=>(next(err)))
24 | .catch((err)=>(next(err)))
25 | }
26 | );
27 |
28 | router.put('/:userId',cors.corsWithOptions,authenticate.verifyUser,
29 | function(req,res,next){
30 | User.findByIdAndUpdate(req.params.userId,{
31 | $set: req.body
32 | },{new: true})
33 | .then((user) => {
34 | res.statusCode = 200;
35 | res.setHeader('Content-Type', 'application/json');
36 | res.json(user);
37 | }, (err) => next(err))
38 | .catch((err) => res.status(400).json({success: false}));
39 | })
40 |
41 | // For change of password
42 | router.put('/password/:userId',cors.corsWithOptions,authenticate.verifyUser,
43 | function(req,res,next){
44 | User.findById(req.params.userId)
45 | .then((user) => {
46 | if(user&&!user.admin){
47 | user.setPassword(req.body.password, function(){
48 |
49 | user.save();
50 | res.status(200).json({message: 'password changed successfully'});
51 | });
52 | }
53 | else if(!user){
54 | res.status(500).json({message: "User doesn't exist"});
55 | }
56 | else{
57 | res.status(400).json({message: "Password of an admin can't be changed this way.\nContact the webmaster"});
58 | }
59 | }, (err) => next(err))
60 | .catch((err) => res.status(400).json({message: 'Internal Server Error'}));
61 | })
62 |
63 |
64 | router.post('/signup',cors.corsWithOptions, (req, res, next) => {
65 | User.register(new User({username: req.body.username,
66 | firstname : req.body.firstname,
67 | lastname : req.body.lastname,
68 | email : req.body.email,
69 | roll : req.body.roll }),
70 | req.body.password, (err, user) => {
71 | if(err) {
72 | res.statusCode = 500;
73 | res.setHeader('Content-Type', 'application/json');
74 | res.json({err: err});
75 | }
76 | else {
77 |
78 | user.save((err, user) => {
79 | if (err) {
80 | res.statusCode = 500;
81 | res.setHeader('Content-Type', 'application/json');
82 | res.json({err: err});
83 | return ;
84 | }
85 | passport.authenticate('local')(req, res, () => {
86 | res.statusCode = 200;
87 | res.setHeader('Content-Type', 'application/json');
88 | res.json({success: true, status: 'Registration Successful!'});
89 | });
90 | });
91 | }
92 | });
93 | });
94 |
95 | router.post('/login',cors.corsWithOptions, passport.authenticate('local'), (req, res,next) => {
96 | passport.authenticate('local', (err, user, info) => {
97 | if (err)
98 | return next(err);
99 |
100 | if (!user) {
101 | res.statusCode = 401;
102 | res.setHeader('Content-Type', 'application/json');
103 | res.json({success: false, status: 'Login Unsuccessful!', err: info});
104 | }
105 | req.logIn(user, (err) => {
106 | if (err) {
107 | res.statusCode = 401;
108 | res.setHeader('Content-Type', 'application/json');
109 | res.json({success: false, status: 'Login Unsuccessful!', err: 'Could not log in user!'});
110 | }
111 |
112 | var token = authenticate.getToken({_id: req.user._id});
113 | res.statusCode = 200;
114 | res.setHeader('Content-Type', 'application/json');
115 | res.json({success: true, status: 'Login Successful!', token: token, userinfo: req.user});
116 | });
117 | }) (req, res, next); // function call IIFE
118 | });
119 |
120 | router.get('/logout',cors.cors, (req, res) => {
121 | if (req.session) {
122 | req.session.destroy();
123 | res.clearCookie('session-id');
124 | res.redirect('/');
125 | }
126 | else {
127 | var err = new Error('You are not logged in!');
128 | err.status = 403;
129 | next(err);
130 | }
131 | });
132 |
133 | router.get('/checkJWTtoken', cors.corsWithOptions, (req, res) => {
134 | passport.authenticate('jwt', {session: false}, (err, user, info) => {
135 | if (err)
136 | return next(err);
137 |
138 | if (!user) {
139 | res.statusCode = 401;
140 | res.setHeader('Content-Type', 'application/json');
141 | return res.json({status: 'JWT invalid!', success: false, err: info});
142 | }
143 | else {
144 | res.statusCode = 200;
145 | res.setHeader('Content-Type', 'application/json');
146 | return res.json({status: 'JWT valid!', success: true, user: user});
147 |
148 | }
149 | }) (req, res);
150 | });
151 |
152 | module.exports = router;
153 |
--------------------------------------------------------------------------------
/routes/cors.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const cors = require('cors');
3 | const app = express();
4 |
5 | const whitelist = ['http://localhost:3000', 'http://localhost:5000','https://lib-manage.herokuapp.com/','https://lib-manage.herokuapp.com:3000','https://lib-manage.herokuapp.com:5000'];
6 | var corsOptionsDelegate = (req, callback) => {
7 | var corsOptions;
8 | console.log(req.header('Origin'));
9 | if(whitelist.indexOf(req.header('Origin')) !== -1) {
10 | corsOptions = { origin: true };
11 | }
12 | else {
13 | corsOptions = { origin: false };
14 | }
15 | callback(null, corsOptions);
16 | };
17 |
18 | exports.cors = cors();
19 | exports.corsWithOptions = cors(corsOptionsDelegate);
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const express=require('express');
2 | const mongoose=require('mongoose');
3 | const bodyParser=require('body-parser');
4 | const path=require('path');
5 | var passport = require('passport');
6 | var authenticate = require('./authenticate');
7 |
8 |
9 | // Loading routers
10 | const bookRouter = require('./routes/api/bookRouter');
11 | const userRouter = require('./routes/api/userRouter');
12 | const issueRouter = require('./routes/api/issueRouter');
13 | const app= express();
14 |
15 | app.use(function(req, res, next) {
16 |
17 | res.header("Access-Control-Allow-Origin", "*");
18 |
19 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
20 |
21 | next();
22 |
23 | });
24 |
25 | // Bodyparser Middleware
26 | app.use(bodyParser.json());
27 |
28 | // DB config
29 | const mongoURI = require('./config/keys').mongoURI;
30 |
31 | // Connect to mongo
32 | mongoose.connect(mongoURI)
33 | .then(()=> {console.log("MongoDB Connected");})
34 | .catch(err => console.log(err));
35 |
36 | app.use(passport.initialize());
37 |
38 | // Use routes
39 | app.use('/api/books',bookRouter);
40 | app.use('/api/users',userRouter);
41 | app.use('/api/issues',issueRouter);
42 |
43 | // Serve static assets if in production
44 | if (process.env.NODE_ENV === 'production') {
45 | // Set static folder
46 | app.use(express.static('client/build'));
47 |
48 | app.get('*', (req, res) => {
49 | res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
50 | });
51 | }
52 |
53 | const port = process.env.PORT || 5000;
54 |
55 | app.listen(port, ()=> console.log(`Server started running on port ${port}`));
--------------------------------------------------------------------------------