Будь ласка, використовуйте цей ідентифікатор, щоб цитувати або посилатися на цей матеріал: https://er.chdtu.edu.ua/handle/ChSTU/9028
Назва: Проектування корпоративного додатку щодо координації руху транспорту
Автори: Куницька, Світлана Юріївна
Кужденко, Максим Володимирович
Дата публікації: 9-чер-2022
URI (Уніфікований ідентифікатор ресурсу): https://er.chdtu.edu.ua/handle/ChSTU/9028
Розташовується у зібраннях:121 Інженерія програмного забезпечення (Інженерія програмного забезпечення)

Файли цього матеріалу:
Файл Опис РозмірФормат 
Кваліфікаційна робота бакалавра Кужденко Максим Володимирович.pdf
  Restricted Access
1.79 MBAdobe PDFПереглянути/Відкрити    Запит копії


Усі матеріали в архіві електронних ресурсів захищено авторським правом, усі права збережено.

Extracted text
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ 
ЧЕРКАСЬКИЙ ДЕРЖАВНИЙ ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ 
ФАКУЛЬТЕТ ІНФОРМАЦІЙНИХ ТЕХНОЛОГІЙ І СИСТЕМ 
 
КАФЕДРА ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ АВТОМАТИЗОВАНИХ 
СИСТЕМ 
         
 
 
 
 
 
 
ПОЯСНЮВАЛЬНА ЗАПИСКА 
до кваліфікаційної роботи бакалавра 
 
 
  
на тему: «ПРОЕКТУВАННЯ КОРПОРАТИВНОГО ДОДАТКУ 
ЩОДО КООРДИНАЦІЇ РУХУ ТРАНСПОРТУ» 
 
 
 
 
 
 
Виконав: студент 4 курсу групи ПЗ-184 
Напряму підготовки: 
121 «Інженерія програмного       
забезпечення» 
Максим КУЖДЕНКО 
 
 
Керівник  
к.т.н., доцент Світлана КУНИЦЬКА 
 
 
Рецензент 
к.т.н., старший викладач кафедри 
інформаційних технологій ЧНУ  
ім. Б. Хмельницького  
Тетяна СТАБЕЦЬКА  
 
 
 
 
Черкаси 2022 року 
 
Черкаський державний технологічний університет 
Факультет Інформаційних технологій і систем 
Кафедра Програмного забезпечення автоматизованих систем 
Освітньо-кваліфікаційний рівень Бакалавр 
Напрям підготовки  121 – Інженерія програмного забезпечення 
Освітня програма                    Інженерія програмного забезпечення 
 
 
 
                      ЗАТВЕРДЖУЮ 
Завідувач кафедри _______ Станіслав ПЕРВУНІНСЬКИЙ 
“_____” ________________ 2022 року 
 
ЗАВДАННЯ 
 
на кваліфікаційну роботу магістра студенту 
Кужденко Максим Володимирович 
(прізвище, ім‘я, по батькові) 
«Розробка корпоративного додатку щодо кооридинації руху 
1. Тема роботи транспорту»  
                           
Керівник роботи               к.т.н., доцент Куницька Світлана Юріївна 
                                                                   (прізвище, ім’я, по батькові, науковий ступінь, вчене звання) 
 
затверджені наказом університету від «4» квітня 2022 р. № 288/01 
 
2. Строк подання студентом роботи 10 грудня 2022 року 
 
3. Вихідні дані до роботи:  
- реляційна база даних; 
- патерн Model-View-Controller; 
- Засоби MySQL; 
- Мова програмування JavaScript. 
 
 
4. Зміст розрахунково-пояснювальної записки (перелік питань, що їх належить розробити):  
Вступ 
Розділ 1 Проектування інформаційної системи 
Розділ 2 Особливості роботи програмного продукту   
Розділ 3 Дослідження та вдосконалення інформаційної системи  
Висновки 
Додатки 
 
5. Перелік графічного матеріалу (з точним зазначенням обов’язкових креслень, плакатів):  
Додаток А – Текст програми 
Додаток Б – Документація додатку 
 
6. Консультанти розділів роботи 
Розділ Підпис, дата 
1 
 
 
Прізвище, ініціали та 
посада завдання видав завдання прийняв 
консультанта 
    
    
    
 
 
7. Дата видачі завдання 21.09.2022 р. 
  
 
 
 
 
КАЛЕНДАРНИЙ ПЛАН 
Строк виконання 
№ з/п Назва етапів дипломного проекту (роботи) Примітка 
етапів роботи 
1 Затвердження теми роботи  21.09.2021 виконано 
2 Постановка задач 22.09-20.10 виконано 
3 Обґрунтування актуальності виконання 21.10-31.10 виконано 
досліджень 
4 Оцінка стану проблеми, виокремлення  01.11-10.11 виконано 
дослідницьких задач, постановка задачі 
дослідження 
5 Викладення результатів дослідження 11.11-15.11 виконано 
6 Практичне застосування результатів 16.11-25.11 виконано 
дослідження 
7 Оформлення результатів в пояснювальну 26.11-08.12 виконано 
записку 
8 Подання роботи на рецензування 09.06.2022 виконано 
9 Подання роботи на відгук 10.06.2022 виконано 
    
    
 
 
 
Студент-бакалавр                 ____________________________      Максим КУЖДЕНКО   
                                                     (підпис) 
 
Керівник роботи                     ____________________________      Світлана КУНИЦЬКА   
                                             (підпис) 
  
2 
 
 
 
 
Зміст 
 
ВСТУП .......................................................................................................................................4 
РОЗДІЛ 1. Проектування системи .......................................................................................6 
1.1 Використані технології .......................................................................................................7 
1.2 TypeScript .............................................................................................................................8 
РОЗДІЛ 2. Опис технічної частини додатку ....................................................................10 
2.1 Структура бази даних .......................................................................................................10 
2.2 Головні вимоги ..................................................................................................................12 
2.3. Діаграма модулів ..............................................................................................................14 
2.4 Розділення користувачів на ролі......................................................................................15 
2.5 Робота з ключами компаній. ............................................................................................17 
2.6 Процедура авторизіції.......................................................................................................19 
2.7 Чат.......................................................................................................................................20 
2.8 Система координації .........................................................................................................23 
РОЗДІЛ 3. Тестування програми. ......................................................................................24 
3.1 Тестування авторизації та реєстрації. .............................................................................24 
3.2 Тестування можливостей admin та manager панелі .......................................................28 
3.3 Тестування чату та мапи...................................................................................................33 
ВИСНОВОК.............................................................................................................................37 
ДОДАТОК А............................................................................................................................41 
ДОДАТОК Б  ............................................................................................................................85 
 
 
      
  
 
ЧДТУ 2218021.003 ПЗ 
Зм. Лис № документа Підпис Дата 
 
Розроб. Кужденко М.В. Проектування корпоративного додатку Літ. Лист Листів 
Керівник Куницька С.Ю щодо координації руху транспорту Н 3 739  
  Д 
Н.контр Куниць ка С.Ю (Пояснювальна записка) ФІТІС, 
 Кафедра ПЗАС, гр. ПЗ-184 
За.т в. Первунінський 
С.М 
 
ВСТУП 
 
Підприємство — самостійний суб'єкт господарювання , 
зареєстрований компетентним органом державної влади або 
органом місцевого самоврядування, для задоволення суспільних та 
особистих потреб шляхом систематичного здійснення виробничої, науково-
дослідної, торговельної, іншої господарської діяльності в порядку, 
передбаченому Господарським кодексом України та іншими законами. 
Головне завдання підприємства полягає в задоволенні потреб ринку в його 
продукції або послугах з метою одержання прибутку. 
Підприємство є одною із найважливіших складових сучасного світу та 
його економіки. І невід'ємною частиною любого підприємства є його 
працівники, і не завжди  їх менеджмент на такому рівні, якому хотілось би, 
а саме – як вони добираються до роботи та назад. Провівши декілька 
досліджень мною було виявлено, що значна частина робітників які 
користуються корпоративним транспортом (зазвичай автобуси) не 
задоволені якістю обслуговування. 
Серед проблем часто зустрічаються неможливість комунікації з 
водієм автобуса, нескоординованість руху та неможливість відслідкувати де 
саме на цей момент знаходиться водій, щоб краще розподілити свій час. 
І спільна риса у всіх цих проблем – втрата часу або морального духу 
працівників, що рівносильне втраті підприємством коштів. 
Багато хто не розуміє наскільки важливий моральний дух робітників, 
та нехтує очевидними покращеннями, які можуть підняти ефективність 
підприємства на новий рівень. 
Проблемам аналізу та поліпшення фiнансового стану присвячено ряд 
робiт вiтчизняних та закордонних вчених, серед яких треба видiлити працi 
Бланка I.О., Олексюка О.С., Ковальова В.В., Стоянової Є.С., Шеремета А.Д., 
Крейнiної М., Альтмана Е., Хелферта Е., Рiшара Ж. та iнших. 
Об'єкт дослідження - підприємства, в яких робітники мають 
4 
 
 
добиратися на роботу за допомогою корпоративного транспорту. 
Предмет дослідження - Логістична система підприємств щодо 
перевозки своїх робітників на фірму та з неї. Створення додатку для 
менеджменту перевезень робітників. 
Мета роботи: Дослідити як різні фірми здійснюють доставку своїх 
робітників на роботу та додому. Зайти спосіб покращити менеджмент 
доставки. Створити додаток, який буде вирішувати проблеми знайдені під 
час досліджень. 
Завдання роботи: Провести дослідження області менеджменту 
доставки робітників у різних фірм. 
Сформувати основні проблеми, які зустрічаються частіше усього. 
На основі цих проблем скласти можливу модель додатку та його 
функціоналу. 
Дізнатись думку раніше допитаних робітників та можливо отримати 
поради щодо покращення додатку. Сформувати ТЗ та структуру додатку. 
Розробити додаток на основі складеного раніше ТЗ та провести тестування. 
Методи дослідження: При вирішенні поставлених задач 
застосовувались такі методи, як метод експертних оцінок, економічний 
аналіз, факторний аналіз. 
Інформаційна база: Інформаційною базою є монографічні 
дослідження та узагальнення після проведення діалогів з працівниками 
різних фірм. 
Структура роботи:  Кваліфікаційна робота бакалавра складається зі: 
вступу, 3 розділів, висновків, списку використаних джерел, 3 додатки. У 
тексті роботи  бакалавра міститься 39 рисунків. Загальний обсяг роботи 79 
листа. 
  
