├── .dockerignore
├── .editorconfig
├── .gitignore
├── .prettierrc.json
├── .stylelintrc.json
├── .vscode
└── settings.json
├── Dockerfile
├── README.md
├── angular.json
├── browserslist
├── config
├── aliases
│ └── hiredis.js
├── dev.setThisFromExternalApiKeys.js
├── keys.js
└── prod.js
├── docker-compose.yml
├── middlewares
├── requireAdmin.js
└── requireLogin.js
├── models
├── Cart.js
├── Order.js
├── Product.js
├── Translation.js
└── User.js
├── nginx-conf
└── nginx.conf
├── ngsw-config.json
├── package-lock.json
├── package.json
├── routes
├── adminRoutes.ts
├── authRoutes.ts
├── billingRoutes.ts
├── cartRoutes.ts
├── index.ts
└── productRoutes.ts
├── server.ts
├── services
├── cache.js
├── emailTemplates.js
├── mailer.js
├── paginate.js
└── passport.js
├── src
├── app
│ ├── app.browser.module.ts
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── app.routes.ts
│ ├── app.server.module.ts
│ ├── cart
│ │ ├── cart.module.ts
│ │ └── cart
│ │ │ ├── cart.component.html
│ │ │ ├── cart.component.scss
│ │ │ └── cart.component.ts
│ ├── dashboard
│ │ ├── dashboard.module.ts
│ │ ├── dashboard
│ │ │ ├── dashboard.component.html
│ │ │ ├── dashboard.component.scss
│ │ │ └── dashboard.component.ts
│ │ ├── orders-edit
│ │ │ ├── order-edit
│ │ │ │ ├── order-edit.component.html
│ │ │ │ ├── order-edit.component.scss
│ │ │ │ └── order-edit.component.ts
│ │ │ ├── orders-edit.component.html
│ │ │ ├── orders-edit.component.scss
│ │ │ └── orders-edit.component.ts
│ │ ├── products-edit
│ │ │ ├── products-edit.component.html
│ │ │ ├── products-edit.component.scss
│ │ │ └── products-edit.component.ts
│ │ ├── tiny-editor.ts
│ │ │ ├── tiny-editor.component.html
│ │ │ ├── tiny-editor.component.scss
│ │ │ └── tiny-editor.component.ts
│ │ └── translations-edit
│ │ │ ├── translations-edit.component.html
│ │ │ ├── translations-edit.component.scss
│ │ │ └── translations-edit.component.ts
│ ├── eshop
│ │ ├── about
│ │ │ ├── about.component.html
│ │ │ ├── about.component.scss
│ │ │ └── about.component.ts
│ │ ├── contact
│ │ │ ├── contact.component.html
│ │ │ ├── contact.component.scss
│ │ │ └── contact.component.ts
│ │ ├── eshop.module.ts
│ │ ├── gdpr
│ │ │ ├── gdpr.component.html
│ │ │ ├── gdpr.component.scss
│ │ │ └── gdpr.component.ts
│ │ └── vop
│ │ │ ├── vop.component.html
│ │ │ ├── vop.component.scss
│ │ │ └── vop.component.ts
│ ├── footer
│ │ ├── footer.component.html
│ │ ├── footer.component.scss
│ │ └── footer.component.ts
│ ├── header
│ │ ├── header.component.html
│ │ ├── header.component.scss
│ │ └── header.component.ts
│ ├── order
│ │ ├── order.component.html
│ │ ├── order.component.scss
│ │ └── order.component.ts
│ ├── orders
│ │ ├── orders.component.html
│ │ ├── orders.component.scss
│ │ └── orders.component.ts
│ ├── pipes
│ │ ├── pipe.module.ts
│ │ ├── price.pipe.ts
│ │ └── translate.pipe.ts
│ ├── product
│ │ ├── product.module.ts
│ │ └── product
│ │ │ ├── product.component.html
│ │ │ ├── product.component.scss
│ │ │ └── product.component.ts
│ ├── products
│ │ ├── products.component.html
│ │ ├── products.component.scss
│ │ └── products.component.ts
│ ├── services
│ │ ├── api.service.ts
│ │ ├── auth-admin.guard.ts
│ │ ├── auth.guard.ts
│ │ ├── auth.service.ts
│ │ ├── browser-http-interceptor.ts
│ │ ├── server-http-interceptor.ts
│ │ ├── translate.service.ts
│ │ └── window.service.ts
│ ├── shared
│ │ ├── card
│ │ │ ├── card.component.html
│ │ │ ├── card.component.scss
│ │ │ └── card.component.ts
│ │ ├── cart-show
│ │ │ ├── cart-show.component.html
│ │ │ ├── cart-show.component.scss
│ │ │ └── cart-show.component.ts
│ │ ├── pagination
│ │ │ ├── pagination.component.html
│ │ │ ├── pagination.component.scss
│ │ │ └── pagination.component.ts
│ │ ├── products-list
│ │ │ ├── products-list.component.html
│ │ │ ├── products-list.component.scss
│ │ │ └── products-list.component.ts
│ │ ├── shared.module.ts
│ │ └── sidebar
│ │ │ ├── sidebar.component.html
│ │ │ ├── sidebar.component.scss
│ │ │ └── sidebar.component.ts
│ ├── store
│ │ ├── actions.ts
│ │ ├── effects.ts
│ │ └── reducers
│ │ │ ├── auth.ts
│ │ │ ├── dashboard.ts
│ │ │ ├── index.ts
│ │ │ └── product.ts
│ └── utils
│ │ └── lazyLoadImg
│ │ ├── lazy-src.directive.ts
│ │ ├── lazy-viewport.directive.ts
│ │ ├── lazy-viewport.ts
│ │ └── lazy.module.ts
├── assets
│ ├── .gitkeep
│ ├── fonts
│ │ └── material-icons
│ │ │ ├── MaterialIcons-Regular.eot
│ │ │ ├── MaterialIcons-Regular.ijmap
│ │ │ ├── MaterialIcons-Regular.svg
│ │ │ ├── MaterialIcons-Regular.ttf
│ │ │ ├── MaterialIcons-Regular.woff
│ │ │ ├── MaterialIcons-Regular.woff2
│ │ │ └── material-icons.css
│ ├── icon-128x128.png
│ ├── icon-152x152.png
│ ├── icon-256x256.png
│ └── icon-512x512.png
├── config
│ └── keys.ts
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── main.server.ts
├── main.ts
├── manifest.json
├── polyfills.ts
├── styles
│ ├── _variables.scss
│ ├── index.scss
│ └── main.scss
├── tsconfig.app.json
├── tsconfig.server.json
└── typings.d.ts
├── tsconfig.json
└── tslint.json
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_size = 2
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist*
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | /node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.sass-cache
29 | /connect.lock
30 | /coverage
31 | /libpeerconnection.log
32 | npm-debug.log
33 | testem.log
34 | /typings
35 | /config/dev.js
36 |
37 | # e2e
38 | /e2e/*.js
39 | /e2e/*.map
40 |
41 | # System Files
42 | .DS_Store
43 | Thumbs.db
44 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 999,
3 | "semi": true
4 | }
5 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "stylelint-config-recommended",
4 | "stylelint-config-standard",
5 | "stylelint-config-recommended-scss"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "[scss]": {
3 | "editor.formatOnSave": true,
4 | "editor.formatOnPaste": true
5 | },
6 | "css.validate": false,
7 | "editor.formatOnPaste": false,
8 | "editor.formatOnSave": false,
9 | "editor.formatOnType": false,
10 | "editor.tabSize": 2,
11 | "extensions.ignoreRecommendations": false,
12 | "files.encoding": "utf8",
13 | "files.eol": "\r\n",
14 | "html.format.enable": false,
15 | "json.format.enable": false,
16 | "typescript.format.enable": false,
17 | "less.validate": false,
18 | "prettier.stylelintIntegration": true,
19 | "scss.validate": false,
20 | "stylelint.additionalDocumentSelectors": [],
21 | "stylelint.config": null,
22 | "stylelint.configOverrides": null,
23 | "stylelint.enable": true,
24 | "telemetry.enableCrashReporter": false,
25 | "telemetry.enableTelemetry": false,
26 | "tslint.enable": true,
27 | "tslint.run": "onType",
28 | "typescript.tsdk": "node_modules/typescript/lib",
29 | "prettier.jsxSingleQuote": true,
30 | "prettier.singleQuote": true,
31 | "prettier.tslintIntegration": true
32 | }
33 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:11.14.0-alpine as node
2 |
3 |
4 | # Sets the path where the app is going to be installed
5 | ENV NODE_ROOT /usr/app/
6 | # Creates the directory and all the parents (if they don’t exist)
7 | RUN mkdir -p $NODE_ROOT
8 | # Sets the /usr/app as the active directory
9 | WORKDIR $NODE_ROOT
10 |
11 | COPY ./package.json ./
12 |
13 | RUN npm install
14 |
15 | COPY . .
16 |
17 | # RUN npm run ssr
18 |
19 | CMD ["npm", "run", "ssr"]
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Angular universal (server-side rendering) with node.js and mongoDB
2 |
3 | Eshop with google login, cart save in session or user, test buy with stripe
4 |
5 | ## PREPARE ENVIROMENT
6 |
7 | Create dev.js in config and add keys as their are prepare in - dev.setThisFromExternalApiKeys
8 |
9 | mongoURI - use link to mongoDB database, e.g. from mlab.com
10 |
11 | googleClientID - create to set login through google - google API
12 |
13 | googleClientSecret - create to set login through google - google API
14 |
15 | stripePublishableKey - set to work with stripe payments
16 |
17 | stripeSecretKey - set to work with stripe payments
18 |
19 | sendGridKey - set to use sendGrid
20 |
21 | cloudinaryName - set to upload images straight from angular to cloudinary
22 |
23 | cloudinaryKey - set to upload images straight from angular to cloudinary
24 |
25 | cloudinarySecret - set to upload images straight from angular to cloudinary
26 |
27 | redisUrl: { host: 'localhost', port: 6379 }
28 |
29 |
30 | ## BUILD AND SERVE
31 |
32 | ## without docker
33 | Serve redis - host:localhost port:6379
34 |
35 | Build and serve
36 |
37 | Run `npm run ssr`
38 |
39 | Serve
40 |
41 | Run `npm run start`
42 |
43 | ## with docker
44 | Run `docker compose up`
45 |
46 | (will start redis and app)
47 |
48 | redisUrl: { host: 'redis-serve', port: 6379 }
49 |
50 |
51 | ## TEST ORDER
52 |
53 | -Use test credit card number for stripe for testing the order - 4242 4242 4242 4242
54 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "eshop": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "architect": {
11 | "build": {
12 | "builder": "@angular-devkit/build-angular:browser",
13 | "options": {
14 | "outputPath": "dist/browser",
15 | "index": "src/index.html",
16 | "main": "src/main.ts",
17 | "tsConfig": "src/tsconfig.app.json",
18 | "polyfills": "src/polyfills.ts",
19 | "aot": true,
20 | "stylePreprocessorOptions": {
21 | "includePaths": [
22 | "src/styles"
23 | ]
24 | },
25 | "assets": [
26 | "src/assets",
27 | "src/favicon.ico",
28 | "src/manifest.json"
29 | ],
30 | "styles": [
31 | "src/assets/fonts/material-icons/material-icons.css",
32 | "src/styles/index.scss"
33 | ],
34 | "scripts": []
35 | },
36 | "configurations": {
37 | "production": {
38 | "budgets": [
39 | {
40 | "type": "anyComponentStyle",
41 | "maximumWarning": "6kb"
42 | }
43 | ],
44 | "optimization": true,
45 | "outputHashing": "all",
46 | "sourceMap": false,
47 | "extractCss": true,
48 | "namedChunks": false,
49 | "extractLicenses": true,
50 | "vendorChunk": false,
51 | "buildOptimizer": true,
52 | "serviceWorker": true,
53 | "fileReplacements": [
54 | {
55 | "replace": "src/environments/environment.ts",
56 | "with": "src/environments/environment.prod.ts"
57 | }
58 | ]
59 | }
60 | }
61 | },
62 | "serve": {
63 | "builder": "@angular-devkit/build-angular:dev-server",
64 | "options": {
65 | "browserTarget": "eshop:build"
66 | },
67 | "configurations": {
68 | "production": {
69 | "browserTarget": "eshop:build:production"
70 | }
71 | }
72 | },
73 | "extract-i18n": {
74 | "builder": "@angular-devkit/build-angular:extract-i18n",
75 | "options": {
76 | "browserTarget": "eshop:build"
77 | }
78 | },
79 | "lint": {
80 | "builder": "@angular-devkit/build-angular:tslint",
81 | "options": {
82 | "tsConfig": [
83 | "src/tsconfig.app.json"
84 | ],
85 | "exclude": []
86 | }
87 | },
88 | "server": {
89 | "builder": "@angular-devkit/build-angular:server",
90 | "options": {
91 | "outputPath": "dist/server",
92 | "main": "server.ts",
93 | "tsConfig": "src/tsconfig.server.json"
94 | }
95 | },
96 | "serve-ssr": {
97 | "builder": "@nguniversal/builders:ssr-dev-server",
98 | "options": {
99 | "browserTarget": "eshop:build",
100 | "serverTarget": "eshop:server"
101 | },
102 | "configurations": {
103 | "production": {
104 | "browserTarget": "eshop:build:production",
105 | "serverTarget": "eshop:server:production"
106 | }
107 | }
108 | },
109 | "prerender": {
110 | "builder": "@nguniversal/builders:prerender",
111 | "options": {
112 | "browserTarget": "eshop:build:production",
113 | "serverTarget": "eshop:server:production",
114 | "routes": [
115 | "/"
116 | ]
117 | },
118 | "configurations": {
119 | "production": {}
120 | }
121 | }
122 | }
123 | },
124 | "eshop-e2e": {
125 | "root": "",
126 | "sourceRoot": "",
127 | "projectType": "application"
128 | }
129 | },
130 | "defaultProject": "eshop",
131 | "schematics": {
132 | "@schematics/angular:component": {
133 | "prefix": "app",
134 | "style": "scss"
135 | },
136 | "@schematics/angular:directive": {
137 | "prefix": "app"
138 | }
139 | },
140 | "cli": {
141 | "analytics": false
142 | }
143 | }
--------------------------------------------------------------------------------
/browserslist:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # You can see what browsers were selected by your queries by running:
6 | # npx browserslist
7 |
8 | > 0.5%
9 | last 2 versions
10 | Firefox ESR
11 | not dead
12 | not IE 9-11 # For IE 9-11 support, remove 'not'.
--------------------------------------------------------------------------------
/config/aliases/hiredis.js:
--------------------------------------------------------------------------------
1 | export default null;
2 |
--------------------------------------------------------------------------------
/config/dev.setThisFromExternalApiKeys.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | googleClientID: '0',
3 | googleClientSecret: '0',
4 | mongoURI: '',
5 | cookieKey: '12345',
6 | stripePublishableKey: '',
7 | stripeSecretKey: '',
8 | sendGridKey: '',
9 | cloudinaryName: '',
10 | cloudinaryKey: '',
11 | cloudinarySecret: '',
12 | redisUrl: '',
13 | adminEmails: []
14 | }
15 |
--------------------------------------------------------------------------------
/config/keys.js:
--------------------------------------------------------------------------------
1 |
2 | if(process.env.NODE_ENV === 'production') {
3 | module.exports = require('./prod');
4 | } else {
5 | // dev.setThisFromExternalApiKeys - not in git ignore
6 | module.exports = require('./dev');
7 |
8 | // remove comment line bellow and set keys from external API to dev.js --it will be in git ignore
9 | // module.exports = require('./dev');
10 | }
11 |
--------------------------------------------------------------------------------
/config/prod.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | googleClientID: process.env.GOOGLE_CLIENT_ID,
3 | googleClientSecret: process.env.GOOGLE_CLIENT_SECRET,
4 | mongoURI: process.env.MONGO_URI,
5 | cookieKey: process.env.COOKIE_KEY,
6 | stripePublishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
7 | stripeSecretKey: process.env.STRIPE_SECRET_KEY,
8 | sendGridKey: process.env.SEND_GRID_KEY,
9 | cloudinaryName: process.env.CLOUDINARY_NAME,
10 | cloudinaryKey: process.env.CLOUDINARY_KEY,
11 | cloudinarySecret: process.env.CLOUDINARY_SECRET,
12 | redisUrl: process.env.REDIS_URL,
13 | adminEmails: process.env.ADMIN_EMAILS,
14 | baseUrl: process.env.baseUrl
15 | }
16 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | redis-server:
4 | image: "redis"
5 | ports:
6 | - '6379:6379'
7 | app:
8 | restart: always
9 | build: .
10 | ports:
11 | - '5000:5000'
12 |
--------------------------------------------------------------------------------
/middlewares/requireAdmin.js:
--------------------------------------------------------------------------------
1 | module.exports = (req, res, next) => {
2 | if(!req.user.admin) {
3 | return res.status(401).send({error: 'No permision!'});
4 | }
5 |
6 | next();
7 | };
8 |
9 |
--------------------------------------------------------------------------------
/middlewares/requireLogin.js:
--------------------------------------------------------------------------------
1 | module.exports = (req, res, next) => {
2 | if(!req.user) {
3 | return res.status(401).send({error: 'You must log in!'});
4 | }
5 | next();
6 | };
7 |
--------------------------------------------------------------------------------
/models/Cart.js:
--------------------------------------------------------------------------------
1 | module.exports = function Cart(oldCart) {
2 | this.items = oldCart.items || [];
3 | this.totalQty = oldCart.totalQty || 0;
4 | this.totalPrice = oldCart.totalPrice || 0;
5 |
6 | this.add = function (item, id) {
7 | if (item['$init']) {
8 | delete item['$init'];
9 | }
10 | const itemExist = this.items.filter(cartItem => cartItem.id == id).length ? true : false;
11 |
12 | if(!itemExist) {
13 | this.items.push({item, id, price : item.salePrice, qty: 1});
14 | this.totalQty++;
15 | this.totalPrice += item.salePrice;
16 | } else {
17 | this.items.forEach(cartItem => {
18 | if(cartItem.id == id) {
19 | cartItem.qty++;
20 | cartItem.price = item.salePrice + cartItem.price;
21 | this.totalQty++;
22 | this.totalPrice += item.salePrice;
23 | }
24 | })
25 | }
26 | };
27 |
28 | this.remove = function(item, id) {
29 | if (item['$init']) {
30 | delete item['$init'];
31 | }
32 | this.items = this.items.map(cartItem => {
33 |
34 | if(cartItem.id == id && cartItem.qty > 1) {
35 | cartItem.qty--;
36 | cartItem.price = cartItem.price - item.salePrice;
37 | this.totalQty--;
38 | this.totalPrice -= item.salePrice;
39 | } else if(cartItem.id == id && cartItem.qty == 1) {
40 | cartItem = {};
41 | this.totalQty--;
42 | this.totalPrice -= item.salePrice;
43 | }
44 | return cartItem;
45 | }).filter(cartItem => cartItem.id);
46 |
47 |
48 | };
49 |
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/models/Order.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const { Schema } = mongoose;
3 |
4 | const orderSchema = new Schema({
5 | orderId : String,
6 | amount: Number,
7 | amount_refunded: Number,
8 | description: String,
9 | customerEmail: String,
10 | status: String,
11 | cart: {},
12 | outcome: {},
13 | source: {},
14 | _user: {type: Schema.Types.ObjectId, ref: 'user'},
15 | dateAdded: { type: Date, default: Date.now }
16 | });
17 |
18 | const Order = mongoose.model('orders', orderSchema);
19 | module.exports = Order;
20 |
--------------------------------------------------------------------------------
/models/Product.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const { Schema } = mongoose;
3 | const paginate = require('../services/paginate');
4 |
5 |
6 | const productSchema = new Schema({
7 | titleUrl:String,
8 | mainImage: {
9 | url: { type: String, trim: true},
10 | name: { type: String, trim: true}
11 | },
12 | onSale: Boolean,
13 | stock: String,
14 | visibility: String,
15 | shipping: String,
16 | images: [],
17 | _user: {type: Schema.Types.ObjectId, ref: 'user'},
18 | dateAdded: Date,
19 |
20 | title: String,
21 | description: String,
22 | descriptionFull: [],
23 | categories: [],
24 | tags: [],
25 | regularPrice: Number,
26 | salePrice: Number,
27 |
28 | sk : {
29 | title: String,
30 | description: String,
31 | descriptionFull: [],
32 | categories: [],
33 | tags: [],
34 | regularPrice: Number,
35 | salePrice: Number
36 | },
37 | cs: {
38 | title: String,
39 | description: String,
40 | descriptionFull: [],
41 | categories: [],
42 | tags: [],
43 | regularPrice: Number,
44 | salePrice: Number
45 | },
46 | en : {
47 | title: String,
48 | description: String,
49 | descriptionFull: [],
50 | categories: [],
51 | tags: [],
52 | regularPrice: Number,
53 | salePrice: Number
54 | },
55 | });
56 |
57 | productSchema.plugin(paginate);
58 |
59 | const Product = mongoose.model('products', productSchema);
60 | module.exports = Product;
61 |
--------------------------------------------------------------------------------
/models/Translation.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const { Schema } = mongoose;
3 |
4 | const translationSchema = new Schema({
5 | lang: String,
6 | keys : {type: Schema.Types.Mixed, default: { } }
7 | });
8 |
9 | const Translation = mongoose.model('translations', translationSchema);
10 | module.exports = Translation;
11 |
--------------------------------------------------------------------------------
/models/User.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const { Schema } = mongoose;
3 |
4 | const userSchema = new Schema({
5 | googleId: String,
6 | email: String,
7 | name: String,
8 | cart: { type: Schema.Types.Mixed, default: {items: [], totalQty: 0, totalPrice: 0} },
9 | images: [],
10 | admin: Boolean
11 | });
12 |
13 | const User = mongoose.model('users', userSchema);
14 | module.exports = User;
15 |
--------------------------------------------------------------------------------
/nginx-conf/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | listen [::]:80;
4 |
5 | root /var/www/html;
6 | index index.html index.htm index.nginx-debian.html;
7 |
8 | server_name localhost;
9 |
10 | location / {
11 | proxy_pass http://nodejs:5000;
12 | }
13 |
14 | location ~ /.well-known/acme-challenge {
15 | allow all;
16 | root /var/www/html;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ngsw-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": "/index.html",
3 | "dataGroups":
4 | [
5 | {
6 | "name": "auth",
7 | "urls": ["/auth"],
8 | "cacheConfig": {
9 | "maxSize": 0,
10 | "maxAge": "0u",
11 | "strategy": "freshness"
12 | }
13 | },
14 | {
15 | "name": "api",
16 | "urls": ["/api"],
17 | "cacheConfig": {
18 | "maxSize": 0,
19 | "maxAge": "0u",
20 | "strategy": "freshness"
21 | }
22 | },
23 | {
24 | "name": "cartApi",
25 | "urls": ["/cartApi"],
26 | "cacheConfig": {
27 | "maxSize": 0,
28 | "maxAge": "0u",
29 | "strategy": "freshness"
30 | }
31 | }
32 | ],
33 | "assetGroups": [{
34 | "name": "app",
35 | "installMode": "prefetch",
36 | "resources": {
37 | "files": [
38 | "/favicon.ico",
39 | "/*.bundle.css",
40 | "/*.bundle.js",
41 | "/*.chunk.js"
42 | ]
43 | }
44 | }, {
45 | "name": "assets",
46 | "installMode": "lazy",
47 | "updateMode": "prefetch",
48 | "resources": {
49 | "files": [
50 | "/assets/**"
51 | ]
52 | }
53 | }]
54 | }
55 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bluetooth-eshop",
3 | "version": "0.9.5",
4 | "main": "./dist/server/main.js",
5 | "license": "MIT",
6 | "engines": {
7 | "node": "13.5.0",
8 | "npm": "6.13.7"
9 | },
10 | "scripts": {
11 | "start": "node dist/server/main.js",
12 | "ssr": "npm run build && npm run start",
13 | "lint": "ng lint",
14 | "heroku-postbuild": "npm run build:client-and-server-bundles && npm run start",
15 | "build:client-and-server-bundles": "ng build --prod --output-hashing none && ng run eshop:server",
16 | "dev:ssr": "ng run eshop:serve-ssr",
17 | "serve:ssr": "node dist/server/main.js",
18 | "build": "ng build --prod && ng run eshop:server",
19 | "prerender": "ng run eshop:prerender"
20 | },
21 | "private": true,
22 | "dependencies": {
23 | "@angular/animations": "^9.1.0",
24 | "@angular/cli": "^9.1.0",
25 | "@angular/common": "^9.1.0",
26 | "@angular/compiler": "^9.1.0",
27 | "@angular/compiler-cli": "^9.1.0",
28 | "@angular/core": "^9.1.0",
29 | "@angular/forms": "^9.1.0",
30 | "@angular/language-service": "^9.1.0",
31 | "@angular/platform-browser": "^9.1.0",
32 | "@angular/platform-browser-dynamic": "^9.1.0",
33 | "@angular/platform-server": "^9.1.0",
34 | "@angular/router": "^9.1.0",
35 | "@angular/service-worker": "^9.1.0",
36 | "@ngrx/effects": "^9.0.0",
37 | "@ngrx/router-store": "^9.0.0",
38 | "@ngrx/store": "^9.0.0",
39 | "@ngrx/store-devtools": "^9.0.0",
40 | "@nguniversal/common": "^9.1.0",
41 | "@nguniversal/express-engine": "^9.1.0",
42 | "@tinymce/tinymce-angular": "^3.5.0",
43 | "body-parser": "^1.19.0",
44 | "cloudinary": "^1.21.0",
45 | "connect-mongo": "^2.0.3",
46 | "cookie-session": "^2.0.0-beta.3",
47 | "core-js": "^3.6.4",
48 | "express": "^4.17.1",
49 | "express-session": "^1.17.0",
50 | "materialize-css": "^1.0.0",
51 | "mongoose": "5.5.11",
52 | "multer": "^1.4.2",
53 | "ng2-file-upload": "^1.4.0",
54 | "ngx-cookie-service": "^3.0.4",
55 | "passport": "^0.4.1",
56 | "passport-google-oauth20": "^2.0.0",
57 | "passport-local": "^1.0.0",
58 | "redis": "^2.8.0",
59 | "reselect": "^4.0.0",
60 | "rxjs": "^6.5.5",
61 | "sendgrid": "^5.2.3",
62 | "stripe": "^6.22.0",
63 | "tslib": "^1.10.0",
64 | "zone.js": "~0.10.2"
65 | },
66 | "devDependencies": {
67 | "@angular-devkit/build-angular": "~0.901.0",
68 | "@nguniversal/builders": "^9.1.0",
69 | "@types/express": "^4.17.0",
70 | "@types/node": "^12.11.1",
71 | "codelyzer": "^5.2.1",
72 | "compression": "^1.7.4",
73 | "node-sass": "^4.13.0",
74 | "stylelint": "^11.1.1",
75 | "stylelint-config-recommended": "^3.0.0",
76 | "stylelint-config-recommended-scss": "^4.0.0",
77 | "stylelint-config-standard": "^19.0.0",
78 | "stylelint-scss": "^3.12.1",
79 | "ts-loader": "^6.2.1",
80 | "ts-node": "^8.6.2",
81 | "tslint": "^5.20.1",
82 | "typescript": "3.7.5",
83 | "webpack-cli": "^3.3.11"
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/routes/authRoutes.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 | import * as passport from 'passport';
3 |
4 | const authRoutes = Router();
5 |
6 | authRoutes.get('/google', passport.authenticate('google', { scope: ['profile', 'email'] }));
7 | authRoutes.get('/google/callback', passport.authenticate('google'), (req, res) => res.redirect('/'));
8 | authRoutes.get('/logout', (req: any, res) => {
9 | req.logout();
10 | res.redirect('/');
11 | });
12 | authRoutes.get('/current_user', (req: any, res) => (req.user ? res.send(req.user) : res.send({})));
13 |
14 | export { authRoutes };
15 |
--------------------------------------------------------------------------------
/routes/billingRoutes.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 |
3 | const keys = require('../config/keys');
4 | const stripe = require('stripe')(keys.stripeSecretKey);
5 | const Order = require('../models/Order');
6 | const Cart = require('../models/Cart');
7 | const Mailer = require('../services/mailer');
8 |
9 | const billingRoutes = Router();
10 |
11 | billingRoutes.post('/order/add', (req:any, res, next) => {
12 | const orderId = 'order' + new Date().getTime() + 't' + Math.floor(Math.random() * 1000 + 1);
13 | const date = Date.now();
14 |
15 | const newOrder = {
16 | orderId,
17 | amount: req.body.amount * 100,
18 | _user: req.user ? req.user.id : '123456789012',
19 | dateAdded: date,
20 | cart: req.session.cart,
21 | status: 'NEW',
22 | description: 'PAY_ON_DELIVERY',
23 | customerEmail: req.body.email,
24 | outcome: {
25 | seller_message: 'Payment on delivery'
26 | },
27 | source: {
28 | address_city: req.body.city,
29 | address_country: req.body.country,
30 | address_line1: req.body.adress,
31 | address_zip: req.body.zip,
32 | name: req.body.name,
33 | object: 'deliver'
34 | }
35 | };
36 |
37 | const adress = {
38 | city: req.body.city,
39 | country: req.body.country,
40 | adress: req.body.adress,
41 | zip: req.body.zip
42 | };
43 |
44 | const emailType = {
45 | subject: 'Order',
46 | cart: req.session.cart,
47 | currency: req.body.currency,
48 | orderId: orderId,
49 | adress,
50 | notes: req.body.notes,
51 | date: new Date()
52 | };
53 |
54 | const mailer = new Mailer(req.body.email, emailType);
55 |
56 | mailer.send();
57 |
58 | keys.adminEmails
59 | .split(',')
60 | .filter(Boolean)
61 | .forEach(email => {
62 | var mailerToAdmin = new Mailer(email, emailType);
63 | mailerToAdmin.send();
64 | });
65 |
66 | const cart = new Cart({});
67 | req.session.cart = cart;
68 | if (req.user) {
69 | req.user.cart = cart;
70 | req.user.save();
71 | }
72 |
73 | const order = new Order(newOrder);
74 | order.save();
75 |
76 | res.send({ order, cart });
77 | });
78 |
79 | billingRoutes.post('/stripe', (req:any, res, next) => {
80 | const charge = stripe.charges
81 | .create({
82 | amount: req.body.amount * 100,
83 | currency: 'eur',
84 | description: 'Credit Card Payment',
85 | source: req.body.token.id
86 | })
87 | .then(
88 | result => {
89 | const orderId = 'order' + new Date().getTime() + 't' + Math.floor(Math.random() * 1000 + 1);
90 | const newOrder = Object.assign(result, {
91 | orderId,
92 | customerEmail: req.body.token.email,
93 | status: 'NEW',
94 | cart: req.session.cart,
95 | _user: req.user ? req.user.id : '123456789012',
96 | dateAdded: Date.now()
97 | });
98 | const order = new Order(newOrder);
99 | order.save();
100 |
101 | const adress = {
102 | city: req.body.token.card.address_city,
103 | country: req.body.token.card.address_country,
104 | adress: req.body.token.card.address_line1,
105 | zip: req.body.token.card.address_zip
106 | };
107 |
108 | const emailType = {
109 | subject: 'Order',
110 | cart: req.session.cart,
111 | currency: req.body.currency,
112 | orderId: orderId,
113 | adress,
114 | notes: req.body.notes,
115 | date: new Date()
116 | };
117 |
118 | const mailer = new Mailer(req.body.token.email, emailType);
119 | mailer.send();
120 |
121 | keys.adminEmails
122 | .split(',')
123 | .filter(Boolean)
124 | .forEach(email => {
125 | var mailerToAdmin = new Mailer(email, emailType);
126 | mailerToAdmin.send();
127 | });
128 |
129 | const cart = new Cart({});
130 | req.session.cart = cart;
131 | if (req.user) {
132 | req.user.cart = cart;
133 | req.user.save();
134 | }
135 |
136 | res.send({ order, cart });
137 | },
138 | err => {
139 | switch (err.type) {
140 | case 'StripeCardError':
141 | // A declined card error
142 | res.send(err);
143 | break;
144 | case 'RateLimitError':
145 | // Too many requests made to the API too quickly
146 | res.send(err);
147 | break;
148 | case 'StripeInvalidRequestError':
149 | // Invalid parameters were supplied to Stripe's API
150 | res.send(err);
151 | break;
152 | case 'StripeAPIError':
153 | // An error occurred internally with Stripe's API
154 | res.send(err);
155 | break;
156 | case 'StripeConnectionError':
157 | // Some kind of error occurred during the HTTPS communication
158 | res.send(err);
159 | break;
160 | case 'StripeAuthenticationError':
161 | // You probably used an incorrect API key
162 | res.send(err);
163 | break;
164 | default:
165 | // Handle any other types of unexpected errors
166 | res.send(err);
167 | break;
168 | }
169 | }
170 | );
171 | });
172 |
173 | billingRoutes.post('/contact', (req:any, res, next) => {
174 | const emailType = {
175 | subject: 'Contact',
176 | cart: req.session.cart,
177 | contact: req.body
178 | };
179 |
180 | const mailer = new Mailer(req.body.email, emailType);
181 | mailer.send();
182 |
183 | keys.adminEmails
184 | .split(',')
185 | .filter(Boolean)
186 | .forEach(email => {
187 | var mailerToAdmin = new Mailer(email, { ...emailType, subject: 'Contact-From-Customer' });
188 | mailerToAdmin.send();
189 | });
190 | });
191 |
192 | export { billingRoutes };
193 |
--------------------------------------------------------------------------------
/routes/cartRoutes.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 |
3 | const Product = require('../models/Product');
4 | const Cart = require('../models/Cart');
5 |
6 | const cartRoutes = Router();
7 |
8 | cartRoutes.get('/addcart/:id/:lang', (req:any, res) => {
9 | const productId = req.params.id;
10 | const cart = new Cart(req.session.cart ? req.session.cart : {});
11 |
12 | Product.findById(productId, (err, product) => {
13 | if (err) {
14 | return res.redirect('/');
15 | }
16 |
17 | const updatedProduct = prepareProduct(product, req.params.lang);
18 | cart.add(updatedProduct, product.id);
19 | req.session.cart = cart;
20 |
21 | if (req.user) {
22 | req.user.cart = cart;
23 | req.user.save();
24 | }
25 | res.send(cart);
26 | });
27 | });
28 |
29 | cartRoutes.get('/removefromcart/:id/:lang', (req:any, res) => {
30 | const productId = req.params.id;
31 | const storeCart = req.session.cart ? req.session.cart : new Cart({});
32 | const cart = new Cart(storeCart);
33 |
34 | Product.findById(productId, (err, product) => {
35 | if (err) {
36 | return res.redirect('/');
37 | }
38 |
39 | const updatedProduct = prepareProduct(product, req.params.lang);
40 |
41 | cart.remove(updatedProduct, product.id);
42 | req.session.cart = cart;
43 | if (req.user) {
44 | req.user.cart = cart;
45 | req.user.save();
46 | }
47 | res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0').send(cart);
48 | });
49 | });
50 |
51 | cartRoutes.get('/cart', (req:any, res) => {
52 | const cart = req.user ? req.user.cart : req.session.cart ? req.session.cart : new Cart({});
53 | req.session.cart = cart;
54 | res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0').send(cart);
55 | });
56 |
57 | const prepareProduct = (product, lang) => {
58 | return {
59 | _id : product._id,
60 | titleUrl : product.titleUrl,
61 | mainImage : product.mainImage,
62 | onSale : product.onSale,
63 | stock : product.stock,
64 | visibility: product.visibility,
65 | shipping : product.shipping,
66 | images : product.images,
67 | _user : product._user,
68 | dateAdded : product.dateAdded,
69 | ...product[lang]
70 | };
71 | };
72 |
73 | export { cartRoutes };
74 |
--------------------------------------------------------------------------------
/routes/index.ts:
--------------------------------------------------------------------------------
1 | import { authRoutes } from './authRoutes';
2 | import { billingRoutes } from './billingRoutes';
3 | import { productRoutes } from './productRoutes';
4 | import { cartRoutes } from './cartRoutes';
5 | import { adminRoutes } from './adminRoutes';
6 |
7 | export {authRoutes, billingRoutes, productRoutes, cartRoutes, adminRoutes};
8 |
--------------------------------------------------------------------------------
/routes/productRoutes.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 |
3 | const Product = require('../models/Product');
4 | const Order = require('../models/Order');
5 | const requireLogin = require('../middlewares/requireLogin');
6 |
7 | const productRoutes = Router();
8 |
9 | productRoutes.get('/products/:lang/:page/:sort', (req, res) => {
10 | var query = {};
11 | var lang = req.params.lang;
12 | var options = {
13 | page: parseFloat(req.params.page),
14 | limit: 10,
15 | sort: prepareSort(req.params.sort, lang)
16 | };
17 | Product.paginate(query, options)
18 | .then(response => {
19 | const updatedResponse = {
20 | ...response,
21 | docs: response.docs.map(product => prepareProduct(product, lang))
22 | };
23 | res.status(200).send(updatedResponse);
24 | });
25 | });
26 |
27 | productRoutes.get('/categoryProducts/:lang/:category/:page/:sort', (req, res) => {
28 | const categoriesLang = `${req.params.lang}.categories`;
29 | var query = {[categoriesLang] : new RegExp(req.params.category, 'i' )};
30 | var lang = req.params.lang;
31 | var options = {
32 | page: parseFloat(req.params.page),
33 | limit: 100,
34 | sort: prepareSort(req.params.sort, lang)
35 | };
36 | Product.paginate(query, options)
37 | .then(response => {
38 | const updatedResponse = {
39 | ...response,
40 | docs: response.docs.map(product => prepareProduct(product, lang))
41 | };
42 | res.status(200).send(updatedResponse);
43 | });
44 | });
45 |
46 | productRoutes.get('/productId/:name/:lang*?', async (req, res) => {
47 | const lang = req.params.lang;
48 | Product.findOne({ titleUrl: req.params.name })
49 | .then(productFind => {
50 | const updatedProduct = req.params.lang
51 | ? prepareProduct(productFind, lang)
52 | : productFind;
53 |
54 | res.send(updatedProduct);
55 | });
56 | });
57 |
58 | productRoutes.get('/productQuery/:query', (req, res) => {
59 | Product.find(
60 | {
61 | titleUrl: new RegExp(req.params.query, 'i')
62 | },
63 | function(err, products) {
64 | const updatedProducts = products
65 | .map(product => product.titleUrl);
66 | res.status(200).send(updatedProducts);
67 | }
68 | );
69 | });
70 |
71 |
72 | productRoutes.get('/categories/:lang', (req, res) => {
73 | const categoriesLang = `${req.params.lang}.categories`;
74 | const lang = req.params.lang;
75 | Product.find({[categoriesLang]: { $gt: [] }}, function(err, products) {
76 | const categories = products
77 | .reduce((catSet, product) => catSet.concat(product[lang].categories) , [])
78 | .filter((cat, i, arr) => arr.indexOf(cat) === i)
79 | .map(category => ({title: category , titleUrl: category.replace(/ /g, '_').toLowerCase() }));
80 |
81 | res.status(200).send(categories);
82 | });
83 | });
84 |
85 |
86 | productRoutes.post('/orders', requireLogin, (req, res) => {
87 | const token = req.body.token;
88 | Order.find(
89 | {
90 | _user: token
91 | },
92 | function(err, orders) {
93 | res.status(200).send(orders);
94 | }
95 | );
96 | });
97 |
98 |
99 | // help functions
100 | const prepareSort = (sortParams, lang) => {
101 | switch (sortParams) {
102 | case 'newest':
103 | return `${lang}.dateAdded`
104 | case 'oldest':
105 | return `-${lang}.dateAdded`;
106 | case 'priceasc':
107 | return `${lang}.salePrice`
108 | case 'pricedesc':
109 | return `-${lang}.salePrice`
110 | default:
111 | return `-${lang}.dateAdded`;
112 | }
113 | };
114 |
115 | const prepareProduct = (product, lang) => {
116 | return {
117 | _id : product._id,
118 | titleUrl : product.titleUrl,
119 | mainImage : product.mainImage,
120 | onSale : product.onSale,
121 | stock : product.stock,
122 | visibility: product.visibility,
123 | shipping : product.shipping,
124 | images : product.images,
125 | _user : product._user,
126 | dateAdded : product.dateAdded,
127 | ...product[lang]
128 | }
129 | }
130 |
131 |
132 | export {productRoutes};
133 |
--------------------------------------------------------------------------------
/server.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js/dist/zone-node';
2 | require('reflect-metadata');
3 |
4 | import { enableProdMode } from '@angular/core';
5 | import { ngExpressEngine } from '@nguniversal/express-engine';
6 | import { existsSync } from 'fs';
7 | import { APP_BASE_HREF } from '@angular/common';
8 |
9 |
10 | const express = require('express');
11 | const session = require('express-session');
12 | const path = require('path');
13 | const mongoose = require('mongoose');
14 | const passport = require('passport');
15 | const bodyParser = require('body-parser');
16 | const keys = require('./config/keys');
17 | const compression = require('compression');
18 | const MongoStore = require('connect-mongo')(session);
19 | import { AppServerModule } from './src/main.server';
20 |
21 | const DIST_FOLDER = path.join(process.cwd(), 'dist');
22 |
23 |
24 | // mongoose models
25 | require('./models/User');
26 | require('./models/Product');
27 | require('./models/Order');
28 | require('./models/Translation');
29 |
30 | // services
31 | require('./services/passport');
32 | require('./services/cache');
33 |
34 | // connect mongoDB
35 | if (keys.mongoURI) {
36 | mongoose.connect(keys.mongoURI, { useNewUrlParser: true });
37 | mongoose.set('useFindAndModify', false);
38 | }
39 |
40 | import { authRoutes, billingRoutes, productRoutes, cartRoutes, adminRoutes } from './routes';
41 |
42 |
43 | enableProdMode();
44 |
45 |
46 | // The Express app is exported so that it can be used by serverless Functions.
47 | export function app() {
48 | const server = express();
49 | // compress files
50 | server.use(compression());
51 |
52 | // set CORS headers
53 | server.all('*', (req, res, next) => {
54 | res.header('Access-Control-Allow-Origin', '*');
55 | res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
56 | res.header('Access-Control-Allow-Credentials', 'true');
57 | res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Key, Authorization');
58 | next();
59 | });
60 | const distFolder = path.join(process.cwd(), 'dist/browser');
61 | const indexHtml = existsSync(path.join(distFolder, 'index.html')) ? 'index.html' : 'index';
62 |
63 | // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
64 | server.engine('html', ngExpressEngine({
65 | bootstrap: AppServerModule,
66 | }));
67 |
68 | server.set('view engine', 'html');
69 | server.set('views', distFolder);
70 |
71 | // Example Express Rest API endpoints
72 | // app.get('/api/**', (req, res) => { });
73 |
74 | server.use(bodyParser.urlencoded({extended: false}));
75 | server.use(bodyParser.json());
76 |
77 | server.use(
78 | session({
79 | cookie: {
80 | maxAge: 30 * 24 * 60 * 60 * 1000
81 | },
82 | secret: keys.cookieKey,
83 | resave: false,
84 | saveUninitialized: false,
85 | store: new MongoStore({
86 | mongooseConnection: mongoose.connection,
87 | collection: 'session'
88 | })
89 | })
90 | );
91 |
92 | // middlewares
93 | server.use(passport.initialize());
94 | server.use(passport.session());
95 |
96 | server.use((req, res, next) => {
97 | res.locals.login = req.isAuthenticated();
98 | res.locals.session = req.session;
99 | next();
100 | });
101 |
102 | server.use('/auth', authRoutes);
103 | server.use('/api', billingRoutes);
104 | server.use('/prod', productRoutes);
105 | server.use('/cartApi', cartRoutes);
106 | server.use('/admin', adminRoutes);
107 |
108 | server.get('*.*', express.static(path.join(DIST_FOLDER, 'browser')));
109 |
110 |
111 | server.get('*', (req, res) => {
112 | res.render(path.join(DIST_FOLDER, 'browser', 'index.html'), {
113 | req,
114 | res,
115 | providers: [
116 | {
117 | provide : 'REQUEST',
118 | useValue : (req)
119 | },
120 | {
121 | provide : 'RESPONSE',
122 | useValue : (res)
123 | }
124 | ]
125 | });
126 | });
127 |
128 |
129 |
130 | // Serve static files from /browser
131 | server.get('*.*', express.static(distFolder, {
132 | maxAge: '1y'
133 | }));
134 |
135 | // All regular routes use the Universal engine
136 | server.get('*', (req, res) => {
137 | res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
138 | });
139 |
140 | return server;
141 | }
142 |
143 |
144 | function run() {
145 | const port = process.env.PORT || 4000;
146 | // Start up the Node server
147 | const server = app();
148 | server.listen(port, () => {
149 | console.log(`Node Express server listening on http://localhost:${port}`);
150 | });
151 | }
152 |
153 |
154 | // Webpack will replace 'require' with '__webpack_require__'
155 | // '__non_webpack_require__' is a proxy to Node 'require'
156 | // The below code is to ensure that the server is run only when not requiring the bundle.
157 | declare const __non_webpack_require__: NodeRequire;
158 | const mainModule = __non_webpack_require__.main;
159 | const moduleFilename = mainModule && mainModule.filename || '';
160 | if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
161 | run();
162 | }
163 |
164 |
165 | export * from './src/main.server';
--------------------------------------------------------------------------------
/services/cache.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const redis = require('redis');
3 | const util = require('util');
4 | const keys = require('../config/keys');
5 |
6 | const client = redis.createClient(keys.redisUrl);
7 | client.get = util.promisify(client.get);
8 | const exec = mongoose.Query.prototype.exec;
9 |
10 | mongoose.Query.prototype.cache = function(options = {}) {
11 | this.useCache = true;
12 | this.hashKey = JSON.stringify(options.key || '');
13 |
14 | return this;
15 | };
16 |
17 | mongoose.Query.prototype.exec = async function() {
18 | if (!this.useCache) {
19 | return exec.apply(this, arguments);
20 | }
21 |
22 | const key = JSON.stringify(
23 | Object.assign({}, this.getQuery(), {
24 | collection: this.mongooseCollection.name
25 | })
26 | );
27 |
28 | // See if we have a value for 'key' in redis
29 | const cacheValue = await client.get(key);
30 |
31 | // If we do, return that
32 | if (cacheValue) {
33 | const doc = JSON.parse(cacheValue);
34 |
35 | return Array.isArray(doc)
36 | ? doc.map(d => new this.model(d))
37 | : new this.model(doc);
38 | }
39 |
40 | // Otherwise, issue the query and store the result in redis
41 | const result = await exec.apply(this, arguments);
42 |
43 | client.set(key, JSON.stringify(result), 'EX', 10000);
44 |
45 | return result;
46 | };
47 |
48 | module.exports = {
49 | clearHash(hashKey) {
50 | client.del(JSON.stringify(hashKey));
51 | }
52 | };
53 |
--------------------------------------------------------------------------------
/services/mailer.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | const sendgrid = require("sendgrid");
4 | const helper = sendgrid.mail;
5 | const keys = require("../config/keys");
6 |
7 | const prepareOrderEmailTemplate = require('./emailTemplates');
8 |
9 |
10 | class Mailer extends helper.Mail {
11 | constructor(reqEmail, emailType) {
12 | super();
13 |
14 | this.sgApi = sendgrid(keys.sendGridKey);
15 |
16 | this.from_email = new helper.Email("no-reply@bluetooh-eshop.com");
17 | this.subject = emailType.subject;
18 |
19 | this.body = new helper.Content("text/html", getContent(emailType));
20 |
21 | this.email = new helper.Email(reqEmail);
22 | const personalize = new helper.Personalization();
23 | personalize.addTo(this.email);
24 |
25 | this.addContent(this.body);
26 | this.addPersonalization(personalize);
27 | }
28 |
29 | async send() {
30 | const request = this.sgApi.emptyRequest({
31 | method: "POST",
32 | path: "/v3/mail/send",
33 | body: this.toJSON()
34 | });
35 |
36 | const response = await this.sgApi.API(request);
37 | return response;
38 | }
39 | }
40 |
41 | module.exports = Mailer;
42 |
43 | function getContent(emailType) {
44 | if (emailType.subject === "Order") {
45 | const cart = emailType.cart;
46 |
47 | return prepareOrderEmailTemplate(cart, emailType);
48 |
49 | } else if (emailType.subject === "Contact") {
50 | return `
51 |
We will let you know soon about your requirement
55 |Your requirement:
56 |Name: ${emailType.contact.name}
57 |Email: ${emailType.contact.email}
58 |Notes: ${emailType.contact.notes}
59 |Name: ${emailType.contact.name}
74 |Email: ${emailType.contact.email}
75 |Notes: ${emailType.contact.notes}
76 |Desc: {{ order?.description }}
36 |Customer: {{ order?.customerEmail }}
37 |Created: {{ order.dateAdded | date:'dd-MM-yy' }}
38 |Paid: {{ order?.outcome?.seller_message }}
39 |Total Price: {{ order?.cart?.totalPrice }}
40 |Total Quantity: {{ order?.cart?.totalQty }}
41 |Customer data:
47 |CITY: {{ order?.source?.address_city }}
48 |COUNTRY: {{ order?.source?.address_country }}
49 |ADRESS: {{ order?.source?.address_line1 }}
50 |ZIP: {{ order?.source?.address_zip }}
51 |CardType: {{ order?.source?.brand }}
52 |Customer Name : {{ order?.source?.name }}
53 |Cart:
56 |{{ order?.orderId }}
9 |Status: {{ order?.status }}
12 |Desc: {{ order?.description }}
15 |Customer: {{ order?.customerEmail }}
16 |Created: {{ order.dateAdded | date:'dd-MM-yy' }}
17 |Paid: {{ order?.outcome?.seller_message }}
18 |Desc: {{ order?.description }}
29 |Customer: {{ order?.customerEmail }}
30 |Created: {{ order.dateAdded | date:'dd-MM-yy' }}
31 |Paid: {{ order?.outcome?.seller_message }}
32 |Total Price: {{ order?.cart?.totalPrice }}
33 |Total Quantity: {{ order?.cart?.totalQty }}
34 |Customer data:
40 |CITY: {{ order?.source?.address_city }}
41 |COUNTRY: {{ order?.source?.address_country }}
42 |ADRESS: {{ order?.source?.address_line1 }}
43 |ZIP: {{ order?.source?.address_zip }}
44 |CardType: {{ order?.source?.brand }}
45 |Customer Name : {{ order?.source?.name }}
46 |Cart:
49 |{{ order?.orderId }}
9 |Status: {{ order?.status }}
12 |Desc: {{ order?.description }}
15 |Customer: {{ order?.customerEmail }}
16 |Created: {{ order.dateAdded | date:'dd-MM-yy' }}
17 |Paid: {{ order?.outcome?.seller_message }}
18 |