├── .gitignore ├── Procfile ├── _config.yml ├── src ├── db │ └── mongoose.js ├── models │ ├── accountModel.js │ ├── flexBudgetModel.js │ ├── trialModel.js │ ├── budgetModel.js │ ├── inventoryModel.js │ ├── journalModel.js │ ├── ledgerModel.js │ └── userModel.js ├── middleware │ └── auth.js ├── routes │ ├── trialRoute.js │ ├── budgetRoute.js │ ├── finalAccRoute.js │ ├── userRoute.js │ ├── flexBudgetRoute.js │ ├── ledgerRoute.js │ ├── inventoryRoute.js │ └── journalRoute.js └── app.js ├── package.json ├── .github ├── workflows │ └── nodejs.yml └── FUNDING.yml ├── license └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node src/app.js -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-merlot -------------------------------------------------------------------------------- /src/db/mongoose.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const localDB = 'mongodb://localhost:27017/accountApp' 4 | mongoose.connect(remoteDB, { 5 | useNewUrlParser: true, 6 | useCreateIndex: true, 7 | useFindAndModify: false 8 | }) 9 | 10 | const db = mongoose.connection 11 | 12 | module.exports = db 13 | 14 | -------------------------------------------------------------------------------- /src/models/accountModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | const bcrypt = require("bcrypt") 3 | 4 | const accountSchema = new mongoose.Schema({ 5 | name: { 6 | type: String, 7 | required: true, 8 | trim: true 9 | }, 10 | user: { 11 | type: mongoose.Schema.Types.ObjectId, 12 | ref: "User" 13 | } 14 | }, { 15 | timestamps: true 16 | }) 17 | 18 | const Account = mongoose.model("Account", accountSchema) 19 | 20 | module.exports = Account -------------------------------------------------------------------------------- /src/models/flexBudgetModel.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | var flexBudgetSchema = new Schema({ 5 | name: String, 6 | type: String, 7 | cost: { 8 | type: Number, 9 | default: undefined 10 | }, 11 | costPerUnit: { 12 | type: Number, 13 | default: undefined 14 | }, 15 | user: { 16 | type: mongoose.Schema.Types.ObjectId, 17 | ref: "User", 18 | required: true, 19 | trim: true 20 | } 21 | }); 22 | 23 | module.exports = mongoose.model('flexBudget', flexBudgetSchema); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "accounts-app", 3 | "version": "1.0.0", 4 | "description": "An accounting API", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon src/app.js" 8 | }, 9 | "author": "Deepak Sharma", 10 | "license": "ISC", 11 | "dependencies": { 12 | "bcrypt": "^5.0.0", 13 | "connect-mongo": "^3.2.0", 14 | "express": "^4.17.1", 15 | "express-session": "^1.17.1", 16 | "express-validator": "^6.9.2", 17 | "helmet": "^4.3.1", 18 | "moment": "^2.29.1", 19 | "mongoose": "^5.11.12", 20 | "validator": "^13.5.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [8.x, 10.x, 12.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: npm install, build, and test 21 | run: | 22 | npm install 23 | npm run build --if-present 24 | npm test 25 | env: 26 | CI: true 27 | -------------------------------------------------------------------------------- /src/models/trialModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const trialSchema = new mongoose.Schema({ 4 | creditBalance: { 5 | type: Number, 6 | required: true 7 | }, 8 | debitBalance: { 9 | type: Number, 10 | required: true 11 | }, 12 | ledger: [{ 13 | account: { 14 | name: { 15 | type: String 16 | } 17 | }, 18 | balance: { 19 | type: { 20 | type: String, 21 | default: undefined 22 | }, 23 | amount: { 24 | type: Number, 25 | default: 0 26 | } 27 | } 28 | }], 29 | user: { 30 | type: mongoose.Schema.Types.ObjectId, 31 | ref: "User" 32 | } 33 | }) 34 | 35 | const Trial = mongoose.model("Trial", trialSchema) 36 | 37 | module.exports = Trial -------------------------------------------------------------------------------- /src/models/budgetModel.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | var BudgetSchema = new Schema({ 5 | payementOfFixedAssets: String, 6 | payementToCreditors: String, 7 | recievedOfDebators: String, 8 | payementOfExpenses: String, 9 | payementOfDividend: String, 10 | saleOfInvestments: String, 11 | saleOfFixedAsset: String, 12 | payementOfBonus: String, 13 | payementOfWages: String, 14 | clossingStock: String, 15 | payementOfTax: String, 16 | openingStock: String, 17 | saleOfShares: String, 18 | budgetMonth: String, 19 | cashSales: String, 20 | dividends: String, 21 | interest: String, 22 | borrowing: String 23 | }); 24 | 25 | module.exports = mongoose.model('Budget', BudgetSchema); -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /src/models/inventoryModel.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | var InventorySchema = new Schema({ 5 | name: { 6 | type: String, 7 | required: true, 8 | trim: true 9 | }, 10 | category: { 11 | type: String, 12 | required: true, 13 | trim: true 14 | }, 15 | quantity:{ 16 | type: Number, 17 | default: 0 18 | }, 19 | cost: { 20 | type: Number, 21 | default: 0 22 | }, 23 | expiry: { 24 | type: Date 25 | }, 26 | thresholdQuantity: { 27 | type: Number, 28 | default: 0 29 | }, 30 | user: { 31 | type: mongoose.Schema.Types.ObjectId, 32 | ref: "User", 33 | required: true, 34 | trim: true 35 | } 36 | }); 37 | 38 | module.exports = mongoose.model('Inventory', InventorySchema); -------------------------------------------------------------------------------- /src/models/journalModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const JournalSchema = new mongoose.Schema({ 4 | from: { 5 | type: mongoose.Schema.Types.ObjectId, 6 | required: true, 7 | ref: "Account" 8 | }, 9 | to: { 10 | type: mongoose.Schema.Types.ObjectId, 11 | required: true, 12 | ref: "Account" 13 | }, 14 | date: { 15 | type: Date, 16 | required: true 17 | }, 18 | debit: { 19 | type: Number, 20 | required: true 21 | }, 22 | credit: { 23 | type: Number, 24 | required: true 25 | }, 26 | narration: [{ 27 | type: String, 28 | trim: true 29 | }], 30 | user: { 31 | type: mongoose.Schema.Types.ObjectId, 32 | ref: "User" 33 | }, 34 | addedToLedger: { 35 | type: Boolean, 36 | default: false 37 | } 38 | }, { 39 | timestamps: true 40 | }) 41 | 42 | const Journal = mongoose.model("Journal", JournalSchema) 43 | 44 | module.exports = Journal -------------------------------------------------------------------------------- /src/models/ledgerModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const ledgerSchema = new mongoose.Schema({ 4 | account: { 5 | type: mongoose.Schema.Types.ObjectId, 6 | required: true, 7 | unique: true, 8 | ref: "Account" 9 | }, 10 | debits: [{ 11 | _id: false, 12 | to: { 13 | _id: false, 14 | type: mongoose.Schema.Types.ObjectId, 15 | ref: "Account" 16 | }, 17 | amount: { 18 | type: Number 19 | } 20 | }], 21 | credits: [{ 22 | _id: false, 23 | from: { 24 | _id: false, 25 | type: mongoose.Schema.Types.ObjectId, 26 | ref: "Account" 27 | }, 28 | amount: { 29 | type: Number 30 | } 31 | }], 32 | balance: { 33 | _id: false, 34 | type: { 35 | type: String, 36 | default: undefined 37 | }, 38 | amount: { 39 | type: Number, 40 | default: 0 41 | } 42 | }, 43 | user: { 44 | type: mongoose.Schema.Types.ObjectId, 45 | ref: "User" 46 | } 47 | }) 48 | 49 | const Ledger = mongoose.model("Ledger", ledgerSchema) 50 | 51 | module.exports = Ledger -------------------------------------------------------------------------------- /src/middleware/auth.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/userModel") 2 | 3 | // Function to check whether the user is logged in 4 | async function isUserLoggedIn (req, res, next) { 5 | try { 6 | if (!(req.session && req.session.user)) { 7 | return res.status(401).send({ 8 | error: "Unauthorized Access!" 9 | }); 10 | }else { 11 | const user = await User.findOne({ _id : req.session.user._id }) 12 | if(user) { 13 | next(); 14 | } else { 15 | req.session.user = null; 16 | return res.status(401).send({ 17 | error: "Unauthorized Access!" 18 | }); 19 | } 20 | } 21 | } catch(e) { 22 | res.status(400).send({ 23 | error: e 24 | }) 25 | } 26 | } 27 | 28 | 29 | // Function to check whether the user is logged out 30 | function isUserLoggedOut (req, res, next) { 31 | if (req.session && req.session.user) { 32 | return res.status(200).send({ 33 | message: "User already Logged In!" 34 | }); 35 | } 36 | next(); 37 | } 38 | 39 | module.exports = { 40 | isUserLoggedIn, 41 | isUserLoggedOut 42 | } -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Adhikansh Mittal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/models/userModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | const bcrypt = require("bcrypt") 3 | 4 | const userSchema = new mongoose.Schema({ 5 | id: { 6 | type: String, 7 | unique: true, 8 | required: true, 9 | trim: true 10 | }, 11 | password: { 12 | type: String, 13 | required: true, 14 | trim: true 15 | } 16 | }) 17 | 18 | userSchema.methods.toJSON = function () { 19 | const user = this 20 | 21 | const userObject = user.toObject() 22 | 23 | delete userObject.password 24 | 25 | return userObject 26 | } 27 | 28 | userSchema.statics.authenticate = async (id, password) => { 29 | const user = await User.findOne({ id }) 30 | 31 | if(!user){ 32 | return null 33 | } 34 | 35 | const isMatch = await bcrypt.compare(password, user.password) 36 | 37 | if(!isMatch){ 38 | return null 39 | } 40 | 41 | return user 42 | } 43 | 44 | userSchema.pre("save", async function(next) { 45 | const user = this 46 | 47 | if(user.isModified("password")) 48 | { 49 | user.password = await bcrypt.hash(user.password, 8) 50 | } 51 | 52 | next() 53 | }) 54 | 55 | const User = mongoose.model("User", userSchema) 56 | 57 | module.exports = User -------------------------------------------------------------------------------- /src/routes/trialRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | 3 | const Ledger = require("../models/ledgerModel") 4 | const Trial = require("../models/trialModel") 5 | const { isUserLoggedIn } = require("../middleware/auth.js") 6 | 7 | const router = new express.Router() 8 | 9 | router.get("/", isUserLoggedIn, async (req, res) => { 10 | try{ 11 | let ledger = await Ledger.find({ user: req.session.user._id }).select("account balance -_id") 12 | let creditBalance = 0 13 | let debitBalance = 0 14 | 15 | for (const account of ledger) { 16 | await account.populate("account", "name -_id").execPopulate() 17 | 18 | if(account.balance.type === "debit") { 19 | debitBalance += account.balance.amount 20 | } else if(account.balance.type === "credit"){ 21 | creditBalance += account.balance.amount 22 | } 23 | } 24 | 25 | const newTrial = new Trial({ 26 | creditBalance, 27 | debitBalance, 28 | ledger, 29 | user: req.session.user._id 30 | }) 31 | 32 | await newTrial.save() 33 | 34 | res.status(200).send({ 35 | creditBalance, 36 | debitBalance, 37 | ledger 38 | }) 39 | 40 | } catch(e) { 41 | res.status(500).send({ 42 | error: e 43 | }) 44 | } 45 | 46 | }) 47 | 48 | module.exports = router 49 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | const helmet = require("helmet") 2 | const express = require("express") 3 | const db = require("./db/mongoose.js") 4 | const session = require("express-session") 5 | const MongoStore = require("connect-mongo")(session) 6 | 7 | const journalRoute = require("./routes/journalRoute") 8 | const ledgerRoute = require("./routes/ledgerRoute") 9 | const trialRoute = require("./routes/trialRoute") 10 | const finalRoute = require("./routes/finalAccRoute") 11 | const userRoute = require("./routes/userRoute") 12 | const budgetRoute = require("./routes/budgetRoute") 13 | const flexBudgetRoute = require("./routes/flexBudgetRoute") 14 | const inventoryRoute = require("./routes/inventoryRoute") 15 | 16 | const app = express() 17 | 18 | const port = process.env.PORT || 3000 19 | 20 | app.enable('trust proxy'); 21 | 22 | app.use(helmet()) 23 | 24 | app.use(express.urlencoded({ extended: true })) 25 | 26 | //setup express-session middleware 27 | app.use(session({ 28 | secret: 'Xy12MIbneRt Weasd3', 29 | resave: true, 30 | saveUninitialized: true, 31 | cookie: { 32 | expires: new Date(Date.now() + 60 * 60 * 3000) 33 | }, 34 | store: new MongoStore({ 35 | mongooseConnection: db 36 | }) 37 | })); 38 | 39 | app.get("/", (req, res) => { 40 | res.status(200).send({ 41 | message: "Welcome!" 42 | }) 43 | }) 44 | 45 | app.use("/budget", budgetRoute) 46 | app.use("/flexBudget", flexBudgetRoute) 47 | app.use("/inventory", inventoryRoute) 48 | app.use("/journal", journalRoute) 49 | app.use("/ledger", ledgerRoute) 50 | app.use("/trial", trialRoute) 51 | app.use("/final", finalRoute) 52 | app.use("/user", userRoute) 53 | 54 | app.listen(port, () => { 55 | console.log(`Server started at port ${port}.`) 56 | }) 57 | -------------------------------------------------------------------------------- /src/routes/budgetRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | 3 | const Budget = require("../models/budgetModel") 4 | const { isUserLoggedIn } = require("../middleware/auth.js") 5 | 6 | const router = new express.Router() 7 | 8 | router.route("/add").post(isUserLoggedIn, async function (req, res) { 9 | 10 | var budgetNew = new Budget(); 11 | budgetNew.budgetMonth = req.body.budgetMonth; 12 | budgetNew.openingStock = req.body.openingStock; 13 | budgetNew.clossingStock = req.body.clossingStock; 14 | budgetNew.cashSales = req.body.cashSales; 15 | budgetNew.recievedOfDebators = req.body.recievedOfDebators; 16 | budgetNew.dividends = req.body.dividends; 17 | budgetNew.interest = req.body.interest; 18 | budgetNew.saleOfFixedAsset = req.body.saleOfFixedAsset; 19 | budgetNew.saleOfShares = req.body.saleOfShares; 20 | budgetNew.borrowing = req.body.borrowing; 21 | budgetNew.saleOfInvestments = req.body.saleOfInvestments; 22 | budgetNew.payementToCreditors = req.body.payementToCreditors; 23 | budgetNew.payementOfWages = req.body.payementOfWages; 24 | budgetNew.payementOfExpenses = req.body.payementOfExpenses; 25 | budgetNew.payementOfDividend = req.body.payementOfDividend; 26 | budgetNew.payementOfFixedAssets = req.body.payementOfFixedAssests; 27 | budgetNew.payementOfTax = req.body.payementOfTax; 28 | budgetNew.payementOfBonus = req.body.payementOfBonus; 29 | 30 | await budgetNew.save() 31 | 32 | res.send({ success: "Budget inserted successfully!"}) 33 | }); 34 | 35 | router.route("/list").get(isUserLoggedIn, async function (req, res) { 36 | const budgets = await Budget.find() 37 | 38 | res.send({ budgets }) 39 | }) 40 | 41 | module.exports = router 42 | -------------------------------------------------------------------------------- /src/routes/finalAccRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | 3 | const Ledger = require("../models/ledgerModel") 4 | const Trial = require("../models/trialModel") 5 | const { isUserLoggedIn } = require("../middleware/auth.js") 6 | 7 | const router = new express.Router() 8 | 9 | router.get("/pnl", isUserLoggedIn, async (req, res) => { 10 | try{ 11 | let ledger = await Ledger.find({ user: req.session.user._id }).select("account balance -_id") 12 | let creditBalance = 0 13 | let debitBalance = 0 14 | 15 | for (const account of ledger) { 16 | await account.populate("account", "name -_id").execPopulate() 17 | 18 | if(account.balance.type === "debit") { 19 | debitBalance += account.balance.amount 20 | } else if(account.balance.type === "credit"){ 21 | creditBalance += account.balance.amount 22 | } 23 | } 24 | 25 | let profit, loss 26 | 27 | if(creditBalance > debitBalance) { 28 | profit = creditBalance - debitBalance 29 | loss = 0 30 | } else if(debitBalance > creditBalance){ 31 | loss = debitBalance - creditBalance 32 | profit = 0 33 | } else { 34 | profit = loss = 0 35 | } 36 | 37 | res.status(200).send({ 38 | profit, 39 | loss, 40 | ledger 41 | }) 42 | 43 | } catch(e) { 44 | res.status(500).send({ 45 | error: e 46 | }) 47 | } 48 | 49 | }) 50 | 51 | router.get("/balance", isUserLoggedIn, async (req, res) => { 52 | try{ 53 | let ledger = await Ledger.find({ user: req.session.user._id }).select("account balance -_id") 54 | let assets = [], liabilities = [] 55 | let creditBalance = 0 56 | let debitBalance = 0 57 | 58 | 59 | for (const account of ledger) { 60 | await account.populate("account", "name -_id").execPopulate() 61 | 62 | if(account.balance.type === "debit") { 63 | assets.push(account) 64 | debitBalance += account.balance.amount 65 | } else if(account.balance.type === "credit"){ 66 | liabilities.push(account) 67 | creditBalance += account.balance.amount 68 | 69 | } 70 | } 71 | 72 | let profit, loss 73 | 74 | if(creditBalance > debitBalance) { 75 | profit = creditBalance - debitBalance 76 | loss = 0 77 | } else if(debitBalance > creditBalance){ 78 | loss = debitBalance - creditBalance 79 | profit = 0 80 | } else { 81 | profit = loss = 0 82 | } 83 | 84 | res.status(200).send({ 85 | assets, 86 | liabilities, 87 | profit, 88 | loss 89 | }) 90 | 91 | } catch(e) { 92 | res.status(500).send({ 93 | error: e 94 | }) 95 | } 96 | 97 | }) 98 | 99 | 100 | module.exports = router 101 | -------------------------------------------------------------------------------- /src/routes/userRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const validator = require("validator") 3 | const mongoose = require("mongoose") 4 | 5 | const User = require("../models/userModel") 6 | const { isUserLoggedOut, isUserLoggedIn } = require("../middleware/auth.js") 7 | const { check, validationResult } = require("express-validator/check"); 8 | 9 | const router = new express.Router() 10 | 11 | router.post("/login", isUserLoggedOut, [ 12 | check("id").not().isEmpty().withMessage("Please provide Id.").trim().escape(), 13 | check("password").not().isEmpty().withMessage("Please provide password.").trim().escape() 14 | ], async (req, res) => { 15 | try { 16 | const errors = validationResult(req) 17 | 18 | if(!errors.isEmpty()){ 19 | return res.status(400).send({ 20 | error: "Bad request!", 21 | message: errors.array() 22 | }) 23 | } 24 | 25 | let id = req.body.id 26 | let password = req.body.password 27 | 28 | const user = await User.authenticate(id, password) 29 | 30 | if(!user){ 31 | return res.status(400).send({ 32 | error: "Invalid Id or password!" 33 | }) 34 | } 35 | 36 | req.session.user = user 37 | 38 | res.status(200).send({ 39 | success: "User Logged In!" 40 | }) 41 | 42 | } catch(e) { 43 | res.status(400).send({ 44 | message: "Unable to login!", 45 | error: e 46 | }) 47 | } 48 | }) 49 | 50 | router.post("/register", isUserLoggedOut, [ 51 | check("id").not().isEmpty().withMessage("Please provide Id.").trim().escape(), 52 | check("password").not().isEmpty().withMessage("Please provide password.").trim().escape() 53 | ], async (req, res) => { 54 | try { 55 | const errors = validationResult(req) 56 | 57 | if(!errors.isEmpty()){ 58 | return res.status(400).send({ 59 | error: "Bad request!", 60 | message: errors.array() 61 | }) 62 | } 63 | 64 | let id = req.body.id 65 | let password = req.body.password 66 | 67 | let user = await User.findOne({ id }) 68 | 69 | if(user){ 70 | return res.status(400).send({ 71 | error: "User already exists!" 72 | }) 73 | } 74 | 75 | user = new User() 76 | 77 | user.id = id 78 | user.password = password 79 | 80 | await user.save() 81 | 82 | req.session.user = user 83 | 84 | res.status(200).send({ 85 | success: "User registered and Logged In!" 86 | }) 87 | 88 | } catch(e) { 89 | res.status(400).send({ 90 | message: "Unable to register!", 91 | error: e 92 | }) 93 | } 94 | }) 95 | 96 | router.post("/logout", isUserLoggedIn, async (req, res) => { 97 | try { 98 | req.session.destroy() 99 | 100 | res.status(200).send({ 101 | success: "User Logged Out!" 102 | }) 103 | 104 | } catch(e) { 105 | res.status(400).send({ 106 | message: "Unable to logout!", 107 | error: e 108 | }) 109 | } 110 | }) 111 | 112 | module.exports = router 113 | -------------------------------------------------------------------------------- /src/routes/flexBudgetRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const { check, validationResult } = require("express-validator/check"); 3 | 4 | const flexBudget = require("../models/flexBudgetModel") 5 | const { isUserLoggedIn } = require("../middleware/auth.js") 6 | 7 | const router = new express.Router() 8 | 9 | router.route("/add").post(isUserLoggedIn, [ 10 | check("name").not().isEmpty().withMessage("Please provide 'name'!").trim().escape(), 11 | check("type").not().isEmpty().withMessage("Please provide 'type'!").custom((value) => ["fixed", "variable"].includes(value)).withMessage("'type' can be 'fixed' or 'variable'!").trim().escape() 12 | ], async function (req, res) { 13 | 14 | const errors = validationResult(req) 15 | 16 | if(!errors.isEmpty()) { 17 | return res.status(400).send({ 18 | error: "Bad request!", 19 | message: errors.array() 20 | }) 21 | } 22 | 23 | let name = req.body.name.toLowerCase() 24 | let type = req.body.type.toLowerCase() 25 | let costPerUnit, cost 26 | 27 | const entry = await flexBudget.findOne({ name, type, user: req.session.user._id }) 28 | 29 | if(entry) { 30 | return res.status(302).send({message: "Entry already exists!"}) 31 | } 32 | 33 | if(type == "fixed") { 34 | if(Number(req.body.cost) && !req.body.costPerUnit) { 35 | cost = req.body.cost 36 | } else { 37 | return res.status(400).send({ message: "Fixed expenses can have only fixed cost!"}) 38 | } 39 | } else if(type == "variable") { 40 | if(Number(req.body.costPerUnit) && !req.body.cost) { 41 | costPerUnit = req.body.costPerUnit 42 | } else { 43 | return res.status(400).send({ message: "Variable expenses can have only costPerUnit!"}) 44 | } 45 | } 46 | 47 | var newEntry = new flexBudget({ 48 | name, 49 | type, 50 | cost, 51 | costPerUnit, 52 | user: req.session.user._id 53 | }); 54 | 55 | await newEntry.save() 56 | 57 | res.send({ success: "New expense inserted in flexible budget successfully!"}) 58 | }); 59 | 60 | router.route("/").post(isUserLoggedIn, [ 61 | check("units").not().isEmpty().withMessage("Please provide 'units'!").custom((value) => Number(value)).withMessage("'units' can be a number only!").trim().escape() 62 | ], async function (req, res) { 63 | 64 | const errors = validationResult(req) 65 | 66 | if(!errors.isEmpty()) { 67 | return res.status(400).send({ 68 | error: "Bad request!", 69 | message: errors.array() 70 | }) 71 | } 72 | 73 | let units = Number(req.body.units) 74 | let totalCostPerUnit = 0, totalCost = 0, cPU, cost 75 | 76 | const entries = await flexBudget.find({ user: req.session.user._id }).select("-_id -__v -user") 77 | 78 | for (const entry of entries) { 79 | if(entry.type == "fixed") { 80 | totalCost += entry.cost 81 | cPU = entry.cost / units 82 | totalCostPerUnit += cPU 83 | } else if(entry.type == "variable") { 84 | totalCostPerUnit += entry.costPerUnit 85 | cost = entry.costPerUnit * units 86 | totalCost += cost 87 | } 88 | } 89 | 90 | res.send({ totalCost, totalCostPerUnit, entries }) 91 | }); 92 | 93 | module.exports = router 94 | -------------------------------------------------------------------------------- /src/routes/ledgerRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | 3 | const Ledger = require("../models/ledgerModel") 4 | const Journal = require("../models/journalModel") 5 | const { isUserLoggedIn } = require("../middleware/auth.js") 6 | const router = new express.Router() 7 | 8 | router.get("/", isUserLoggedIn, async (req, res) => { 9 | try{ 10 | 11 | let ledger = await Ledger.find({ user: req.session.user._id }).select("account debits credits") 12 | let creditBalance = 0 13 | let debitBalance = 0 14 | let balance = {} 15 | 16 | for (const account of ledger) { 17 | await account.populate("account", "name -_id").execPopulate() 18 | await account.populate("debits.to", "name -_id").execPopulate() 19 | await account.populate("credits.from", "name -_id").execPopulate() 20 | 21 | for await (const entry of account.debits) { 22 | debitBalance += entry.amount 23 | } 24 | 25 | for await (const entry of account.credits) { 26 | creditBalance += entry.amount 27 | } 28 | 29 | account.balance = { 30 | type : (creditBalance > debitBalance) ? "credit" : "debit", 31 | amount: (creditBalance > debitBalance) ? creditBalance - debitBalance : debitBalance - creditBalance, 32 | } 33 | 34 | await account.save() 35 | 36 | delete account._doc._id 37 | 38 | creditBalance = debitBalance = 0 39 | } 40 | 41 | 42 | 43 | res.status(200).send({ 44 | ledger 45 | }) 46 | 47 | } catch(e) { 48 | res.status(500).send({ 49 | error: e 50 | }) 51 | } 52 | 53 | }) 54 | 55 | 56 | router.get("/update", isUserLoggedIn, async (req, res) => { 57 | try{ 58 | const journal = await Journal.find({ user: req.session.user._id, addedToLedger: false }) 59 | 60 | let ledgerFrom, ledgerTo 61 | 62 | for (const entry of journal) { 63 | ledgerFrom = await Ledger.findOne({ account: entry.from._id, user: req.session.user._id }) 64 | 65 | if(!ledgerFrom) { 66 | let newLedgerAcc = new Ledger({ 67 | account: entry.from._id, 68 | user: req.session.user._id 69 | }) 70 | await newLedgerAcc.save() 71 | ledgerFrom = newLedgerAcc 72 | } 73 | 74 | ledgerFrom.debits.push({ 75 | record: entry._id, 76 | to: entry.to._id, 77 | amount: entry.debit 78 | }) 79 | 80 | await ledgerFrom.save() 81 | 82 | let ledgerTo = await Ledger.findOne({ account: entry.to._id, user: req.session.user._id }) 83 | 84 | if(!ledgerTo) { 85 | let newLedgerAcc = new Ledger({ 86 | account: entry.to._id, 87 | user: req.session.user._id 88 | }) 89 | await newLedgerAcc.save() 90 | ledgerTo = newLedgerAcc 91 | } 92 | 93 | ledgerTo.credits.push({ 94 | record: entry._id, 95 | from: entry.from._id, 96 | amount: entry.credit 97 | }) 98 | 99 | await ledgerTo.save() 100 | 101 | entry.addedToLedger = true 102 | await entry.save() 103 | } 104 | 105 | let ledger = await Ledger.find({ user: req.session.user._id }).select("account debits credits") 106 | let creditBalance = 0 107 | let debitBalance = 0 108 | let balance = {} 109 | 110 | for (const account of ledger) { 111 | await account.populate("account", "name -_id").execPopulate() 112 | await account.populate("debits.to", "name -_id").execPopulate() 113 | await account.populate("credits.from", "name -_id").execPopulate() 114 | 115 | for await (const entry of account.debits) { 116 | debitBalance += entry.amount 117 | } 118 | 119 | for await (const entry of account.credits) { 120 | creditBalance += entry.amount 121 | } 122 | 123 | account.balance = { 124 | type : (creditBalance > debitBalance) ? "credit" : "debit", 125 | amount: (creditBalance > debitBalance) ? creditBalance - debitBalance : debitBalance - creditBalance, 126 | } 127 | 128 | await account.save() 129 | 130 | delete account._doc._id 131 | 132 | creditBalance = debitBalance = 0 133 | } 134 | 135 | 136 | 137 | res.status(200).send({ 138 | ledger 139 | }) 140 | 141 | } catch(e) { 142 | res.status(500).send({ 143 | error: e 144 | }) 145 | } 146 | 147 | }) 148 | module.exports = router -------------------------------------------------------------------------------- /src/routes/inventoryRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const moment = require("moment") 3 | const { check, validationResult } = require("express-validator/check"); 4 | 5 | const Inventory = require("../models/inventoryModel") 6 | const { isUserLoggedIn } = require("../middleware/auth.js") 7 | 8 | const router = new express.Router() 9 | 10 | router.route("/add").post(isUserLoggedIn, [ 11 | check("name").not().isEmpty().withMessage("Please provide inventory 'name' !").trim().escape(), 12 | check("category").not().isEmpty().withMessage("Please provide 'category' !").trim().escape(), 13 | check("quantity").not().isEmpty().withMessage("Please provide 'quantity' !").custom((value) => Number(value)).withMessage("'quantity' must be a number!").trim().escape(), 14 | check("thresholdQuantity").not().isEmpty().withMessage("Please provide 'quantity' !").custom((value) => Number(value)).withMessage("'thresholdQuantity' must be a number!").trim().escape(), 15 | check("expiry").not().isEmpty().withMessage("Provide 'expiry' field!").trim(), 16 | check("cost").not().isEmpty().withMessage("Please provide 'cost' !").custom((value) => Number(value)).withMessage("'cost' must be a number!").trim().escape() 17 | ], async function (req, res) { 18 | try { 19 | const errors = validationResult(req) 20 | 21 | if(!errors.isEmpty()) { 22 | return res.status(400).send({ 23 | error: "Bad request!", 24 | message: errors.array() 25 | }) 26 | } 27 | 28 | let expiry = new Date(req.body.expiry) 29 | 30 | if(isNaN(expiry)) { 31 | return res.status(400).send({ message: "Bad request!", error: "Invalid expiry date, use format like '05 September 2019'"}) 32 | } 33 | 34 | let name = req.body.name.toLowerCase() 35 | let category = req.body.category.toLowerCase() 36 | let quantity = Number(req.body.quantity) 37 | let thresholdQuantity = Number(req.body.thresholdQuantity) 38 | let cost = Number(req.body.cost) 39 | let user = req.session.user._id 40 | 41 | let item = await Inventory.findOne({ name, category, user }) 42 | 43 | if(item) { 44 | return res.status(400).send({ message: "Item already present in the given category!"}) 45 | } 46 | 47 | item = new Inventory({ 48 | name, 49 | category, 50 | quantity, 51 | thresholdQuantity, 52 | expiry, 53 | cost, 54 | user 55 | }) 56 | 57 | await item.save() 58 | 59 | item = item.toObject() 60 | 61 | item.expiry = item.expiry.toDateString() 62 | 63 | delete item._id 64 | delete item.user 65 | delete item.__v 66 | 67 | res.status(200).send({ message: "Item added to inventory!", item }) 68 | 69 | } catch(e) { 70 | res.status(400).send({ message: "Something went wrong!", error: e }) 71 | } 72 | }); 73 | 74 | router.route("/update").post(isUserLoggedIn, [ 75 | check("name").not().isEmpty().withMessage("Please provide inventory 'name' !").trim().escape(), 76 | check("category").not().isEmpty().withMessage("Please provide 'category' !").trim().escape(), 77 | check("usedQuantity").not().isEmpty().withMessage("Please provide 'quantity' !").custom((value) => Number(value)).withMessage("'quantity' must be a number!").trim().escape() 78 | ], async function (req, res) { 79 | try { 80 | const errors = validationResult(req) 81 | 82 | if(!errors.isEmpty()) { 83 | return res.status(400).send({ 84 | error: "Bad request!", 85 | message: errors.array() 86 | }) 87 | } 88 | 89 | let name = req.body.name.toLowerCase() 90 | let category = req.body.category.toLowerCase() 91 | let usedQuantity = Number(req.body.usedQuantity) 92 | let user = req.session.user._id 93 | 94 | let item = await Inventory.findOne({ name, category, user }) 95 | 96 | if(!item) { 97 | return res.status(404).send({ message: "No such item in inventory found!"}) 98 | } 99 | 100 | if(usedQuantity > item.quantity) { 101 | return res.status(400).send({ message: "Used quantity is more than present quantity!", quantity: item.quantity, usedQuantity }) 102 | } 103 | 104 | item.quantity -= usedQuantity 105 | 106 | await item.save() 107 | 108 | item = item.toObject() 109 | 110 | delete item._id 111 | delete item.user 112 | delete item.__v 113 | 114 | res.status(200).send({ message: "Item updated successfully!", item }) 115 | 116 | } catch(e) { 117 | res.status(400).send({ message: "Something went wrong!", error: e }) 118 | } 119 | res.send({ success: "Budget inserted successfully!"}) 120 | }); 121 | 122 | router.route("/").get(isUserLoggedIn, async function (req, res) { 123 | try{ 124 | const inventory = await Inventory.find({ user: req.session.user._id }).select("-user -__v -_id") 125 | 126 | for (const item of inventory) { 127 | item.expiry = item.expiry.toDateString() 128 | } 129 | 130 | res.status(200).send({ inventory }) 131 | } catch(e) { 132 | res.status(500).send({ message: "Something went wrong!", error: e }) 133 | } 134 | }) 135 | 136 | module.exports = router 137 | -------------------------------------------------------------------------------- /src/routes/journalRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const validator = require("validator") 3 | const moment = require("moment") 4 | const mongoose = require("mongoose") 5 | 6 | const Journal = require("../models/journalModel") 7 | const Ledger = require("../models/ledgerModel") 8 | const Account = require("../models/accountModel") 9 | const { isUserLoggedIn } = require("../middleware/auth.js") 10 | const { check, validationResult } = require("express-validator/check"); 11 | 12 | const router = new express.Router() 13 | 14 | router.get("/", isUserLoggedIn, async (req, res) => { 15 | try{ 16 | let journal = await Journal.find({ user: req.session.user._id }).sort({ date: -1}) 17 | 18 | let journalData = [] 19 | 20 | for (const record of journal) { 21 | await record.populate("from", "name").execPopulate() 22 | await record.populate("to", "name").execPopulate() 23 | 24 | journalData.push({ 25 | from: record.from.name, 26 | to: record.to.name, 27 | date: record.date.toDateString(), 28 | debit: record.debit, 29 | credit: record.credit, 30 | narration: record.narration 31 | }) 32 | } 33 | 34 | res.status(200).send({ 35 | journalData 36 | }) 37 | 38 | } catch(e) { 39 | res.status(500).send({ 40 | error: e 41 | }) 42 | } 43 | 44 | }) 45 | 46 | router.post("/add", isUserLoggedIn, [ 47 | check("from").not().isEmpty().withMessage("Provide 'from' field!").isAlpha().withMessage("'from' field must be a name!").trim().escape(), 48 | check("to").not().isEmpty().withMessage("Provide 'to' field!").isAlpha().withMessage("'to' field must be a name!").trim().escape(), 49 | check("date").not().isEmpty().withMessage("Provide 'date' field!").custom((value) => moment(value, "MM/DD/YYYY", true).isValid()).withMessage("'date' must be in 'MM/DD/YYYY' format!").trim() , 50 | check("debit").not().isEmpty().withMessage("Provide 'debit' field!").custom((value) => Number(value)).withMessage("'debit' must be a number!").trim().escape(), 51 | check("credit").not().isEmpty().withMessage("Provide 'credit' field!").custom((value) => Number(value)).withMessage("'credit' must be a number!").trim().escape(), 52 | check("narration").not().isEmpty().withMessage("Provide 'narration' field!").trim().escape() 53 | ], async (req, res) => { 54 | try{ 55 | const errors = validationResult(req) 56 | 57 | if(!errors.isEmpty()) { 58 | return res.status(400).send({ 59 | error: "Bad request!", 60 | message: errors.array() 61 | }) 62 | } 63 | 64 | let from = req.body.from.toLowerCase() 65 | let to = req.body.to.toLowerCase() 66 | let date = new Date(req.body.date) 67 | let debit = req.body.debit 68 | let credit = req.body.credit 69 | let narration = req.body.narration 70 | 71 | const fromAccount = await Account.findOne({ name: from, user: req.session.user._id }) 72 | 73 | if(!fromAccount) { 74 | let newAccount = new Account({ 75 | name: from, 76 | user: req.session.user._id 77 | }) 78 | await newAccount.save() 79 | from = newAccount 80 | } else { 81 | from = fromAccount 82 | } 83 | 84 | const toAccount = await Account.findOne({ name: to, user: req.session.user._id }) 85 | 86 | if(!toAccount) { 87 | let newAccount = new Account({ 88 | name: to, 89 | user: req.session.user._id 90 | }) 91 | await newAccount.save() 92 | to = newAccount 93 | } else { 94 | to = toAccount 95 | } 96 | 97 | let newEntry = new Journal({ 98 | from: from._id, 99 | to: to._id, 100 | date, 101 | debit, 102 | credit, 103 | narration: [narration + ` (Amount = ${debit} on date = ${date.toDateString()})`], 104 | user: req.session.user._id 105 | }) 106 | 107 | await newEntry.save() 108 | 109 | res.status(200).send({ 110 | message: "Entry added to Journal!", 111 | entry: { 112 | from: from.name, 113 | to: to.name, 114 | date: newEntry.date.toDateString(), 115 | debit: newEntry.debit, 116 | credit: newEntry.credit, 117 | narration: newEntry.narration 118 | } 119 | }) 120 | 121 | } catch(e) { 122 | res.status(500).send({ 123 | error: e 124 | }) 125 | } 126 | }) 127 | 128 | router.post("/update", isUserLoggedIn, [ 129 | check("from").not().isEmpty().withMessage("Provide 'from' field!").isAlpha().withMessage("'from' field must be a name!").trim().escape(), 130 | check("to").not().isEmpty().withMessage("Provide 'to' field!").isAlpha().withMessage("'to' field must be a name!").trim().escape(), 131 | check("entryDate").not().isEmpty().withMessage("Provide 'entryDate' field!").custom((value) => moment(value, "MM/DD/YYYY", true).isValid()).withMessage("'entryDate' must be in 'MM/DD/YYYY' format!").trim(), 132 | check("updateDate").not().isEmpty().withMessage("Provide 'updateDate' field!").custom((value) => moment(value, "MM/DD/YYYY", true).isValid()).withMessage("'updateDate' must be in 'MM/DD/YYYY' format!").trim(), 133 | check("action").not().isEmpty().withMessage("Provide 'action' field!").custom((value) => ["increase", "decrease"].includes(value)).withMessage("'action' must be a either 'decrease', 'increase'!").trim().escape(), 134 | check("amount").not().isEmpty().withMessage("Provide 'amount' field!").custom((value) => Number(value)).withMessage("'amount' must be a number!").trim().escape(), 135 | check("narration").not().isEmpty().withMessage("Provide 'narration' field!").trim().escape() 136 | ], async (req, res) => { 137 | try{ 138 | const errors = validationResult(req) 139 | 140 | if(!errors.isEmpty()) { 141 | return res.status(400).send({ 142 | error: "Bad request!", 143 | message: errors.array() 144 | }) 145 | } 146 | 147 | let from = req.body.from.toLowerCase() 148 | let to = req.body.to.toLowerCase() 149 | let entryDate = new Date(req.body.entryDate) 150 | let updateDate = new Date(req.body.updateDate) 151 | let action = req.body.action 152 | let amount = Number(req.body.amount) 153 | let narration = req.body.narration 154 | 155 | 156 | const fromAccount = await Account.findOne({ name: from, user: req.session.user._id }) 157 | 158 | if(!fromAccount) { 159 | return res.status(404).send({ 160 | error: "From account does not exisit!" 161 | }) 162 | } 163 | 164 | const toAccount = await Account.findOne({ name: to, user: req.session.user._id }) 165 | 166 | if(!toAccount) { 167 | return res.status(404).send({ 168 | error: "To account does not exisit!" 169 | }) 170 | } 171 | 172 | const entry = await Journal.findOne({ from: fromAccount._id , to: toAccount._id , date: entryDate.toDateString(), user: req.session.user._id }) 173 | 174 | if(!entry) { 175 | return res.status(404).send({ 176 | error: "Journal entry does not exisit!" 177 | }) 178 | } 179 | 180 | if(action === "increase") { 181 | entry.debit = entry.credit = entry.debit + amount 182 | } else if(action === "decrease"){ 183 | entry.debit = entry.credit = entry.debit - amount 184 | } 185 | 186 | narration = `${narration} Amount = ${amount} ${action} on date = ${updateDate.toDateString()}` 187 | 188 | entry.narration.push(narration) 189 | 190 | await entry.save() 191 | 192 | res.status(200).send({ 193 | message: "Updated Journal Entry!", 194 | entry: { 195 | from: from.name, 196 | to: to.name, 197 | date: entry.date.toDateString(), 198 | debit: entry.debit, 199 | credit: entry.credit, 200 | narration: entry.narration 201 | } 202 | }) 203 | 204 | } catch(e) { 205 | res.status(500).send({ 206 | error: e 207 | }) 208 | } 209 | }) 210 | 211 | 212 | 213 | module.exports = router 214 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nexus Accounts [![HitCount](http://hits.dwyl.io/HrithikMittal/HrithikMittal/nexus-account.svg)](http://hits.dwyl.io/HrithikMittal/HrithikMittal/nexus-account) 2 | [![license](https://img.shields.io/github/license/HrithikMittal/Nexus-Account.svg)](https://img.shields.io/github/license/HrithikMittal/Nexus-Account.svg) 3 | 4 | Nexus Account is an API which can be used to perform different accounting tasks such as creating Journal, Ledger, Trial Balance, Profit & Loss Account and Balance Sheet. It is also useful to create Flexible Budget or an Inventory. 5 | 6 | ### Prerequisites 7 | 8 | To work with the api you must have to install the following: 9 | * [NodeJS](https://nodejs.org/en/download/) - Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. 10 | * [MongoDB Server](https://docs.mongodb.com/manual/installation/) - NoSql Database and server 11 | * [Postman](https://www.getpostman.com/downloads/) - API development environment 12 | 13 | ## Installation 14 | 15 | Before doing anything you have to clone or download(and unzip) the project folder, open terminal and navigate to the project folder and run: 16 | 17 | ```bash 18 | npm install 19 | ``` 20 | This will install all the dependencies required by the project. 21 | 22 | ## Getting Started 23 | 24 | To start using this API, start your local database server, open terminal and navigate to the project folder and run: 25 | ```bash 26 | npm run start 27 | ``` 28 | If an error occur, check your database server or check if you have installed the prerequisites correctly. 29 | 30 | If there was no error, open Postman and create and send a new get request to: 31 | 32 | ``` 33 | http://localhost:3000/ 34 | ``` 35 | Expected Output: 36 | ``` 37 | { 38 | message: "Welcome!" 39 | } 40 | ``` 41 | 42 | Firstly, you have to create a new user for working with the API. 43 | 44 | Send a post request to: 45 | 46 | ``` 47 | http://localhost:3000/user/register 48 | ``` 49 | Along with 'id' and 'password' field in the 'x-www-form-urlencoded' option for the body of the request in postman: 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
iddemouserid
passworddemo
60 | 61 | Expected Output: 62 | ``` 63 | { 64 | "success": "User registered and Logged In!" 65 | } 66 | ``` 67 | 68 | Once you get this message, you are ready to work with the api. 69 | ## Routes 70 | 71 | Url for all these routes is ```http://localhost:3000``` and parameters for POST request are sent through 'x-www-form-urlencoded' method. 72 | 73 | ### User Routes 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 90 | 93 | 96 | 97 | 98 | 105 | 108 | 111 | 112 | 113 | 119 | 122 | 125 | 126 |
URLParametersMethodDescription
/user/register 84 | 85 |
    86 |
  • id
  • 87 |
  • password
  • 88 |
89 |
91 | POST 92 | 94 | Register a user 95 |
/user/login 99 | 100 |
    101 |
  • id
  • 102 |
  • password
  • 103 |
104 |
106 | POST 107 | 109 | Login a user 110 |
/user/logout 114 | 115 |
    116 | None 117 |
      118 |
120 | POST 121 | 123 | Logout a user 124 |
127 | 128 | ### Accounting Route 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 149 | 152 | 155 | 156 | 157 | 163 | 166 | 169 | 170 | 171 | 183 | 186 | 189 | 190 | 191 | 197 | 200 | 203 | 204 | 205 | 211 | 214 | 217 | 218 | 219 | 225 | 228 | 231 | 232 | 233 | 239 | 242 | 245 | 246 | 247 | 253 | 256 | 259 | 260 |
URLParametersMethodDescription
/journal/add 139 | 140 |
    141 |
  • from
  • 142 |
  • to
  • 143 |
  • date (MM/DD/YYYY)
  • 144 |
  • debit
  • 145 |
  • credit
  • 146 |
  • narration
  • 147 |
148 |
150 | POST 151 | 153 | Add new entry into journal 154 |
/journal 158 | 159 |
    160 | None 161 |
162 |
164 | GET 165 | 167 | Get all journal entry 168 |
/journal/update 172 | 173 |
    174 |
  • from
  • 175 |
  • to
  • 176 |
  • entryDate (MM/DD/YYYY)
  • 177 |
  • updateDate (MM/DD/YYYY)
  • 178 |
  • amount
  • 179 |
  • narration
  • 180 |
  • action ('increase' or 'decrease')
  • 181 |
      182 |
184 | POST 185 | 187 | Update a journal entry 188 |
/ledger/update 192 | 193 |
    194 | None 195 |
      196 |
198 | GET 199 | 201 | Update the ledger 202 |
/ledger 206 | 207 |
    208 | None 209 |
      210 |
212 | GET 213 | 215 | Get ledger 216 |
/trial 220 | 221 |
    222 | None 223 |
      224 |
226 | Get 227 | 229 | Get trial balance 230 |
/final/pnl 234 | 235 |
    236 | None 237 |
      238 |
240 | GET 241 | 243 | Get Profit and Loss Account 244 |
/final/balance 248 | 249 |
    250 | None 251 |
      252 |
254 | GET 255 | 257 | Get Balance Sheet 258 |
261 | 262 | ### Flexible Budget Route 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 281 | 284 | 287 | 288 | 289 | 295 | 298 | 301 | 302 |
URLParametersMethodDescription
/flexBudget/add 273 | 274 |
    275 |
  • name
  • 276 |
  • type ("fixed" or "variable")
  • 277 |
  • cost (only if 'type' is "fixed")
  • 278 |
  • costPerUnit (only if 'type' is "variable")
  • 279 |
280 |
282 | POST 283 | 285 | Add an entry to budget 286 |
/flexBudget 290 | 291 |
    292 |
  • units (Number)
  • 293 |
294 |
296 | POST 297 | 299 | Get the flexible budget 300 |
303 | 304 | ### Inventory Route 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 325 | 328 | 331 | 332 | 333 | 341 | 344 | 347 | 348 | 349 | 355 | 358 | 361 | 362 |
URLParametersMethodDescription
/inventory/add 315 | 316 |
    317 |
  • name
  • 318 |
  • category
  • 319 |
  • quantity
  • 320 |
  • thresholdQuantity
  • 321 |
  • expiry (format '05 September 2019')
  • 322 |
  • cost
  • 323 |
324 |
326 | POST 327 | 329 | Add an item to inventory 330 |
/inventory/update 334 | 335 |
    336 |
  • name
  • 337 |
  • category
  • 338 |
  • usedQuantity
  • 339 |
340 |
342 | POST 343 | 345 | Update an item in inventory 346 |
/inventory 350 | 351 |
    352 | None 353 |
354 |
356 | GET 357 | 359 | Get the inventory 360 |
363 | 364 | ### Repos for Seperate Access to the API's 365 | 379 | 380 | ### Authentication 381 | I used express-session to manage sessions to authenticate. We have isUserLoggedIn, isUserLoggedOut middleware function which checks if the user is authenticated or not. The session token is stored in the database using connect-mongo package and is deleted when the user logout
382 | ``` 383 | async function isUserLoggedIn (req, res, next) { 384 | try { 385 | if (!(req.session && req.session.user)) { 386 | return res.status(401).send({ 387 | error: "Unauthorized Access!" 388 | }); 389 | }else { 390 | const user = await User.findOne({ _id : req.session.user._id }) 391 | if(user) { 392 | next(); 393 | } else { 394 | req.session.user = null; 395 | return res.status(401).send({ 396 | error: "Unauthorized Access!" 397 | }); 398 | } 399 | } 400 | } catch(e) { 401 | res.status(400).send({ 402 | error: e 403 | }) 404 | } 405 | } 406 | 407 | 408 | // Function to check whether the user is logged out 409 | function isUserLoggedOut (req, res, next) { 410 | if (req.session && req.session.user) { 411 | return res.status(200).send({ 412 | message: "User already Logged In!" 413 | }); 414 | } 415 | next(); 416 | } 417 | 418 | module.exports = { 419 | isUserLoggedIn, 420 | isUserLoggedOut 421 | } 422 | ``` 423 | Note: some of the APIs which are mentionted above are not authenticate so please remember to add it. So it will help to proctect the private routes. 424 | 425 | ## Deployment 426 | 427 | This api can be hosted on platform like heroku, aws, and others. MongoDB Atlas or Matlab can be used for remote database.
For instance, the application can be deployed on [Heroku](https://signup.heroku.com/login) by creating and registering an account. Following, create a new app and choose a deployment method (terminal or github) and follow the instruction there. Remote database can be created using Mongodb Atlas or Matlab.
For [Mongodb Atlas](https://cloud.mongodb.com/user?_ga=2.185306281.1809166196.1559570784-2125252051.1557828824#/atlas/register/accountProfile), you need to just to create your account and make a new cluster and link the cluster to your application through a URL. Following the given steps, you would have a remote application up and running. 428 | 429 | ## Contributing [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/dwyl/esta/issues) 430 | 431 | If you are the helping and contributing one, your efforts and suggestion are always welcomed. 432 | --------------------------------------------------------------------------------