5 
 
 
РОЗДІЛ 1. ПРОЕКТУВАННЯ СИСТЕМИ 
 
В останній час багато компаній-гігантів таких, як наприклад google 
все більше приділяють увагу таким очевидним проблемам як моральне 
здоров’я та командний дух своїх працівників. І якщо подивитись на 
розвиток таких компаній стає ясно, що це дає свої плоди. Так що ж робити 
тим, хто хоче організувати розвезення працівників більш ефективно, а  що 
головніше – з комфортом. Провівши ще одне дослідження мною було 
виявлено, що найкращим рішенням було б створення додатку для 
об’єднання працівників та автобуси на яких вони пересуваються в свого 
роду “Маршрути”, які будуть давати можливість комунікації та ще декілька 
ключових переваг. Наприклад організувати чат, карту на якій можна буде 
відслідковувати де на цей момент знаходиться водій та ще декілька “Фіч” 
які значливо зможуть покращити життя працівникам. 
Головними проблемами були: 
1. При розмові із робітником який досить часто користується транспортом 
фірми мною було виявлено, шо не завжди фіксований маршрут може давати 
користь, так як в основі автобус їздить серед міста, та збирає робітників на 
фіксованих зупинках. Але з цим методом є кілька проблем, серед них – не 
завжди на зупинці може хтось бути, і розпитавши у водія, я вияснив що це 
зовсім не рідкість, в основному на зупинках по 1-2 робітнику, і якщо це 
центр або жвавий район, то попасти на таку зупинку буває не просто, і 
приходиться стояти деякий час і ждати поки громадський транспорт 
звільнить зупинку. Через це втрачається чимала кількість часу, але може 
виявитись, що працівника зовсім не було на зупинці через будь-які причини, 
і тоді час було згайновано за дарма. 
2. Рідше, але ж все-таки зустрічаються ситуації, коли робітник не може підійти 
на сплановану зупинку, але може до якоїсь іншої, яка може навіть бути на 
маршруті водія, але ні швидкого зв’язку ні другого виду комунікації немає, 
6 
 
 
і в наслідок цього працівник вимушений витрачати більше часу щоб 
добратися до роботи, або навіть запізнитись. 
Саме для вирішення таких проблем мною було створено програмний 
додаток, під назвою – “Pick Me”. Головна ідея та мета проекту була 
покращити рівень якості обслуговування та задоволення працівників. Але 
під час розробки було виявлено ще декілька ключових особливостей, які 
можуть стати вагомим плюсом додатку, але поки вони не були втілені в 
життя, та будуть описані в подальших розділах. 
Що саме вирішує додаток “Pick Me”? На сам перед – проблему 
швидкої комунікації водій – робітник, яка і була одною із головних проблем. 
А також ще деяких проблем, таких як об’єднання автобусів в групи, та 
відслідковування місця знаходження водія в реальному часі. 
 
1.1 Використані технології 
Додаток “Pick Me” – клієнт – серверне програмне забезпечення, 
написане на мові JavaScript із надстройкою TypeScript та платформі NodeJS 
NodeJS – серверне середовище виконання коду написаному на JavaScript, 
розроблений Раяном Далом ще в 2009 році. Написане середовище на мовах 
JavaScript, C,  C++. Та має властивості асинхронного та одно-нитевого 
виконання запитів, та неблокуючим властивостям вводу/виводу, через що і 
став популярним в останній час. 
З використанням передового фреймворку для покращення багатьох 
аспектів додатку NestJS, яких дозволяє впровадити модулеву систему, яка 
давно зарекомендувала себе в інших мовах програмування. 
NestJS – фреймворк, створений для поліпшення процесу розробки та 
подальшої підтримки продукту, який використовує правильні підходи для 
розробки, та диктує їх розробникам. Тому, NestJS- це не тільки фреймворк 
для бекенда, але й можливість увійти у світ передових концепцій, наприклад 
таких як DDD, Event sourcing та мікросервісної архітектури. Все упаковано 
в простій і легкій формі. 
7 
 
 
Для зв’язку з базами даних було використано сучасну ORM TypeORM  
ORM (англ. Object-Relational Mapping) - технологія програмування, яка 
пов'язує бази даних з концепціями об'єктно-орієнтованих мов 
програмування, створюючи «віртуальну об'єктну базу даних». Існують як 
пропрієтарні, і вільні реалізації цієї технології. 
TypeORM – це ORM, яка може запускатись в таких середовищах, як 
NodeJS, Браузер, Cordova, PhoneGap, Ionic, React Native, NativeScript, Expo 
та Electron. Та може використовуватись з TypeScript та JavaScript, і 
підтримує найостанніші їх фічі. Головна мета TypeORM полягає в тому, щоб 
завжди підтримувати найновіші функції JavaScript і надавати додаткові 
функції, які допоможуть вам розробляти будь-які програми, які 
використовують бази даних - від невеликих програм з кількома таблицями 
до великомасштабних корпоративних програм з кількома базами даних. 
Та СУБД MySQL, що дозволило зберігати дані користувачів та компаній в 
базі даних. 
Фінальна версія продукту має мати клієнт версію в виді телефонного 
додатку на Android та IOS девайси, але на момент розробки можливості 
створити такий не було, тому було створено його web-версію для 
демонстрації можливостей серверу та покращення процесу розробки. 
 
1.2 TypeScript 
TypeScript – мова програмування, представлена компанією Microsoft 
в 2012 році. Розробником якої був Андрес Хейлсберг, який раніше створив 
C#, Delphy та Turbo Pascal. Надстройка TypeScript позиціонує себе як засіб 
розробки веб-додатків, який розширює можливості чистого JavaScript. 
Після написання коду на TypeScript, його потрібно компілювати в 
стандартний JavaScript, та використовувати в продакшн версіях продукту. 
TypeScript мною було обрано не просто так, адже ті, хто щось писав на 
чистому JavaScript знає, що ООП (Об’єктно орієнтоване програмування) 
мовою назвати його складно, а якщо подивитися на топ популярних back-
8 
 
 
end мов програмування, можна помітить одну особливість, а саме це те, що 
переважна їх більшість саме ООП мови. І настройка TypeScript це ООП в 
якійсь мірі і добавляє, а саме – робота з класами, інтерфейси, типи даних та 
інші красоти ООП мов. Розпитавши професіоналів в області розробки на 
NodeJS, мною було виявлено, що майже все сучасні проекти починають 
писати або хоча б якось адаптувати під TypeScript, та саме завдяки їхнім 
коментарям та порадам я обрав TypeScript. 
  
9 
 
 
РОЗДІЛ 2. ОПИС ТЕХНІЧНОЇ ЧАСТИНИ ДОДАТКУ 
 
2.1 Структура бази даних 
Entity TypeORM. Так як мною було використано ORM TypeORM, 
напряму взаємодіяти з базами не знадобилося, лише описати їх таблиці та 
взаємодії між ними як Entity 
 
 
Рисунок 2.1- Entity ключа 
 
Так як клас entity наслідує інтерфейс, цей самий інтерфейс можна 
використовувати всередині програми та спокійно змінювати його поля за 
потреби. Також стає доступним клас цьго entity, що дозволяє 
використовувати його як тип даних, та зручно працювати з ним. 
 
10 
 
 
 
 Рисунок 2.2 - Entity компанії 
 
    
Рисунок 2.3 - Entity користувача 
 
В більш традиційному плані зі схемою це буде виглядати приблизно 
так: 
11 
 
 
 
Рисунок 2.4 - Схема бази даних 
 
Також за рекомендацією було додано поля createdAT та updatedAT, 
що відповідно відображують дату додання запису до бази, та його 
оновлення. 
Як СУБД була обрана MySql, через її швидкодію та головне це те, що 
вона реляційна. В майбутньому ще можна буде добавити MongoDB для 
зберігання лог-документів, так як зберігання логів в файлах може бути 
небезпечним, при любих помилках, так як дістати потім їх буде складніше, або 
якщо сервер “впаде” під час запису в файл, буде неможливо дізнатись логи, і як 
наслідок - причину по якій впав сервер. 
 
2.2 Головні вимоги 
• Вимоги до реєстрації користувача. 
·    Користувач при реєстрації має вказувати електронну адресу, яка в 
подальшому буде логіном. При запиті на API, сервер повинен перевіряти 
адресу. 
·    При реєстрації потрібно вказати ім’я та прізвище робітника. Ім’я 
повинно бути від 1 до 64 символів. 
12 
 
 
·    Пароль при реєстрації зашифровується, та зберігається в базі даних 
лише в зашифрованому вигляді. Довжина паролю – від 5 до 64 символів. 
·    За замовчуванням надається роль USER. 
• Вимоги до мапи та чату. 
·    Мапа повинна бути побудована на google maps API. 
· Координати повинні передаватись від водія всім учасникам групи 
ключа миттєво та без затримок. 
· Чат повинен бути в реальному часі між усіма учасниками групи 
ключа. 
· Чат повинен бути у форматі <Ім'я користувача вказане при 
реєстрації>: <Повідомлення> 
• Системні сервіси: 
Сервер повинен мати rest-api, який буде виконувати задачі, поставлені в ТЗ. 
Всі дані які приходять на сервер та виходять з сервера повинні бути бути в 
форматі JSON. 
• Системні обмеження: 
Сервер повинен бути неблокуючимся, та здатним обробляти тисячі запитів 
за хвилину. 
Всі конфіденційні дані повинні передаватися захищеними шляхами та 
згідно стандартам. 
Сервер повинен запускатись на ОС linux, та бути відмовостійким. 
• Характеристика області застосування: 
Область застосування додатку - підприємства які мають та реалізують 
можливість доставки співробітників на фірму. 
Підставами для розробки послугували скарги деяких робітників на 
недосконалість та незручність цієї системи, та надали деякі ідеї щодо 
реалізації. 
Призначення розробки - Допомогти фірмам реалізувати менеджмент 
та доставку своїх робітників найзручнішим способом. 
13 
 
 
• Специфікація вимог: 
Додаток має бути back-end rest-api частиною проекту pickme. 
Програмне забезпечення повинно бути написано в ООП стилі та відповідати 
модульному шаблону. 
Документація повинна бути виконана в файлі README.md, та 
містити перелічень енд-поінтів та описом вхідних даних на них. 
• Випробування для ПЗ для прийняття його: зареєструвати акаунт 
використовуючи postman або любий інший подібний додаток. Також 
дозволяється використовувати демонстраційну front-end частину. 
Спробувати зайти на любий закритий енд-поінт без ключа авторизації. 
Відповідь повинна бути 403 Forbidden. 
Авторизуватися за допомогою тільки що створеного акаунту, та 
отримати JWT ключ. Встановити його в хедер authorization. 
Відправити запрос на енд-поінт який доступний лише користувачам.  
За допомогою акаунта менеджера надати двом або більше користувачам 
однаковий ключ фірми, перевірити чи спільний у них чат та мапа. 
 
