From 27552e5eb01d84f55786c8ded0ee385dc63a677b Mon Sep 17 00:00:00 2001 From: Jonasz Bigda Date: Wed, 16 Sep 2020 17:34:24 +0200 Subject: [PATCH] server v1.0.0 --- README.md | 11 +++- models/restaurant.js | 5 +- models/users.js | 5 ++ package-lock.json | 5 -- package.json | 1 - routes/routeRestaurant.js | 17 ++++- routes/routeSearch.js | 24 ++++++- routes/routeTest.js | 8 ++- routes/routeUser.js | 16 ++--- services/dataPrepServices.js | 123 +++++++++++++++++------------------ services/databaseServices.js | 63 ++++++++++++++++-- services/services.js | 40 ++---------- services/services.test.js | 19 ------ 13 files changed, 187 insertions(+), 150 deletions(-) delete mode 100644 services/services.test.js diff --git a/README.md b/README.md index 8dd71a2..b077e4d 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,8 @@ Takes **restaurantId** query and returns a specific restaurant **JSON** if found. Else returns **400** if restaurantId is invalid, or **404** if specified restaurantId is not found. - #### **POST** Takes a **restaurant** document, and JWT **token (header)**, tries to create new restaurant in a database, and also add it to user restaurants list. Returns **201** on success. Else returns **401** on invalid token, and **400** on general error while adding restaurant. - - #### **PUT** - empty! + - #### **PUT** + Takes a **restaurantId** and updates it with a supplied document.
@@ -179,6 +180,14 @@
+* ### **/search/location** + + - #### **GET** + + Takes a **lon, lat, and radius** query parameters and returns an **array** of restaurants in a specified radius from supplied location. + +
+ ## **3. Important functions**
diff --git a/models/restaurant.js b/models/restaurant.js index 9bdd6c9..f21c899 100644 --- a/models/restaurant.js +++ b/models/restaurant.js @@ -30,7 +30,6 @@ const restaurantSchema = mongoose.Schema({ }, imgUrl: { type: String, - required: true, }, workingHours: { type: String, @@ -57,8 +56,8 @@ const restaurantSchema = mongoose.Schema({ phone: Number, hidden: Boolean, subscriptionActive: Boolean, - subscriptionStarted: String, - subscriptionDue: String, + subscriptionStarted: Date, + subscriptionDue: Date, dishes: [mongoose.Types.ObjectId], }); diff --git a/models/users.js b/models/users.js index 2ea9179..07f72b2 100644 --- a/models/users.js +++ b/models/users.js @@ -13,23 +13,28 @@ const userSchema = mongoose.Schema({ firstname: { type: String, required: true, + maxlength: 64, }, lastname: { type: String, required: true, + maxlength: 64, }, billing: { NIP: { type: String, required: true, + maxlength: 64, }, adress: { type: String, required: true, + maxlength: 128, }, companyName: { type: String, required: true, + maxlength: 64, }, }, restaurants: [mongoose.Types.ObjectId], diff --git a/package-lock.json b/package-lock.json index 0adc379..635e026 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1889,11 +1889,6 @@ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "dev": true }, - "agile_crm": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/agile_crm/-/agile_crm-1.2.5.tgz", - "integrity": "sha1-gj+s18LUpzesJ2viFc9t6wwylVg=" - }, "ajv": { "version": "6.12.4", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", diff --git a/package.json b/package.json index f6bff4f..b799091 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "license": "ISC", "dependencies": { "@azure/storage-blob": "^12.2.0-preview.1", - "agile_crm": "^1.2.5", "bcrypt": "^5.0.0", "body-parser": "^1.19.0", "cors": "^2.8.5", diff --git a/routes/routeRestaurant.js b/routes/routeRestaurant.js index 9c33d86..24e9c95 100644 --- a/routes/routeRestaurant.js +++ b/routes/routeRestaurant.js @@ -35,7 +35,7 @@ router.post("/", async (req, res) => { try { const token = req.headers["x-auth-token"]; const user = validateUserToken(token); - const restaurant = createRestaurant(req.body); + const restaurant = await createRestaurant(req.body); await restaurant.save(); await addRestaurantToUser(user, restaurant); res.sendStatus(201); @@ -44,6 +44,21 @@ router.post("/", async (req, res) => { } }); +// UPDATE RESTAURANT + +router.put("/", async (req, res) => { + try { + const token = req.headers["x-auth-token"]; + const user = validateUserToken(token); + const oldRestaurant = await fetchRestaurant(req.body.restaurantId); + const newRestaurant = await createRestaurant(req.body, oldRestaurant); + await Restaurant.replaceOne({ _id: req.body.restaurantId }, newRestaurant); + res.send("Dane zostały zaktualizowane."); + } catch (error) { + handleError(error, res); + } +}); + // GET ALL DISHES FROM A RESTAURANT ID router.get("/dishes", async (req, res) => { diff --git a/routes/routeSearch.js b/routes/routeSearch.js index 912aa9c..906a99d 100644 --- a/routes/routeSearch.js +++ b/routes/routeSearch.js @@ -1,8 +1,7 @@ import express from "express"; -import * as services from "../services/services.js"; import Restaurant from "../models/restaurant.js"; import sanitizer from "string-sanitizer"; -import restaurant from "../models/restaurant.js"; +import { handleError } from "../services/services.js"; var router = express.Router(); @@ -18,9 +17,10 @@ router.get("/", (req, res) => { $and: [ { $or: [{ city: { $regex: regex } }, { name: { $regex: regex } }] }, { hidden: false }, + { subscriptionActive: true }, ], }, - "_id name city imgUrl workingHours description tags phone hidden", + "_id name city imgUrl workingHours description tags location links", (err, results) => { if (err) { res.sendStatus(500); @@ -36,6 +36,24 @@ router.get("/", (req, res) => { } }); +// SEARCH RESTAURANTS BY LOCATION + +router.get("/location", async (req, res) => { + try { + const location = { + coordinates: [req.query.lon, req.query.lat], + type: "Point", + }; + const radius = parseInt(req.query.radius) * 1000; + const results = await Restaurant.find({ + location: { $near: { $maxDistance: radius, $geometry: location } }, + }); + res.send(results); + } catch (error) { + handleError(error, res); + } +}); + // AUTOCOMPLETE router.get("/autocomplete/", (req, res) => { diff --git a/routes/routeTest.js b/routes/routeTest.js index a882860..7fd27f0 100644 --- a/routes/routeTest.js +++ b/routes/routeTest.js @@ -1,14 +1,16 @@ import express from "express"; import * as services from "../services/services.js"; +import * as databaseServices from "../services/databaseServices.js"; var router = express.Router(); router.post("/", async (req, res) => { try { - const decodedToken = services.validateUserToken( - req.headers["x-auth-token"] + const newDate = await databaseServices.renewSubscription( + req.body.restaurantId, + 1 ); - res.send(decodedToken); + res.send(`Subskrypcja przedłużona do: ${newDate}`); } catch (error) { services.handleError(error, res); } diff --git a/routes/routeUser.js b/routes/routeUser.js index b64cfbc..35fa24a 100644 --- a/routes/routeUser.js +++ b/routes/routeUser.js @@ -1,10 +1,6 @@ import express from "express"; import { changeUserPass, fetchUser } from "../services/databaseServices.js"; -import { - composeNewContact, - createUser, - prepareSafeUser, -} from "../services/dataPrepServices.js"; +import { createUser, prepareSafeUser } from "../services/dataPrepServices.js"; import { newError, handleError, @@ -14,13 +10,9 @@ import { validateUserToken, hashPass, } from "../services/services.js"; -import * as config from "../config/index.js"; -import AgileCRMManager from "agile_crm"; import { resetPassword } from "../services/mailServices.js"; -const { CRM_USER, CRM_EMAIL, CRM_KEY } = config; var router = express.Router(); -var agileAPI = new AgileCRMManager(CRM_USER, CRM_KEY, CRM_EMAIL); // LOGIN router.post("/login", async (req, res) => { @@ -43,9 +35,9 @@ router.post("/register", async (req, res) => { try { await checkEmailTaken(req.body.email); const user = await createUser(req); - await user.save(); - const contact = composeNewContact(user); - agileAPI.contactAPI.add(contact, null, null); + await user.save().catch((e) => { + throw newError("Niewłaściwe dane.", 500); + }); res.sendStatus(201); } catch (e) { handleError(e, res); diff --git a/services/dataPrepServices.js b/services/dataPrepServices.js index b7e658a..ff67941 100644 --- a/services/dataPrepServices.js +++ b/services/dataPrepServices.js @@ -5,52 +5,6 @@ import Dish from "../models/dish.js"; import User from "../models/users.js"; import Restaurant from "../models/restaurant.js"; -export function composeNewContact(request) { - const contact = { - lead_score: "100", - tags: ["newUser"], - properties: [ - { - type: "SYSTEM", - name: "first_name", - value: request.firstname, - }, - { - type: "SYSTEM", - name: "last_name", - value: request.lastname, - }, - { - type: "SYSTEM", - name: "email", - subtype: "work", - value: request.email, - }, - { - type: "CUSTOM", - name: "UserID", - value: request._id, - }, - { - type: "CUSTOM", - name: "NIP", - value: request.NIP, - }, - { - type: "CUSTOM", - name: "CompanyName", - value: request.companyName, - }, - { - type: "CUSTOM", - name: "CompanyAdress", - value: request.adress, - }, - ], - }; - return contact; -} - export async function createUser(request) { const password = await hashPass(request.body.password); const user = new User({ @@ -68,25 +22,68 @@ export async function createUser(request) { return user; } -export function createRestaurant(request) { +async function handleImageUpdate(request, previous) { + if (!previous) { + if (!request.imgURL) { + return "empty"; + } else { + const img = await saveImage(request.imgURL); + return img; + } + } else { + if (request.imgURL == previous.imgUrl) { + return previous.imgUrl; + } else { + if (!request.imgURL) { + return previous.imgUrl; + } else { + const img = await saveImage(request.imgURL); + return img; + } + } + } +} + +export async function createRestaurant(request, oldRestaurant) { try { - const restaurant = new Restaurant({ - _id: new mongoose.Types.ObjectId(), - name: sanitizer.sanitize.keepUnicode(request.name), - city: sanitizer.sanitize.keepUnicode(request.city), - adress: sanitizer.sanitize.keepUnicode(request.adress), - location: request.location, - imgUrl: saveImage(request.imgURL), - workingHours: request.workingHours, - description: sanitizer.sanitize.keepUnicode(request.description), - tags: request.tags, - links: request.links, - phone: request.phone, - hidden: request.hidden, - }); - return restaurant; + if (!oldRestaurant) { + const img = await handleImageUpdate(request); + const restaurant = new Restaurant({ + _id: new mongoose.Types.ObjectId(), + name: sanitizer.sanitize.keepUnicode(request.name), + city: sanitizer.sanitize.keepUnicode(request.city), + adress: sanitizer.sanitize.keepUnicode(request.adress), + location: request.location, + imgUrl: img, + workingHours: request.workingHours, + description: sanitizer.sanitize.keepUnicode(request.description), + tags: request.tags, + links: request.links, + phone: request.phone, + hidden: request.hidden, + }); + return restaurant; + } else { + const img = await handleImageUpdate(request, oldRestaurant); + const restaurant = new Restaurant({ + name: sanitizer.sanitize.keepUnicode(request.name), + city: sanitizer.sanitize.keepUnicode(request.city), + dishes: oldRestaurant.dishes, + adress: sanitizer.sanitize.keepUnicode(request.adress), + location: request.location, + imgUrl: img, + workingHours: request.workingHours, + description: sanitizer.sanitize.keepUnicode(request.description), + tags: request.tags, + links: request.links, + phone: request.phone, + hidden: request.hidden, + }); + return restaurant; + } } catch (error) { - throw newError("Invalid input data", 206); + console.log(error); + throw newError("Niewłaściwe dane", 206); } } diff --git a/services/databaseServices.js b/services/databaseServices.js index abc7c33..6507c50 100644 --- a/services/databaseServices.js +++ b/services/databaseServices.js @@ -41,7 +41,7 @@ export async function addDishToRestaurant(restaurantId, dishId) { { _id: restaurantId }, { $push: { dishes: dishId } } ).catch((error) => { - throw newError("Couldn't add dish to restaurant", 500); + throw newError("Nie udało się dodać dania do restauracji", 500); }); } @@ -49,16 +49,67 @@ export async function addRestaurantToUser(user, restaurant) { await User.findByIdAndUpdate(user.id, { $push: { restaurants: restaurant._id }, }).catch((e) => { - throw newError("Couldn't add restaurant to user", 500); + throw newError("Nie udało się dodać restauracji do użytkownika", 500); }); } +function dueDateBasedOnSubscription(restaurant, monthsToAdd) { + let date; + if ( + restaurant.subscriptionActive === false || + !restaurant.subscriptionActive + ) { + date = new Date(); + date.setMonth(date.getMonth() + monthsToAdd); + return date; + } else { + date = restaurant.subscriptionDue; + date.setMonth(date.getMonth() + monthsToAdd); + return date; + } +} + +function startDate(restaurant) { + let date; + if ( + restaurant.subscriptionActive === true && + restaurant.subscriptionStarted + ) { + date = restaurant.subscriptionStarted; + return date; + } else { + date = new Date(); + return date; + } +} + +export async function renewSubscription(restaurantId, monthsToAdd) { + const restaurant = await Restaurant.findById(restaurantId).catch((err) => { + throw newError("Nie udało się pobrać restauracji.", 404); + }); + const dueDate = dueDateBasedOnSubscription(restaurant, monthsToAdd); + const start = startDate(restaurant); + await Restaurant.findByIdAndUpdate(restaurantId, { + $set: { + subscriptionActive: true, + subscriptionDue: dueDate, + subscriptionStarted: start, + }, + }).catch((e) => { + throw newError( + "Nie udało się przedłużyć subskrypcji, spróbuj ponownie.", + 500 + ); + }); + return dueDateBasedOnSubscription(restaurant, monthsToAdd); +} + export async function fetchRestaurant(id) { let data; await Restaurant.findById(id, (err, result) => { data = result; }).catch((e) => { - throw newError("Couldn't fetch restaurant", 500); + throw newError("Nie udało się pobrać restauracji.", 500); }); return data; } @@ -74,14 +125,14 @@ export async function fetchAllDishesForRestaurant(restaurant) { export async function fetchDish(id) { let data = await Dish.findById(id).catch((e) => { - throw newError(`Couldn't fetch ${id}`, 404); + throw newError(`Nie udało się pobrać ${id}`, 404); }); return data; } export async function fetchUser(email) { - if (!email) throw newError("No input", 204); + if (!email) throw newError("Brak danych", 204); const user = await User.findOne({ email: email }); - if (!user) throw newError("No such user...", 404); + if (!user) throw newError("Użytkownik nie istnieje", 404); return user; } diff --git a/services/services.js b/services/services.js index 28bdb54..007b781 100644 --- a/services/services.js +++ b/services/services.js @@ -88,13 +88,13 @@ export async function checkEmailTaken(email) { export function validateUserToken(token) { if (!token) throw newError("Brak dostępu", 401); - const verified = jwt - .verify(token, jwtSecret, { ignoreExpiration: false }) - .catch((e) => { - throw newError("Brak dostępu", 401); - }); - if (!verified) throw newError("Brak dostępu", 401); - return verified; + try { + const verified = jwt.verify(token, jwtSecret, { ignoreExpiration: false }); + if (!verified) throw newError("Brak dostępu", 401); + return verified; + } catch (error) { + throw newError("Brak dostępu", 401); + } } export async function validateDishId(id) { @@ -142,17 +142,6 @@ export function yearFromNowDate() { return date.addDays(365); } -export function halfYearFromNowDate() { - Date.prototype.addDays = function (days) { - var date = new Date(this.valueOf()); - date.setDate(date.getDate() + days); - return date; - }; - var nowDate = new Date(); - var resultDate = nowDate.addDays(183); - return toShortDate(resultDate); -} - export async function hashPass(pass) { if (pass.length < 6) { throw newError("Hasło za krótkie.", 500); @@ -166,21 +155,6 @@ export async function hashPass(pass) { } } -export function dueDateBasedOnSubscription(subscriptionActive) { - if (subscriptionActive === true) { - return halfYearFromNowDate(); - } else { - return new Date(); - } -} - -export function toShortDate(date) { - if (!date) return false; - const shortDate = - date.getMonth() + "/" + date.getDay() + "/" + date.getFullYear(); - return shortDate; -} - export async function saveImage(url) { const newURL = await renameBlob(url); return newURL; diff --git a/services/services.test.js b/services/services.test.js deleted file mode 100644 index b452632..0000000 --- a/services/services.test.js +++ /dev/null @@ -1,19 +0,0 @@ -import { toShortDate, generateNewPassword } from "./services"; - -jest.mock("@azure/storage-blob", () => { - return { - StorageSharedKeyCredential: jest.fn(), - newPipeline: jest.fn(), - BlobServiceClient: jest.fn(), - }; -}); - -jest.mock("bcrypt", () => { - return { - foo: jest.fn(), - }; -}); - -test("should return false for no date on input", () => { - expect(toShortDate()).toBe(false); -});