diff --git a/.gitignore b/.gitignore index 266b8b8..ed2db8e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,6 @@ package-lock.json # debug npm-debug.log* yarn-debug.log* -yarn-error.log* \ No newline at end of file +yarn-error.log* + +.idea \ No newline at end of file diff --git a/client/public/index.html b/client/public/index.html index 1beac3c..1891643 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -5,7 +5,7 @@ - MERN Ecommerce Application + Por el comercion local diff --git a/package.json b/package.json index 8707ab0..4118965 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ }, "homepage": "https://github.com/mohamedsamara/mern-ecommerce#readme", "dependencies": { + "awesome-phonenumber": "^2.29.0", "axios": "^0.19.0", "bcryptjs": "^2.4.3", "bootstrap": "^4.3.1", @@ -40,9 +41,11 @@ "cors": "^2.8.5", "crypto": "^1.0.1", "dotenv": "^8.0.0", + "email-validator": "^2.0.4", "express": "^4.17.1", "font-awesome": "^4.7.0", "history": "^4.9.0", + "iban": "^0.0.14", "jsonwebtoken": "^8.5.1", "mailchimp-v3": "^1.0.5", "mailgun-js": "^0.22.0", diff --git a/server/controllers/auth.js b/server/controllers/auth.js new file mode 100644 index 0000000..dc82ac5 --- /dev/null +++ b/server/controllers/auth.js @@ -0,0 +1,58 @@ +const { login, register } = require("../services/auth"); +const { loginValidation, registerValidation } = require("../validations/auth"); +const mailgun = require('../config/mailgun'); +const template = require('../config/template'); + +exports.doRegister = async (req, res) => { + try{ + const { error } = registerValidation(req); + if (error) return res.status(422).json(error); + const { + email, + password, + firstName, + lastName, + } = req.body; + + const { user, token } = await register(email, password, { firstName, lastName }); + + const message = template.signupEmail(user.profile); + mailgun.sendEmail(user.email, message); + + res.status(200).json({ + success: true, + token: `Bearer ${token}`, + user, + }); + } catch (e) { + res.status(400).json({ + error: "Something went wrong" + }); + } +}; + +exports.doLogin = async (req, res) => { + try { + const { error } = loginValidation(req); + if (error) return res.status(422).json(error); + + const { + email, + password, + } = req.body; + + const { user, token, error: passwordError } = await login(email, password); + + if (passwordError) return res.status(404).json({ success: false, error: passwordError}); + + res.status(200).json({ + success: true, + token: `Bearer ${token}`, + user: user, + }); + } catch (e) { + res.status(400).json({ + error: e.toString() + }); + } +}; diff --git a/server/controllers/cities.js b/server/controllers/cities.js new file mode 100644 index 0000000..0b3df8e --- /dev/null +++ b/server/controllers/cities.js @@ -0,0 +1,21 @@ +const { createCity, updateCity } = require("../services/cities"); +const { updateCity } = require("../services/users"); + +exports.postCity = async (req, res) => { + try { + const { + name, + description + } = req.body; + const { _id: userId } = req.user; + + const city = await createCity(userId, name, description); + const updatedUser = await updateCity(userId, city._id); + + res.status(200).json({ city, user: updatedUser }); + } catch (e) { + res.status(400).json({ + error: "Something went wrong" + }) + } +}; \ No newline at end of file diff --git a/server/controllers/commerces.js b/server/controllers/commerces.js new file mode 100644 index 0000000..72db80f --- /dev/null +++ b/server/controllers/commerces.js @@ -0,0 +1,23 @@ + +const { createCommerce } = require("../services/commerces"); +const { generateDefaultCards } = require("../services/gift-cards"); + +exports.postCommerce = async (req, res) => { + try { + // TODO: validate body + const { + name, + description, + images, + cityId + } = req.body; + const commerce = await createCommerce({ name, description, images }, req.user.id, cityId); + const cards = await generateDefaultCards(commerce.id); + + res.status(200).json({ commerce, cards }); + } catch (e) { + res.status(400).json({ + error: "Something went wrong" + }); + } +} \ No newline at end of file diff --git a/server/models/brand.js b/server/models/brand.js deleted file mode 100644 index 18e269b..0000000 --- a/server/models/brand.js +++ /dev/null @@ -1,35 +0,0 @@ -const Mongoose = require('mongoose'); -const slug = require('mongoose-slug-generator'); -const { Schema } = Mongoose; - -const options = { - separator: '-', - lang: 'en', - truncate: 120 -}; - -Mongoose.plugin(slug, options); - -// Brand Schema -const BrandSchema = new Schema({ - name: { - type: String, - trim: true - }, - slug: { type: String, slug: 'name', unique: true }, - image: { - data: Buffer, - contentType: String - }, - description: { - type: String, - trim: true - }, - updated: Date, - created: { - type: Date, - default: Date.now - } -}); - -module.exports = Mongoose.model('Brand', BrandSchema); diff --git a/server/models/category.js b/server/models/city.js similarity index 58% rename from server/models/category.js rename to server/models/city.js index fa0c585..49273ce 100644 --- a/server/models/category.js +++ b/server/models/city.js @@ -11,7 +11,7 @@ const options = { Mongoose.plugin(slug, options); // Category Schema -const CategorySchema = new Schema({ +const CitySchema = new Schema({ _id: { type: Schema.ObjectId, auto: true @@ -21,25 +21,26 @@ const CategorySchema = new Schema({ trim: true }, slug: { type: String, slug: 'name', unique: true }, - image: { - data: Buffer, - contentType: String - }, description: { type: String, trim: true }, - products: [ - { - type: Schema.Types.ObjectId, - ref: 'Product' - } - ], - updated: Date, - created: { - type: Date, - default: Date.now + deleted: { + type: Boolean, + default: false, + }, + admin: { + type: Schema.Types.ObjectId, + ref: "User", + required: true, + } +}, { + timestamps: { + createdAt: "created", + updatedAt: "updated", } }); -module.exports = Mongoose.model('Category', CategorySchema); +CitySchema.index({ name: 'text' }); + +module.exports = Mongoose.model('City', CitySchema); diff --git a/server/models/commerce.js b/server/models/commerce.js new file mode 100644 index 0000000..531920d --- /dev/null +++ b/server/models/commerce.js @@ -0,0 +1,84 @@ +const Mongoose = require('mongoose'); +const slug = require('mongoose-slug-generator'); +const PhoneNumber = require('awesome-phonenumber'); +const IBAN = require('iban'); +const { Schema } = Mongoose; + +const options = { + separator: '-', + lang: 'en', + truncate: 120 +}; + +Mongoose.plugin(slug, options); + +const CommerceSchema = new Schema({ + name: { + type: String, + trim: true, + required: true, + }, + slug: { type: String, slug: 'name', unique: true }, + images: [{ + data: Buffer, + contentType: String + }], + description: { + type: String, + trim: true, + required: true, + }, + city: { + type: Schema.Types.ObjectId, + ref: "City", + required: true, + }, + phoneNumber: { + type: String, + trim: true, + required: true, + validate: { + validator: phone => { + const parsedNumber = PhoneNumber(phone); + return parsedNumber.isMobile() || parsedNumber.isFixedLine(); + }, + message: props => `${props.value} is not a phone number!` + }, + }, + bizum: { + type: String, + trim: true, + validate: { + validator: phone => PhoneNumber(phone).isMobile(), + message: props => `${props.value} is not a phone number!` + }, + }, + bankAccount: { + type: String, + trim: true, + required: true, + validate: { + validator: IBAN.isValid, + message: props => `${props.value} is not a valid IBAN!` + }, + }, + discount: { + type: Number, + min: 0, + max: 50, + required: true, + }, + deleted: { + type: Boolean, + default: false, + }, +}, { + timestamps: { + createdAt: "created", + updatedAt: "updated", + } +}); + +CommerceSchema.index({ name: 'text' }); + +module.exports = Mongoose.model('Commerce', CommerceSchema); diff --git a/server/models/gift-card.js b/server/models/gift-card.js new file mode 100644 index 0000000..116a1c1 --- /dev/null +++ b/server/models/gift-card.js @@ -0,0 +1,44 @@ +const Mongoose = require('mongoose'); +const slug = require('mongoose-slug-generator'); +const { Schema } = Mongoose; + +const options = { + separator: '-', + lang: 'en', + truncate: 120 +}; + +Mongoose.plugin(slug, options); + +const GiftCardSchema = new Schema({ + hash: { + type: String, + trim: true, + unique: true, + required: true, + }, + quantity: { + type: Number, + required: true, + min: 0, + }, + transaction: { + type: Schema.Types.ObjectId, + ref: 'Transaction', + required: true, + }, + isConsumed: { + type: Boolean, + default: false, + }, + deleted: { + type: Boolean, + default: false, + }, +},{ + timestamps: { + createdAt: "created", + } +}); + +module.exports = Mongoose.model('GiftCard', GiftCardSchema); diff --git a/server/models/product.js b/server/models/product.js deleted file mode 100644 index a9a8677..0000000 --- a/server/models/product.js +++ /dev/null @@ -1,48 +0,0 @@ -const Mongoose = require('mongoose'); -const slug = require('mongoose-slug-generator'); -const { Schema } = Mongoose; - -const options = { - separator: '-', - lang: 'en', - truncate: 120 -}; - -Mongoose.plugin(slug, options); - -// Product Schema -const ProductSchema = new Schema({ - sku: { - type: String - }, - name: { - type: String, - trim: true - }, - slug: { type: String, slug: 'name', unique: true }, - image: { - data: Buffer, - contentType: String - }, - description: { - type: String, - trim: true - }, - quantity: { - type: Number - }, - price: { - type: Number - }, - brand: { - type: Schema.Types.ObjectId, - ref: 'Brand' - }, - updated: Date, - created: { - type: Date, - default: Date.now - } -}); - -module.exports = Mongoose.model('Product', ProductSchema); diff --git a/server/models/transaction.js b/server/models/transaction.js new file mode 100644 index 0000000..7dad1c9 --- /dev/null +++ b/server/models/transaction.js @@ -0,0 +1,71 @@ +const Mongoose = require('mongoose'); +const slug = require('mongoose-slug-generator'); +const PhoneNumber = require('awesome-phonenumber'); +const EmailValidator = require("email-validator"); +const { Schema } = Mongoose; + +const options = { + separator: '-', + lang: 'en', + truncate: 120 +}; + +Mongoose.plugin(slug, options); + +const TransactionSchema = new Schema({ + client: { + firstName: { + type: String, + trim: true, + required: true, + }, + lastName: { + type: String, + trim: true, + required: true, + }, + email: { + type: String, + trim: true, + required: true, + validate: { + validator: email => EmailValidator.validate(email), + message: props => `${props.value} is not a valid email!` + }, + }, + phoneNumber: { + type: String, + trim: true, + required: true, + validate: { + validator: phone => PhoneNumber(phone).isMobile(), + message: props => `${props.value} is not a phone number!` + }, + } + }, + commerce: { + type: Schema.Types.ObjectId, + ref: "Commerce", + required: true, + }, + quantity: { + type: Number, + min: 1, + required: true, + }, + isConfirmed: { + type: Boolean, + default: false, + }, + deleted: { + type: Boolean, + default: false, + }, +}, { + timestamps: { + createdAt: "created", + updatedAt: "updated", + } +}); + +module.exports = Mongoose.model('Transaction', TransactionSchema); diff --git a/server/models/user.js b/server/models/user.js index 5b1974b..357f951 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -22,6 +22,10 @@ const UserSchema = new Schema({ enum: ['ROLE_MEMBER', 'ROLE_ADMIN', 'ROLE_COLLABORATOR'], default: 'ROLE_ADMIN' }, + city: { + type: Schema.Types.ObjectId, + ref: "City" + }, resetPasswordToken: { type: String }, resetPasswordExpires: { type: Date } }); diff --git a/server/routes/api/auth.js b/server/routes/api/auth.js index 1b3cac7..1f14a7b 100644 --- a/server/routes/api/auth.js +++ b/server/routes/api/auth.js @@ -1,142 +1,9 @@ const express = require('express'); const router = express.Router(); -const bcrypt = require('bcryptjs'); -const jwt = require('jsonwebtoken'); -const crypto = require('crypto'); -// Bring in Models & Helpers -const User = require('../../models/user'); -const mailgun = require('../../config/mailgun'); -const template = require('../../config/template'); - -const key = process.env.SECRET_OR_KEY; - -router.post('/login', (req, res) => { - const email = req.body.email; - const password = req.body.password; - - if (!email) { - return res.status(422).json({ error: 'You must enter an email address.' }); - } - - if (!password) { - return res.status(422).json({ error: 'You must enter a password.' }); - } - - User.findOne({ email }).then(user => { - if (!user) { - return res - .status(422) - .send({ error: 'no user found for this email address.' }); - } - bcrypt.compare(password, user.password).then(isMatch => { - if (isMatch) { - const payload = { id: user.id }; - jwt.sign(payload, key, { expiresIn: 3600 }, (err, token) => { - res.status(200).json({ - success: true, - token: `Bearer ${token}`, - user: { - id: user.id, - profile: { - firstName: user.profile.firstName, - lastName: user.profile.lastName, - is_subscribed: user.profile.is_subscribed - }, - email: user.email, - role: user.role - } - }); - }); - } else { - return res.status(404).json({ - success: false, - error: 'Password Incorrect' - }); - } - }); - }); -}); - -router.post('/register', (req, res, next) => { - const email = req.body.email; - const firstName = req.body.firstName; - const lastName = req.body.lastName; - const password = req.body.password; - const is_subscribed = req.body.isSubscribed; - - if (!email) { - return res.status(422).json({ error: 'You must enter an email address.' }); - } - - if (!firstName || !lastName) { - return res.status(422).json({ error: 'You must enter your full name.' }); - } - - if (!password) { - return res.status(422).json({ error: 'You must enter a password.' }); - } - - User.findOne({ email }, (err, existingUser) => { - if (err) { - return next(err); - } - - if (existingUser) { - return res - .status(422) - .json({ error: 'That email address is already in use.' }); - } - - const user = new User({ - email, - password, - profile: { firstName, lastName, is_subscribed } - }); - - bcrypt.genSalt(10, (err, salt) => { - bcrypt.hash(user.password, salt, (err, hash) => { - if (err) { - return res.status(422).json({ - error: 'Your request could not be processed. Please try again.' - }); - } - user.password = hash; - - user.save((err, user) => { - if (err) { - return res.status(422).json({ - error: 'Your request could not be processed. Please try again.' - }); - } - - const payload = { id: user.id }; - - jwt.sign(payload, key, { expiresIn: 3600 }, (err, token) => { - res.status(200).json({ - success: true, - token: `Bearer ${token}`, - user: { - id: user.id, - profile: { - firstName: user.profile.firstName, - lastName: user.profile.lastName, - is_subscribed: user.profile.is_subscribed - }, - email: user.email, - role: user.role - } - }); - }); - - const message = template.signupEmail(user.profile); - - mailgun.sendEmail(user.email, message); - }); - }); - }); - }); -}); +const { doLogin, doRegister } = require("../../controllers/auth"); +router.post('/login', doLogin); +router.post('/register', doRegister); module.exports = router; diff --git a/server/routes/api/category.js b/server/routes/api/category.js index 0f65daf..8b3361c 100644 --- a/server/routes/api/category.js +++ b/server/routes/api/category.js @@ -3,7 +3,7 @@ const router = express.Router(); const passport = require('passport'); // Bring in Models & Helpers -const Category = require('../../models/category'); +const Category = require('../../models/city'); router.post( '/add', diff --git a/server/routes/api/brand.js b/server/routes/api/commerce.js similarity index 80% rename from server/routes/api/brand.js rename to server/routes/api/commerce.js index bf737fc..7d49aae 100644 --- a/server/routes/api/brand.js +++ b/server/routes/api/commerce.js @@ -3,7 +3,11 @@ const router = express.Router(); const passport = require('passport'); // Bring in Models & Helpers -const Brand = require('../../models/brand'); +const Commerce = require('../../models/commerce'); +const { isAdmin } = require("../middlewares/auth"); +const { postCommerce } = require("../../controllers/commerces"); + +router.post("/", passport.authenticate('jwt', { session: false }), isAdmin, postCommerce); router.post( '/add', @@ -18,7 +22,7 @@ router.post( .json({ error: 'You must enter description & name.' }); } - const brand = new Brand({ + const brand = new Commerce({ name, description }); @@ -41,7 +45,7 @@ router.post( // fetch all brands api router.get('/list', (req, res) => { - Brand.find({}, (err, data) => { + Commerce.find({}, (err, data) => { if (err) { return res.status(422).json({ error: 'Your request could not be processed. Please try again.' @@ -57,7 +61,7 @@ router.get( '/list/select', passport.authenticate('jwt', { session: false }), (req, res) => { - Brand.find({}, 'name', (err, data) => { + Commerce.find({}, 'name', (err, data) => { if (err) { return res.status(422).json({ error: 'Your request could not be processed. Please try again.' @@ -75,7 +79,7 @@ router.delete( '/delete/:id', passport.authenticate('jwt', { session: false }), (req, res) => { - Brand.deleteOne({ _id: req.params.id }, (err, data) => { + Commerce.deleteOne({ _id: req.params.id }, (err, data) => { if (err) { return res.status(422).json({ error: 'Your request could not be processed. Please try again.' diff --git a/server/routes/api/index.js b/server/routes/api/index.js index d4ee58e..1c7357f 100644 --- a/server/routes/api/index.js +++ b/server/routes/api/index.js @@ -4,7 +4,7 @@ const userRoutes = require('./user'); const newsletterRoutes = require('./newsletter'); const productRoutes = require('./product'); const categoryRoutes = require('./category'); -const brandRoutes = require('./brand'); +const brandRoutes = require('./commerce'); const contactRoutes = require('./contact'); // auth routes diff --git a/server/routes/api/product.js b/server/routes/api/product.js index 5365f85..2ef82db 100644 --- a/server/routes/api/product.js +++ b/server/routes/api/product.js @@ -3,9 +3,9 @@ const router = express.Router(); const passport = require('passport'); // Bring in Models & Helpers -const Product = require('../../models/product'); -const Brand = require('../../models/brand'); -const Category = require('../../models/category'); +const Product = require('../../models/gift-card'); +const Brand = require('../../models/commerce'); +const Category = require('../../models/city'); router.post( '/add', diff --git a/server/routes/middlewares/auth.js b/server/routes/middlewares/auth.js new file mode 100644 index 0000000..94ae6ce --- /dev/null +++ b/server/routes/middlewares/auth.js @@ -0,0 +1,5 @@ + +exports.isAdmin = (req, res, next) => { + if (req.user.role === "ROLE_ADMIN") next(req, res); + else res.status(404).json({ error: "Debes ser administrador"}); +}; \ No newline at end of file diff --git a/server/services/auth.js b/server/services/auth.js new file mode 100644 index 0000000..6dd95b1 --- /dev/null +++ b/server/services/auth.js @@ -0,0 +1,42 @@ +const bcrypt = require('bcryptjs'); +const jwt = require('jsonwebtoken'); + +const User = require('../models/user'); +const key = process.env.SECRET_OR_KEY; + +const createResult = (user) => { + const { id, profile, email, role } = user; + return { id, profile, email, role }; +}; + +const loginError = () => ({ error: "Password incorrect" }); + +exports.register = async (email, password, profile) => { + const user = new User({ + email, + password, + profile + }); + + const salt = await bcrypt.genSalt(10); + const hash = await bcrypt.hash(password, salt); + user.password = hash; + await user.save(); + + const payload = { id: user.id }; + const token = await jwt.sign(payload, key, { expiresIn: 3600 }); + + return { user: createResult(user), token }; +}; + +exports.login = async (email, password) => { + const user = await User.findOne({ email }); + + const matched = await bcrypt.compare(password, user.password); + if (matched) { + const payload = { id: user.id }; + const token = jwt.sign(payload, key, { expiresIn: 3600 }); + return { user: createResult(user), token }; + } + return loginError(); +}; diff --git a/server/services/cities.js b/server/services/cities.js new file mode 100644 index 0000000..2a318aa --- /dev/null +++ b/server/services/cities.js @@ -0,0 +1,48 @@ +const City = require('../models/city.js'); + +const createResult = (city) => { + if (!city) return {}; + const { _id, name, description, created } = city; + return { _id, name, description, created }; +}; + +exports.createCity = async (userId, name, description) => { + const city = new City({ + name, + description, + admin: userId, + }); + await city.save(); + return createResult(city); +}; + +exports.updateCity = async (cityId, name, description) => { + const updatedCity = await City.updateOne({ _id: cityId }, { + $set: { name, description }, + }); + await updatedCity.save(); + return createResult(updatedCity); +}; + +exports.deleteCity = async (cityId) => { + const deletedCity = await City.updateOne({ _id: cityId}, { + $set: { deleted: true }, + }); + await deletedCity.save(); + return true; +}; + +exports.getCitiesByName = async (textQuery) => { + const cities = await City.find({ $text: { $search: textQuery }, deleted: false }); + return cities ? cities.map(createResult) : [] +}; + +exports.getCityById = async (cityId) => { + const city = await City.findOne({ _id: cityId}); + return createResult(city); +}; + +exports.getAllCities = async () => { + const cities = await City.find({ deleted: false }); + return cities ? cities.map(createResult) : [] +}; diff --git a/server/services/commerces.js b/server/services/commerces.js new file mode 100644 index 0000000..c4a1a16 --- /dev/null +++ b/server/services/commerces.js @@ -0,0 +1,59 @@ +const Commerce = require('../models/commerce'); + +exports.createCommerce = async (name, description, phoneNumber, cityId, bizum, bankAccount, discount) => { + const commerce = new Commerce({ + name, + description, + city: cityId, + bizum, + bankAccount, + discount + }); + await commerce.save(); + return commerce +}; + +exports.updateCommerce = async (id, name, description, phoneNumber, cityId, bizum, bankAccount, discount) => { + const updated = await Commerce.updateOne({ _id: id }, { + $set: { + name, + description, + phoneNumber, + cityId, + bizum, + bankAccount, + discount + }, + }); + await updated.save(); + return updated +}; + +exports.deleteCommerce = async (id) => { + const deletedItem = await Commerce.updateOne({ _id: id}, { + $set: { deleted: true }, + }); + await deletedItem.save(); + return true; +}; + +exports.getCommerceById = async (id) => { + const item = await Commerce.findOne({ _id: id, deleted: false }); + return item || {}; +}; + +exports.getCommercesByName = async (textQuery) => { + const items = await Commerce.find({ $text: { $search: textQuery }, deleted: false }); + return items || []; +}; + +exports.getCommercesByCity = async (cityId) => { + const items = await Commerce.find({ city: cityId, deleted: false }); + return items || []; +}; + +exports.getCommerces = async ({ query, select, cursor }) => { + const newQuery = { ...query, deleted: false }; + const items = await Commerce.find(newQuery, select, cursor); + return items || []; +}; diff --git a/server/services/gift-cards.js b/server/services/gift-cards.js new file mode 100644 index 0000000..96bc0d7 --- /dev/null +++ b/server/services/gift-cards.js @@ -0,0 +1,39 @@ +const GiftCard = require('../models/gift-card'); + +exports.generateDefaultCards = async (commerceId) => { + // TODO: generate default gift cards + return [] +}; + +exports.createGiftCard = async (quantity, transaction) => { + const item = new GiftCard({ + quantity, + transaction, + hash: '' // TODO: Generate hash + }); + await item.save(); + return item +}; + +exports.updateGiftCard = async (id, quantity, isConsumed) => { + // TODO: Hash validation + const updated = await GiftCard.updateOne({ _id: id }, { + $set: { quantity, isConsumed }, + }); + await updated.save(); + return updated +}; + +exports.deleteGiftCard = async (id) => { + const deletedItem = await GiftCard.updateOne({ _id: id}, { + $set: { deleted: true }, + }); + await deletedItem.save(); + return true; +}; + +exports.getGiftCards = async ({ query, select, cursor }) => { + const newQuery = { ...query, deleted: false }; + const items = await GiftCard.find(newQuery, select, cursor); + return items || []; +}; diff --git a/server/services/transactions.js b/server/services/transactions.js new file mode 100644 index 0000000..50c0d36 --- /dev/null +++ b/server/services/transactions.js @@ -0,0 +1,36 @@ +const Transaction = require('../models/commerce'); + +exports.createTransaction = async (name, description, phoneNumber, cityId, bizum, bankAccount, discount) => { + const transaction = new Transaction({ + name, + description, + city: cityId, + bizum, + bankAccount, + discount + }); + await transaction.save(); + return transaction +}; + +exports.updateTransaction = async (id, client, quantity, commerce, isConfirmed) => { + const updated = await Transaction.updateOne({ _id: id }, { + $set: { client, quantity, commerce, isConfirmed }, + }); + await updated.save(); + return updated +}; + +exports.deleteTransaction = async (id) => { + const deletedItem = await Transaction.updateOne({ _id: id}, { + $set: { deleted: true }, + }); + await deletedItem.save(); + return true; +}; + +exports.getTransactions = async ({ query, select, cursor }) => { + const newQuery = { ...query, deleted: false }; + const items = await Transaction.find(newQuery, select, cursor); + return items || []; +}; diff --git a/server/services/users.js b/server/services/users.js new file mode 100644 index 0000000..480d760 --- /dev/null +++ b/server/services/users.js @@ -0,0 +1,13 @@ +const User = require("../models/user"); + +const updateCity = async (userId, cityId) => { + const updatedUser = await User.updateOne({ _id: userId }, { + $set: { city: cityId } + }); + await updatedUser.save(); + return updatedUser; +}; + +exports = { + updateCity +}; \ No newline at end of file diff --git a/server/validations/auth.js b/server/validations/auth.js new file mode 100644 index 0000000..9101c28 --- /dev/null +++ b/server/validations/auth.js @@ -0,0 +1,37 @@ + +exports.loginValidation = (req) => { + const { email, password } = req.body; + + if (!email) { + return { error: 'You must enter an email address.' }; + } + + if (!password) { + return { error: 'You must enter a password.' }; + } + + return {} +}; + +exports.registerValidation = (req) => { + const { + email, + firstName, + lastName, + password + } = req.body; + + if (!email) { + return { error: 'You must enter an email address.' }; + } + + if (!firstName || !lastName) { + return { error: 'You must enter your full name.' }; + } + + if (!password) { + return {error: 'You must enter a password.'}; + } + + return {}; +}; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 984edd8..b119e39 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1626,6 +1626,11 @@ autoprefixer@^9.6.0: postcss "^7.0.27" postcss-value-parser "^4.0.3" +awesome-phonenumber@^2.29.0: + version "2.29.0" + resolved "https://registry.yarnpkg.com/awesome-phonenumber/-/awesome-phonenumber-2.29.0.tgz#bda4c2bf303a095054d7933ac51956276d49bfba" + integrity sha512-SLa0WBpMkZHCupRWHWoFxl+VKSmqdQDcqwBc6B1A72yNr6rewspARsqvgsl+y1Ervw4cf2fW1x+YyeAbazXuYw== + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -3013,11 +3018,6 @@ detect-file@^1.0.0: resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - detect-node@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" @@ -3204,6 +3204,11 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" +email-validator@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed" + integrity sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ== + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -3767,13 +3772,6 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" -fs-minipass@^1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" @@ -4335,7 +4333,12 @@ https-proxy-agent@^3.0.0: agent-base "^4.3.0" debug "^3.1.0" -iconv-lite@0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: +iban@^0.0.14: + version "0.0.14" + resolved "https://registry.yarnpkg.com/iban/-/iban-0.0.14.tgz#fd8a65ac50c8b770682634b20dc5c5c9feba5c24" + integrity sha512-+rocNKk+Ga9m8Lr9fTMWd+87JnsBrucm0ZsIx5ROOarZlaDLmd+FKdbtvb0XyoBw9GAFOYG2GuLqoNB16d+p3w== + +iconv-lite@0.4.24, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -4369,13 +4372,6 @@ ignore-by-default@^1.0.1: resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= -ignore-walk@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" - integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== - dependencies: - minimatch "^3.0.4" - ignore@^3.3.5: version "3.3.10" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" @@ -5517,21 +5513,6 @@ minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.2.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - mississippi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" @@ -5697,15 +5678,6 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" -needle@^2.2.1: - version "2.3.3" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.3.tgz#a041ad1d04a871b0ebb666f40baaf1fb47867117" - integrity sha512-EkY0GeSq87rWp1hoq/sH/wnTWgFVhYlnIkbJ0YJFfRgEFlz2RraCjBpFQ+vrEgEdp0ThfyHADmkChEhcb7PKyw== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - negotiator@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" @@ -5793,22 +5765,6 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-pre-gyp@*: - version "0.14.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" - integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4.4.2" - node-releases@^1.1.52: version "1.1.53" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" @@ -5860,14 +5816,6 @@ nodemon@^1.19.1: dependencies: abbrev "1" -nopt@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== - dependencies: - abbrev "1" - osenv "^0.1.4" - nopt@~1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" @@ -5917,27 +5865,6 @@ normalize-url@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== -npm-bundled@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" - integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== - dependencies: - npm-normalize-package-bin "^1.0.1" - -npm-normalize-package-bin@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" - integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== - -npm-packlist@^1.1.6: - version "1.4.8" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" - integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - npm-normalize-package-bin "^1.0.1" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -5945,7 +5872,7 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2: +"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -6142,7 +6069,7 @@ os-tmpdir@^1.0.0: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@0, osenv@^0.1.4: +osenv@0: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== @@ -7117,7 +7044,7 @@ raw-body@^2.2.0: iconv-lite "0.4.24" unpipe "1.0.0" -rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: +rc@^1.0.1, rc@^1.1.6: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -7651,7 +7578,7 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= -rimraf@2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: +rimraf@2, rimraf@^2.5.4, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -7723,7 +7650,7 @@ sass-loader@^7.1.0: pify "^4.0.1" semver "^6.3.0" -sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4: +sax@>=0.6.0, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -7780,7 +7707,7 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" -"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -8426,19 +8353,6 @@ tar@^2.0.0: fstream "^1.0.12" inherits "2" -tar@^4.4.2: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - term-size@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" @@ -9275,7 +9189,7 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: +yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==