added docker

This commit is contained in:
Sergey Yarkov 2020-05-04 16:32:09 +03:00
parent 020d8599b9
commit 965e6b0a56
29 changed files with 357 additions and 552 deletions

5
.dockerignore Normal file
View File

@ -0,0 +1,5 @@
/node_modules
npm-debug.log
yarn-debug.log*
yarn-error.log*

11
Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM node:10
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "start"]

412
README.md
View File

@ -27,281 +27,45 @@
</a> </a>
</p> </p>
## Содержание ## 🐬 Установка через Docker:
- [О проекте](#О-проекте)
- [Структура репозитория](#Структура-репозитория)
- [Установка и настройка скрипта](#Установка-и-настройка-скрипта)
- [Запуск скрипта на локальном компьютере](#Запуск-скрипта-на-локальном-компьютере)
- [Запуск скрипта на удаленном сервере](#Запуск-скрипта-на-удаленном-сервере)
- [Выгрузка скрипта на бесплтаный хостинг от Heroku](#Выгрузка-скрипта-на-бесплтаный-хостинг-от-Heroku)
- [Переменные среды](#Переменные-среды)
- [API](#API)
- [Требования](#Требования)
- [Лицензия](#Лицензия)
## О проекте: -
Этот проект представляет из себя интернет-магазин на платформе NodeJS, Вы можете продавать свои цифровые товары именно загрузив этот репозиторий, в [панели управления](#Панель-управления) Вы сможете создавать, удалять и редактировать товары или категории . В проекте используется концепция REST API, поэтому для отдачи информации клиенту существует отдельный API. Информация о покупателях, товарах, категориях и т.д хранится в БД MongoDB (используется нативный драйвер).
### Функционал покупателя:
- Возможность выбирать интересующие категории, переход по одиночным страницам конкретного товара.
- Возможность комментировать как отдельный товар, так и весь магазин на отдельной странице с помощью системы комментариев Disqus.
- Возможность фильтровать интересующую категорию или все товары через фильтр.
- Покупатель может совершать одну или несколько покупок через корзину или кнопку "Купить".
- Оплата осуществляется через сервис QIWI API.
- Результатом оплаты будут данные одного или нескольких товаров, все данные отсылаются на электронную почту покупателя, которую он указал при создании заказа.
- Покупатель может узнать все свои совершенные покупки в магазине на отдельной странице.
### Функционал администратора:
Для входа в админ-панель необходимо дописать в адресной строке `/admin`, после Вы попадете на страницу авторизации где нужно ввести данные в формате `email:password`. По умолчанию `admin@support.ru:admin`
- Пароль админа хэшируется через `bcrypt` для безопасности и записывается в БД.
- На главной странице есть небольшая статистика, где можно посмотреть: сумму проданных товаров, кол-во проданных товаров, кол-во товаров в наличии.
- На главной странце так же можно узнать подробные данные о покупателях: номер заказа, почта покупателя, способ оплаты, дата покупки, сумма и данные которые были высланы по почте.
- Возможность находить конкретного покупателя через поле (нужно знать номер заказа).
- Возможность добавлять, удалять или редактировать товары на странице "Товары", так же можно посмотреть и информацию о товаре.
- Поиск товаров через поле поиска (нужно знать частичное или полное название товара).
- Добавление, удаление и редактирование категорий через страницу "Категории", каждой категории присваивается собственная иконка, которую можно загрузить через страницу "Иконки".
- Возможность править контент, изменять общее оформление магазина (только цвет).
- Возможность изменять данные администратора: почта, пароль, имя.
## Структура репозитория:
В основе всего репозитория существуют две ветки. Ветка master предназначена для разработки приложения, для выгрузки готового приложения на сервер используйте ветку production.
## Установка и настройка скрипта:
### Запуск скрипта на локальном компьютере:
- [Установите последний релиз Digital Store](https://github.com/sergeyyarkov/digital-store/releases) из GitHub репозитория.
- Или клонировать репозиторий `git clone https://github.com/sergeyyarkov/digital-store.git -b production`.
- Установите [Node.js](https://nodejs.org/ru/) не ниже версии 7.6.0, в процессе установки Node.js установите Node Package Manager.
- Установите БД MongoDB на ваш компьютер или создайте кластер на сайте [www.mongodb.com](https://www.mongodb.com/) и импортируйте туда дамп структуры БД, которые находится в корне приложения `/database_dump`.
- Для работоспособности интернет-магазина необходимо установить зависимости. Выполните комманду `npm i`.
- Далле необходимо настроить переменные среды. Создайте файл `.env` в корне вашего интернет-магазина и запишите туда [значения для переменных](#Переменные-среды). Обратите внимание, что для выгрузки скрипта на production сервер, нужно устанавливать переменные среды для вашего сервера, файл `.env` создавать не нужно.
- После этого, введите `npm run start` и перейдите по адресу `localhost:3000`, вы увидите уже рабочий интернет-магазин который запущен у вас на локальном компьютере.
### Запуск скрипта на удаленном сервере:
В данном примере на удаленном сервере будет использоваться связка `Ubuntu 18.04, Nginx, MongoDB, PM2, NodeJS` + `SSL Lets Encrypt`
- Переходим на сайт [RUVDS.COM](https://ruvds.com/pr4435) и приобретаем услугу VPS-хостинга.
- Подключаемся к серверу по SSH и следуем далее описанным шагам.
- Устанавливаем NodeJS:
```bash
apt-get update && apt-get install curl -y
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
source ~/.bashrc
nvm install 12
```
Если при выполнении команд `node -v` и `npm -v` выдается версия, то идем дальше.
- Создаем папку `/apps`, после переходим в нее и клонируем проект с github и переходим в наш магазин:
```bash
mkdir apps
cd apps/
git clone https://github.com/sergeyyarkov/digital-store.git -b production
cd digital-store/
```
- Устанавливаем зависимости для интернет-магазина `npm install`.
- Устанавливаем MongoDB:
```bash
apt install -y mongodb
systemctl status mongodb <--- проверяем состояние службы, должно быть active (running)
```
Теперь нужно дать доступ для подключения к БД именно с вашего компьютера, это нам понадобится для импортирования дампа БД:
ip адресс берете с сайта 2ip
```bash
ufw enable
ufw allow from ваш_ip_адресс_компьютера/32 to any port 27017
```
- Далее открываем конфигурационный файл MongoDB: `nano /etc/mongodb.conf`:
Добавляем IP-адрес вашего сервера в значение bindIP:
```bash
logappend=true
bind_ip = 127.0.0.1,your_server_ip
#port = 27017
```
- Сохраните файл, закройте редактор и перезапустите MongoDB: `systemctl restart mongodb`:
- Теперь доступ к БД есть только у вашего компьютера, далее скачиваем [Studio 3T](https://studio3t.com/) и создаем новое соединение, ip адресс указываем вашего сервера.
- После подключения, создаем новую БД и называем её как хотим.
- Кликаем по вашей созданной БД и вверху нажимаем `Import`, после выбираем все JSON файлы, которые хранятся в папке `/database_dump`.
- Если все 6 коллекций успешно импортировались, то идем дальше.
- Устанавливаем менеджер процессов PM2 `npm install -g pm2`.
- Редактируем файл с переменными в файле ecosystem.config.js и устанавливаем свои значения `nano ecosystem.config.js`.
- Далее запускаем наше приложение:
```bash
pm2 start ecosystem.config.js --env production <--- стартуем наше приложение
pm2 status <--- как узнать статус запущенных процессов
pm2 save <--- сохраняем процесс
pm2 startup ubuntu <--- автоматически запускаем наше приложение в случае рестарта сервера
```
- Настраиваем файрволл:
```bash
ufw allow ssh
ufw allow http
ufw allow https
```
- Далее устанавливаем веб-сервер nginx `apt install nginx`, после редактируем конфигурационный файл `nano /etc/nginx/sites-available/default`:
```bash
server_name yourdomain.com www.yourdomain.com;
location / {
proxy_pass http://localhost:3000; #порт запущенного приложения
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
```
- Обязательно настройте gzip сжатие, ваш сайт будет работать быстрее:
```bash
server {
gzip on;
gzip_disable "msie6";
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
}
```
- Так же желательно избавиться от дубля страницы www:
```bash
if ($host = 'www.example.com') {
return 301 https://example.com$request_uri;
}
```
- После настройки проверьте ваш конфигурационный файл на ошибки `nginx -t`, если ошибок нет, то перезапускаем сервис nginx `service nginx restart`.
- На сайте RUDVS добавьте свой домен и привяжите его к своему серверу, настройте DNS записи у регистратора. Через какое то время ваш магазин будет доступен по домену.
- Далее настройте SSL сертификат:
```bash
add-apt-repository ppa:certbot/certbot
apt-get update
apt-get install python-certbot-nginx
certbot --nginx -d yourdomain.com <--- в процессе установки выбираем цифру 2
```
- Ваш конфигурационный файл должен выглядеть примерно так:
```bash
server {
gzip on;
gzip_disable "msie6";
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
server_name yourdomain.ru www.yourdomain.ru;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
if ($host = 'www.yourdomain.ru') {
return 301 https://yourdomain.ru$request_uri;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/yourdomain.ru/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/yourdomain.ru/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = www.yourdomain.ru) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = yourdomain.ru) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80 default_server;
listen [::]:80 default_server;
server_name yourdomain.ru www.yourdomain.ru;
return 404; # managed by Certbot
}
```
### Установка скрипта на бесплатный хостинг от Heroku:
- Регистрируемся на сайте [Heroku](https://signup.heroku.com/login).
- Устанавливаем heroku-cli `https://devcenter.heroku.com/articles/heroku-cli`.
- Проверьте установку, написав в консоле `heroku --help` если всё выводит, идем дальше.
- Залогиньтесь: `heroku login`.
- Создайте новый проект Herkou: `heroku create`.
- Создайте кластер на сайте [www.mongodb.com](https://www.mongodb.com/) и импортируйте туда дамп структуры БД, который находится в корне приложения `/database_dump`. При подключении к БД выберите метод `Connect Your Application`
- Переходим на сайт heroku, идем в свое созданное приложение и открываем вкладку `Settings -> Config Vars` кнопка `Reveal Config Vars` и настраиваем [переменные среды](#Переменные-среды).
- Создаем папку с любым названием, открываем её и клонируем проект с github: `git clone https://github.com/sergeyyarkov/digital-store.git -b production .`
- Сменим ветку на master: `git checkout -b master`
- Удаляем файл ecosystem.config.js (в случае с heroku он не нужен т.к переменные мы храним у heroku)
- Добавляем удаленный репозиторий `heroku git:remote -a название вашего приложения`.
- Деплой:
```bash
git add .
git commit -m "make it better"
git push heroku master
```
### Настройка платежной системы: ### Настройка платежной системы:
В скрипте на данный момент доступна система оплаты через сервис QIWI, чтобы настроить оплату в вашем магазине следуйте следующим шагам: В скрипте на данный момент доступна система оплаты через сервис QIWI, чтобы настроить оплату в вашем магазине следуйте следующим шагам:
- Перейдите на сайт [QIWI API](https://qiwi.com/p2p-admin/transfers/api) и создайте пару ключей авторизации. Сохраните секретный ключ. - Перейдите на сайт [QIWI API](https://qiwi.com/p2p-admin/transfers/api) и создайте пару ключей авторизации. Сохраните секретный ключ.
- Если вы хотите настроить оформление формы приемов перевода, то перейдите [сюда](https://qiwi.com/p2p-admin/transfers/link) и скопируйте значение переменной `themeCode` и вставьте это значение по адресу `config/routes/payments/qiwi.payment.js`. - Если вы хотите настроить оформление формы приемов перевода, то перейдите [сюда](https://qiwi.com/p2p-admin/transfers/link) и скопируйте значение переменной `themeCode` и вставьте это значение по адресу `config/routes/payments/qiwi.payment.js`.
- Секретный ключ нужно указать в поле переменной среды `QIWI_SECRET_KEY`. - Секретный ключ нужно указать в поле переменной среды `QIWI_SECRET_KEY`.
- Редирект после оплаты товара через QIWI на локальный адрес `localhost:3000` работать не будет, необходим домен. - Редирект после оплаты товара через QIWI на локальный адрес `localhost:3000` работать не будет, необходим домен.
### Настройка почтового сервиса: ### Настройка почтового сервиса:
Для отдачи данных товара на почту покупателя после покупки товара, нужно настроить nodemailer: Для отдачи данных товара на почту покупателя после покупки товара, нужно настроить nodemailer:
- Настройте почтовый сервис по адресу `config/nodemailer.config.js`: - Настройте почтовый сервис по адресу `config/nodemailer.config.js`:
```javascript ```javascript
host: "smtp.mail.ru", host: "smtp.mail.ru",
port: 465, port: 465,
secure: true, // true для 465, false для остальных. secure: true, // true для 465, false для остальных.
``` ```
- Затем укажите переменные среды `EMAIL_LOGIN` и `EMAIL_PASSWORD`. - Затем укажите переменные среды `EMAIL_LOGIN` и `EMAIL_PASSWORD`.
### Настройка системы комментариев: ### Настройка системы комментариев:
В интернет магазине используется система комментариев [Disqus](https://disqus.com/): В интернет магазине используется система комментариев [Disqus](https://disqus.com/):
- Добавьте свой домен на их сайт и замените embed код по адресу `dist/public/js/disqus.js`. - Добавьте свой домен на их сайт и замените embed код по адресу `dist/public/js/disqus.js`.
## Переменные среды: ## Переменные среды:
Перменные среды нужны для работоспособности всего приложения, очень важно вписать верные данные для избежания возможных ошибок. Перменные среды нужны для работоспособности всего приложения, очень важно вписать верные данные для избежания возможных ошибок.
### Описание переменных: ### Описание переменных:
| Переменная | Описание | | Переменная | Описание |
| --------------- | -------------------------------------------------------- | | --------------- | -------------------------------------------------------- |
| DATABASE_URI | Подключение к БД MongoDB | | DATABASE_URI | Подключение к БД MongoDB |
@ -311,6 +75,7 @@ secure: true, // true для 465, false для остальных.
| QIWI_SECRET_KEY | Секретный ключ QIWI для работоспособности системы оплаты | | QIWI_SECRET_KEY | Секретный ключ QIWI для работоспособности системы оплаты |
`.env` файл: `.env` файл:
```bash ```bash
DATABASE_URI= DATABASE_URI=
DATABASE_NAME= DATABASE_NAME=
@ -320,159 +85,184 @@ EMAIL_PASSWORD=
``` ```
## API: ## API:
### API представляет из себя различные URL, которые отвечают данными только в формате JSON. Здесь описаны основные `GET` запросы на сервер для получения данных: ### API представляет из себя различные URL, которые отвечают данными только в формате JSON. Здесь описаны основные `GET` запросы на сервер для получения данных:
#### Для просмотра всех товаров которые существуют в магазине отправляется запрос: #### Для просмотра всех товаров которые существуют в магазине отправляется запрос:
```bash ```bash
https://store-name.com/api/items https://store-name.com/api/items
``` ```
Ответ: Ответ:
```javascript ```javascript
[ ;[
{ {
_id: "5e15dbc415303a1980cd77a0", _id: '5e15dbc415303a1980cd77a0',
title: "Тестовый товар №1", title: 'Тестовый товар №1',
count: 0, count: 0,
price: 1, price: 1,
category: "steam", category: 'steam',
description: "-", description: '-',
date: "2020-01-08T13:40:20.353Z" date: '2020-01-08T13:40:20.353Z',
}, },
{ {
_id: "5e15dcb4138c3339bcbc3719", _id: '5e15dcb4138c3339bcbc3719',
title: "Тестовый товар №2", title: 'Тестовый товар №2',
count: 0, count: 0,
price: 1, price: 1,
category: "uplay", category: 'uplay',
description: "-", description: '-',
date: "2020-01-08T13:44:20.940Z" date: '2020-01-08T13:44:20.940Z',
}, },
] ]
``` ```
### Для просмотра товаров в интересующей категории отправляется запрос: ### Для просмотра товаров в интересующей категории отправляется запрос:
```bash ```bash
https://store-name.com/api/items/{category} https://store-name.com/api/items/{category}
``` ```
Ответ: Ответ:
```javascript ```javascript
[ ;[
{ {
_id: "5e15dbc415303a1980cd77a0", _id: '5e15dbc415303a1980cd77a0',
title: "Тестовый товар №1", title: 'Тестовый товар №1',
count: 0, count: 0,
price: 1, price: 1,
category: "steam", category: 'steam',
description: "-", description: '-',
date: "2020-01-08T13:40:20.353Z" date: '2020-01-08T13:40:20.353Z',
}, },
{ {
_id: "5e1ccf30d5b894228c86385a", _id: '5e1ccf30d5b894228c86385a',
title: "Тестовый товар №2", title: 'Тестовый товар №2',
count: 10, count: 10,
price: 1, price: 1,
category: "steam", category: 'steam',
description: "-", description: '-',
date: "2020-01-13T20:12:32.814Z" date: '2020-01-13T20:12:32.814Z',
} },
] ]
``` ```
### Для просмотра информации о всех категориях отправляется запрос: ### Для просмотра информации о всех категориях отправляется запрос:
```bash ```bash
https://store-name.com/api/categories https://store-name.com/api/categories
``` ```
Ответ: Ответ:
```javascript ```javascript
[ ;[
{ {
_id: "5df64563309e691f2c33d400", _id: '5df64563309e691f2c33d400',
title: "uplay", title: 'uplay',
img: "img-1576349954743.svg", img: 'img-1576349954743.svg',
type: "type", type: 'type',
format: "format" format: 'format',
}, },
{ {
_id: "5e04ea023625771abc75fd14", _id: '5e04ea023625771abc75fd14',
title: "fortnite", title: 'fortnite',
img: "img-1576349989720.svg", img: 'img-1576349989720.svg',
type: "type", type: 'type',
format: "format" format: 'format',
}, },
] ]
``` ```
### Для просмотра информации о интересующей категории отправляется запрос: ### Для просмотра информации о интересующей категории отправляется запрос:
```bash ```bash
https://store-name.com/api/category/{category} https://store-name.com/api/category/{category}
``` ```
Ответ: Ответ:
```javascript ```javascript
[ ;[
{ {
_id: "5df64563309e691f2c33d400", _id: '5df64563309e691f2c33d400',
title: "uplay", title: 'uplay',
img: "img-1576349954743.svg", img: 'img-1576349954743.svg',
type: "type", type: 'type',
format: "format" format: 'format',
} },
] ]
``` ```
### Для просмотра данных всех товаров отправляется запрос: **Нужна авторизация для администратора.** ### Для просмотра данных всех товаров отправляется запрос: **Нужна авторизация для администратора.**
```bash ```bash
https://store-name.com/api/items/data https://store-name.com/api/items/data
``` ```
Ответ: Ответ:
```javascript ```javascript
[ ;[
{ {
_id: "5e15dbc415303a1980cd77a0", _id: '5e15dbc415303a1980cd77a0',
title: "Тестовый товар №1", title: 'Тестовый товар №1',
data: ["login:pass", "login:pass", "login:pass"] data: ['login:pass', 'login:pass', 'login:pass'],
}, },
{ {
_id: "5e175c63b479600ac493ce88", _id: '5e175c63b479600ac493ce88',
title: "Тестовый товар №2", title: 'Тестовый товар №2',
data: ["login:pass", "login:pass", "login:pass"] data: ['login:pass', 'login:pass', 'login:pass'],
} },
] ]
``` ```
### Для просмотра покупателей отправляется запрос: **Нужна авторизация для администратора.** ### Для просмотра покупателей отправляется запрос: **Нужна авторизация для администратора.**
```bash ```bash
https://store-name.com/api/buyers https://store-name.com/api/buyers
``` ```
Ответ: Ответ:
```javascript ```javascript
[ ;[
{ {
_id: "5e1c97701b619a31f010062c", _id: '5e1c97701b619a31f010062c',
bill_id: "9244e5ae-560a-432a-9882-bc1b941c8cca", bill_id: '9244e5ae-560a-432a-9882-bc1b941c8cca',
email: "test-email@mail.com", email: 'test-email@mail.com',
method: "qiwi", method: 'qiwi',
date: "2020-01-13T18:13:38.623+03:00", date: '2020-01-13T18:13:38.623+03:00',
amount: 2, amount: 2,
data: ["login:pass", "login:pass"] data: ['login:pass', 'login:pass'],
} },
] ]
``` ```
### Для просмотра списка иконок отправляется запрос: **Нужна авторизация для администратора.** ### Для просмотра списка иконок отправляется запрос: **Нужна авторизация для администратора.**
```bash ```bash
https://store-name.com/api/icons https://store-name.com/api/icons
``` ```
Ответ: Ответ:
```javascript ```javascript
[ ;[
"img-1576349954743.svg", 'img-1576349954743.svg',
"img-1576349989720.svg", 'img-1576349989720.svg',
"img-1576420937629.svg", 'img-1576420937629.svg',
"img-1576421960189.svg" 'img-1576421960189.svg',
] ]
``` ```
## Требования: ## Требования:
- Node.js v7.6.0 или выше - Node.js v7.6.0 или выше
## Лицензия: ## Лицензия:
[MIT](https://github.com/sergeyyarkov/digital-store/blob/master/LICENSE)<br><br> [MIT](https://github.com/sergeyyarkov/digital-store/blob/master/LICENSE)<br><br>

Binary file not shown.

View File

@ -0,0 +1 @@
{"options":{},"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_","ns":"digital_store.administrators"}],"uuid":"7c7a24d1f1984eb3823ca3af77c8f887"}

View File

View File

@ -0,0 +1 @@
{"options":{},"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_","ns":"digital_store.buyers"}],"uuid":"4e3bebb2aa9648a5ba18b6dc30b2b2e8"}

Binary file not shown.

View File

@ -0,0 +1 @@
{"options":{},"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_","ns":"digital_store.categories"}],"uuid":"73f3159b00974d27a9c03a507b4734dd"}

Binary file not shown.

View File

@ -0,0 +1 @@
{"options":{},"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_","ns":"digital_store.content"}],"uuid":"3f734f39ee614324be532246ad14faf3"}

Binary file not shown.

View File

@ -0,0 +1 @@
{"options":{},"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_","ns":"digital_store.info"}],"uuid":"33f3568c85fe4350bfae3aec7059bd91"}

Binary file not shown.

View File

@ -0,0 +1 @@
{"options":{},"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_","ns":"digital_store.items"},{"v":{"$numberInt":"2"},"key":{"_fts":"text","_ftsx":{"$numberInt":"1"}},"name":"title_text","language_override":"language","ns":"digital_store.items","weights":{"title":{"$numberInt":"1"}},"default_language":"english","textIndexVersion":{"$numberInt":"3"}}],"uuid":"14bff4a55fcf4fd2963b07f75add3ae8"}

20
data/nginx/app.conf Normal file
View File

@ -0,0 +1,20 @@
server {
listen 80;
gzip on;
gzip_disable "msie6";
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
server_name localhost;
server_tokens off;
location / {
proxy_pass http://nodejs:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}

View File

@ -1,7 +0,0 @@
{
"_id" : ObjectId("5de9733dc58a41381415a7b0"),
"id" : "1575580477024",
"name" : "Administrator",
"email" : "admin@support.ru",
"password" : "$2a$10$l5A1VHuN3a9IXhPq.tK.WOWvrIDZJxu1LKIPOYe6ywlS4dbjIMBu6"
}

View File

@ -1,11 +0,0 @@
{
"_id" : ObjectId("5e2c6e4770964b0017bb9009"),
"bill_id" : "51076c5e-25d7-422e-8f73-9bc3f583679c",
"email" : "testmail@mail.com",
"method" : "qiwi",
"date" : "2020-01-25T19:35:14.025+03:00",
"amount" : NumberInt(1),
"data" : [
"login:pass"
]
}

View File

@ -1,28 +0,0 @@
{
"_id" : ObjectId("5df63ed5044ecc18c43e9e91"),
"title" : "steam",
"img" : "img-1576420937629.svg",
"type" : "Steam Type",
"format" : "Steam Format"
},
{
"_id" : ObjectId("5df64563309e691f2c33d400"),
"title" : "uplay",
"img" : "img-1576349954743.svg",
"type" : "Uplay Type",
"format" : "Uplay Format"
},
{
"_id" : ObjectId("5e04ea023625771abc75fd14"),
"title" : "fortnite",
"img" : "img-1576349989720.svg",
"type" : "Fortnite Type",
"format" : "Fortnite Format"
},
{
"_id" : ObjectId("5e11c381471ae208f0b6aae4"),
"title" : "origin",
"img" : "img-1576421960189.svg",
"type" : "Origin Type",
"format" : "Origin Format"
}

View File

@ -1,11 +0,0 @@
{
"_id" : ObjectId("5e0b7466fbd02d01c46038a7"),
"title" : "Store Name",
"email" : "email@support.ru",
"info" : "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?",
"infoBottom" : "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?",
"howToBuy" : "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?",
"contacts" : "1. Почта: email@support.ru\r\n2. Telegram: @support.ru",
"recommendations" : "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?",
"color" : "#26a69a"
}

View File

@ -1,64 +0,0 @@
{
"_id" : ObjectId("5e15dbc415303a1980cd77a0"),
"title" : "Тестовый товар №1",
"data" : [
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass"
]
},
{
"_id" : ObjectId("5e15dcb4138c3339bcbc3719"),
"title" : "Тестовый товар №3",
"data" : [
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass"
]
},
{
"_id" : ObjectId("5e175c63b479600ac493ce88"),
"title" : "Тестовый товар №4",
"data" : [
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass"
]
},
{
"_id" : ObjectId("5e1ccf30d5b894228c86385a"),
"title" : "Тестовый товар №5",
"data" : [
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass",
"login:pass"
]
}

View File

@ -1,36 +0,0 @@
{
"_id" : ObjectId("5e15dbc415303a1980cd77a0"),
"title" : "Тестовый товар №1",
"count" : NumberInt(10),
"price" : NumberInt(1),
"category" : "steam",
"description" : "Тестовый товар №1 описание",
"date" : ISODate("2020-01-08T13:40:20.353+0000")
},
{
"_id" : ObjectId("5e15dcb4138c3339bcbc3719"),
"title" : "Тестовый товар №3",
"count" : NumberInt(10),
"price" : NumberInt(1),
"category" : "uplay",
"description" : "Тестовый товар №3 fdsfd",
"date" : ISODate("2020-01-08T13:44:20.940+0000")
},
{
"_id" : ObjectId("5e175c63b479600ac493ce88"),
"title" : "Тестовый товар №4",
"count" : NumberInt(10),
"price" : NumberInt(1),
"category" : "fortnite",
"description" : "Тестовый товар №4 opis",
"date" : ISODate("2020-01-09T17:01:23.446+0000")
},
{
"_id" : ObjectId("5e1ccf30d5b894228c86385a"),
"title" : "Тестовый товар №5",
"count" : NumberInt(10),
"price" : NumberInt(1),
"category" : "origin",
"description" : "sdsdsds",
"date" : ISODate("2020-01-13T20:12:32.814+0000")
}

47
docker-compose.yml Normal file
View File

@ -0,0 +1,47 @@
version: '3'
services:
mongodb:
container_name: mongodb
image: mongo
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=secret
- MONGO_INITDB_DATABASE=digital_store
ports:
- '27017'
restart: always
volumes:
- ./data/backup:/backup
- ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
nodejs:
container_name: nodejs
restart: always
build: .
ports:
- '3000'
environment:
- DATABASE_NAME=digital_store
- DATABASE_URI=mongodb://root:secret@mongodb:27017
- EMAIL_LOGIN=
- EMAIL_PASSWORD=
- HOSTNAME_URI=
- QIWI_SECRET_KEY=
nginx:
image: nginx
container_name: nginx
restart: unless-stopped
ports:
- '80:80'
- '443:443'
volumes:
- ./data/nginx:/etc/nginx/conf.d
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
command: '/bin/sh -c ''while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g "daemon off;"'''
certbot:
image: certbot/certbot
restart: unless-stopped
volumes:
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

View File

@ -1,21 +0,0 @@
module.exports = {
apps : [{
name: 'server',
script: 'server/server.js',
autorestart: true,
watch: true,
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'development'
},
env_production: {
NODE_ENV: 'production',
DATABASE_URI: 'mongodb://127.0.0.1:27017',
DATABASE_NAME: '',
EMAIL_LOGIN: '',
EMAIL_PASSWORD: '',
QIWI_SECRET_KEY: ''
}
}],
};

80
init-letsencrypt.sh Normal file
View File

@ -0,0 +1,80 @@
#!/bin/bash
if ! [ -x "$(command -v docker-compose)" ]; then
echo 'Error: docker-compose is not installed.' >&2
exit 1
fi
domains=(domain.ru www.domain.ru)
rsa_key_size=4096
data_path="./data/certbot"
email="yourmail@mail.ru" # Adding a valid address is strongly recommended
staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits
if [ -d "$data_path" ]; then
read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision
if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then
exit
fi
fi
if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then
echo "### Downloading recommended TLS parameters ..."
mkdir -p "$data_path/conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem"
echo
fi
echo "### Creating dummy certificate for $domains ..."
path="/etc/letsencrypt/live/$domains"
mkdir -p "$data_path/conf/live/$domains"
docker-compose run --rm --entrypoint "\
openssl req -x509 -nodes -newkey rsa:1024 -days 1\
-keyout '$path/privkey.pem' \
-out '$path/fullchain.pem' \
-subj '/CN=localhost'" certbot
echo
echo "### Starting nginx ..."
docker-compose up --force-recreate -d
echo
echo "### Deleting dummy certificate for $domains ..."
docker-compose run --rm --entrypoint "\
rm -Rf /etc/letsencrypt/live/$domains && \
rm -Rf /etc/letsencrypt/archive/$domains && \
rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot
echo
echo "### Requesting Let's Encrypt certificate for $domains ..."
#Join $domains to -d args
domain_args=""
for domain in "${domains[@]}"; do
domain_args="$domain_args -d $domain"
done
# Select appropriate email arg
case "$email" in
"") email_arg="--register-unsafely-without-email" ;;
*) email_arg="--email $email" ;;
esac
# Enable staging mode if needed
if [ $staging != "0" ]; then staging_arg="--staging"; fi
docker-compose run --rm --entrypoint "\
certbot certonly --webroot -w /var/www/certbot \
$staging_arg \
$email_arg \
$domain_args \
--rsa-key-size $rsa_key_size \
--agree-tos \
--force-renewal" certbot
echo
echo "### Reloading nginx ..."
docker-compose exec nginx nginx -s reload

10
mongo-init.js Normal file
View File

@ -0,0 +1,10 @@
db.createUser({
user: 'root',
pwd: 'secret',
roles: [
{
role: 'readWrite',
db: 'digital_store',
},
],
})

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "digital-store", "name": "digital-store",
"version": "1.0.0", "version": "1.1.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,4 +1,4 @@
module.exports = { module.exports = {
url: process.env.DATABASE_URI, url: process.env.DATABASE_URI,
name: process.env.DATABASE_NAME name: process.env.DATABASE_NAME,
} }

View File

@ -1,63 +1,77 @@
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
require('dotenv').config(); require('dotenv').config()
} }
const MongoClient = require('mongodb').MongoClient; const MongoClient = require('mongodb').MongoClient
const db_config = require('./database'); const db_config = require('./database')
const express = require('express'); const express = require('express')
const server = express(); const server = express()
const cors = require('cors'); const cors = require('cors')
const path = require('path'); const path = require('path')
const passport = require('passport'); const passport = require('passport')
const flash = require('express-flash'); const flash = require('express-flash')
const session = require('express-session'); const session = require('express-session')
const methodOverride = require('method-override'); const methodOverride = require('method-override')
const multer = require('multer'); const multer = require('multer')
const QiwiBillPaymentsAPI = require('@qiwi/bill-payments-node-js-sdk'); const QiwiBillPaymentsAPI = require('@qiwi/bill-payments-node-js-sdk')
const qiwiApi = new QiwiBillPaymentsAPI(process.env.QIWI_SECRET_KEY); const qiwiApi = new QiwiBillPaymentsAPI(process.env.QIWI_SECRET_KEY)
MongoClient.connect(db_config.url, {useNewUrlParser: true, useUnifiedTopology: true}) MongoClient.connect(db_config.url, {
.then((client) => { useNewUrlParser: true,
const db = client.db(db_config.name); useUnifiedTopology: true,
const storage = multer.diskStorage({ })
destination: 'dist/public/img/service-icons', .then((client) => {
filename: function(req, file, cb) { const db = client.db(db_config.name)
cb(null, `${file.fieldname}-${Date.now()}${path.extname(file.originalname)}`); const storage = multer.diskStorage({
} destination: 'dist/public/img/service-icons',
}); filename: function (req, file, cb) {
cb(
// Config null,
server.use(cors()); `${file.fieldname}-${Date.now()}${path.extname(file.originalname)}`
server.use(express.static(`${__dirname}/../dist/public`)); )
server.use(multer({storage: storage}).single('img')); },
server.use(express.urlencoded({ extended: false }));
server.use(express.json());
server.use(flash());
server.use(session({secret: require('crypto-random-string')({length: 64, type: 'base64'}), resave: true,saveUninitialized: false}));
server.use(passport.initialize());
server.use(passport.session());
server.use(methodOverride('_method'));
server.set('view engine', 'pug');
server.set('views', `${__dirname}/../dist/views`);
// Routes
require('./routes/main')(server, db); // маршруты сайта
require('./routes/admin')(server, db); // маршрут с формой авторизации админа
require('./routes/control-panel')(server, db); // маршруты с панелью управления
require('./routes/api')(server, db); // отдача данных клиенту
// Payments
require('./routes/success')(server, db, qiwiApi);
require('./routes/payments/qiwi.payment')(server, db, qiwiApi);
// 404 Redirect
server.use((req, res) => res.status(404).render('main/404'));
}) })
.catch((error) => {
server.get('*', (req, res) => { // Config
res.send('Произошла ошибка при запуске. Используйте команду "pm2 log" для того чтобы узнать ошибку.'); server.use(cors())
console.log(error); server.use(express.static(`${__dirname}/../dist/public`))
}); server.use(multer({ storage: storage }).single('img'))
server.use(express.urlencoded({ extended: false }))
server.use(express.json())
server.use(flash())
server.use(
session({
secret: require('crypto-random-string')({ length: 64, type: 'base64' }),
resave: true,
saveUninitialized: false,
})
)
server.use(passport.initialize())
server.use(passport.session())
server.use(methodOverride('_method'))
server.set('view engine', 'pug')
server.set('views', `${__dirname}/../dist/views`)
// Routes
require('./routes/main')(server, db) // маршруты сайта
require('./routes/admin')(server, db) // маршрут с формой авторизации админа
require('./routes/control-panel')(server, db) // маршруты с панелью управления
require('./routes/api')(server, db) // отдача данных клиенту
// Payments
require('./routes/success')(server, db, qiwiApi)
require('./routes/payments/qiwi.payment')(server, db, qiwiApi)
// 404 Redirect
server.use((req, res) => res.status(404).render('main/404'))
})
.catch((error) => {
server.get('*', (req, res) => {
res.send('Произошла ошибка.')
console.log(error)
}) })
.finally(server.listen(process.env.PORT || 3000, console.log('App is running...'))); })
.finally(
server.listen(process.env.PORT || 3000, console.log('App is running...'))
)