2.3 Створення графічного представлення взаємозвязків елементів  
Так як проект буде розроблений з використанням NestJS, класи мають 
взаємодіяти через dependency injection та будуть містити в собі модуль, 
сервіс та можливо контроллер. 
Що з себе представляє діаграма модулів. Як видно з діаграми 
головним модулем є main module, він же app.module, який все в собі містить 
інші модулі, та дозволяє їм взаємодіяти між собою. Така модель побудована 
на основі імпортів на експортів NestJS змушує розділяти модулі на 
контролери та сервіси і цим самим притримуватись правильних патернів 
програмування. 
Контролери відповідать за API, та любий інший вхідний зв'язок з 
іншими програмами. Сервіси в свою чергу це класи функцій та інструкцій 
щодо виконання, перевірки та обробки даних, які ми отримуємо з інших 
14 
 
 
модулів або контролерів. 
 
 
Рисунок 2.5 - Діаграма модулів 
 
Entity в свою чергу - фішка TypeORM, яка зв'язує бази даних та ООП 
програмування, що виводить роботу з ним на новий рівень. 
Вибір NodeJS був для мене досить очевидним, так як в останній час ця 
технологія набирає популярність та може сказати, що не с проста. Адже в 
ньому є все, що потрібно для розробки додатків такого типу, від 
відмовостостійкості до гнучкої варіативності написання коду, завдяки 
природі JavaScript. А щоб привнести ще стабільність та швидкість 
виконання - мною було обрано надстройку TypeScript, для ООП та типів 
даних. 
 
2.4 Розділення користувачів на ролі 
Користувачі  будуть розподілені на 4 ролі, а сама роль за 
замовчуванням - USER, який буде видаватися при реєстрації. Та матиме 
15 
 
 
доступ до мапи, чату та домашньої сторінки.  
Роль ADMIN може видаватись лише адміністратором та має доступ до 
всіх енд-поінтів та можливостей.  
Роль MANAGER - має доступ до налаштувань своєї фірми, видавати 
роль водія та присвоювати ключі робітникам.  
Роль DRIVER - має всі ті ж самі можливості що й USER, але може 
бути присвоєний лише один до одного ключа, та має можливість посилати 
свої координати всім користувачам ключа. 
Програмне рішення: 
Ролі по собі - це просто ENUM, який завдяки типізації TypeScript став 
типом, і присвоїти роль, якої не існує в цих enum неможливо. 
Виглядає це приблизно ось так: 
 
Рисунок 2.6 - Ролі в ENUM 
 
І після реєстрації або оновлення ролі, вона просто попадає в базу 
даних. Саме рішення по обмеженню енд-поінтів приходить від NestJS, та 
його AuthGuard та декораторів, які працюють як Middleware, і виконують 
інструкції із запитом ще до того, як функція почне своє виконання. 
Це мені дозволило створити кастомный декоратор roles, який задає 
мета денні енд-поінту 
 
Рисунок 2.7 - Створення кастомного декоратору 
16 
 
 
І вже після цього я створив guard для ролей, та зробив його 
глобальним. Тобто він буде розповсюджуватися на всі енд-поінти, та 
перевіряти, чи є в них метадата roles, яку я присвоюю за допомогою 
декораторів.  
 
Рисунок 2.8 - Створення Roles Guard 
 
2.5 Робота з ключами компаній 
• Створення ключа 
Ключ можна назвати індексом групи, в якій знаходяться водій та 
робітники. Створити ключ досить легко, але зробити це може лише 
адміністратор або менеджер фірми. 
Для фронт частини було створено панель менеджера, на якій він може  
самостійно створити ключ. Для цього ніяких зайвих дій не потрібно, лише 
ввести бажаний ключ, та натиснути кнопку Create!. Після цього на сервер 
буде надісланий запит на створення ключа. В хедері авторизації повинен 
бути вказаний JWT токен, який користувач отримує при авторизації, та який 
тимчасово зберігається в local storage. Сервер передає ключ авторизації на 
17 
 
 
Guard, який перевіряє всі дані авторизації. Насамперед з токена збирається 
інформація про користувача, та звіряється з базою даних. Якщо все добре, 
інформація про користувача та тіло запита передаються в функцію 
виконання. Де знаходиться бізнес-логіка обробки ключа. 
 
Рисунок 2.9 - Панель створення ключа 
 
 
Рисунок 2.10 -  Блок-схема додавання ключа 
18 
 
 
Якщо всі кроки було пройдено успішно, ключ додається до ключів 
компанії. В однієї компанії може бути безліч ключів, але повинна існувати 
причина для створення, які вже обговорюються заранє з адміністраторами. 
• Встановлення ключа користувачеві 
Для встановлення ключа користувачем, йому потрібно зайти в 
особистий кабінет, та встановити його там. 
• Можливості розширення функціоналу 
Також в майбутньому без проблем можна буде надавати можливість 
робити “Закриті ключі”, які не можна буде встановити самостійно, і лише 
менеджер матиме можливість встановити його користувачеві. 
 
2.6 Процедура авторизіції 
• Процес авторизації користувача 
Авторизація відбувається за адресою /auth/login, та потребує тіло 
запиту, в якому буде вказано електронна адреса користувача, та його пароль. 
Передаватись тіло запиту повинно в форматі json. 
• Перевірка та створення токену 
Перевірка користувача проводиться за допомогою “стратегій”, у 
випадку з авторизацією, вона була названа local, та виконується подібно до 
гвардів, тобто перевіряє та модифікує дані ще до того, як вони попали в 
функцію обробки.  
Створення JWT токену відбувається за допомогою бібліотеки 
nestjs/jwt, яка знаходиться в npm. Для її роботи потрібен секретний ключ та 
тіло для нього, яке потім можна буде дістати за надобності. Я використовую 
для цього username, email та role, які я потім зможу дістати та це буде 
головна інформація про користувача даного токена. Секретний ключ 
зберігається в змінних процесу, який вважається найбезпечнішим способом, 
так як може задаваться при старті програми, але в моєму випадку я 
використовував файл .env для його зберігання на час розробки. 
 
19 
 
 
При успішній авторизації сервер повернає назад токен, який потім 
можна використовувати для доступу до закритих енд-поінтів та веб сокетів. 
 
 
Рисунок 2.11 - Блок-схема авторизації 
 
2.7 Чат 
• Технології побудови чату 
Чат побудований за допомогою веб сокетів. Веб сокети - протокол 
комунікації з сервером в реальному часі він забезпечує двунаправленный 
зв'язок  між клієнтом та сервером через один TCP сокет, які вже давно 
зарекомендували себе як один із найкращих засобів для побудови чату або 
інших систем передачі інформації, яким важливіше час передачі ніж 
нагрузка на сервер. 
• Демонстрація можливостей 
Для тестування чату я використовую 2 різні браузери, із двома 
20 
 
 
різними акаунтами на них. Перший із яких - Maksim, другий - Oleg, і так як 
чат прив'язаний до ключа компанії, на ці акаунти буде встановлено ключ 
123 для перевірки. 
 
 
Рисунок 2.12 - Вигляд чату при двух аккаунтах в мережі 
 
Як видно на рисунку 2.12, також інснує ідентифікатор кількості 
користувачів онлайн, які можуть читати все що пишуть в чаті. І так як було 
використано технологію веб-сокетів, всі повідомлення будуть миттєво 
відображені в усіх користувачів онлайн. Повідомлення ніяк не зберігаються, 
тому почитати історію переписки якщо користувач вийшов та знову зайшов 
буде неможливо. 
 
Рисунок 2.13 - Приклад вводу повідомлення 
21 
 
 
 
Рисунок 2.14 - Відправлене повідомлення користувачем Maksim 
 
Як тільки повідомлення було відправлене, його відразу отримали всі 
користувачі онлайн, як видно на рисунку 9.4. За таким самим принципом 
працює і система координації. 
 
 
Рисунок 2.15 - Приклад роботи чату 
22 
 
 
2.8 Система координації 
Система координації виконана за допомогою google maps api на 
клієнтській частині, та за допомогою простих координат на серверній. 
Користувач з ролью DRIVER (водій) має біля карти дві кнопки, які 
відповідають за початок передачі координат усім користувачам ключа, та 
його припиненню. 
 
 
 
Рисунок 2.16 - Кнопки у водія 
  
23 
 
 
РОЗДІЛ 3.  ТЕСТУВАННЯ ПРОГРАМИ 
 
3.1 Тестування авторизації та реєстрації 
Усі тести будуть проходити в додатку POSTMAN та клієнтській  
частині додатку. Реєстрація нового користувача відбувається за посиланням 
POST /auth/register та потребує тіло запиту у вигляді: 
{ 
   "email": "", 
   "name": "", 
   "password_hash": "" 
} 
 
Для початку зареєструю користувача за усіма вимогами: 
 
Рисунок 3.1 - Реєстрація нового користувача за всіма вимогами 
 
Сервер повертає код 201 та повідомлення що реєстрація пройшла 
успішно. 
На серверній частині встановлені обмеження щодо реєстрації, а саме:  
1. Ім'я користувача повинно бути від 1 до 64 символів 
2. Пошта користувача повинна бути від 5 до 64 символів, та мати в собі ім'я  
пошти, “@” та домен. 
3. Пароль користувача повинен бути від 5 до 64 символів. 
 
24 
 
 
 
Рисунок 3.2 - Повідомлення про успішну реєстрацію 
 
Для перевірки відправлю запит з невірними даними: 
 
Рисунок 3.3 - Реєстрація з невалідними даними 
 
Як бачимо, при спробі реєстрації з невалідними даними сервер 
повертає помилку 400, та в полі message причину або причини помилок. 
25 
 
 
 
