merchantData.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. const Owner = require("../models/owner.js");
  2. const Merchant = require("../models/merchant.js");
  3. const InventoryAdjustment = require("../models/inventoryAdjustment");
  4. const Transaction = require("../models/transaction.js");
  5. const helper = require("./helper.js");
  6. const verifyEmail = require("../emails/verifyEmail.js");
  7. const bcrypt = require("bcryptjs");
  8. const mailgun = require("mailgun-js")({apiKey: process.env.MG_SUBLINE_APIKEY, domain: "mail.thesubline.net"});
  9. const ObjectId = require("mongoose").Types.ObjectId;
  10. const axios = require("axios");
  11. module.exports = {
  12. /*
  13. POST - Create a new merchant with no POS system
  14. req.body = {
  15. name: retaurant name,
  16. email: registration email,
  17. password: password,
  18. confirmPassword: confirmation password
  19. owner: String (id of the owner, new owner of undefined)
  20. }
  21. Redirects to /dashboard
  22. */
  23. createMerchantNone: async function(req, res){
  24. if(req.body.password.length < 10){
  25. req.session.error = "PASSWORD MUST CONTAIN AT LEAST 10 CHARACTERS";
  26. return res.redirect("/register");
  27. }
  28. if(req.body.password !== req.body.confirmPassword){
  29. req.session.error = "PASSWORDS DO NOT MATCH";
  30. return res.redirect("/register");
  31. }
  32. let email = req.body.email.toLowerCase();
  33. const merchantFind = await Merchant.findOne({email: email});
  34. if(merchantFind !== null){
  35. req.session.error = "USER WITH THIS EMAIL ADDRESS ALREADY EXISTS";
  36. return res.redirect("/login");
  37. }
  38. let salt = bcrypt.genSaltSync(10);
  39. let hash = bcrypt.hashSync(req.body.password, salt);
  40. let expirationDate = new Date();
  41. expirationDate.setDate(expirationDate.getDate() + 90);
  42. let owner = new Owner({
  43. email: email,
  44. name: req.body.name,
  45. password: hash,
  46. createdAt: new Date(),
  47. status: ["unverified"],
  48. session: {
  49. sessionId: helper.generateId(25),
  50. expiration: expirationDate
  51. },
  52. merchants: []
  53. });
  54. let merchant = new Merchant({
  55. owner: owner._id,
  56. name: req.body.name,
  57. pos: "none",
  58. createdAt: Date.now(),
  59. inventory: [],
  60. recipes: []
  61. });
  62. owner.merchants.push(merchant._id);
  63. if(email.includes("***")){
  64. owner.email = owner.email.replace("***", "");
  65. owner.status = [];
  66. Promise.all([owner.save(), merchant.save()])
  67. .then((response)=>{
  68. req.session.owner = owner.session.sessionId;
  69. req.session.merchant = merchant._id;
  70. return res.redirect("/dashboard");
  71. })
  72. .catch((err)=>{});
  73. return;
  74. }
  75. Promise.all([owner.save(), merchant.save()])
  76. .then((response)=>{
  77. return res.redirect(`/verify/email/${response[0]._id}`);
  78. })
  79. .catch((err)=>{
  80. if(typeof(err) === "string"){
  81. req.session.error = err;
  82. }else if(err.name === "ValidationError"){
  83. req.session.error = err.errors[Object.keys(err.errors)[0]].properties.message;
  84. }else{
  85. req.session.error = "ERROR: UNABLE TO CREATE ACCOUNT AT THIS TIME";
  86. }
  87. return res.redirect("/");
  88. });
  89. },
  90. /*
  91. POST: create new merchant for existing owner
  92. req.body = {
  93. name: String
  94. }
  95. response = [Owner, Merchant]
  96. */
  97. addMerchantNone: function(req, res){
  98. let merchant = new Merchant({
  99. owner: res.locals.owner._id,
  100. name: req.body.name,
  101. pos: "none",
  102. createdAt: new Date(),
  103. inventory: [],
  104. recipes: []
  105. });
  106. res.locals.owner.merchants.push(merchant._id);
  107. let populate = res.locals.owner.populate("merchants", "name").execPopulate();
  108. Promise.all([res.locals.owner.save(), merchant.save(), populate])
  109. .then((response)=>{
  110. let owner = {
  111. _id: response[0]._id,
  112. email: response[0].email,
  113. merchants: response[0].merchants,
  114. name: response[0].name
  115. }
  116. req.session.merchant = response[1]._id;
  117. return res.json([owner, response[1]]);
  118. })
  119. .catch((err)=>{
  120. if(err.name === "ValidationError"){
  121. return res.json(err.errors[Object.keys(err.errors)[0]].properties.message);
  122. }
  123. return res.json("ERROR: UNABLE TO CREATE NEW MERCHANT");
  124. });
  125. },
  126. /*
  127. POST - Update the quantity for a merchant inventory item
  128. req.body = [{
  129. id: id of ingredient to update,
  130. quantity: change in quantity
  131. }]
  132. */
  133. updateIngredientQuantities: function(req, res){
  134. let adjustments = [];
  135. let changedIngredients = [];
  136. res.locals.merchant
  137. .populate("inventory.ingredient")
  138. .execPopulate()
  139. .then((merchant)=>{
  140. for(let i = 0; i < req.body.length; i++){
  141. let updateIngredient;
  142. for(let j = 0; j < merchant.inventory.length; j++){
  143. if(merchant.inventory[j].ingredient._id.toString() === req.body[i].id){
  144. updateIngredient = merchant.inventory[j];
  145. break;
  146. }
  147. }
  148. adjustments.push(new InventoryAdjustment({
  149. date: Date.now(),
  150. merchant: req.session.owner,
  151. ingredient: req.body[i].id,
  152. quantity: req.body[i].quantity - updateIngredient.quantity,
  153. }));
  154. updateIngredient.quantity = helper.convertQuantityToBaseUnit(req.body[i].quantity, updateIngredient.defaultUnit);
  155. changedIngredients.push(updateIngredient);
  156. }
  157. return merchant.save();
  158. })
  159. .then((newMerchant)=>{
  160. res.json(changedIngredients);
  161. InventoryAdjustment.create(adjustments).catch(()=>{});
  162. return;
  163. })
  164. .catch((err)=>{
  165. if(typeof(err) === "string"){
  166. return res.json(err);
  167. }
  168. if(err.name === "ValidationError"){
  169. return res.json(err.errors[Object.keys(err.errors)[0]].properties.message);
  170. }
  171. return res.json("ERROR: UNABLE TO UPDATE DATA");
  172. });
  173. },
  174. /*
  175. POST - Changes the users password
  176. req.body = {
  177. pass: new password,
  178. confirmPass: new password confirmation,
  179. hash: hashed version of old password
  180. }
  181. */
  182. updatePassword: function(req, res){
  183. Merchant.findOne({password: req.body.hash})
  184. .then((merchant)=>{
  185. if(merchant){
  186. if(req.body.pass.length < 10){
  187. throw "PASSWORD MUST CONTAIN AT LEAST 10 CHARACTERS";
  188. }
  189. if(req.body.pass !== req.body.confirmPass){
  190. throw "PASSWORDS DO NOT MATCH";
  191. }
  192. let salt = bcrypt.genSaltSync(10);
  193. let hash = bcrypt.hashSync(req.body.pass, salt);
  194. merchant.password = hash;
  195. return merchant.save();
  196. }else{
  197. req.session.error = "ERROR: UNABLE TO RETRIEVE DATA";
  198. return res.redirect("/");
  199. }
  200. })
  201. .then((merchant)=>{
  202. req.session.success = "PASSWORD SUCCESSFULLY RESET. PLEASE LOG IN";
  203. return res.redirect("/login");
  204. })
  205. .catch((err)=>{
  206. if(typeof(err) === "string") return res.json(err);
  207. if(err.name === "ValidationError"){
  208. return res.json(err.errors[Object.keys(err.errors)[0]].properties.message);
  209. }
  210. return res.json("ERROR: UNABLE TO UPDATE YOUR PASSWORD");
  211. });
  212. },
  213. /*
  214. PUT: Update merchant data
  215. req.body = {
  216. email: String
  217. name: String
  218. address: String
  219. },
  220. response = {
  221. email: String
  222. name: String,
  223. address: String
  224. }
  225. */
  226. updateData: async function(req, res){
  227. if(req.body.email !== res.locals.owner.email){
  228. let ownerCheck = await Owner.findOne({email: req.body.email});
  229. if(ownerCheck !== null) return res.json("USER WITH THIS EMAIL ADDRESS ALREADY EXISTS");
  230. res.locals.owner.email = req.body.email.toLowerCase();
  231. res.locals.owner.status.push("unverified");
  232. const mailgunData = {
  233. from: "The Subline <clientsupport@thesubline.net>",
  234. to: res.locals.owner.email,
  235. subject: "Email Verification",
  236. html: verifyEmail({
  237. name: res.locals.owner.name,
  238. link: `${process.env.SITE}/verify/${res.locals.owner._id}/${res.locals.owner.session.sessionId}`
  239. })
  240. };
  241. mailgun.messages().send(mailgunData, (err, body)=>{});
  242. }
  243. if(req.body.address !== "", req.body.address.toLowerCase() !== res.locals.merchant.address.full.toLowerCase()){
  244. let baseURL = "https://geocoding.geo.census.gov/geocoder/locations/onelineaddress/";
  245. let address = req.body.address.replace(/ /g, "+");
  246. let geocode = await axios.get(`${baseURL}?address=${address}&benchmark=2020&format=json`);
  247. let addressData = geocode.data.result.addressMatches[0];
  248. res.locals.merchant.address = {
  249. full: addressData.matchedAddress,
  250. city: addressData.addressComponents.city,
  251. state: addressData.addressComponents.state,
  252. zip: addressData.addressComponents.zip
  253. };
  254. res.locals.merchant.location = {
  255. type: "Point",
  256. coordinates: [addressData.coordinates.x, addressData.coordinates.y]
  257. };
  258. }
  259. res.locals.owner.name = req.body.name;
  260. Promise.all([res.locals.owner.save(), res.locals.merchant.save()])
  261. .then(()=>{
  262. return res.json({
  263. email: res.locals.owner.email,
  264. name: res.locals.owner.name,
  265. address: res.locals.merchant.address.full
  266. });
  267. })
  268. .catch((err)=>{
  269. if(err.name === "ValidationError") return res.json(err.errors[Object.keys(err.errors)[0]].properties.message);
  270. return res.json("ERROR: UNABLE TO UPDATE DATA");
  271. });
  272. },
  273. /*
  274. PUT: Update merchant password with current password
  275. req.body = {
  276. current: String (current merchant password),
  277. new: String (new password),
  278. confirm: String (new password again for confirmation)
  279. }
  280. response = {redirect: String (link to redirect to)}
  281. */
  282. changePassword: function(req, res){
  283. if(req.body.new !== req.body.confirm) return res.json("PASSWORDS DO NOT MATCH");
  284. bcrypt.compare(req.body.current, res.locals.owner.password, (err, result)=>{
  285. if(result === true){
  286. let salt = bcrypt.genSaltSync(10);
  287. let hash = bcrypt.hashSync(req.body.new, salt);
  288. res.locals.owner.password = hash;
  289. let newExpiration = new Date();
  290. newExpiration.setDate(newExpiration.getDate() + 90);
  291. res.locals.owner.session.sessionId = helper.generateId(25);
  292. res.locals.owner.session.expiration = newExpiration;
  293. res.locals.owner.save()
  294. .then((owner)=>{
  295. req.session.owner = undefined;
  296. req.session.merchant = undefined;
  297. req.session.success = "PASSWORD RESET. PLEASE LOG IN AGAIN.";
  298. return res.json({redirect: `http://${process.env.SITE}/login`});
  299. })
  300. .catch((err)=>{
  301. return res.json("ERROR: UNABLE TO UPDATE PASSWORD");
  302. });
  303. }else{
  304. return res.json("INCORRECT PASSWORD");
  305. }
  306. });
  307. },
  308. /*
  309. DELETE: remove a merchant from its owner
  310. response = [Owner, Merchant (the next), [Transaction]]
  311. */
  312. deleteMerchant: function(req, res){
  313. if(res.locals.owner.merchants.length === 1) throw "one";
  314. for(let i = 0; i < res.locals.owner.merchants.length; i++){
  315. if(res.locals.owner.merchants[i].toString() === res.locals.merchant._id.toString()){
  316. res.locals.owner.merchants.splice(i, 1);
  317. break;
  318. }
  319. }
  320. res.locals.merchant.removed = true;
  321. let now = new Date();
  322. let then = new Date(now.getFullYear(), now.getMonth() - 1, 1);
  323. let transactions = Transaction.aggregate([
  324. {$match: {
  325. merchant: ObjectId(res.locals.owner.merchants[0]._id),
  326. date: {$gte: then}
  327. }},
  328. {$sort: {date: -1}},
  329. {$project: {
  330. date: 1,
  331. recipes: 1
  332. }}
  333. ]);
  334. Promise.all([
  335. Merchant.findOne({_id: res.locals.owner.merchants[0]._id}).populate("inventory.ingredient").populate("recipes"),
  336. res.locals.owner.save(),
  337. res.locals.merchant.save(),
  338. res.locals.owner.populate("merchants", "name").execPopulate(),
  339. transactions
  340. ])
  341. .then((response)=>{
  342. let responseOwner = {
  343. _id: res.locals.owner._id,
  344. email: res.locals.owner.email,
  345. merchants: res.locals.owner.merchants.slice(1),
  346. name: res.locals.owner.name
  347. };
  348. response[0].owner = undefined;
  349. response[0].createdAt = undefined;
  350. response[0].locationId = undefined;
  351. req.session.merchant = response[0]._id;
  352. return res.json([responseOwner, response[0], response[4]]);
  353. })
  354. .catch((err)=>{
  355. if(err === "one") return res.json("YOU CANNOT DELETE YOUR ONLY MERCHANT");
  356. return res.json("ERROR: UNABLE TO DELETE THE MERCHANT");
  357. });
  358. },
  359. /*
  360. GET: gets a merchant to send back to its owner
  361. req.params.id = String (merchant id)
  362. response = [Owner, Merchant, [Transaction]];
  363. */
  364. getMerchant: function(req, res){
  365. let owner = Owner.findOne({"session.sessionId": req.session.owner}).populate("merchants", "name");
  366. let merchant = Merchant.findOne({_id: req.params.id}).populate("inventory.ingredient").populate("recipes");
  367. let now = new Date();
  368. let then = new Date(now.getFullYear(), now.getMonth() - 1, 1);
  369. let transactions = Transaction.aggregate([
  370. {$match: {
  371. merchant: ObjectId(req.params.id),
  372. date: {$gte: then}
  373. }},
  374. {$sort: {date: -1}},
  375. {$project: {
  376. date: 1,
  377. recipes: 1
  378. }}
  379. ]);
  380. Promise.all([owner, merchant, transactions])
  381. .then((response)=>{
  382. if(response[0] === null || response[1] === null) throw "unfound";
  383. if(response[1].owner.toString() !== response[0]._id.toString()) throw "permissions";
  384. let responseOwner = {
  385. _id: response[0]._id,
  386. email: response[0].email,
  387. merchants: response[0].merchants,
  388. name: response[0].name
  389. };
  390. for(let i = 0; i < responseOwner.merchants.length; i++){
  391. if(response[1]._id.toString() === responseOwner.merchants[i]._id.toString()){
  392. responseOwner.merchants.splice(i, 1);
  393. break;
  394. }
  395. }
  396. response[1].owner = undefined;
  397. response[1].createdAt = undefined;
  398. req.session.merchant = response[1]._id;
  399. return res.json([responseOwner, response[1], response[2]]);
  400. })
  401. .catch((err)=>{
  402. if(err === "unfound") return res.json("UNABLE TO FIND THAT MERCHANT");
  403. if(err === "permissions") return res.json("YOU DO NOT HAVE PERMISSION TO DO THAT");
  404. return res.json("ERROR: UNABLE TO RETRIEVE DATA");
  405. });
  406. }
  407. }