Please use this identifier to cite or link to this item:
https://er.chdtu.edu.ua/handle/ChSTU/6191Full metadata record
| DC Field | Value | Language |
|---|---|---|
| dc.contributor.advisor | Підгорний, Микола Володимирович | - |
| dc.contributor.author | Ляшенко, Ростислав Іванович | - |
| dc.date.accessioned | 2025-11-25T07:40:09Z | - |
| dc.date.available | 2025-11-25T07:40:09Z | - |
| dc.date.issued | 2024-06-12 | - |
| dc.identifier.uri | https://er.chdtu.edu.ua/handle/ChSTU/6191 | - |
| dc.description.abstract | На сьогоднішній день реалізація вторинних товарів через Інтернет набула широкого розповсюдження та стала невід'ємною частиною електронної комерції. Хоча ідея продажу вживаних речей не нова, але саме в останні роки ця галузь почала стрімко розвиватися. Необхідність в створенні веб-орієнтованого сервісу для надання послуг з реалізації вторинних товарів обумовлює актуальність обраної теми кваліфікаційної роботи бакалавра. Проєктування та розробка такого сервісу вимагає глибокого аналізу потреб ринку, розробки зручного інтерфейсу користувача та впровадження найсучасніших методів безпеки та захисту даних. Мета кваліфікаційної роботи бакалавра –створення веб-орієнтованого сервісу для надання послуг з реалізації второнних товарів. Об’єкт дослідження: процеси реалізації товару і оформлення онлайн замовлень. Предмет дослідження: веб-орієнтований сервіс для надання послуг з реалізації вторинних товарів. | uk_UA |
| dc.language.iso | uk | uk_UA |
| dc.subject | ПРЕДМЕТНА ОБЛАСТЬ | uk_UA |
| dc.subject | ВЕБ-ОРІЄНТОВАНИЙ СЕРВІС | uk_UA |
| dc.subject | ВТОРИННІ ТОВАРИ | uk_UA |
| dc.subject | ПРОЄКТУВАННЯ | uk_UA |
| dc.subject | БАЗА ДАНИХ | uk_UA |
| dc.subject | REDUX TOOLKIT | uk_UA |
| dc.subject | LIQPAY. | uk_UA |
| dc.title | Веб-орієнтовний сервіс для надання послуг з реалізації вторинних товарів | uk_UA |
| dc.type | Bachelor Thesis | uk_UA |
| Appears in Collections: | 122 Комп’ютерні науки (Комп’ютерні науки та прикладне програмування) | |
Files in This Item:
| File | Description | Size | Format | |
|---|---|---|---|---|
| Пояснювальна записка_Ляшенко Ростислав_КНС-2201_2023-2024.pdf Restricted Access | 5.12 MB | Adobe PDF | View/Open Request a copy |
Items in DSpace are protected by copyright, with all rights reserved, unless otherwise indicated.
Extracted text
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
ЧЕРКАСЬКИЙ ДЕРЖАВНИЙ ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ
Факультет інформаційних технологій і систем
Кафедра комп’ютерних наук та системного аналізу
Пояснювальна записка
до кваліфікаційної роботи
Бакалавра
(освітньо-кваліфікаційний рівень)
на тему: «Веб-орієнтований сервіс для надання послуг з реалізації вторинних
товарів»
Виконав: студент 4 курсу, групи КНС-2201
спеціальності 122 «Комп’ютерні науки»
(шифр і назва спеціальності)
освітня програма «Комп’ютерні науки та
(назва освітньої програми)
прикладне програмування
Ляшенко Ростислав Іванович
Керівник __________ Підгорний М.В.
(прізвище та ініціали)
Рецензент __________ Мельник В. П.
(прізвище та ініціали)
Черкаси 2024 року
Черкаський державний технологічний університет
Факультет Інформаційних технологій і систем
Кафедра Комп’ютерних наук та системного аналізу
Освітньо-кваліфікаційний рівень Бакалавр
Спеціальність 122 – Комп’ютерні науки
Освітня програма Комп’ютерні науки та прикладне програмування
ЗАТВЕРДЖУЮ
Завідувач кафедри КНСА
_______________ Юрій ТРИУС
«____» _____________ 2024 р.
ЗАВДАННЯ
на кваліфікаційну роботу бакалавра студенту
Ляшенку Ростиславу Івановичу
(прізвище, ім’я, по батькові)
1. Тема роботи Веб-орієнтований сервіс для надання послуг з реалізації второнних товарів
Керівник роботи Підгорний М.В., к.т.н., професор
(прізвище, ім’я, по батькові, науковий ступінь, вчене звання)
затверджені наказом університету від «26» лютого 2024 р. №60/04.
2. Строк подання студентом роботи до 10 червня 2024 року
3. Вихідні дані до роботи:
Практичні навики роботи з інформаційними ресурсами. Робота з базами даних.
Звіт з виробничої практики. Звіт з переддипломної практики.
4. Зміст пояснювальної записки (перелік питань, що їх належить розробити):
Вступ
4.1. Дослідження предметної області.
4.2. Проєктування та розробка веб-орієнтованого сервісу для надання посуг з реалізації
вторинних товарів
4.3. Програмна реалізація.
Висновки.
5. Перелік додатків (з точним зазначенням назв додатків):
5.1. Додаток А. Специфікація 482.ЧДТУ. 42287-01.
5.2. Додаток Б. Текст програми.
5.3. Додаток В. Інструкція користувача.
5.4. Додаток Г. Структура бази даних.
5.5. Презентація у вигляді 29 слайдів.
6. Консультанти розділів роботи
Прізвище, ініціали та Підпис, дата
Розділ посада
завдання видав завдання прийняв
Консультанта
7. Дата видачі завдання 15.01.2024
КАЛЕНДАРНИЙ ПЛАН
Строк виконання
№ з/п Назва етапів кваліфікаційної роботи бакалавра Примітка
етапів роботи
1 Видача завдання на кваліфікаційну роботу
15.01.2024 Виконано
бакалавра.
2 Аналіз літературних джерел, об’єкту та
до 12.02.2024 Виконано
предмету дослідження.
3 Написання теоретичного розділу
до 18.03.2024 Виконано
кваліфікаційної роботи бакалавра.
4 Написання аналітичного розділу кваліфікаційної
до 01.04.2024 Виконано
роботи бакалавра.
5 Написання практичних розділів й висновків до
до 01.05.2024 Виконано
кваліфікаційної роботи бакалавра.
6 Передзахист кваліфікаційної роботи бакалавра
03.06.2024 Виконано
на засіданні кафедри КНСА.
7 Подання роботи завідувачу кафедри КНСА. до 10.06.2024 Виконано
8 Захист кваліфікаційної роботи бакалавра. 12.06.2024 Виконано
Студент _____________________________ Ростислав ЛЯШЕНКО
(підпис)
Керівник роботи ____________________________ Микола ПІДГОРНИЙ
(підпис)
РЕФЕРАТ
Кваліфікаційна робота бакалавра містить: 62 с., 34 рис., 2 таблиці, 10
використаних джерел, 4 додатки.
Актуальність теми. На сьогоднішній день реалізація вторинних товарів через
Інтернет набула широкого розповсюдження та стала невід'ємною частиною
електронної комерції. Хоча ідея продажу вживаних речей не нова, але саме в останні
роки ця галузь почала стрімко розвиватися. За даними на 2020 рік, обсяг цього
сектору оцінювався у $25 млрд. Це вказує на постійне зростання популярності та
попиту на такі послуги серед споживачів. Найважливішими критеріями у цій галузі
є забезпечення зручності користувачів та ефективність процесу реалізації товарів.
Важливою складовою є організація доставки та забезпечення безпеки покупців і
продавців. Сучасні веб-сервіси для реалізації вторинних товарів намагаються
вирішити ці питання, використовуючи новітні інформаційні технології та методи
логістики. Необхідність в створенні веб-орієнтованого сервісу для надання послуг з
реалізації вторинних товарів обумовлює актуальність обраної теми кваліфікаційної
роботи бакалавра. Проєктування та розробка такого сервісу вимагає глибокого
аналізу потреб ринку, розробки зручного інтерфейсу користувача та впровадження
найсучасніших методів безпеки та захисту даних.
Мета роботи і задачі дослідження. Мета кваліфікаційної роботи бакалавра –
створення веб-орієнтованого сервісу для надання послуг з реалізації второнних
товарів.
Для досягнення поставленої мети були поставлені такі завдання:
дослідити предметну область;
виконати порівняльний аналіз існуючих веб-орієнтованих сервісів для надання
послуг з реалізації вторинних товарів;
розробити схему базу даних і реалізувати її за допомогою сучасних СУБД;
обрати програмно-технічні засоби для реалізації веб-орієнтованого сервісу;
розробити веб-орієнтовий сервіс для надання послуг з реалізації вторинних
товарів та виконати його тестування.
Об’єкт дослідження: процеси реалізації товару і оформлення онлайн
замовлень.
Предмет дослідження: веб-орієнтований сервіс для надання послуг з
реалізації вторинних товарів.
Методи дослідження. Для вирішення поставлених завдань були застосовані
наступні методи дослідження: теоретичний (аналіз електронної літератури з
проблеми дослідження); емпіричний – полягав у спостереженні та налаштуванні
веб-орієнтованого сервісу для надання послуг з реалізації вторинних товарів;
порівняльний – для здійснення порівнянь існуючих аналогів веб-орієнтованих
сервісів для надання послуг з реалізації вторинних товарів.
Перелік ключових слів: ПРЕДМЕТНА ОБЛАСТЬ, ВЕБ-ОРІЄНТОВАНИЙ
СЕРВІС, ВТОРИННІ ТОВАРИ, ПРОЄКТУВАННЯ, БАЗА ДАНИХ, NESTJS.
REACT, REDUX TOOLKIT, LIQPAY.
ABSTRACT
Master’s work contains. 62 pages, 33 figures, 2 table, 10 sources used, 4
attachments.
Actuality of theme. Today, the online resale of second-hand goods has become
widespread and an integral part of e-commerce. Although the concept of selling used items
is not new, in recent years, this sector has experienced rapid growth. According to 2020
data, the volume of this sector was estimated at $25 billion, indicating a continuous
increase in popularity and demand for such services among consumers. The most
important criteria in this industry are ensuring user convenience and the efficiency of the
goods realization process. An important component is the organization of delivery and
ensuring the safety of both buyers and sellers. Modern web services for the resale of
second-hand goods strive to address these issues by utilizing the latest information
technologies and logistics methods. The necessity to create a web-oriented service for
providing second-hand goods resale services underscores the relevance of the chosen topic
for a bachelor's thesis. The design and development of such a service require in-depth
analysis of market needs, the development of a user-friendly interface, and the
implementation of state-of-the-art security and data protection methods..
Purpose and tasks of the research. The goal of the bachelor's thesis is to create a
web-oriented service for providing second-hand goods resale services.
To achieve this goal, the following tasks were set:
reseach the subject area;
to perform a comparative analysis of existing web-oriented services for providing
second-hand goods resale services;
develop a database schema and implement it using modern database management
systems (DBMS);
choose software and technical tools for implementing the web-oriented service;
develop a web-oriented service for providing second-hand goods resale services and
conduct its testing;
The object of research: The processes of product publication and online order
placement.
The subject of research: Web-oriented service for providing second-hand goods
resale services.
Research methods: To address the stated objectives, the following research
methods were employed: theoretical (analysis of electronic literature on the research
issue); empirical – involved observation and adjustment of the web-oriented service for
providing second-hand goods resale services; comparative – for making comparisons of
existing analogs of web-oriented services for providing second-hand goods resale services.
The list of keywords: SUBJECT AREA, WEB-ORIENTED SERVICE, SECOND-
HAND GOODS, DESIGN, DATABASE, NESTJS, REACT, REDUX TOOLKIT,
LIQPAY.
7
ЗМІСТ
ПЕРЕЛІК УМОВНИХ ПОЗНАЧЕНЬ, СИМВОЛІВ, СКОРОЧЕНЬ І ТЕРМІНІВ ........ 9
ВСТУП ................................................................................................................................ 10
1 ДОСЛІДЖЕННЯ ПРЕДМЕТНОЇ ОБЛАСТІ ............................................................... 12
1.1 Аналіз сучасного стану веб-орієнтованих сервісів для надання послуг з
реалізації вторинних ресурсів…………………… ...................................................... 13
1.2 Огляд існуючих веб-орієнтованих сервісів для надання послуг з реалізації
вторинних товарів .......................................................................................................... 16
1.2.1 Аналіз сервісу «OLX» ..................................................................................... 16
1.2.2 Аналіз сервісу «Prom» ..................................................................................... 18
1.3 Аналіз порівнянь існуючих рішень розглянутих реалізацій та постановка
завдання……………….. ................................................................................................ 19
Висновки до розділу 1 ................................................................................................... 22
2 ПРОЄКТУВАННЯ ВЕБ-ОРІЄНТОВАНОГО СЕРВІСУ ДЛЯ НАДАННЯ ПОСЛУГ
З РЕАЛІЗАЦІЇ ВТОРИННИХ ТОВАРІВ ........................................................................ 23
2.1 Опис функціонального програмного середовища для розробки ........................ 23
2.2 Основні принципи розробки веб-орієнтованих сервісів ...................................... 28
2.3 Архітектура веб-орієнтованих сервісів ................................................................. 29
2.4 Розробка діаграми варіантів використання ........................................................... 33
2.5 Проєктування структури бази даних .................................................................... 34
2.6 Опис процесу проєктування веб-орієнтованого сервісу для надання послуг з
реалізації вторинних товарів ......................................................................................... 37
2.7 Проєктування інтерфейсу користувача ................................................................ 40
Висновки до розділу 2 ................................................................................................... 43
3 ПРОГРАМНА РЕАЛІЗАЦІЯ ......................................................................................... 44
3.1 Постановка завдання ............................................................................................... 44
3.2 Інтерфейс веб-орієнтованого сервісу для надання послуг з реалізації вторинних
товарів … ........................................................................................................................ 46
3.3 Програмна реалізація веб-орієнтованого сервісу ................................................ 48
8
3.4 Тестуваня веб-орієнтованого сервісу..................................................................... 53
3.5 Розгортання веб-орієнтованого сервісу ................................................................. 56
Висновки до розділу 3 ................................................................................................... 61
ВИСНОВКИ ....................................................................................................................... 62
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ ......................................................................... 63
Додаток А. Специфікація 482.ЧДТУ. 42287-01 ……………………………………… 64
Додаток Б. Текст програми............................................................................................... 66
Додаток В. Інструкція користувача ................................................................................. 95
Додаток Г. Структура бази даних .................................................................................... 99
9
ПЕРЕЛІК УМОВНИХ ПОЗНАЧЕНЬ, СИМВОЛІВ, СКОРОЧЕНЬ І ТЕРМІНІВ
СКБД – система керування базами даних;
БД – база даних;
ПЗ – програмне забезпечення;
Unit-testing – рівень тестування програмного забезпечення, на якому тестуються
окремі одиниці/компоненти програмного забезпечення;
E2E testing – рівень тестування програмного забезпечення, на якому тестуються
повна функціональність додатка;
AI – Artificial Intelligence;
UI/UX – User interface/User experience – методологія створення користувацького
інтерфейсу;
HTTP-протокол (Hyper Text Transfer Protocol) – протокол передачі гіпер-текстових
документів.
HTTPS-протокол (Hyper Text Transfer Protocol Secure) – протокол передачі гіпер-
текстових документів із додатковим шаром захисту.
10
ВСТУП
У сучасному цифровому світі багато завдань стають доступнішими та
зручнішими завдяки інтернет-технологіям. Наприклад, здійснити продаж або
купівлю вторинних товарів, таких як вживані товари, меблі, електроніка та інше,
стає простіше завдяки веб-орієнтованим сервісам. Люди шукають можливості
ефективно та економно реалізувати непотрібні речі, а також придбати необхідне,
переглядаючи та порівнюючи пропозиції онлайн.
Розробник мають можливість знайти цікаві ринкові ніші та використовувати їх
для створення веб-сервісу, спрямованого на реалізацію вторинних товарів.
Зростаючий попит на такі сервіси вказує на потребу в їх подальшому розвитку та
удосконаленні. Незважаючи на існуючу конкуренцію на ринку, розробники можуть
зосередитися на вдосконаленні функціоналу та якості обслуговування, щоб залучити
та утримати користувачів. Таким чином, веб-орієнтовані сервіси для реалізації
вторинних товарів відкривають широкі можливості для розробників та сприяють
оптимізації процесу обміну товарами та послугами в онлайн-середовищі.
Актуальність кваліфікаційної роботи бакалавра. Звернення до веб-
орієнтованих сервісів для надання послуг з реалізації вторинних товарів стає все
більш популярним серед користувачів, завдяки його зручності та ефективності.
Однією з основних переваг таких сервісів є можливість користувача здійснювати
покупку або продаж вторинних товарів, таких як вживані товари чи меблі, просто за
допомогою веб-браузера на комп'ютері чи мобільному пристрої. Крім того, вони
часто пропонують додаткові інструменти, які дозволяють користувачам вести
спілкування з потенційними покупцями або продавцями, відстежувати статус
операцій та зручно керувати своїми оголошеннями.
Веб-орієнтовані сервіси також можуть мати свої недоліки, такі як можливість
зустрічі з проблемами у виконанні операцій або управління оголошеннями. Однак, з
розвитком технологій та вдосконаленням сервісів, багато з цих проблем можуть
бути вирішені або мінімізовані.
11
Загалом, веб-орієнтовані сервіси для надання послуг з реалізації вторинних
товарів відкривають перед користувачами широкі можливості для ефективного та
зручного управління своїми ресурсами, сприяючи тим самим оптимізації процесу
обміну товарами та послугами в онлайн-середовищі.
Мета кваліфікаційної роботи бакалавра – створення веб-орієнтованого
сервісу для надання послуг з реалізації второнних товарів.
Для досягнення поставленої мети були поставлені такі завдання:
дослідити предметну область;
виконати порівняльний аналіз існуючих веб-орієнтованих сервісів для надання
послуг з реалізації вторинних товарів;
розробити схему базу даних і реалізувати її за допомогою сучасних СУБД;
обрати програмно-технічні засоби для реалізації веб-орієнтованого сервісу;
розробити веб-орієнтовий сервіс для надання послуг з реалізації вторинних
товарів та виконати його тестування.
Об’єкт і предмет дослідження.
Об’єктом дослідження процеси публікації товару і оформлення онлайн
замовлень.
Предметом дослідження є веб-орієнтований сервіс для надання послуг з
реалізації вторинних товарів.
Для вирішення поставлених завдань були застосовані наступні методи
дослідження: теоретичний (аналіз електронної літератури з проблеми дослідження);
емпіричний – полягав у спостереженні та налаштуванні веб-орієнтованого сервісу
для надання послуг з реалізації вторинних товарів; порівняльний – для здійснення
порівнянь існуючих аналогів веб-орієнтованих сервісів для надання послуг з
реалізації вторинних товарів.
Веб-орієнтований сервіс для надання послуг з реалізації вторинних товарів
був розроблений на мові програмування JavaScript. Серверна сторона сервісу була
реалізована за допомогою фреймворку NestJs, який будується на бібліотекі Express,
яка відома в колі розробників JavaScript. Для реалізації бази даних
використовувалася база з відкритим кодом Postgres. В якості ORM було обрано
12
TypeORM. Оплата організована за допомогою сервісу LiqPay, відомо в Україні та за
її межами. Для написання інтерфейсу користувача була обрана бібліотека React, яка
пропагує функціональний стиль програмування. Для покращення керуванням стану
інтерфейсу застосувався менеджер станів Redux Toolkit. Для виконання запитів до
серверної частини була використана бібліотека RTK Query, яка заінтегрована з
Redux Toolkit. Контроль проєктування програмного забезпечення та написання коду
проводилось в редакторі коду Visual Studio Code, з додатковими розширеннями, що
перетворюють Visual Studio Code до програмного середовища. Для проєктування і
реалізації бази даних було використано графічну оболочку DBeaver, яка має
відкритий код і безкоштовну ліцензію.
Обрані технології задовольняють всім функціональним і не функціональним
вимогам до такого типу програмного забезпечення (ПЗ).
13
1 ДОСЛІДЖЕННЯ ПРЕДМЕТНОЇ ОБЛАСТІ
1.1 Аналіз сучасного стану веб-орієнтованих сервісів для надання послуг
з реалізації вторинних товарів
У сучасному динамічному світі споживачі стають все більш вимогливими до
сервісів надання послуг з реалізації вторинних товарів, спостерігаючи за їх
функціональністю та зручністю використання. Зростаюча кількість користувачів
веб-орієнтованих сервісів породжує попит на розширені можливості, спрямовані на
спрощення пошуку та обміну вторинними ресурсами, а також на мінімізацію
необхідного особистого втручання в процесі здійснення операцій.
Багато сервісів надання послуг мають обмежений функціонал, тоді як
користувачі вимагають більш широкого спектру можливостей. Відповідно,
проєктування та розробка веб-орієнтованого сервісу для надання послуг з реалізації
вторинних товарів стає актуальним завданням у сучасному інформаційному
середовищі. Такий сервіс може не лише задовольнити потреби користувачів у
зручному та ефективному обміні вторинними ресурсами, але й сприяти розвитку
бізнесу компаній, що займаються цією сферою [1].
Таким чином, розгляд проєктування та розробки веб-орієнтованого сервісу
для надання послуг з реалізації вторинних товарів у кваліфікаційній роботі
визначається як важливий крок у відповіді на потреби сучасного ринку та сприяє
його подальшому розвитку. Це обумовлюється кількома причинами, а саме:
1. Залучення нових клієнтів за допомогою доступності платформи. Зважаючи,
що кількість людей, які використовують браузер в своєму повсякденному житті
зростає кожен рік, а веб платформи стають все доступнішими, веб-орієнтований
сервіс дозволяє малому, середньому, великому бізнесу і фізичним особам
охоплювати більшу вікову аудиторію та успішно залучати нових постійних клієнтів.
2. Підвищення лояльності користувача. Якщо веб-орієнтований сервіс має
приємний дизайн і швидкодію, користувачеві буде приємно знову і знову
повертатися до веб-сервісу. Також, якщо бізнес не важливо, якого масштабу буде
14
постійно перевіряти і публікувати вживані товари під своїм брендом, то
підприємець буде збільшувати свою впізнавість серед клієнтів платформи, що може
позитивно покращитися на його продажах.
3. Швидша окупність бізнесу для користувача сервісу, не залежно від масштабу
його діяльності. Завдяки реєстрації і діяльності компанії на веб-орієнтованому
сервісі, компанія отримує більше розподілених клієнтів по місці проживання, що
означає більший прибуток і впізнанавість бренда серед людей. Для початку старту
продажу вторинної продукції потрібно бути всього лише фізичною особою, а для
початку бізнесу бути фізичною особою підприємцем. Не потрібне ніяке специфічне
обладння, або налогодження зв’язків із іншими компаніями по доставці або оплаті.
Розглянемо принцип роботи веб-орієнтованого сервісу для надання послуг
реалізації второнних продуктів.
Веб-орієнтований сервіс для надання послуг реалізації второнних товарів
може реалізовуватися в трьох форматах, кожен з яких має свої особливості та
переваги.
Платформа онлайн оголошення. В такому форматі продавець-компанія
відповідає за оформлення і менеджмент оголошеннь на платформі. В такому
випадку платформа буде стягувати 5-10% за кожне оформленне або оброблене
замовлення. Головний недолік, якщо продавець компанія відповідає за доставку і
обробку платежів, в випадку помилки, клієнт-покупець буде не задоволений
платформою, а не продавцем-копанією.
Веб-орієнтований сервіс. Стратегія такого формату полягає в тому, що
компанія-розробник бере відповальність за платежі і доставку на себе, а компанія-
продавець тільки публікує замовлення на платформі. Головний недолік, складніша
реаліація для компанії-розробник, так як потрібно адаптувати свій веб-сервіс для
всіх можливих випадків і варіантів.
Брендований сервіс. У цьому випадку кожна компанія створює свій веб-сервіс
для продажу своїх вторинних товарів. Головний недолік, даного формату полягає, в
тому, що це складний спосіб реалізації, кожен сервіс повинен все реалізувати
самотужки. Головна перевага, можливість кожного бренду все адаптувати під себе.
15
Головні характеристики веб-орієнтованого сервісу для надання послуг з
реалізації вторинних товарів.
При створенні веб-орієнтованого сервісу для надання послуг з реалізації
вторинних товарів важливо враховувати потреби не тільки продавців, але й
покупців. Ось які функції повинна включати кожна з версій веб-орієнтованого
сервісу (табл. 1.1).
Таблиця 1.1 – Потреби продаців та покупців
Продавецька версія Покупецька версія
Особистий кабінет, з інформацією, Особистий кабінет із інформацією про
можливістю зміни фотографії і себе, можливість редагування
платіжних даних. інформації про себе.
Публікація товарів зручним способом. Список бажань, корзина, оплата товару.
Менеджмент товару. Відображення статусу замовлення в
реальному часі.
Чат з покупцем. Чат з продавцем, можливість створення
торгів.
Можливість зміни ціни на товар. Зручна оплата сервісу.
Для керуванням веб-сервісом потрібна сторінка адміністратора. Адміністратор
повинен мати доступ до перегляду і зміни даних користувачі, керувати категоріями
сайту, створювати і редагувати атрибути для товару, додавати, видаляти, редагувати
товар, перевіряти корректність платежів.
Основні етапи розробки веб-орієнтованого сервісу для надання послуг по
реалізації вторинних товарів.
Створення веб-орієнтованого сервісу для надання послуг з реалізації
вторинних ресурів – складний та мінливий процес, який включає ряд ключових
кроків:
1. Дослідження ринку та конкурентного середовища. Для досягнення
конкурентоспроможності необхідно ретельно вивчити сучасні тенденції та практики
в сфері реалізації вторинних товарів. Це дозволить зрозуміти потреби користувачів і
визначити прогалини в ринковому просторі.
2. Вибір оптимальної бізнес-моделі. На основі аналізу ринку слід визначити
найбільш підходящу модель ведення бізнесу, яка забезпечить успішну реалізацію
16
проєкту. Це може бути агрегатор товарів, платформа для прямої торгівлі або інше
рішення, яке відповідає специфіці сегменту ринку.
3. Аналіз цільової аудиторії. Для ефективного взаємодії з користувачами
необхідно детально вивчити їхні потреби, уподобання та поведінку. Це допоможе
налаштувати сервіс таким чином, щоб він відповідав їхнім очікуванням і
забезпечував максимальний комфорт під час взаємодії з ним.
4. Проєктування функціоналу. На основі виявлених потреб користувачів слід
розробити набір функцій, які забезпечать зручність та ефективність використання
сервісу. Це можуть бути функції пошуку, фільтрації, спілкування з іншими
користувачами, оплати та інші.
5. Вибір технологій та реалізація. Для успішної реалізації проєкту важливо
вибрати технології, які забезпечать швидку та надійну роботу сервісу. Це можуть
бути веб-фреймворки, бази даних, інструменти для аналізу та моніторингу даних та
інші технології, які відповідають вимогам проєкту.
1.2 Огляд існуючих веб-орієнтованих сервісів для надання послуг з
реалізації вторинних товарів
В кваліфікаційній роботі бакалавра проведено огляд існуючих аналогів веб-
орієнтованих сервісів для надання послу з реалізації вторинних товарів, таких як:
«OLX», «Promo».
1.2.1 Аналіз сервісу «OLX». «OLX» – це онлайн-платформа, яка виконує
важливу роль у вирішенні потреб українських користувачів у продажу та купівлі
різноманітних товарів та послуг. Заснований у 2006 році, «OLX.ua» став одним із
найбільших та найпопулярніших веб-орієнтованих сервісів в Україні, забезпечуючи
зручність та доступність для тисяч користувачів.
На «OLX» має широкий асортимент доступних категорій товарів і послуг.
Користувачі можуть з легкістю розмістити оголошення про продаж будь-якого
товару або послуги, починаючи від електроніки та автомобілів, і закінчуючи
орендою житла та пошуком роботи.
17
Один із ключових аспектів «OLX» – це безкоштовність розміщення
оголошень, що робить платформу привабливою для широкого кола користувачів, які
шукають спосіб продати свої товари або послуги без додаткових витрат.
Окрім того, «OLX» пропонує зручний пошуковий інтерфейс та функціональну
фільтрацію, яка дозволяє користувачам швидко розмістити товари серед великої
кількості оголошень. Також важливою перевагою є можливість комунікації між
покупцями та продавцями через внутрішню систему повідомлень платформи, що
сприяє взаєморозумінню та укладенню угод.
У такий спосіб «OLX.ua» виконує важливу роль у стимулюванні економічної
активності, сприяючи обміну товарами та послугами між користувачами,
забезпечуючи при цьому зручність і безпеку транзакцій.
Рисунок 1.1 – Головна сторінка сервісу «OLX»
Недоліки «OLX»:
Велика кількість реклами на будь-яких сторінках.
Інтерфейс сервісу складний і запутаний.
Чат використовує концепцію Long Polling.
Унікальні відвідування, не є унікальними
При змінні мови, сайт повноцінно перезавантажується
Не існує англійського перекладу
18
Є переклад на російську мову
Переваги сервісу «OLX»:
Велика кількість категорій і товарів.
Зручна доствка через «OLX» доставку.
Зручні сервіси для оплати товару.
Можливість спілкування з продавцем або покупцем.
Єдиний інтерфейс як для продавця, так і для покупця.
1.2.2 Аналіз сервісу «Prom». Другим в роботі проаналізовано сервіс «Prom».
Сервіс «Prom» – це інноваційна онлайн-платформа, яка відіграє важливу роль
у задоволенні потреб користувачів українського ринку у реалізації та придбанні
різноманітних вторинних товарів. Заснований у 2008 році, Prom виріс у одного з
найбільших та найпопулярніших веб-орієнтованих сервісів в Україні, надаючи
широкий спектр можливостей для тисяч користувачів.
Рисунок 1.2 – Головна сторінка сервісу «Prom»
На «Prom» доступні різноманітні категорії товарів та послуг, що дозволяє
користувачам легко розміщувати оголошення про реалізацію будь-яких вторинних
товарів. Від електроніки та домашніх речей до автомобілів та нерухомості – на
«Prom» можна знайти все, що потрібно.
19
Однією з ключових переваг «Prom» є безкоштовне розміщення оголошень, що
робить платформу привабливою для широкого кола користувачів. Це дозволяє
людям ефективно реалізовувати свої товари або послуги без додаткових витрат.
Крім того, «Prom» пропонує зручний інтерфейс пошуку та функціональну
фільтрацію, що допомагає користувачам швидко знаходити потрібні ресурси серед
великої кількості оголошень. Також важливою перевагою є можливість зв'язку між
покупцями та продавцями через внутрішню систему повідомлень платформи, що
сприяє взаєморозумінню та укладенню угод.
Отже, «Prom» відіграє важливу роль у сприянні економічній активності,
сприяючи обміну вторинними ресурсами між користувачами, забезпечуючи при
цьому зручність та безпеку угод.
Недоліки сервісу «Prom»:
Російська мова в багатьох банерах.
Реєстрація по номеру телефону.
Обмежений контроль та відповідальність.
Потрібно оновлювати сторінку із статусом замолення
Переваги сервісу «Prom»:
Широкий асортимент товарів і послуг.
Безкоштовне розміщення оголошень.
Зручний інтерфейс та функція фільтрації.
Внутрішня система повідомлень.
1.3 Аналіз порівнянь існуючих рішень розглянутих реалізацій та
постановка завдання
Проаналізувавши існуючі рішення розглянутих реалізацій, в кваліфікаційній
роботі бакалавра є кілька пропозицій щодо усунення обмежень існуючих сервісів:
1. Реєстрація користувачів лише за електронною адресою, номер телефона
можна використовувати як додаткову інформацію або для автентифікації
користувача.
20
2. Потрібно створювати більш жорстку політику для продавців, щоб покупці
відчували себе в більший безпеці.
3. Блокувати облікові записи шахраїв.
4. Оновлення статусу повинно бути динамічним.
5. Нереалізовувати або видалити російську мову із веб-сервісу.
6. Реалізація чату повинна використовувати технологію WebSocket замість Long
Polling.
7. Мова на сайті повинна змінювати динамічно, без видимого перезавантажень
для користувача.
У таблиці 1.1 представлено порівняльний аналіз розглянутих веб-сервісів для
надання послуг з реалізації вторинних товарів, а саме: «OLX», «Prom».
Таблиця 1.2 – Таблиця порівнянь існуючих рішень розглянутих реалізацій
Критерії Назва сервісу
порівняння
«OLX» «Prom»
сервісів
UX/UI Дизайн 4 5
Швидкість
4 4
відклику
Швидкість
обробки товарів і 4 3
замовлень
Вартість
5 5
оголошення
Як можна побачити із табл. 1.1, що у якості критеріїв для порівняння було
обрано: UX/UI дизайн; швидкість відклику; швидкість обробки товарів і замовлень;
вартість оголошення.
Підсумовуючи перегляд суб’єктивних оцінок приведених вище веб-сервісів,
варто зазначити, що кожному з них притаманні такі характеристики:
− Зручний і зрозумілий UX\UI дизайн;
− Висока швидкодія;
21
− Помірна швидкість обробки товарів і замовлень.
− Вартість оголошення безкоштовна.
Проаналізувавши існуючі рішення розглянутих реалізацій веб-сервісів, в
кваліфікаційній роботі бакалавра при розробці веб-орієнтованого сервісу для
надання послуг з реалізації вторинних ресурів варто привертати увагу на такі
значені моменти:
− веб-сервіс повинен мати зручний, зрозумілий, інтуїтивний UX\UI дизайн;
− веб-сервіс повинен мати високу швидкодію, користувач не повинен витрачати
надлишковий час на обробку даних;
− веб-сервіс повинен мати помірну швидкість для обробки нових товарів і
замовлень;
− веб-сервіс не повинен брати плату за публікацію оголошення продажу товару.
Постановка завдання
1. Аналіз стратегії бізнесу, функціональних вимог та використаних інструментів
у користувацьких інтерфейсах існуючих онлайн-платформ для надання послуг
з вторинної реалізації товарів.
2. Дослідження та використання найактуальніших технологій серверної і
клієнтської веб-розробки для реалізації веб-орієнтованого сервісу.
3. Розробка веб-орієнтованого сервісу для надання послуг з реалізації вторинних
товарів, що задовольняє таким функціональним вимогам:
− автентифікація, авторизація та реєстрація продавців і покупців;
− перегляд товарів/товарів за категоріями із різним рівнем вкладеності;
− додавання та перегляд додаткової інформації до профіля продавця покупця;
− додавання товарів/ресурів до кошика покупця;
− покупка товару/ресурса, з відслідковуванням статусу замовлення;
− можливість зв’язатися покупцеві з продавцем через чат платформи.
22
Висновки до розділу 1
В даному розділі вивчено і досліджено предметну або доменну область, а
саме: надання послуг з реалізації вторинних товарів; основні проблеми реалізації
веб-орієнтованих сервісів для надання послуг з реалізації вторинних товарів; огляд
аналогічних рішень для надання послуг з реалізації вторинних товарів.
Виконано порівняльний аналіз веб-орієнтованих сервісів для надання послуг з
реалізації вторинних товарів: «OLX», «Promo».
Проведено аналіз існуючих варіантів реалізації та складено таблицю порівняння,
а також сформульовано постановку завдання.
23
2 ПРОЄКТУВАННЯ ВЕБ-ОРІЄНТОВАНОГО СЕРВІСУ ДЛЯ НАДАННЯ
ПОСЛУГ З РЕАЛІЗАЦІЇ ВТОРИНИХ ТОВАРІВ
2.1 Опис функціонального програмного середовища для розробки
Розглянемо детальніше програмне середовище та його особливості для
розробки веб-орієнтованого сервісу для надання послуг з реалізації всторинних
товарів.
Visual studio code – це текстовий редактор із можливістю бути розширеним до
повноцінної (IDE), створений компанією Microsoft, яка надає інструменти для
розробки різних видів програмного забезпечення, включаючи веб-додатки. Він є
вільно розповсюдженою платформою та підтримується в багатьох операційних
системах, таких як Windows, macOS та Linux [2].
Visual Studio Code забезпечує безпечний інтерфейс розробки та розширюється
за допомогою різних розширень, що дозволяє розробникам адаптувати його під свої
потреби. Це IDE підтримує багато мов програмування, включаючи JavaScript,
TypeScript, HTML, CSS та інші. Visual Studio Code особливо підходить для розробки
веб-додатків на базах NestJS та React.
Особливості Visual studio code
− Підтримка мови TypeScript та інтелектуальне автодоповнення для роботи з
NestJS.
− Розширені функції для роботи з React, такі як підтримка JSX, розширені
можливості рефакторинга та інші.
− Інтеграція із системами керування версіями, такими як Git, що дозволяє
командам розробників ефективно керувати версіями коду.
− Можливість встановлення різних розширень для полегшення роботи з NestJS і
React, таких як розширення для автоматичного завершення коду, збірки та
інше.
− Вбудована підтримка ESLint і prettier.
24
TypeScript – це мова програмування, що базується на високорівневому, об'єктно-
орієнтованому підході та розроблена з максимальною увагою до типізації. Вона є
строго типізованою та компізованою в JavaScript. TypeScript надає розробникам
зручний інструмент для роботи з веб-додатками та іншими програмами на основі
JavaScript, забезпечуючи переваги типізації, які допомагають відлагоджувати код та
запобігати випадкам помилок ще на етапі розробки [3].
Програми написані на TypeScript компілюються в JavaScript, що може
запускатися на будь-якій платформі, що підтримує JavaScript. Це дозволяє
використовувати код TypeScript у різних середовищах без необхідності
перекомпіляції. Синтаксис TypeScript подібний до JavaScript, але має додаткові
можливості, такі як статична типізація, засоби ООП, декоратори та інші. Час
виконання TypeScript надає додаткові можливості, які розширюють можливості
JavaScript, такі як використання сучасних стандартів мови та інтеграція з іншими
інструментами розробки.
NestJs – прогресивний фреймворк для побудови масштабованих та ефективних
серверних застосунків на основі Node.js. Він надає розробникам можливість
створювати досконало структуровані застосунки за допомогою зручного для
використання API та підтримки шаблону проєктування MVC (Model-View-
Controller) [4].
Заснований на TypeScript, NestJS пропонує статичну типізацію, що дозволяє
виявляти та усувати помилки на етапі розробки, а також полегшує спільну роботу в
команді та розвиток додатків. Фреймворк використовує модульну архітектуру, яка
дозволяє легко організовувати функціональність додатка на окремі компоненти та
забезпечує високу перевикористовуваність коду.
Крім того, NestJS має широкий набір вбудованих модулів та плагінів, таких як
маршрутизація, серіалізація, аутентифікація та авторизація, що спрощують розробку
різноманітних застосунків. Завдяки підтримці засобів тестування та
документування, NestJS стає потужним інструментом для розробки сучасних веб-
орієнтованих сервісів.
25
TypeOrm – це ORM (Object-Relational Mapping) для TypeScript та JavaScript, що
дозволяє розробникам працювати з реляційними базами даних, використовуючи
об’єктно-орієнтований підхід. TypeORM дозволяє використовувати звичайні класи
та декоратори для опису моделей бази даних, а потім автоматично генерує SQL-
запити для взаємодії з базою даних [5].
З TypeORM розробники можуть виконувати операції створення, читання,
оновлення та видалення записів у базі даних, використовуючи звичні мовні
конструкції, що робить роботу з даними більш зрозумілою та зручною. TypeORM
підтримує різні типи баз даних, такі як MySQL, PostgreSQL, SQLite, і дозволяє
використовувати міграції для керування структурою бази даних у різних версіях
додатків.
Крім того, TypeORM надає можливість зв'язків між таблицями, кешування,
логування та інші функціональні можливості, які роблять роботу з базою даних
більш продуктивною та ефективною. Використання TypeORM допоможе
розробникам створити масштабовані та надійні додатки з використанням сучасних
підходів до роботи з даними.
PostgreSQL – це потужна об'єктно-реляційна система управління базами даних
(СУБД), яка забезпечує високу надійність, масштабованість та функціональність.
Вона підтримує широкий спектр стандартів SQL і забезпечує багато додаткових
можливостей, які роблять її ідеальним вибором для різних видів додатків, від
невеликих проєктів до великих корпоративних систем [6].
PostgreSQL включає в себе розширені можливості управління даними, такі як
транзакції, відновлення після збоїв, забезпечення цілісності даних, а також
підтримку зовнішніх ключів та індексів. Вона також підтримує різні типи даних,
включаючи текстові, числові, географічні та JSON-дані.
Однією з ключових переваг PostgreSQL є її розширюваність та гнучкість. Вона
має велику кількість розширень та модулів, що дозволяють розробникам
розширювати функціональність бази даних згідно з їхніми потребами. Крім того,
PostgreSQL має активну спільноту користувачів та підтримку з боку комерційних
26
організацій, що забезпечує швидке реагування на проблеми та вдосконалення
продукту.
Загалом, PostgreSQL є потужним та надійним рішенням для зберігання даних, що
відповідає вимогам найвибагливіших проєктів.
React – це потужна бібліотека для розробки інтерфейсів користувачів у веб-
додатках. Вона дозволяє створювати динамічні та ефективні інтерфейси за
допомогою компонентного підходу.
Основними перевагами React є його простота та зручність використання. За
допомогою React розробники можуть розділити інтерфейс на невеликі незалежні
компоненти, якими легко керувати та перевикористовувати. Це спрощує процес
розробки, забезпечує чистоту коду та покращує його підтримку.
Крім того, React пропонує віртуальний DOM (Document Object Model), що
дозволяє мінімізувати кількість маніпуляцій з DOM, що підвищує продуктивність і
швидкість додатків. Він також має широку підтримку та активну спільноту, що
забезпечує доступ до багатьох корисних інструментів, бібліотек та розширень.
React надає можливість використовувати JSX (JavaScript XML), що дозволяє
писати код у вигляді комбінації JavaScript та HTML, що покращує структуру
додатка та робить його більш зрозумілим для розробників. JSX дозволяє легко
вбудовувати JavaScript в HTML і навпаки, що полегшує роботу з даними та
динамічною генерацією контенту [7].
Ще перевагою React є його активна екосистема та велика кількість сторонніх
бібліотек та компонентів, які допомагають прискорити процес розробки та
забезпечують гнучкість та розширюваність додатків. Засновані на принципах
бібліотеки React, такі як Redux для управління станом додатка, React Router для
навігації та багато інших, залишаються невід’ємною частиною розробки сучасних
веб-додатків.
Redux – це потужна бібліотека управління станом для додатків на основі React та
інших JavaScript-фреймворків. Вона допоможе організувати та керувати станом
додатка в одному центральному місці, що полегшує розробку, тестування та код
підтримки.
27
Основними концепціями Redux є однозначність (єдине джерело істини) та
незмінність (незмінність) даних. У Redux стан додаток представлений у вигляді
дерев об'єктів, які зберігаються в одному місці – відомому як store. Це дозволяє
зробити стан додатком передбачуваним та легким керованим.
Redux також надає можливість використовувати проміжне програмне
забезпечення для обробки асинхронних дій, таких як отримання даних із сервера. Це
дозволяє розробникам ефективно виконувати складні завдання, такі як управління
асинхронними запитами та синхронізація стану. Крім того, Redux має широкі
спільні користувачі та розширення активної екосистеми, які допомагають
прискорити процес розробки та забезпечують підтримку різних функцій. Засновані
на принципах Redux бібліотеки, такі як Redux Toolkit, допомагають спростити
роботу зі станом додатка та забезпечують більш ефективне управління ним [8].
Узагальнюючи, Redux є незамінним інструментом для управління станом
додатків, який надає розробникам зручність, стабільність та продуктивність у роботі
зі складними структурами даних.
RTK Query – це бібліотека, яка надає зручний та простий спосіб для виконання
запитів до API та керування даними у веб-додатках, побудованих з використанням
Redux Toolkit. Вона дозволяє розробникам зосередитися на функціональності
додатка, а не на вирішенні технічних питань, пов’язаних з мережевими запитами та
управлінням станом даних.
Однією з ключових особливостей RTK Query є вбудована підтримка
нормалізованої структури даних, що дозволяє ефективно керувати даними в Redux
Store і уникнути дублювання. Вона автоматично генерує набір дій Redux і
редукторів для кожного API-запиту, що запускає роботу зі станом даних і дозволяє
розробникам швидко і легко взаємодіяти з сервером.
Крім того, RTK Query має вбудовану підтримку кешування даних та
автоматичного оновлення, що дозволяє забезпечити швидку та ефективну роботу з
даними в додатку. Вона також має вбудовану обробку помилок та можливість
конфігурації під потреби конкретного додатка.
28
Загалом, RTK Query є потужним інструментом для розробки веб-додатків, який
забезпечує безпечний та ефективний спосіб взаємодії з API та управління станом
даних. Вона після розробників зосереджена на розробці функціональної добавки,
забезпечуючи при цьому високу продуктивність та швидкість.
2.2 Основні принципи розробки веб-орієнтованих сервісів
Розробка веб-орієнтованих сервісів базується на декількох основних
принципах, що допомогають створити зручний, ефективний програмний продукт
для користувачів із різної аудиторіїі.
Основні принципи розробки веб-орієнтованих сервісів включають:
Визначення цілей та аудиторії: перш за все, необхідно проаналізувати
аудиторію сервісу. Це можуть бути користувачі, які шукають ефективний спосіб
продажу або придбання вторинних товарів, таких як електроніка, одяг, меблі тощо.
Важливо застосовувати їх потреби, переваги та особливості, а також побудувати
інтерфейс та функціональні можливості, які відповідають їх очікуванню.
Дизайн інтерфейсу користувача (UI/UX): ефективний дизайн інтерфейсу
користувача визначає зручність та естетичність додатку. Він має бути легким у
використанні, інтуїтивно зрозумілим і відповідати принципам UX-дизайну, щоб
створити приємний користувацький досвід.
Платформа: вибір платформи (Chrome, Firefox, Safari) має великий вплив на
технологічні аспекти розробки. Кожна з цих платформ має свої унікальні
характеристики та вимоги до розробки додатків, тому важливо враховувати цей
фактор при прийнятті рішення.
Функціональність: додаток повинен реалізувувати функціонал для досягнення
цілей користувачів. Важливо уникати перезавантаження додаткової зайвою
функцією, щоб уникнути потреб користувачів і забезпечити їм необхідний набір
можливостей.
Безпека: забезпечення безпеки даних за допомогою шифрування і
використання сучасних практик в аутентифікації і авторизації користувачів.
29
2.3 Архітектура веб-орієнтованих сервісів
Архітектура веб-орієнтованих сервісів базується на архітектурі клієнт-сервер.
Клієнт-серверна архітектура є однією з основних моделей розподіленої
обчислювальної системи, яка використовується в комп'ютерних мережах та
програмному забезпеченні. Вона базується на взаємодії між двома типами
компонентів: клієнтами і серверами. Основна ідея полягає в тому, що клієнтські
програми (клієнти) виконують запити до серверних програм (серверів), які надають
певні послуги або ресурси.
Основні характеристики клієнт-серверної архітектури:
– Клієнт – це програма або пристрій, який ініціює запити до сервера для
отримання певної послуги або ресурсу. Клієнт може бути реалізований у вигляді
програмного забезпечення, веб-браузера, мобільного додатка або будь-якого
пристрою, який може взаємодіяти з мережею.
– Сервер – це програма або комп'ютер, який надає певні послуги або ресурси
клієнтам, відповідаючи на їх запити. Сервер може бути фізичним комп'ютером або
віртуальною машиною, яка служить для обробки запитів та відповідей.
– Комунікація через мережу – клієнти та сервери взаємодіють через мережу,
використовуючи стандартні протоколи передачі даних, такі як HTTP, TCP/IP, FTP
тощо. Клієнти надсилають запити серверам, які обробляють ці запити та надсилають
назад відповіді.
– Розділення обов’язків – у клієнт-серверній архітектурі, клієнти та сервери
виконують різні функції. Клієнти відповідають за взаємодію з користувачем та
ініціювання запитів, тоді як сервери відповідають за обробку цих запитів та надання
відповідей.
– Масштабованість та доступність – клієнт-серверна архітектура дозволяє
масштабувати систему шляхом додавання нових серверів або клієнтів у мережу. Це
також дозволяє підтримувати високу доступність системи, оскільки можна
запускати багато екземплярів серверів для обробки запитів [9].
Схема клієнт-серверної архітектури представлено на рис. 2.1.
30
Рисунок 2.1 – Схема клієнт-серверної архітектури
В свою чергу клієнт (Fronted) і сервер (Backend) мають власні архітектурні
підходи.
Бекенд (Backend) – це частина програмного забезпечення, яка відповідає за
обробку даних, бізнес-логіку та взаємодію з базами даних, а також за забезпечення
функціональності для фронтенду (frontend) або інших клієнтів через API (інтерфейс
програмування додатків). Загальна архітектура бекенду може різнитися в залежності
від конкретних вимог проєкту, але включає в себе деякі загальні складові:
Web-сервер – це програмне забезпечення, яке обробляє HTTP-запити від
клієнтів та повертає їм відповіді. Популярними веб-серверами є Apache, Nginx,
Microsoft IIS, Tomcat тощо. Вони забезпечують обробку запитів, маршрутизацію,
кешування та інші функції.
Бізнес-логіка – частина бекенду, яка містить основні правила та алгоритми,
необхідні для обробки даних та виконання функцій, визначених у специфікації
проєкту. Бізнес-логіка зазвичай включає обробку запитів, перевірку прав доступу,
валідацію даних, розрахунки та інші обчислювальні завдання.
31
База даних – це місце, де зберігаються дані, з якими працює бекенд. База
даних може бути реляційною (наприклад, MySQL, PostgreSQL) або нереляційною
(наприклад, MongoDB, Redis). Бекенд взаємодіє з базою даних для зберігання,
вибірки, оновлення та видалення даних.
API – це набір точок доступу, які клієнтам можуть взаємодіяти з бекендом.
API може бути реалізовано у вигляді RESTful API, GraphQL, SOAP тощо. Вони
описують доступні операції, формати даних та правила взаємодії.
Аутентифікація та авторизація – це процеси перевірки ідентичності
користувачів та контролю доступу до товарів. Бекенд зазвичай включає механізми
аутентифікації (перевірка ідентичності) та авторизації (контроль доступу).
Кешування та оптимізація – ці функції дозволяють підвищити
продуктивність бекенду шляхом зберігання проміжних результатів обчислень у
кеші, попередньої обробки даних або використання спеціалізованих алгоритмів
оптимізації.
Загальна схема серверної архітектури представлено на рис. 2.2.
Рисунок 2.2 – Загальна схема серверної архітектури
Фронтенд (Frontend) – це частина програмного забезпечення, яка відповідає
для відображення інтерфейсу користувача (UI) та взаємодії з користувачем. Загальна
архітектура фронтенду може включати такі складові:
32
HTML (Hyper Text Markup Language) – HTML визначає структуру та контент
веб-сторінки. Він використовується для створення різноманітних елементів, таких
як текст, посилання, зображення, форми тощо.
CSS (Cascading Style Sheets) – відповідає за оформлення та стиль веб-сторінки.
Він використовується для задання кольорів, шрифтів, розмірів, відступів, анімацій
тощо.
JavaScript – це мова програмування, яка використовується для динамічної
поведінки веб-сторінки. Вона дозволяє реагувати на події, взаємодіяти з
користувачем, маніпулювати DOM (Document Object Model), виконувати асинхронні
запити на сервер, а також реалізувати різноманітні ефекти та анімації.
Фреймворки та бібліотеки – існують різні фреймворки та бібліотеки
JavaScript, які полегшують розробку фронтенду, такі як React.js, Angular, Vue.js,
jQuery тощо. Вони надають різні інструменти та підходи для організації коду,
управління станом додатку, роботи з компонентами та інші функціональності.
Взаємодія з бекендом через API – взаємодіяти з бекендом через API,
виконуючи запити на сервер для отримання або надсилання даних. Це дозволяє
створювати динамічні та інтерактивні веб-додатки, які здатні обмінюватися даними
з сервером без перезавантаження сторінки.
Тестування і відлагодження – важливо проводити тестування та
відлагодження фронтенду для забезпечення правильної роботи та відображення на
різних пристроях та браузерах. Для цього використовуються різні інструменти та
підходи, такі як юніт-тестування, інтеграційне тестування, засоби відлагодження
браузера тощо.
Узагальнюючи вищесказане, можна зробити висновок, що архітектура веб-
орієнтованих сервісів, скаладається із двох різних частин, які доповнюють один
одну.
33
2.4 Розробка діаграми варіантів використання
Діаграма варіантів використання є ключовим елементом у плануванні проєкту
веб-орієнтованого сервісу для надання послуг з реалізації вторинних товарів. Ця
діаграма відображає взаємодію між користувачами та функціоналом системи,
надаючи уявлення про те, як користувачі можуть взаємодіяти з сервісом і які
операції вони можуть виконати в інформаційній системі. Це інструмент для опису
функціональності системи і визначення потреб користувачів.
Базовий актор, що використовується при створенні діаграми варіантів
використання, це «User Bayer» – це особа, яка реєструється на веб-орієнтованому
сервісі з метою придбання товару. Після вибору і відоокремлення всіх потенційних
акторів системи формується перелік можливих варіантів взаємодії з системою.
У кваліфікаційній роботі була розроблена діаграма варіантів використання на
основі аналізу користувачів веб-сервісу та потенційних сценаріїв їх взаємодії з ним.
В діаграмі використовується три актори: синім кольором визначаються загальні дії,
а решта дій – відповідно до кольору кожного користувача.
Варіанти використання веб-орієнтованого сервісу наступні:
− Реєстрація.
− Аутентифікація/Авторизація.
− Перегляд товарів.
− Додання товару в корзину.
− Додання товару в список бажань.
− Формування замовлення.
− Чат користувачів.
− Перегляд сповіщень.
− Підтримка користувачів.
− Налаштування профілю і додаткової інформації.
− Керування товарами.
− Керування категоріями.
− Керування атрибутами.
34
− Публікація товару.
− Моніторинг продажу.
− Підтвердження продажу.
Базуючись на інформації про акторів та всі можливі сценарії використання
веб-орієнтованого сервісу, була реалізована діаграма варіантів використання (Use
Case), що відображає всі можливі інтеракції між акторами та функціоналом додатку
(рис. 2.3).
Рисунок 2.3 – Діаграма варіантів використання Use Case
2.5 Проєктування структури бази даних
База даних відіграє важливу роль у функціонуванні будь-якого веб-сервісу,
забезпечуючи зберігання, організацію та доступ до даних, необхідних для
ефективної роботи системи. Правильно спроєктована структура бази даних дозволяє
забезпечити ефективну обробку даних, швидкий доступ до необхідної інформації та
забезпечити стабільність та надійність сервісу.
35
Для реалізації структури бази даних була обрана СУБД PostgreSQL і ORM
TypeOrm.
PostgreSQL – це потужна та дуже популярна об'єктно-реляційна система
управління базами даних (СКБД). Вона відома своєю надійністю, масштабованістю
та розширюваністю, що робить її ідеальним вибором для розробки веб-орієнтованих
додатків та сервісів.
Переваги PostgreSQL:
− Відкритість та безкоштовність.
− Підтримка стандартів SQL.
− Масштабованість.
− Розширюваність.
− Надійність.
− Підтрика JSON.
− Підтримка NoSQL.
TypeOrm – це бібліотека для Node.js та TypeScript, яка дозволяє розробникам
працювати з реляційними базами даних з використанням об'єктно-орієнтованого
підходу. Вона надає зручні інструменти для взаємодії з базами даних, такими як
створення, читання, оновлення та видалення записів, а також для виконання
складних операцій, таких як з'єднання таблиць та транзакції.
Переваги TypeOrm:
− Підтримка різних СКБД.
− Об’єктно-реляційне відображення.
− Автогенерування міграцій.
− Підтримка TypeScript.
− Плагін для підтримки NestJs.
Доменна область в контексті розробки програмного забезпечення відображає
сферу діяльності або проблемний діапазон, для якого створюється програмний
продукт. Вона описує конкретну галузь знань, бізнес-процеси та концепції, які
визначають контекст та об'єкти, що розглядаються в системі. Доменна область є
36
основою для розуміння вимог до програмного забезпечення та визначення моделей
даних, архітектури та функціональності системи.
Сутність в доменній області – це конкретний об'єкт або поняття, яке має
важливе значення для контексту доменної області. Вона представляє собою
інформаційний або концептуальний елемент, який ідентифікується та описується у
межах доменної області. Сутності можуть бути реальними об'єктами (наприклад,
користувачі, продукти, замовлення) або абстрактними концепціями (наприклад,
роль, статус, категорія). Вони відображають ключові аспекти діяльності та
інформаційні потреби в межах доменної області, і використовуються для побудови
моделі даних та функціональності програмного продукту.
Наприклад, у веб-орієнтованому сервісі для надання послуг з реалізації
вторинних товарів, можливі сутності в доменній області можуть включати такі
елементи як "Користувач", "Товар", "Замовлення", "Категорія товару", "Оцінка",
"Оплата" та інші. Кожна з цих сутностей визначається своїми атрибутами та
відносинами з іншими сутностями, що відображає складність та багатогранність
доменної області. Розуміння цих сутностей та їх взаємозв'язків дозволяє ефективно
проєктувати базу даних, розробляти функціональність та створювати зручний та
ефективний для користувача програмний продукт.
Атрибут в доменній області – це конкретна властивість або характеристика
сутності, яка описується або ідентифікується в межах системи. Вони представляють
собою деталізовану інформацію про сутності та визначають їх властивості або
особливості. Атрибути можуть бути різних типів, таких як числові, рядкові, дати,
булеві тощо, в залежності від характеру даних, які вони відображають.
Зв'язок в доменній області визначає взаємозв'язок між сутностями та вказує на
їх взаємодію або залежність одна від одної. Зв'язки можуть бути однонаправленими
або двонаправленими, а також мати різні ступені асоціації, такі як один до одного,
один до багатьох або багато до багатьох.
Після проведення дослідження предметної області можна виділити такі
сутності: продукт, атрибут продукта, атрибут, категорія, атрибут категорії, фото
продукта, налаштування оплати, інформація про банківський рахунок, чат,
37
користувач чату, повідомлення чату, прочитані повідомлення, інформація про
доставку продукта, замовлення, офер продукта, список бажань, корзина, користувач,
сповіщення, отримувач сповіщення, авторизація.
В Додатку Г представлено: схему БД; таблицю опублікованих товарів у БД;
таблицю замовлень у БД; таблицю користувачів у БД; таблицю корзини у БД;
таблицю списку бажань у БД; таблицю атрибутів у БД; таблицю категорій у БД;
таблицю сповіщень у БД;
2.6 Опис процесу проєктування веб-орієнтованого сервісу для надання
послуг з реалізації вторинних товарів
У рамках кваліфікаційної роботи бакалавра, окрім розгляду предметної
області та вибору програмно-технічних засобів, включається розробка веб-
орієнтованого сервісу для надання послуг з реалізації вторинних товарів і
проведення тестування. Для опису функціональності сервісу необхідно визначити і
описати наступні процеси:
− процес реєстрації користувача;
− процес авторизації/аутентифікації користувача;
− процес перегляду товарів;
− процес створення замовлення;
− процес публікації товару.
Перший процес, в якому користувач приймає участь – процес реєстрація (рис.
2.4). Для реєстрації на веб-сервісі користувач повинен: ввести свій номер телефону;
ввести домашню адресу; ввести свою банківську інформацію; перевірка даних на
валідність; підтвердження електронної пошти.
Рисунок 2.4 – Діаграма процесу реєстрації
38
Процес авторизації/аутентифікації користувача (рис. 2.5) потрібен для
індентифікації користувача сервісом і надання відповідних прав на її використання.
Для авторизації користувач повинен:
− ввести електронну пошту і пароль;
− перевірити валідності даних;
− підтвердити авторизацію/аутентифікацію.
Рисунок 2.5 – Діаграма процесу авторизації
Процес створення замовлення (рис. 2.6) є найбільш цінним із огляду
функціонування бізнесу, так як він дозволяє отримувати прибуток сервісу.
Для створення замовлення користувач повинен:
− переглянути товар;
− додати товар в корзину;
− перевірити корзину;
− оплатити покупку.
Рисунок 2.6 – Діаграма процесу створення замовлення
Провівши етап проєктування для веб-орієнтованого сервісу для надання
послуг з реалізації вторинних товарів, в кваліфікаційній роботі бакалавра було
обрано струкуту веб-орієнтованого сервісу, що скадається з таких розділів:
− головна сторінка;
− адміністративна сторінка;
− реєстрація користувача;
39
− авторизація/аутентифікація користувача;
− особистий кабінет користувач;
− чат;
− створення замовлення;
− корзина;
− керування товарами;
− публікація товару;
− список бажань;
− каталог товарів.
Структура веб-орієнтованого сервісу представлена на рисунку 2.7.
Рисунок 2.7 – Структура веб-орієнтованого сервісу
40
2.7 Проєктування інтерфейсу користувача
В рамках кваліфікаційної роботи бакалавра був розроблений макет проєкту,
який покращують досвід користувача у використанні веб-орієнтовних сервісів. Для
досягнення цієї мети використовувався веб-інструмент Figma і направлення Material
Design.
Figma – це програмне забезпечення для дизайну, яке стало надзвичайно
популярним серед дизайнерів і розробників інтерфейсів. Вона дозволяє створювати
векторні графічні елементи, такі як іконки, кнопки, макети сторінок та інші
елементи інтерфейсу.
Основні переваги Figma:
Відкритість і спільна робота: Figma забезпечує можливість спільної роботи
над проєктами в реальному часі. Кілька користувачів можуть працювати над тим
самим файлом, переглядати зміни та коментувати їх.
Хмарне зберігання і синхронізація: Файли у Figma зберігаються в хмарі, що
робить їх доступними з будь-якого пристрою з Інтернет-підключенням. Це дозволяє
працювати над проєктами з будь-якого місця і з будь-якого пристрою.
Векторна графіка і масштабованість: Figma працює з векторною графікою, що
дозволяє масштабувати елементи без втрати якості. Це особливо корисно для
дизайну інтерфейсів, де різні пристрої можуть мати різні роздільні здатності.
Бібліотеки та компоненти: Figma дозволяє створювати бібліотеки
компонентів, які можна повторно використовувати в різних проєктах. Це прискорює
процес дизайну і забезпечує єдність дизайну між різними частинами проєкту.
Протипування: За допомогою Figma можна створювати прототипи
інтерфейсів, які дозволяють перевіряти взаємодію користувача з додатком або веб-
сайтом до початку розробки [10].
Material Design – це дизайн-мова, розроблена Google, яка стала стандартом для
розробки інтерфейсів користувача на платформах Google, таких як Android, веб-
додатки і деякі інші продукти компанії.
41
Матеріальні стандарти: Material Design базується на розумінні поведінки
реальних матеріалів, таких як папір і метал. Він використовує тіні, освітлення, рух і
ефекти, щоб створити реалістичний вигляд інтерфейсів.
Естетика і зручність: Material Design прагне до поєднання краси і зручності.
Це включає в себе чистий та зрозумілий дизайн, великий акцент на використанні
кольорів і типографії для створення інтуїтивно зрозумілих інтерфейсів.
Анімація і рух: Material Design активно використовує анімацію та рух для
покращення взаємодії користувача з додатками. Рухливість відтворює важливу роль
у візуальному сприйнятті інтерфейсу, допомагаючи користувачам зрозуміти, що
відбувається на екрані.
Адаптивність: Material Design розроблений з урахуванням адаптивного
дизайну, що дозволяє інтерфейсам працювати на різних пристроях і роздільних
здатностях, забезпечуючи при цьому зручність користування.
Документація і ресурси: Google забезпечує широкий набір товарів для
розробників, які включають в себе рекомендації з дизайну, готові компоненти і інші
інструменти для створення інтерфейсів відповідно до стандартів Material Design.
За допомогою інструменту «Figma» були розроблені макети для подальшої
реалізації за допомогою JSX (рис. 2.8 – 2.10).
Рисунок 2.8 – Інтерфейс Головної сторінки
42
На голній сторінці знаходиться компонент шапки веб-орієнтованого сервісу,
яка відборжає вибрану мову, кількість сповіщень, поточні чати і профіль
користувача.
На риснку 2.9 зоброжений футер веб-сервісу в якому знаходиться додаткова
інформація про сервіс.
Рисунок 2.9 – Інтерфейс футера веб-орієнтованого сервісу
Важливою сторінку для користувача – особистий кабінет, де користувач може
встановити своє зображення і надати додаткову інформацію (рис. 2.10).
Рисунок 2.10 – Інтерфейс особистого кабінета користувача
43
Висновки до розділу 2
У розділі надається опис функціонального програмного середовища для веб-
орієнтованого сервісу з надання послуг з реалізації вторинних товарів, а також
розглядаються основні принципи розробки веб-орієнтованих сервісів.
Була розроблена діаграма Use Case на основі інформації про користувачів веб-
орієнтованих сервісів для надання послуг з реалізації вторинних товарів.
За допомогою веб-додатку Figma був розроблений дизайн веб-орієнтованого
сервісу для надання послуг із реаліазції вторинних товарів.
44
3 ПРОГРАМНА РЕАЛІЗАЦІЯ
3.1 Постановка завдання
Завданням кваліфікаційної роботи бакалавра є реалізція веб-орієнтованого
сервісу для надання послуг з реалізації вторинних товарів.
Серверна частина веб-сервісу буде реалізована за допомогою фреймоврка
NestJS, який підтримує мову програмування TypeScript. Інтерфейс веб-сервісу буде
реалізований за допомогою бібліотеки React і менеджеру станів Redux.
Буде розроблена система аутентифікації, особистий кабінет користувача із
можливістю зміни фото аватара і доповненням даними, реалізована можливість
публікації товара із подальшим його продажом, користувач матиме корзину, для
перевірки доданого товару і подальшого оформлення замовлення, якщо користувачу
подобається товар, але він не може його на даний момент купити, він може додати
товар у список бажаного і повернутися до нього згодом.
Для зручності адміністратор матиме відповідно сторінку, на якій будуть
знаходить таблиці для керування і перегляду товарів, користувачів, замовлень,
категорій.
Для використання веб-сервісу апаратне забезпечення повинно відповідати
наступим мінімальним вимогам:
− телефон або комп’ютер повинен мати більше 350 пікселів ширини;
− доступ до Інтернету;
− 500 МБ опертивної пам’яті.
− мати пристрої введення (мишка або підтримка сенсорного вводу).
− більше 1 ГБ постійної пам’яті
− процес із частотою 1,1 Гц або більше
Основні програмні модулі, з яких складається серверна частина веб-
орієнтованого сервісу, зображено на рис 3.1.
45
Рисунок 3.1 – Модулі серверної частини веб-сервісу
Модулі серверної частини веб-сервісу поділені на підрозділи, які мають назву
контролери, сервіси, репозиторії, які можна розглянути на рис 3.2. Контролер
відповідає за транспортний рівень, в випадку веб-сервісу – Http протокол,
пересилання і отримування даних від клієнта. В сервісах знаходяться сутності, які
виконують бізнес-логіку додатка. Репозиторії відповідають за отримування і
повернення даних із бази даних за допомогою відповідної ORM.
Рисунок 3.2 – Підрозділи серверного модуля
Основні програмні модулі, з яких складається інтерфейсна частина веб-
орієнтованаго сервісу, зображена на рис 3.3.
Модулі інтерфейсної частини веб-сервісу поділенні на підрозділи, які мають
назву компоненти, сторінки, типи.
46
Компоненти – це самостійні частини інтерфейсу, які мають свій власний стан і
несуть відповідну користь для бізнес логіки. Сторінки – це компоненти, в яких
головна мета, об’єднувати компоненти в групи і налаштовувати взаємодію між ними
за допомогою колбеків від компонентів.
Рисунок 3.3 – Модулі інтерфейсної частини веб-сервісу
3.2 Інтерфейс веб-орієнтованого сервісу для надання послуг з реалізації
вторинних товарів
Для реалізації інтерфейсу веб-сервісу використовувалася технологія CSS
(cascade style sheet), яку найчастіше називають каскадна таблиця стилів. Головна
особливість полягає в тому, що на елементи інтрефейсу, які потрібно стилізувати,
потрібно навісити селектор. В файлі із розширенням .css потрібно написати правила
стилізації для цього селектора, на один селектор можна написати багато різних
правил, що дозволяє їх комбінувати, створюючи таким способом каскадну таблицю
стилів. Приклад каскадної таблиці стилів для компонента, можна розглянути на рис
3.4.
47
Рисунок 3.4 – Каскадна таблиця стилів компонента ProfilePage
Для реалізації інтерфейсу використовувався стиль Material Design, він в себе
включає прості і зрозумілі правила, які використовую Google в своїх проєктах.
Інтерфейс повинен бути не перегружений елементами, легко сприйматися, мати
прості форми. Приклад сторінки реєстрації веб-сервісу реалізований за домогою
Material Design можна розглянути на рис. 3.5.
Рисунок 3.5 – Сторінка реєстрації
48
Іншим прикладом реалізації Material Design, може послугувати сторінка
адміністратора (рис. 3.6).
Рисунок 3.6 – Сторінка адміністратора
Сторінка складається із двох частин. В першій частині знаходиться, меню по
якому адміністратора може вибрати таблицю для керування. В другій частині
знаходиться таблиця, яка в залежностві від вибору адміністратора, буде містити
різну інформацію про користувачів, товари, категорії товарів, атрибути.
3.3 Програмна реалізація веб-орієнтованого сервісу
У класі AuthService в методі register (рис. 3.7) отримуємо дані від клієнта, які
знаходяться в змінній userCreateRequest, перевіряємо чи існує користувач з
вказаною електронною поштою, якщо користувач існує, то повертаємо
InvalidCredentialsException, який інформує клієнт, про невалідовані дані для
реєстрації. Після валідації користувача, завдяки cryptoService, хешуємо пароль
користувача, створюємо в базі користувача і оновлюємо його дані, які стосуються
доставки і кредитної карточки. Метод повертає створенного користувача.
49
Рисунок 3.7 – Метод register
Для аворизації користувача (рис. 3.8), виконується метод login. Метод приймає
користувача, і генерує JWT-токен, завдяки методу generateJwtToken. Метод повертає
токен, який складається із двох полів accessToken і RefreshToken. RefreshToken
зберігається в базі даних, для подальшого використання.
AccessToken використовується для авторизованого доступу до різноматних
товарів сервера, але має малий період життя, приблизно від 10 – 20 хвилин, після
смерті користувач повинен, оновити свій AccessToken, завдяки надсиланню
RefreshToken на відповідну кінцеву точку.
RefreshToken також має обмежений час життя, коли термін життя токену
закінчеться, користувач більше не зможе його використати для отримання нової
50
пари accessToken і refreshToken, в такому випадку користувачеві потрібно буде
знову пройти процес авторизації.
Рисунок 3.8 – Метод login
Клас ProductService використовується для публікації товару на веб-сервісу,
завдяки використанню методу create (рис. 3.9). Для публікації товару, метод отримує
ідентифікатор категорії і атрибутів продуктів, які налаштував адміністратор, на
своїй сторінці. Після отримання атрибутів, створюється запис в базі даних.
Рисунок 3.9 – Метод create
Клас CartService використовується для реалізації корзини користувача. Для
додання продукта в корзину використовується метод addProduct (рис. 3.10). Метод
приймає параметер ProductCartRequestDto, шукає сутність корзини користувача за
змінною userId. Перевіряє чи існує вже продукт в корзині, чи користувач є
продавцем цього продукту, якщо користувач є продавцем цього продукту, або такий
продукт вже в корзині, то ми отримуємо повертаємо InvalidCredentialsException. В
іншому випадку ми додаємо продукт до корзини.
51
Рисунок 3.10 – Метод addProduct
Клас LiqPayService використовується для реалізації оплати за замовлення
користувачем, за допомогою метода checkout (рис. 3.11). Метод приймає параметер
createPayment, створює нову змінну initPayment, яка є об’єктом, що включає в себе
інформацію про платіжну карту користувача, суму замовлення, публічний ключ
доступу.
Рисунок 3.11 – Метод checkout
52
Клас Order Service використовується для реалізації замовлень за допомогою
методу create (рис. 3.12). Метод приймає параметер createOrderRequestDto,
знаходить всі продукти за допомогою їхніх ідентифікаторів, перевіряє чи є товар,
який може бути вже проданим, якщо є товар, який проданий, то повертає помилку
SoldProductException(). Інакше групує замовлення із їхніми продавцями, і записує в
базу даних.
Рисунок 3.12 – Метод create класа OrderService
Клас ChatGateway використовується для реалізації чату на основі технологій
WebSocket, яка дозволяє обмінюватися даними серверу і клієнту в реальному часі.
Завдяки технології WebSocket, сервер може ініціювати запит до клієнта, що
дозволяє писати більш гнучку бізнес-логіку. Процес комунікації сервера і клієнта
побувадований на подіях. Метод handleCreateChat (рис. 3.13) оброблює подію
ChatEvent.CREATE_CHAT, яку надсилає клієнт, при створенні чату між продавцем і
покупцем. Завдяки аргументу createChatRequestData, створюється чат між двома
користувачами. Отримуємо продавця і покупця, створюємо між ними зв’язок за
допомогою WebSocket сервера, додаємо їх в одну віртуальну кімнату. Віртуальна
53
кімната потрібна, для перехоплення подій і пересилання подій всім клієнтам, які
знаходяться в цій кімнаті.
Рисунок 3.13 – Метод handleCreateChat
3.4 Тестування веб-орієнтованого сервісу
Процес тестування програмного забезпечення є важливим етапом в розробці
програмного забезпечення різного рівня складності. Від якості та ефективності
тестування залежить не лише функціональність та надійність програмного
забезпечення, але і його спроможність задовольняти потреби та очікування
користувачів.
Існують різні види і методи тестування програмного забезпечення, але одним
із найкращих методів тестування – ручне верифікаційне тестування. Головна суть
даного методу тестування полягає, в тому, що тестувальник скаладає план
тестування з різними віріантами тестування програмного забезпечення.
Розглянемо тестування реєстрації користувача (рис. 3.14). Для успішної
реєстрації користувачеві потрібно заповнити інформацію про себе і свою платіжну
картку, щоб платформа мала змогу виплачувати винагороду в випадку покупки
товару.
54
Рисунок 3.14 – Форма реєстрації користувача
Після заповнення форми, користувач має змогу аваторизувався, відповідно
тестовий випадок реєстрації перевірено успішно.
Наступним кроком, який є важливий для продавця – публікація продукта на
платформі (рис. 3.15). Для цього тестового випадку користувач повинен ввести назву
продукту, його опис, категорію, зображення.
Рисунок 3.15 – Форма публікації товару
55
Після заповнення форми, ми отримуємо продукти, користувача перенаправляє
на сторінку створеного продукту, відповідний тестовий випадок публікації товару
успішно протестований.
Зазвичай продавець, заповнює додаткову інформацію про себе, щоб
підвищити продажі, для покупця важливо розуміти, що і в кого він купляє. Для
цього продавцеві потрібно перейти в особистий кабінет і заповнити інформацію про
себе (рис. 3.16).
Рисунок 3.16 – Форма оновлення даних користувача
Після заповнення форми, інформація про продавця оновлюється. Тестовий
випадок успішно пройдено.
Важливою для продавця і покупця є можливість комунікації між собою. Для
досягнення цієї цілі веб-сервіс реалізую чат навколо товару (рис. 3.17). Покупець може
розпочати чати навколо товару із продавецем, перейшовши на сторінку товару і
натиснувши на кнопку розпочати чат.
Відкривається сторінка чату, в якому покупець може задавати питання
продавцеві, а продавець відповідати на питання. В разі відповіді покупець може
56
купити товар, або навіть почати торги, щоб змінити ціну товару, на більш кращу для
себе.
Рисунок 3.17 – Чат покупця і продавця
Після відправки повідомлення продавець і покупець бачать повідомлення, і
можу відповідати на це повідомлення. Покупець може запропонувати нижчу ціну
для товару. Тестовий випадок успішно пройдений.
3.5 Розгортання веб-орієнтованого сервісу
Розгортання веб-орієнтованого сервісу – це процес публікації та налаштування
програмного забезпечення (сервісу), який доступний через Інтернет за допомогою
веб-браузера або інших клієнтських програм. Цей процес включає в себе
розміщення веб-серверів, баз даних, файлів інформації та іншого необхідного
програмного забезпечення на серверах, які забезпечують доступ до сервісу через
Інтернет. Розгортання також включає в себе конфігурацію та налаштування
середовища для оптимальної роботи програми, а також встановлення заходів
безпеки для захисту від несанкціонованого доступу та інших загроз.
57
Для розгортання веб-орієнтоваго сервісу для надання послуг з реалізації
вторинних товарів була обрана послуга по наданню обчислювальної потужності
EC2 платформи Amazon.
Amazon EC2 (Elastic Compute Cloud) – це веб-сервіс від Amazon Web Services
(AWS), який надає масштабовані обчислювальні ресурси у вигляді віртуальних
серверів, відомих як інстанси (рис. 3.18).
Рисунок 3.18 – Створений інстанс Amazon EC2
Для автоматизації розгортування використовується техніка CI\CD. За
допомогою техніки CI/CD (Continuous Integration/Continuous Deployment)
використовується підхід, який полягає у постійному інтегруванні змін в коді та
автоматичному розгортанні цих змін в програмному продукті.
Continuous Integration (CI) – це процес автоматичного тестування та інтеграції
змін у вихідний код програми на регулярній основі. Це допомагає вчасно виявляти
та виправляти помилки, а також забезпечує стабільність коду.
Continuous Deployment (CD) – це автоматизований процес розгортання
зміненого коду до продуктивного середовища безпосередньо після проходження
58
тестів у CI. Це дозволяє швидко впроваджувати новий функціонал та виправлення
без затримок.
Для реалізації CI\CD була використана платформа GitLab, яка розроблена
українськими розробниками. Головна суть GitLab – надання послуг для хостингу git-
репозиторівії і автоматизація рутинних справ розробників.
Для реалізації CI\CD потрібен GitLab Runner. Його суть полягає в тому, що він
проводить всі операції, які потрібні для успішного розгортання програмного
продукту (рис. 3.19).
Рисунок 3.19 – Створений GitLab Runner
Після створення GitLab Runner, потрібно розробити pipeline. Pipeline
скалається із кроків, де кожний крок є задачаю для автоматизації, прикладом кроку
може бути – збірка проєкту, тестування проєкту, відправка зібраного проєкту на
віддалений сервер (рис. 3.20). Для автоматизації розгортання було розроблено три
кроки pipeline – збірка, тестування і розгортання.
59
На кроці збірка GitLab Runner, налаштовую npm (node package manager),
встановлює за допомогою npm всі сторонні залежності проєкту, починає збирати
проєкт в вигляд, який зможе виконати Node.js.
Рисунок 3.20 – Створений Pipeline
На кроці тестування, відбуваються схожі операції – за допомогою npm
встановлються всі залежності, і запускається unit-тестування, яке автоматизовано
перевіряє проєкт на помилки.
На кроці розгортання (рис. 3.21), GitLab Runner отримує зміни середовище із
безпечного сервісу GitLab, змінні містять ключ, хост і ім’я користувача для
встановлння з’єднання із серверами EC2 AWS. Після встановлення з’єднання, ми
підключаємося до сервера за допомогою служби SSH і ключа, який ми отримати із
змінних середовищ.
Після вдалого підключення SSH, відбувається перехід до каталогу із файлами
проєкту Node.js, які ми отримали на кроці збірки проєкту, змінюємо git гілку на dev,
отримуємо всі оновлення із віддаленого сервера за допомогою команди git,
60
припиняємо всі процеси, які пов’язані із docker compose, і створюємо нові процеси
docker compose, які вже будуть пов’язані із новою збіркою проєкту.
Рисунок 3.21 – Крок автоматизованого розгортання
Для перегляду виконання автоматизованого pipeline можна використовувати
сервіс GitLab. На рисинку 3.22 показаний приклад успішно виконаного pipeline.
Рисунок 3.22 – Результат автоматизації розгортання
61
Висновки до розділу 3
В даному розділі представлена програмна реалізація веб-орієнтованого
сервісу. Визначено, що сервіс буде розроблений за допомогою мови програмування
JavaScript, серверна частина буде створенна за допомогою фреймоврку NestJS, і бази
даних Postgresql, клієнтська частина за допомогою бібліотеки React і менеджеру
станів Redux.
Описано інтерфейс та реалізацію веб-орієнтованого сервісу для надання
послуг з реалізації вторинних товарів, а також тестування і розгортання сервісу за
домогою техніки CI\CD.
Виконано ручне тестування веб-орієнтованого сервісу. Результати тестування
надають змогу підтвердити якість програмного продукту. Докладно описаний
процес автоматизованого розгортання програмного продукту на серверах Amazon.
62
ВИСНОВКИ
Мета кваліфікаційної роботи бакалавра досягнута. В ході її виконання
спроєктовано та розроблено веб-орієнтований сервіс для надання послуг з реалізації
вторинних товарів.
Для досягнення поставленої мети в роботі розв’язувалися наступні завдання:
досліджено предметну область;
виконано порівняльний аналіз існуючих веб-орієнтованих сервісів для
надання послуг з реалізації вторинних товарів, а саме: «OLX», «Prom»;
розроблено схему бази даних на основі Postgresql;
обрано програмно-технічні засоби для реалізації програмного продукту;
розроблено веб-орієнтований сервіс для надання послуг з реалізації вторинних
товарів, виконано тестування програмного продукту і створена
авториматизація його розгортання на серверах.
Веб-орієнтований сервіс для надання послуг з реалізації вторинних товарів
був розроблений за допомогою мови програмування JavaScript, серверна частина
була розроблена за допомогою фреймворку NestJs, клієнтська частина реалізована
завдяки бібліотеці React і менеджеру станів Redux.
Обрані техніки і технології задовольняють всім функціональним і не
функціональним вимогам для розробки і впровадження програмного забезпечення.
Розроблений веб-орієнтований сервіс має зручний, сучасний і зрозумілий
інтерфейс, орієнтований на досвід користувача.
63
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ
1. Веб-орієнтовані сервіси. URL: https://2ip.ua/ua/blog/web-services (дата
звернення: 20.01.2024).
2. Редактор коду Visual Studio Code. URL: https://code.visualstudio.com/ (дата
звернення: 19.02.2024).
3. Мова програмування TypeScript. URL: https://www.typescriptlang.org/ (дата
звернення: 20.02.2024).
4. Фреймворк NestJS. URL: https://nestjs.com/ (дата звернення: ).
5. Робота з базами даних за допомогою TypeOrm. URL: https://typeorm.io/ (дата
звернення: 21.02.204).
6. Postgresql база даних. URL: https://www.postgresql.org/ (дата звернення:
01.03.2024).
7. ReactJs бібліотека. URL: https://react.dev/ (дата звернення: 03.03.2024).
8. Redux менеджер станів. URL: https://redux.js.org/ (дата звернення: 03.04.2024).
9. Архітектура веб-орієнтованих сервісів. URL:
https://pidru4niki.com/1383121947801/informatika/servis-oriyentovana_arhitektura
(дата звернення: 06.04.2024).
10. Figma – проєктування інтерфейсів. URL: https://www.figma.com/ (дата
звернення: 07.04.2024).
64
ДОДАТОК А
Затверджую
Зав. кафедри КНСА,
______________ Юрій ТРИУС
«____»____________2024 р.
ВЕБ-ОРІЄНТОВАНИЙ СЕРВІС ДЛЯ НАДННЯ ПОСЛУГ З РЕАЛІЗАЦІЇ
ВТОРИННИХ ТОВАРІВ
Специфікація
482.ЧДТУ. 42287-01
Листів 1
Розробник ____________________ Ляшенко Р.І.
Керівник ____________________ Підгорний М.В.
Черкаси – 2024
65
482.ЧДТУ. 42287-01
Позначення Найменування Примітка
Документація
482.ЧДТУ. 42287-01 12 01 Текст програми
482.ЧДТУ. 42287-01 34 01 Інструкція користувача
482.ЧДТУ. 42287-01 90 01 Структура бази даних
66
ДОДАТОК Б
ВЕБ-ОРІЄНТОВАНИЙ СЕРВІС ДЛЯ НАДАННЯ ПОСЛУГ З РЕАЛІЗАЦІЇ
ВТОРИННИХ ТОВАРІВ
Текст програми
482.ЧДТУ. 42287-01 12 01
Листів 28
Розробник _____________ Ляшенко Р.І.
Черкаси – 2024
67
Фрагмент програми
Клас AuthService:
import { Injectable } from '@nestjs/common';
import { UserService } from '../../user/services/user.service';
import { User } from '../../user/entities/user.entity';
import { JwtService } from '@nestjs/jwt';
import { JwtToken } from '../types/jwt-token.type';
import { BaseService } from '../../core/services/base.service';
import { UnitOfWorkService } from '../../core/services/unit-of-work.service';
import { UserResponseDto } from '../../user/dtos/response/user.response.dto';
import { EmailConfirmService } from './email-confirm.service';
import { LocalRegistrationRequestDto } from '../dtos/request/local-registration.request.dto';
import { EmailConfirmRequestDto } from '../dtos/request/email-confirm.request.dto';
import { HashService } from './hash.service';
import { EmailExistResponseDto } from '../dtos/response/email-exist.response.dto';
import * as process from 'node:process';
import { NotMatchException } from '../../core/exceptions/not-match.exception';
import { InvalidCredentialsException } from '../../core/exceptions/invalid-credentials.exception';
import { UserPayload } from '../types/user.payload.type';
import { LocalRegistrationWithoutTokenRequestDto } from '../dtos/request/local-registration-without-
token.request.dto';
import { TryResetPasswordRequestDto } from '../dtos/request/try-reset-password.request.dto';
import { NotFoundException } from '../../core/exceptions/not-found.exception';
import { ConfirmEmailPayload } from '../types/confirm-email-payload.type';
import { ConfirmEmail } from '../../views/types/email-confirm.type';
import { EmailService } from '../../core/services/email.service';
import { ResetPasswordRequestDto } from '../dtos/request/reset-password.request.dto';
import { InvalidJwtTokenException } from '../../core/exceptions/invalid-jwt-token.exception';
import { CustomLoggerService } from '../../core/logger/custom-logger.service';
import { PersonalSettingService } from '../../user/services/personal-setting.service';
import { UpdatePasswordRequestDto } from '../../user/dtos/request/update-password.request.dto';
import { UpdateShippingInfoRequestDto } from '../../shipping-info/dtos/request/update-shipping-info.request.dto';
import {
UpsertCardRequestDto
} from '../../card/dtos/request/upsert-card.request.dto';
import { ShippingInfoService } from '../../shipping-info/services/shipping-info.service';
import { CardService } from '../../card/services/card.service';
import { AdminRegistrationRequestDto } from '../dtos/request/admin-registration.request.dto';
@Injectable()
export class AuthService extends BaseService {
constructor(
protected readonly unitOfWork: UnitOfWorkService,
private readonly userService: UserService,
private readonly jwtService: JwtService,
private readonly emailConfirmService: EmailConfirmService,
private readonly emailService: EmailService,
private readonly cryptoService: HashService,
private readonly customLoggerService: CustomLoggerService,
private readonly personalSettingService: PersonalSettingService,
private readonly shippingInfoService: ShippingInfoService,
private readonly cardService: CardService,
) {
super(unitOfWork);
this.customLoggerService.setContext(AuthService.name);
68
}
public async adminRegister(adminRegister: AdminRegistrationRequestDto): Promise<UserResponseDto> {
const emailExist = await this.emailExist(adminRegister.email.toLowerCase());
if (!emailExist.isAvailability) {
throw new InvalidCredentialsException();
}
adminRegister.email = adminRegister.email.toLowerCase();
adminRegister.password = await this.cryptoService.hashing(adminRegister.password);
return await this.userService.createByLocalAuth(adminRegister);
}
public async register(userCreateRequest: LocalRegistrationWithoutTokenRequestDto):
Promise<UserResponseDto> {
const work = async () => {
const emailExist = await this.emailExist(userCreateRequest.email.toLowerCase());
if (!emailExist.isAvailability) {
throw new InvalidCredentialsException();
}
userCreateRequest.email = userCreateRequest.email.toLowerCase();
userCreateRequest.password = await this.cryptoService.hashing(userCreateRequest.password);
const user = await this.userService.createByRegistrationAuth(userCreateRequest);
const updateShippingInfo: UpdateShippingInfoRequestDto = {
addressLine: userCreateRequest.addressLine,
city: userCreateRequest.city,
zipCode: userCreateRequest.zipCode
};
const updateCard: UpsertCardRequestDto = {
number: userCreateRequest.number,
cvv: userCreateRequest.cvv,
expireMonth: userCreateRequest.expireMonth,
expireYear: userCreateRequest.expireYear,
};
await this.shippingInfoService.updateByUserId(user.id, updateShippingInfo);
await this.cardService.updateByUserId(user.id, updateCard);
return user;
};
return this.unitOfWork.doWork(work);
}
public async login(user: User): Promise<JwtToken> {
const token = await this.generateJwtToken(user);
await this.updateRefreshToken(user.id, token.refreshToken);
return token;
69
}
public async logout(userId: string): Promise<void> {
await this.userService.updateRefreshToken(userId, null);
}
public async refreshTokens(userId: string, refreshToken: string): Promise<JwtToken> {
const user = await this.unitOfWork.userRepository.findById(userId);
if (!user) {
throw new NotMatchException();
}
const refreshTokenMatches = await this.cryptoService.isCompare(
refreshToken,
user.auth.refreshToken,
);
if (!refreshTokenMatches) {
throw new NotMatchException();
}
return await this.login(user);
}
public async emailConfirm(confirmEmailRequest: EmailConfirmRequestDto): Promise<UserResponseDto> {
const email = await this.emailConfirmService.decryptConfirmToken(confirmEmailRequest.token);
return await this.userService.markEmailAsConfirmed(email);
}
public async userExistByLocalAuth(email: string, password: string): Promise<User | null> {
const user = await this.unitOfWork.userRepository.findByEmailLocalAuth(email.toLowerCase());
if (user) {
const isPasswordMatching = await this.cryptoService.isCompare(password, user.auth.localAuth.password);
if (isPasswordMatching) {
return user;
}
}
return null;
}
public async emailExist(email: string): Promise<EmailExistResponseDto> {
const user = await this.unitOfWork.userRepository.findByEmail(email.toLowerCase());
return {
isAvailability: !Boolean(user),
};
}
public async tryResetPassword(tryResetPasswordRequestDto: TryResetPasswordRequestDto): Promise<void> {
const { email } = tryResetPasswordRequestDto;
const foundedUser = await this.unitOfWork.userRepository.findByEmail(email);
70
if (!foundedUser) {
throw new NotFoundException();
}
const payload: ConfirmEmailPayload = { email };
const token = this.jwtService.sign(payload, {
secret: process.env.JWT_VERIFICATION_TOKEN_SECRET,
expiresIn: `${process.env.JWT_VERIFICATION_TOKEN_EXPIRATION_TIME}s`
});
const emailWithLink: ConfirmEmail = {
link: `${process.env.SITE_URL}/${process.env.RESET_PASSWORD_URL}?token=${token}`
};
await this.updateResetPasswordToken(foundedUser.id, token);
return this.emailService.sendResetPassword(email, emailWithLink);
}
public async resetPassword(resetPasswordRequestDto: ResetPasswordRequestDto): Promise<void> {
const { resetPasswordToken, password } = resetPasswordRequestDto;
const email = await this.decryptResetPasswordToken(resetPasswordToken);
const foundedUser = await this.unitOfWork.userRepository.findByEmail(email);
if (!foundedUser) {
throw new NotFoundException();
}
if (!foundedUser.auth.resetPasswordToken) {
throw new InvalidJwtTokenException();
}
const isSameResetPasswordToken = await this.cryptoService.isCompare(
resetPasswordToken,
foundedUser.auth.resetPasswordToken
);
if (!isSameResetPasswordToken) {
throw new InvalidJwtTokenException();
}
const updatePasswordRequestDto: UpdatePasswordRequestDto = { password };
await this.personalSettingService.updatePassword(foundedUser.id, updatePasswordRequestDto);
foundedUser.auth.resetPasswordToken = null;
foundedUser.auth.refreshToken = null;
await this.unitOfWork.userRepository.save(foundedUser);
}
private async generateJwtToken(user: User): Promise<JwtToken> {
const payload: UserPayload = {
sub: user.id,
firstName: user.firstName,
lastName: user.lastName,
imageUrl: User.toImageResponseDto(user.imageUrl),
71
publicProfileId: user.publicProfileId,
systemRole: user.systemRole,
isEmailConfirm: user.isEmailConfirm,
};
const accessToken = this.jwtService.sign(payload, {
secret: process.env.JWT_ACCESS_SECRET,
expiresIn: `${process.env.JWT_ACCESS_EXPIRE_TIME}s`,
});
const refreshToken = this.jwtService.sign(payload, {
secret: process.env.JWT_REFRESH_SECRET,
expiresIn: `${process.env.JWT_REFRESH_EXPIRE_TIME}s`,
});
return {
accessToken,
refreshToken,
};
}
private async updateRefreshToken(userId: string, refreshToken: string): Promise<void> {
const hashedRefreshToken = await this.cryptoService.hashing(refreshToken);
await this.userService.updateRefreshToken(userId, hashedRefreshToken);
}
private async updateResetPasswordToken(userId: string, resetPasswordToken: string): Promise<void> {
const hashedResetToken = await this.cryptoService.hashing(resetPasswordToken);
await this.userService.updateResetPasswordToken(userId, hashedResetToken);
}
private async decryptResetPasswordToken(resetPasswordToken: string): Promise<string> {
try {
const payload = await this.jwtService.verify(resetPasswordToken, {
secret: process.env.JWT_VERIFICATION_TOKEN_SECRET,
});
return payload.email;
} catch (error) {
this.customLoggerService.error(error);
throw new InvalidJwtTokenException();
}
}
}
Клас EmailConfirmService:
import { Injectable } from '@nestjs/common';
import { BaseService } from '../../core/services/base.service';
import { UnitOfWorkService } from '../../core/services/unit-of-work.service';
import { JwtService } from '@nestjs/jwt';
import { ConfirmEmailPayload } from '../types/confirm-email-payload.type';
import * as process from 'node:process';
import { CustomLoggerService } from '../../core/logger/custom-logger.service';
import { InvalidJwtTokenException } from '../../core/exceptions/invalid-jwt-token.exception';
import { ConfirmEmail } from '../../views/types/email-confirm.type';
72
import { NotMatchException } from '../../core/exceptions/not-match.exception';
import { InvalidCredentialsException } from '../../core/exceptions/invalid-credentials.exception';
import { EmailService } from '../../core/services/email.service';
@Injectable()
export class EmailConfirmService extends BaseService {
constructor(
protected readonly unitOfWork: UnitOfWorkService,
private readonly jwtService: JwtService,
private readonly emailService: EmailService,
private readonly customLoggerService: CustomLoggerService,
) {
super(unitOfWork);
customLoggerService.setContext(EmailConfirmService.name);
}
public async sendVerificationLinkByUserId(userId: string): Promise<void> {
const user = await this.unitOfWork.userRepository.findById(userId);
if (!user) {
throw new NotMatchException();
}
if (user.isEmailConfirm) {
throw new InvalidCredentialsException();
}
const emailToConfirm = user.auth.email.toLowerCase();
void this.sendVerificationLink(emailToConfirm);
}
public async sendVerificationLink(email: string): Promise<void> {
const payload: ConfirmEmailPayload = { email: email.toLowerCase() };
const token = this.jwtService.sign(payload, {
secret: process.env.JWT_VERIFICATION_TOKEN_SECRET,
expiresIn: `${process.env.JWT_VERIFICATION_TOKEN_EXPIRATION_TIME}s`
});
const confirmEmail: ConfirmEmail = {
link: `${process.env.SITE_URL}/${process.env.EMAIL_CONFIRMATION_URL}/${token}`,
}
return this.emailService.sendConfirmEmail(email, confirmEmail);
}
public async decryptConfirmToken(token: string): Promise<string> {
try {
const payload = await this.jwtService.verify(token, {
secret: process.env.JWT_VERIFICATION_TOKEN_SECRET,
});
return payload.email;
} catch (error) {
this.customLoggerService.error(error);
throw new InvalidJwtTokenException();
73
}
}
}
Клас UserService:
import { Injectable } from '@nestjs/common';
import { BaseService } from '../../core/services/base.service';
import { UnitOfWorkService } from '../../core/services/unit-of-work.service';
import { UserResponseDto } from '../dtos/response/user.response.dto';
import { User } from '../entities/user.entity';
import { SocialLoginRequestDto } from '../../auth/dtos/request/social-login.request.dto';
import { UpdateUserRequestDto } from '../dtos/request/update-user.request.dto';
import { NotFoundException } from '../../core/exceptions/not-found.exception';
import { PublicProfileResponseDto } from '../dtos/response/public-profile.response.dto';
import { PageOptionsRequestDto } from '../../core/dto/request/page-options.request.dto';
import { PageResponseDto } from '../../core/dto/response/page.response.dto';
import { PaginationService } from '../../core/services/pagination.service';
import { DetailUserResponseDto } from '../dtos/response/detail-user.response.dto';
import { AdminRegistrationRequestDto } from '../../auth/dtos/request/admin-registration.request.dto';
import { ProductService } from '../../product/services/product.service';
import {
LocalRegistrationWithoutTokenRequestDto
} from '../../auth/dtos/request/local-registration-without-token.request.dto';
@Injectable()
export class UserService extends BaseService {
constructor(
protected readonly unitOfWork: UnitOfWorkService,
private readonly paginationService: PaginationService,
private readonly productService: ProductService,
) {
super(unitOfWork);
}
public async findAll(pageOptionsDto: PageOptionsRequestDto):
Promise<PageResponseDto<DetailUserResponseDto>> {
const userSelectQueryBuilder = await
this.unitOfWork.userRepository.findAllQueryBuilder(pageOptionsDto);
const { entities } = await userSelectQueryBuilder.getRawAndEntities();
const totalItemCount = await userSelectQueryBuilder.getCount();
const detailUserResponseDtos = entities.map((entity) => User.toDetailUser(entity));
return this.paginationService.createPage(detailUserResponseDtos, pageOptionsDto, totalItemCount);
}
public async findById(id: string): Promise<DetailUserResponseDto> {
const user = await this.unitOfWork.userRepository.findById(id);
return User.toDetailUser(user);
}
public async findOwners(): Promise<UserResponseDto[]> {
74
const owners = await this.unitOfWork.userRepository.findAll();
return owners.map((owner) => User.toDto(owner));
}
public async findTopSellers(countSeller: number): Promise<UserResponseDto[]> {
const topSellers = await this.unitOfWork.userRepository.findSellersByProductCount(countSeller);
return topSellers.map((topSeller) => User.toDto(topSeller));
}
public async findByPublicProfileId(publicProfileId: string): Promise<PublicProfileResponseDto> {
const user = await this.unitOfWork.userRepository.findByPublicProfileId(publicProfileId);
if (!user) {
throw new NotFoundException();
}
return User.toPublicProfileDto(user);
}
public async createByLocalAuth(localRegistrationRequestDto: AdminRegistrationRequestDto):
Promise<UserResponseDto> {
const work = async () => {
const userFromDto = User.fromLocalAuthDto(localRegistrationRequestDto);
const publicProfileId = await this.generatePublicProfileId(userFromDto.firstName, userFromDto.lastName
);
const userPublicProfile = { ...userFromDto, publicProfileId };
const user = await this.unitOfWork.userRepository.save(userPublicProfile);
await this.generateStandardEntities(user.id);
return User.toDto(user);
}
return await this.unitOfWork.doWork(work);
}
public async createByRegistrationAuth(userCreate: LocalRegistrationWithoutTokenRequestDto):
Promise<UserResponseDto> {
const work = async () => {
const userFromDto = User.fromLocalAuthDto(userCreate);
const publicProfileId = await this.generatePublicProfileId(userFromDto.firstName, userFromDto.lastName
);
userFromDto.firstName = userCreate.firstName;
userFromDto.lastName = userCreate.lastName;
userFromDto.phoneNumber = userCreate.phoneNumber;
userFromDto.aboutMe = '';
const userPublicProfile = { ...userFromDto, publicProfileId };
const user = await this.unitOfWork.userRepository.save(userPublicProfile);
await this.generateStandardEntities(user.id);
75
return User.toDto(user);
};
return await this.unitOfWork.doWork(work);
}
public async createByGoogleAuth(socialLoginRequestDto: SocialLoginRequestDto):
Promise<UserResponseDto> {
const work = async () => {
const userFromDto = User.fromGoogleAuthDto(socialLoginRequestDto);
const publicProfileId = await this.generatePublicProfileId(userFromDto.firstName, userFromDto.lastName
);
const userPublicProfile = { ...userFromDto, publicProfileId };
const user = await this.unitOfWork.userRepository.save(userPublicProfile);
await this.generateStandardEntities(user.id);
return User.toDto(user);
}
return await this.unitOfWork.doWork(work);
}
public async createByFacebookAuth(socialLoginRequestDto: SocialLoginRequestDto):
Promise<UserResponseDto> {
const work = async () => {
const userFromDto = User.fromFacebookAuthDto(socialLoginRequestDto);
const publicProfileId = await this.generatePublicProfileId(userFromDto.firstName, userFromDto.lastName
);
const userPublicProfile = { ...userFromDto, publicProfileId };
const user = await this.unitOfWork.userRepository.save(userPublicProfile);
await this.generateStandardEntities(user.id);
return User.toDto(user);
}
return await this.unitOfWork.doWork(work);
}
public async getByGoogleSocialId(socialId: string): Promise<User> | null {
const user = await this.unitOfWork.userRepository.findByGoogleSocialId(socialId);
return Boolean(user) ? user : null;
}
public async getByFacebookSocialId(socialId: string): Promise<User> | null {
const user = await this.unitOfWork.userRepository.findByFacebookSocialId(socialId);
return Boolean(user) ? user: null;
}
public async updateRefreshToken(userId: string, refreshToken: string): Promise<User> {
const work = async () => {
const user = await this.unitOfWork.userRepository.findById(userId);
76
if (!user) {
throw new NotFoundException();
}
user.auth.refreshToken = refreshToken;
return await this.unitOfWork.userRepository.save(user);
}
return await this.unitOfWork.doWork(work);
}
public async updateResetPasswordToken(userId: string, resetToken: string): Promise<User> {
const work = async() => {
const user = await this.unitOfWork.userRepository.findById(userId);
if (!user) {
throw new NotFoundException();
}
user.auth.resetPasswordToken = resetToken;
return await this.unitOfWork.userRepository.save(user);
}
return await this.unitOfWork.doWork(work);
}
public async markEmailAsConfirmed(email: string): Promise<UserResponseDto> {
const work = async () => {
const userFromRepository = await this.unitOfWork.userRepository.findByEmailLocalAuth(email);
if (!userFromRepository) {
throw new NotFoundException();
}
userFromRepository.isEmailConfirm = true;
const user = await this.unitOfWork.userRepository.save(userFromRepository);
return User.toDto(user);
}
return this.unitOfWork.doWork(work);
}
public async update(id: string, updateUserRequestDto: UpdateUserRequestDto): Promise<UserResponseDto> {
const work = async () => {
const userFromDto = User.fromUpdateUserDto(updateUserRequestDto);
const userToUpdate = await this.unitOfWork.userRepository.findById(id);
if (!userToUpdate) {
throw new NotFoundException();
}
userToUpdate.firstName = userFromDto.firstName;
userToUpdate.lastName = userFromDto.lastName;
77
userToUpdate.isEmailConfirm = userFromDto.isEmailConfirm;
userToUpdate.phoneNumber = userFromDto.phoneNumber;
userToUpdate.systemRole = userFromDto.systemRole;
const user = await this.unitOfWork.userRepository.save(userToUpdate);
return User.toDto(user);
}
return await this.unitOfWork.doWork(work);
}
public async findUserByPhoneNumber(phoneNumber: string): Promise<UserResponseDto | null> {
const user = await this.unitOfWork.userRepository.findByPhoneNumber(phoneNumber);
return user ? User.toDto(user) : null;
}
public async isExistUserByPhoneNumber(phoneNumber: string): Promise<boolean> {
const user = await this.findUserByPhoneNumber(phoneNumber);
return Boolean(user);
}
public async generatePublicProfileId(firstName?: string, lastName?: string): Promise<string> {
const publicIdSuffixMax = 9999;
const lengthPublicIdSuffix = 4;
const fullName = `${firstName ?? ''}${lastName ?? ''}`;
let publicProfileId = '';
do {
const randomNumber = Math.random() * publicIdSuffixMax;
const publicIdSuffix = Math.round(randomNumber).toString();
const formattedPublicIdSuffix = publicIdSuffix.padStart(lengthPublicIdSuffix, '0');
publicProfileId = fullName + formattedPublicIdSuffix;
const userWithPublicId = await this.unitOfWork.userRepository.findByPublicProfileId(publicProfileId);
const isUserExists = Boolean(userWithPublicId);
if (!isUserExists) break;
} while (true);
return publicProfileId;
}
public async softDeleteById(id: string): Promise<void> {
const work = async () => {
const user = await this.unitOfWork.userRepository.findByIdWithProduct(id);
if (!user) {
throw new NotFoundException();
}
const productIds = user.products.map((product) => product.id);
if (productIds.length > 0) {
78
await this.productService.deleteByIds(productIds);
}
await this.unitOfWork.cartRepository.softDeleteByUserId(id);
await this.unitOfWork.wishlistRepository.softDeleteByUserId(id);
await this.unitOfWork.shippingInfoRepository.softDeleteByUserId(id);
await this.unitOfWork.cardRepository.softDeleteByUserId(id);
await this.unitOfWork.userRepository.softRemove(user);
}
return this.unitOfWork.doWork(work);
}
private async generateStandardEntities(userId: string): Promise<void> {
await this.unitOfWork.wishlistRepository.generateWishlist(userId);
await this.unitOfWork.cartRepository.generateCart(userId);
await this.unitOfWork.shippingInfoRepository.generateShippingInfo(userId);
await this.unitOfWork.cardRepository.generateCard(userId);
}
}
Клас ProductService:
import { Injectable } from '@nestjs/common';
import { BaseService } from '../../core/services/base.service';
import { UnitOfWorkService } from '../../core/services/unit-of-work.service';
import { UpdateProductRequestDto } from '../dtos/request/update-product.request.dto';
import { Product } from '../entities/product.entity';
import { ProductResponseDto } from '../dtos/response/product.response.dto';
import { NotFoundException } from '../../core/exceptions/not-found.exception';
import { PageOptionsRequestDto } from '../../core/dto/request/page-options.request.dto';
import { PageResponseDto } from '../../core/dto/response/page.response.dto';
import { PaginationService } from '../../core/services/pagination.service';
import { QuantityLikeResponseDto } from '../dtos/response/quantity-like.response.dto';
import { SelectQueryBuilder } from 'typeorm';
import { ProductDetailedAttributeResponseDto } from '../dtos/response/product-detailed-attribute.response.dto';
import { FilterProductRequestDto } from '../dtos/request/filter-product.request.dto';
import { CreateProductRequestDto } from '../dtos/request/create-product.request.dto';
import { ProductAttributeService } from '../../attribute/services/product-attribute.service';
import { CategoryService } from '../../category/services/category.service';
import { IdValuesRequestDto } from '../../core/dto/request/id-values.request.dto';
import { AttributeService } from '../../attribute/services/attribute.service';
import { AttributeType } from '../../attribute/enums/attribute-type.enum';
import { TotalMaxMinPropertyResponseDto } from '../dtos/response/total-max-min-property.response.dto';
import { PageWithDataMetaResponseDto } from '../../core/dto/response/page-with-data-meta.response.dto';
import { corePath, storageProductFolder } from '../../configs/upload-file-config';
import { FileService } from '../../core/services/file.service';
import { ProductWithStatisticResponseDto } from '../dtos/response/product-with-statistic.response.dto';
import {
ProductDetailedAttributeBeforeUpdateResponseDto
} from '../dtos/response/product-detailed-attribute-before-update.response.dto';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class ProductService extends BaseService {
constructor(
79
protected readonly unitOfWork: UnitOfWorkService,
private readonly paginationService: PaginationService,
private readonly attributeService: AttributeService,
private readonly productAttributeService: ProductAttributeService,
private readonly categoryService: CategoryService,
private readonly fileService: FileService,
) {
super(unitOfWork);
}
public async findAll(pageOptionsDto: PageOptionsRequestDto):
Promise<PageResponseDto<ProductResponseDto>> {
const productQueryBuilder = await this.unitOfWork.productRepository.findQueryBuilderByFilter(
pageOptionsDto,
);
return this.createPaginationPage(productQueryBuilder, pageOptionsDto);
}
public async findQuantityLike(id: string): Promise<QuantityLikeResponseDto> {
const quantityLike = await this.unitOfWork.productWishlistRepository.findQuantityProduct(id);
return { quantityLike };
}
public async findJustArrived(quantity: number, userId?: string): Promise<ProductResponseDto[]> {
const products = await this.unitOfWork.productRepository.findJustArrived(quantity, userId);
return products.map((product) => Product.toDto(product));
}
public async findById(id: string, userId?: string): Promise<ProductDetailedAttributeResponseDto> {
const product = await this.unitOfWork.productRepository.findById(id, userId);
if (!product) {
throw new NotFoundException();
}
const attributes = await this.unitOfWork.attributeRepository.findByProductId(product.id);
return Product.toProductDetailedAttributesDto(product, attributes);
}
public async findByIdBeforeUpdate(id: string, userId?: string):
Promise<ProductDetailedAttributeBeforeUpdateResponseDto> {
const product = await this.unitOfWork.productRepository.findById(id, userId);
if (!product) {
throw new NotFoundException();
}
const attributes = await this.unitOfWork.attributeRepository.findByProductId(product.id);
return {
...Product.toProductDetailedAttributesDto(product, attributes),
80
reservedPrice: product.reservedPrice,
}
}
public async findByCategoryId(
categoryId: string,
pageOptionsDto: PageOptionsRequestDto,
productFilterRequestDto: FilterProductRequestDto,
userId?: string,
): Promise<PageWithDataMetaResponseDto<ProductResponseDto[], TotalMaxMinPropertyResponseDto>> {
const categoryIds = await this.categoryService.findChildrenIdsIncludeParentId(categoryId);
const productQueryBuilder = await this.unitOfWork.productRepository.findQueryBuilderByFilter(
pageOptionsDto,
productFilterRequestDto,
categoryIds,
userId,
);
return this.createFilteredPaginationPage(productQueryBuilder, pageOptionsDto);
}
public async findByUserPublicProfileId(
userPublicProfileId: string,
pageOptionsDto: PageOptionsRequestDto,
userId?: string,
): Promise<PageResponseDto<ProductResponseDto>> {
const productQueryBuilder = await this.unitOfWork.productRepository.findByUserPublicProfileId(
pageOptionsDto,
userPublicProfileId,
userId,
);
return this.createPaginationPage(productQueryBuilder, pageOptionsDto);
}
public async findBySellerId(sellerId: string): Promise<ProductResponseDto[]> {
const products = await this.unitOfWork.productRepository.findByOwnerId(sellerId);
return products.map((product) => Product.toDto(product));
}
public async findBySellerIdWithStatistic(sellerId: string): Promise<ProductWithStatisticResponseDto[]> {
const selectQueryBuilder = await this.unitOfWork.productRepository.findByOwnerIdWithStatistic(sellerId);
const { raw, entities } = await selectQueryBuilder.getRawAndEntities();
return entities.map((entity) => {
const foundedEntity = raw.find((rawRecord) => rawRecord.product_id === entity.id);
const statistic = foundedEntity
? { countChats: +foundedEntity.chatCount, countLikes: +foundedEntity.wishlistCount, countView:
+foundedEntity.viewCount }
: { countChats: 0, countLikes: 0, countView: 0 }
return {
product: Product.toDto(entity),
...statistic,
81
}
});
}
public async create(productCreateRequestDto: CreateProductRequestDto): Promise<ProductResponseDto> {
const work = async () => {
const { categoryId, attributeIdValues } = productCreateRequestDto;
const additionalIdValues = await this.findAdditionalIdValues(categoryId, attributeIdValues);
const attributesToProductAttributes = [...attributeIdValues, ...additionalIdValues];
const productFromDto = Product.fromCreateProductRequest(productCreateRequestDto);
productFromDto.productAttributes = await
this.productAttributeService.toProductAttributes(attributesToProductAttributes);
const product = await this.unitOfWork.productRepository.save(productFromDto);
return Product.toDto(product);
}
return await this.unitOfWork.doWork(work);
}
public async update(id: string, productRequestDto: UpdateProductRequestDto): Promise<ProductResponseDto>
{
const work = async () => {
const attributeIdValues = productRequestDto.attributeIdValues;
const productFromDto = Product.fromUpdateProductRequest(productRequestDto);
const productToUpdate = await this.unitOfWork.productRepository.findById(id);
if (!productToUpdate) {
throw new NotFoundException();
}
const productIdToUpdate = productToUpdate.id;
productToUpdate.name = productFromDto.name;
productToUpdate.price = productFromDto.price;
productToUpdate.reservedPrice = productFromDto.reservedPrice ?? null;
productToUpdate.owner = productFromDto.owner;
productToUpdate.category = productFromDto.category;
productToUpdate.description = productFromDto.description;
await this.unitOfWork.productAttributeRepository.deleteByProductId(productIdToUpdate);
await this.unitOfWork.productAttributeRepository.batchInsertByIdValues(attributeIdValues,
productIdToUpdate);
const product = await this.unitOfWork.productRepository.save(productToUpdate);
return Product.toDto(product);
}
return await this.unitOfWork.doWork(work);
}
public async updateCountView(productId: string): Promise<void> {
await this.unitOfWork.productRepository.updateCountView(productId);
}
82
public async markAsSold(productIds: string[]): Promise<void> {
const work = async () => {
await this.unitOfWork.productRepository.markProductsAsSold(productIds);
};
await this.unitOfWork.doWork(work);
}
public async delete(id: string): Promise<void> {
const work = async () => {
const product = await this.unitOfWork.productRepository.findById(id);
if (!product) {
throw new NotFoundException();
}
this.deleteImagesByProductId(product.id);
await this.unitOfWork.productCartRepository.softDeleteByProductId(id);
await this.unitOfWork.productWishlistRepository.softDeleteByProductId(id);
await this.unitOfWork.productAttributeRepository.softDeleteByProductId(id);
await this.unitOfWork.productImageRepository.softDeleteByProductId(id);
await this.unitOfWork.messageReaderRepository.softDeleteByProductId(id);
await this.unitOfWork.messageRepository.softDeleteByProductId(id);
await this.unitOfWork.userChatRepository.softDeleteByProductId(id);
await this.unitOfWork.chatRepository.softDeleteByProductId(id);
await this.unitOfWork.productRepository.softRemove(product);
}
return await this.unitOfWork.doWork(work);
}
public async deleteByIds(ids: string[]): Promise<void> {
const work = async () => {
ids.forEach((id) => this.deleteImagesByProductId(id));
await this.unitOfWork.productCartRepository.softDeleteByProductIds(ids);
await this.unitOfWork.productWishlistRepository.softDeleteByProductIds(ids);
await this.unitOfWork.productAttributeRepository.softDeleteByProductIds(ids);
await this.unitOfWork.productImageRepository.softDeleteByProductIds(ids);
await this.unitOfWork.messageReaderRepository.softDeleteByProductIds(ids);
await this.unitOfWork.messageRepository.softDeleteByProductIds(ids);
await this.unitOfWork.userChatRepository.softDeleteByProductIds(ids);
await this.unitOfWork.chatRepository.softDeleteByProductIds(ids);
await this.unitOfWork.productRepository.deleteByIds(ids);
};
return await this.unitOfWork.doWork(work);
}
@Cron(CronExpression.EVERY_12_HOURS)
public async deleteSoldProducts(): Promise<void> {
const soldProducts = await this.unitOfWork.productRepository.findSoldProducts();
const isEmpty = soldProducts.length === 0;
if (isEmpty) return;
83
const productIds = soldProducts.map((soldProduct) => soldProduct.id);
await this.deleteByIds(productIds);
}
private deleteImagesByProductId(productId: string): void {
const productDirectory = `${corePath}/${storageProductFolder}/${productId}`;
const isExistDirectory = this.fileService.isExistDirectory(productDirectory);
if (!isExistDirectory) return;
this.fileService.deleteDirectoryIncludeFile(productDirectory);
}
private async createPaginationPage(
queryBuilder: SelectQueryBuilder<Product>,
pageOptionsDto: PageOptionsRequestDto
): Promise<PageResponseDto<ProductResponseDto>> {
const { entities } = await queryBuilder.getRawAndEntities();
const totalMaxMinProperty = await this.findTotalMaxMinPropertyFromProducts(queryBuilder);
const totalItemCount = totalMaxMinProperty.total;
const productResponseDtos = entities.map((product) => Product.toDto(product));
return this.paginationService.createPage(productResponseDtos, pageOptionsDto, totalItemCount);
}
private async createFilteredPaginationPage(
queryBuilder: SelectQueryBuilder<Product>,
pageOptionsDto: PageOptionsRequestDto
): Promise<PageWithDataMetaResponseDto<ProductResponseDto[], TotalMaxMinPropertyResponseDto>> {
const { entities } = await queryBuilder.getRawAndEntities();
const totalMaxMinProperty = await this.findTotalMaxMinPropertyFromProducts(queryBuilder);
const totalItemCount = totalMaxMinProperty.total;
const productResponseDtos = entities.map((product) => Product.toDto(product));
return this.paginationService.createPageWithDataMeta(productResponseDtos, totalMaxMinProperty,
pageOptionsDto, totalItemCount);
}
private async findTotalMaxMinPropertyFromProducts(
productQueryBuilder: SelectQueryBuilder<Product>
): Promise<TotalMaxMinPropertyResponseDto> {
const totalMaxMinQueryBuilder = productQueryBuilder
.select('COUNT(DISTINCT(product.id)) as total, MAX(product.price) as "maxPrice", MIN(product.price)
as "minPrice"')
.orderBy({});
const result = await totalMaxMinQueryBuilder.getRawOne();
return {
total: +result.total,
min: +result.minPrice,
max: +result.maxPrice
}
}
84
private async findAdditionalIdValues(
categoryId: string,
existIdValues: IdValuesRequestDto[]
): Promise<IdValuesRequestDto[]> {
const attributesByCategory = await this.unitOfWork.attributeRepository.findByCategoryId(categoryId);
const additionalIdValues: IdValuesRequestDto[] = [];
attributesByCategory.forEach((attributeByCategory) => {
const foundedIdValues = existIdValues.find(
(attributeIdValue) => attributeIdValue.id === attributeByCategory.id
);
if (!foundedIdValues && attributeByCategory.type === AttributeType.CHECKBOX) {
const defaultValue = this.attributeService.findDefaultValue(attributeByCategory.type);
additionalIdValues.push({ id: attributeByCategory.id, values: [String(defaultValue)] });
}
});
return additionalIdValues;
}
}
Клас CartService:
import { Injectable } from '@nestjs/common';
import { BaseService } from '../../core/services/base.service';
import { UnitOfWorkService } from '../../core/services/unit-of-work.service';
import { NotFoundException } from '../../core/exceptions/not-found.exception';
import { Product } from '../../product/entities/product.entity';
import { InvalidCredentialsException } from '../../core/exceptions/invalid-credentials.exception';
import { ProductCart } from '../entities/product-cart.entity';
import { ProductCartRequestDto } from '../dtos/request/product-cart.request.dto';
import { ProductCartResponseDto } from '../dtos/responses/product-cart.response.dto';
import { ProductQuantityResponseDto } from '../dtos/responses/product-quantity.response.dto';
import { CartResponseDto } from '../dtos/responses/cart.response.dto';
import { PriceOfferStatus } from '../../price-offer/enums/price-offer-status.enum';
@Injectable()
export class CartService extends BaseService {
constructor(protected readonly unitOfWork: UnitOfWorkService) {
super(unitOfWork);
}
public async findByUserId(userId: string): Promise<CartResponseDto> { // TODO add decimal package
const cart = await this.unitOfWork.cartRepository.findByUserId(userId);
if (!cart) {
throw new NotFoundException();
}
const productDtos = cart.productCart.map((productCart) => {
const product = productCart.product;
const priceOffers = product.priceOffers;
const isEmpty = priceOffers.length === 0;
85
if (!isEmpty && priceOffers.at(-1).status === PriceOfferStatus.APPROVED) {
const lastApprovedOffer = priceOffers.at(-1);
product.price = lastApprovedOffer.price;
}
return Product.toDto(product)
});
const totalSum = productDtos.reduce((sum: number, product) => sum + product.price, 0);
return {
products: productDtos,
totalSum,
};
}
public async findProductQuantityByUserId(userId: string): Promise<ProductQuantityResponseDto> {
const productQuantity = await this.unitOfWork.productCartRepository.findProductQuantityByUserId(userId);
return { quantity: productQuantity };
}
public async addProduct(productCartRequestDto: ProductCartRequestDto): Promise<ProductCartResponseDto>
{
const work = async () => {
const cart = await this.unitOfWork.cartRepository.findByUserId(productCartRequestDto.userId);
if (!cart) {
throw new NotFoundException();
}
const isExistProduct = cart.productCart.some((productCart) => productCart.product.id ===
productCartRequestDto.productId);
const productOwner = await
this.unitOfWork.userRepository.findOwnerByProductId(productCartRequestDto.productId);
if (!productOwner) {
throw new NotFoundException();
}
const isSameOwner = productOwner.id === cart.user.id;
if (isExistProduct || isSameOwner) {
throw new InvalidCredentialsException();
}
const productCartFromDto = ProductCart.fromDto(productCartRequestDto, cart.id);
const productCart = await this.unitOfWork.productCartRepository.save(productCartFromDto);
return ProductCart.toDto(productCart);
}
return await this.unitOfWork.doWork(work);
}
public async deleteProduct(userId: string, productId: string): Promise<void> {
const work = async () => {
86
const productCart = await this.unitOfWork.productCartRepository.findByUserIdAndProductId(
userId,
productId,
);
if (!productCart) {
throw new NotFoundException();
}
await this.unitOfWork.productCartRepository.remove(productCart);
}
return await this.unitOfWork.doWork(work);
}
public async deleteByUserIdAndOwnerId(userId: string, ownerId: string): Promise<void> {
const work = async () => {
const productCarts = await this.unitOfWork.productCartRepository.findByUserIdAndOwnerId(
userId,
ownerId
);
const productCartIds = productCarts.map((productCart) => productCart.id);
await this.unitOfWork.productCartRepository.deleteByIds(productCartIds);
}
return await this.unitOfWork.doWork(work);
}
public async deleteByUserId(userId: string): Promise<void> {
const work = async () => {
const cart = await this.unitOfWork.cartRepository.findByUserId(userId);
if (!cart) {
throw new NotFoundException();
}
const productCartIds = cart.productCart.map((productCart) => productCart.id);
await this.unitOfWork.productCartRepository.deleteByIds(productCartIds);
}
return await this.unitOfWork.doWork(work);
}
}
Компонент Home:
import React from 'react';
import { Hero } from '../components/hero/hero';
import globalStyles from '../../global.module.css';
import styles from './home.module.css';
import { useJustArrivedQuery } from '../../core/http-services/product.http-service';
import { type JustArrivedPayload } from '../../products/types/just-arrived-payload.type';
import { useSelector } from 'react-redux';
import { selectIsAuthorized } from '../../store/user/user.selectors';
import { identifyService } from '../../core/services/identify.service';
import { JustArrived } from '../components/just-arrived/just-arrived';
87
import { HowWork } from '../components/how-work/how-work';
import { Section } from '../components/section/section';
import { Sellers } from '../components/sellers/sellers';
import { CoreValues } from '../components/core-values/core-values';
import { ReadMoreSection } from '../components/read-more-section/read-more-section';
import { useTranslation } from 'react-i18next';
import { useGetTopSellersQuery } from '../../core/http-services/user.http-service';
export const Home = (): React.ReactElement => {
const isUserAuthorized = useSelector(selectIsAuthorized);
const { t } = useTranslation();
const newProductTitle = t('newProduct.title');
const sellerTitle = t('seller.title');
const ourCoreValuesTitle = t('ourCoreValues.title');
const countObject = 4;
const justArrivedPayload: JustArrivedPayload = { quantity: countObject, isUserAuthorized };
const { data: justArrivedProducts } = useJustArrivedQuery(justArrivedPayload, {
refetchOnMountOrArgChange: true,
});
const { data: topSellers } = useGetTopSellersQuery(countObject);
const justArrived = justArrivedProducts
? <JustArrived products={justArrivedProducts} />
: <></>;
const sectionJustArrived = justArrivedProducts && justArrivedProducts.length > 0
? <Section
key={identifyService.getUniqueId()}
title={newProductTitle}
content={justArrived}
/>
: <></>;
const sellers = topSellers && topSellers.length > 0
? <Section
title={sellerTitle}
content={<Sellers sellers={topSellers} />}
/>
: <></>;
const ourCoreValues = <Section
title={ourCoreValuesTitle}
titleStyle={{ textAlign: 'center' }}
content={<CoreValues />}
/>;
const howWork = <HowWork />;
const readMoreSection = <ReadMoreSection />;
return (
<>
<Hero />
<div className={`${globalStyles.boundingContainer} ${styles.contentWrapper}`}>
<div className={`${styles.content} ${styles.topContainer}`}>
88
{sectionJustArrived}
{sellers}
<div className={styles.howWorkWrapper}>
<div className={styles.howWorkContent}>
{howWork}
</div>
</div>
</div>
<div className={`${styles.content} ${styles.coreValueContainer}`}>
{ourCoreValues}
</div>
<div className={`${styles.content} ${styles.moreCoreContainer}`}>
<div className={styles.readMoreWrapper}>
{readMoreSection}
</div>
</div>
</div>
</>
);
};
Компонент DescriptionPage модуля публікації товару:
import React from 'react';
import styles from './description-page.module.css';
import globaStyles from '../../../global.module.css';
import { AdditionalInfo } from '../../components/additonal-info/additional-info';
import {
useGetDefaultAttributeByCategoryIdQuery,
} from '../../../core/http-services/default-attribute-value.http-service';
import { SellNavigation } from '../../components/sell-navigation/sell-navigation';
import { sellProductValidationService } from '../../../core/validation/sell-product-validation.service';
import {
type inputAttribute,
type MultiSelectAttribute,
type numberInputAttribute,
type SingleSelectAttribute,
} from '../../../attributes/types/attribute.type';
import { type IdValues } from '../../../shared/types/id-values.type';
import { useDispatch, useSelector } from 'react-redux';
import {
selectCategoryId,
selectCheckboxAttributes,
selectDescription,
selectInputAttributes,
selectMultiSelectAttributes,
selectNumberInputAttributes,
selectSingleSelectAttributes,
} from '../../../store/sell-product/sell-product.selectors';
import {
addFormedInput,
addFormedNumberInput,
increaseCurrentPage,
setDescriptionPayload,
} from '../../../store/sell-product/sell-product.slice';
import { type IdValue } from '../../../shared/types/id-value.type';
import { useForm } from 'react-hook-form';
89
import { AppMultiSelect } from '../../../shared/components/app-multi-select/app-multi-select';
import { AppCheckbox } from '../../../shared/components/app-checkbox/app-checkbox';
import { useTranslation } from 'react-i18next';
import { AppSingleSelect } from '../../../shared/components/app-single-select/app-single-select';
import { AppInput } from '../../../shared/components/app-input/app-input';
import { AppTextarea } from '../../../shared/components/app-textarea/app-textarea';
import { coreValidationService } from '../../../core/validation/core-validation.service';
import { AppSpinner } from '../../../shared/components/app-spinner/app-spinner';
type FormValues = {
description: string
multiSelectorValues: IdValues[]
singleSelectValues: string[]
inputValues: string[]
numberInputValues: number[]
checkboxValues: Array<IdValue<boolean>>
};
export const DescriptionPage = (): React.ReactElement => {
const selectedCategoryId = useSelector(selectCategoryId);
const { isLoading } = useGetDefaultAttributeByCategoryIdQuery(selectedCategoryId, {
refetchOnMountOrArgChange: true,
});
const typedDescription = useSelector(selectDescription);
const multiSelectAttributes = useSelector(selectMultiSelectAttributes);
const singleSelectAttributes = useSelector(selectSingleSelectAttributes);
const checkboxAttributes = useSelector(selectCheckboxAttributes);
const inputAttributes = useSelector(selectInputAttributes);
const numberInputAttributes = useSelector(selectNumberInputAttributes);
const dispatch = useDispatch();
const { t } = useTranslation();
const defaultSelectAttributes: IdValues[] = multiSelectAttributes.map((attribute) => {
const selectedOptions = attribute.values.filter((value) => value.isSelected);
const selectedValues = selectedOptions.map((selectedValue) => attribute.id + selectedValue.value);
return { id: attribute.id, values: selectedValues };
});
const defaultCheckboxAttributes = checkboxAttributes.map((checkboxAttribute) => ({
id: checkboxAttribute.id,
value: checkboxAttribute.isSelected,
}));
const defaultSingleSelectValues = singleSelectAttributes.map((selectSingleAttribute) => {
const selectedOptions = selectSingleAttribute.values.filter((value) => value.isSelected);
const selectedOptionsIds = selectedOptions.map((selectedValue) => selectSingleAttribute.id +
selectedValue.value);
return selectedOptionsIds.length > 0
? selectedOptionsIds[0]
: '';
});
90
const defaultInputAttributes = inputAttributes.map((inputAttribute) => inputAttribute.value);
const defaultNumberInputAttributes = numberInputAttributes.map((numberInputAttribute) => {
return numberInputAttribute.meta && numberInputAttribute.meta.min > numberInputAttribute.value
? numberInputAttribute.meta.min
: numberInputAttribute.value;
});
const form = useForm<FormValues>({
defaultValues: {
description: typedDescription,
multiSelectorValues: defaultSelectAttributes,
singleSelectValues: defaultSingleSelectValues,
inputValues: defaultInputAttributes,
numberInputValues: defaultNumberInputAttributes,
checkboxValues: defaultCheckboxAttributes,
},
mode: 'onTouched',
});
const {
register,
control,
formState: { errors, isValid },
handleSubmit,
} = form;
register('description', sellProductValidationService.description);
const createMultiSelectors = (multiSelectAttributes: MultiSelectAttribute[]): React.ReactElement[] => {
return multiSelectAttributes.map((attribute, index) => {
const beginPlaceholder: string = t('sellProduct.placeholder');
const placeholder = `${beginPlaceholder} ${attribute.name}`;
const options = attribute.values.map((attributeValue) => ({
id: attribute.id + attributeValue.value,
label: attributeValue.value,
isSelected: attributeValue.isSelected,
}));
const selectedValues = attribute.values
.filter((attributeValue) => attributeValue.isSelected)
.map((attributeValue) => attribute.id + attributeValue.value);
const rules = attribute.isRequired
? sellProductValidationService.attributeSelect
: undefined;
return <AppMultiSelect
key={attribute.id}
id={attribute.id}
control={control}
controlName={`multiSelectorValues.${index}`}
label={attribute.name}
placeholder={placeholder}
defaultValue={selectedValues}
error={errors.multiSelectorValues ? errors.multiSelectorValues[index] : undefined}
91
options={options}
rules={rules}
/>;
});
};
const createSingleSelectors = (singleSelectAttributes: MultiSelectAttribute[]): React.ReactElement[] => {
return singleSelectAttributes.map((attribute, index) => {
const beginPlaceholder: string = t('sellProduct.placeholder');
const placeholder = `${beginPlaceholder} ${attribute.name}`;
const options = attribute.values.map((attributeValue) => ({
id: attribute.id + attributeValue.value,
label: attributeValue.value,
}));
const selectedValue = attribute.values.find((attributes) => attributes.isSelected);
const selectedValueId = selectedValue
? attribute.id + selectedValue.value
: '';
const rules = attribute.isRequired
? sellProductValidationService.attributeSelect
: undefined;
return <AppSingleSelect
key={attribute.id}
control={control}
controlName={`singleSelectValues.${index}`}
label={attribute.name}
placeholder={placeholder}
options={options}
defaultValue={selectedValueId}
rules={rules}
error={errors.singleSelectValues ? errors.singleSelectValues[index] : undefined}
/>;
});
};
const createCheckboxes = (checkboxAttributes: SingleSelectAttribute[]): React.ReactElement[] => {
return checkboxAttributes.map((attribute, index) => <AppCheckbox
key={attribute.id}
id={attribute.id}
control={control}
controlName={`checkboxValues.${index}`}
defaultValue={{ id: attribute.id, value: attribute.isSelected }}
label={attribute.name}
/>);
};
const createInputs = (inputAttributes: inputAttribute[]): React.ReactElement[] => {
const handleCustomChange = (id: string, value: string): void => {
const idValue: IdValue<string> = { id, value };
dispatch(addFormedInput(idValue));
};
92
return inputAttributes.map((attribute, index) => {
const beginPlaceholder: string = t('sellProduct.placeholder');
const placeholder = `${beginPlaceholder} ${attribute.name}`;
return <AppInput
key={attribute.id}
control={control}
controlName={`inputValues.${index}`}
placeholder={placeholder}
onCustomChange={(value) => { handleCustomChange(attribute.id, value); }}
label={attribute.name}
rules={attribute.isRequired ? coreValidationService.requiredField : undefined}
error={errors.inputValues ? errors.inputValues[index] : undefined}
/>;
});
};
const createNumberInputs = (numberInputAttributes: numberInputAttribute[]): React.ReactElement[] => {
const handleCustomChange = (id: string, value: string): void => {
const number = Number.parseInt(value);
const idValue: IdValue<number> = { id, value: number };
dispatch(addFormedNumberInput(idValue));
};
return numberInputAttributes.map((attribute, index) => {
const beginPlaceholder: string = t('sellProduct.placeholder');
const placeholder = `${beginPlaceholder} ${attribute.name}`;
const rules = attribute.isRequired
? coreValidationService.requiredField
: undefined;
const inputRestriction = {
min: attribute.meta ? attribute.meta.min : 0,
max: attribute.meta ? attribute.meta.max : 0,
step: 0.01,
};
return <AppInput
key={attribute.id}
control={control}
controlName={`numberInputValues.${index}`}
placeholder={placeholder}
onCustomChange={(value) => { handleCustomChange(attribute.id, value); }}
label={attribute.name}
type='number'
inputProps={attribute.meta ? inputRestriction : undefined}
rules={rules}
error={errors.numberInputValues ? errors.numberInputValues[index] : undefined}
defaultValue={defaultNumberInputAttributes[index]}
/>;
});
};
const getValuesByAttributeId = (attributeId: string, values: string[]): string[] => {
const foundedAttribute = multiSelectAttributes.find((dropdownAttribute) => dropdownAttribute.id ===
attributeId);
93
if (!foundedAttribute) return [];
const selectedOption = foundedAttribute.values.filter((value) => values.includes(foundedAttribute.id +
value.value));
return selectedOption.map((selectedOption) => selectedOption.value);
};
const dispatchDescriptionPayload = (dataForm: FormValues): void => {
const formedCheckBox: Array<IdValue<string>> = dataForm.checkboxValues.map((checkbox) => ({
id: checkbox.id,
value: String(checkbox.value),
}));
const formedValues: IdValues[] = dataForm.multiSelectorValues.map((dropdown) => {
const newValues = getValuesByAttributeId(dropdown.id, dropdown.values);
return { id: dropdown.id, values: newValues };
});
const formedSingleSelectors: Array<IdValue<string>> = dataForm.singleSelectValues.map((singleSelector)
=> {
const lengthId = 36;
const id = singleSelector.slice(0, lengthId);
const value = singleSelector.slice(lengthId);
return { id, value };
});
const descriptionPayload = {
description: dataForm.description,
formedValues,
formedCheckBox,
formedSingleSelectors,
};
dispatch(setDescriptionPayload(descriptionPayload));
};
const textAreaLabel = t('sellProduct.description.textArea.title');
const textAreaPlaceholder = t('sellProduct.description.textArea.placeholder');
const isDisableNextPageButton = !isValid || isLoading;
const getContent = (): React.ReactElement => {
return isLoading
? <div className={styles.spinnerContainer}>
<AppSpinner
style={{ color: 'var(--color-green-light-dark)' }}
size={'150px'}
isLoading={isLoading}
/>
</div>
: <> <div className={styles.inputsContainer}>
<AppTextarea
label={textAreaLabel}
placeholder={textAreaPlaceholder}
control={control}
94
controlName='description'
error={errors.description}
defaultValue={typedDescription}
/>
{createMultiSelectors(multiSelectAttributes)}
</div>
<div className={styles.inputsContainer}>
{createSingleSelectors(singleSelectAttributes)}
</div>
<div className={styles.inputsContainer}>
{createInputs(inputAttributes)}
</div>
<div className={styles.inputsContainer}>
{createNumberInputs(numberInputAttributes)}
</div>
<div className={styles.checkboxesContainer}>
{createCheckboxes(checkboxAttributes)}
</div>
</>;
};
const onSubmit = (dataForm: FormValues): void => {
dispatchDescriptionPayload(dataForm);
dispatch(increaseCurrentPage());
};
return (
<>
<form onSubmit={handleSubmit(onSubmit)} className={styles.formContainer} noValidate>
<div className={styles.container}>
<div className={`${globaStyles.sellPageIndent} ${styles.formControlContainer}`}>
{getContent()}
</div>
<div>
<AdditionalInfo />
</div>
</div>
<div className={`${styles.sellNavigationContainer} ${globaStyles.sellPageIndent}`}>
<SellNavigation
onClickBack={handleSubmit(dispatchDescriptionPayload)}
isDisableNextPageButton={isDisableNextPageButton}
/>
</div>
</form>
</>
);
};
95
ДОДАТОК В
ВЕБ-ОРІЄНТОВАНИЙ СЕРВІС ДЛЯ НАДАННЯ ПОСЛУГ З РЕАЛІЗАЦІЇ
ВТОРИННИХ ТОВАРІВ
ІНСТРУКЦІЯ КОРИСТУВАЧА
482. ЧДТУ. 42287-01 34 01
Листів 4
Розробник _____________ Ляшенко Р.І.
Черкаси – 2024
96
Веб-орієнтований сервіс був орієнтований і розроблений, на декілька різних
ролей користувачів:
− Покупець;
− Продавець;
− Адміністратор платформи.
Користувач після заповнення регістраційної форми, може бути як продавцем
так і покупцем. Після реєстрації, користувач може авторизуватися за допомогою
авторизаційної форми, щоб мати доступ до функцій веб-сервісу.
На рис. В.1 представлений процес «Реєстрація».
Рисунок В.1 – Реєстрація користувача
Після успішної реєстрації користувача, його перенаправить на головну
сторінку веб-сервісу. На головній сторінці веб-сервісу знаходиться інформація про
нові товари, які опубліковалися недавно і інформація про продаців, що
зареєструвалися на веб-сервісі. Для публікації товару, користувачу потрібно
натиснути на кнопку «Продати товар», після заповнення трьох ступінчастої форми
97
публікації товару (рис. В.2), користувач буде перенаправлений на сторінку
створеного товару.
Рисунок В.2 – Публікація товару
Користувач може переглядати товари інших продавців за допомогою вибору
товарів за категоріями. Після вибору категорії, покупець може фільтрувати і
сортувати товари, як показано на рисунку В.3.
Рисунок В.3 – Фільтрація і сортування товарів
98
Після вибору товару, покупець може зв’язатися з продавцем за допомогою
внутрішнього чату веб-сервісу, який дозволяє в реальному часі спілкуватися,
уточнювати інформацію, домовлятися за знижку товару (рис. В.4).
Рисунок В.4 – Чат між продавцем і покупцем
Після узгодження всіх деталів про товар, користувач може натиснути на товар,
настинути на кнопку «Додати в корзину» і перейти на сторінку перевірка
замовлення і натиснути на кнопку «Здійснити покупку», як показано на рисунку В.5.
Рисунок В.5 – Сторінка перевірки замовлення
99
ДОДАТОК Г
ВЕБ-ОРІЄНТОВАНИЙ СЕРВІС ДЛЯ НАДАННЯ ПОСЛУГ З РЕАЛІЗАЦІЇ
ВТОРИННИХ ТОВАРІВ
Структура бази даних
482. ЧДТУ. 42287-01 90 01
Листів 3
Розробник _____________ Ляшенко Р.І.
Черкаси – 2024
1 00
Рисунок Г.1 – Структура бази даних
Рисунок Г.2 – Таблиця товарів
1 01
Рисунок Г.3 – Таблиця користувачів
Рисунок Г.4 – Таблиця повідомлень
1 02
Рисунок Г.5 – Таблиця замовлень