Рисунок 3.4 - Реєстрація користувача з існуючою поштою 
 
Також, якщо користувач з поштою вже існує, сервер видасть помилку 
409 як показано на рисунку 3.4, та процедура реєстрації завершиться. 
Авторизація користувача проходить за POST посиланням на 
/auth/login, куди потрібно передати тіло запиту у вигляді: 
{ 
    "username":"", 
    "password": "" 
} 
 
Та при успішній авторизації, назад користувач отримує інформацію 
про себе та токен доступу. 
26 
 
 
 
Рисунок 3.5 - Приклад успішної авторизації 
 
Також для тесту можна відправити запит з невірними або частково 
вірними даними, на що сервер очікувано повертає помилку 401, як видно на 
рисунку 3.6: 
 
 
Рисунок 3.6 - Спроба авторизації з хибними даними 
27 
 
 
Як було виявлено за допомогою ручного тестування, сервіси 
реєстрації та авторизації працюють згідно документації. 
 
3.2 Тестування можливостей admin та manager панелі 
Панель адміністратора знаходиться за посиланням /admin, та манель 
менеджера за посиланням /manager. Так як інтерфейс був створений лише 
для випробувань серверної частини, захисту там немає майже ніякого. 
Якщо зайти з аккаунта адміністратора, нам відразу будуть доступні 
дві панелі, почнемо з панелі адміністратора. 
 
Рисунок 3.7 - Головне меню від лиця адміністратора 
 
В панелі адміністратора є майже всі можливості серверної частини 
додатку, а саме: 
- Створити компанію; 
- Отримати всю інформацію про компанію; 
- Отримати список користувачів; 
- Отримати користувача за id; 
- Присвоїти користувачеві нову роль; 
Тому почнемо тестування зі створення нової компанії.  
28 
 
 
 
Рисунок 3.8 - Створити нову компанію 
 
Як бачимо для створення потрібно ввести ім'я нової компанії, її 
електронну пошту та пошту менеджера. Як менеджера для нової компанії 
буду використовувати акаунт з іменем Maksim, який виглядає поки що отак  
 
 
Рисунок 3.9 - Користувачі в базі даних 
 
Можемо помітити, що у  користувача Maksim пошта 
[email protected], та роль user, це знадобиться пізніше.  
Вимоги для створення компанії: 
- Ім'я повинно бути від 1 до 64 символів; 
- Пошта повинна бути від 5 до 64 символів, та мати в собі ім'я  пошти, 
“@” та домен; 
- Електронну адресу менеджера, який повинен бути зареєстрованим в 
системі; 
Для початку відправимо запит на реєстрацію компанії без 
дотримування документації. 
29 
 
 
 
Рисунок 3.9 -  Запит на реєстрацію компанії 
 
І як бачимо з рисунку 3.9, сервер повернув помилку 400 та в полі 
message усі помилки які ми допустили при відправці запиту. 
І якщо виправити їх, але не адресу менеджера, то отримаємо нову 
помилку: 
 
Рисунок 3.10 - Запит на реєстрацію компанії 
 
30 
 
 
А саме як видно з рисунку 3.10, ми отримали помилку, що користувача 
з такою поштою не існує. 
Та якщо поправити все, то ми створимо компанію під назвою Amazon. 
 
Рисунок 3.11 - Успішний запит на реєстрацію компанії 
 
Як бачимо, компанія успішно була створена, та у користувача Maksim 
автоматично була встановлена роль MANAGER 
 
 
Рисунок 3.12 - Оновлений користувач 
 
Далі по списку - отримати інформацію про компанію, що просто 
повертає всю інформацію яка пов'язана з компанією. 
31 
 
 
 
Рисунок 3.13 - Повернена інформація про компанію 
 
Далі перевіримо на права доступа в звичайних користувачів. Для 
цього я створив нового користувача User з ролью USER.
 
Рисунок 3.14 - Домашня сторінка звичайного користувача 
 
Якщо зайти на посилання /admin то побачимо ту ж саму панель 
адміністратора. 
32 
 
 
 
Рисунок 3.15 - Панель адміністратора 
 
Але так як я звичайний користувач, я не зможу нічого тут зробити не 
можемо і будемо лише отримувати помилку 403 access denied.  
 
Рисунок 3.16 - Панель адміністратора 
33 
 
 
3.3 Тестування чату та мапи 
Перед тестуванням потрібно створити ключ компанії та присвоїти 
його кільком аккаунтам. 
Після створення нових акаунтів та присвоєння їм однакового ключа 
ми можемо помітити, що тепер між ними став доступний чат та мапа. Після 
відправки декількох повідомлень в чат, стало зрозуміло, що працює він 
згідно вимогам. 
 
     
Рисунок 3.17 - Тест чату 
 
Далі слід перевірити мапу, для цього користувачу Sasha буде видано 
роль водія, після чого у нього з'явилися 2 нові кнопки, а саме почати 
передачу координат та припинити її.  
Далі для переміщення водія я буду використовувати додаток для 
браузера який дозволяє змінювати поточну локацію пристрою. 
 
34 
 
 
 
Рисунок 3.18 - Мапа водія 
 
 
Рисунок 3.19 - Мапа до посилання координат 
35 
 
 
 
Рисунок 3.20 - Мапа після початку посилання координат 
 
Як видно з рисунку 3.18-3.20 у користувача Vitya з'явилася помітка як 
тільки водій Sasha почав передавати свої координати. 
 
 
Рисунок 3.21 - Зміна координат водія 
 
При зміні координат у водія, всі учасники онлайн отримають його 
поточне місцезнаходження. 
36 
 
 
ВИСНОВОК 
 
 В роботі було розглянуто проблеми менеджменту доставки робітників 
на робочі місця та додому, розглянуті деякі підприємства та зібрані головні 
проблеми в результаті спілкування з працівниками.  
Були розроблені ідеї для вирішення спільних у більшості компаній 
проблем та з них сформовані вимоги до майбутнього додатку, з яких потім 
сформоване технічне завдання та модель майбутньої програми. Описані всі 
головні структури проекту, такі як база даних та модель модулів. Потім 
написана серверна частина додатку у відповідності з технічним завданням, 
після чого для зручності тестування та наглядної демонстрації була 
написана і маленька клієнтська частина.  
Після завершення написання був проведений ряд тестів, який дав 
змогу зрозуміти що не так і що потрібно поправити. І після повного 
завершення написання та проведення всих тестів програму вважаю 
завершеною. 
  
37 
 
 
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ 
 
1 Що таке веб-сервіс та їх види? [Електронний ресурс]. - Режим 
доступу: https://2ip.ua/ua/blog/web-services. 
2 Що таке веб-сервіс та їх види? [Електронний ресурс]. - Режим 
доступу: https://2ip.ua/ua/blog/web-services. 
3 What are Web services? [Електронний ресурс]. - Режим доступу: 
https://www.techtarget.com/searchapparchitecture/definition/Web-services. 
4 What is a RESTful API? [Електронний ресурс]. - Режим доступу: 
https://aws.amazon.com/ru/what-is/restful-api/. 
5 LPU аналіз вимог [Електронний ресурс]. - Режим доступу: 
https://comp.ucoz.net/TSPP/KursRob/LPUAnalizVymog15.pdf. 
6 UML діаграми випадків використання [Електронний ресурс]. - 
Режим доступу: https://lvivqaclub.blogspot.com/2008/10/use-case-uml-
diagram.html. 
7 Діаграми класів [Електронний ресурс]. - Режим доступу: 
https://ua5.org/oop/392-diagrami-klasiv.html. 
8 Теоретичні основи UML діаграм [Електронний ресурс]. - Режим 
доступу: https://studfile.net/preview/16869580/page:5/. 
9 UML діаграми компонентів [Електронний ресурс]. - Режим 
доступу: https://www.mindonmap.com/uk/blog/uml-component-diagram/. 
10 Документація Python [Електронний ресурс]. - Режим доступу: 
https://www.python.org/doc/. 
11 Flask документація [Електронний ресурс]. - Режим доступу: 
https://flask.palletsprojects.com/en/3.0.x/. 
12 Куницька С. Ю., Сергієнко О. С. Аспекти та практичне 
використання засобів і функцій flask framework для забезпечення web-
додатку. [Електронний ресурс]. - Режим доступу: https://sci-conf.com.ua/wp-
content/uploads/2023/11/GLOBAL-SCIENCE-PROSPECTS-AND-
INNOVATIONS-2-4.11.23.pdf. 
13 Сергієнко О.С., Потапенко Л.В. Usage of Flask framework tools 
and functions for user’s communication with a web application. [Електронний 
ресурс]. - Режим доступу: http://csbc.edu.ua/documents/student/110424.pdf. 
38 
 
 
14 Основи HTML [Електронний ресурс]. - Режим доступу: 
https://developer.mozilla.org/ru/docs/Learn/Getting_started_with_the_web/HT
ML_basics. 
15 Основи CSS [Електронний ресурс]. - Режим доступу: 
https://developer.mozilla.org/ru/docs/Learn/Getting_started_with_the_web/CSS
_basics. 
16 JavaScript [Електронний ресурс]. - Режим доступу: 
https://uk.wikipedia.org/wiki/JavaScript. 
17 Вступ до JavaScript [Електронний ресурс]. - Режим доступу: 
https://uk.javascript.info/intro. 
18 Що таке Bootstrap? [Електронний ресурс]. - Режим доступу: 
https://www.techtarget.com/whatis/definition/bootstrap. 
19 Про PostgreSQL [Електронний ресурс]. - Режим доступу: 
https://www.postgresql.org/about/. 
20 Що таке тестування програмного забезпечення? [Електронний 
ресурс]. - Режим доступу: https://university.sigma.software/what-is-software-
testing/. 
21 Інтеграційне тестування [Електронний ресурс]. - Режим 
доступу: https://qalight.ua/baza-znaniy/integratsijne-testuvannya/. 
22 Системне тестування [Електронний ресурс]. - Режим доступу: 
https://qalight.ua/baza-znaniy/sistemne-testuvannya/. 
23 Приймальне тестування [Електронний ресурс]. - Режим 
доступу: https://qalight.ua/baza-znaniy/prijmalne-testuvannya/. 
24 Модульне тестування [Електронний ресурс]. - Режим доступу: 
https://qalight.ua/baza-znaniy/modulne-testuvannya/. 
25 Launch your own REST API using Flask and Python [Електронний 
ресурс]. - Режим доступу: https://towardsdatascience.com/launch-your-own-
rest-api-using-flask-python-in-7-minutes-c4373eb34239. 
26 Архітектура PostgreSQL [Електронний ресурс]. - Режим 
доступу: https://www.educba.com/postgresql-architecture/.  
27 Методичні рекомендації до підготовки кваліфікаційної роботи 
бакалавра для здобувачів вищої освіти зі спеціальності 121 «Інженерія 
програмного забезпечення» усіх форм навчання [Текст] /Укл.: Голуб С.В., 
Заспа Г.О., Куницька С.Ю., Півень О.Б. Катаєва Є.Ю., Салапатов В.І., 
39 
 
 
Метелап В.В., Олексюк В.В.; М-во освіти і науки України, Черкас, держ. 
технол. ун-т. - Черкаси : ЧДТУ, 2023. - 96 с. 
28 Процес моделювання предметної області. Гайд для бізнес-
аналітиків https://dou.ua/forums/topic/42366/  
29 Предметна область, моделювання предметної області 
https://studfile.net/preview/5474325/page:2/  
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40 
 
 
ДОДАТОК А 
Лістинг програми 
Auth.controller.ts 
import { AuthGuard } from '@nestjs/passport'; 
import { 
    Post, 
    Body, 
    HttpCode, 
    Controller, 
    Request, 
    ConflictException, 
    BadRequestException, 
    UsePipes, 
    ValidationPipe, 
    UseGuards, 
} from '@nestjs/common'; 
import { UserService } from '../../user/user.service'; 
import { UsersRequestDto } from '../../dtos/user/user.request.dto'; 
import { AuthService } from '../../auth/auth.service'; 
import { Public } from '../../guards/publickey'; 
 
