squareData.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. const Owner = require("../models/owner.js");
  2. const Merchant = require("../models/merchant.js");
  3. const Recipe = require("../models/recipe.js");
  4. const helper = require("./helper.js");
  5. const axios = require("axios");
  6. const bcrypt = require("bcryptjs");
  7. module.exports = {
  8. /*POST - Redirects user to Square OAuth and saves input data
  9. req.body = {
  10. name: String,
  11. email: String,
  12. password: String,
  13. confirmPassword: String
  14. }
  15. */
  16. redirect: async function(req, res){
  17. if(req.body.password !== req.body.confirmPassword){
  18. req.session.error = "YOUR PASSWORDS DO NOT MATCH";
  19. return res.redirect("/");
  20. }
  21. let email = req.body.email.toLowerCase();
  22. let potentialOwner = await Owner.findOne({email: email})
  23. if(potentialOwner !== null){
  24. req.session.error = "USER WITH THIS EMAIL ADDRESS ALREADY EXISTS";
  25. return res.redirect("/login");
  26. }
  27. let expirationDate = new Date();
  28. expirationDate.setDate(expirationDate.getDate() + 90);
  29. let salt = bcrypt.genSaltSync(10);
  30. let hash = bcrypt.hashSync(req.body.password, salt);
  31. let owner = new Owner({
  32. email: email,
  33. name: req.body.name,
  34. password: hash,
  35. square: {},
  36. createdAt: new Date(),
  37. status: ["unverified"],
  38. session: {
  39. sessionId: helper.generateId(25),
  40. expiration: expirationDate
  41. },
  42. merchants: []
  43. });
  44. let merchant = new Merchant({
  45. owner: owner._id,
  46. name: req.body.name,
  47. pos: "square",
  48. inventory: [],
  49. recipes: [],
  50. square: {},
  51. createdAt: new Date()
  52. });
  53. owner.merchants.push(merchant._id);
  54. Promise.all([owner.save(), merchant.save()])
  55. .then((response)=>{
  56. req.session.owner = response[0].session.sessionId;
  57. return res.redirect(`${process.env.SQUARE_ADDRESS}/oauth2/authorize?client_id=${process.env.SUBLINE_SQUARE_APPID}&scope=INVENTORY_READ+ITEMS_READ+MERCHANT_PROFILE_READ+ORDERS_READ+PAYMENTS_READ`);
  58. })
  59. .catch((err)=>{
  60. req.session.error = "ERROR: UNABLE TO CREATE NEW USER";
  61. return res.redirect("/");
  62. });
  63. },
  64. //GET: Gathers all data from square to create our merchant
  65. //Redirects to the dashboard
  66. createMerchant: function(req, res){
  67. let code = req.url.slice(req.url.indexOf("code=") + 5, req.url.indexOf("&"));
  68. let url = `${process.env.SQUARE_ADDRESS}/oauth2/token`;
  69. let data = {
  70. client_id: process.env.SUBLINE_SQUARE_APPID,
  71. client_secret: process.env.SUBLINE_SQUARE_APPSECRET,
  72. grant_type: "authorization_code",
  73. code: code,
  74. };
  75. let merchant = {};
  76. let owner = Owner.findOne({"session.sessionId": req.session.owner}).populate("merchants");
  77. let squareMerchant = axios.post(url, data);
  78. Promise.all([owner, squareMerchant])
  79. .then((response)=>{
  80. if(response[0] === null) throw "ERROR: UNABLE TO CREATE ACCOUNT";
  81. owner = response[0];
  82. merchant = response[0].merchants[0];
  83. owner.square = {
  84. id: response[1].data.merchant_id,
  85. expires: new Date(response[1].data.expires_at),
  86. refreshToken: response[1].data.refresh_token,
  87. accessToken: response[1].data.access_token
  88. };
  89. return axios.get(`${process.env.SQUARE_ADDRESS}/v2/merchants/${owner.square.id}`, {
  90. headers: {Authorization: `Bearer ${owner.square.accessToken}`}
  91. });
  92. })
  93. .then((response)=>{
  94. merchant.locationId = response.data.merchant.main_location_id;
  95. owner.name = response.data.merchant.business_name;
  96. let items = axios.post(`${process.env.SQUARE_ADDRESS}/v2/catalog/search-catalog-items`, {
  97. enabled_location_ids: [merchant.locationId]
  98. }, {
  99. headers: {
  100. Authorization: `Bearer ${owner.square.accessToken}`
  101. }
  102. });
  103. let location = axios.get(`${process.env.SQUARE_ADDRESS}/v2/locations/${merchant.locationId}`, {
  104. headers: {
  105. Authorization: `Bearer ${owner.square.accessToken}`
  106. }
  107. });
  108. let categories = axios.post(`${process.env.SQUARE_ADDRESS}/v2/catalog/search`, {
  109. object_types: ["CATEGORY"],
  110. },{
  111. headers: {
  112. Authorization: `Bearer ${owner.square.accessToken}`
  113. }
  114. });
  115. return Promise.all([items, location, categories]);
  116. })
  117. .then((response)=>{
  118. let location = response[1].data.location;
  119. if(owner.email === location.business_email.toLowerCase()) owner.status = [];
  120. merchant.name = location.name;
  121. let recipes = helper.createRecipesFromSquare(response[0].data.items, response[2].data.objects, merchant._id);
  122. merchant.recipes = recipes;
  123. let baseUrl = "https://api.geocod.io/v1.6/geocode/";
  124. let address = location.address;
  125. let geocode = axios.get(`${baseUrl}?q=${address.address_line_1}+${address.locality}+${address.administrative_district_level_1}+${address.postal_code}&api_key=${process.env.SUBLINE_GEOCODE_API}&limit=1`);
  126. return Promise.all([Recipe.create(recipes), geocode, owner.save()]);
  127. })
  128. .then((response)=>{
  129. let addressData = response[1].data.results[0];
  130. merchant.address = {
  131. full: response[1].data.results[0].address_components.formatted_address,
  132. city: addressData.address_components.city,
  133. state: addressData.address_components.state,
  134. zip: addressData.address_components.zip
  135. };
  136. merchant.location = {
  137. type: "Point",
  138. coordinates: [addressData.location.lat, addressData.location.lng]
  139. };
  140. return merchant.save();
  141. })
  142. .then((response)=>{
  143. req.session.owner = owner.session.sessionId;
  144. req.session.merchant = merchant._id;
  145. res.redirect("/dashboard");
  146. helper.getAllMerchantTransactions(merchant, owner.square.accessToken);
  147. })
  148. .catch((err)=>{
  149. if(typeof(err) === "string"){
  150. req.session.error = err;
  151. }else if(err.name === "ValidationError"){
  152. req.session.error = err.errors[Object.keys(err.errors)[0]].properties.message;
  153. }else{
  154. req.session.error = "ERROR: UNABLE TO CREATE NEW USER";
  155. }
  156. Merchant.deleteOne({_id: merchant._id});
  157. Owner.deleteOne({_id: owner._id});
  158. return res.redirect("/");
  159. });
  160. },
  161. updateRecipes: function(req, res){
  162. let merchantRecipes = [];
  163. let newRecipes = [];
  164. let populate = res.locals.merchant.populate("recipes").execPopulate();
  165. let squareRecipes = axios.post(`${process.env.SQUARE_ADDRESS}/v2/catalog/search-catalog-items`, {
  166. enabled_location_ids: [res.locals.merchant.locationId]
  167. }, {
  168. headers: {
  169. Authorization: `Bearer ${res.locals.owner.square.accessToken}`
  170. }
  171. });
  172. let categories = axios.post(`${process.env.SQUARE_ADDRESS}/v2/catalog/search`, {
  173. object_types: ["CATEGORY"]
  174. },{
  175. headers: {
  176. Authorization: `Bearer ${res.locals.owner.square.accessToken}`
  177. }
  178. })
  179. Promise.all([populate, squareRecipes, categories])
  180. .then((response)=>{
  181. merchantRecipes = res.locals.merchant.recipes.slice();
  182. for(let i = 0; i < response[1].data.items.length; i++){
  183. let itemData = response[1].data.items[i].item_data;
  184. for(let j = 0; j < itemData.variations.length; j++){
  185. let isFound = false;
  186. for(let k = 0; k < merchantRecipes.length; k++){
  187. if(itemData.variations[j].id === merchantRecipes[k].posId){
  188. merchantRecipes.splice(k, 1);
  189. k--;
  190. isFound = true;
  191. break;
  192. }
  193. }
  194. if(isFound === false){
  195. let priceMoney = itemData.variations[j].item_variation_data.price_money;
  196. let price = (priceMoney === undefined) ? 0 : priceMoney.amount;
  197. let newRecipe = new Recipe({
  198. posId: itemData.variations[j].id,
  199. merchant: res.locals.merchant._id,
  200. name: "",
  201. price: price,
  202. ingredients: [],
  203. category: ""
  204. });
  205. if(itemData.variations.length > 1){
  206. newRecipe.name = `${itemData.name} '${itemData.variations[j].item_variation_data.name}'`;
  207. }else{
  208. newRecipe.name = itemData.name;
  209. }
  210. for(let k = 0; k < response[2].data.objects.length; k++){
  211. if(itemData.category_id === response[2].data.objects[k].id){
  212. newRecipe.category = response[2].data.objects[k].category_data.name;
  213. break;
  214. }
  215. }
  216. newRecipes.push(newRecipe);
  217. res.locals.merchant.recipes.push(newRecipe);
  218. }
  219. }
  220. }
  221. let ids = [];
  222. for(let i = 0; i < merchantRecipes.length; i++){
  223. ids.push(merchantRecipes[i]._id);
  224. for(let j = 0; j < res.locals.merchant.recipes.length; j++){
  225. if(merchantRecipes[i]._id.toString() === res.locals.merchant.recipes[j]._id.toString()){
  226. res.locals.merchant.recipes.splice(j, 1);
  227. j--;
  228. break;
  229. }
  230. }
  231. }
  232. if(newRecipes.length > 0) Recipe.create(newRecipes);
  233. return res.locals.merchant.save();
  234. })
  235. .then((merchant)=>{
  236. return res.json({new: newRecipes, removed: merchantRecipes});
  237. })
  238. .catch((err)=>{
  239. if(typeof(err) === "string") return res.json(err);
  240. if(err.name === "ValidationError") return res.json(err.errors[Object.keys(err.errors)[0]].properties.message);
  241. return res.json("ERROR: UNABLE TO RETRIEVE RECIPE DATA FROM SQUARE");
  242. });
  243. },
  244. /*
  245. GET: add another merchant to an owner with another square location
  246. response = [{
  247. name: String,
  248. id: String
  249. }]
  250. */
  251. getLocations: function(req, res){
  252. let ownerLocation = res.locals.owner.populate("merchants", "locationId").execPopulate();
  253. let locations = axios.get(`${process.env.SQUARE_ADDRESS}/v2/locations`, {
  254. headers: {
  255. "Authorization": `Bearer ${res.locals.owner.square.accessToken}`,
  256. "Content-Type": "application/json"
  257. }
  258. });
  259. Promise.all([ownerLocation, locations])
  260. .then((response)=>{
  261. let checkLocations = [];
  262. let locations = [];
  263. for(let i = 0; i < res.locals.owner.merchants.length; i++){
  264. checkLocations.push(res.locals.owner.merchants[i].locationId);
  265. }
  266. for(let i = 0; i < response[1].data.locations.length; i++){
  267. if(checkLocations.includes(response[1].data.locations[i].id) === false){
  268. locations.push({
  269. name: response[1].data.locations[i].name,
  270. id: response[1].data.locations[i].id
  271. });
  272. }
  273. }
  274. return res.json(locations);
  275. })
  276. .catch((err)=>{
  277. return res.json("ERROR: UNABLE TO RETRIEVE LOCATION DATA FROM SQUARE");
  278. });
  279. },
  280. /*
  281. GET: create new merchant from square location and add to owner
  282. req.params.location = location.id
  283. response = [Owner, Merchant]
  284. */
  285. addMerchant: function(req, res){
  286. let merchant = new Merchant({
  287. owner: res.locals.owner._id,
  288. pos: "square",
  289. locationId: req.params.location,
  290. createdAt: new Date(),
  291. inventory: [],
  292. recipes: []
  293. });
  294. res.locals.owner.merchants.push(merchant._id);
  295. let location = axios.get(`${process.env.SQUARE_ADDRESS}/v2/locations/${req.params.location}`, {
  296. headers: {
  297. "Authorization": `Bearer ${res.locals.owner.square.accessToken}`,
  298. "Content-Type": "application/json"
  299. }
  300. });
  301. let recipes = axios.post(`${process.env.SQUARE_ADDRESS}/v2/catalog/search-catalog-items`, {
  302. enabled_location_ids: [req.params.location]
  303. }, {
  304. headers: {
  305. Authorization: `Bearer ${res.locals.owner.square.accessToken}`,
  306. "Content-Type": "application/json"
  307. }
  308. });
  309. Promise.all([location, recipes])
  310. .then((response)=>{
  311. merchant.name = response[0].data.location.name;
  312. let recipes = helper.createRecipesFromSquare(response[1].data.items, merchant._id);
  313. merchant.recipes = recipes;
  314. let populateOwner = res.locals.owner.populate("merchants", "name").execPopulate();
  315. let baseURL = "https://geocoding.geo.census.gov/geocoder/locations/onelineaddress/";
  316. let address = response[0].data.location.address;
  317. let geocode = axios.get(`${baseURL}?address=${address.address_line_1}+${address.locality}+${address.administrative_district_level_1}+${address.postal_code}&benchmark=2020&format=json`);
  318. return Promise.all([geocode, Recipe.create(recipes), res.locals.owner.save(), populateOwner]);
  319. })
  320. .then((response)=>{
  321. let addressData = response[0].data.result.addressMatches[0];
  322. merchant.address = {
  323. full: addressData.matchedAddress,
  324. city: addressData.addressComponents.city,
  325. state: addressData.addressComponents.state,
  326. zip: addressData.addressComponents.zip
  327. };
  328. merchant.location = {
  329. type: "Point",
  330. coordinates: [addressData.coordinates.x, addressData.coordinates.y]
  331. };
  332. return merchant.save();
  333. })
  334. .then((response)=>{
  335. req.session.merchant = merchant._id;
  336. res.json([{
  337. _id: res.locals.owner._id,
  338. email: res.locals.owner.email,
  339. merchants: res.locals.owner.merchants,
  340. name: res.locals.owner.name
  341. }, merchant]);
  342. helper.getAllMerchantTransactions(merchant, res.locals.owner.square.accessToken);
  343. })
  344. .catch((err)=>{
  345. if(err.name === "ValidationError") return req.session.err = err.errors[Object.keys(err.errors)[0]].properties.message;
  346. return res.json("ERROR: UNABLE TO CREATE NEW MERCHANT");
  347. });
  348. }
  349. }