{"id":28390215,"url":"https://github.com/study-pm/electricity","last_synced_at":"2026-02-27T01:32:12.991Z","repository":{"id":295677415,"uuid":"990862952","full_name":"study-pm/electricity","owner":"study-pm","description":null,"archived":false,"fork":false,"pushed_at":"2025-06-05T19:30:20.000Z","size":107,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-05T20:29:28.934Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/study-pm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-26T18:55:26.000Z","updated_at":"2025-06-05T19:30:22.000Z","dependencies_parsed_at":"2025-05-26T20:48:58.153Z","dependency_job_id":null,"html_url":"https://github.com/study-pm/electricity","commit_stats":null,"previous_names":["study-pm/electricity"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/study-pm/electricity","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/study-pm%2Felectricity","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/study-pm%2Felectricity/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/study-pm%2Felectricity/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/study-pm%2Felectricity/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/study-pm","download_url":"https://codeload.github.com/study-pm/electricity/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/study-pm%2Felectricity/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29880742,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-26T23:51:21.483Z","status":"ssl_error","status_checked_at":"2026-02-26T23:50:46.793Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-31T03:39:30.668Z","updated_at":"2026-02-27T01:32:12.971Z","avatar_url":"https://github.com/study-pm.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Отчёт\n\n- [1 Техническое описание системы](#1-техническое-описание-системы)\n- [2 Инсталляция программного обеспечения компьютерных систем](#2-инсталляция-программного-обеспечения-компьютерных-систем)\n  - [2.1 Установка и настройка операционной системы Linux. Создание пользователя с соответствующими правами](#21-установка-и-настройка-операционной-системы-linux-создание-пользователя-с-соответствующими-правами)\n  - [2.2 Установка и настройка системы контроля версии git. Конфигурация пользователя](#22-установка-и-настройка-системы-контроля-версии-git-конфигурация-пользователя)\n  - [2.3 Установка и настройка среды разработки VSCode](#23-установка-и-настройка-среды-разработки-vscode)\n  - [2.4 Установка и настройка среды контейнеризации Docker и Docker Compose на рабочей машине](#24-установка-и-настройка-среды-контейнеризации-docker-и-docker-compose-на-рабочей-машине)\n  - [2.5 Установка кроссплатформенной среды для разработки клиентских приложений Node.js](#25-установка-кроссплатформенной-среды-для-разработки-клиентских-приложений-nodejs)\n- [3 Разработка и настройка базового системного и сервисного ПО](#3-разработка-и-настройка-базового-системного-и-сервисного-по)\n  - [3.1 Создание репозитория на GitHub и клонирование его локально](#31-создание-репозитория-на-github-и-клонирование-его-локально)\n  - [3.2 Создание новой ветки для разработки](#32-создание-новой-ветки-для-разработки)\n  - [3.3 Определение структуры проекта](#33-определение-структуры-проекта)\n- [4 Разработка структуры базы данных под управлением СУБД MySQL](#4-разработка-структуры-базы-данных-под-управлением-субд-mysql)\n  - [4.1 Описание сущностей](#41-описание-сущностей)\n  - [4.2 Концептуальная, логическая, физическая схема БД](#42-концептуальная-логическая-физическая-схема-бд)\n  - [4.3 Создание схемы БД и сценария заполнения данными](#43-создание-схемы-бд-и-сценария-заполнения-данными)\n- [5 Разработка функционала веб-сервера](#5-разработка-функционала-веб-сервера)\n  - [5.1 Инициализация проекта и установка зависимостей](#51-инициализация-проекта-и-установка-зависимостей)\n  - [5.2 Настройка подключения к базе данных](#52-настройка-подключения-к-базе-данных)\n  - [5.3 Определение моделей данных](#53-определение-моделей-данных)\n  - [5.4 Определение связей между моделями](#54-определение-связей-между-моделями)\n  - [5.5 Создание Express-приложения](#55-создание-express-приложения)\n  - [5.6 Отображение (рендеринг) и передача данных в EJS](#56-отображение-рендеринг-и-передача-данных-в-ejs)\n- [6 Разворачивание компонентов](#6-разворачивание-компонентов)\n  - [6.1 Разворачивание БД](#61-разворачивание-бд)\n  - [6.2 Разворачивание веб-приложения](#62-разворачивание-веб-приложения)\n- [7 Тестирование и отладка](#7-тестирование-и-отладка)\n  - [Ошибка подключения](#ошибка-подключения)\n  - [Ошибка привязки портов](#ошибка-привязки-портов)\n  - [Ошибка сборки Docker: отсутствует Dockerfile](#ошибка-сборки-docker-отсутствует-dockerfile)\n\n## 1 Техническое описание системы\n\n- Описание целей и задач практики\n- Краткий обзор используемых технологий (Linux, git, GitHub, nodejs, Docker, базы данных SQL, веб-технологии);\n- Обоснование выбора проекта и его значимости для формирования профессиональных компетенций.\n\nВеб-проект на стеке:\n- **ОС**: Linux\n- **СКВ**: git\n- **БД**: mySQL\n- **Сервер**: Node.js\n- **Веб-фреймворк**: Express;\n- **Шаблонизатор**: EJS;\n- **ORM**: Sequelize;\n- **Контейнеризация**: Docker\n- **Система автоматизации и развертывания**: Docker Compose.\n\n## 2 Инсталляция программного обеспечения компьютерных систем\n\n### 2.1 Установка и настройка операционной системы Linux. Создание пользователя с соответствующими правами\n\nИспользовать материал практических работ или любой другой доступный материал из сети.\n\n### 2.2 Установка и настройка системы контроля версии git. Конфигурация пользователя\n\nУстановка git:\n- [1.5 Getting Started - Installing Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)\n\nКонфигурирование git:\n- [1.6 Getting Started - First-Time Git Setup](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup)\n\n### 2.3 Установка и настройка среды разработки VSCode\n\nУстановка VSCode:\n- [Visual Studio Code on Linux](https://code.visualstudio.com/docs/setup/linux)\n\nУстановка плагинов:\n\n### 2.4 Установка и настройка среды контейнеризации Docker и Docker Compose на рабочей машине\n\nУстановка Docker и Docker Compose:\n- [Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/)\n- [Overview of installing Docker Compose](https://docs.docker.com/compose/install/)\n\nКонфигурирование Docker:\n- [Linux post-installation steps for Docker Engine](https://docs.docker.com/engine/install/linux-postinstall/)\n\n### 2.5 Установка кроссплатформенной среды для разработки клиентских приложений Node.js\n\n- [Download Node.js®](https://nodejs.org/en/download)\n\nВаирант установки через NVM (Node Version Manager):\n```sh\n# Download and install nvm:\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash\n\n# in lieu of restarting the shell\n\\. \"$HOME/.nvm/nvm.sh\"\n\n# Download and install Node.js:\nnvm install 22\n\n# Verify the Node.js version:\nnode -v # Should print \"v22.16.0\".\nnvm current # Should print \"v22.16.0\".\n\n# Verify npm version:\nnpm -v # Should print \"10.9.2\".\n```\n\nВариант установки с использованием Docker:\n```sh\n# Docker has specific installation instructions for each operating system.\n# Please refer to the official documentation at https://docker.com/get-started/\n\n# Pull the Node.js Docker image:\ndocker pull node:22-alpine\n\n# Create a Node.js container and start a Shell session:\ndocker run -it --rm --entrypoint sh node:22-alpine\n\n# Verify the Node.js version:\nnode -v # Should print \"v22.16.0\".\n\n# Verify npm version:\nnpm -v # Should print \"10.9.2\".\n```\n\n## 3 Разработка и настройка базового системного и сервисного ПО\n\n### 3.1 Создание репозитория на GitHub и клонирование его локально\n\nНа сайте https://github.com под своим аккаунтом создать новый проект. Задать ему имя, описание (опционально), выбрать тип проекта (публичный или приватный). Иницализировать репозиторий файлом *README.md*:\n\n- [x] Add a README file\n\nКлонировать проект через SSH-протокол (более безопасно):\n\n```sh\ngit clone git@study-pm.github.com:study-pm/electricity.git\n```\n\n### 3.2 Создание новой ветки для разработки\n\n1. Перейти в папку проекта:\n\n    ```sh\n    cd electricity\n    ```\n\n2. Проверить главную ветку проекта:\n\n    ```sh\n    git branch\n    ```\n\n3. Создать ветку для разработки базовой структуры и конфигурации проекта и сразу переключиться в нее:\n\n    ```sh\n    git checkout -b setup-project\n    ```\n\n4. Создать в удалённом репозитории ветку, соответствующую локальной и связать их:\n\n    ```sh\n    git push -u origin setup-project\n    ```\n\n### 3.3 Определение структуры проекта\n\n1. В каталоге проекта выполнить команды создания папок для базы данных и веб-сайта (бэкенда):\n\n    ```sh\n    mkdir db     # папка для файлов бд\n    mkdir site   # папка для веб-сайта\n    ```\n\n2. Создать файл списка игнорируемых файлов (*.gitignore*):\n\n    ```sh\n    touch .gitignore\n    ```\n\n3. Зафиксировать изменения:\n\n    ```sh\n    git add . # добавить все файлы в область отслеживания\n    git commit -m 'add gitignore file' # фиксация изменений с сообщением\n    ```\n\n4. Отправить изменения на удаленный сервер:\n\n    ```sh\n    git push\n    ```\n\n5. На удаленном сервере (https://github.com) создать новый запрос на принятие изменений (Pull Request), проверить выполненные изменения и сделать слияние ветки *setup-project* с главной веткой проекта (*main*).\n\n6. Удалить ветку *setup-project* в удаленном репозитории (кнопка *Delete Branch*).\n\n7. Переключиться в основную ветку и забрать слитые изменения из удаленного репозитория.\n\n    ```sh\n    git checkout main # переключение в главную ветку\n    git pull -p       # синхронизация с удаленным репозиторием\n    ```\n\n8. Удалить ветку *setup-project* локально:\n\n    ```sh\n    git branch -d setup-project # удаление ветки\n    git branch -a               # проверка (отображение всех веток)\n    ```\n\n## 4 Разработка структуры базы данных под управлением СУБД MySQL\n\n### 4.1 Описание сущностей\n\nБаза данных *OPK12A* содержит таблицы:\n\n- `Proekty` — проекты (id, номер, название)\n\n- `SmejnieOborudovaniya` — смежное оборудование (связано с проектами и типами оборудования)\n\n- `TipySmejnogoOborudovaniya` — типы оборудования\n\n- `Sklady` — склады (с расположением)\n\n- `Yacheiki` — ячейки на складах (связаны с оборудованием и складом)\n\n- `Zayavki` — заявки (с номером и датой)\n\n- `SostaviZayavok` — состав заявок (связь между ячейками и заявками)\n\nДанные таблицы и связи позволяют реализовать функционал отображения проектов, деталей проектов с оборудованием и складами, а также список заявок с их составом.\n\n### 4.2 Концептуальная, логическая, физическая схема БД\n\n### 4.3 Создание схемы БД и сценария заполнения данными\n\n1. Создать ветку для разработки структуры БД, переключиться в нее и связать с удаленной\n\n    ```sh\n    git checkout -b setup-db-scheme\n    git push -u origin setup-db-scheme\n    ```\n\n2. Скрипт по созданию БД разместить в папке */db/init.sql*, добавить в область отслеживаемых файлов и зафиксировать это изменение.\n\n    ```sh\n    git add db/\n    git commit -m 'add db schema and data backup'\n    ```\n\n3. Отправить изменения на удаленный сервер, сделать там слияние ветки с главной веткой проекта, переключиться в основную ветку, забрать изменения и удалить неактуальную ветку локально:\n\n    ```sh\n    git push                      # отправка изменений\n    git checkout main             # переключение в главную ветку\n    git pull -p                   # синхронизация с удаленным репозиторием\n    git branch -d setup-db-scheme # удаление ветки\n    ```\n\n## 5 Разработка функционала веб-сервера\nДля реализации backend части веб-сайта с использованием Node.js, Express и Sequelize на основе базы данных MySQL с приведённой схемой, необходимо выполнить несколько шагов. Основные моменты реализации:\n\n- Sequelize модели отражают структуру и связи базы данных.\n\n- Express маршруты получают данные из базы и передают их в EJS для рендеринга.\n\n- Следует использовать асинхронный код с async/await.\n\n- Реализовать основные страницы: проекты, детали проекта с оборудованием и складами, заявки.\n\n- Всё это можно запускать в Docker-контейнере с настройками из Docker Compose.\n\nТаким образом, backend будет полноценно обслуживать функционал, аналогичный исходным HTML-файлам, обеспечивая динамическую загрузку и отображение данных из MySQL с помощью Node.js, Express и Sequelize.\n\n### 5.1 Инициализация проекта и установка зависимостей\n\n1. Создать ветку для разработки бэкенда, переключиться в нее и связать с удаленной\n\n    ```sh\n    git checkout -b setup-backend\n    git push -u origin setup-backend\n    ```\n\n2. Инициализировать проект веб-сервера и установить необходимые зависимости:\n\n    ```sh\n    cd site     # переход в папку бэкенда\n    npm init -y # инициализация проекта с настройка по умолчанию\n    npm install express ejs sequelize mysql2 dotenv # установка зависимостей\n    ```\n\n    - `express` — веб-фреймворк для Node.js.\n\n    - `ejs` — шаблонизатор.\n\n    - `sequelize` — ORM для работы с MySQL.\n\n    - `mysql2` — драйвер MySQL.\n\n    - `dotenv` — для работы с переменными окружения.\n\n3. Добавить папку с библиотеками в список исключения:\n\n    ```sh\n    cd ../ # перейти в корневую папку проекта\n    echo  node_modules/ \u003e\u003e .gitignore # добавить в исключения\n    git add .gitignore # добавить файл для фиксации\n    git commit -m 'ignore libs' # зафиксировать изменения\n    ```\n\n4. Зафиксировать остальные изменения:\n\n    ```sh\n    git add site # добавить все остальные файлы\n    git commit -m 'install deps'\n    ```\n\n### 5.2 Настройка подключения к базе данных\n\n1. Создать каталог для конфигурационного файла:\n\n    ```sh\n    mkdir site/config\n    ```\n\n2. Создать конфигурационный файл:\n\n    ```sh\n    touch site/config/database.js\n    ```\n\n3. Открыть проект в VSCode:\n\n    ```sh\n    code -n .\n    ```\n\n4. Заполнить файл конфигурации следующим содержимым:\n\n    ```js\n    require('dotenv').config();\n    const { Sequelize } = require('sequelize');\n\n    const sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASSWORD, {\n    host: process.env.DB_HOST || 'localhost',\n    dialect: 'mysql',\n    logging: false,\n    define: {\n        timestamps: false, // в базе нет полей createdAt, updatedAt\n    },\n    });\n\n    module.exports = sequelize;\n    ```\n\n5. Создать файл с переменными окружения:\n\n    ```\n    cd site\n    touch .env\n    ```\n\n6. В *.env* указать параметры подключения:\n\n    ```js\n    DB_HOST=db\n    DB_USER=user\n    DB_PASSWORD=password\n    DB_NAME=OPK12A\n    ```\n\n4. Зафиксировать изменения:\n\n    ```sh\n    git add .env config/\n    git commit -m 'configure db connection'\n    ```\n\n### 5.3 Определение моделей данных\nОпределить модели, соответствующие таблицам базы данных, с правильными связями:\n\n- `Project` (*Proekty*)\n\n- `Equipment` (*SmejnieOborudovaniya*)\n\n- `EquipmentType` (*TipySmejnogoOborudovaniya*)\n\n- `Warehouse` (*Sklady*)\n\n- `Cell` (*Yacheiki*)\n\n- `Request` (*Zayavki*)\n\n- `RequestItem` (*SostaviZayavok*)\n\n1. Создать папку *models/*\n\n    ```sh\n    mkdir models\n    ```\n\n2. В этой папке создать файлы моделей, соответствующие таблицам базы данных:\n\n    ```sh\n    cd models\n    touch Project.js Equipment.js EquipmentType.js Warehouse.js Cell.js Request.js RequestItem.js\n    ```\n\n3. В VSCode заполнить файлы моделей.\n\n    *Project.js*:\n    ```js\n    const { DataTypes } = require('sequelize');\n    const sequelize = require('../config/database');\n\n    const Project = sequelize.define('Proekty', {\n    idProekty: {\n        type: DataTypes.INTEGER,\n        primaryKey: true,\n        autoIncrement: true,\n    },\n    NomerPr: {\n        type: DataTypes.STRING(45),\n        unique: true,\n        allowNull: false,\n    },\n    NazvaniePr: {\n        type: DataTypes.STRING(45),\n        unique: true,\n        allowNull: true,\n    },\n    }, {\n    tableName: 'Proekty',\n    });\n\n    module.exports = Project;\n    ```\n\n    *Equipment.js*:\n    ```js\n    const { DataTypes } = require('sequelize');\n    const sequelize = require('../config/database');\n    const Project = require('./Project');\n    const EquipmentType = require('./EquipmentType');\n\n    const Equipment = sequelize.define('SmejnieOborudovaniya', {\n    idSmejnieOborudovaniya: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },\n    SeriinyNomerSmejObor: { type: DataTypes.STRING(45), unique: true, allowNull: false },\n    TemperaturaVozduhaSmejObor: DataTypes.INTEGER,\n    TemperaturaVodySmejObor: DataTypes.INTEGER,\n    TokiSmejObor: DataTypes.INTEGER,\n    NapryajenieSmejObor: DataTypes.INTEGER,\n    TipySmejnogoOborudovaniya_idTipySmejnogoOborudovaniya: { type: DataTypes.INTEGER, allowNull: false },\n    Proekty_idProekty: { type: DataTypes.INTEGER, allowNull: false },\n    }, {\n    tableName: 'SmejnieOborudovaniya',\n    });\n\n    Equipment.belongsTo(Project, { foreignKey: 'Proekty_idProekty' });\n    Equipment.belongsTo(EquipmentType, { foreignKey: 'TipySmejnogoOborudovaniya_idTipySmejnogoOborudovaniya' });\n\n    module.exports = Equipment;\n    ```\n\n    *EquipmentType.js*:\n    ```js\n    const { DataTypes } = require('sequelize');\n    const sequelize = require('../config/database');\n\n    const EquipmentType = sequelize.define('TipySmejnogoOborudovaniya', {\n    idTipySmejnogoOborudovaniya: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },\n    NazvanieTipy: { type: DataTypes.STRING(45), unique: true, allowNull: false },\n    }, {\n    tableName: 'TipySmejnogoOborudovaniya',\n    });\n\n    module.exports = EquipmentType;\n    ```\n\n    *Warehouse.js*:\n    ```js\n    const { DataTypes } = require('sequelize');\n    const sequelize = require('../config/database');\n\n    const Warehouse = sequelize.define('Sklady', {\n    idSklady: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },\n    Raspolojenie: { type: DataTypes.STRING(45), unique: true, allowNull: false },\n    }, {\n    tableName: 'Sklady',\n    });\n\n    module.exports = Warehouse;\n    ```\n\n    *Cell.js*:\n    ```js\n    const { DataTypes } = require('sequelize');\n    const sequelize = require('../config/database');\n    const Warehouse = require('./Warehouse');\n    const Equipment = require('./Equipment');\n\n    const Cell = sequelize.define('Yacheiki', {\n    idYacheiki: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },\n    SeriinyNomerYacheikji: { type: DataTypes.STRING(45), unique: true, allowNull: false },\n    Stoimost: DataTypes.STRING(45),\n    Sklady_idSklady: { type: DataTypes.INTEGER, allowNull: false },\n    SmejnieOborudovaniya_idSmejnieOborudovaniya: { type: DataTypes.INTEGER, allowNull: false },\n    }, {\n    tableName: 'Yacheiki',\n    });\n\n    Cell.belongsTo(Warehouse, { foreignKey: 'Sklady_idSklady' });\n    Cell.belongsTo(Equipment, { foreignKey: 'SmejnieOborudovaniya_idSmejnieOborudovaniya' });\n\n    module.exports = Cell;\n    ```\n\n    *Request.js*:\n    ```js\n    const { DataTypes } = require('sequelize');\n    const sequelize = require('../config/database');\n\n    const Request = sequelize.define('Zayavki', {\n    idZayavki: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },\n    NomerZayavki: { type: DataTypes.STRING(45), unique: true, allowNull: true },\n    DataZayavki: { type: DataTypes.DATEONLY, allowNull: true },\n    }, {\n    tableName: 'Zayavki',\n    });\n\n    module.exports = Request;\n    ```\n\n    *RequestItem.js*:\n    ```js\n    const { DataTypes } = require('sequelize');\n    const sequelize = require('../config/database');\n    const Cell = require('./Cell');\n    const Request = require('./Request');\n\n    const RequestItem = sequelize.define('SostaviZayavok', {\n    idSostaviZayavok: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },\n    Yacheiki_idYacheiki: { type: DataTypes.INTEGER, allowNull: false },\n    Zayavki_idZayavki: { type: DataTypes.INTEGER, allowNull: false },\n    }, {\n    tableName: 'SostaviZayavok',\n    });\n\n    RequestItem.belongsTo(Cell, { foreignKey: 'Yacheiki_idYacheiki' });\n    RequestItem.belongsTo(Request, { foreignKey: 'Zayavki_idZayavki' });\n\n    module.exports = RequestItem;\n    ```\n\n4. Зафиксировать изменения:\n\n    ```sh\n    git add .\n    git commit -m 'define data models'\n    ```\n\n### 5.4 Определение связей между моделями\n\n1. Создать файл *index.js* в папке *models*\n\n    ```sh\n    touch index.js\n    ```\n\n2. В этом файле собираются все модели и определяются связи между ними:\n\n    ```js\n    const sequelize = require('../config/database');\n\n    const Project = require('./Project');\n    const Equipment = require('./Equipment');\n    const EquipmentType = require('./EquipmentType');\n    const Warehouse = require('./Warehouse');\n    const Cell = require('./Cell');\n    const Request = require('./Request');\n    const RequestItem = require('./RequestItem');\n\n    // Связи (если не заданы в моделях)\n    Project.hasMany(Equipment, { foreignKey: 'Proekty_idProekty' });\n    Equipment.belongsTo(Project, { foreignKey: 'Proekty_idProekty' });\n\n    Equipment.belongsTo(EquipmentType, { foreignKey: 'TipySmejnogoOborudovaniya_idTipySmejnogoOborudovaniya' });\n    EquipmentType.hasMany(Equipment, { foreignKey: 'TipySmejnogoOborudovaniya_idTipySmejnogoOborudovaniya' });\n\n    Warehouse.hasMany(Cell, { foreignKey: 'Sklady_idSklady' });\n    Cell.belongsTo(Warehouse, { foreignKey: 'Sklady_idSklady' });\n\n    Equipment.hasMany(Cell, { foreignKey: 'SmejnieOborudovaniya_idSmejnieOborudovaniya' });\n    Cell.belongsTo(Equipment, { foreignKey: 'SmejnieOborudovaniya_idSmejnieOborudovaniya' });\n\n    Request.hasMany(RequestItem, { foreignKey: 'Zayavki_idZayavki' });\n    RequestItem.belongsTo(Request, { foreignKey: 'Zayavki_idZayavki' });\n\n    Cell.hasMany(RequestItem, { foreignKey: 'Yacheiki_idYacheiki' });\n    RequestItem.belongsTo(Cell, { foreignKey: 'Yacheiki_idYacheiki' });\n\n    module.exports = {\n    sequelize,\n    Project,\n    Equipment,\n    EquipmentType,\n    Warehouse,\n    Cell,\n    Request,\n    RequestItem,\n    };\n    ```\n\n3. Зафиксировать изменения:\n\n    ```sh\n    git add .\n    git commit -m 'define object mapping'\n    ```\n\n### 5.5 Создание Express-приложения\nНеобходимо создать приложение, запускающее сервер и выполняющее базовую маршрутизацию. Маршруты Express:\n\n- */projects* — список проектов (для главной страницы)\n\n- */projects/:id* — детали проекта с оборудованием и складами\n\n- */requests* — список заявок с количеством позиций и общей стоимостью\n\nВ каждом маршруте получаются данные из базы через Sequelize и рендерятся EJS-шаблоны.\n\n1. Создать файл веб-приложения в папке *site*:\n\n    ```sh\n    cd ../\n    touch app.js\n    ```\n\n2. Открыть файл в VSCode и заполнить следующим содержимым:\n\n    ```js\n    require('dotenv').config();\n    const express = require('express');\n    const path = require('path');\n    const { sequelize, Project, Equipment, EquipmentType, Warehouse, Cell, Request, RequestItem } = require('./models');\n\n    const app = express();\n\n    app.set('view engine', 'ejs');\n    app.set('views', path.join(__dirname, 'views'));\n\n    app.use(express.static(path.join(__dirname, 'public')));\n\n    // Главная страница: список проектов\n    app.get('/', async (req, res) =\u003e {\n    const projects = await Project.findAll();\n    res.render('index', { projects }); // projects — массив объектов проектов\n    });\n\n    // Детали проекта\n    app.get('/projects/:id', async (req, res) =\u003e {\n    const projectId = req.params.id;\n\n    // Получаем проект\n    const project = await Project.findByPk(projectId);\n    if (!project) return res.status(404).send('Проект не найден');\n\n    // Получаем оборудование проекта с типом\n    const equipment = await Equipment.findAll({\n        where: { Proekty_idProekty: projectId },\n        include: [EquipmentType],\n    });\n\n    // Получаем склады и ячейки с оборудованием для проекта\n    const warehouses = await Warehouse.findAll({\n        include: {\n        model: Cell,\n        include: {\n            model: Equipment,\n            where: { Proekty_idProekty: projectId },\n            required: false,\n        },\n        },\n    });\n\n    res.render('project', { project, equipment, warehouses });\n    });\n\n    // Список заявок\n    app.get('/requests', async (req, res) =\u003e {\n    // Получаем заявки с количеством позиций и общей стоимостью\n    const requests = await Request.findAll({\n        include: {\n        model: RequestItem,\n        include: {\n            model: Cell,\n        },\n        },\n    });\n\n    // Подсчёт количества позиций и общей стоимости для каждой заявки\n    const requestsData = requests.map(req =\u003e {\n        const positionsCount = req.SostaviZayavoks.length;\n        const totalCost = req.SostaviZayavoks.reduce((sum, item) =\u003e {\n        const cost = parseFloat(item.Yacheiki.Stoimost) || 0;\n        return sum + cost;\n        }, 0);\n        return {\n        idZayavki: req.idZayavki,\n        NomerZayavki: req.NomerZayavki,\n        DataZayavki: req.DataZayavki,\n        positionsCount,\n        totalCost,\n        };\n    });\n\n    res.render('requests', { requests: requestsData });\n    });\n\n    // Запуск сервера и проверка подключения к БД\n    const PORT = process.env.PORT || 3000;\n    sequelize.authenticate()\n    .then(() =\u003e {\n        console.log('Подключение к базе данных успешно');\n        app.listen(PORT, () =\u003e console.log(`Сервер запущен на порту ${PORT}`));\n    })\n    .catch(err =\u003e {\n        console.error('Ошибка подключения к базе данных:', err);\n    });\n    ```\n\n3. Зафиксировать изменения:\n\n    ```sh\n    git add .\n    git commit -m 'add backend application'\n    ```\n\n### 5.6 Отображение (рендеринг) и передача данных в EJS\nВ маршрутах Express (в *app.js*) после получения данных из базы через Sequelize вызывается метод `res.render()`, который рендерит EJS-шаблон и передаёт в него объект с данными.\n\n- В *views/index.ejs* выводится список проектов с ссылками на детали.\n\n- В *views/project.ejs* выводится основная информацию о проекте, таблица оборудования и информация о складах и ячейках.\n\n- В *views/requests.ejs* выводится таблица заявок с их данными.\n\nПример для главной страницы со списком проектов:\n```js\napp.get('/', async (req, res) =\u003e {\n  const projects = await Project.findAll();\n  res.render('index', { projects }); // projects — массив объектов проектов\n});\n```\n\nВ шаблоне *views/index.ejs* можно обращаться к переменной `projects`.\n\nКлючевые особенности:\n- Данные из базы передаются в шаблон как объекты или массивы.\n\n- В EJS происходит динамическое построение HTML с учётом содержимого.\n\n- Это позволяет создавать страницы, аналогичные исходным HTML-файлам, но с динамическим наполнением из MySQL через Sequelize и Express.\n\nОсновные приёмы работы с EJS\n- Вставка переменных: `\u003c%= variable %\u003e` — вывод с экранированием.\n\n- Логика: `\u003c% if (...) { %\u003e ... \u003c% } %\u003e`, циклы `\u003c% array.forEach(...) { %\u003e ... \u003c% }) %\u003e`.\n\n- Проверка наличия данных для вывода сообщений об отсутствии.\n\n- Вложенные циклы для сложных структур (например, склады → ячейки → оборудование).\n\n\nТаким образом реализуется динамическое отображение данных, полученных из базы, с помощью шаблонизатора EJS, что обеспечивает полноценный функционал сайта по управлению проектами, оборудованием и заявками. Алгоритм действий:\n\n1. Создать папку для хранения шаблонов:\n\n    ```sh\n    mkdir views\n    ```\n\n2. Создать файлы шаблонов:\n\n    ```sh\n    cd views\n    touch index.ejs project.ejs requests.ejs\n    ```\n\n3. Открыть и заполнить файлы шаблонов в VSCode.\n\n    *views/index.ejs — список проектов*:\n    ```js\n    \u003c!DOCTYPE html\u003e\n    \u003chtml lang=\"ru\"\u003e\n    \u003chead\u003e\n    \u003cmeta charset=\"UTF-8\" /\u003e\n    \u003ctitle\u003eПроекты\u003c/title\u003e\n    \u003clink rel=\"stylesheet\" href=\"/css/style.css\" /\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n    \u003cheader\u003e\n        \u003ch1\u003eПроекты\u003c/h1\u003e\n        \u003cnav\u003e\n        \u003ca href=\"/\"\u003eПроекты\u003c/a\u003e\n        \u003ca href=\"/requests\"\u003eЗаявки\u003c/a\u003e\n        \u003c/nav\u003e\n    \u003c/header\u003e\n\n    \u003cmain\u003e\n        \u003cdiv id=\"projects-list\" class=\"grid-container\"\u003e\n        \u003c% if (projects.length === 0) { %\u003e\n            \u003cp\u003eПроекты не найдены.\u003c/p\u003e\n        \u003c% } else { %\u003e\n            \u003c% projects.forEach(project =\u003e { %\u003e\n            \u003cdiv class=\"project-item\"\u003e\n                \u003ch2\u003e\u003ca href=\"/projects/\u003c%= project.idProekty %\u003e\"\u003e\u003c%= project.NazvaniePr %\u003e\u003c/a\u003e\u003c/h2\u003e\n                \u003cp\u003eНомер проекта: \u003c%= project.NomerPr %\u003e\u003c/p\u003e\n            \u003c/div\u003e\n            \u003c% }) %\u003e\n        \u003c% } %\u003e\n        \u003c/div\u003e\n    \u003c/main\u003e\n\n    \u003cfooter\u003e\n        \u003cp\u003eСистема управления проектами © 2025\u003c/p\u003e\n    \u003c/footer\u003e\n    \u003c/body\u003e\n    \u003c/html\u003e\n    ```\n\n    - Цикл `\u003c% projects.forEach(...) %\u003e` выводит каждый проект.\n\n    - Ссылка ведёт на страницу деталей проекта `/projects/:id`.\n\n    *views/project.ejs — детали проекта*:\n    ```js\n    \u003c!DOCTYPE html\u003e\n    \u003chtml lang=\"ru\"\u003e\n    \u003chead\u003e\n    \u003cmeta charset=\"UTF-8\" /\u003e\n    \u003ctitle\u003eДетали проекта: \u003c%= project.NazvaniePr %\u003e\u003c/title\u003e\n    \u003clink rel=\"stylesheet\" href=\"/css/style.css\" /\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n    \u003cheader\u003e\n        \u003ch1 id=\"project-title\"\u003e\u003c%= project.NazvaniePr %\u003e\u003c/h1\u003e\n        \u003cnav\u003e\n        \u003ca href=\"/\"\u003eНазад к проектам\u003c/a\u003e\n        \u003ca href=\"/requests\"\u003eЗаявки\u003c/a\u003e\n        \u003c/nav\u003e\n    \u003c/header\u003e\n\n    \u003cmain\u003e\n        \u003csection id=\"project-info\"\u003e\n        \u003ch2\u003eОсновная информация\u003c/h2\u003e\n        \u003cdiv class=\"info-grid\"\u003e\n            \u003cdiv\u003eНомер проекта:\u003c/div\u003e\n            \u003cdiv\u003e\u003c%= project.NomerPr %\u003e\u003c/div\u003e\n            \u003cdiv\u003eНазвание:\u003c/div\u003e\n            \u003cdiv\u003e\u003c%= project.NazvaniePr %\u003e\u003c/div\u003e\n        \u003c/div\u003e\n        \u003c/section\u003e\n\n        \u003csection id=\"equipment-section\"\u003e\n        \u003ch2\u003eОборудование\u003c/h2\u003e\n        \u003ctable id=\"equipment-table\"\u003e\n            \u003cthead\u003e\n            \u003ctr\u003e\n                \u003cth\u003eТип\u003c/th\u003e\n                \u003cth\u003eСерийный номер\u003c/th\u003e\n                \u003cth\u003eТемп. воздуха\u003c/th\u003e\n                \u003cth\u003eТемп. воды\u003c/th\u003e\n                \u003cth\u003eТок\u003c/th\u003e\n                \u003cth\u003eНапряжение\u003c/th\u003e\n            \u003c/tr\u003e\n            \u003c/thead\u003e\n            \u003ctbody\u003e\n            \u003c% if (equipment.length === 0) { %\u003e\n                \u003ctr\u003e\u003ctd colspan=\"6\"\u003eОборудование не найдено\u003c/td\u003e\u003c/tr\u003e\n            \u003c% } else { %\u003e\n                \u003c% equipment.forEach(eq =\u003e { %\u003e\n                \u003ctr\u003e\n                    \u003ctd\u003e\u003c%= eq.TipySmejnogoOborudovaniya.NazvanieTipy %\u003e\u003c/td\u003e\n                    \u003ctd\u003e\u003c%= eq.SeriinyNomerSmejObor %\u003e\u003c/td\u003e\n                    \u003ctd\u003e\u003c%= eq.TemperaturaVozduhaSmejObor || '-' %\u003e\u003c/td\u003e\n                    \u003ctd\u003e\u003c%= eq.TemperaturaVodySmejObor || '-' %\u003e\u003c/td\u003e\n                    \u003ctd\u003e\u003c%= eq.TokiSmejObor || '-' %\u003e\u003c/td\u003e\n                    \u003ctd\u003e\u003c%= eq.NapryajenieSmejObor || '-' %\u003e\u003c/td\u003e\n                \u003c/tr\u003e\n                \u003c% }) %\u003e\n            \u003c% } %\u003e\n            \u003c/tbody\u003e\n        \u003c/table\u003e\n        \u003c/section\u003e\n\n        \u003csection id=\"warehouse-section\"\u003e\n        \u003ch2\u003eСклады\u003c/h2\u003e\n        \u003c% if (warehouses.length === 0) { %\u003e\n            \u003cp\u003eСклады не найдены.\u003c/p\u003e\n        \u003c% } else { %\u003e\n            \u003c% warehouses.forEach(warehouse =\u003e { %\u003e\n            \u003cdiv class=\"warehouse\"\u003e\n                \u003ch3\u003e\u003c%= warehouse.Raspolojenie %\u003e\u003c/h3\u003e\n                \u003c% if (warehouse.Yacheikis.length === 0) { %\u003e\n                \u003cp\u003eЯчейки отсутствуют\u003c/p\u003e\n                \u003c% } else { %\u003e\n                \u003cul\u003e\n                    \u003c% warehouse.Yacheikis.forEach(cell =\u003e { %\u003e\n                    \u003c% if(cell.SmejnieOborudovaniya \u0026\u0026 cell.SmejnieOborudovaniya.Proekty_idProekty === project.idProekty) { %\u003e\n                        \u003cli\u003e\n                        Ячейка: \u003c%= cell.SeriinyNomerYacheikji %\u003e, Стоимость: \u003c%= cell.Stoimost || 'не указана' %\u003e, Оборудование: \u003c%= cell.SmejnieOborudovaniya.SeriinyNomerSmejObor %\u003e\n                        \u003c/li\u003e\n                    \u003c% } %\u003e\n                    \u003c% }) %\u003e\n                \u003c/ul\u003e\n                \u003c% } %\u003e\n            \u003c/div\u003e\n            \u003c% }) %\u003e\n        \u003c% } %\u003e\n        \u003c/section\u003e\n    \u003c/main\u003e\n\n    \u003cfooter\u003e\n        \u003cp\u003eСистема управления проектами © 2025\u003c/p\u003e\n    \u003c/footer\u003e\n    \u003c/body\u003e\n    \u003c/html\u003e\n    ```\n\n    - Используются вложенные циклы для вывода складов и ячеек.\n\n    - Проверяется наличие данных, чтобы избежать пустых таблиц.\n\n    *views/requests.ejs — список заявок*:\n    ```js\n    \u003c!DOCTYPE html\u003e\n    \u003chtml lang=\"ru\"\u003e\n    \u003chead\u003e\n    \u003cmeta charset=\"UTF-8\" /\u003e\n    \u003ctitle\u003eЗаявки\u003c/title\u003e\n    \u003clink rel=\"stylesheet\" href=\"/css/style.css\" /\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n    \u003cheader\u003e\n        \u003ch1\u003eЗаявки\u003c/h1\u003e\n        \u003cnav\u003e\n        \u003ca href=\"/\"\u003eПроекты\u003c/a\u003e\n        \u003ca href=\"/requests\"\u003eЗаявки\u003c/a\u003e\n        \u003c/nav\u003e\n    \u003c/header\u003e\n\n    \u003cmain\u003e\n        \u003ctable id=\"requests-table\"\u003e\n        \u003cthead\u003e\n            \u003ctr\u003e\n            \u003cth\u003eНомер заявки\u003c/th\u003e\n            \u003cth\u003eДата\u003c/th\u003e\n            \u003cth\u003eКоличество позиций\u003c/th\u003e\n            \u003cth\u003eОбщая стоимость\u003c/th\u003e\n            \u003cth\u003eДействия\u003c/th\u003e\n            \u003c/tr\u003e\n        \u003c/thead\u003e\n        \u003ctbody\u003e\n            \u003c% if (requests.length === 0) { %\u003e\n            \u003ctr\u003e\u003ctd colspan=\"5\"\u003eЗаявки не найдены\u003c/td\u003e\u003c/tr\u003e\n            \u003c% } else { %\u003e\n            \u003c% requests.forEach(request =\u003e { %\u003e\n                \u003ctr\u003e\n                \u003ctd\u003e\u003c%= request.NomerZayavki || '-' %\u003e\u003c/td\u003e\n                \u003ctd\u003e\u003c%= request.DataZayavki || '-' %\u003e\u003c/td\u003e\n                \u003ctd\u003e\u003c%= request.positionsCount %\u003e\u003c/td\u003e\n                \u003ctd\u003e\u003c%= request.totalCost.toFixed(2) %\u003e\u003c/td\u003e\n                \u003ctd\u003e\n                    \u003ca href=\"/requests/\u003c%= request.idZayavki %\u003e\"\u003eПросмотр\u003c/a\u003e\n                \u003c/td\u003e\n                \u003c/tr\u003e\n            \u003c% }) %\u003e\n            \u003c% } %\u003e\n        \u003c/tbody\u003e\n        \u003c/table\u003e\n    \u003c/main\u003e\n\n    \u003cfooter\u003e\n        \u003cp\u003eСистема управления проектами © 2024\u003c/p\u003e\n    \u003c/footer\u003e\n    \u003c/body\u003e\n    \u003c/html\u003e\n\n    ```\n\n    - Выводятся подсчитанные из контроллера количество позиций и общая стоимость.\n\n4. Зафиксировать изменения:\n\n    ```sh\n    git add .\n    git commit -m 'add view templates'\n    ```\n\n5. Отправить изменения на удаленный сервер, сделать там слияние ветки с главной веткой проекта, переключиться в основную ветку, забрать изменения и удалить неактуальную ветку локально:\n\n    ```sh\n    git push                      # отправка изменений\n    git checkout main             # переключение в главную ветку\n    git pull -p                   # синхронизация с удаленным репозиторием\n    git branch -d setup-backend   # удаление ветки\n    ```\n\n## 6 Разворачивание компонентов\nДля развертывания приложения используется среда виртуализации Docker, причем для удобства оркестрации контейнерами задействуется Docker Compose с двумя сервисами: MySQL и Node.js приложением.\n\n### 6.1 Разворачивание БД\nНеобходимо развернуть БД MySQL с данными из скрипта *db/init.sql* в Docker-контейнере, чтобы данные сохранялись в примонтированном томе (`/db/data:/var/lib/mysql`), определённом в *docker-compose.yml*.\n\nКлючевые особенности:\n- Все данные и структура БД будут храниться в volume `data`.\n\n- При повторных перезапусках контейнера БД не будет теряться.\n\n- Для повторной инициализации нужно просто удалить volume.\n\nТаким образом, всё, что здесь нужно сделать — положить скрипт в папку, примонтировать его через *docker-compose.yml* в `/docker-entrypoint-initdb.d/,` и при первом запуске контейнера MySQL БД будет создана и заполнена, а все данные будут храниться в volume db/data. Если потребуется несколько файлов или дамп, то можно положить в папку *db/* несколько файлов, все они будут выполнены по алфавиту.\n\n1. Создать ветку для развертывания приложения, переключиться в нее и связать с удаленной\n\n    ```sh\n    git checkout -b app-deploy\n    git push -u origin app-deploy\n    ```\n\n2. Создать в корне проекта файл *docker-compose.yml*:\n\n    ```sh\n    touch docker-compose.yml\n    ```\n\n3. Открыть файл в VSCode и заполнить его следующим содержимым:\n\n    ```yaml\n    services:\n        db:\n            image: mysql:8\n            environment:\n                MYSQL_ROOT_PASSWORD: rootpassword\n                MYSQL_DATABASE: OPK12A\n                MYSQL_USER: user\n                MYSQL_PASSWORD: password\n            ports:\n                - \"3306:3306\"\n            volumes:\n                - ./db/data:/var/lib/mysql\n                - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro\n\n    volumes:\n        db_data:\n    ```\n\n    Пояснения:\n\n    - `./db_data:/var/lib/mysql` — все данные БД будут храниться в локально в папке проекта (и, соответственно, будут доступны в пристыкованном к ней томе), даже после перезапуска контейнера.\n\n    - `./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro` — при первом запуске контейнера MySQL выполнит этот скрипт, создаст структуру и наполнит БД начальными данными.\n\n    Как работает инициализация:\n    - *init.sql* будет выполнен только при первом запуске, когда volume `db_data` ещё пустой.\n\n    - Если volume уже существует (БД уже была создана), скрипт повторно не выполнится.\n\n    - Если нужно пересоздать БД — удалить `volume` командой:\n\n        ```sh\n        docker compose down -v\n        ```\n\n    Это удалит volume и при следующем запуске БД инициализируется заново.\n\n    Флаг `-v` в команде `docker compose down -v` означает, что вместе с остановкой и удалением контейнеров будут удалены также все тома (volumes), которые были созданы для этих контейнеров. Это позволяет полностью очистить все данные, связанные с сервисами, включая данные, сохранённые в томах. Иными словами, команда остановит и удалит контейнеры, сети и тома, определённые в *docker-compose.yml* файле. Без флага `-v` тома сохраняются, чтобы данные не потерялись при повторном запуске контейнеров. Следует использовать `-v`, если необходимо полностью удалить все связанные данные.\n\n4. Запуск контейнера с БД:\n\n    ```sh\n    docker compose up -d # Вариант 1\n    docker-compose up -d # Вариант 2\n    ```\n\n    Разница между `docker-compose` и `docker compose` заключается в их происхождении, реализации и интеграции с Docker CLI:\n\n    - `docker-compose` — это оригинальное автономное приложение, написанное на Python, для оркестрации многоконтейнерных приложений Docker. Оно использовалось долгое время как отдельный инструмент и устанавливалось отдельно от Docker. Команды выглядели как `docker-compose up`, `docker-compose down` и т.д.\n\n    - `docker compose` (с пробелом) — это более новая версия Docker Compose, переписанная на Go и интегрированная непосредственно в основной Docker CLI. Она является частью ветки Compose v2 и поставляется вместе с Docker (например, в Docker Desktop). Команды теперь вызываются через основной Docker CLI, например, `docker compose up`. Это обеспечивает единообразие флагов и опций с другими командами Docker, улучшенную поддержку и обновления\n\n    Таким образом, `docker compose` — это современный, более интегрированный и поддерживаемый инструмент, который заменяет `docker-compose`. Рекомендуется переходить на использование `docker compose` для новых проектов и обновлять скрипты, заменяя дефис на пробел в командах.\n\n    В результате выполнения команды Docker скачает все необходимые образы и запустит сервис. Консольный вывод:\n    ```\n    [+] Running 11/11\n    ✔ db Pulled                                                                                                                                                                                                                            13.9s \n    ✔ c2eb5d06bfea Pull complete                                                                                                                                                                                                          2.8s \n    ✔ 253ce0d09858 Pull complete                                                                                                                                                                                                          2.8s \n    ✔ 80d03d2c4741 Pull complete                                                                                                                                                                                                          2.8s \n    ✔ e4440634f2d6 Pull complete                                                                                                                                                                                                          3.0s \n    ✔ 398e77186e87 Pull complete                                                                                                                                                                                                          3.0s \n    ✔ ff3f8be14317 Pull complete                                                                                                                                                                                                          3.0s \n    ✔ 7c52b30fb2be Pull complete                                                                                                                                                                                                          4.6s \n    ✔ eb0fdd6a2898 Pull complete                                                                                                                                                                                                          4.6s \n    ✔ 61a34481c5ea Pull complete                                                                                                                                                                                                         11.9s \n    ✔ 41f706ab26e6 Pull complete                                                                                                                                                                                                         12.0s \n    [+] Running 3/3\n    ✔ Network electricity_default   Created                                                                                                                                                                                                 0.2s \n    ✔ Volume \"electricity_db_data\"  Created                                                                                                                                                                                                 0.0s \n    ✔ Container electricity-db-1    Started    \n    ```\n\n5. Проверка правильности развертывания БД.\n\n    Выполняется проверка того, что БД развернулась и данные на месте (получить содержимое базы данных).\n\n    Через `docker compose` подключиться к MySQL и выполнить запросы:\n    ```sh\n    docker compose exec db mysql -uuser -ppassword -e \"SHOW DATABASES;\"\n    ```\n\n    Если контейнеры еще не успели полноценно развернуться, то можно получить следующую ошибку в консоли:\n    ```\n    ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)\n    ```\n\n    или такую:\n    ```\n    ERROR 1045 (28000): Access denied for user 'user'@'localhost' (using password: YES)\n    ```\n\n    В этом случае нужно немного подождать и повторить команду. В случае успеха должен быть следующий консольный вывод:\n    ```\n    mysql: [Warning] Using a password on the command line interface can be insecure.\n    +--------------------+\n    | Database           |\n    +--------------------+\n    | OPK12A             |\n    | information_schema |\n    | performance_schema |\n    +--------------------+\n    ```\n\n    Поскольку база инициализирована, то можно увидить среди баз данных *OPK12A*. \n\n    Показать таблицы базы:\n    ```sh\n    docker compose exec db mysql -uuser -ppassword -e \"USE OPK12A; SHOW TABLES;\"\n    ```\n\n    Консольный вывод:\n    ```\n    mysql: [Warning] Using a password on the command line interface can be insecure.\n    +---------------------------+\n    | Tables_in_OPK12A          |\n    +---------------------------+\n    | Proekty                   |\n    | Sklady                    |\n    | SmejnieOborudovaniya      |\n    | SostaviZayavok            |\n    | TipySmejnogoOborudovaniya |\n    | Yacheiki                  |\n    | Zayavki                   |\n    +---------------------------+\n    ```\n\n    Показать данные (например, из таблицы проектов):\n    ```sh\n    docker compose exec db mysql -uuser -ppassword -e \"USE OPK12A; SELECT * FROM Proekty;\"\n    ```\n\n    Консольный вывод:\n    ```\n    +-----------+---------+----------------+\n    | idProekty | NomerPr | NazvaniePr     |\n    +-----------+---------+----------------+\n    |         1 | 52300   | Арктика |\n    |         2 | 53201   | Ермак     |\n    |         3 | 5543    | Ясень     |\n    |         4 | 5698-б | Бальзам |\n    |         5 | 2045    | Клубень |\n    |         6 | 21445   | Крюйс     |\n    +-----------+---------+----------------+\n    ```\n\n    Проверка логов контейнера:\n    ```sh\n    docker compose logs db\n    ```\n\n    Посмотр логов MySQL даст возможность  убедиться, что скрипт был выполнен без ошибок.\n\n6. Файлы базы данных необходимо игнорировать:\n\n    ```sh\n    echo /db/data/ \u003e\u003e .gitignore\n    git add .gitignore \u0026\u0026 git commit -m 'ignore db files'\n    ```\n\n7. Зафиксировать изменения:\n\n    ```sh\n    git add . \u0026\u0026 git commit -m 'setup db service'\n    ```\n\n### 6.2 Разворачивание веб-приложения\nНеобходимо упаковать Node.js-приложение в единый контейнер, который можно запустить на любом сервере, где есть Docker. Для этого необходимо создать Dockerfile, который описывает всё, что нужно для работы приложения (код, зависимости, настройки). Docker сам скачает Node.js, установит зависимости и подготовит среду. *Dockerfile* позволяет запускать приложение как сервис в `docker compose.yml` — вместе с базой данных и другими сервисами. Контейнер работает изолированно от основной системы. Всё, что делает приложение, не влияет на хостовую ОС и наоборот.\n\n1. Создать в корне проекта файл *Dockerfile*:\n\n    ```sh\n    touch Dockerfile\n    ```\n\n2. Открыть файл в VSCode и заполнить его следующим содержимым:\n\n    ```dockerfile\n    FROM node:18-alpine\n    # Базовый образ: минимальная версия Linux с Node.js 18.\n    # Это экономит место и ускоряет загрузку.\n\n    WORKDIR /app\n    # Рабочая директория внутри контейнера. Все дальнейшие команды будут выполняться здесь.\n\n    COPY site/package*.json ./\n    # Копируются файлы зависимостей (package.json и package-lock.json).\n\n    RUN npm install\n    # Устанавливаются все npm-зависимости.\n\n    COPY site/. .\n    # Копируется весь исходный код приложения внутрь контейнера.\n\n    EXPOSE 3000\n    # Оповещает Docker, что приложение слушает порт 3000 (стандарт для Express).\n\n    CMD [\"node\", \"app.js\"]\n    # Запускает приложение командой node app.js при старте контейнера.\n    ```\n\n    В данном файле учтено, что основной код лежит в подкаталоге, а не в корне (*Dockerfile* копирует файлы из папки *site*).\n\n3. Открыть файл *docker-compose.yml* в VSCode и добавить сервис веб-приложения на бэкенде:\n\n    ```yml\n    services:\n    db:\n        image: mysql:8\n        environment:\n            MYSQL_ROOT_PASSWORD: rootpassword\n            MYSQL_DATABASE: OPK12A\n            MYSQL_USER: user\n            MYSQL_PASSWORD: password\n        ports:\n            - \"3306:3306\"\n        volumes:\n            - ./db/data:/var/lib/mysql\n            - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro\n\n    app:\n        build:\n            context: ./site\n        ports:\n            - \"3000:3000\"\n        environment:\n            DB_HOST: db\n            DB_USER: user\n            DB_PASSWORD: password\n            DB_NAME: OPK12A\n        depends_on:\n            - db\n        volumes:\n            - ./site:/app\n\n    volumes:\n        db_data:\n\n    ```\n\n    Здесь учитывается контекст сборки: теперь это папка *site*, а не корень проекта. Особенности:\n\n    - Если используется volume `./site:/app`, то при запуске контейнера содержимое папки *site* будет доступно внутри контейнера по пути */app*, и запуск `node app.js` будет работать.\n\n    - Если используются оба метода (и копирование в *Dockerfile*, и volume), то volume \"перекроет\" то, что было скопировано на этапе сборки.\n\n\n4. Запустить проект:\n\n    ```sh\n    docker compose up -d\n    ```\n\n5. Запустить веб-обозреватель и проследователь по адресу http://localhost:3000/. Должна открыться главная страница проекта.\n\n6. Зафиксировать изменения:\n\n    ```sh\n    git add . \u0026\u0026 git commit -m 'setup web app service'\n    ```\n\n7. Отправить изменения на удаленный сервер, сделать там слияние ветки с главной веткой проекта, переключиться в основную ветку, забрать изменения и удалить неактуальную ветку локально:\n\n    ```sh\n    git push                      # отправка изменений\n    git checkout main             # переключение в главную ветку\n    git pull -p                   # синхронизация с удаленным репозиторием\n    git branch -d app-deploy      # удаление ветки\n    ```\n\n## 7 Тестирование и отладка\n\n### Ошибка подключения\nПри попытке запуске проекта можно столкнуться с различными ошибками. Например, по какой-либо причине тот или иной контейнер может быть аварийно остановлен. Наличие работающих контейнерв можно проверить командой `docker ps`:\n\n```sh\ndocker ps\nCONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS                                                  NAMES\na7d10b393d7e   mysql:8   \"docker-entrypoint.s…\"   4 seconds ago   Up 4 seconds   0.0.0.0:3306-\u003e3306/tcp, :::3306-\u003e3306/tcp, 33060/tcp   electricity-db-1\n```\n\nВ данном случае отсутствует контейнер веб-приложения. Если проверить все контейнеры, то можно убедиться в том, что он преждевременно завершил свою работу:\n```sh\ndocker ps -a\nCONTAINER ID   IMAGE             COMMAND                  CREATED              STATUS                          PORTS                                                  NAMES\nd737f255446f   electricity-app   \"docker-entrypoint.s…\"   About a minute ago   Exited (0) About a minute ago                                                          electricity-app-1\na7d10b393d7e   mysql:8           \"docker-entrypoint.s…\"   About a minute ago   Up About a minute               0.0.0.0:3306-\u003e3306/tcp, :::3306-\u003e3306/tcp, 33060/tcp   electricity-db-1\n```\n\nМожно проверить логи контейнера:\n```\ndocker compose logs app\napp-1  | Ошибка подключения к базе данных: ConnectionRefusedError [SequelizeConnectionRefusedError]: connect ECONNREFUSED 172.18.0.2:3306\napp-1  |     at ConnectionManager.connect (/app/node_modules/sequelize/lib/dialects/mysql/connection-manager.js:92:17)\napp-1  |     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\napp-1  |     at async ConnectionManager._connect (/app/node_modules/sequelize/lib/dialects/abstract/connection-manager.js:222:24)\napp-1  |     at async /app/node_modules/sequelize/lib/dialects/abstract/connection-manager.js:174:32\napp-1  |     at async ConnectionManager.getConnection (/app/node_modules/sequelize/lib/dialects/abstract/connection-manager.js:197:7)\napp-1  |     at async /app/node_modules/sequelize/lib/sequelize.js:305:26\napp-1  |     at async Sequelize.authenticate (/app/node_modules/sequelize/lib/sequelize.js:457:5) {\napp-1  |   parent: Error: connect ECONNREFUSED 172.18.0.2:3306\napp-1  |       at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1555:16) {\napp-1  |     errno: -111,\napp-1  |     code: 'ECONNREFUSED',\napp-1  |     syscall: 'connect',\napp-1  |     address: '172.18.0.2',\napp-1  |     port: 3306,\napp-1  |     fatal: true\napp-1  |   },\napp-1  |   original: Error: connect ECONNREFUSED 172.18.0.2:3306\napp-1  |       at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1555:16) {\napp-1  |     errno: -111,\napp-1  |     code: 'ECONNREFUSED',\napp-1  |     syscall: 'connect',\napp-1  |     address: '172.18.0.2',\napp-1  |     port: 3306,\napp-1  |     fatal: true\napp-1  |   }\napp-1  | }\n```\n\nОбращает на себя внимание следующая проблема:\n```\nОшибка подключения к базе данных: ConnectionRefusedError [SequelizeConnectionRefusedError]: connect ECONNREFUSED 172.18.0.2:3306\n```\n\nДале излагается алгоритм проверки и отладки с изложением причин и предлагаемых решений.\n\n1. MySQL ещё не успел запуститься. Это самая частая причина. Контейнер с приложением стартует раньше, чем база данных готова принимать подключения. Решения:\n\n    - Убедиться, что в коде Node.js есть повторные попытки подключения (retry) или задержка перед первым запросом.\n\n    - Можно добавить небольшую задержку старта приложения (например, через npm-пакет wait-port или wait-for-it.sh).\n\n    - В `docker compose` `depends_on` НЕ гарантирует, что база уже готова, он только запускает контейнеры в нужном порядке.\n\n2. Проверить логи MySQL. Посмотреть, нет ли ошибок инициализации базы, например:\n\n    ```sh\n    docker compose logs db\n    ```\n\n    Если есть ошибки (например, из-за скрипта инициализации), MySQL может не стартовать. Необходимо исправить указанные ошибки.\n\n3. Проверить порт и имя хоста. В `docker compose` для Node.js приложения должно быть:\n\n    ```js\n    environment:\n    DB_HOST: db\n    DB_USER: user\n    DB_PASSWORD: password\n    DB_NAME: OPK12A\n    ```\n\n    где db — имя сервиса MySQL в `docker compose`.\n\n    В Sequelize-конфигураторе необходимо использовать эти переменные.\n\n4. Проверить что база MySQL реально слушает порт 3306:\n\n    ```sh\n    docker compose exec db netstat -ln | grep 3306\n    ```\n\n5. Проверить, что база инициализирована корректно\n\n    Если только что создан volume с данными, и инициализационный скрипт был некорректен, база может не стартовать.\n\n6. Перезапустить сервисы:\n\n    ```sh\n    docker compose down\n    docker compose up -d\n    ```\n\n7. Если всё хорошо, но ошибка повторяется, то следует добавить retry-подключение в код Node.js.\n\n    Retry должен быть только там, где происходит инициализация подключения к БД, то есть при вызове `sequelize.authenticate()` или при создании самого экземпляра Sequelize. Легче всего добавить функцию переподключения в конец главного файл веб-приложения *app.js* и изменить метод запуска сервера:\n    ```js\n    ...\n    // Функция для повторных попыток подключения к БД\n    async function connectWithRetry(retries = 10, delay = 3000) {\n    for (let i = 0; i \u003c retries; i++) {\n        try {\n        await sequelize.authenticate();\n        console.log('Подключение к базе данных успешно');\n        return;\n        } catch (err) {\n        console.error(`Ошибка подключения к базе данных (попытка ${i + 1}):`, err.message);\n        if (i \u003c retries - 1) {\n            await new Promise(res =\u003e setTimeout(res, delay));\n        } else {\n            throw err;\n        }\n        }\n    }\n    }\n\n    // Запуск сервера только после успешного подключения к БД\n    const PORT = process.env.PORT || 3000;\n    connectWithRetry()\n    .then(() =\u003e {\n        app.listen(PORT, () =\u003e console.log(`Сервер запущен на порту ${PORT}`));\n    })\n    .catch(err =\u003e {\n        console.error('Не удалось подключиться к базе данных. Завершение работы.');\n        process.exit(1);\n    });\n    ```\n\n8. Перезапустить сервисы:\n\n    ```sh\n    docker compose down\n    docker compose up -d\n    ```\n\n### Ошибка привязки портов\nПри запуске контейнера с базой данных можно столкнуться со следующей ошибкой:\n```\nError starting userland proxy: listen tcp4 0.0.0.0:3306: bind: address already in use\n```\n\nДанная ошибка, как правило, возникает при запуске контейнера, пытающегося использовать порт 3306 (обычно ассоциированного с MySQL/MariaDB), который уже занят каким-либо другим процессом хостовой машины. Основные причины ошибки:\n\n- Уже запущенный MySQL на хосте, который занимает порт 3306.\n\n- Предыдущий Docker-контейнер, который не был корректно остановлен.\n\n- Другие сервисы, использующие этот порт.\n\nВарианты решения проблемы:\n\n1. Найти и остановить конфликтующий процесс.\n\n    Если необходимо именно использовать порт 3306, сначала нужно освободить его, остановив или перенастроив процессы, которые его занимают\n\n    ```sh\n    sudo lsof -i :3306\n    ```\n\n    или\n    ```sh\n    netstat -tanlp | grep 3306\n    ```\n\n    чтобы узнать, какой процесс занимает порт 3306. Затем можно остановить этот процесс, если это безопасно, например:\n    ```sh\n    kill \u003cPID\u003e\n    ```\n\n    После освобождения порта можно повторить запуск Docker-контейнера.\n\n    Если это по каким-то причинам нежелательно или невозможно, то можно назначить контейнеру Docker другой порт (см. следующий пункт).\n\n4. Изменить сопоставление портов Docker-контейнера.\n\n    Вместо `-p 3306:3306` можно использовать другой порт хостовой машины:\n    ```sh\n    docker run -p 3307:3306 mysql\n    ```\n\n    В этом случае порт 3306 контейнера будет отображен на хостовый порт 3307. Аналогично в *docker-compose.yml* можно изменить:\n    ```yml\n    services:\n    db:\n        ports:\n        - \"3307:3306\"\n    ```\n\n    Это позволит избежать конфликта портов\n\n5. Проблема, связанная с запущенным и неактуальным контейнером Docker, который занимает ресурсы, решается способом, описанным ниже.\n\n    Проверить список всех контейнеров:\n    ```sh\n    docker ps -a\n    ```\n\n    и удалите ненужные:\n    ```sh\n    docker rm \u003ccontainer_id\u003e\n    ```\n\n    После чего можно попробовать запустить контейнер снова.\n\n4. Перезапустить Docker.\n\n    Иногда помогает перезапуск Docker-сервиса:\n    ```sh\n    systemctl restart docker\n    ```\n\n    После этого можно повторить запуск контейнера.\n\n### Ошибка сборки Docker: отсутствует Dockerfile\nЭта ошибка означает, что Docker Compose не может найти файл *Dockerfile*, который необходим для сборки образа вашего приложения.\n\nПричины ошибки:\n1. Файл Dockerfile отсутствует в текущей директории или в указанном месте.\n2. Неправильное имя файла (например, с опечаткой).\n3. Неправильный путь к Dockerfile в *docker-compose.yml*.\n\nКак исправить:\n1. Проверить наличие Dockerfile.\n\n    Необходимо убедиться, что в папке проекта есть файл с именем *Dockerfile* (без расширения). Проверить можно командой:\n    ```sh\n    ls -la\n    ```\n\n2. Проверить *docker-compose.yml*.\n\n    Если *Dockerfile* находится в другой папке, необходимо указать правильный путь в *docker-compose.yml*:\n    ```yml\n    services:\n    app:\n        build:\n        context: .  # путь к папке с Dockerfile\n        dockerfile: ./путь/к/Dockerfile  # если Dockerfile не в корне\n    ```\n\n3. Если *Dockerfile* действительно отсутствует, то нужно создать его. Пример минимального *Dockerfile* для Node.js приложения:\n\n    ```docker\n    FROM node:18\n    WORKDIR /app\n    COPY package*.json ./\n    RUN npm install\n    COPY . .\n    CMD [\"npm\", \"start\"]\n    ```\n\n4. Если используется другой файл, можно переименовать его в *Dockerfile* или указать конкретное имя в *docker-compose.yml*:\n\n    ```yml\n    build:\n    dockerfile: Dockerfile.prod  # если используете другое имя\n    ```\n\n    После исправления можно попробовать снова запустить сборку:\n    ```sh\n    docker-compose build\n    ```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstudy-pm%2Felectricity","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstudy-pm%2Felectricity","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstudy-pm%2Felectricity/lists"}