@Controller('auth') 
export class AuthController { 
    constructor( 
        private userService: UserService, 
        private authService: AuthService, 
    ) { 
    } 
 
    @Public() 
41 
 
 
    @Post('register') 
    @HttpCode(201) 
    @UsePipes(new ValidationPipe()) 
    async create(@Body() data: UsersRequestDto): Promise<string> { 
        try { 
            await this.userService.createUser(data); 
            return 'Registration successful!'; 
        } catch (error) { 
            if (error.code === 'ER_DUP_ENTRY') { 
                throw new ConflictException('User with this email already exists'); 
            } else { 
                throw new BadRequestException(); 
            } 
        } 
    } 
 
    @Public() 
    @UseGuards(AuthGuard('local')) 
    @Post('login') 
    async login(@Request() req) { 
        const jwt = this.authService.login(req.user); 
        return Object.assign(await jwt, req.user); 
    } 
} 
Auth.module.ts 
import { Module } from '@nestjs/common'; 
import { JwtModule } from '@nestjs/jwt'; 
import { PassportModule } from '@nestjs/passport'; 
import { AuthController } from './auth.controller'; 
import { UserModule } from '../../user/user.module'; 
42 
 
 
import { AuthService } from '../../auth/auth.service'; 
import { LocalStrategy } from '../../strategy/local.strategy'; 
import { jwtConstants } from '../../auth/constants'; 
import { JwtStrategy } from '../../strategy/jwt.strategy'; 
 
@Module({ 
    imports: [ 
        UserModule, 
        PassportModule, 
        JwtModule.register({ 
            secret: jwtConstants.secret, 
            signOptions: { expiresIn: process.env.AUTHKEYEXPIRETIME }, 
        }), 
    ], 
    providers: [AuthService, LocalStrategy, JwtStrategy], 
    controllers: [AuthController], 
}) 
export class AuthApiModule {} 
 
Keys.controller.ts 
import { Body, Controller, Post, UsePipes, ValidationPipe, Headers, 
BadRequestException } from '@nestjs/common'; 
import { KeysService } from '../../../companies/keys/keys.service'; 
import { CompanyKeyDto } from '../../../dtos/company/company.key.dto'; 
import { Roles } from '../../../decorators/roles.decorator'; 
import { JwtService } from '@nestjs/jwt'; 
import { CompanyService } from '../../../companies/company.service'; 
import { UserService } from '../../../user/user.service'; 
 
@Controller('keys') 
43 
 
 
export class KeysController { 
    constructor( 
    private keyService: KeysService, 
        private jwtService: JwtService, 
        private companyService: CompanyService, 
        private userService: UserService 
    ) {} 
 
  @Post() 
  @Roles('ADMIN', 'MANAGER') 
  @UsePipes(new ValidationPipe()) 
  async createKey(@Headers('authorization') header, @Body() body: 
CompanyKeyDto) { 
      const JWTUser = this.jwtService.verify(header.split(' ')[1]) 
      const user = await this.userService.getUserByEmail(JWTUser.email); 
      const company = await 
this.companyService.getCompanyByRepresentative(user) 
      if (!company) throw new BadRequestException('We could not find a 
company where you represent it') 
      return this.keyService.createKey(body.key, company) 
    } 
} 
 
keys.module.ts 
import { Module } from '@nestjs/common'; 
import { KeyModule } from '../../../companies/keys/key.module'; 
import { KeysController } from './keys.controller'; 
import { CompanyModule } from '../../../companies/company.module'; 
import { JwtModule } from '@nestjs/jwt'; 
import { jwtConstants } from '../../../auth/constants'; 
44 
 
 
import { UserModule } from '../../../user/user.module'; 
 
@Module({ 
    imports: [ 
        KeyModule, 
        CompanyModule, 
        UserModule, 
        JwtModule.register({ secret: jwtConstants.secret }),], 
    controllers: [KeysController], 
}) 
export class KeysModule {} 
Companies.controller.ts 
import { 
    BadRequestException, 
    Body, 
    ConflictException, 
    Controller, 
    Get, 
    Param, 
    Post, 
    UsePipes, 
    ValidationPipe, 
} from '@nestjs/common'; 
import { CompanyService } from '../../companies/company.service'; 
import { CompanyRequestDto } from '../../dtos/company/company.request.dto'; 
import { Roles } from '../../decorators/roles.decorator'; 
import { companyGetDto } from '../../dtos/company/company.get.dto'; 
 
@Controller('companies') 
export class CompaniesController { 
45 
 
 
    constructor(private companyService: CompanyService) {} 
 
  @Roles('ADMIN') 
  @Post() 
  @UsePipes(new ValidationPipe()) 
    async addCompany(@Body() body: CompanyRequestDto) { 
        try { 
            return await this.companyService.addCompany(body); 
        } catch (error) { 
            if (error.code === 'ER_DUP_ENTRY') { 
                console.log(error.code); 
                throw new ConflictException( 
                    'Company with this email/name already exists', 
                ); 
            } else if ( 
                error.message.startsWith( 
                    'Could not find any entity of type "User" matching:', 
                ) 
            ) { 
                throw new BadRequestException('No user with this email found'); 
            } else { 
                throw new BadRequestException(); 
            } 
        } 
    } 
 
  @Roles('ADMIN') 
  @Get(':name') 
  async getCompanyInfo(@Param() name: companyGetDto) { 
      return await this.companyService.getCompanyByName(name.name); 
46 
 
 
  } 
} 
 
companies.module.ts 
import { Module } from '@nestjs/common'; 
import { CompaniesController } from './companies.controller'; 
import { CompanyModule } from '../../companies/company.module'; 
import { KeysModule } from './keys/keys.module'; 
 
@Module({ 
    imports: [CompanyModule, KeysModule], 
    controllers: [CompaniesController], 
}) 
export class CompaniesModule {} 
 
users.controller.ts 
import { 
    Get, 
    Post, 
    Body, 
    Param, 
    Controller, 
    ConflictException, 
    BadRequestException, 
    NotFoundException, 
    UsePipes, 
    ValidationPipe, 
} from '@nestjs/common'; 
 
import { UserService } from '../../user/user.service'; 
47 
 
 
import { UsersResponseDto } from '../../dtos/user/user.response.dto'; 
import { UserUpdateKeyDto } from '../../dtos/user/user.updateKey.dto'; 
import { UserUpdateRoleDto } from '../../dtos/user/user.updateRole.dto'; 
import { Roles } from '../../decorators/roles.decorator'; 
import { UserUpdateDriverDto } from '../../dtos/user/user.updateDriver.dto'; 
import { UserRoleEnum } from '../../enums/users/roles'; 
 
@Controller('user') 
export class UsersController { 
    constructor(private userService: UserService) { 
    } 
 
    @Roles('ADMIN') 
    @Get() 
    async findAll(): Promise<UsersResponseDto[]> { 
        try { 
            const users = await this.userService.getAllUsers(); 
            return users.map((user) => new UsersResponseDto(user)); 
        } catch (e) { 
            throw new BadRequestException(); 
        } 
    } 
 
    @Roles('ADMIN') 
    @Get(':userId') 
    async findOne(@Param('userId') id: number): Promise<UsersResponseDto> { 
        let user; 
        try { 
            user = await this.userService.getUserById(id); 
        } catch (e) { 
48 
 
 
            throw new BadRequestException(); 
        } 
        if (!user) throw new NotFoundException(); 
        return user; 
    } 
 
    @Roles('admin', 'manager') 
    @Post('updateKey') 
    @UsePipes(new ValidationPipe()) 
    async updateCompanyKey( 
        @Body() data: UserUpdateKeyDto, 
    ): Promise<UsersResponseDto> { 
        try { 
            const { email, key } = data; 
            const user = await this.userService.updateCompanyKey(email, key); 
            return new UsersResponseDto(user); 
        } catch (error) { 
            throw BadRequestException; 
        } 
    } 
 
    @Roles('admin', 'manager') 
    @Post('updateDriverRole') 
    @UsePipes(new ValidationPipe()) 
    async updateDriverRole(@Body() data: UserUpdateDriverDto): 
Promise<UsersResponseDto> { 
        let user; 
        try { 
            user = await this.userService.getUserByEmail(data.email); 
        } catch (e) { 
49 
 
 
            throw new NotFoundException(); 
        } 
        if (!user) throw new NotFoundException(); 
 
        if (user.role === 'ADMIN' || user.role === 'MANAGER') { 
            throw new BadRequestException('You can\'t update role of that user!'); 
        } 
        return new UsersResponseDto( 
            await this.userService.updateUserRole(user.email, 
UserRoleEnum.DRIVER), 
        ); 
    } 
 
