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==