├── README.md ├── client ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── README.md ├── build │ ├── build.js │ ├── check-versions.js │ ├── logo.png │ ├── utils.js │ ├── vue-loader.conf.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── config │ ├── dev.env.js │ ├── index.js │ └── prod.env.js ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── HelloWorld.vue │ │ ├── auth │ │ │ └── LoginUser.vue │ │ ├── dash │ │ │ └── UserDash.vue │ │ ├── layouts │ │ │ └── AppLayout.vue │ │ ├── product │ │ │ ├── manufacturer │ │ │ │ └── ManufacturerList.vue │ │ │ ├── option │ │ │ │ ├── ProductOptionGroupList.vue │ │ │ │ └── ProductOptionList.vue │ │ │ ├── product │ │ │ │ ├── ProductAttribute.vue │ │ │ │ ├── ProductCreate.vue │ │ │ │ ├── ProductImage.vue │ │ │ │ └── ProductList.vue │ │ │ └── section │ │ │ │ ├── ProductCategoryList.vue │ │ │ │ └── ProductSubCategoryList.vue │ │ └── utils │ │ │ └── Loader.vue │ ├── helpers │ │ └── ucWords.js │ ├── main.js │ ├── router │ │ ├── index.js │ │ └── metaCheck.js │ ├── store │ │ ├── index.js │ │ └── modules │ │ │ ├── auth │ │ │ └── auth.js │ │ │ ├── manufacturer │ │ │ └── manufacturer.js │ │ │ ├── product │ │ │ ├── category.js │ │ │ ├── option.js │ │ │ ├── optionGroup.js │ │ │ ├── product.js │ │ │ └── subCategory.js │ │ │ └── utils │ │ │ ├── error.js │ │ │ └── loader.js │ └── utils │ │ └── setAuthToken.js └── static │ ├── .gitkeep │ └── favicon.ico └── server ├── .gitignore ├── api ├── index.js └── routes │ ├── admin │ ├── auth.js │ └── product │ │ ├── option.js │ │ ├── optionGroup.js │ │ ├── product.js │ │ ├── productCategory.js │ │ ├── productManufacturer.js │ │ └── productSubCategory.js │ └── client │ ├── auth.js │ ├── order │ └── order.js │ └── product │ ├── category.js │ ├── product.js │ └── subCategory.js ├── app.js ├── config ├── custom-environment-variables.json ├── default.json └── production.json ├── helpers ├── errors_response.js ├── jwt_access_token.js └── something_error.js ├── middlewares └── admin │ ├── admin.js │ └── auth.js ├── models ├── admin │ ├── product │ │ ├── option.js │ │ ├── optionGroup.js │ │ ├── product.js │ │ ├── productCategory.js │ │ ├── productManufacturer.js │ │ └── productSubCategory.js │ └── user.js └── client │ └── customer.js ├── package-lock.json ├── package.json ├── utils ├── cloudinary.js ├── passport.js └── setValidationErrors.js └── validations ├── admin ├── auth │ ├── login.js │ └── register.js └── product │ ├── option.js │ ├── optionGroup.js │ ├── product.js │ ├── productCategory.js │ ├── productManufacturer.js │ └── productSubCategory.js └── client └── auth ├── login.js └── register.js /README.md: -------------------------------------------------------------------------------- 1 | # Ecom-backend 2 | [Live Demo](https://alphaecom.herokuapp.com) 3 | -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /client/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /client/.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | 'standard' 17 | ], 18 | // required to lint *.vue files 19 | plugins: [ 20 | 'vue' 21 | ], 22 | // add your custom rules here 23 | rules: { 24 | // allow async-await 25 | 'generator-star-spacing': 'off', 26 | // allow debugger during development 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /client/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # client 2 | 3 | > A Vue.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | ``` 20 | 21 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 22 | -------------------------------------------------------------------------------- /client/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /client/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /client/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudio-qui/node-vue-ecommerce-backend/6fd72ad226b3bc3715fd34351efc5afa9659497d/client/build/logo.png -------------------------------------------------------------------------------- /client/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /client/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | const createLintingRule = () => ({ 12 | test: /\.(js|vue)$/, 13 | loader: 'eslint-loader', 14 | enforce: 'pre', 15 | include: [resolve('src'), resolve('test')], 16 | options: { 17 | formatter: require('eslint-friendly-formatter'), 18 | emitWarning: !config.dev.showEslintErrorsInOverlay 19 | } 20 | }) 21 | 22 | module.exports = { 23 | context: path.resolve(__dirname, '../'), 24 | entry: { 25 | app: './src/main.js' 26 | }, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: '[name].js', 30 | publicPath: process.env.NODE_ENV === 'production' 31 | ? config.build.assetsPublicPath 32 | : config.dev.assetsPublicPath 33 | }, 34 | resolve: { 35 | extensions: ['.js', '.vue', '.json'], 36 | alias: { 37 | 'vue$': 'vue/dist/vue.esm.js', 38 | '@': resolve('src'), 39 | } 40 | }, 41 | module: { 42 | rules: [ 43 | ...(config.dev.useEslint ? [createLintingRule()] : []), 44 | { 45 | test: /\.vue$/, 46 | loader: 'vue-loader', 47 | options: vueLoaderConfig 48 | }, 49 | { 50 | test: /\.js$/, 51 | loader: 'babel-loader', 52 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 53 | }, 54 | { 55 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 56 | loader: 'url-loader', 57 | options: { 58 | limit: 10000, 59 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 60 | } 61 | }, 62 | { 63 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 64 | loader: 'url-loader', 65 | options: { 66 | limit: 10000, 67 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 68 | } 69 | }, 70 | { 71 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 72 | loader: 'url-loader', 73 | options: { 74 | limit: 10000, 75 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 76 | } 77 | } 78 | ] 79 | }, 80 | node: { 81 | // prevent webpack from injecting useless setImmediate polyfill because Vue 82 | // source contains it (although only uses it if it's native). 83 | setImmediate: false, 84 | // prevent webpack from injecting mocks to Node native modules 85 | // that does not make sense for the client 86 | dgram: 'empty', 87 | fs: 'empty', 88 | net: 'empty', 89 | tls: 'empty', 90 | child_process: 'empty' 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /client/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay 38 | ? { warnings: false, errors: true } 39 | : false, 40 | publicPath: config.dev.assetsPublicPath, 41 | proxy: config.dev.proxyTable, 42 | quiet: true, // necessary for FriendlyErrorsPlugin 43 | watchOptions: { 44 | poll: config.dev.poll, 45 | } 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/dev.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }) 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err) 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /client/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = require('../config/prod.env') 15 | 16 | const webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true, 21 | usePostCSS: true 22 | }) 23 | }, 24 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 25 | output: { 26 | path: config.build.assetsRoot, 27 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 28 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 29 | }, 30 | plugins: [ 31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 32 | new webpack.DefinePlugin({ 33 | 'process.env': env 34 | }), 35 | new UglifyJsPlugin({ 36 | uglifyOptions: { 37 | compress: { 38 | warnings: false 39 | } 40 | }, 41 | sourceMap: config.build.productionSourceMap, 42 | parallel: true 43 | }), 44 | // extract css into its own file 45 | new ExtractTextPlugin({ 46 | filename: utils.assetsPath('css/[name].[contenthash].css'), 47 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 51 | allChunks: true, 52 | }), 53 | // Compress extracted CSS. We are using this plugin so that possible 54 | // duplicated CSS from different components can be deduped. 55 | new OptimizeCSSPlugin({ 56 | cssProcessorOptions: config.build.productionSourceMap 57 | ? { safe: true, map: { inline: false } } 58 | : { safe: true } 59 | }), 60 | // generate dist index.html with correct asset hash for caching. 61 | // you can customize output by editing /index.html 62 | // see https://github.com/ampedandwired/html-webpack-plugin 63 | new HtmlWebpackPlugin({ 64 | filename: config.build.index, 65 | template: 'index.html', 66 | inject: true, 67 | minify: { 68 | removeComments: true, 69 | collapseWhitespace: true, 70 | removeAttributeQuotes: true 71 | // more options: 72 | // https://github.com/kangax/html-minifier#options-quick-reference 73 | }, 74 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 75 | chunksSortMode: 'dependency' 76 | }), 77 | // keep module.id stable when vendor modules does not change 78 | new webpack.HashedModuleIdsPlugin(), 79 | // enable scope hoisting 80 | new webpack.optimize.ModuleConcatenationPlugin(), 81 | // split vendor js into its own file 82 | new webpack.optimize.CommonsChunkPlugin({ 83 | name: 'vendor', 84 | minChunks (module) { 85 | // any required modules inside node_modules are extracted to vendor 86 | return ( 87 | module.resource && 88 | /\.js$/.test(module.resource) && 89 | module.resource.indexOf( 90 | path.join(__dirname, '../node_modules') 91 | ) === 0 92 | ) 93 | } 94 | }), 95 | // extract webpack runtime and module manifest to its own file in order to 96 | // prevent vendor hash from being updated whenever app bundle is updated 97 | new webpack.optimize.CommonsChunkPlugin({ 98 | name: 'manifest', 99 | minChunks: Infinity 100 | }), 101 | // This instance extracts shared chunks from code splitted chunks and bundles them 102 | // in a separate chunk, similar to the vendor chunk 103 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 104 | new webpack.optimize.CommonsChunkPlugin({ 105 | name: 'app', 106 | async: 'vendor-async', 107 | children: true, 108 | minChunks: 3 109 | }), 110 | 111 | // copy custom static assets 112 | new CopyWebpackPlugin([ 113 | { 114 | from: path.resolve(__dirname, '../static'), 115 | to: config.build.assetsSubDirectory, 116 | ignore: ['.*'] 117 | } 118 | ]) 119 | ] 120 | }) 121 | 122 | if (config.build.productionGzip) { 123 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 124 | 125 | webpackConfig.plugins.push( 126 | new CompressionWebpackPlugin({ 127 | asset: '[path].gz[query]', 128 | algorithm: 'gzip', 129 | test: new RegExp( 130 | '\\.(' + 131 | config.build.productionGzipExtensions.join('|') + 132 | ')$' 133 | ), 134 | threshold: 10240, 135 | minRatio: 0.8 136 | }) 137 | ) 138 | } 139 | 140 | if (config.build.bundleAnalyzerReport) { 141 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 142 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 143 | } 144 | 145 | module.exports = webpackConfig 146 | -------------------------------------------------------------------------------- /client/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /client/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: {}, 14 | 15 | // Various Dev Server settings 16 | host: 'localhost', // can be overwritten by process.env.HOST 17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 | autoOpenBrowser: false, 19 | errorOverlay: true, 20 | notifyOnErrors: true, 21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 | 23 | // Use Eslint Loader? 24 | // If true, your code will be linted during bundling and 25 | // linting errors and warnings will be shown in the console. 26 | useEslint: true, 27 | // If true, eslint errors and warnings will also be shown in the error overlay 28 | // in the browser. 29 | showEslintErrorsInOverlay: false, 30 | 31 | /** 32 | * Source Maps 33 | */ 34 | 35 | // https://webpack.js.org/configuration/devtool/#development 36 | devtool: 'cheap-module-eval-source-map', 37 | 38 | // If you have problems debugging vue-files in devtools, 39 | // set this to false - it *may* help 40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 41 | cacheBusting: true, 42 | 43 | cssSourceMap: true 44 | }, 45 | 46 | build: { 47 | // Template for index.html 48 | index: path.resolve(__dirname, '../dist/index.html'), 49 | 50 | // Paths 51 | assetsRoot: path.resolve(__dirname, '../dist'), 52 | assetsSubDirectory: 'static', 53 | assetsPublicPath: '/', 54 | 55 | /** 56 | * Source Maps 57 | */ 58 | 59 | productionSourceMap: true, 60 | // https://webpack.js.org/configuration/devtool/#production 61 | devtool: '#source-map', 62 | 63 | // Gzip off by default as many popular static hosts such as 64 | // Surge or Netlify already gzip all static assets for you. 65 | // Before setting to `true`, make sure to: 66 | // npm install --save-dev compression-webpack-plugin 67 | productionGzip: false, 68 | productionGzipExtensions: ['js', 'css'], 69 | 70 | // Run the build command with an extra argument to 71 | // View the bundle analyzer report after build finishes: 72 | // `npm run build --report` 73 | // Set to `true` or `false` to always turn it on or off 74 | bundleAnalyzerReport: process.env.npm_config_report 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /client/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Alpha Ecom 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "Mir HB Rahman ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.18.0", 15 | "vue": "^2.5.2", 16 | "vue-jwt-decode": "^0.1.0", 17 | "vue-loading-overlay": "^3.2.0", 18 | "vue-moment": "^4.0.0", 19 | "vue-router": "^3.0.1", 20 | "vuetify": "^1.5.7", 21 | "vuex": "^3.1.0" 22 | }, 23 | "devDependencies": { 24 | "autoprefixer": "^7.1.2", 25 | "babel-core": "^6.22.1", 26 | "babel-eslint": "^8.2.1", 27 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 28 | "babel-loader": "^7.1.1", 29 | "babel-plugin-syntax-jsx": "^6.18.0", 30 | "babel-plugin-transform-runtime": "^6.22.0", 31 | "babel-plugin-transform-vue-jsx": "^3.5.0", 32 | "babel-preset-env": "^1.3.2", 33 | "babel-preset-stage-2": "^6.22.0", 34 | "chalk": "^2.0.1", 35 | "copy-webpack-plugin": "^4.0.1", 36 | "css-loader": "^0.28.0", 37 | "eslint": "^4.15.0", 38 | "eslint-config-standard": "^10.2.1", 39 | "eslint-friendly-formatter": "^3.0.0", 40 | "eslint-loader": "^1.7.1", 41 | "eslint-plugin-import": "^2.7.0", 42 | "eslint-plugin-node": "^5.2.0", 43 | "eslint-plugin-promise": "^3.4.0", 44 | "eslint-plugin-standard": "^3.0.1", 45 | "eslint-plugin-vue": "^4.0.0", 46 | "extract-text-webpack-plugin": "^3.0.0", 47 | "file-loader": "^1.1.4", 48 | "friendly-errors-webpack-plugin": "^1.6.1", 49 | "html-webpack-plugin": "^2.30.1", 50 | "node-notifier": "^5.1.2", 51 | "optimize-css-assets-webpack-plugin": "^3.2.0", 52 | "ora": "^1.2.0", 53 | "portfinder": "^1.0.13", 54 | "postcss-import": "^11.0.0", 55 | "postcss-loader": "^2.0.8", 56 | "postcss-url": "^7.2.1", 57 | "rimraf": "^2.6.0", 58 | "semver": "^5.3.0", 59 | "shelljs": "^0.7.6", 60 | "uglifyjs-webpack-plugin": "^1.1.1", 61 | "url-loader": "^0.5.8", 62 | "vue-loader": "^13.3.0", 63 | "vue-style-loader": "^3.0.1", 64 | "vue-template-compiler": "^2.5.2", 65 | "webpack": "^3.6.0", 66 | "webpack-bundle-analyzer": "^2.9.0", 67 | "webpack-dev-server": "^2.9.1", 68 | "webpack-merge": "^4.1.0" 69 | }, 70 | "engines": { 71 | "node": ">= 6.0.0", 72 | "npm": ">= 3.0.0" 73 | }, 74 | "browserslist": [ 75 | "> 1%", 76 | "last 2 versions", 77 | "not ie <= 8" 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 26 | 27 | 42 | -------------------------------------------------------------------------------- /client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudio-qui/node-vue-ecommerce-backend/6fd72ad226b3bc3715fd34351efc5afa9659497d/client/src/assets/logo.png -------------------------------------------------------------------------------- /client/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 96 | 97 | 98 | 114 | -------------------------------------------------------------------------------- /client/src/components/auth/LoginUser.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 95 | 96 | 98 | -------------------------------------------------------------------------------- /client/src/components/dash/UserDash.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 20 | 21 | 24 | -------------------------------------------------------------------------------- /client/src/components/layouts/AppLayout.vue: -------------------------------------------------------------------------------- 1 | 83 | 84 | 171 | 172 | 177 | -------------------------------------------------------------------------------- /client/src/components/product/option/ProductOptionGroupList.vue: -------------------------------------------------------------------------------- 1 | 117 | 118 | 221 | 222 | 224 | -------------------------------------------------------------------------------- /client/src/components/product/option/ProductOptionList.vue: -------------------------------------------------------------------------------- 1 | 154 | 155 | 281 | 282 | 284 | -------------------------------------------------------------------------------- /client/src/components/product/product/ProductAttribute.vue: -------------------------------------------------------------------------------- 1 | 104 | 105 | 190 | 191 | 193 | -------------------------------------------------------------------------------- /client/src/components/product/product/ProductImage.vue: -------------------------------------------------------------------------------- 1 | 91 | 92 | 173 | 174 | 176 | -------------------------------------------------------------------------------- /client/src/components/product/product/ProductList.vue: -------------------------------------------------------------------------------- 1 | 179 | 180 | 257 | 258 | 280 | -------------------------------------------------------------------------------- /client/src/components/product/section/ProductCategoryList.vue: -------------------------------------------------------------------------------- 1 | 117 | 118 | 221 | 222 | 224 | -------------------------------------------------------------------------------- /client/src/components/product/section/ProductSubCategoryList.vue: -------------------------------------------------------------------------------- 1 | 154 | 155 | 281 | 282 | 284 | -------------------------------------------------------------------------------- /client/src/components/utils/Loader.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 34 | 35 | 38 | -------------------------------------------------------------------------------- /client/src/helpers/ucWords.js: -------------------------------------------------------------------------------- 1 | module.exports = (str) => { 2 | let text = str.toLowerCase() 3 | .split(' ') 4 | .map((s) => s.charAt(0).toUpperCase() + s.substring(1)) 5 | .join(' ') 6 | 7 | return text 8 | } 9 | -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import axios from 'axios' 6 | import router from './router' 7 | import store from './store' 8 | import jwtDecode from 'vue-jwt-decode' 9 | import setAuthToken from './utils/setAuthToken' 10 | 11 | Vue.use(require('vue-moment')) 12 | 13 | Vue.config.productionTip = false 14 | 15 | // Setup axios to be available globally through Vue 16 | Vue.axios = Vue.prototype.$http = axios.create({ 17 | baseURL: 'http://localhost:3000/api' 18 | }) 19 | 20 | // Check for token 21 | if (localStorage.getItem('jwt')) { 22 | // Getting token form local storage 23 | const bearerToken = localStorage.getItem('jwt') 24 | // Set auth token to header auth 25 | setAuthToken(bearerToken) 26 | const tokenArray = bearerToken.split(' ') 27 | const token = tokenArray[1] 28 | // Decoded token 29 | const decoded = jwtDecode.decode(token) 30 | // Check for expired token 31 | const currentTime = Date.now() / 1000 32 | if (decoded.exp < currentTime) { 33 | // Logout user 34 | store.dispatch('auth/logout') 35 | // Redirect to login 36 | router.push({name: 'loginUser'}) 37 | } 38 | } 39 | 40 | /* eslint-disable no-new */ 41 | new Vue({ 42 | el: '#app', 43 | router, 44 | store, 45 | components: { App }, 46 | template: '' 47 | }) 48 | -------------------------------------------------------------------------------- /client/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import LoginUser from '@/components/auth/LoginUser' 4 | import UserDash from '@/components/dash/UserDash' 5 | import ProductCategoryList from '@/components/product/section/ProductCategoryList' 6 | import ManufacturerList from '@/components/product/manufacturer/ManufacturerList' 7 | import ProductSubCategoryList from '@/components/product/section/ProductSubCategoryList' 8 | import ProductOptionGroup from '@/components/product/option/ProductOptionGroupList' 9 | import ProductOption from '@/components/product/option/ProductOptionList' 10 | import ProductCreate from '@/components/product/product/ProductCreate' 11 | import ProductList from '@/components/product/product/ProductList' 12 | import ProductAttribute from '@/components/product/product/ProductAttribute' 13 | import ProductImage from '@/components/product/product/ProductImage' 14 | 15 | Vue.use(Router) 16 | 17 | let router = new Router({ 18 | mode: 'history', 19 | routes: [ 20 | { 21 | path: '/', 22 | name: 'LoginUser', 23 | component: LoginUser, 24 | meta: { 25 | guest: true 26 | } 27 | }, 28 | { 29 | path: '/login', 30 | name: 'loginUser', 31 | component: LoginUser, 32 | meta: { 33 | guest: true 34 | } 35 | }, 36 | { 37 | path: '/dashboard', 38 | name: 'userDashboard', 39 | component: UserDash, 40 | meta: { 41 | requiresAuth: true 42 | } 43 | }, 44 | { 45 | path: '/product/categories', 46 | name: 'productCategoryList', 47 | component: ProductCategoryList, 48 | meta: { 49 | requiresAuth: true 50 | } 51 | }, 52 | { 53 | path: '/product/sub-categories', 54 | name: 'productSubCategoryList', 55 | component: ProductSubCategoryList, 56 | meta: { 57 | requiresAuth: true 58 | } 59 | }, 60 | { 61 | path: '/product/option-groups', 62 | name: 'productOptionGroupList', 63 | component: ProductOptionGroup, 64 | meta: { 65 | requiresAuth: true 66 | } 67 | }, 68 | { 69 | path: '/product/options', 70 | name: 'productOptionList', 71 | component: ProductOption, 72 | meta: { 73 | requiresAuth: true 74 | } 75 | }, 76 | { 77 | path: '/product/manufacturers', 78 | name: 'manufacturerList', 79 | component: ManufacturerList, 80 | meta: { 81 | requiresAuth: true 82 | } 83 | }, 84 | { 85 | path: '/products/create', 86 | name: 'productCreate', 87 | component: ProductCreate, 88 | meta: { 89 | requiresAuth: true 90 | } 91 | }, 92 | { 93 | path: '/products', 94 | name: 'productList', 95 | component: ProductList, 96 | meta: { 97 | requiresAuth: true 98 | } 99 | }, 100 | { 101 | path: '/products/attributes/:productId', 102 | name: 'productAttribute', 103 | component: ProductAttribute, 104 | props: true, 105 | meta: { 106 | requiresAuth: true 107 | } 108 | }, 109 | { 110 | path: '/products/images/:productId', 111 | name: 'productImage', 112 | component: ProductImage, 113 | props: true, 114 | meta: { 115 | requiresAuth: true 116 | } 117 | } 118 | ] 119 | }) 120 | 121 | // check route meta 122 | require('./metaCheck')(router) 123 | 124 | export default router 125 | -------------------------------------------------------------------------------- /client/src/router/metaCheck.js: -------------------------------------------------------------------------------- 1 | module.exports = (router) => { 2 | router.beforeEach((to, from, next) => { 3 | if (to.matched.some(record => record.meta.guest)) { 4 | if (localStorage.getItem('jwt') == null) { 5 | next() 6 | } else { 7 | next({name: 'userDashboard'}) 8 | } 9 | } else if (to.matched.some(record => record.meta.requiresAuth)) { 10 | if (localStorage.getItem('jwt') != null) { 11 | next() 12 | } else { 13 | next({name: 'loginUser'}) 14 | } 15 | } else { 16 | next() 17 | } 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /client/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import error from './modules/utils/error' 4 | import loader from './modules/utils/loader' 5 | import auth from './modules/auth/auth' 6 | import productCategory from './modules/product/category' 7 | import manufacturer from './modules/manufacturer/manufacturer' 8 | import productSubCategory from './modules/product/subCategory' 9 | import productOptionGroup from './modules/product/optionGroup' 10 | import productOption from './modules/product/option' 11 | import product from './modules/product/product' 12 | 13 | Vue.use(Vuex) 14 | 15 | export default new Vuex.Store({ 16 | modules: { 17 | error, 18 | loader, 19 | auth, 20 | manufacturer, 21 | productCategory, 22 | productSubCategory, 23 | productOptionGroup, 24 | productOption, 25 | product 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /client/src/store/modules/auth/auth.js: -------------------------------------------------------------------------------- 1 | import router from '../../../router' 2 | import setAuthToken from '../../../utils/setAuthToken' 3 | import Vue from 'vue' 4 | // initial state 5 | const state = { 6 | user: {}, 7 | isLoggedIn: false, 8 | errors: {}, 9 | isError: false 10 | } 11 | 12 | // getters 13 | const getters = { 14 | isError (state) { 15 | return state.isError 16 | }, 17 | getErrors (state) { 18 | return state.errors 19 | } 20 | } 21 | 22 | // actions 23 | const actions = { 24 | login ({commit}, payload) { 25 | Vue.axios.post('/admin/login', payload) 26 | .then(response => { 27 | if (response.data.success) { 28 | commit('loginUser', response.data.user) 29 | // Set token to local storage 30 | localStorage.setItem('jwt', response.data.token) 31 | // Set auth token to header auth 32 | setAuthToken(localStorage.getItem('jwt')) 33 | router.push({name: 'userDashboard'}) 34 | } 35 | }) 36 | .catch(err => { 37 | commit('setErrors', err.response.data) 38 | }) 39 | }, 40 | logout ({commit}) { 41 | if (localStorage.getItem('jwt')) { 42 | localStorage.removeItem('jwt') 43 | commit('clearCurrentUser') 44 | router.push({name: 'loginUser'}) 45 | } 46 | } 47 | } 48 | 49 | // mutations 50 | const mutations = { 51 | setErrors (state, errors) { 52 | state.errors = errors 53 | state.isError = true 54 | }, 55 | loginUser (state, user) { 56 | state.user = user 57 | state.isLoggedIn = true 58 | // Clear error 59 | state.isError = false 60 | state.errors = {} 61 | }, 62 | clearCurrentUser (state) { 63 | state.user = null 64 | state.isLoggedIn = false 65 | } 66 | } 67 | 68 | export default { 69 | namespaced: true, 70 | state, 71 | getters, 72 | actions, 73 | mutations 74 | } 75 | -------------------------------------------------------------------------------- /client/src/store/modules/manufacturer/manufacturer.js: -------------------------------------------------------------------------------- 1 | import store from '../../../store' 2 | import Vue from 'vue' 3 | // Initial state 4 | const state = { 5 | manufacturers: [] 6 | } 7 | 8 | // Getters 9 | const getters = { 10 | getManufacturers (state) { 11 | return state.manufacturers 12 | } 13 | } 14 | 15 | // Actions 16 | const actions = { 17 | getManufacturers ({ commit }) { 18 | // Active preloader 19 | store.dispatch('loader/activeLoader') 20 | // Make server request 21 | Vue.axios.get('/admin/product/manufacturers') 22 | .then(response => { 23 | if (response.data.success) { 24 | // Store to state 25 | commit('setmanufacturers', response.data.manufacturers) 26 | // Deactive loader 27 | store.dispatch('loader/deactiveLoader') 28 | // Clear error 29 | store.dispatch('error/clearErrors') 30 | } 31 | }) 32 | .catch(err => { 33 | // Deactive loader 34 | store.dispatch('loader/deactiveLoader') 35 | // Handle all error by error state 36 | store.dispatch('error/setErrors', err.response.data) 37 | }) 38 | }, 39 | createManufacturer ({ commit }, payload) { 40 | // Active preloader 41 | store.dispatch('loader/activeLoader') 42 | // Make form data 43 | const fd = new FormData() 44 | fd.append('name', payload.name) 45 | fd.append('url', payload.url) 46 | fd.append('image', payload.image) 47 | // Make server request 48 | Vue.axios.post('/admin/product/manufacturers', fd) 49 | .then(response => { 50 | if (response.data.success) { 51 | commit('createManufacturer', response.data.manufacturer) 52 | // Deactive loader 53 | store.dispatch('loader/deactiveLoader') 54 | // Clear error 55 | store.dispatch('error/clearErrors') 56 | } 57 | }) 58 | .catch(err => { 59 | // Deactive loader 60 | store.dispatch('loader/deactiveLoader') 61 | // Handle all error by error state 62 | store.dispatch('error/setErrors', err.response.data) 63 | }) 64 | }, 65 | updateManufacturer ({ commit }, payload) { 66 | // Active preloader 67 | store.dispatch('loader/activeLoader') 68 | // Make form data 69 | const fd = new FormData() 70 | fd.append('name', payload.data.name) 71 | fd.append('url', payload.data.url) 72 | fd.append('image', payload.data.image) 73 | // Make server request 74 | store.dispatch('loader/activeLoader') 75 | Vue.axios.put('/admin/product/manufacturers/' + payload._id, fd) 76 | .then(response => { 77 | if (response.data.success) { 78 | commit('updateManufacturer', response.data.manufacturer) 79 | // Deactive loader 80 | store.dispatch('loader/deactiveLoader') 81 | // Clear error 82 | store.dispatch('error/clearErrors') 83 | } 84 | }) 85 | .catch(err => { 86 | // Deactive loader 87 | store.dispatch('loader/deactiveLoader') 88 | // Handle all error by error state 89 | store.dispatch('error/setErrors', err.response.data) 90 | }) 91 | }, 92 | deleteManufacturer ({ commit }, payload) { 93 | // Active preloader 94 | store.dispatch('loader/activeLoader') 95 | // Make server request 96 | Vue.axios.delete('/admin/product/manufacturers/' + payload._id) 97 | .then(response => { 98 | if (response.data.success) { 99 | commit('deleteManufacturer', response.data.manufacturer) 100 | // Deactive loader 101 | store.dispatch('loader/deactiveLoader') 102 | // Clear error 103 | store.dispatch('error/clearErrors') 104 | } 105 | }) 106 | .catch(err => { 107 | // Deactive loader 108 | store.dispatch('loader/deactiveLoader') 109 | // Handle all error by error state 110 | store.dispatch('error/setErrors', err.response.data) 111 | }) 112 | } 113 | } 114 | 115 | // Mutations 116 | const mutations = { 117 | setmanufacturers (state, manufacturers) { 118 | state.manufacturers = manufacturers 119 | }, 120 | createManufacturer (state, manufacturer) { 121 | // Push new data to state 122 | state.manufacturers.push(manufacturer) 123 | // Clear error 124 | store.dispatch('error/clearErrors') 125 | }, 126 | updateManufacturer (state, manufacturer) { 127 | state.manufacturers = state.manufacturers.map(cat => cat._id === manufacturer._id ? (cat = manufacturer) : cat) 128 | // Clear error 129 | store.dispatch('error/clearErrors') 130 | }, 131 | deleteManufacturer (state, manufacturer) { 132 | state.manufacturers = state.manufacturers.filter(cat => cat._id !== manufacturer._id) 133 | } 134 | } 135 | 136 | export default { 137 | namespaced: true, 138 | state, 139 | getters, 140 | actions, 141 | mutations 142 | } 143 | -------------------------------------------------------------------------------- /client/src/store/modules/product/category.js: -------------------------------------------------------------------------------- 1 | import store from '../../../store' 2 | import Vue from 'vue' 3 | // Initial state 4 | const state = { 5 | categories: [], 6 | subCategories: [] 7 | } 8 | 9 | // Getters 10 | const getters = { 11 | getCategories (state) { 12 | return state.categories 13 | }, 14 | getSubCategoriesByCategory (state) { 15 | return state.subCategories 16 | } 17 | } 18 | 19 | // Actions 20 | const actions = { 21 | getCategories ({ commit }) { 22 | // Active preloader 23 | store.dispatch('loader/activeLoader') 24 | // Make server request 25 | Vue.axios.get('/admin/product/categories') 26 | .then(response => { 27 | if (response.data.success) { 28 | // Store to state 29 | commit('setCategories', response.data.categories) 30 | // Deactive loader 31 | store.dispatch('loader/deactiveLoader') 32 | // Clear error 33 | store.dispatch('error/clearErrors') 34 | } 35 | }) 36 | .catch(err => { 37 | // Deactive loader 38 | store.dispatch('loader/deactiveLoader') 39 | // Handle all error by error state 40 | store.dispatch('error/setErrors', err.response.data) 41 | }) 42 | }, 43 | getSubCategoriesByCategory ({ commit }, category) { 44 | // Active preloader 45 | store.dispatch('loader/activeLoader') 46 | // Make server request 47 | Vue.axios.get(`/admin/product/categories/${category._id}/sub-categories`) 48 | .then(response => { 49 | if (response.data.success) { 50 | // Store to state 51 | commit('setSubCategories', response.data.subCategories) 52 | // Deactive loader 53 | store.dispatch('loader/deactiveLoader') 54 | // Clear error 55 | store.dispatch('error/clearErrors') 56 | } 57 | }) 58 | .catch(err => { 59 | // Deactive loader 60 | store.dispatch('loader/deactiveLoader') 61 | // Handle all error by error state 62 | store.dispatch('error/setErrors', err.response.data) 63 | }) 64 | }, 65 | createCategory ({ commit }, payload) { 66 | // Active preloader 67 | store.dispatch('loader/activeLoader') 68 | // Make server request 69 | Vue.axios.post('/admin/product/categories', payload) 70 | .then(response => { 71 | if (response.data.success) { 72 | commit('createCategory', response.data.category) 73 | // Deactive loader 74 | store.dispatch('loader/deactiveLoader') 75 | // Clear error 76 | store.dispatch('error/clearErrors') 77 | } 78 | }) 79 | .catch(err => { 80 | // Deactive loader 81 | store.dispatch('loader/deactiveLoader') 82 | // Handle all error by error state 83 | store.dispatch('error/setErrors', err.response.data) 84 | }) 85 | }, 86 | updateCategory ({ commit }, payload) { 87 | // Active preloader 88 | store.dispatch('loader/activeLoader') 89 | // Make server request 90 | store.dispatch('loader/activeLoader') 91 | Vue.axios.put('/admin/product/categories/' + payload._id, payload.data) 92 | .then(response => { 93 | if (response.data.success) { 94 | commit('updateCategory', response.data.category) 95 | // Deactive loader 96 | store.dispatch('loader/deactiveLoader') 97 | // Clear error 98 | store.dispatch('error/clearErrors') 99 | } 100 | }) 101 | .catch(err => { 102 | // Deactive loader 103 | store.dispatch('loader/deactiveLoader') 104 | // Handle all error by error state 105 | store.dispatch('error/setErrors', err.response.data) 106 | }) 107 | }, 108 | deleteCategory ({ commit }, payload) { 109 | // Active preloader 110 | store.dispatch('loader/activeLoader') 111 | // Make server request 112 | Vue.axios.delete('/admin/product/categories/' + payload._id) 113 | .then(response => { 114 | if (response.data.success) { 115 | commit('deleteCategory', response.data.category) 116 | // Deactive loader 117 | store.dispatch('loader/deactiveLoader') 118 | // Clear error 119 | store.dispatch('error/clearErrors') 120 | } 121 | }) 122 | .catch(err => { 123 | // Deactive loader 124 | store.dispatch('loader/deactiveLoader') 125 | // Handle all error by error state 126 | store.dispatch('error/setErrors', err.response.data) 127 | }) 128 | } 129 | } 130 | 131 | // Mutations 132 | const mutations = { 133 | setCategories (state, categories) { 134 | state.categories = categories 135 | }, 136 | setSubCategories (state, subCategories) { 137 | state.subCategories = subCategories 138 | }, 139 | createCategory (state, category) { 140 | // Push new data to state 141 | state.categories.push(category) 142 | // Clear error 143 | store.dispatch('error/clearErrors') 144 | }, 145 | updateCategory (state, category) { 146 | state.categories = state.categories.map(cat => cat._id === category._id ? (cat = category) : cat) 147 | // Clear error 148 | store.dispatch('error/clearErrors') 149 | }, 150 | deleteCategory (state, category) { 151 | state.categories = state.categories.filter(cat => cat._id !== category._id) 152 | } 153 | } 154 | 155 | export default { 156 | namespaced: true, 157 | state, 158 | getters, 159 | actions, 160 | mutations 161 | } 162 | -------------------------------------------------------------------------------- /client/src/store/modules/product/option.js: -------------------------------------------------------------------------------- 1 | import store from '../../../store' 2 | import Vue from 'vue' 3 | // Initial state 4 | const state = { 5 | options: [] 6 | } 7 | 8 | // Getters 9 | const getters = { 10 | getOptions (state) { 11 | return state.options 12 | } 13 | } 14 | 15 | // Actions 16 | const actions = { 17 | getOptions ({ commit }) { 18 | // Active preloader 19 | store.dispatch('loader/activeLoader') 20 | // Make server request 21 | Vue.axios.get('/admin/product/options') 22 | .then(response => { 23 | if (response.data.success) { 24 | // Store to state 25 | commit('setOptions', response.data.options) 26 | // Deactive loader 27 | store.dispatch('loader/deactiveLoader') 28 | // Clear error 29 | store.dispatch('error/clearErrors') 30 | } 31 | }) 32 | .catch(err => { 33 | // Deactive loader 34 | store.dispatch('loader/deactiveLoader') 35 | // Handle all error by error state 36 | store.dispatch('error/setErrors', err.response.data) 37 | }) 38 | }, 39 | createOption ({ commit }, payload) { 40 | // Active preloader 41 | store.dispatch('loader/activeLoader') 42 | // Make server request 43 | Vue.axios.post('/admin/product/options', payload) 44 | .then(response => { 45 | if (response.data.success) { 46 | commit('createOption', response.data.option) 47 | // Deactive loader 48 | store.dispatch('loader/deactiveLoader') 49 | // Clear error 50 | store.dispatch('error/clearErrors') 51 | } 52 | }) 53 | .catch(err => { 54 | // Deactive loader 55 | store.dispatch('loader/deactiveLoader') 56 | // Handle all error by error state 57 | store.dispatch('error/setErrors', err.response.data) 58 | }) 59 | }, 60 | updateOption ({ commit }, payload) { 61 | // Active preloader 62 | store.dispatch('loader/activeLoader') 63 | // Make server request 64 | store.dispatch('loader/activeLoader') 65 | Vue.axios.put('/admin/product/options/' + payload._id, payload.data) 66 | .then(response => { 67 | if (response.data.success) { 68 | commit('updateOption', response.data.option) 69 | // Deactive loader 70 | store.dispatch('loader/deactiveLoader') 71 | // Clear error 72 | store.dispatch('error/clearErrors') 73 | } 74 | }) 75 | .catch(err => { 76 | // Deactive loader 77 | store.dispatch('loader/deactiveLoader') 78 | // Handle all error by error state 79 | store.dispatch('error/setErrors', err.response.data) 80 | }) 81 | }, 82 | deleteOption ({ commit }, payload) { 83 | // Active preloader 84 | store.dispatch('loader/activeLoader') 85 | // Make server request 86 | Vue.axios.delete('/admin/product/options/' + payload._id) 87 | .then(response => { 88 | if (response.data.success) { 89 | commit('deleteOption', response.data.option) 90 | // Deactive loader 91 | store.dispatch('loader/deactiveLoader') 92 | // Clear error 93 | store.dispatch('error/clearErrors') 94 | } 95 | }) 96 | .catch(err => { 97 | // Deactive loader 98 | store.dispatch('loader/deactiveLoader') 99 | // Handle all error by error state 100 | store.dispatch('error/setErrors', err.response.data) 101 | }) 102 | } 103 | } 104 | 105 | // Mutations 106 | const mutations = { 107 | setOptions (state, options) { 108 | state.options = options 109 | }, 110 | createOption (state, option) { 111 | // Push new data to state 112 | state.options.push(option) 113 | // Clear error 114 | store.dispatch('error/clearErrors') 115 | }, 116 | updateOption (state, option) { 117 | state.options = state.options.map(opt => opt._id === option._id ? (opt = option) : opt) 118 | // Clear error 119 | store.dispatch('error/clearErrors') 120 | }, 121 | deleteOption (state, option) { 122 | state.options = state.options.filter(opt => opt._id !== option._id) 123 | } 124 | } 125 | 126 | export default { 127 | namespaced: true, 128 | state, 129 | getters, 130 | actions, 131 | mutations 132 | } 133 | -------------------------------------------------------------------------------- /client/src/store/modules/product/optionGroup.js: -------------------------------------------------------------------------------- 1 | import store from '../../../store' 2 | import Vue from 'vue' 3 | // Initial state 4 | const state = { 5 | optionGroups: [], 6 | options: [] 7 | } 8 | 9 | // Getters 10 | const getters = { 11 | getOptionGroups (state) { 12 | return state.optionGroups 13 | }, 14 | getOptionsByOptionGroup (state) { 15 | return state.options 16 | } 17 | } 18 | 19 | // Actions 20 | const actions = { 21 | getOptionGroups ({ commit }) { 22 | // Active preloader 23 | store.dispatch('loader/activeLoader') 24 | // Make server request 25 | Vue.axios.get('/admin/product/option-groups') 26 | .then(response => { 27 | if (response.data.success) { 28 | // Store to state 29 | commit('setOptionGroups', response.data.optionGroups) 30 | // Deactive loader 31 | store.dispatch('loader/deactiveLoader') 32 | // Clear error 33 | store.dispatch('error/clearErrors') 34 | } 35 | }) 36 | .catch(err => { 37 | // Deactive loader 38 | store.dispatch('loader/deactiveLoader') 39 | // Handle all error by error state 40 | store.dispatch('error/setErrors', err.response.data) 41 | }) 42 | }, 43 | getOptionsByOptionGroup ({ commit }, optionGroup) { 44 | // Active preloader 45 | store.dispatch('loader/activeLoader') 46 | // Make server request 47 | Vue.axios.get(`/admin/product/option-groups/${optionGroup._id}/options`) 48 | .then(response => { 49 | if (response.data.success) { 50 | // Store to state 51 | commit('setOptions', response.data.options) 52 | // Deactive loader 53 | store.dispatch('loader/deactiveLoader') 54 | // Clear error 55 | store.dispatch('error/clearErrors') 56 | } 57 | }) 58 | .catch(err => { 59 | // Deactive loader 60 | store.dispatch('loader/deactiveLoader') 61 | // Handle all error by error state 62 | store.dispatch('error/setErrors', err.response.data) 63 | }) 64 | }, 65 | createOptionGroup ({ commit }, payload) { 66 | // Active preloader 67 | store.dispatch('loader/activeLoader') 68 | // Make server request 69 | Vue.axios.post('/admin/product/option-groups', payload) 70 | .then(response => { 71 | if (response.data.success) { 72 | commit('createOptionGroup', response.data.optionGroup) 73 | // Deactive loader 74 | store.dispatch('loader/deactiveLoader') 75 | // Clear error 76 | store.dispatch('error/clearErrors') 77 | } 78 | }) 79 | .catch(err => { 80 | // Deactive loader 81 | store.dispatch('loader/deactiveLoader') 82 | // Handle all error by error state 83 | store.dispatch('error/setErrors', err.response.data) 84 | }) 85 | }, 86 | updateOptionGroup ({ commit }, payload) { 87 | // Active preloader 88 | store.dispatch('loader/activeLoader') 89 | // Make server request 90 | store.dispatch('loader/activeLoader') 91 | Vue.axios.put('/admin/product/option-groups/' + payload._id, payload.data) 92 | .then(response => { 93 | if (response.data.success) { 94 | commit('updateOptionGroup', response.data.optionGroup) 95 | // Deactive loader 96 | store.dispatch('loader/deactiveLoader') 97 | // Clear error 98 | store.dispatch('error/clearErrors') 99 | } 100 | }) 101 | .catch(err => { 102 | // Deactive loader 103 | store.dispatch('loader/deactiveLoader') 104 | // Handle all error by error state 105 | store.dispatch('error/setErrors', err.response.data) 106 | }) 107 | }, 108 | deleteOptionGroup ({ commit }, payload) { 109 | // Active preloader 110 | store.dispatch('loader/activeLoader') 111 | // Make server request 112 | Vue.axios.delete('/admin/product/option-groups/' + payload._id) 113 | .then(response => { 114 | if (response.data.success) { 115 | commit('deleteOptionGroup', response.data.optionGroup) 116 | // Deactive loader 117 | store.dispatch('loader/deactiveLoader') 118 | // Clear error 119 | store.dispatch('error/clearErrors') 120 | } 121 | }) 122 | .catch(err => { 123 | // Deactive loader 124 | store.dispatch('loader/deactiveLoader') 125 | // Handle all error by error state 126 | store.dispatch('error/setErrors', err.response.data) 127 | }) 128 | } 129 | } 130 | 131 | // Mutations 132 | const mutations = { 133 | setOptionGroups (state, optionGroups) { 134 | state.optionGroups = optionGroups 135 | }, 136 | setOptions (state, options) { 137 | state.options = options 138 | }, 139 | createOptionGroup (state, optionGroup) { 140 | // Push new data to state 141 | state.optionGroups.push(optionGroup) 142 | // Clear error 143 | store.dispatch('error/clearErrors') 144 | }, 145 | updateOptionGroup (state, optionGroup) { 146 | state.optionGroups = state.optionGroups.map(og => og._id === optionGroup._id ? (og = optionGroup) : og) 147 | // Clear error 148 | store.dispatch('error/clearErrors') 149 | }, 150 | deleteOptionGroup (state, optionGroup) { 151 | state.optionGroups = state.optionGroups.filter(og => og._id !== optionGroup._id) 152 | } 153 | } 154 | 155 | export default { 156 | namespaced: true, 157 | state, 158 | getters, 159 | actions, 160 | mutations 161 | } 162 | -------------------------------------------------------------------------------- /client/src/store/modules/product/product.js: -------------------------------------------------------------------------------- 1 | import router from '../../../router' 2 | import store from '../../../store' 3 | import Vue from 'vue' 4 | 5 | // Initial state 6 | const state = { 7 | products: [], 8 | attributes: [], 9 | images: [] 10 | } 11 | 12 | // Getters 13 | const getters = { 14 | getProducts (state) { 15 | return state.products 16 | }, 17 | getAttributes (state) { 18 | return state.attributes 19 | }, 20 | getImages (state) { 21 | return state.images 22 | } 23 | } 24 | // Actions 25 | const actions = { 26 | getProducts ({commit}) { 27 | // Active preloader 28 | store.dispatch('loader/activeLoader') 29 | // Make server request 30 | Vue.axios.get('/admin/product/products') 31 | .then(res => { 32 | // Set product to state 33 | commit('setProducts', res.data.products) 34 | // Deactive preloader 35 | store.dispatch('loader/deactiveLoader') 36 | // Clear all error data 37 | store.dispatch('error/clearErrors') 38 | }) 39 | .catch(err => { 40 | // Deactive preloader 41 | store.dispatch('loader/deactiveLoader') 42 | // Handle all errors by error state 43 | store.dispatch('error/setErrors', err.response.data) 44 | }) 45 | }, 46 | getAttributes ({commit}, productId) { 47 | // Active preloader 48 | store.dispatch('loader/activeLoader') 49 | // Make server request 50 | Vue.axios.get(`/admin/product/products/${productId}/attributes`) 51 | .then(res => { 52 | // Set attribute to state 53 | commit('setAttributes', res.data.attributes) 54 | // Deactive preloader 55 | store.dispatch('loader/deactiveLoader') 56 | // Clear all error data 57 | store.dispatch('error/clearErrors') 58 | }) 59 | .catch(err => { 60 | // Deactive preloader 61 | store.dispatch('loader/deactiveLoader') 62 | // Handle all errors by error state 63 | store.dispatch('error/setErrors', err.response.data) 64 | }) 65 | }, 66 | getImages ({commit}, productId) { 67 | // Active preloader 68 | store.dispatch('loader/activeLoader') 69 | // Make server request 70 | Vue.axios.get(`/admin/product/products/${productId}/images`) 71 | .then(res => { 72 | // Set images to state 73 | commit('setImages', res.data.images) 74 | // Deactive preloader 75 | store.dispatch('loader/deactiveLoader') 76 | // Clear all error data 77 | store.dispatch('error/clearErrors') 78 | }) 79 | .catch(err => { 80 | // Deactive preloader 81 | store.dispatch('loader/deactiveLoader') 82 | // Handle all errors by error state 83 | store.dispatch('error/setErrors', err.response.data) 84 | }) 85 | }, 86 | createProduct ({commit}, payload) { 87 | // Active preloader 88 | store.dispatch('loader/activeLoader') 89 | // Generate form data 90 | const fd = new FormData() 91 | fd.append('sku', payload.sku) 92 | fd.append('category', payload.category) 93 | fd.append('subCategory', payload.subCategory) 94 | fd.append('model', payload.model) 95 | fd.append('name', payload.name) 96 | fd.append('sortDesc', payload.sortDesc) 97 | fd.append('longDesc', payload.longDesc) 98 | fd.append('weight', payload.weight) 99 | fd.append('price', payload.price) 100 | fd.append('stock', payload.stock) 101 | fd.append('inStock', payload.inStock) 102 | fd.append('isFeature', payload.isFeature) 103 | fd.append('status', payload.status) 104 | fd.append('thumb', payload.thumb) 105 | 106 | // Manufacturer 107 | if (payload.hasManufacturer) { 108 | fd.append('hasManufacturer', payload.hasManufacturer) 109 | fd.append('manufacturerId', payload.manufacturer._id) 110 | fd.append('manufacturerName', payload.manufacturer.name) 111 | fd.append('manufacturerImage', payload.manufacturer.image ? payload.manufacturer.image.url : '') 112 | } 113 | // Flash sale 114 | if (payload.isFlashSale) { 115 | fd.append('isFlashSale', payload.isFlashSale) 116 | fd.append('flashPrice', payload.flashPrice) 117 | fd.append('flashStart', payload.flashStartDate) 118 | fd.append('flashEnd', payload.flashEndDate) 119 | fd.append('flashStatus', payload.flashStatus) 120 | } 121 | 122 | // Special sale 123 | if (payload.isFlashSale) { 124 | fd.append('isSpecialSale', payload.isSpecialSale) 125 | fd.append('specialPrice', payload.specialPrice) 126 | fd.append('specialExpire', payload.specialExpireDate) 127 | fd.append('specialStatus', payload.specialStatus) 128 | } 129 | // Make server request 130 | Vue.axios.post('/admin/product/products', fd) 131 | .then(res => { 132 | // Set new created product to state 133 | commit('createProduct', res.data.product) 134 | // Deactive preloader 135 | store.dispatch('loader/deactiveLoader') 136 | // Clear all error data 137 | store.dispatch('error/clearErrors') 138 | // Redirect to dashboard 139 | router.push({name: 'productList'}) 140 | }) 141 | .catch(err => { 142 | // Deactive preloader 143 | store.dispatch('loader/deactiveLoader') 144 | // Handle all errors by error state 145 | store.dispatch('error/setErrors', err.response.data) 146 | }) 147 | }, 148 | addProductAttribute ({commit}, payload) { 149 | // Active preloader 150 | store.dispatch('loader/activeLoader') 151 | Vue.axios.post(`/admin/product/products/${payload.productId}/attributes`, payload) 152 | .then(res => { 153 | // Set attributes to state 154 | commit('setAttributes', res.data.attributes) 155 | // Deactive preloader 156 | store.dispatch('loader/deactiveLoader') 157 | // Clear all error data 158 | store.dispatch('error/clearErrors') 159 | }) 160 | .catch(err => { 161 | // Deactive preloader 162 | store.dispatch('loader/deactiveLoader') 163 | // Handle all errors by error state 164 | store.dispatch('error/setErrors', err.response.data) 165 | }) 166 | }, 167 | deleteAttribute ({commit}, payload) { 168 | // Active preloader 169 | store.dispatch('loader/activeLoader') 170 | Vue.axios.delete(`/admin/product/products/${payload.productId}/attributes/${payload.attId}`) 171 | .then(res => { 172 | // Delete attributes from state 173 | commit('deleteAttributes', res.data.attributes) 174 | // Deactive preloader 175 | store.dispatch('loader/deactiveLoader') 176 | // Clear all error data 177 | store.dispatch('error/clearErrors') 178 | }) 179 | .catch(err => { 180 | // Deactive preloader 181 | store.dispatch('loader/deactiveLoader') 182 | // Handle all errors by error state 183 | store.dispatch('error/setErrors', err.response.data) 184 | }) 185 | }, 186 | addProductImage ({commit}, payload) { 187 | // Make form data 188 | const fd = new FormData() 189 | fd.append('image', payload.image) 190 | // Active preloader 191 | store.dispatch('loader/activeLoader') 192 | Vue.axios.post(`/admin/product/products/${payload.productId}/images`, fd) 193 | .then(res => { 194 | // Set attributes to state 195 | commit('setImages', res.data.images) 196 | // Deactive preloader 197 | store.dispatch('loader/deactiveLoader') 198 | // Clear all error data 199 | store.dispatch('error/clearErrors') 200 | }) 201 | .catch(err => { 202 | // Deactive preloader 203 | store.dispatch('loader/deactiveLoader') 204 | // Handle all errors by error state 205 | store.dispatch('error/setErrors', err.response.data) 206 | }) 207 | }, 208 | deleteImage ({commit}, payload) { 209 | // Active preloader 210 | store.dispatch('loader/activeLoader') 211 | Vue.axios.delete(`/admin/product/products/${payload.productId}/images`) 212 | .then(res => { 213 | // Delete images from state 214 | commit('deleteImages', res.data.images) 215 | // Deactive preloader 216 | store.dispatch('loader/deactiveLoader') 217 | // Clear all error data 218 | store.dispatch('error/clearErrors') 219 | }) 220 | .catch(err => { 221 | // Deactive preloader 222 | store.dispatch('loader/deactiveLoader') 223 | // Handle all errors by error state 224 | store.dispatch('error/setErrors', err.response.data) 225 | }) 226 | } 227 | } 228 | // Mutations 229 | const mutations = { 230 | setProducts (state, products) { 231 | // set all received product to state 232 | state.products = products 233 | }, 234 | setAttributes (state, attributes) { 235 | // set all received product attributes to state 236 | state.attributes = attributes 237 | }, 238 | setImages (state, images) { 239 | // set all received product images to state 240 | state.images = images 241 | }, 242 | createProduct (state, product) { 243 | // Push new product to product state 244 | state.products.push(product) 245 | }, 246 | addProductAttribute (state, attributes) { 247 | state.attributes = attributes 248 | }, 249 | deleteAttributes (state, attributes) { 250 | state.attributes = attributes 251 | }, 252 | deleteImages (state, images) { 253 | state.images = images 254 | } 255 | } 256 | 257 | export default { 258 | namespaced: true, 259 | state, 260 | getters, 261 | actions, 262 | mutations 263 | } 264 | -------------------------------------------------------------------------------- /client/src/store/modules/product/subCategory.js: -------------------------------------------------------------------------------- 1 | import store from '../../../store' 2 | import Vue from 'vue' 3 | // Initial state 4 | const state = { 5 | subCategories: [] 6 | } 7 | 8 | // Getters 9 | const getters = { 10 | getSubCategories (state) { 11 | return state.subCategories 12 | } 13 | } 14 | 15 | // Actions 16 | const actions = { 17 | getSubCategories ({ commit }) { 18 | // Active preloader 19 | store.dispatch('loader/activeLoader') 20 | // Make server request 21 | Vue.axios.get('/admin/product/sub-categories') 22 | .then(response => { 23 | if (response.data.success) { 24 | // Store to state 25 | commit('setSubCategories', response.data.subCategories) 26 | // Deactive loader 27 | store.dispatch('loader/deactiveLoader') 28 | // Clear error 29 | store.dispatch('error/clearErrors') 30 | } 31 | }) 32 | .catch(err => { 33 | // Deactive loader 34 | store.dispatch('loader/deactiveLoader') 35 | // Handle all error by error state 36 | store.dispatch('error/setErrors', err.response.data) 37 | }) 38 | }, 39 | createSubCategory ({ commit }, payload) { 40 | // Active preloader 41 | store.dispatch('loader/activeLoader') 42 | // Make server request 43 | Vue.axios.post('/admin/product/sub-categories', payload) 44 | .then(response => { 45 | if (response.data.success) { 46 | commit('createSubCategory', response.data.subCategory) 47 | // Deactive loader 48 | store.dispatch('loader/deactiveLoader') 49 | // Clear error 50 | store.dispatch('error/clearErrors') 51 | } 52 | }) 53 | .catch(err => { 54 | // Deactive loader 55 | store.dispatch('loader/deactiveLoader') 56 | // Handle all error by error state 57 | store.dispatch('error/setErrors', err.response.data) 58 | }) 59 | }, 60 | updateSubCategory ({ commit }, payload) { 61 | // Active preloader 62 | store.dispatch('loader/activeLoader') 63 | // Make server request 64 | store.dispatch('loader/activeLoader') 65 | Vue.axios.put('/admin/product/sub-categories/' + payload._id, payload.data) 66 | .then(response => { 67 | if (response.data.success) { 68 | commit('updateSubCategory', response.data.subCategory) 69 | // Deactive loader 70 | store.dispatch('loader/deactiveLoader') 71 | // Clear error 72 | store.dispatch('error/clearErrors') 73 | } 74 | }) 75 | .catch(err => { 76 | // Deactive loader 77 | store.dispatch('loader/deactiveLoader') 78 | // Handle all error by error state 79 | store.dispatch('error/setErrors', err.response.data) 80 | }) 81 | }, 82 | deleteSubCategory ({ commit }, payload) { 83 | // Active preloader 84 | store.dispatch('loader/activeLoader') 85 | // Make server request 86 | Vue.axios.delete('/admin/product/sub-categories/' + payload._id) 87 | .then(response => { 88 | if (response.data.success) { 89 | commit('deleteSubCategory', response.data.subCategory) 90 | // Deactive loader 91 | store.dispatch('loader/deactiveLoader') 92 | // Clear error 93 | store.dispatch('error/clearErrors') 94 | } 95 | }) 96 | .catch(err => { 97 | // Deactive loader 98 | store.dispatch('loader/deactiveLoader') 99 | // Handle all error by error state 100 | store.dispatch('error/setErrors', err.response.data) 101 | }) 102 | } 103 | } 104 | 105 | // Mutations 106 | const mutations = { 107 | setSubCategories (state, subCategories) { 108 | state.subCategories = subCategories 109 | }, 110 | createSubCategory (state, subCategory) { 111 | // Push new data to state 112 | state.subCategories.push(subCategory) 113 | // Clear error 114 | store.dispatch('error/clearErrors') 115 | }, 116 | updateSubCategory (state, subCategory) { 117 | state.subCategories = state.subCategories.map(scat => scat._id === subCategory._id ? (scat = subCategory) : scat) 118 | // Clear error 119 | store.dispatch('error/clearErrors') 120 | }, 121 | deleteSubCategory (state, subCategory) { 122 | state.subCategories = state.subCategories.filter(scat => scat._id !== subCategory._id) 123 | } 124 | } 125 | 126 | export default { 127 | namespaced: true, 128 | state, 129 | getters, 130 | actions, 131 | mutations 132 | } 133 | -------------------------------------------------------------------------------- /client/src/store/modules/utils/error.js: -------------------------------------------------------------------------------- 1 | // Initial state 2 | const state = { 3 | errors: {}, 4 | isError: false 5 | } 6 | // Getters 7 | const getters = { 8 | getErrors (state) { 9 | return state.errors 10 | }, 11 | isError (state) { 12 | return state.isError 13 | } 14 | } 15 | 16 | // Actions 17 | const actions = { 18 | setErrors ({commit}, errors) { 19 | commit('setErrors', errors) 20 | }, 21 | clearErrors ({commit}) { 22 | commit('clearErrors') 23 | } 24 | } 25 | 26 | // Mutations 27 | const mutations = { 28 | setErrors (state, errors) { 29 | state.errors = errors 30 | state.isError = true 31 | }, 32 | clearErrors (state) { 33 | state.errors = {} 34 | state.isError = false 35 | } 36 | } 37 | 38 | export default { 39 | namespaced: true, 40 | state, 41 | getters, 42 | actions, 43 | mutations 44 | } 45 | -------------------------------------------------------------------------------- /client/src/store/modules/utils/loader.js: -------------------------------------------------------------------------------- 1 | // Initial state 2 | const state = { 3 | isLoading: true 4 | } 5 | // Getters 6 | const getters = { 7 | isLoading (state) { 8 | return state.isLoading 9 | } 10 | } 11 | 12 | // Actions 13 | const actions = { 14 | activeLoader ({commit}) { 15 | commit('activeLoader') 16 | }, 17 | deactiveLoader ({commit}) { 18 | commit('deactiveLoader') 19 | } 20 | } 21 | 22 | // Mutations 23 | const mutations = { 24 | activeLoader (state) { 25 | state.isLoading = true 26 | }, 27 | deactiveLoader (state) { 28 | state.isLoading = false 29 | } 30 | } 31 | 32 | export default { 33 | namespaced: true, 34 | state, 35 | getters, 36 | actions, 37 | mutations 38 | } 39 | -------------------------------------------------------------------------------- /client/src/utils/setAuthToken.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const setAuthToken = token => { 4 | if (token) { 5 | // Apply to every request 6 | axios.defaults.headers.common['Authorization'] = token 7 | } else { 8 | // Delete auth header 9 | delete axios.defaults.headers.common['Authorization'] 10 | } 11 | } 12 | 13 | export default setAuthToken 14 | -------------------------------------------------------------------------------- /client/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudio-qui/node-vue-ecommerce-backend/6fd72ad226b3bc3715fd34351efc5afa9659497d/client/static/.gitkeep -------------------------------------------------------------------------------- /client/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudio-qui/node-vue-ecommerce-backend/6fd72ad226b3bc3715fd34351efc5afa9659497d/client/static/favicon.ico -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | config/development.json 3 | -------------------------------------------------------------------------------- /server/api/index.js: -------------------------------------------------------------------------------- 1 | const auth = require('./routes/admin/auth'); 2 | const productCategory = require('./routes/admin/product/productCategory'); 3 | const productSubCategory = require('./routes/admin/product/productSubCategory'); 4 | const optionGroup = require('./routes/admin/product/optionGroup'); 5 | const option = require('./routes/admin/product/option'); 6 | const productMfr = require('./routes/admin/product/productManufacturer'); 7 | const product = require('./routes/admin/product/product'); 8 | // Client route data 9 | const clientAuth = require('./routes/client/auth'); 10 | const clientProductCategory = require('./routes/client/product/category'); 11 | const clientProductSubCategory = require('./routes/client/product/subCategory'); 12 | const clientProduct = require('./routes/client/product/product'); 13 | const order = require('./routes/client/order/order'); 14 | 15 | module.exports = (app)=>{ 16 | // All admin routes 17 | // Admin auth 18 | app.use('/api/admin', auth); 19 | // Product category 20 | app.use('/api/admin/product/categories', productCategory); 21 | // Product sub category 22 | app.use('/api/admin/product/sub-categories', productSubCategory); 23 | // Option group 24 | app.use('/api/admin/product/option-groups', optionGroup); 25 | // Option 26 | app.use('/api/admin/product/options', option); 27 | // Manufacturer 28 | app.use('/api/admin/product/manufacturers', productMfr); 29 | // Product 30 | app.use('/api/admin/product/products', product); 31 | 32 | // All client route 33 | // Customer auth 34 | app.use('/api', clientAuth); 35 | // Category 36 | app.use('/api/product/categories', clientProductCategory); 37 | // Sub category 38 | app.use('/api/product/sub-categories', clientProductSubCategory); 39 | // Product 40 | app.use('/api/product/products', clientProduct); 41 | // order 42 | app.use('/api/product/', order); 43 | } 44 | -------------------------------------------------------------------------------- /server/api/routes/admin/auth.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcryptjs'); 2 | const dbDebugger = require('debug')('app:db'); 3 | const _ = require('lodash'); 4 | const express = require('express'); 5 | const router = express.Router(); 6 | 7 | // Middlewares 8 | const auth = require('../../../middlewares/admin/auth'); 9 | const admin = require('../../../middlewares/admin/admin'); 10 | 11 | // Helpers 12 | const { generateJwtToken } = require('../../../helpers/jwt_access_token'); 13 | 14 | // Validatins 15 | const validateRegisterInput = require('../../../validations/admin/auth/register'); 16 | const validateLoginInput = require('../../../validations/admin/auth/login'); 17 | 18 | // Model 19 | const User = require('../../../models/admin/user'); 20 | 21 | // @route POST /api/admin/login 22 | // @des Admin login 23 | // @access Public 24 | router.post('/login', (req, res) => { 25 | // Validate user input 26 | const { errors } = validateLoginInput(req.body); 27 | if (errors) return res.status(400).json(errors); 28 | 29 | const email = req.body.email; 30 | const password = req.body.password; 31 | 32 | // Check user email 33 | User.findOne({ email: email }) 34 | .then(user => { 35 | if (!user) { 36 | return res.status(404).json({ email: "Email or password incorrect!" }); 37 | } else { 38 | bcrypt.compare(password, user.password) 39 | .then(isMatch => { 40 | if (isMatch) { 41 | // Payload 42 | const payload = { 43 | id: user._id, 44 | name: user.name, 45 | email: user.email, 46 | status: user.status 47 | }; 48 | // Generate jwt access token 49 | generateJwtToken(payload, (token) => { 50 | if (token) { 51 | return res.status(200).json({ 52 | success: true, 53 | token: "Bearer " + token, 54 | user: payload 55 | }); 56 | } else { 57 | throw new Error(); 58 | } 59 | }); 60 | } else { 61 | throw new Error(); 62 | } 63 | }) 64 | .catch(err => res.status(404).json({ error: true, email: "Email or password incorrect!" })) 65 | } 66 | }) 67 | .catch(err => res.status(404).json({ error: true, email: "Email or password incorrect!" })); 68 | }); 69 | 70 | // @route Post /api/admin/register 71 | // @des Admin register 72 | // @access Public 73 | router.post('/register', (req, res) => { 74 | // Validate user input 75 | const { errors } = validateRegisterInput(req.body); 76 | if (errors) return res.status(400).json(errors); 77 | 78 | // Check user already exist or not 79 | User.findOne({ email: req.body.email }) 80 | .then(user => { 81 | // If user exits return 82 | if (user) { 83 | return res.status(409).json({ "error": "User already exist" }) 84 | } else { 85 | // Extrecting user information 86 | const user = new User(_.pick(req.body, ["name", "email", "password"])); 87 | // Generate user password 88 | const salt = bcrypt.genSalt(10, (err, salt) => { 89 | if (err) throw err; 90 | bcrypt.hash(user.password, salt, (err, hash) => { 91 | if (err) throw err; 92 | user.password = hash; 93 | // Store user to DB 94 | user.save() 95 | .then(user => res.status(201).json(_.pick(user, ["_id", "name", "email", "isAdmin","status"]))) 96 | .catch(err => { 97 | res.status(400).json({ "error": "Something error!" }); 98 | dbDebugger(err) 99 | }); 100 | }); 101 | }); 102 | } 103 | }) 104 | .catch(err => { 105 | res.status(400).json({ "error": "Something error!" }); 106 | dbDebugger(err) 107 | }); 108 | }); 109 | 110 | // @route GET /api/admin/me 111 | // @des Admin user info 112 | // @access Private 113 | router.get('/me',[auth, admin], (req, res)=>{ 114 | res.status(200).json(_.pick(req.user, ["_id", "name", "email", "isAdmin", "status"])); 115 | }); 116 | 117 | module.exports = router; -------------------------------------------------------------------------------- /server/api/routes/admin/product/option.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | // Middlewares 5 | const auth = require('../../../../middlewares/admin/auth'); 6 | const admin = require('../../../../middlewares/admin/admin'); 7 | 8 | // Validation 9 | const validateOptionInput = require('../../../../validations/admin/product/option'); 10 | 11 | // Model 12 | const Option = require('../../../../models/admin/product/option'); 13 | 14 | // Helpers 15 | const somethinError = require('../../../../helpers/something_error'); 16 | 17 | // @route GET /api/admin/product/options 18 | // @des Get all options 19 | // @access Private 20 | router.get('/', [auth, admin], (req, res) => { 21 | 22 | Option.find() 23 | .populate('optionGroup', ['_id','name']) 24 | .sort({ name: 1 }) 25 | .then(options => { 26 | // No option found 27 | if (!options) return res.status(404).json({ success: false, error: 'No data found!' }); 28 | 29 | return res.status(200).json({ success: true, 'options': options }); 30 | }) 31 | .catch(err => somethinError(res, err)); 32 | }); 33 | 34 | // @route GET /api/admin/product/options/:id 35 | // @des Get options by id 36 | // @access Private 37 | router.get('/:id', [auth, admin], (req, res) => { 38 | 39 | Option.findById(req.params.id) 40 | .populate('optionGroup', ['_id', 'name']) 41 | .then(option => { 42 | // No option found 43 | if (!option) return res.status(404).json({ success: false, error: 'No data found!' }); 44 | 45 | return res.status(200).json({ success: true, 'option': option }); 46 | }) 47 | .catch(err => somethinError(res, err)); 48 | }); 49 | 50 | // @route POST /api/admin/product/options 51 | // @des Create options 52 | // @access Private 53 | router.post('/', [auth, admin], (req, res) => { 54 | const { errors } = validateOptionInput(req.body); 55 | if (errors) return res.status(400).json(errors); 56 | 57 | // let optionName = (req.body.name).toLowerCase(); 58 | let optionName = req.body.name; 59 | // Check option already exist or not 60 | Option.findOne({ name: optionName }) 61 | .then(option => { 62 | if (option) { 63 | return res.status(409).json({ name: "Option already exist!" }); 64 | } else { 65 | // Create new option 66 | const newOption = new Option({ 67 | optionGroup: req.body.optionGroup, 68 | name: optionName, 69 | other: req.body.other, 70 | note: req.body.note 71 | }); 72 | // Save to DB 73 | newOption.save() 74 | .then(option => { 75 | // Return option with oprion group details 76 | Option.findById(option._id) 77 | .populate('optionGroup') 78 | .then(option=>{ 79 | res.status(201).json({ success: true, option: option }) 80 | }) 81 | .catch(err => somethinError(res)); 82 | }) 83 | .catch(err => somethinError(res)); 84 | } 85 | }) 86 | .catch(err => somethinError(res, err)); 87 | 88 | }); 89 | 90 | // @route PUT /api/admin/product/options/:id 91 | // @des Update option by id 92 | // @access Private 93 | router.put('/:id', [auth, admin], (req, res) => { 94 | const { errors } = validateOptionInput(req.body); 95 | if (errors) return res.status(400).json(errors); 96 | 97 | let optionName = req.body.name; 98 | // Check option exist or not 99 | Option.findById(req.params.id) 100 | .then(option => { 101 | if (!option) { 102 | return res.status(404).json({ error: "Option Not Found!" }); 103 | } else { 104 | // Update option 105 | option.optionGroup = req.body.optionGroup; 106 | option.name = optionName; 107 | option.other = req.body.other; 108 | option.note = req.body.note 109 | 110 | // Save to DB 111 | option.save() 112 | .then(option => { 113 | // Return option with option group details 114 | Option.findById(option._id) 115 | .populate('optionGroup') 116 | .then(option=>{ 117 | res.status(201).json({ success: true, option: option }) 118 | }) 119 | .catch(err => somethinError(res)); 120 | }) 121 | .catch(err => somethinError(res)); 122 | } 123 | }) 124 | .catch(err => somethinError(res, err)); 125 | 126 | }); 127 | 128 | // @route DELETE /api/admin/product/options/:id 129 | // @des Delete option by id 130 | // @access Private 131 | router.delete('/:id', [auth, admin], (req, res) => { 132 | // Check option exist or not 133 | Option.findById(req.params.id) 134 | .then(option => { 135 | if (!option) { 136 | return res.status(404).json({ error: "Option Not Found!" }); 137 | } else { 138 | // Delete option 139 | option.delete() 140 | .then(option => res.status(200).json({ success: true, option: option })) 141 | .catch(err => somethinError(res)); 142 | } 143 | }) 144 | .catch(err => somethinError(res, err)); 145 | 146 | }); 147 | 148 | module.exports = router; -------------------------------------------------------------------------------- /server/api/routes/admin/product/optionGroup.js: -------------------------------------------------------------------------------- 1 | const slugify = require('slugify'); 2 | const express = require('express'); 3 | const router = express.Router(); 4 | 5 | // Middlewares 6 | const auth = require('../../../../middlewares/admin/auth'); 7 | const admin = require('../../../../middlewares/admin/admin'); 8 | 9 | // Validation 10 | const validateOptionGroupInput = require('../../../../validations/admin/product/optionGroup'); 11 | 12 | // Model 13 | const OptionGroup = require('../../../../models/admin/product/optionGroup'); 14 | const Option = require('../../../../models/admin/product/option'); 15 | 16 | // Helpers 17 | const somethinError = require('../../../../helpers/something_error'); 18 | 19 | // @route GET /api/admin/product/option-groups 20 | // @des Get all product option groups 21 | // @access Private 22 | router.get('/', [auth, admin], (req, res) => { 23 | 24 | OptionGroup.find() 25 | .sort({ name: 1 }) 26 | .then(optionGroups => { 27 | // No option group found 28 | if (!optionGroups) return res.status(404).json({ success: false, error: 'No data found!' }); 29 | 30 | return res.status(200).json({ success: true, 'optionGroups': optionGroups }); 31 | }) 32 | .catch(err => somethinError(res, err)); 33 | }); 34 | 35 | // @route GET /api/admin/product/option-groups/:id 36 | // @des Get product option group by id 37 | // @access Private 38 | router.get('/:id', [auth, admin], (req, res) => { 39 | 40 | OptionGroup.findById(req.params.id) 41 | .then(optinGroup => { 42 | // No option group found 43 | if (!optinGroup) return res.status(404).json({ success: false, error: 'No data found!' }); 44 | 45 | return res.status(200).json({ success: true, 'optinGroup': optinGroup }); 46 | }) 47 | .catch(err => somethinError(res, err)); 48 | }); 49 | 50 | // @route GET /api/admin/product/option-groups/:id/options 51 | // @des Get product option belongs to this group 52 | // @access Private 53 | router.get('/:id/options', [auth, admin], (req, res) => { 54 | 55 | Option.find({optionGroup: req.params.id}) 56 | .then(options => { 57 | // No option found 58 | if (!options) return res.status(404).json({ success: false, error: 'No data found!' }); 59 | 60 | return res.status(200).json({ success: true, 'options': options }); 61 | }) 62 | .catch(err => somethinError(res, err)); 63 | }); 64 | 65 | // @route POST /api/admin/product/option-groups 66 | // @des Create option group 67 | // @access Private 68 | router.post('/', [auth, admin], (req, res) => { 69 | const { errors } = validateOptionGroupInput(req.body); 70 | if (errors) return res.status(400).json(errors); 71 | 72 | let optionGroupName = (req.body.name).toLowerCase(); 73 | // Check option group already exist or not 74 | OptionGroup.findOne({ name: optionGroupName }) 75 | .then(optionGroup => { 76 | if (optionGroup) { 77 | return res.status(409).json({ name: "Option group already exist!" }); 78 | } else { 79 | 80 | // Create new option group 81 | const newOptionGroup = new OptionGroup({ 82 | name: optionGroupName, 83 | slug: slugify(optionGroupName) 84 | }); 85 | // Save to DB 86 | newOptionGroup.save() 87 | .then(optionGroup => res.status(201).json({ success: true, optionGroup: optionGroup })) 88 | .catch(err => somethinError(res)); 89 | } 90 | }) 91 | .catch(err => somethinError(res, err)); 92 | 93 | }); 94 | 95 | // @route PUT /api/admin/product/option-groups/:id 96 | // @des Update option group by id 97 | // @access Private 98 | router.put('/:id', [auth, admin], (req, res) => { 99 | const { errors } = validateOptionGroupInput(req.body); 100 | if (errors) return res.status(400).json(errors); 101 | 102 | let optionGroupName = (req.body.name).toLowerCase(); 103 | // Check option group exist or not 104 | OptionGroup.findById(req.params.id) 105 | .then(optionGroup => { 106 | if (!optionGroup) { 107 | return res.status(404).json({ error: "Option Groupe Not Found!" }); 108 | } else { 109 | // Update option groupe 110 | optionGroup.name = optionGroupName; 111 | optionGroup.slug = slugify(optionGroupName); 112 | // Save to DB 113 | optionGroup.save() 114 | .then(optionGroup => res.status(200).json({ success: true, optionGroup: optionGroup })) 115 | .catch(err => somethinError(res)); 116 | } 117 | }) 118 | .catch(err => somethinError(res, err)); 119 | 120 | }); 121 | 122 | // @route DELETE /api/admin/product/option-groups/:id 123 | // @des Delete option group by id 124 | // @access Private 125 | router.delete('/:id', [auth, admin], (req, res) => { 126 | // Check option group exist or not 127 | OptionGroup.findById(req.params.id) 128 | .then(optionGroup => { 129 | if (!optionGroup) { 130 | return res.status(404).json({ error: "Option Groupe Not Found!" }); 131 | } else { 132 | // Delete option groupe 133 | optionGroup.delete() 134 | .then(optionGroup => res.status(200).json({ success: true, optionGroup: optionGroup })) 135 | .catch(err => somethinError(res)); 136 | } 137 | }) 138 | .catch(err => somethinError(res, err)); 139 | 140 | }); 141 | 142 | module.exports = router; -------------------------------------------------------------------------------- /server/api/routes/admin/product/productCategory.js: -------------------------------------------------------------------------------- 1 | const slugify = require('slugify'); 2 | const express = require('express'); 3 | const router = express.Router(); 4 | 5 | // Middlewares 6 | const auth = require('../../../../middlewares/admin/auth'); 7 | const admin = require('../../../../middlewares/admin/admin'); 8 | 9 | // Validation 10 | const validateCategoryInput = require('../../../../validations/admin/product/productCategory'); 11 | 12 | // Model 13 | const ProductCategory = require('../../../../models/admin/product/productCategory'); 14 | const ProductSubCategory = require('../../../../models/admin/product/productSubCategory'); 15 | 16 | // Helpers 17 | const somethinError = require('../../../../helpers/something_error'); 18 | 19 | // @route GET /api/admin/product/categories 20 | // @des Get all product category 21 | // @access Private 22 | router.get('/', [auth, admin], (req, res) => { 23 | 24 | ProductCategory.find() 25 | .sort({ name: 1 }) 26 | .then(cats => { 27 | // No category found 28 | if (!cats) return res.status(404).json({ success: false, error: 'No data found!' }); 29 | 30 | return res.status(200).json({ success: true, 'categories': cats }); 31 | }) 32 | .catch(err => somethinError(res, err)); 33 | }); 34 | 35 | // @route GET /api/admin/product/categories/:id 36 | // @des Get product category by id 37 | // @access Private 38 | router.get('/:id', [auth, admin], (req, res) => { 39 | 40 | ProductCategory.findById(req.params.id) 41 | .then(cat => { 42 | // No category found 43 | if (!cat) return res.status(404).json({ success: false, error: 'No data found!' }); 44 | 45 | return res.status(200).json({ success: true, 'category': cat }); 46 | }) 47 | .catch(err => somethinError(res, err)); 48 | }); 49 | 50 | // @route GET /api/admin/product/categories/:id/sub-categories 51 | // @des Get product sub categories by category id 52 | // @access Private 53 | router.get('/:id/sub-categories', [auth, admin], (req, res) => { 54 | 55 | ProductSubCategory.where({category: req.params.id}) 56 | .then(subCat => { 57 | // No sub category found 58 | if (!subCat) return res.status(404).json({ success: false, error: 'No data found!' }); 59 | 60 | return res.status(200).json({ success: true, 'subCategories': subCat }); 61 | }) 62 | .catch(err => somethinError(res, err)); 63 | }); 64 | 65 | // @route POST /api/admin/product/categories 66 | // @des Create product category 67 | // @access Private 68 | router.post('/', [auth, admin], (req, res) => { 69 | const { errors } = validateCategoryInput(req.body); 70 | if (errors) return res.status(400).json(errors); 71 | 72 | let catName = (req.body.name).toLowerCase(); 73 | // Check category already exist or not 74 | ProductCategory.findOne({ name: catName }) 75 | .then(cat => { 76 | if (cat) { 77 | return res.status(409).json({ name: "Category already exist!" }); 78 | } else { 79 | // Create new category 80 | const newCategory = new ProductCategory({ 81 | name: catName, 82 | slug: slugify(catName) 83 | }); 84 | // Save to DB 85 | newCategory.save() 86 | .then(cat => res.status(201).json({ success: true, category: cat })) 87 | .catch(err => somethinError(res, err)); 88 | } 89 | }) 90 | .catch(err => somethinError(res, err)); 91 | 92 | }); 93 | 94 | // @route PUT /api/admin/product/categories/:id 95 | // @des Update product category by id 96 | // @access Private 97 | router.put('/:id', [auth, admin], (req, res) => { 98 | const { errors } = validateCategoryInput(req.body); 99 | if (errors) return res.status(400).json(errors); 100 | 101 | let catName = (req.body.name).toLowerCase(); 102 | // Check category exist or not 103 | ProductCategory.findById(req.params.id) 104 | .then(cat => { 105 | if (!cat) { 106 | return res.status(404).json({ error: "Category Not Found!" }); 107 | } else { 108 | // Update category 109 | cat.name = catName; 110 | cat.slug = slugify(catName); 111 | // Save to DB 112 | cat.save() 113 | .then(cat => res.status(200).json({ success: true, category: cat })) 114 | .catch(err => somethinError(res)); 115 | } 116 | }) 117 | .catch(err => somethinError(res, err)); 118 | 119 | }); 120 | 121 | // @route DELETE /api/admin/product/categories/:id 122 | // @des Delete product category by id 123 | // @access Private 124 | router.delete('/:id', [auth, admin], (req, res) => { 125 | // Check category exist or not 126 | ProductCategory.findById(req.params.id) 127 | .then(cat => { 128 | if (!cat) { 129 | return res.status(404).json({ error: "Category Not Found!" }); 130 | } else { 131 | // Delete category 132 | cat.delete() 133 | .then(cat => res.status(200).json({ success: true, category: cat })) 134 | .catch(err => somethinError(res, err)); 135 | } 136 | }) 137 | .catch(err => somethinError(res, err)); 138 | 139 | }); 140 | 141 | module.exports = router; -------------------------------------------------------------------------------- /server/api/routes/admin/product/productManufacturer.js: -------------------------------------------------------------------------------- 1 | const slugify = require('slugify'); 2 | const express = require('express'); 3 | const router = express.Router(); 4 | 5 | // Middlewares 6 | const auth = require('../../../../middlewares/admin/auth'); 7 | const admin = require('../../../../middlewares/admin/admin'); 8 | 9 | // Validation 10 | const validateManufacturerInput = require('../../../../validations/admin/product/productManufacturer'); 11 | 12 | // Model 13 | const ProductManufacturer = require('../../../../models/admin/product/productManufacturer'); 14 | 15 | // Helpers 16 | const somethinError = require('../../../../helpers/something_error'); 17 | // Image upload 18 | const { multerParser } = require('../../../../utils/cloudinary'); 19 | // Delete image 20 | const { deleteServerImage } = require('../../../../utils/cloudinary'); 21 | 22 | 23 | // @route GET /api/admin/product/manufacturers 24 | // @des Get all product manufacturers 25 | // @access Private 26 | router.get('/', [auth, admin], (req, res) => { 27 | 28 | ProductManufacturer.find() 29 | .sort({ 30 | name: 1 31 | }) 32 | .then(mfrs => { 33 | // No manufacturers found 34 | if (!mfrs) return res.status(404).json({ 35 | success: false, 36 | error: 'No data found!' 37 | }); 38 | 39 | return res.status(200).json({ 40 | success: true, 41 | 'manufacturers': mfrs 42 | }); 43 | }) 44 | .catch(err => somethinError(res, err)); 45 | }); 46 | 47 | // @route GET /api/admin/product/manufacturers/:id 48 | // @des Get product manufacturers by id 49 | // @access Private 50 | router.get('/:id', [auth, admin], (req, res) => { 51 | 52 | ProductManufacturer.findById(req.params.id) 53 | .then(mfr => { 54 | // No manufacturers found 55 | if (!mfr) return res.status(404).json({ 56 | success: false, 57 | error: 'No data found!' 58 | }); 59 | 60 | return res.status(200).json({ 61 | success: true, 62 | 'manufacturers': mfr 63 | }); 64 | }) 65 | .catch(err => somethinError(res, err)); 66 | }); 67 | 68 | // @route POST /api/admin/product/manufacturers 69 | // @des Create product manufacturer 70 | // @access Private 71 | router.post('/', [auth, admin, multerParser.single("image")], (req, res) => { 72 | const { 73 | errors 74 | } = validateManufacturerInput(req.body); 75 | if (errors) return res.status(400).json(errors); 76 | let mfrName = (req.body.name).toLowerCase(); 77 | // Check manufacturer already exist or not 78 | ProductManufacturer.findOne({ name: mfrName }) 79 | .then(mfr => { 80 | if (mfr) { 81 | return res.status(409).json({ name: "Manufacturer already exist!" }); 82 | } else { 83 | // Create new manufacturer 84 | const newMfr = new ProductManufacturer({ 85 | name: mfrName, 86 | slug: slugify(mfrName), 87 | url : req.body.url 88 | }); 89 | 90 | // If image found 91 | if(req.file){ 92 | newMfr.image = { 93 | id: req.file.public_id, 94 | url: req.file.url 95 | } 96 | } 97 | 98 | // Save to DB 99 | newMfr.save() 100 | .then(mfr => res.status(201).json({ success: true, manufacturer: mfr })) 101 | .catch(err => somethinError(res, err)); 102 | } 103 | }) 104 | .catch(err => somethinError(res, err)); 105 | 106 | }); 107 | 108 | // @route PUT /api/admin/product/manufacturer/:id 109 | // @des Update product manufacturer by id 110 | // @access Private 111 | router.put('/:id', [auth, admin, multerParser.single("image")], (req, res) => { 112 | const { 113 | errors 114 | } = validateManufacturerInput(req.body); 115 | if (errors) return res.status(400).json(errors); 116 | 117 | let mfrName = (req.body.name).toLowerCase(); 118 | // Check manufacturer exist or not 119 | ProductManufacturer.findById(req.params.id) 120 | .then(mfr => { 121 | if (!mfr) { 122 | return res.status(404).json({ 123 | error: "Manufacturer Not Found!" 124 | }); 125 | } else { 126 | // Update manufacturer 127 | mfr.name = mfrName; 128 | mfr.slug = slugify(mfrName); 129 | mfr.url = req.body.url; 130 | // If new image found 131 | if(req.file){ 132 | // Delete old image from server 133 | if(mfr.image){ 134 | const result = deleteServerImage(mfr.image.id); 135 | if(result) console.log(result); 136 | } 137 | // Set new image 138 | mfr.image = { 139 | id: req.file.public_id, 140 | url: req.file.url 141 | } 142 | } 143 | // Save to DB 144 | mfr.save() 145 | .then(mfr => res.status(200).json({ 146 | success: true, 147 | manufacturer: mfr 148 | })) 149 | .catch(err => somethinError(res)); 150 | } 151 | }) 152 | .catch(err => somethinError(res, err)); 153 | 154 | }); 155 | 156 | // @route DELETE /api/admin/product/manufacturer/:id 157 | // @des Delete product manufacturer by id 158 | // @access Private 159 | router.delete('/:id', [auth, admin], (req, res) => { 160 | // Check manufacturer exist or not 161 | ProductManufacturer.findById(req.params.id) 162 | .then(mfr => { 163 | if (!mfr) { 164 | return res.status(404).json({ 165 | error: "Manufacturer Not Found!" 166 | }); 167 | } else { 168 | // Delete old image from server 169 | if(mfr.image){ 170 | const result = deleteServerImage(mfr.image.id); 171 | if(result) console.log(result); 172 | } 173 | // Delete manufacturer 174 | mfr.delete() 175 | .then(mfr => res.status(200).json({ 176 | success: true, 177 | manufacturer: mfr 178 | })) 179 | .catch(err => somethinError(res, err)); 180 | } 181 | }) 182 | .catch(err => somethinError(res, err)); 183 | 184 | }); 185 | 186 | module.exports = router; -------------------------------------------------------------------------------- /server/api/routes/admin/product/productSubCategory.js: -------------------------------------------------------------------------------- 1 | const slugify = require('slugify'); 2 | const express = require('express'); 3 | const router = express.Router(); 4 | 5 | // Middlewares 6 | const auth = require('../../../../middlewares/admin/auth'); 7 | const admin = require('../../../../middlewares/admin/admin'); 8 | 9 | // Validation 10 | const validateSubCategoryInput = require('../../../../validations/admin/product/productSubCategory'); 11 | 12 | // Model 13 | const ProductSubCategory = require('../../../../models/admin/product/productSubCategory'); 14 | 15 | // Helpers 16 | const somethinError = require('../../../../helpers/something_error'); 17 | 18 | // @route GET /api/admin/product/sub-categories 19 | // @des Get all product sub category 20 | // @access Private 21 | router.get('/', [auth, admin], (req, res) => { 22 | 23 | ProductSubCategory.find() 24 | .populate('category', ['_id','name']) 25 | .sort({ name: 1 }) 26 | .then(subCats => { 27 | // No category found 28 | if (!subCats) return res.status(404).json({ success: false, error: 'No data found!' }); 29 | 30 | return res.status(200).json({ success: true, 'subCategories': subCats }); 31 | }) 32 | .catch(err => somethinError(res, err)); 33 | }); 34 | 35 | // @route GET /api/admin/product/sub-categories/:id 36 | // @des Get product sub category by id 37 | // @access Private 38 | router.get('/:id', [auth, admin], (req, res) => { 39 | 40 | ProductSubCategory.findById(req.params.id) 41 | .populate('category', ['_id', 'name']) 42 | .then(subCat => { 43 | // No category found 44 | if (!subCat) return res.status(404).json({ success: false, error: 'No data found!' }); 45 | 46 | return res.status(200).json({ success: true, 'subCategory': subCat }); 47 | }) 48 | .catch(err => somethinError(res, err)); 49 | }); 50 | 51 | // @route POST /api/admin/product/sub-categories 52 | // @des Create product sub category 53 | // @access Private 54 | router.post('/', [auth, admin], (req, res) => { 55 | const { errors } = validateSubCategoryInput(req.body); 56 | if (errors) return res.status(400).json(errors); 57 | 58 | let subCatName = (req.body.name).toLowerCase(); 59 | // Check sub category already exist or not 60 | ProductSubCategory.findOne({ name: subCatName }) 61 | .then(cat => { 62 | if (cat) { 63 | return res.status(409).json({ name: "Sub Category already exist!" }); 64 | } else { 65 | // Create new sub category 66 | const newSubCategory = new ProductSubCategory({ 67 | category: req.body.category, 68 | name: subCatName, 69 | slug: slugify(subCatName) 70 | }); 71 | // Save to DB 72 | newSubCategory.save() 73 | .then(subCat => { 74 | // Return sub category with category details 75 | ProductSubCategory.findById(subCat._id) 76 | .populate('category') 77 | .then(subCat=>{ 78 | res.status(201).json({ success: true, subCategory: subCat }) 79 | }) 80 | .catch(err => somethinError(res)); 81 | }) 82 | .catch(err => somethinError(res)); 83 | } 84 | }) 85 | .catch(err => somethinError(res, err)); 86 | 87 | }); 88 | 89 | // @route PUT /api/admin/product/sub-categories/:id 90 | // @des Update product sub category by id 91 | // @access Private 92 | router.put('/:id', [auth, admin], (req, res) => { 93 | const { errors } = validateSubCategoryInput(req.body); 94 | if (errors) return res.status(400).json(errors); 95 | 96 | let subCatName = (req.body.name).toLowerCase(); 97 | // Check category exist or not 98 | ProductSubCategory.findById(req.params.id) 99 | .then(subCat => { 100 | if (!subCat) { 101 | return res.status(404).json({ error: "Sub Category Not Found!" }); 102 | } else { 103 | // Update sub category 104 | subCat.category = req.body.category; 105 | subCat.name = subCatName; 106 | subCat.slug = slugify(subCatName); 107 | // Save to DB 108 | subCat.save() 109 | .then(subCat => { 110 | // Return sub category with category details 111 | ProductSubCategory.findById(subCat._id) 112 | .populate('category') 113 | .then(subCat=>{ 114 | res.status(201).json({ success: true, subCategory: subCat }) 115 | }) 116 | .catch(err => somethinError(res)); 117 | }) 118 | .catch(err => somethinError(res)); 119 | } 120 | }) 121 | .catch(err => somethinError(res, err)); 122 | 123 | }); 124 | 125 | // @route DELETE /api/admin/product/sub-categories/:id 126 | // @des Delete product sub category by id 127 | // @access Private 128 | router.delete('/:id', [auth, admin], (req, res) => { 129 | // Check sub category exist or not 130 | ProductSubCategory.findById(req.params.id) 131 | .then(subCat => { 132 | if (!subCat) { 133 | return res.status(404).json({ error: "Sub Category Not Found!" }); 134 | } else { 135 | // Delete sub category 136 | subCat.delete() 137 | .then(subCat => res.status(200).json({ success: true, subCategory: subCat })) 138 | .catch(err => somethinError(res)); 139 | } 140 | }) 141 | .catch(err => somethinError(res, err)); 142 | 143 | }); 144 | 145 | module.exports = router; -------------------------------------------------------------------------------- /server/api/routes/client/auth.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcryptjs'); 2 | const express = require('express'); 3 | const router = express.Router(); 4 | 5 | // Helpers 6 | const { generateJwtToken } = require('../../../helpers/jwt_access_token'); 7 | const { somethingError } = require('../../../helpers/errors_response'); 8 | 9 | // Validation 10 | const validateRegisterInput = require('../../../validations/client/auth/register'); 11 | const validateLoginInput = require('../../../validations/client/auth/login'); 12 | // Model 13 | const Customer = require('../../../models/client/customer'); 14 | 15 | // @route POST /api/login 16 | // @des Customer login 17 | // @access Public 18 | router.post('/login', (req, res) => { 19 | // Validate customer input 20 | const { errors } = validateLoginInput(req.body); 21 | if (errors) return res.status(400).json(errors); 22 | 23 | const email = req.body.email; 24 | const password = req.body.password; 25 | 26 | // Check cutomer email 27 | Customer.findOne({ email: email }) 28 | .then(customer => { 29 | if (!customer) { 30 | return res.status(404).json({ error: true, email: "Email or password incorrect!" }); 31 | } else { 32 | bcrypt.compare(password, customer.password) 33 | .then(isMatch => { 34 | if (isMatch) { 35 | // Payload 36 | const payload = { 37 | id: customer._id, 38 | name: customer.name, 39 | email: customer.email, 40 | status: customer.status 41 | }; 42 | // Generate jwt access token 43 | generateJwtToken(payload, (token) => { 44 | if (token) { 45 | return res.status(200).json({ 46 | success: true, 47 | token: "Bearer " + token, 48 | customer: payload 49 | }); 50 | } else { 51 | throw new Error(); 52 | } 53 | }); 54 | } else { 55 | throw new Error(); 56 | } 57 | }) 58 | .catch(err => res.status(404).json({ error: true, email: "Email or password incorrect!" })) 59 | } 60 | }) 61 | .catch(err => res.status(404).json({ error: true, email: "Email or password incorrect!" })); 62 | }); 63 | 64 | // @route POST /api/register 65 | // @des Client register 66 | // @access Public 67 | router.post('/register', (req, res) => { 68 | const { errors } = validateRegisterInput(req.body); 69 | if (errors) return res.status(400).json(errors); 70 | 71 | // Check customer already exist or not 72 | Customer.findOne({email: req.body.email}) 73 | .then(customer => { 74 | // If customer exist return 75 | if(customer){ 76 | return res.status(409).json({"error": "customer already exist"}) 77 | }else{ 78 | // Extract customer info 79 | const customer = new Customer({ 80 | name: req.body.name, 81 | email: req.body.email, 82 | password: req.body.password 83 | }); 84 | 85 | // Generate password 86 | // Salt 87 | bcrypt.genSalt(10, (err, salt)=>{ 88 | if(err) throw err; 89 | bcrypt.hash(customer.password, salt, (err, hash)=>{ 90 | if(err) throw err; 91 | customer.password = hash; 92 | // Store customer to DB 93 | customer.save() 94 | .then(customer => { 95 | res.status(201).json({ 96 | success: true, 97 | customer: { 98 | name: customer.name, 99 | email: customer.email, 100 | status: customer.status 101 | } 102 | }) 103 | }) 104 | .catch(err => somethingError(res, err)); 105 | }); 106 | }); 107 | } 108 | }) 109 | .catch(err => somethingError(res, err)); 110 | }); 111 | 112 | module.exports = router; -------------------------------------------------------------------------------- /server/api/routes/client/order/order.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const config = require('config'); 4 | const stripe = require('stripe')(config.get('stripe.secretKey')); 5 | 6 | // Models 7 | const Product = require('../../../../models/admin/product/product'); 8 | 9 | // Helpers 10 | const { noDataFound, somethingError } = require('../../../../helpers/errors_response'); 11 | 12 | // @route POST /api/product/checkout 13 | // @des Checkout product 14 | // @access Privte 15 | router.post('/checkout', (req, res) => { 16 | // Extract product id 17 | let productIds = []; 18 | req.body.products.map(item => { 19 | productIds.push(item.productId); 20 | }) 21 | // Get all the ordered product 22 | Product.find({ 23 | _id: { 24 | $in: productIds 25 | } 26 | }).then(products => { 27 | // Total price 28 | let total = 0; 29 | // Calculate total amount 30 | products.map(product => { 31 | // Get product quantity 32 | let filterProduct = req.body.products.filter(item => item.productId == product._id); 33 | let quantity = filterProduct[0].quantity; 34 | 35 | if (product.isFlashSale) { 36 | total += quantity * product.flashSale.flashPrice; 37 | } else { 38 | total += quantity * product.price; 39 | } 40 | }); 41 | 42 | // Create the charge object with data from the Vue.js client 43 | var newCharge = { 44 | amount: total*100, 45 | currency: "usd", 46 | source: req.body.stripeToken, // obtained with Stripe.js on the client side 47 | }; 48 | 49 | // Call the stripe objects helper functions to trigger a new charge 50 | stripe.charges.create(newCharge, function (err, charge) { 51 | // send response 52 | if (err) { 53 | console.error(err); 54 | res.json({ error: err, charge: false }); 55 | } else { 56 | 57 | // TODO : pass data to order table 58 | 59 | // send response with charge data 60 | res.json({ success: true, charge: charge }); 61 | } 62 | }); 63 | }) 64 | 65 | }); 66 | 67 | 68 | 69 | module.exports = router; -------------------------------------------------------------------------------- /server/api/routes/client/product/category.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | // Models 5 | const Category = require('../../../../models/admin/product/productCategory'); 6 | const Product = require('../../../../models/admin/product/product'); 7 | 8 | // Helpers 9 | const { noDataFound, somethingError } = require('../../../../helpers/errors_response'); 10 | 11 | // @route GET /api/product/categories 12 | // @des Get all category 13 | // @access Public 14 | router.get('/', (req, res) => { 15 | Category.find() 16 | .then(categories => { 17 | if (!categories) return noDataFound(res); 18 | return res.status(200).json({ 19 | success: true, 20 | categories: categories 21 | }) 22 | }) 23 | .catch(err => somethingError(res, err)); 24 | }); 25 | 26 | // @route GET /api/product/categories/:slug/products 27 | // @des Get all products belongs to this category 28 | // @access Public 29 | router.get('/:slug/products', (req, res) => { 30 | Category.find({ slug: req.params.slug }) 31 | .then(cat => { 32 | if (!cat || cat.length < 1) return noDataFound(res); 33 | // Get product belongs to this category 34 | Product.find({ category: cat[0]._id }) 35 | .populate('category') 36 | .populate('subCategory') 37 | .then(products => { 38 | if (!products || products.length < 1) return noDataFound(res); 39 | 40 | return res.status(200).json({ 41 | success: true, 42 | products: products 43 | }) 44 | }) 45 | .catch(err => somethingError(res, err)); 46 | }) 47 | .catch(err => somethingError(res, err)); 48 | }); 49 | 50 | module.exports = router; -------------------------------------------------------------------------------- /server/api/routes/client/product/product.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | // Models 5 | const Product = require('../../../../models/admin/product/product'); 6 | 7 | // Helpers 8 | const { noDataFound, somethingError } = require('../../../../helpers/errors_response'); 9 | 10 | // @route GET /api/product/products 11 | // @des Get all products 12 | // @access Public 13 | router.get('/', (req, res) => { 14 | // Pagination 15 | const perPage = req.query.perPage ? parseInt(req.query.perPage) : 20; 16 | const pageNumber = req.query.pageNumber ? parseInt(req.query.pageNumber) : 1; 17 | 18 | Product.find() 19 | .skip((pageNumber - 1) * perPage) 20 | .limit(perPage) 21 | .populate('category') 22 | .populate('subCategory') 23 | .then(products => { 24 | if (!products) return noDataFound(res); 25 | return res.status(200).json({ 26 | success: true, 27 | totoalProduct: products.length, 28 | products: products 29 | }) 30 | }) 31 | .catch(err => somethingError(res, err)); 32 | }); 33 | 34 | // @route GET /api/product/products/:slug 35 | // @des Get product by slug 36 | // @access Public 37 | router.get('/:slug', (req, res) => { 38 | Product.find({slug: req.params.slug}) 39 | .limit(1) 40 | .populate('category') 41 | .populate('subCategory') 42 | .then(product => { 43 | if (!product) return noDataFound(res); 44 | return res.status(200).json({ 45 | success: true, 46 | product: product 47 | }) 48 | }) 49 | .catch(err => somethingError(res, err)); 50 | }); 51 | 52 | module.exports = router; -------------------------------------------------------------------------------- /server/api/routes/client/product/subCategory.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | // Models 5 | const SubCategory = require('../../../../models/admin/product/productSubCategory'); 6 | const Product = require('../../../../models/admin/product/product'); 7 | 8 | // Helpers 9 | const { noDataFound, somethingError } = require('../../../../helpers/errors_response'); 10 | 11 | // @route GET /api/product/sub-categories 12 | // @des Get all sub category 13 | // @access Public 14 | router.get('/', (req, res) => { 15 | SubCategory.find() 16 | .then(subCategories => { 17 | if (!subCategories) return noDataFound(res); 18 | return res.status(200).json({ 19 | success: true, 20 | subCategories: subCategories 21 | }) 22 | }) 23 | .catch(err => somethingError(res, err)); 24 | }); 25 | 26 | // @route GET /api/product/sub-categories/:slug/products 27 | // @des Get all products belongs to this sub category 28 | // @access Public 29 | router.get('/:slug/products', (req, res) => { 30 | SubCategory.find({ slug: req.params.slug }) 31 | .then(scat => { 32 | if (!scat || scat.length < 1) return noDataFound(res); 33 | // Get product belongs to this sub category 34 | Product.find({ subCategory: scat[0]._id }) 35 | .populate('category') 36 | .populate('subCategory') 37 | .then(products => { 38 | if (!products || products.length < 1) return noDataFound(res); 39 | 40 | return res.status(200).json({ 41 | success: true, 42 | products: products 43 | }) 44 | }) 45 | .catch(err => somethingError(res, err)); 46 | }) 47 | .catch(err => somethingError(res, err)); 48 | }); 49 | 50 | module.exports = router; -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | const passport = require("passport"); 2 | const config = require('config'); 3 | const startupDebugger = require('debug')('app:startup'); 4 | const dbDebugger = require('debug')('app:db'); 5 | const morgan = require('morgan'); 6 | const helmet = require('helmet'); 7 | const bodyParser = require('body-parser'); 8 | const mongoose = require('mongoose'); 9 | const express = require('express'); 10 | const app = express(); 11 | 12 | // DB connection 13 | mongoose.connect(config.get('mongoURI'), { useNewUrlParser: true }) 14 | .then(() => console.log('Connected to mongoDB')) 15 | .catch(err => console.log(err)); 16 | 17 | // Load body parser 18 | app.use(bodyParser.json()); 19 | app.use(bodyParser.urlencoded({ extended: false })); 20 | 21 | // Helmet 22 | app.use(helmet()); 23 | 24 | // Passport middleware 25 | app.use(passport.initialize()); 26 | 27 | // Passport Config 28 | require("./utils/passport")(passport); 29 | 30 | // Helper for development environment 31 | if (app.get('env') == 'development') { 32 | app.use(morgan('tiny')); 33 | startupDebugger('Morgan enable...'); 34 | } 35 | 36 | // CORS middleware 37 | const allowCrossDomain = function(req, res, next) { 38 | res.header('Access-Control-Allow-Origin', '*'); 39 | res.header('Access-Control-Allow-Methods', '*'); 40 | res.header('Access-Control-Allow-Headers', '*'); 41 | next(); 42 | } 43 | 44 | app.use(allowCrossDomain) 45 | 46 | // All routes 47 | require('./api/index')(app); 48 | 49 | // Listener 50 | const port = process.env.PORT || 3000; 51 | app.listen(port, () => console.log(`Listing on port ${3000}`)); -------------------------------------------------------------------------------- /server/config/custom-environment-variables.json: -------------------------------------------------------------------------------- 1 | { 2 | "mail" : { 3 | "password" : "app_password" 4 | }, 5 | "mongoURI" : "MONGO_URI", 6 | "cloudinary": { 7 | "cloud_name": "CLOUD_NAME", 8 | "api_key": "CLOUD_API_KEY", 9 | "api_secret": "CLOUD_API_SECRET" 10 | }, 11 | "stripe": { 12 | "secretKey": "STRIPE_KEY" 13 | } 14 | } -------------------------------------------------------------------------------- /server/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "My Express App" 3 | } -------------------------------------------------------------------------------- /server/config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "mongoURI" : "", 3 | "jwt": { 4 | "secretOrKey" : "secret", 5 | "tokenExpireIn" : 3600 6 | } 7 | } -------------------------------------------------------------------------------- /server/helpers/errors_response.js: -------------------------------------------------------------------------------- 1 | const noDataFound = (res) => { 2 | return res.status(404).json({ success: false, error: 'No data found!' }); 3 | } 4 | 5 | const somethingError = (res, err) => { 6 | res.status(400).json({ "error": "Something error!" }); 7 | console.log(err); 8 | } 9 | 10 | exports.noDataFound = noDataFound; 11 | exports.somethingError = somethingError; -------------------------------------------------------------------------------- /server/helpers/jwt_access_token.js: -------------------------------------------------------------------------------- 1 | const config = require('config'); 2 | const jwt = require('jsonwebtoken'); 3 | 4 | module.exports.generateJwtToken = (payload, callback) => { 5 | // Sign jwt 6 | jwt.sign( 7 | payload, 8 | config.get('jwt.secretOrKey'), 9 | { expiresIn: config.get('jwt.tokenExpireIn') }, 10 | (err, token) => { 11 | if (token) { 12 | callback(token); 13 | } else { 14 | return false; 15 | } 16 | } 17 | ); 18 | } -------------------------------------------------------------------------------- /server/helpers/something_error.js: -------------------------------------------------------------------------------- 1 | const dbDebugger = require('debug')('app:db'); 2 | 3 | module.exports = (res, err)=>{ 4 | res.status(400).json({ "error": "Something error!" }); 5 | console.log(err); 6 | dbDebugger(err); 7 | } -------------------------------------------------------------------------------- /server/middlewares/admin/admin.js: -------------------------------------------------------------------------------- 1 | module.exports = (req, res, next)=>{ 2 | if(req.user.isAdmin){ 3 | next(); 4 | }else{ 5 | return res.status(401).json({ 6 | success: false, 7 | message: 'Unauthorized' 8 | }) 9 | } 10 | } -------------------------------------------------------------------------------- /server/middlewares/admin/auth.js: -------------------------------------------------------------------------------- 1 | const passport = require("passport"); 2 | module.exports = passport.authenticate("jwt", { session: false }); -------------------------------------------------------------------------------- /server/models/admin/product/option.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const optionSchema = new Schema({ 5 | optionGroup: { type: Schema.Types.ObjectId, ref: "OptionGroup" }, 6 | name: { type: String, required: true, minlength: 1, maxlength: 200 }, 7 | other: { type: String, maxlength: 200 }, 8 | note: { type: String, maxlength:200 }, 9 | created_at: { type: Date, default: Date.now() } 10 | }); 11 | 12 | const Option = mongoose.model('Option', optionSchema); 13 | 14 | module.exports = Option; -------------------------------------------------------------------------------- /server/models/admin/product/optionGroup.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const optionGroupSchema = new Schema({ 5 | name: { type: String, required: true, minlength: 2, maxlength: 200 }, 6 | slug: { type: String, required: true, minlength: 2 }, 7 | created_at: {type: Date, default: Date.now()} 8 | }); 9 | 10 | const OptionGroup = mongoose.model('OptionGroup', optionGroupSchema); 11 | 12 | module.exports = OptionGroup; -------------------------------------------------------------------------------- /server/models/admin/product/product.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const productSchema = new Schema({ 5 | sku: { 6 | type: String, 7 | required: true 8 | }, 9 | category: { 10 | type: mongoose.Types.ObjectId, 11 | ref: "ProductCategory", 12 | required: true 13 | }, 14 | subCategory: { 15 | type: mongoose.Types.ObjectId, 16 | ref: "ProductSubCategory", 17 | required: true 18 | }, 19 | manufacturer: { 20 | _id: String, 21 | name: String, 22 | image: String 23 | }, 24 | name: { 25 | type: String, 26 | minlength: 2, 27 | maxlength: 400, 28 | required: true 29 | }, 30 | slug: { 31 | type: String, 32 | minlength: 2, 33 | maxlength: 500, 34 | required: true, 35 | }, 36 | model: { 37 | type: String, 38 | minlength: 2, 39 | maxlength: 200 40 | }, 41 | weight: Number, 42 | sortDesc: { 43 | type: String, 44 | minlength: 2, 45 | maxlength: 200, 46 | required: true 47 | }, 48 | longDesc: { 49 | type: String, 50 | }, 51 | price: { 52 | type: Number, 53 | required: true 54 | }, 55 | stock: { 56 | type: Number, 57 | required: true 58 | }, 59 | inStock: { 60 | type: Boolean, 61 | default: true // Default product in stock 62 | }, 63 | isFeature: { 64 | type: Boolean, 65 | default: false // Default not a feature product 66 | }, 67 | status: { 68 | type: Boolean, 69 | required: true, 70 | default: true // Default active 71 | }, 72 | isFlashSale: { 73 | type: Boolean, 74 | default: false 75 | }, 76 | flashSale: Object, 77 | isSpecialSale: { 78 | type: Boolean, 79 | default: false 80 | }, 81 | specialSale: Object, 82 | attributes: Array, 83 | thumb: { 84 | type: Object, 85 | required: true 86 | }, 87 | images: Array, 88 | created_at: { 89 | type: Date, 90 | default: Date.now() 91 | }, 92 | modified_at: { 93 | type: Date, 94 | default: Date.now() 95 | } 96 | }); 97 | 98 | const Product = mongoose.model('Product', productSchema); 99 | 100 | module.exports = Product; -------------------------------------------------------------------------------- /server/models/admin/product/productCategory.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const productCategory = new Schema({ 5 | name: { type: String, required: true, minlength: 2, maxlength: 200 }, 6 | slug: { type: String, required: true, minlength: 2 }, 7 | created_at: {type: Date, default: Date.now()} 8 | }); 9 | 10 | const ProductCategory = mongoose.model('ProductCategory', productCategory); 11 | 12 | module.exports = ProductCategory; -------------------------------------------------------------------------------- /server/models/admin/product/productManufacturer.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const manufacturerSchema = new Schema({ 5 | name: { type: String, required: true, minlength: 2, maxlength: 200 }, 6 | slug: { type: String, required: true, minlength: 2 }, 7 | url: { type: String, minlength: 2 }, 8 | image: { type: Object }, 9 | created_at: { type: Date, default: Date.now() } 10 | }); 11 | 12 | const ProductManufacturer = mongoose.model('ProductManufacturer', manufacturerSchema); 13 | 14 | module.exports = ProductManufacturer; -------------------------------------------------------------------------------- /server/models/admin/product/productSubCategory.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const subCategorySchema = new Schema({ 5 | category: { type: Schema.Types.ObjectId, ref: "ProductCategory" }, 6 | name: { type: String, required: true, minlength: 2, maxlength: 200 }, 7 | slug: { type: String, required: true, minlength: 2 }, 8 | created_at: { type: Date, default: Date.now() } 9 | }); 10 | 11 | const ProductSubCategory = mongoose.model('ProductSubCategory', subCategorySchema); 12 | 13 | module.exports = ProductSubCategory; -------------------------------------------------------------------------------- /server/models/admin/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const userSchema = new Schema({ 5 | name: { type: String, required: true, minlength: 2, maxlength: 100 }, 6 | email: { type: String, required: true, minlength: 5, maxlength: 255 }, 7 | password: { type: String, required: true, minlength: 6, maxlength: 255 }, 8 | isAdmin: { type: Boolean, default: true }, 9 | status: { type: Boolean, default: true } 10 | }); 11 | 12 | const User = mongoose.model('User', userSchema); 13 | module.exports = User; -------------------------------------------------------------------------------- /server/models/client/customer.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const customerSchema = new Schema({ 5 | name: { type: String, required: true, minlength: 2, maxlength: 100 }, 6 | email: { type: String, required: true, minlength: 5, maxlength: 255 }, 7 | password: { type: String, required: true, minlength: 6, maxlength: 255 }, 8 | status: { type: Boolean, default: true } 9 | }); 10 | 11 | const Customer = mongoose.model('Customer', customerSchema); 12 | module.exports = Customer; -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcryptjs": "^2.4.3", 14 | "body-parser": "^1.18.3", 15 | "cloudinary": "^1.14.0", 16 | "config": "^3.0.1", 17 | "debug": "^4.1.1", 18 | "express": "^4.16.4", 19 | "helmet": "^3.16.0", 20 | "joi": "^14.3.1", 21 | "joi-objectid": "^2.0.0", 22 | "jsonwebtoken": "^8.5.1", 23 | "lodash": "^4.17.11", 24 | "mongoose": "^5.4.19", 25 | "morgan": "^1.9.1", 26 | "multer": "^1.4.1", 27 | "multer-storage-cloudinary": "^2.2.1", 28 | "passport": "^0.4.0", 29 | "passport-jwt": "^4.0.0", 30 | "slugify": "^1.3.4", 31 | "stripe": "^6.31.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /server/utils/cloudinary.js: -------------------------------------------------------------------------------- 1 | const multer = require("multer"); 2 | const cloudinary = require("cloudinary"); 3 | const cloudinaryStorage = require("multer-storage-cloudinary"); 4 | const config = require('config'); 5 | 6 | cloudinary.config({ 7 | cloud_name: config.get('cloudinary.cloud_name'), 8 | api_key: config.get('cloudinary.api_key'), 9 | api_secret: config.get('cloudinary.api_secret') 10 | }); 11 | const storage = cloudinaryStorage({ 12 | cloudinary: cloudinary, 13 | folder: "mfr", 14 | allowedFormats: ["jpg", "png"], 15 | transformation: [{ width: 500, height: 500, crop: "limit" }] 16 | }); 17 | const multerParser = multer({ storage: storage }); 18 | 19 | // Delete image from server 20 | const deleteServerImage = (imageId)=>{ 21 | cloudinary.v2.uploader.destroy(imageId, function(error,result) { 22 | if(error) return error; 23 | return true; 24 | }); 25 | } 26 | 27 | exports.multerParser = multerParser; 28 | exports.deleteServerImage = deleteServerImage; 29 | 30 | -------------------------------------------------------------------------------- /server/utils/passport.js: -------------------------------------------------------------------------------- 1 | const config = require('config'); 2 | const JwtStrategy = require("passport-jwt").Strategy; 3 | const ExtractJwt = require("passport-jwt").ExtractJwt; 4 | const User = require("../models/admin/user"); 5 | 6 | const opts = {}; 7 | opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); 8 | opts.secretOrKey = config.get('jwt.secretOrKey'); 9 | 10 | module.exports = passport => { 11 | passport.use( 12 | new JwtStrategy(opts, (jwt_payload, done) => { 13 | User.findById(jwt_payload.id) 14 | .then(user => { 15 | if (user) { 16 | return done(null, user); 17 | } 18 | return done(null, false); 19 | }) 20 | .catch(err => console.log(err)); 21 | }) 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /server/utils/setValidationErrors.js: -------------------------------------------------------------------------------- 1 | module.exports = (errorPayload) => { 2 | let errors = {}; 3 | if (errorPayload) { 4 | // Set all errors 5 | errorPayload.details.map(err => { 6 | errors[err.path] = err.message; 7 | }); 8 | } else { 9 | errors = false; 10 | } 11 | 12 | return { errors }; 13 | } -------------------------------------------------------------------------------- /server/validations/admin/auth/login.js: -------------------------------------------------------------------------------- 1 | const Joi = require("joi"); 2 | 3 | module.exports = data => { 4 | let errors = {}; 5 | const schema = { 6 | email: Joi.string() 7 | .min(5) 8 | .max(255) 9 | .required() 10 | .email(), 11 | password: Joi.string() 12 | .min(6) 13 | .max(50) 14 | .required() 15 | }; 16 | 17 | const { error } = Joi.validate(data, schema, { abortEarly: false }); 18 | if (error) { 19 | // Set all errors 20 | error.details.map(err => { 21 | errors[err.path] = err.message; 22 | }); 23 | } else { 24 | errors = false; 25 | } 26 | 27 | return { errors }; 28 | }; 29 | -------------------------------------------------------------------------------- /server/validations/admin/auth/register.js: -------------------------------------------------------------------------------- 1 | const Joi = require("joi"); 2 | 3 | module.exports = data => { 4 | let errors = {}; 5 | const schema = { 6 | name: Joi.string() 7 | .min(2) 8 | .max(100) 9 | .required(), 10 | email: Joi.string() 11 | .min(5) 12 | .max(255) 13 | .required() 14 | .email(), 15 | password: Joi.string() 16 | .min(6) 17 | .max(50) 18 | .required(), 19 | password_confirmation: Joi.any() 20 | .valid(Joi.ref('password')) 21 | .required() 22 | .options({ language: { any: { allowOnly: 'must match password' } } }) 23 | }; 24 | 25 | const { error } = Joi.validate(data, schema, { abortEarly: false }); 26 | if (error) { 27 | // Set all errors 28 | error.details.map(err => { 29 | errors[err.path] = err.message; 30 | }); 31 | } else { 32 | errors = false; 33 | } 34 | 35 | return { errors }; 36 | }; 37 | -------------------------------------------------------------------------------- /server/validations/admin/product/option.js: -------------------------------------------------------------------------------- 1 | const setValidationErrors = require('../../../utils/setValidationErrors'); 2 | const Joi = require('joi') 3 | Joi.objectId = require('joi-objectid')(Joi) 4 | 5 | module.exports = data => { 6 | let errors = {}; 7 | const schema = { 8 | optionGroup: Joi.objectId().required(), 9 | name: Joi.string() 10 | .min(1) 11 | .max(200) 12 | .required() 13 | .trim(), 14 | other: Joi.string() 15 | .max(200) 16 | .allow(null) 17 | .allow('') 18 | .trim(), 19 | note: Joi.string() 20 | .max(200) 21 | .allow(null) 22 | .allow('') 23 | .trim() 24 | 25 | }; 26 | 27 | const { error } = Joi.validate(data, schema, { abortEarly: false }); 28 | // Set validation errors 29 | return setValidationErrors(error); 30 | }; 31 | -------------------------------------------------------------------------------- /server/validations/admin/product/optionGroup.js: -------------------------------------------------------------------------------- 1 | const setValidationErrors = require('../../../utils/setValidationErrors'); 2 | const Joi = require("joi"); 3 | 4 | module.exports = data => { 5 | const schema = { 6 | name: Joi.string() 7 | .min(2) 8 | .max(200) 9 | .required() 10 | .trim() 11 | }; 12 | 13 | const { error } = Joi.validate(data, schema, { abortEarly: false }); 14 | // Set validation errors 15 | return setValidationErrors(error); 16 | }; 17 | -------------------------------------------------------------------------------- /server/validations/admin/product/product.js: -------------------------------------------------------------------------------- 1 | const setValidationErrors = require('../../../utils/setValidationErrors'); 2 | const Joi = require('joi') 3 | Joi.objectId = require('joi-objectid')(Joi) 4 | 5 | module.exports = data => { 6 | const schema = { 7 | sku: Joi.string().required().trim(), 8 | category: Joi.objectId().required(), 9 | subCategory: Joi.objectId().required(), 10 | hasManufacturer: Joi.allow(null).allow(''), 11 | manufacturerId: Joi.allow(null).allow(''), 12 | manufacturerName: Joi.allow(null).allow(''), 13 | manufacturerImage: Joi.allow(null).allow(''), 14 | name: Joi.string() 15 | .min(1) 16 | .max(400) 17 | .required() 18 | .trim(), 19 | model: Joi.string().min(2).max(200).allow(null).allow(''), 20 | weight: Joi.number().allow(null).allow(''), 21 | sortDesc: Joi.string() 22 | .min(2) 23 | .max(200) 24 | .required() 25 | .trim(), 26 | longDesc: Joi.string().allow(null).allow(''), 27 | price: Joi.number().required(), 28 | stock: Joi.number().required(), 29 | inStock: Joi.boolean().allow(null).allow(''), 30 | isFeature: Joi.boolean().allow(null).allow(''), 31 | status: Joi.boolean().required(), 32 | isFlashSale: Joi.allow(null).allow(''), 33 | flashPrice: Joi.allow(null).allow(''), 34 | flashStart: Joi.allow(null).allow(''), 35 | flashEnd: Joi.allow(null).allow(''), 36 | flashStatus: Joi.allow(null).allow(''), 37 | isSpecialSale: Joi.allow(null).allow(''), 38 | specialPrice: Joi.allow(null).allow(''), 39 | specialExpire: Joi.allow(null).allow(''), 40 | specialStatus: Joi.allow(null).allow(''), 41 | attributes: Joi.array().allow(null).allow(''), 42 | thumb: Joi.string().allow(null).allow(''), 43 | images: Joi.array().allow(null).allow(''), 44 | 45 | }; 46 | 47 | const { 48 | error 49 | } = Joi.validate(data, schema, { 50 | abortEarly: false 51 | }); 52 | // Set validation errors 53 | return setValidationErrors(error); 54 | }; -------------------------------------------------------------------------------- /server/validations/admin/product/productCategory.js: -------------------------------------------------------------------------------- 1 | const setValidationErrors = require('../../../utils/setValidationErrors'); 2 | const Joi = require("joi"); 3 | 4 | module.exports = data => { 5 | const schema = { 6 | name: Joi.string() 7 | .min(2) 8 | .max(200) 9 | .required() 10 | .trim() 11 | }; 12 | 13 | const { error } = Joi.validate(data, schema, { abortEarly: false }); 14 | // Set validation errors 15 | return setValidationErrors(error); 16 | }; 17 | -------------------------------------------------------------------------------- /server/validations/admin/product/productManufacturer.js: -------------------------------------------------------------------------------- 1 | const setValidationErrors = require('../../../utils/setValidationErrors'); 2 | const Joi = require("joi"); 3 | 4 | module.exports = data => { 5 | const schema = { 6 | name: Joi.string() 7 | .min(2) 8 | .max(200) 9 | .required() 10 | .trim(), 11 | url: Joi.string() 12 | .allow(null) 13 | .allow('') 14 | .uri() 15 | .min(2) 16 | .trim(), 17 | image: Joi.allow(null) 18 | }; 19 | 20 | const { 21 | error 22 | } = Joi.validate(data, schema, { 23 | abortEarly: false 24 | }); 25 | // Set validation errors 26 | return setValidationErrors(error); 27 | }; -------------------------------------------------------------------------------- /server/validations/admin/product/productSubCategory.js: -------------------------------------------------------------------------------- 1 | const setValidationErrors = require('../../../utils/setValidationErrors'); 2 | const Joi = require('joi') 3 | Joi.objectId = require('joi-objectid')(Joi) 4 | 5 | module.exports = data => { 6 | const schema = { 7 | category: Joi.objectId().required(), 8 | name: Joi.string() 9 | .min(2) 10 | .max(200) 11 | .required() 12 | .trim() 13 | }; 14 | 15 | const { error } = Joi.validate(data, schema, { abortEarly: false }); 16 | // Set validation errors 17 | return setValidationErrors(error); 18 | }; 19 | -------------------------------------------------------------------------------- /server/validations/client/auth/login.js: -------------------------------------------------------------------------------- 1 | const Joi = require("joi"); 2 | 3 | module.exports = data => { 4 | let errors = {}; 5 | const schema = { 6 | email: Joi.string() 7 | .min(5) 8 | .max(255) 9 | .required() 10 | .email(), 11 | password: Joi.string() 12 | .min(6) 13 | .max(50) 14 | .required() 15 | }; 16 | 17 | const { error } = Joi.validate(data, schema, { abortEarly: false }); 18 | if (error) { 19 | // Set all errors 20 | error.details.map(err => { 21 | errors[err.path] = err.message; 22 | }); 23 | } else { 24 | errors = false; 25 | } 26 | 27 | return { errors }; 28 | }; 29 | -------------------------------------------------------------------------------- /server/validations/client/auth/register.js: -------------------------------------------------------------------------------- 1 | const Joi = require("joi"); 2 | 3 | module.exports = data => { 4 | let errors = {}; 5 | const schema = { 6 | name: Joi.string() 7 | .min(2) 8 | .max(100) 9 | .required(), 10 | email: Joi.string() 11 | .min(5) 12 | .max(255) 13 | .required() 14 | .email(), 15 | password: Joi.string() 16 | .min(6) 17 | .max(50) 18 | .required(), 19 | password_confirmation: Joi.any() 20 | .valid(Joi.ref('password')) 21 | .required() 22 | .options({ language: { any: { allowOnly: 'must match password' } } }) 23 | }; 24 | 25 | const { error } = Joi.validate(data, schema, { abortEarly: false }); 26 | if (error) { 27 | // Set all errors 28 | error.details.map(err => { 29 | errors[err.path] = err.message; 30 | }); 31 | } else { 32 | errors = false; 33 | } 34 | 35 | return { errors }; 36 | }; 37 | --------------------------------------------------------------------------------