    @Roles('admin') 
    @Post('updateRole') 
    @UsePipes(new ValidationPipe()) 
    async updateRole(@Body() data: UserUpdateRoleDto): 
Promise<UsersResponseDto> { 
        try { 
            const user = await this.userService.updateUserRole(data.email, 
data.role); 
            return new UsersResponseDto(user); 
        } catch (error) { 
            if (error.code === 'ER_WARN_DATA_TRUNCATED') { 
                throw new ConflictException('User role is not valid'); 
            } else { 
                throw new BadRequestException(); 
            } 
        } 
    } 
50 
 
 
} 
users.module.ts 
import { Module } from '@nestjs/common'; 
import { UsersController } from './users.controller'; 
import { UserModule } from '../../user/user.module'; 
 
@Module({ 
    imports: [UserModule], 
    controllers: [UsersController], 
}) 
export class UsersApiModule {} 
 
 
api.module.ts 
import { Module } from '@nestjs/common'; 
import { APP_GUARD } from '@nestjs/core'; 
import { UsersApiModule } from './users/users.module'; 
import { AuthApiModule } from './auth/auth.module'; 
import { JwtAuthGuard } from '../guards/jwt-auth.guard'; 
import { RolesGuard } from '../guards/roles.guard'; 
import { CompaniesModule } from './companies/companies.module'; 
 
@Module({ 
    imports: [UsersApiModule, AuthApiModule, CompaniesModule], 
    providers: [ 
        { 
            provide: APP_GUARD, 
            useClass: JwtAuthGuard, 
        }, 
        { 
51 
 
 
            provide: APP_GUARD, 
            useClass: RolesGuard, 
        }, 
    ], 
}) 
export class ApiModule {} 
 
auth.service.ts 
import { Injectable } from '@nestjs/common'; 
import { JwtService } from '@nestjs/jwt'; 
import { UserService } from '../user/user.service'; 
import { UserInterface } from '../user/interfaces/user.interface'; 
 
@Injectable() 
export class AuthService { 
    constructor( 
    private userService: UserService, 
    private jwtService: JwtService, 
    ) {} 
 
    async validateUser(email: string, pass: string): Promise<any> { 
        const user = await this.userService.getUserByEmail(email); 
        if (user && user.password_hash === pass) { 
            const { password_hash, ...result } = user; 
            return result; 
        } 
        return null; 
    } 
 
    async login(user: UserInterface): Promise<object> { 
52 
 
 
        const payload = { email: user.email, sub: user.name, role: user.role }; 
        return { 
            access_token: this.jwtService.sign(payload), 
        }; 
    } 
} 
 
constants.ts 
export const jwtConstants = { 
    secret: process.env.JWTSECRETKEY, 
}; 
 
cron.module.ts 
import { Module } from '@nestjs/common'; 
import { KeysRemoverCron } from './expiredKeysRemover.cron'; 
import { KeyModule } from '../../companies/keys/key.module'; 
import { LoggerModule } from '../logger/logger.module'; 
 
@Module({ 
    imports: [KeyModule, LoggerModule], 
    providers: [KeysRemoverCron], 
    exports: [KeysRemoverCron], 
}) 
export class CronModule {} 
 
expiredKeysRemover.cron.ts 
import { Injectable } from '@nestjs/common'; 
import { Cron } from '@nestjs/schedule'; 
import { KeysService } from '../../companies/keys/keys.service'; 
import { LoggerService } from '../logger/logger.service'; 
53 
 
 
 
@Injectable() 
export class KeysRemoverCron { 
    constructor( 
    private keysService: KeysService, 
    private logger: LoggerService, 
    ) {} 
 
  //Ones a day check 
  @Cron('0 0 * * *') 
    async handleCron() { 
        let deletedKeysCount = 0; 
        const deletedKeys = ''; 
        const allKeys = this.keysService.getAllKeys(); 
        const dateNow = new Date(); 
        allKeys.then((keys) => { 
            keys.forEach((key) => { 
                const keyWithDuration = key.createdAt.setMonth( 
                    key.createdAt.getMonth() + 
parseInt(process.env.COMPANYKEYDURATION), 
                ); 
                if (keyWithDuration < dateNow.getTime()) { 
                    deletedKeysCount += 1; 
                    this.keysService.deleteKey(key.id); 
                    deletedKeys.concat( 
                        'KEY: ', 
                        key.key, 
                        'COMPANY: ', 
                        key.company.companyName, 
                    ); 
54 
 
 
                } 
            }); 
        }); 
        const logText = `CRON: Deleting expired keys... ${deletedKeysCount} 
were deleted. ${deletedKeys}`; 
        this.logger.createLog(logText); 
    } 
} 
 
logger.module.ts 
import { Module } from '@nestjs/common'; 
import { LoggerService } from './logger.service'; 
 
@Module({ 
    providers: [LoggerService], 
    exports: [LoggerService], 
}) 
export class LoggerModule {} 
 
logger.service.ts 
import { Injectable } from '@nestjs/common'; 
import * as fs from 'fs'; 
 
@Injectable() 
export class LoggerService { 
    filePath = './log.txt'; 
 
    createLog(logValue: string): void { 
        fs.access(this.filePath, (err) => { 
            if (err) { 
55 
 
 
                fs.writeFile('log.txt', `${logValue} \n`, (e) => { 
                    if (e) throw err; 
                }); 
                return; 
            } 
            fs.appendFile('log.txt', `${logValue} \n`, (e) => { 
                if (e) throw err; 
            }); 
        }); 
    } 
} 
 
 
cors.middleware.ts 
import { Injectable, NestMiddleware } from '@nestjs/common'; 
import { Request, Response, NextFunction } from 'express'; 
 
@Injectable() 
export class CORSMiddleware implements NestMiddleware { 
    constructor() {} 
 
    use(req: Request, res: Response, next: NextFunction): any { 
        res.header('Access-Control-Allow-Origin', '*'); 
        res.header( 
            'Access-Control-Allow-Headers', 
            'X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, 
Access-Control-Request-Method,Access-Control-Request-Headers, 
Authorization', 
        ); 
        res.header( 
56 
 
 
            'Access-Control-Allow-Methods', 
            'HEAD, GET, POST, PUT, PATCH, DELETE, OPTIONS', 
        ); 
        next(); 
    } 
} 
 
logger.middleware.ts 
import { Injectable, NestMiddleware } from '@nestjs/common'; 
import { Request, Response, NextFunction } from 'express'; 
import { LoggerService } from '../logger/logger.service'; 
 
@Injectable() 
export class LoggerMiddleware implements NestMiddleware { 
    constructor(private logger: LoggerService) {} 
 
    use(req: Request, res: Response, next: NextFunction): any { 
        const dateOb = new Date().toISOString(); 
 
        const logInfo = `API: ${req.method} request to PATH: ${req.path}${ 
            Object.entries(req.params)[0][1] 
        } time: ${dateOb}`; 
 
        this.logger.createLog(logInfo); 
 
        next(); 
    } 
} 
 
websockets.service.ts 
57 
 
 
import { 
    ConnectedSocket, 
    MessageBody, 
    OnGatewayConnection, 
    OnGatewayDisconnect, 
    SubscribeMessage, 
    WebSocketGateway, 
    WebSocketServer, 
} from '@nestjs/websockets'; 
import { Socket } from 'socket.io'; 
import { JwtService } from '@nestjs/jwt'; 
import { UnauthorizedException } from '@nestjs/common'; 
 
@WebSocketGateway(81, { cors: true }) 
export class WebsocketService 
    implements OnGatewayConnection, OnGatewayDisconnect { 
    @WebSocketServer() server; 
 
    constructor(private jwtService: JwtService) { 
    } 
 
    handleConnection(socket: Socket) { 
        const authorizationHeader = socket.handshake.headers.authorization; 
        try { 
            const token = this.jwtService.verify(authorizationHeader.split(' ')[1]); 
            if (!token) { 
                socket.emit('Error', new UnauthorizedException()); 
                socket.disconnect(); 
            } 
        } catch (e) { 
58 
 
 
            socket.emit('Error', new UnauthorizedException(e)); 
            socket.disconnect(); 
        } 
    } 
 
    handleDisconnect(socket) { 
        const [key] = socket.adapter.rooms.keys(); 
        const rooms = this.server.sockets.adapter.rooms.get(key); 
        const usersCount = rooms ? rooms.size : 0; 
        this.server.to(key).emit('users', usersCount); 
    } 
 
    @SubscribeMessage('room') 
    handleRoom( 
        @MessageBody('room') room: string, 
        @ConnectedSocket() socket: Socket, 
    ) { 
        socket.join(room); 
        const rooms = this.server.sockets.adapter.rooms.get(room); 
        this.server.to(room).emit('users', rooms.size); 
    } 
 
    @SubscribeMessage('chat') 
    handleChat( 
        @MessageBody('text') text: string, 
        @MessageBody('room') room: string, 
        @MessageBody('sender') sender: string, 
        @ConnectedSocket() client: Socket, 
    ) { 
        client.broadcast 
59 
 
 
            .to(room) 
            .emit('chat', { sender: JSON.parse(sender).name, text: text }); 
    } 
 
    @SubscribeMessage('coordinates') 
    handleCoordinates( 
        @MessageBody('room') room: string, 
        @MessageBody('lat') lat: string, 
        @MessageBody('lng') lng: string, 
    ) { 
        this.server.to(room).emit('coordinates', { lat: lat, lng: lng }); 
    } 
} 
 
websockets.module.ts 
import { Global, Module } from '@nestjs/common'; 
import { JwtModule } from '@nestjs/jwt'; 
import { WebsocketService } from './websocket.service'; 
import { jwtConstants } from '../../auth/constants'; 
 
@Global() 
@Module({ 
    imports: [JwtModule.register({ secret: jwtConstants.secret })], 
    providers: [WebsocketService], 
    exports: [WebsocketService], 
}) 
export class WebsocketsModule {} 
 
company.interface.ts 
import { User } from '../../user/user.entity'; 
60 
 
 
import { Key } from '../keys/key.entity'; 
 
export interface CompanyInterface { 
  id?: number; 
  companyName: string; 
  email: string; 
  Representative: User; 
  keys?: Key[]; 
  createdAt: Date; 
  updatedAt: Date; 
} 
 
 
key.interface.ts 
import { Company } from '../company.entity'; 
 
export interface KeyInterface { 
  id?: number; 
  key: string; 
  company: Company; 
  createdAt: Date; 
  updatedAt: Date; 
} 
 
key.entity.ts 
import { 
    Entity, 
    Column, 
    CreateDateColumn, 
    UpdateDateColumn, 
61 
 
 
    PrimaryGeneratedColumn, 
    ManyToOne, 
} from 'typeorm'; 
import { KeyInterface } from '../interfaces/key.interface'; 
import { Company } from '../company.entity'; 
 
@Entity() 
export class Key implements KeyInterface { 
  @PrimaryGeneratedColumn() 
      id: number; 
 
