server v1.0.0

This commit is contained in:
2020-09-16 17:34:24 +02:00
parent a5232e7257
commit 27552e5eb0
13 changed files with 187 additions and 150 deletions

View File

@@ -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. 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** - #### **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. 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.
<br> <br>
@@ -179,6 +180,14 @@
<br> <br>
* ### **/search/location**
- #### **GET**
Takes a **lon, lat, and radius** query parameters and returns an **array** of restaurants in a specified radius from supplied location.
<br>
## **3. Important functions** ## **3. Important functions**
</br> </br>

View File

@@ -30,7 +30,6 @@ const restaurantSchema = mongoose.Schema({
}, },
imgUrl: { imgUrl: {
type: String, type: String,
required: true,
}, },
workingHours: { workingHours: {
type: String, type: String,
@@ -57,8 +56,8 @@ const restaurantSchema = mongoose.Schema({
phone: Number, phone: Number,
hidden: Boolean, hidden: Boolean,
subscriptionActive: Boolean, subscriptionActive: Boolean,
subscriptionStarted: String, subscriptionStarted: Date,
subscriptionDue: String, subscriptionDue: Date,
dishes: [mongoose.Types.ObjectId], dishes: [mongoose.Types.ObjectId],
}); });

View File

@@ -13,23 +13,28 @@ const userSchema = mongoose.Schema({
firstname: { firstname: {
type: String, type: String,
required: true, required: true,
maxlength: 64,
}, },
lastname: { lastname: {
type: String, type: String,
required: true, required: true,
maxlength: 64,
}, },
billing: { billing: {
NIP: { NIP: {
type: String, type: String,
required: true, required: true,
maxlength: 64,
}, },
adress: { adress: {
type: String, type: String,
required: true, required: true,
maxlength: 128,
}, },
companyName: { companyName: {
type: String, type: String,
required: true, required: true,
maxlength: 64,
}, },
}, },
restaurants: [mongoose.Types.ObjectId], restaurants: [mongoose.Types.ObjectId],

5
package-lock.json generated
View File

@@ -1889,11 +1889,6 @@
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
"dev": true "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": { "ajv": {
"version": "6.12.4", "version": "6.12.4",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz",

View File

@@ -12,7 +12,6 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@azure/storage-blob": "^12.2.0-preview.1", "@azure/storage-blob": "^12.2.0-preview.1",
"agile_crm": "^1.2.5",
"bcrypt": "^5.0.0", "bcrypt": "^5.0.0",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"cors": "^2.8.5", "cors": "^2.8.5",

View File

@@ -35,7 +35,7 @@ router.post("/", async (req, res) => {
try { try {
const token = req.headers["x-auth-token"]; const token = req.headers["x-auth-token"];
const user = validateUserToken(token); const user = validateUserToken(token);
const restaurant = createRestaurant(req.body); const restaurant = await createRestaurant(req.body);
await restaurant.save(); await restaurant.save();
await addRestaurantToUser(user, restaurant); await addRestaurantToUser(user, restaurant);
res.sendStatus(201); 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 // GET ALL DISHES FROM A RESTAURANT ID
router.get("/dishes", async (req, res) => { router.get("/dishes", async (req, res) => {

View File

@@ -1,8 +1,7 @@
import express from "express"; import express from "express";
import * as services from "../services/services.js";
import Restaurant from "../models/restaurant.js"; import Restaurant from "../models/restaurant.js";
import sanitizer from "string-sanitizer"; import sanitizer from "string-sanitizer";
import restaurant from "../models/restaurant.js"; import { handleError } from "../services/services.js";
var router = express.Router(); var router = express.Router();
@@ -18,9 +17,10 @@ router.get("/", (req, res) => {
$and: [ $and: [
{ $or: [{ city: { $regex: regex } }, { name: { $regex: regex } }] }, { $or: [{ city: { $regex: regex } }, { name: { $regex: regex } }] },
{ hidden: false }, { hidden: false },
{ subscriptionActive: true },
], ],
}, },
"_id name city imgUrl workingHours description tags phone hidden", "_id name city imgUrl workingHours description tags location links",
(err, results) => { (err, results) => {
if (err) { if (err) {
res.sendStatus(500); 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 // AUTOCOMPLETE
router.get("/autocomplete/", (req, res) => { router.get("/autocomplete/", (req, res) => {

View File

@@ -1,14 +1,16 @@
import express from "express"; import express from "express";
import * as services from "../services/services.js"; import * as services from "../services/services.js";
import * as databaseServices from "../services/databaseServices.js";
var router = express.Router(); var router = express.Router();
router.post("/", async (req, res) => { router.post("/", async (req, res) => {
try { try {
const decodedToken = services.validateUserToken( const newDate = await databaseServices.renewSubscription(
req.headers["x-auth-token"] req.body.restaurantId,
1
); );
res.send(decodedToken); res.send(`Subskrypcja przedłużona do: ${newDate}`);
} catch (error) { } catch (error) {
services.handleError(error, res); services.handleError(error, res);
} }

View File

@@ -1,10 +1,6 @@
import express from "express"; import express from "express";
import { changeUserPass, fetchUser } from "../services/databaseServices.js"; import { changeUserPass, fetchUser } from "../services/databaseServices.js";
import { import { createUser, prepareSafeUser } from "../services/dataPrepServices.js";
composeNewContact,
createUser,
prepareSafeUser,
} from "../services/dataPrepServices.js";
import { import {
newError, newError,
handleError, handleError,
@@ -14,13 +10,9 @@ import {
validateUserToken, validateUserToken,
hashPass, hashPass,
} from "../services/services.js"; } from "../services/services.js";
import * as config from "../config/index.js";
import AgileCRMManager from "agile_crm";
import { resetPassword } from "../services/mailServices.js"; import { resetPassword } from "../services/mailServices.js";
const { CRM_USER, CRM_EMAIL, CRM_KEY } = config;
var router = express.Router(); var router = express.Router();
var agileAPI = new AgileCRMManager(CRM_USER, CRM_KEY, CRM_EMAIL);
// LOGIN // LOGIN
router.post("/login", async (req, res) => { router.post("/login", async (req, res) => {
@@ -43,9 +35,9 @@ router.post("/register", async (req, res) => {
try { try {
await checkEmailTaken(req.body.email); await checkEmailTaken(req.body.email);
const user = await createUser(req); const user = await createUser(req);
await user.save(); await user.save().catch((e) => {
const contact = composeNewContact(user); throw newError("Niewłaściwe dane.", 500);
agileAPI.contactAPI.add(contact, null, null); });
res.sendStatus(201); res.sendStatus(201);
} catch (e) { } catch (e) {
handleError(e, res); handleError(e, res);

View File

@@ -5,52 +5,6 @@ import Dish from "../models/dish.js";
import User from "../models/users.js"; import User from "../models/users.js";
import Restaurant from "../models/restaurant.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) { export async function createUser(request) {
const password = await hashPass(request.body.password); const password = await hashPass(request.body.password);
const user = new User({ const user = new User({
@@ -68,25 +22,68 @@ export async function createUser(request) {
return user; 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 { try {
const restaurant = new Restaurant({ if (!oldRestaurant) {
_id: new mongoose.Types.ObjectId(), const img = await handleImageUpdate(request);
name: sanitizer.sanitize.keepUnicode(request.name), const restaurant = new Restaurant({
city: sanitizer.sanitize.keepUnicode(request.city), _id: new mongoose.Types.ObjectId(),
adress: sanitizer.sanitize.keepUnicode(request.adress), name: sanitizer.sanitize.keepUnicode(request.name),
location: request.location, city: sanitizer.sanitize.keepUnicode(request.city),
imgUrl: saveImage(request.imgURL), adress: sanitizer.sanitize.keepUnicode(request.adress),
workingHours: request.workingHours, location: request.location,
description: sanitizer.sanitize.keepUnicode(request.description), imgUrl: img,
tags: request.tags, workingHours: request.workingHours,
links: request.links, description: sanitizer.sanitize.keepUnicode(request.description),
phone: request.phone, tags: request.tags,
hidden: request.hidden, links: request.links,
}); phone: request.phone,
return restaurant; 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) { } catch (error) {
throw newError("Invalid input data", 206); console.log(error);
throw newError("Niewłaściwe dane", 206);
} }
} }

View File

@@ -41,7 +41,7 @@ export async function addDishToRestaurant(restaurantId, dishId) {
{ _id: restaurantId }, { _id: restaurantId },
{ $push: { dishes: dishId } } { $push: { dishes: dishId } }
).catch((error) => { ).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, { await User.findByIdAndUpdate(user.id, {
$push: { restaurants: restaurant._id }, $push: { restaurants: restaurant._id },
}).catch((e) => { }).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) { export async function fetchRestaurant(id) {
let data; let data;
await Restaurant.findById(id, (err, result) => { await Restaurant.findById(id, (err, result) => {
data = result; data = result;
}).catch((e) => { }).catch((e) => {
throw newError("Couldn't fetch restaurant", 500); throw newError("Nie udało się pobrać restauracji.", 500);
}); });
return data; return data;
} }
@@ -74,14 +125,14 @@ export async function fetchAllDishesForRestaurant(restaurant) {
export async function fetchDish(id) { export async function fetchDish(id) {
let data = await Dish.findById(id).catch((e) => { 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; return data;
} }
export async function fetchUser(email) { 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 }); 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; return user;
} }

View File

@@ -88,13 +88,13 @@ export async function checkEmailTaken(email) {
export function validateUserToken(token) { export function validateUserToken(token) {
if (!token) throw newError("Brak dostępu", 401); if (!token) throw newError("Brak dostępu", 401);
const verified = jwt try {
.verify(token, jwtSecret, { ignoreExpiration: false }) const verified = jwt.verify(token, jwtSecret, { ignoreExpiration: false });
.catch((e) => { if (!verified) throw newError("Brak dostępu", 401);
throw newError("Brak dostępu", 401); return verified;
}); } catch (error) {
if (!verified) throw newError("Brak dostępu", 401); throw newError("Brak dostępu", 401);
return verified; }
} }
export async function validateDishId(id) { export async function validateDishId(id) {
@@ -142,17 +142,6 @@ export function yearFromNowDate() {
return date.addDays(365); 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) { export async function hashPass(pass) {
if (pass.length < 6) { if (pass.length < 6) {
throw newError("Hasło za krótkie.", 500); 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) { export async function saveImage(url) {
const newURL = await renameBlob(url); const newURL = await renameBlob(url);
return newURL; return newURL;

View File

@@ -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);
});