  @ManyToOne(() => Company, (company) => company.keys) 
      company: Company; 
 
  @Column() 
      key: string; 
 
  @CreateDateColumn({ update: false }) 
      createdAt: Date; 
 
  @UpdateDateColumn({ update: true }) 
      updatedAt: Date; 
} 
 
 
key.module.ts 
import { Module } from '@nestjs/common'; 
import { TypeOrmModule } from '@nestjs/typeorm'; 
import { KeysService } from './keys.service'; 
import { KeyRepository } from './key.repository'; 
62 
 
 
 
@Module({ 
    imports: [TypeOrmModule.forFeature([KeyRepository]) 
    ], 
    providers: [ 
        KeysService, 
    ], 
    exports: [KeysService], 
}) 
export class KeyModule { 
} 
 
key.repository.ts 
import { EntityRepository, Repository } from 'typeorm'; 
import { Key } from './key.entity'; 
 
@EntityRepository(Key) 
export class KeyRepository extends Repository<Key> { 
    findById(keyId: string): Promise<Key> { 
        return this.findOne({ 
            where: { id: keyId }, 
        }); 
    } 
} 
 
key.service.ts 
import { Injectable } from '@nestjs/common'; 
import { InjectRepository } from '@nestjs/typeorm'; 
import { KeyRepository } from './key.repository'; 
import { Company } from '../company.entity'; 
63 
 
 
 
@Injectable() 
export class KeysService { 
    constructor( 
    @InjectRepository(KeyRepository) 
    private keyRepository: KeyRepository, 
    ) {} 
 
    async getAllKeys() { 
        return this.keyRepository.find(); 
    } 
 
    async createKey(key: string, company: Company) { 
        return this.keyRepository.save({ 
            company: company, 
            key: key 
        }) 
    } 
 
    async deleteKey(keyId: number) { 
        return this.keyRepository.delete(keyId); 
    } 
} 
 
company.entity.ts 
import { 
    Entity, 
    Column, 
    CreateDateColumn, 
    UpdateDateColumn, 
64 
 
 
    PrimaryGeneratedColumn, 
    Unique, 
    OneToOne, 
    JoinColumn, 
    OneToMany, 
} from 'typeorm'; 
 
import { CompanyInterface } from './interfaces/company.interface'; 
import { User } from '../user/user.entity'; 
import { Key } from './keys/key.entity'; 
 
@Entity() 
@Unique('email', ['email']) 
@Unique('companyName', ['companyName']) 
export class Company implements CompanyInterface { 
  @PrimaryGeneratedColumn() 
      id: number; 
 
  @Column({ type: 'varchar', length: 32, nullable: false, unique: false }) 
      companyName: string; 
 
  @Column({ type: 'varchar', length: 100, nullable: false }) 
      email: string; 
 
  @OneToOne(() => User) 
  @JoinColumn() 
      Representative: User; 
 
  @OneToMany(() => Key, (key) => key.company) 
      keys?: Key[]; 
65 
 
 
 
  @CreateDateColumn({ update: false }) 
      createdAt: Date; 
 
  @UpdateDateColumn({ update: true }) 
      updatedAt: Date; 
} 
 
company.module.ts 
import { Module } from '@nestjs/common'; 
import { TypeOrmModule } from '@nestjs/typeorm'; 
import { CompanyService } from './company.service'; 
import { KeyModule } from './keys/key.module'; 
import { CompanyRepository } from './company.repository'; 
import { UserModule } from '../user/user.module'; 
 
@Module({ 
    imports: [ 
        TypeOrmModule.forFeature([CompanyRepository]), 
        KeyModule, 
        UserModule, 
    ], 
    providers: [CompanyService], 
    exports: [CompanyService], 
}) 
export class CompanyModule {} 
 
company.repository.ts 
import { EntityRepository, Repository } from 'typeorm'; 
import { Company } from './company.entity'; 
66 
 
 
 
@EntityRepository(Company) 
export class CompanyRepository extends Repository<Company> { 
    findByEmail(email: string): Promise<Company> { 
        return this.findOne({ 
            where: { email }, 
        }); 
    } 
} 
company.service.ts 
import { Injectable } from '@nestjs/common'; 
import { InjectRepository } from '@nestjs/typeorm'; 
import { CompanyRepository } from './company.repository'; 
import { UserService } from '../user/user.service'; 
import { UserRoleEnum } from '../enums/users/roles'; 
import { Company } from './company.entity'; 
import { CompanyRequestDto } from '../dtos/company/company.request.dto'; 
import { User } from '../user/user.entity'; 
 
@Injectable() 
export class CompanyService { 
    constructor( 
    @InjectRepository(CompanyRepository) 
    private companiesRepository: CompanyRepository, 
    private userService: UserService, 
    ) {} 
 
    async addCompany(company: CompanyRequestDto) { 
        const newCompany = new Company(); 
        let user; 
67 
 
 
        try { 
            user = this.userService.getUserByEmail(company.representativeEmail); 
        } catch (e) { 
            return e.code; 
        } 
        await this.userService.updateUserRole( 
            company.representativeEmail, 
            UserRoleEnum.MANAGER, 
        ); 
        newCompany.email = company.email; 
        newCompany.Representative = await user; 
        newCompany.companyName = company.name; 
        return this.companiesRepository.save(newCompany); 
    } 
 
    async getCompanyByName(companyName: string) { 
        return this.companiesRepository.findOne({ 
            where: { companyName: companyName }, 
            relations: ['Representative', 'keys'], 
        }); 
    } 
 
    async getCompanyByRepresentative(user: User) { 
        return this.companiesRepository.findOne({ 
            relations: ['Representative'], 
            where: { Representative: user } 
        }); 
    } 
} 
 
68 
 
 
roles.decorator.ts 
import { SetMetadata } from '@nestjs/common'; 
 
export const Roles = (...roles: string[]) => SetMetadata('roles', roles); 
 
company.get.dto 
import { IsNotEmpty, IsString } from 'class-validator'; 
 
export class companyGetDto { 
  @IsString() 
  @IsNotEmpty() 
  readonly name: string; 
} 
 
company.key.dto 
import { IsNotEmpty, IsString } from 'class-validator'; 
 
export class CompanyKeyDto { 
  @IsString() 
  @IsNotEmpty() 
  readonly key: string; 
} 
 
company.request.dto 
import { IsEmail, IsNotEmpty, IsString, Length } from 'class-validator'; 
 
export class CompanyRequestDto { 
  @IsString() 
  @IsNotEmpty() 
  @Length(1, 64, { message: 'name must be between 1 and 64 characters' }) 
69 
 
 
  readonly name: string; 
 
  @IsString() 
  @IsNotEmpty() 
  @IsEmail() 
  @Length(5, 64, { message: 'email must be between 5 and 64 characters' }) 
  readonly email: string; 
 
  @IsNotEmpty() 
  @IsEmail() 
  readonly representativeEmail: string; 
} 
 
user.request.dto 
import { IsEmail, IsNotEmpty, IsString, Length } from 'class-validator'; 
 
export class UsersRequestDto { 
  @IsString() 
  @IsNotEmpty() 
  @Length(1, 64, { message: 'name must be between 1 and 64 characters' }) 
  readonly name: string; 
 
  @IsString() 
  @IsNotEmpty() 
  @IsEmail() 
  @Length(5, 64, { message: 'email must be between 5 and 64 characters' }) 
  readonly email: string; 
 
  @IsString() 
  @IsNotEmpty() 
70 
 
 
  @Length(5, 64, { message: 'email must be between 5 and 64 characters' }) 
  readonly password_hash; 
} 
 
 
user.response.dto 
import { UserInterface } from '../../user/interfaces/user.interface'; 
import { UserRoleEnum } from '../../enums/users/roles'; 
 
export class UsersResponseDto { 
    readonly id?: number; 
 
    readonly name: string; 
 
    readonly email: string; 
 
    readonly password_hash: string; 
 
    readonly role: UserRoleEnum; 
 
    readonly company_key?: string; 
 
    readonly createdAt: Date; 
 
    readonly updatedAt: Date; 
 
    constructor(data?: Partial<UserInterface>) { 
        if (data) { 
            this.id = data.id; 
            this.role = data.role; 
71 
 
 
            this.name = data.name; 
            this.email = data.email; 
            this.createdAt = data.createdAt; 
            this.company_key = data.company_key; 
        } 
    } 
} 
 
user.updateDriverRole.dto.ts 
 
export class UserUpdateDriverDto { 
    email: string 
} 
user.updateKey.dto.ts 
import { IsEmail, IsNotEmpty, IsString, Length } from 'class-validator'; 
 
export class UserUpdateKeyDto { 
  @IsString() 
  @IsNotEmpty() 
  readonly key: string; 
 
  @IsString() 
  @IsNotEmpty() 
  @IsEmail() 
  @Length(5, 64, { message: 'email must be between 5 and 64 characters' }) 
  readonly email: string; 
} 
 
 
user.updateRole.dto.ts 
72 
 
 
import { IsEmail, IsEnum, IsNotEmpty, IsString, Length } from 'class-validator'; 
import { UserRoleEnum } from '../../enums/users/roles'; 
 
export class UserUpdateRoleDto { 
  @IsNotEmpty() 
  @IsEnum(UserRoleEnum, { message: 'Role is not valid' }) 
  readonly role: UserRoleEnum; 
 
  @IsString() 
  @IsNotEmpty() 
  @IsEmail() 
  @Length(5, 64, { message: 'Email must be between 5 and 64 characters' }) 
  readonly email: string; 
} 
 
roles.enum.ts 
export enum UserRoleEnum { 
  USER = 'USER', 
  DRIVER = 'DRIVER', 
  MANAGER = 'MANAGER', 
  ADMIN = 'ADMIN', 
} 
 
jwt-auth.guard.ts 
import { ExecutionContext, Injectable } from '@nestjs/common'; 
import { AuthGuard } from '@nestjs/passport'; 
import { Reflector } from '@nestjs/core'; 
import { IS_PUBLIC_KEY } from './publickey'; 
 
@Injectable() 
73 
 
 
export class JwtAuthGuard extends AuthGuard('jwt') { 
    constructor(private reflector: Reflector) { 
        super(); 
    } 
 
    canActivate(context: ExecutionContext) { 
        const isPublic = 
this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [ 
            context.getHandler(), 
            context.getClass(), 
        ]); 
        if (isPublic) { 
            return true; 
        } 
        return super.canActivate(context); 
    } 
} 
 
 
publickey.ts 
import { SetMetadata } from '@nestjs/common'; 
 
export const IS_PUBLIC_KEY = 'isPublic'; 
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); 
 
roles.guard.ts 
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; 
import { Reflector } from '@nestjs/core'; 
 
@Injectable() 
74 
 
 
export class RolesGuard implements CanActivate { 
    constructor(private reflector: Reflector) {} 
 
    canActivate(context: ExecutionContext): boolean { 
        const roles = this.reflector.get<string[]>('roles', context.getHandler()); 
        if (!roles) { 
            return true; 
        } 
        const request = context.switchToHttp().getRequest(); 
        const user = request.user; 
 
        return roles.some( 
            (element) => element.toLowerCase() === user.role.toLowerCase(), 
        ); 
    } 
} 
 
jwt.strategy.js 
import { ExtractJwt, Strategy } from 'passport-jwt'; 
import { PassportStrategy } from '@nestjs/passport'; 
import { Injectable } from '@nestjs/common'; 
import { jwtConstants } from '../auth/constants'; 
 
@Injectable() 
export class JwtStrategy extends PassportStrategy(Strategy) { 
    constructor() { 
        super({ 
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 
            ignoreExpiration: false, 
            secretOrKey: jwtConstants.secret, 
75 
 
 
        }); 
    } 
 
    async validate(payload: any) { 
        return { userName: payload.sub, email: payload.email, role: payload.role }; 
    } 
} 
 
local.strategy.ts 
import { Strategy } from 'passport-local'; 
import { PassportStrategy } from '@nestjs/passport'; 
import { Injectable, UnauthorizedException } from '@nestjs/common'; 
import { AuthService } from '../auth/auth.service'; 
 
@Injectable() 
export class LocalStrategy extends PassportStrategy(Strategy) { 
    constructor(private authService: AuthService) { 
        super(); 
    } 
 
    async validate(userEmail: string, password: string): Promise<any> { 
        const user = await this.authService.validateUser(userEmail, password); 
        if (!user) { 
            throw new UnauthorizedException(); 
        } 
        return user; 
    } 
} 
 
user.interface.ts 
76 
 
 
import { UserRoleEnum } from '../../enums/users/roles'; 
 
export interface UserInterface { 
  id?: number; 
  name: string; 
  email: string; 
  password_hash: string; 
  role: UserRoleEnum; 
  company_key?: string; 
  createdAt: Date; 
  updatedAt: Date; 
} 
 
 
user.entity.ts 
import { 
    Entity, 
    Column, 
    CreateDateColumn, 
    UpdateDateColumn, 
    PrimaryGeneratedColumn, 
    Unique, 
} from 'typeorm'; 
 
import { UserInterface } from './interfaces/user.interface'; 
import { UserRoleEnum } from '../enums/users/roles'; 
 
@Entity() 
@Unique('email', ['email']) 
export class User implements UserInterface { 
77 
 
 
  @PrimaryGeneratedColumn() 
      id: number; 
 
  @Column({ type: 'varchar', length: 32, nullable: false, unique: false }) 
      name: string; 
 
  @Column({ type: 'varchar', length: 100, nullable: false }) 
      email: string; 
 
  @Column({ type: 'varchar', length: 100, nullable: false }) 
      password_hash: string; 
 
  @Column({ type: 'varchar', length: 100, nullable: true }) 
      company_key?: string; 
 
  @Column({ type: 'enum', enum: UserRoleEnum, default: 
UserRoleEnum.USER }) 
      role: UserRoleEnum; 
 
  @CreateDateColumn({ update: false }) 
      createdAt: Date; 
 
  @UpdateDateColumn({ update: true }) 
      updatedAt: Date; 
} 
 
user.module.ts 
import { Module } from '@nestjs/common'; 
import { TypeOrmModule } from '@nestjs/typeorm'; 
import { UserService } from './user.service'; 
78 
 
 
import { UserRepository } from './user.repository'; 
 
@Module({ 
    imports: [TypeOrmModule.forFeature([UserRepository])], 
    providers: [UserService], 
    exports: [UserService], 
}) 
export class UserModule {} 
 
user.repository.ts 
import { EntityRepository, Repository } from 'typeorm'; 
 
import { User } from './user.entity'; 
 
@EntityRepository(User) 
export class UserRepository extends Repository<User> { 
    findByEmail(email: string): Promise<User> { 
        return this.findOne({ 
            where: { email }, 
        }); 
    } 
} 
 
 
user.service.ts 
import { Injectable } from '@nestjs/common'; 
import { InjectRepository } from '@nestjs/typeorm'; 
import { UserInterface } from './interfaces/user.interface'; 
import { User } from './user.entity'; 
import { UserRoleEnum } from '../enums/users/roles'; 
79 
 
 
import { UserRepository } from './user.repository'; 
 
@Injectable() 
export class UserService { 
    constructor( 
    @InjectRepository(UserRepository) 
    private usersRepository: UserRepository, 
    ) {} 
 
    async updateCompanyKey(userEmail: string, companyKey: string): 
Promise<User> { 
        const user = await this.usersRepository.findOneOrFail({ 
            where: `email = "${userEmail}"`, 
        }); 
        user.company_key = companyKey; 
 
        return this.usersRepository.save(user); 
    } 
 
    async updateUserRole( 
        userEmail: string, 
        newRole: UserRoleEnum, 
    ): Promise<User> { 
        const user = await this.usersRepository.findOneOrFail({ 
            where: `email = "${userEmail}"`, 
        }); 
        user.role = newRole; 
 
        return this.usersRepository.save(user); 
    } 
80 
 
 
 
    async getAllUsers(): Promise<User[]> { 
        return this.usersRepository.find({}); 
    } 
 
    async getUserById(id: number): Promise<User> { 
        return this.usersRepository.findOne({ 
            where: { id }, 
        }); 
    } 
 
    async getUserByEmail(userEmail: string): Promise<User> { 
        return this.usersRepository.findOne({ 
            where: `email = "${userEmail}"`, 
        }); 
    } 
 
    async createUser(user: Partial<UserInterface>): Promise<UserInterface> { 
        return this.usersRepository.save({ 
            name: user.name, 
            email: user.email, 
            password_hash: user.password_hash, 
            role: UserRoleEnum[user.role], 
            company_key: user.company_key, 
        }); 
    } 
} 
 
app.module.ts 
import { MiddlewareConsumer, Module } from '@nestjs/common'; 
81 
 
 
import { TypeOrmModule } from '@nestjs/typeorm'; 
import { ScheduleModule } from '@nestjs/schedule'; 
import config from '../ormconfig'; 
import { ApiModule } from './api/api.module'; 
import { LoggerMiddleware } from './common/middewares/LoggerMiddleware'; 
import { CompanyModule } from './companies/company.module'; 
import { CronModule } from './common/cron/cron.module'; 
import { LoggerModule } from './common/logger/logger.module'; 
import { WebsocketsModule } from './common/web-
sockets/websockets.module'; 
import { CORSMiddleware } from './common/middewares/CORSMiddleware'; 
 
@Module({ 
    imports: [ 
        TypeOrmModule.forRoot(config), 
        ScheduleModule.forRoot(), 
        ApiModule, 
        CompanyModule, 
        CronModule, 
        LoggerModule, 
        WebsocketsModule, 
    ], 
}) 
export class AppModule { 
    configure(consumer: MiddlewareConsumer) { 
        consumer 
            .apply(LoggerMiddleware) 
            .forRoutes('*') 
            .apply(CORSMiddleware) 
            .forRoutes('*'); 
82 
 
 
    } 
} 
 
main.ts 
import { NestFactory } from '@nestjs/core'; 
import { AppModule } from './app.module'; 
 
async function bootstrap() { 
    const app = await NestFactory.create(AppModule, { 
        cors: true, 
    }); 
    await app.listen(3000); 
} 
 
bootstrap(); 
 
orm.config.ts 
import { MysqlConnectionOptions } from 
'typeorm/driver/mysql/MysqlConnectionOptions'; 
require('dotenv').config() 
 
const config: MysqlConnectionOptions = { 
    type: "mysql", 
    database: process.env.DATABASE, 
    username: process.env.DBUSERNAME, 
    password: process.env.PASSWORD, 
    host: process.env.DBHOST, 
    entities: ['dist/src/**/*.entity.js'], 
    synchronize: true 
}; 
83 
 
 
 
export default config;  
84 
 
 
ДОДАТОК Б 
Документація до енд-поінтів 
Endpoints description: 
All DTOs can be found in src/dtos 
/auth/login POST: takes username and password object. If login is successful 
returns object of JWT key with role email and name properties in it. And user 
object. 
/auth/registration POST: takes userRequestDto style data and appends user to 
the database. Email must be unique. 
/companies POST: (requires JWT token with role: ADMIN) takes 
companyRequestDto style data and appends new company to the database. 
/companies/:name GET: (requires JWT token with role: ADMIN) return all info 
about company with that name. 
/user GET: (requires JWT token with role: ADMIN) return all users from 
database. 
/user/:id GET: (requires JWT token with role: ADMIN) return one user with a 
specific ID. 
/user/updateCompanyKey POST: (requires JWT token with role: 
ADMIN/MANAGER) update user company key. 
/user/updateDriverRole POST: (requires JWT token with role: 
ADMIN/MANAGER) update user role to DRIVER. 
/user/updateRole POST: (requires JWT token with role: ADMIN) update user 
role for one of ENUM roles that available in src/enums folder 
 
85