Please use this identifier to cite or link to this item: https://er.chdtu.edu.ua/handle/ChSTU/9696
Title: Інформаційни сайт бібліотеки
Authors: Дяченко, Петро Васильович
Чернишенко, Ілля Васильович
Keywords: ВЕБ-ДОДАТОК;ЕЛЕКТРОННА БІБЛІОТЕКА;АВТЕНТИФІКАЦІЯ;КАТАЛОГ КНИГ;ПЕРСОНАЛЬНИЙ КАБІНЕТ;ОДНОСТОРІНКОВИЙ ДОДАТОК;ФІЛЬТРАЦІЯ;ІНТЕРФЕЙС КОРИСТУВАЧА.
Issue Date: 11-Jun-2026
Abstract: Актуальність теми полягає в тому, що стрімкий розвиток інформаційних технологій та зростання потреби у цифровому доступі до літератури вимагають створення сучасних веб-додатків для електронних бібліотек. Традиційні бібліотечні системи не завжди забезпечують зручний дистанційний доступ до каталогів, а існуючі рішення часто є комерційними та недоступними для невеликих установ. Розробка власного веб-додатку дозволяє створити гнучку та масштабовану платформу для каталогізації та перегляду книг з можливістю персоналізації користувацького досвіду. Метою роботи є розробка та програмна реалізація веб-додатку електронної бібліотеки із застосуванням сучасних веб-технологій, що забезпечує реєстрацію та автентифікацію користувачів, перегляд та пошук книг за різними критеріями, формування персонального кабінету та здійснення читання. Об'єкт дослідження: процеси каталогізації та надання доступу до електронних бібліотечних ресурсів у веб-середовищі. Предмет дослідження: методи та засоби розробки веб-додатку електронної бібліотеки з використанням фреймворків Flask та Vue.js.
URI: https://er.chdtu.edu.ua/handle/ChSTU/9696
Appears in Collections:122 Комп’ютерні науки (Комп’ютерні науки та прикладне програмування)

Files in This Item:
File Description SizeFormat 
Пояснювальна записка_Чернишенко Ілля_КН-2201_2025-2026.pdf
  Restricted Access
2.03 MBAdobe PDFView/Open Request a copy


Items in DSpace are protected by copyright, with all rights reserved, unless otherwise indicated.

Extracted text
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ  
ЧЕРКАСЬКИЙ ДЕРЖАВНИЙ ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ 
 
Факультет інформаційних технологій і систем 
 
Кафедра комп’ютерних наук та системного аналізу 
 
 
 
 
 
 
Пояснювальна записка 
до кваліфікаційної роботи 
бакалавра 
 (освітньо-кваліфікаційний рівень) 
 
на тему: «Інформаційний сайт бібліотеки» 
 
 
 
 
 
 
  Виконав: студент 4 курсу, групи КН-2201 
 
                                                        Спеціальності 122  «Комп’ютерні науки»  
                                                             (шифр і назва спеціальності) 
 
                                              Освітня програма  «Комп’ютерні науки та______ 
(назва освітньої програми) 
                                              прикладне програмування»  
 
Ілля ЧЕРНИШЕНКО 
 
Керівник                             Петро  ДЯЧЕНКО     
                                                                                 (прізвище та ініціали) 
 
Рецензент                            Руслан ІЩЕНКО       
                                                                                  (прізвище та ініціали) 
 
 
 
 
 
 
 
Черкаси 2026 року 
 
Бланк завдання на кваліфікаційну роботу бакалавра студенту 
 
Черкаський державний технологічний університет 
Факультет Інформаційних технологій і систем 
Кафедра Комп’ютерних наук та системного аналізу 
Освітньо-кваліфікаційний рівень Бакалавр 
Спеціальність 122 (F3) – Комп’ютерні науки 
Освітня програма Комп’ютерні науки та прикладне програмування 
 
 
ЗАТВЕРДЖУЮ 
Завідувач кафедри КНСА 
_______________ Юрій ТРИУС 
«____» _____________ 2026 р. 
 
 
ЗАВДАННЯ 
на кваліфікаційну роботу бакалавра студенту 
Чернишенку Іллі Васильовичу 
(прізвище, ім‘я, по батькові) 
1. Тема роботи Інформаційний сайт бібліотеки 
 
Керівник роботи     Дяченко П.В., к.т.н., доцент 
(прізвище, ім’я, по батькові, науковий ступінь, вчене звання) 
затверджені наказом університету від «12» березня 2026 р. №56/03-03  . 
2. Строк подання студентом роботи «08» червня 2026 року 
3. Вихідні дані до роботи: 
Курсова робота з конструювання прикладного програмного забезпечення. 
Звіт з виробничої практики. Звіт з переддипломної практики. 
4. Зміст пояснювальної записки (перелік питань, що їх належить розробити): 
Вступ 
4.1. Аналіз предметної області та постановка задачі. 
4.2. Проєктування архітектури та методів обробки. 
4.3. Програмна реалізація підсистеми. 
4.4. Тестування та введення в експлуатацію. 
4.5. Оцінка використаних засобів та напрями розвитку. 
Висновки. 
5. Перелік додатків (з точним зазначенням назв додатків): 
5.1. Додаток А. Специфікація 482.ЧДТУ. 62296-01 
5.2. Додаток Б. Текст програми. 
5.3. Додаток В. Інструкція користувачеві. 
5.4. Презентація у вигляді 16 слайдів.  
 
2 
 
 
6. Консультанти розділів роботи 
 
Прізвище, ініціали та Підпис, дата 
Розділ  
посада консультанта завдання видав завдання прийняв 
 
    
 
    
 
 
7. Дата видачі 
завдання  12.01.2026 р. 
 
  
 
 
КАЛЕНДАРНИЙ ПЛАН 
 
Строк 
№ з/п Назва етапів кваліфікаційної роботи бакалавра виконання Примітка 
етапів роботи 
 
1 Видача завдання на кваліфікаційну роботу 
12.01.2026  Виконано 
бакалавра. 
 
2 Аналіз літературних джерел, об’єкту та 
предмету дослідження.  до 09.02.2026 Виконано 
 
3 Написання теоретичного розділу 
кваліфікаційної роботи бакалавра.  до 24.03.2026 Виконано 
 
4 Написання аналітичного розділу 
Виконано 
кваліфікаційної роботи бакалавра.  до 05.04.2026 
 
5 Написання практичних розділів й висновків до 
 до 16.05.2026 Виконано 
кваліфікаційної роботи бакалавра. 
 
6 Передзахист кваліфікаційної роботи бакалавра 
Виконано 
на засіданні кафедри КНСА.  до 19.05.2026 
 
7 Подання роботи завідувачу кафедри КНСА.  до 20.06. 2026 Виконано 
 
8 Захист кваліфікаційної роботи бакалавра.  11.06.2026 Виконано 
 
    
 
    
 
    
 
 
 
 
Студент                                   _____________________________     Ілля ЧЕРНИШЕНКО 
                                                                            (підпис) 
 
 
Керівник роботи                     ____________________________       Петро ДЯЧЕНКО 
 
                                           (підпис) 
 
 
  
3 
РЕФЕРАТ 
Кваліфікаційна робота бакалавра містить: 135 с., 7 рис., 9 табл., 12 
використаних джерел, 3 додатка. 
Актуальність теми полягає в тому, що стрімкий розвиток інформаційних 
технологій та зростання потреби у цифровому доступі до літератури вимагають 
створення сучасних веб-додатків для електронних бібліотек. Традиційні бібліотечні 
системи не завжди забезпечують зручний дистанційний доступ до каталогів, а 
існуючі рішення часто є комерційними та недоступними для невеликих установ. 
Розробка власного веб-додатку дозволяє створити гнучку та масштабовану 
платформу для каталогізації та перегляду книг з можливістю персоналізації 
користувацького досвіду. 
Метою роботи є розробка та програмна реалізація веб-додатку електронної 
бібліотеки із застосуванням сучасних веб-технологій, що забезпечує реєстрацію та 
автентифікацію користувачів, перегляд та пошук книг за різними критеріями, 
формування персонального кабінету та здійснення читання. 
Завдання кваліфікаційної роботи бакалавра: 
− провести аналіз предметної області та огляд існуючих аналогів електронних 
бібліотек; 
− обрати та обґрунтувати інструментальні засоби розробки; 
− спроектувати базу даних та архітектуру веб-додатку; 
− реалізувати серверну частину з використанням фреймворку Flask мовою 
Python; 
− реалізувати клієнтську частину з використанням фреймворку Vue.js мовою 
JavaScript; 
− реалізувати механізми реєстрації, авторизації та управління персональним 
кабінетом користувача; 
− провести тестування розробленого додатку. 
Об'єкт дослідження: процеси каталогізації та надання доступу до 
електронних бібліотечних ресурсів у веб-середовищі. 
4 
Предмет дослідження: методи та засоби розробки веб-додатку електронної 
бібліотеки з використанням фреймворків Flask та Vue.js. 
Методи дослідження: аналіз наукової та технічної літератури (для огляду 
предметної області), об'єктно-орієнтоване проєктування (для розробки структури 
додатку), програмування мовами Python та JavaScript (для реалізації серверної та 
клієнтської частин), тестування (для перевірки працездатності розробленого 
додатку). 
Ключові слова: ВЕБ-ДОДАТОК, ЕЛЕКТРОННА БІБЛІОТЕКА, FLASK, 
VUE.JS, SQLITE, АВТЕНТИФІКАЦІЯ, КАТАЛОГ КНИГ, ПЕРСОНАЛЬНИЙ 
КАБІНЕТ, REST API, ОДНОСТОРІНКОВИЙ ДОДАТОК, ПОШУК, ФІЛЬТРАЦІЯ, 
БАЗА ДАНИХ, ІНТЕРФЕЙС КОРИСТУВАЧА. 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5 
ABSTRACT 
The qualification work of bachelor contains: 135 pages, 7 figures, 9 tables, 12 
sources used, 3 attachments. 
Actuality of theme. The relevance of the topic lies in the rapid development of 
information technologies and the growing need for digital access to literature, which 
requires the creation of modern web applications for electronic libraries. Traditional 
library systems do not always provide convenient remote access to catalogs, and existing 
solutions are often commercial and inaccessible to small institutions. Developing a custom 
web application enables the creation of a flexible and scalable platform for cataloging and 
browsing books with personalized user experience. 
The purpose of the work is to develop and implement a web application for an 
electronic library using modern web technologies, providing user registration and 
authentication, browsing and searching books by various criteria, creating a personal 
cabinet, and reading functionality. 
Objectives of qualifying work: 
− analyze the subject domain and review existing analogues of electronic libraries; 
− select and justify the development tools; 
− design the database and web application architecture; 
− implement the server-side using the Flask framework in Python; 
− implement the client-side using the Vue.js framework in JavaScript; 
− implement registration, authentication, and personal cabinet management 
mechanisms; 
− test the developed application. 
The object of research: is the processes of cataloging and providing access to 
electronic library resources in the web environment. 
The subject of research: is the methods and tools for developing a web application 
for an electronic library using the Flask and Vue.js frameworks. 
Research methods: analysis of scientific and technical literature (for domain 
overview), object-oriented design (for application structure development), programming in 
6 
Python and JavaScript (for server-side and client-side implementation), testing (for 
verifying the developed application functionality). 
Keywords: WEB APPLICATION, ELECTRONIC LIBRARY, FLASK, VUE.JS, 
SQLITE, AUTHENTICATION, BOOK CATALOG, PERSONAL CABINET, REST API, 
SINGLE PAGE APPLICATION, SEARCH, FILTERING, DATABASE, USER 
INTERFACE. 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7 
ЗМІСТ  
 
ПЕРЕЛІК УМОВНИХ ПОЗНАЧЕНЬ, СИМВОЛІВ, СКОРОЧЕНЬ І 
11 
ТЕРМІНІВ 
ВСТУП 12 
1 АНАЛІТИЧНИЙ ОГЛЯД ЛІТЕРАТУРНИХ ТА ІНШИХ ДЖЕРЕЛ 14 
 1.1 Огляд та критичний аналіз систем електронних бібліотек 14 
 1.2 Аналіз підходів до розробки веб-додатків та серверних технологій 15 
 1.3 Аналіз клієнтських технологій та інтерфейсних рішень 16 
 1.4 Аналіз систем управління базами даних та моделей автентифікації 17 
 Висновки до розділу 1 18 
2 СИСТЕМНИЙ АНАЛІЗ ТА ФОРМУВАННЯ ВИМОГ 20 
 2.1 Системний аналіз об'єкта дослідження та предметної області 20 
 2.2 Постановка та обґрунтування проблеми 23 
 2.3 Формалізація вимог до системи 26 
 2.4 Техніко-економічне обґрунтування доцільності розробки 29 
 Висновки до розділу 2 31 
3 МЕТОДИ ТА ЗАСОБИ ВИРІШЕННЯ ПРОБЛЕМИ 32 
 3.1 Вибір та обґрунтування методів вирішення проблеми 32 
 3.2 Вибір та обґрунтування засобів розробки 35 
 3.2.1 Мова програмування Python та фреймворк Flask 35 
 3.2.2 Мова програмування JavaScript та фреймворк Vue.js 36 
 3.2.3 Система управління базами даних SQLite 37 
 3.2.4 Додаткові технології. 39 
 3.3 Описання та обґрунтування алгоритмів 39 
 Висновки до розділу 3 44 
8 
4 ПРОЄКТУВАННЯ СИСТЕМИ 45 
 4.1 Об'єктно-орієнтований аналіз вимог 45 
 4.1.1 Межі системи та коло користувачів 45 
 4.1.2 Словник предметної області 46 
 4.1.3 Варіанти використання 48 
 4.1.4 Потоки даних для бізнес-процесів. 49 
 4.2 Об'єктно-орієнтоване проєктування 53 
 4.2.1 Платформо-незалежна модель 53 
 4.2.2 Платформо-залежна модель 54 
 4.2.3 Структура системи 55 
 4.3 Динамічна поведінка системи 56 
4.3.1 Послідовність взаємодій для бізнес-процесу «Реєстрація 
 
користувача» 56 
4.3.2 Послідовність взаємодій для бізнес-процесу «Пошук та перегляд 
 
каталогу» 57 
4.3.3 Послідовність взаємодій для бізнес-процесу «Управління 
 
персональним кабінетом» 58 
 4.3.4 Діаграма станів об'єкта «Користувач» 58 
 4.3.5 Діаграма станів об'єкта «Книга в кабінеті» 59 
 Висновки до розділу 4 59 
5 ПРАКТИЧНА РЕАЛІЗАЦІЯ ТА АНАЛІЗ РЕЗУЛЬТАТІВ 61 
 5.1 Опис реалізації завдання 61 
 5.1.1 Структура проекту 61 
 5.1.2 Структура бази даних 62 
 5.1.3 Склад та функції серверних модулів 64 
 5.1.4 Склад та функції клієнтських модулів 67 
9 
 5.1.5 Взаємодія компонентів системи 70 
 5.1.6 Опис користувацького інтерфейсу 73 
 5.2 Аналіз результатів 74 
 5.2.1 Функціональне тестування 74 
 5.2.2 Контрольний приклад 76 
 5.2.3 Перевірка відповідності нефункціональним вимогам 78 
 5.2.4 Теоретична та практична цінність результатів 79 
 Висновки до розділу 5 80 
ВИСНОВКИ 81 
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ 83 
Додаток А  Специфікація 482.ЧДТУ. 26 2296 85 
Додаток Б   Текст програми 87 
Додаток В   Інструкція користувачеві 129 
  
10 
ПЕРЕЛІК УМОВНИХ ПОЗНАЧЕНЬ, СИМВОЛІВ, СКОРОЧЕНЬ І 
ТЕРМІНІВ 
API – Application Programming Interface, інтерфейс програмування застосунків; 
CSS – Cascading Style Sheets, каскадні таблиці стилів для оформлення 
вебсторінок;  
Flask – Мікрофреймворк мовою Python для розробки серверної частини 
вебзастосунків;  
HTML – HyperText Markup Language, стандартна мова розмітки 
вебдокументів;  
HTTP – HyperText Transfer Protocol, протокол передавання гіпертексту; 
JSON – JavaScript Object Notation, текстовий формат обміну даними; 
JWT – JSON Web Token, формат токена для автентифікації; 
REST – Representational State Transfer, архітектурний стиль взаємодії 
компонентів вебзастосунку через HTTP;  
SPA – Single Page Application, односторінковий вебзастосунок, що забезпечує 
динамічне оновлення інтерфейсу;  
SQL – Structured Query Language, мова структурованих запитів до реляційних 
баз даних;  
SQLite – Вбудована реляційна система управління базами даних;  
UI – User Interface, користувацький інтерфейс;  
UX – User Experience, досвід взаємодії користувача з інтерфейсом;  
Vue.js – Прогресивний JavaScript-фреймворк для створення клієнтських 
інтерфейсів;  
БД – База даних;  
ПЗ – Програмне забезпечення; 
ПІБ – Прізвище, ім’я, по батькові;  
СУБД – Система управління базами даних.  
11 
ВСТУП 
Сучасний етап розвитку суспільства характеризується стрімкою цифровою 
трансформацією всіх сфер діяльності, у тому числі й культури, освіти та науки. 
Процеси інформатизації та глобалізації зумовлюють необхідність переходу від 
традиційних форм зберігання та поширення інформації до електронних, що 
забезпечують оперативний доступ до значних обсягів знань незалежно від 
географічного розташування користувача. У цьому контексті одним із пріоритетних 
напрямків є створення та розвиток електронних бібліотек - інформаційних систем, 
які забезпечують каталогізацію, зберігання та надання доступу до електронних 
версій друкованих видань через мережу Інтернет. 
Актуальність теми зумовлена тим, що традиційні бібліотечні системи, 
засновані на паперових носіях та ручному обліку, не задовольняють сучасним 
потребам користувачів у швидкому пошуку, фільтрації та перегляді каталогів. 
Існуючі комерційні рішення для електронних бібліотек часто є закритими, дорогими 
та складними в адаптації до потреб конкретного закладу. Відтак виникає потреба у 
розробці власних веб-додатків, які б поєднували зручний користувацький інтерфейс, 
можливість персоналізації та гнучку архітектуру, доступну для модифікації та 
розширення. Застосування сучасних веб-технологій, таких як фреймворк Flask для 
серверної частини та Vue.js для клієнтської, дозволяє створювати ефективні та 
масштабовані додатки з мінімальними витратами на розробку та супровід. 
Оцінка стану проблеми свідчить про те, що в Україні та за її межами 
спостерігається активний розвиток систем електронних бібліотек. Серед провідних 
зарубіжних платформ варто відзначити Google Books, Project Gutenberg, Open 
Library, які забезпечують доступ до мільйонів видань. У вітчизняній практиці 
функціонують такі системи, як «Українська бібліотека онлайн» (УБО) та електронні 
каталоги Національної бібліотеки України імені В. І. Вернадського. Проте більшість 
існуючих рішень орієнтовані передусім на зберігання повнотекстових документів і 
не завжди надають зручні інструменти для каталогізації, пошуку та формування 
персональних добірок користувачів. Наукові розробки у цій галузі представлені 
12 
працями вітчизняних та зарубіжних учених, серед яких можна виділити дослідження 
О. В. Баркова, В. В. Різуна, С. В. Кошовий з питань цифровізації бібліотечних 
процесів, а також праці М. В. Дубровіна, К. Дейла з розробки веб-додатків на основі 
сучасних фреймворків. 
Світові тенденції у розробці електронних бібліотек свідчать про перехід до 
використання архітектури односторінкових додатків (Single Page Application, SPA), 
яка забезпечує безперервну взаємодію з користувачем без перезавантаження 
сторінок. Основними трендами є: застосування RESTful API для розділення 
серверної та клієнтської частин; використання реактивних JavaScript-фреймворків 
(Vue.js, React, Angular) для побудови динамічних інтерфейсів; застосування 
легковагих серверних фреймворків (Flask, Express.js) для створення масштабованих 
API; впровадження механізмів автентифікації та авторизації для персоналізації 
користувацького досвіду. Отже, використання комбінації Flask та Vue.js є 
обґрунтованим та актуальним вибором для реалізації веб-додатку електронної 
бібліотеки, оскільки дозволяє забезпечити високу продуктивність, зручність 
розробки та можливість подальшого масштабування системи. 
Здійснено аналіз підходів до проєктування баз даних та побудови веб-
інтерфейсів, викладених у наукових працях співробітників кафедри. Роль автора в 
цих дослідженнях полягає в самостійному проєктуванні та програмній реалізації 
веб-додатку електронної бібліотеки, включаючи розробку бази даних, серверного 
API та клієнтського інтерфейсу, а також проведенні тестування розробленого 
програмного забезпечення. 
Практичне значення результатів роботи полягає в тому, що розроблений веб-
додаток є готовим до впровадження у діяльність бібліотечних установ освітнього та 
культурного профілю. Додаток забезпечує повний цикл роботи з електронним 
каталогом: від реєстрації користувача до формування персональної добірки книг та 
їх перегляду. Програмний код може бути використаний як базова основа для 
подальшого розширення функціональності, зокрема інтеграції системи резервування 
примірників, додавання повнотекстових документів та реалізації рекомендаційної 
системи на основі аналізу уподобань користувачів. 
13 
1 АНАЛІТИЧНИЙ ОГЛЯД ЛІТЕРАТУРНИХ ТА ІНШИХ ДЖЕРЕЛ 
У даному розділі розглядається сучасний стан проблеми створення веб-
додатків для електронних бібліотек, проводиться критичний аналіз існуючих 
підходів, методів та засобів вирішення аналогічних задач, оцінюються переваги та 
недоліки відомих рішень, а також обґрунтовується актуальність та доцільність  
проведення власного дослідження в обраному напрямку. 
1.1 Огляд та критичний аналіз систем електронних бібліотек 
Стрімкий розвиток інформаційних технологій та глобальна цифрова 
трансформація сприяли появі великої кількості систем електронних бібліотек, які 
забезпечують віддалений доступ до каталогів та повнотекстових документів. 
Провідними зарубіжними фірмами в цій галузі є Google (платформа Google Books), 
яка забезпечує пошук та перегляд фрагментів мільйонів книг, Internet Archive 
(проєкт Open Library), що надає безкоштовний доступ до оцифрованих видань, а 
також Project Gutenberg - найстаріша електронна бібліотека з колекцією понад 70 
тисяч безкоштовних електронних книг. 
Серед вітчизняних розробок варто виділити «Українську бібліотеку онлайн» 
(УБО), яка надає доступ до підручників та навчальних матеріалів для студентів 
вищих навчальних закладів, та електронний каталог Національної бібліотеки 
України імені В. І. Вернадського - найбільшу вітчизняну базу бібліографічних 
записів. Вченими В. В. Різуном та О. В. Барковим досліджено проблеми 
цифровізації бібліотечних процесів в Україні та обґрунтовано необхідність 
створення національної інфраструктури електронних бібліотек. Проте, як зазначає 
С. В. Кошовий, більшість вітчизняних рішень мають суттєві недоліки: застарілий 
користувацький інтерфейс, відсутність механізмів персоналізації та обмежені 
функції пошуку. 
Критичний аналіз вказаних рішень дозволяє виділити такі недоліки. По-
перше, Google Books є закритою комерційною платформою, яка не надає 
можливості розгортання у локальних мережах і обмежує доступ до повних текстів 
14 
книг авторським правом. По-друге, Open Library та Project Gutenberg орієнтовані 
передусім на англомовний контент і не забезпечують зручний інтерфейс для 
каталогізації  україномовних видань. По-третє, УБО та електронний каталог НБУВ 
мають монолітну архітектуру, що ускладнює їх модифікацію та розширення, а 
користувацький інтерфейс не відповідає сучасним стандартам UX/UI-дизайну. 
Отже, існує об'єктивна потреба у створенні нового веб-додатку, який поєднував би 
сучасний інтерфейс, гнучку архітектуру та можливість персоналізації. 
1.2 Аналіз підходів до розробки веб-додатків та серверних технологій 
Сучасна практика розробки веб-додатків базується на архітектурі клієнт-
сервер з використанням RESTful API. Цей архітектурний стиль передбачає 
розділення системи на незалежні серверну та клієнтську частини, які взаємодіють 
через стандартні HTTP-запити, що забезпечує слабкий зв'язок між компонентами та 
дозволяє масштабувати їх незалежно. 
На стороні сервера набули поширення фреймворки мовою Python, серед яких 
виділяються Flask та Django. Flask є мікрофреймворком, розробленим Арміном 
Ронахером (Armin Ronacher) у 2010 році, який надає мінімальний набір 
інструментів: маршрутизацію URL, обробку запитів та відповідей, підтримку 
шаблонів Jinja2 та сесій. До переваг Flask належать легковагість, гнучкість та 
можливість самостійного вибору компонентів проекту. Django, створений Адріаном 
Головіним (Adrian Holovaty) у 2005 році, реалізує парадигму «батарейки в 
комплекті» з вбудованими моделями користувачів, адміністративним інтерфейсом 
та ORM, що прискорює розробку складних проектів, проте суттєво обмежує свободу 
вибору архітектурних рішень. 
Критичне порівняння Flask та Django для задачі розробки веб-додатку 
електронної бібліотеки свідчить про те, що Flask є більш доцільним вибором з таких 
причин: по-перше, для проекту середнього масштабу не потрібен вбудований 
адміністративний інтерфейс Django; по-друге, мікрофреймворкова архітектура Flask 
дозволяє уникнути надмірної складності та забезпечити високий рівень контролю 
над структурою проекту; по-третє, Flask має значно менший розмір та швидший час 
15 
завантаження. Разом з тим, Django є кращим вибором для великих проектів з 
великою кількістю моделей та складною бізнес-логікою, однак для розроблюваного 
додатку електронної бібліотеки з трьома таблицями бази даних функціональність 
Django є надлишковою. 
Серед альтернативних серверних технологій варто зазначити фреймворк 
Express.js  для мови JavaScript (Node.js), який також реалізує мікрофреймворковий 
підхід і дозволяє розробляти REST API. Проте вибір Python як серверної мови 
зумовлений широко поширеною практикою використання Python в академічному 
середовищі, наявністю розгалуженої екосистеми бібліотек та високою 
читабельністю коду. 
1.3 Аналіз клієнтських технологій та інтерфейсних рішень 
На стороні клієнта домінують JavaScript-фреймворки для побудови 
односторінкових додатків (SPA), серед яких Vue.js, React та Angular. Vue.js 
створений Еваном Ю (Evan You) у 2014 році і вирізняється низьким порогом 
входження, реактивною системою зв'язування даних та компонентною 
архітектурою. React, розроблений компанією Meta (Facebook), використовує 
віртуальний DOM та декларативний підхід до опису інтерфейсу, проте вимагає 
вивчення додаткових концепцій (JSX, хуки). Angular, створений компанією Google, 
є повнофункціональним фреймворком із вбудованою системою залежностей та 
двостороннім зв'язуванням даних, однак має значно вищий поріг входження та 
більший розмір результуючого додатку. 
Порівняльний аналіз вказаних фреймворків за критеріями, релевантними для 
задачі розробки додатку електронної бібліотеки, наведено у роботі М. В. Дубровіна, 
який обґрунтував переваги Vue.js для проектів середнього масштабу: менший розмір 
результуючого додатку (близько 33 КБ у стиснутому вигляді проти 42 КБ у React та 
143 КБ у Angular), інтуїтивно зрозумілий синтаксис шаблонів та вбудована система 
маршрутизації (Vue Router). Крім того, як зазначає К. Дейл (Kyle Dawe), Vue.js 
забезпечує ефективне оновлення DOM за рахунок реактивної системи, що є 
критично важливим для інтерфейсу з фільтрацією та пошуком у реальному часі. 
16 
Для стилізації користувацького інтерфейсу обрано утилітарний CSS-
фреймворк Tailwind CSS, який забезпечує швидке створення адаптивних макетів без 
необхідності написання окремих CSS-файлів. Порівняно з Bootstrap, Tailwind CSS не 
нав'язує заздалегідь визначений дизайн і дає більшу гнучкість у кастомізації 
візуального стилю, що є важливим для створення унікального інтерфейсу 
електронної бібліотеки. 
1.4 Аналіз систем управління базами даних та моделей автентифікації 
Вибір системи управління базами даних є ключовим рішенням при 
проєктуванні веб-додатку. Для інформаційних систем середнього масштабу 
розглянуто три основні реляційні СУБД: SQLite, PostgreSQL та MySQL. SQLite є 
вбудованою СУБД, яка зберігає всю базу даних у єдиному файлі на диску, не 
потребує окремого серверного процесу та забезпечує повну підтримку стандарту 
SQL. продуктивність SQLite для операцій читання є порівнянною з PostgreSQL та 
MySQL, а для проектів з очікуваним навантаженням до кількох сотень одночасних 
підключень SQLite є оптимальним вибором. 
PostgreSQL та MySQL є більш потужними рішеннями з підтримкою 
конкурентних записів, реплікації та розширеного індексування. Проте для 
розроблюваного додатку електронної бібліотеки на початковому етапі використання 
SQLite є доцільним з огляду на простоту розгортання, відсутність необхідності 
адміністрування сервера БД та можливість міграції на PostgreSQL з мінімальними 
змінами в коді завдяки використанню стандартів SQL. 
Щодо моделі автентифікації користувачів, розглянуто два основні підходи: 
сесійну автентифікацію на основі cookie-файлів та токенову автентифікацію (JWT - 
JSON Web Token). Сесійний підхід передбачає зберігання стану авторизації на 
сервері з ідентифікацією клієнта за допомогою cookie-файлу з ідентифікатором сесії. 
Перевагами сесійного підходу є простота реалізації, можливість негайного 
відкликання сесії та відсутність необхідності передавати повний токен у кожному 
запиті. Для веб-додатку електронної бібліотеки, де клієнтська та серверна частини 
17 
обмінюються даними через REST API на одному домені, сесійний підхід є достатнім 
та більш простим у реалізації. 
Висновки до розділу 1 
Проведений аналітичний огляд літературних та інших джерел дозволяє 
зробити такі висновки. 
По-перше, аналіз існуючих систем електронних бібліотек (Google Books, Open 
Library, Project Gutenberg, УБО, електронний каталог НБУВ) виявив низку недоліків: 
закритість архітектури, відсутність механізмів персоналізації, застарілий 
користувацький інтерфейс, монолітність кодової бази та висока вартість 
комерційних рішень. Це обґрунтовує доцільність проведення власного дослідження 
з метою створення відкритого, гнучкого та зручного для користувача веб-додатку 
електронної бібліотеки. 
По-друге, критичне порівняння серверних фреймворків (Flask, Django, 
Express.js) та клієнтських фреймворків (Vue.js, React, Angular) дало змогу обрати 
оптимальну технологічну комбінацію: Flask як серверний мікрофреймворк 
(легковагість, гнучкість, адекватність масштабу проекту) та Vue.js як клієнтський 
фреймворк (низький поріг входження, реактивність, компактність). Обґрунтовано 
вибір SQLite як СУБД початкового рівня з можливістю міграції на PostgreSQL та 
сесійної моделі автентифікації як найбільш доцільної для архітектури з єдиним 
доменом. 
По-третє, результати аналізу підтверджують актуальність постановки та 
розв'язання задачі розробки веб-додатку електронної бібліотеки саме у даній 
кваліфікаційній роботі, оскільки жодне з розглянутих існуючих рішень не поєднує в 
собі відкритість коду, сучасний користувацький інтерфейс на основі SPA-
архітектури, механізми персоналізації та гнучку мікрофреймворкову структуру. 
На основі проведеного аналізу сформульовано мету роботи – розробка та 
програмна реалізація веб-додатку електронної бібліотеки із застосуванням сучасних 
веб-технологій (Flask, Vue.js, SQLite), що забезпечує реєстрацію та автентифікацію 
18 
користувачів, перегляд та пошук книг за різними критеріями, формування 
персонального кабінету та здійснення читання. 
Об'єктом дослідження визначено процеси каталогізації та надання доступу до 
електронних бібліотечних ресурсів у веб-середовищі, а предметом дослідження - 
методи та засоби розробки веб-додатку електронної бібліотеки з використанням 
фреймворків Flask та Vue.js. Для досягнення мети поставлено такі задачі:  
1) провести аналіз предметної області та огляд існуючих аналогів;  
2) обрати та обґрунтувати інструментальні засоби розробки; 
3) спроектувати базу даних та архітектуру веб-додатку; 
4) реалізувати серверну частину з використанням Flask; 
5) реалізувати клієнтську частину з використанням Vue.js; 
6) реалізувати механізми реєстрації, авторизації та управління персональним 
кабінетом; 
7) провести тестування розробленого додатку. 
 
 
 
 
 
 
 
 
 
 
 
19 
2 СИСТЕМНИЙ АНАЛІЗ ТА ФОРМУВАННЯ ВИМОГ 
У даному розділі на основі результатів аналітичного огляду, проведеного в 
розділі 1, здійснюється системний аналіз об'єкта дослідження та предметної області, 
аналізується існуючий стан справ (система «як є» – as is), формулюється постановка 
проблеми та розробляється концептуальна модель системи «як повинно бути» (to 
be), формалізуються вимоги до системи та обґрунтовується доцільність розробки з 
техніко-економічної точки зору. 
2.1 Системний аналіз об'єкта дослідження та предметної області 
Застосування системного підходу до аналізу об'єкта дослідження є 
обґрунтованим, оскільки процеси каталогізації та надання доступу до бібліотечних 
ресурсів являють собою складну багаторівневу систему з численними 
взаємозв'язками між компонентами, що функціонують у спільному інформаційному 
просторі. Системний аналіз дозволяє розглянути об'єкт як цілісну сукупність 
взаємопов'язаних елементів, визначити межі системи, її структуру, функції та 
взаємодію з зовнішнім середовищем, а також виявити протиріччя між існуючим 
станом та бажаним станом системи. 
Визначимо основні поняття системного аналізу в контексті даного 
дослідження. Система – це сукупність взаємопов'язаних компонентів (програмних 
модулів, бази даних, користувацького інтерфейсу), що функціонують як єдине ціле 
для забезпечення каталогізації та надання доступу до електронних бібліотечних 
ресурсів. Оточення системи - це сукупність зовнішніх об'єктів, що взаємодіють із 
системою: користувачі (читачі та адміністратори), зовнішні інформаційні ресурси 
(каталоги інших бібліотек, джерела метаданих книг), а також технічне середовище 
(веб-браузери, мережева інфраструктура, операційні системи). Елементи системи - 
найменші неподільні складові, що виконують визначені функції: модуль 
автентифікації, модуль каталогу книг, модуль персонального кабінету, модуль 
читання, база даних та користувацький інтерфейс. Функції системи – ролі, які 
елементи виконують у процесі досягнення мети системи: реєстрація та авторизація 
20 
користувачів, пошук та фільтрація книг, збереження книг у персональному кабінеті, 
відображення інформації та здійснення читання. 
Класифікація проблеми. Дану проблему віднесено до класу складних проблем. 
Обґрунтування: по-перше, система має множинні взаємозв'язки між компонентами 
(наприклад, модуль авторизації впливає на доступність функцій кабінету та 
читання); по-друге, існує невизначеність у вимогах користувачів щодо інтерфейсу та 
набору функцій; по-третє, система повинна функціонувати в умовах різноманітного 
зовнішнього середовища (різні браузери, пристрої, швидкість з'єднання). Проста 
проблема передбачала б єдине розв'язання без альтернатив, тоді як для даної задачі 
існує кілька альтернативних шляхів побудови системи, що вимагає системного 
порівняння та обґрунтованого вибору. 
Межі системи та зовнішнє середовище. Межами розроблюваної системи є: з 
боку користувача – веб-браузер як точка входу в систему; з боку сервера – HTTP-
інтерфейс (REST API) як межа між серверною частиною та зовнішніми сервісами. 
Зовнішнє середовище включає: кінцевих користувачів (читачі); адміністраторів 
(особи, які управляють каталогом книг); джерела даних про книги (зовнішні 
каталоги, метадані обкладинок); технічну інфраструктуру (веб-сервер, мережа 
Інтернет, база даних). 
Для аналізу структури та функціонування існуючої системи «як є» обрано 
критерій декомпозиції за функціональними підсистемами. Існуюча система надання 
бібліотечних послуг у цифровому форматі складається з таких підсистем. 
Підсистема каталогізації – відповідає за формування та ведення каталогу книг. 
В існуючій практиці (as is) каталогізація здійснюється бібліотекарями вручну або за 
допомогою спеціалізованого ПЗ, при цьому інформація про книги зберігається у 
структурованому вигляді, проте пошук та фільтрація обмежені. 
Підсистема пошуку та перегляду – відповідає за надання користувачам 
можливості пошуку книг за різними критеріями. В існуючій практиці пошук часто 
реалізується у вигляді простого текстового запиту без розширених фільтрів, 
сортування та посторінкового перегляду (пагінації), що знижує зручність 
використання. 
21 
Підсистема управління користувачами – відповідає за реєстрацію, 
авторизацію та управління профілями. В існуючій практиці багато бібліотечних 
систем або не мають функції реєстрації взагалі, або реалізують її спрощено без 
механізмів персоналізації (збереження книг, кабінет користувача). 
Підсистема персоналізації – відповідає за формування персональних добірок 
книг. В існуючій практиці (as is) ця підсистема найчастіше відсутня, що є однією з 
ключових проблем: користувачі не мають можливості зберігати обрані книги та 
формувати індивідуальні списки читання. 
Підсистема читання – відповідає за надання доступу до тексту книги. В 
існуючій практиці читання книг реалізується або через завантаження файлу, або 
через зовнішні сервіси, що ускладнює інтеграцію з каталогом. 
Аналіз потоків даних. Потік даних у системі «як є» має лінійний характер: 
дані про книги вносяться бібліотекарем у каталог – каталог відображається 
користувачеві у вигляді списку - користувач здійснює пошук вручну – результати не 
персоналізовані. Зворотний зв'язок від користувача до системи практично відсутній: 
немає можливості зберігати уподобання, формувати добірки, відстежувати історію 
переглядів. Така структура потоків даних є неефективною та не задовольняє 
сучасним вимогам до електронних бібліотек. 
Визначення основних варіантів побудови системи та вибір оптимального. На 
основі проведеного аналізу виділено три основні альтернативи. 
Варіант А – монолітна архітектура з серверним рендерингом сторінок 
(наприклад, на основі Django). Переваги: швидкий початок розробки, вбудований 
адміністративний інтерфейс. Недоліки: тісний зв'язок між серверною та клієнтською 
частинами, складність модифікації інтерфейсу, неможливість незалежного 
масштабування компонентів, низька швидкість реакції інтерфейсу при фільтрації та 
пошуку. 
Варіант Б – архітектура клієнт-сервер з REST API та односторінковим 
клієнтом (SPA) на основі Flask та Vue.js. Переваги: слабкий зв'язок між 
компонентами, висока швидкість реакції інтерфейсу, можливість незалежного 
розвитку серверної та клієнтської частин, зручна персоналізація. Недоліки: 
22 
необхідність розробки двох окремих додатків, складність налаштування крос-
доменних запитів (CORS). 
Варіант В – архітектура на основі мікросервісів з окремими сервісами для 
каталогу, користувачів та читання. Переваги: максимальна гнучкість, можливість 
незалежного розгортання та масштабування кожного сервісу. Недоліки: надмірна 
складність для проекту середнього масштабу, значні витрати на інфраструктуру, 
складність налагодження та тестування взаємодії між сервісами. 
Обґрунтування вибору. Для розроблюваного веб-додатку електронної 
бібліотеки обрано варіант Б – архітектуру клієнт-сервер з REST API та 
односторінковим клієнтом. Цей вибір обґрунтовується тим, що: по-перше, 
розділення на серверну та клієнтську частини забезпечує слабкий зв'язок між 
компонентами та можливість їх незалежної модифікації; по-друге, SPA-архітектура 
забезпечує високу швидкість реакції інтерфейсу при пошуку, фільтрації та навігації; 
по-третє, обсяг функціональності проекту не вимагає мікросервісної архітектури, а 
монолітний підхід є надто жорстким для задачі зі змінними вимогами до інтерфейсу. 
2.2 Постановка та обґрунтування проблеми 
На основі аналізу системи «як є» та вибору архітектурного варіанту 
сформульовано концептуальну модель розроблюваної системи «як повинно бути». 
Мета та призначення системи. Веб-додаток електронної бібліотеки 
створюється з метою забезпечення зручного дистанційного доступу користувачів до 
каталогу книг, персоналізації користувацького досвіду та здійснення читання книг 
через веб-браузер. Система призначена для використання в бібліотечних установах 
освітнього та культурного профілю, а також може бути розгорнута у локальній 
мережі закладу або в Інтернеті. 
Обґрунтування розробки та очікувані ефекти. Розробка нового веб-додатку 
обґрунтовується такими аргументами. По-перше, існуючі рішення не забезпечують 
достатнього рівня персоналізації та зручності користувацького інтерфейсу. По-
друге, комерційні продукти мають високу вартість ліцензування та не допускають 
модифікації під потреби конкретного закладу. По-третє, сучасні веб-технології 
23 
(SPA, REST API) дозволяють створити більш ефективне та масштабоване рішення 
порівняно з монолітними архітектурами. 
Очікувані ефекти від впровадження системи: 
− підвищення зручності доступу користувачів до бібліотечних ресурсів завдяки 
сучасному SPA-інтерфейсу; 
− скорочення часу на пошук потрібних видань завдяки розширеному пошуку, 
фільтрації та сортуванню; 
− надання можливості персоналізації (кабінет користувача, збереження книг, 
історія); 
− зниження витрат на супровід та модифікацію системи завдяки модульній 
архітектурі; 
− забезпечення можливості подальшого розширення функціональності. 
Опис вхідних та вихідних даних концептуальної моделі. 
Вхідні дані системи:  
− дані реєстрації користувача: ім'я, електронна пошта (формат email), пароль; 
− дані авторизації: електронна пошта, пароль; 
− параметри пошуку та фільтрації: рядок пошуку (назва або автор), жанр, тип 
сортування, номер сторінки; 
− команди управління кабінетом: ідентифікатор книги для додавання або 
видалення; 
− запит на детальну інформацію: ідентифікатор книги. 
Динамічні характеристики вхідних даних: асинхронне надходження через 
HTTP-запити від клієнтської частини, пікове навантаження обумовлене кількістю 
одночасних користувачів (до кількох сотень). 
Вихідні дані системи: 
− відповідь на реєстрацію: результат операції, дані створеного користувача; 
− відповідь на авторизацію: результат операції, дані користувача або 
повідомлення про помилку; 
24 
− список книг: масив об'єктів з полями (title, author, genre, year, pages, description, 
cover, language, saved), загальна кількість знайдених книг, номер поточної 
сторінки, кількість сторінок; 
− деталі книги: повний об'єкт книги з позначкою saved; 
− список книг кабінету: масив об'єктів з полями книг та датою додавання; 
− список жанрів: масив рядків; 
− повідомлення про результати операцій. 
Динамічні характеристики вихідних даних: формування у реальному часі на 
основі запитів, надання у форматі JSON. 
Опис функцій та структури системи «to be». Перелік бізнес-процесів, що 
мають бути реалізовані: 
Бізнес-процес 1 – «Реєстрація користувача»: користувач заповнює форму 
реєстрації - система валідує вхідні дані (перевіряє заповненість полів, унікальність 
email) - система створює запис у таблиці users - система ініціалізує сесію - система 
повертає підтвердження реєстрації з даними користувача. 
Бізнес-процес 2 – «Авторизація користувача»: користувач вводить email та 
пароль - система шукає запис у таблиці users за співпадінням пари email/password - 
при успіху: система ініціалізує сесію з ідентифікатором користувача - повертає дані 
користувача; при невдачі: повертає повідомлення про помилку. 
Бізнес-процес 3 – «Пошук та перегляд каталогу»: користувач вводить 
параметри пошуку, фільтрації та сортування - клієнтська частина формує GET-запит 
з параметрами - серверна частина виконує параметризований SQL-запит до БД - 
система повертає пагінований список книг із зазначенням наявності в кабінеті для 
авторизованого користувача. 
Бізнес-процес 4 – «Управління персональним кабінетом»: користувач натискає 
кнопку «Додати до кабінету»: Система перевіряє авторизацію, створює запис у 
таблиці user_books, повертає підтвердження. Видалення книг з кабінету 
здійснюється через DELETE-запит. 
25 
Бізнес-процес 5 – «Читання книги»: авторизований користувач обирає книгу з 
кабінету - система відображає сторінку читання з навігацією по сторінках книги та 
індикатором прогресу. 
Модель «to be» функціонування системи базується на архітектурі клієнт-
сервер з REST API. Серверна частина (Flask) забезпечує обробку HTTP-запитів, 
взаємодію з базою даних (SQLite) та управління сесіями. Клієнтська частина (Vue.js 
SPA) забезпечує відображення даних, взаємодію з користувачем та відправку 
запитів до сервера. Взаємодія здійснюється через RESTful API за протоколом HTTP 
з форматом JSON. Стан авторизації зберігається у серверній сесії з ідентифікатором 
через cookie-файл. База даних SQLite зберігає три таблиці: users, books та 
user_books, пов'язані між собою зовнішніми ключами з обмеженнями цілісності. 
2.3 Формалізація вимог до системи 
На основі концептуальної моделі системи «to be», розробленої в підрозділі 2.2, 
сформульовано формальні вимоги до розроблюваного веб-додатку, які поділяються 
на функціональні та нефункціональні. 
Функціональні вимоги визначають поведінку системи та її функціональні 
можливості. Нижче наведено повний перелік функціональних вимог до 
розроблюваної системи. 
Таблиця 2.1 – Функціональні вимоги до системи 
Ідентифікатор Вимога Опис 
FR-01 Реєстрація Система повинна забезпечувати 
користувача реєстрацію нового користувача шляхом 
введення імені, електронної пошти та 
пароля з валідацією вхідних даних та 
перевіркою унікальності email 
FR-02 Авторизація Система повинна забезпечувати 
користувача авторизацію зареєстрованого користувача 
шляхом введення email та пароля з 
ініціалізацією сесії 
26 
FR-03 Деавторизація Система повинна забезпечувати вихід 
користувача із системи з очищенням сесії 
FR-04 Перегляд каталогу Система повинна відображати каталог 
книг з посторінковим переглядом (9 книг 
на сторінку) 
FR-05 Пошук книг Система повинна забезпечувати пошук 
книг за назвою або автором з дебаунс-
затримкою 350 мс 
FR-06 Фільтрація за Система повинна забезпечувати 
жанром фільтрацію каталогу книг за жанром 
FR-07 Сортування Система повинна забезпечувати 
сортування книг за назвою, автором, 
роком видання (спадання/зростання), 
кількістю сторінок 
FR-08 Деталі книги Система повинна відображати детальну 
інформацію про обрану книгу (назва, 
автор, жанр, рік, сторінки, мова, опис) 
FR-09 Додавання до Система повинна дозволяти 
кабінету авторизованому користувачу додавати 
книгу до персонального кабінету з 
перевіркою на дублювання 
FR-10 Видалення з Система повинна дозволяти 
кабінету авторизованому користувачу видаляти 
книгу з персонального кабінету 
FR-11 Перегляд кабінету Система повинна відображати список книг 
у персональному кабінеті авторизованого 
користувача зі статистикою (кількість 
книг, сторінок, жанрів) 
FR-12 Читання книги Система повинна забезпечувати функцію 
читання обраної книги з навігацією по 
сторінках та індикатором прогресу 
FR-13 Отримання списку Система повинна повертати список 
27 
жанрів унікальних жанрів книг для фільтрації 
FR-14 Перевірка Система повинна перевіряти стан 
авторизації авторизації користувача при доступі до 
захищених функцій та перенаправляти 
неавторизованих користувачів на сторінку 
реєстрації 
Нефункціональні вимоги визначають якісні характеристики системи, що не 
пов'язані безпосередньо з її функціональністю, проте є необхідними для 
забезпечення прийнятного рівня експлуатаційних показників. 
Таблиця 2.2 – Нефункціональні вимоги до системи 
Ідентифікатор Категорія Вимога 
NFR-01 Продуктивність Час відповіді сервера на запит пошуку 
books не повинен перевищувати 500 мс 
при базі з 1000 книг 
NFR-02 Масштабованість Система повинна підтримувати до 100 
одночасних користувачів без суттєвого 
зниження продуктивності 
NFR-03 Безпека Паролі користувачів повинні передаватися 
виключно через POST-запити; 
автентифікація здійснюється через 
серверні сесії з секретним ключем; 
параметри SQL-запитів повинні бути 
параметризованими для запобігання 
ін'єкціям 
NFR-04 Надійність Система повинна коректно обробляти 
помилкові вхідні дані та повертати 
зрозумілі повідомлення про помилки 
NFR-05 Сумісність Клієнтська частина повинна коректно 
відображатися в останніх версіях 
браузерів Chrome, Firefox, Safari та Edge 
NFR-06 Адаптивність Користувацький інтерфейс повинен бути 
адаптивним та коректно відображатися на 
екранах від 320 пікселів (мобільні) до 1920 
28 
пікселів (десктоп) 
NFR-07 Зручність Інтерфейс повинен відповідати сучасним 
використання стандартам UX/UI-дизайну, мати 
інтуїтивно зрозумілу навігацію та 
візуальне сповіщення про результати дій 
користувача 
NFR-08 Модифікованість Архітектура системи повинна 
забезпечувати можливість додавання 
нових функцій без суттєвої переробки 
існуючого коду 
Формалізація вимог до системи є необхідною умовою для подальшого 
проєктування та реалізації, оскільки визначає однозначний перелік критеріїв, за 
якими буде проводитися верифікація та тестування розробленого програмного 
забезпечення. Функціональні вимоги FR-01 – FR-14 відповідають бізнес-процесам, 
описаним у підрозділі 2.2, а нефункціональні вимоги NFR-01 – NFR-08 задають 
якісні характеристики системи. 
2.4 Техніко-економічне обґрунтування доцільності розробки 
Техніко-економічне обґрунтування розробки веб-додатку електронної 
бібліотеки базується на зіставленні витрат на розробку з очікуваними економічними 
ефектами від впровадження системи, а також на порівнянні з альтернативними 
варіантами забезпечення бібліотечних послуг. 
Оцінка витрат на розробку. Розробка веб-додатку електронної бібліотеки 
здійснюється з використанням вільно поширюваного програмного забезпечення: 
мова Python та фреймворк Flask поширюються під ліцензією BSD, фреймворк Vue.js 
– під ліцензією MIT, СУБД SQLite – під ліцензією Public Domain, CSS-фреймворк 
Tailwind CSS – під ліцензією MIT. Таким чином, витрати на ліцензування 
програмного забезпечення практично відсутні. Основними витратами є заробітна 
плата розробника та витрати на технічну інфраструктуру. 
Для оцінки трудомісткості розробки застосовано метод експертних оцінок. 
Розподіл трудомісткості за етапами розробки наведено у таблиці 2.3. 
29 
Таблиця 2.3 – Трудомісткість розробки веб-додатку за етапами 
Етап розробки Тривалість, дні Питома вага, % 
Аналіз предметної області та формування 5 8,3 
вимог 
Проєктування бази даних та архітектури 4 6,7 
Розробка серверної частини (Flask API) 12 20,0 
Розробка клієнтської частини (Vue.js SPA) 15 25,0 
Інтеграція та налагодження 6 10,0 
Тестування та усунення дефектів 8 13,3 
Оформлення документації 10 16,7 
Разом 60 100,0 
Оцінка економічних ефектів від впровадження системи Conduct проведена з 
урахуванням таких факторів. 
Економія часу користувачів на пошук літератури. За оцінками, використання 
електронного каталогу з розширеним пошуком, фільтрацією та сортуванням 
скорочує середній час пошуку книги з 5-7 хвилин (при ручному пошуку в 
паперовому каталозі або застарілому електронному каталозі) до 15-30 секунд. За 
наявності 200 активних користувачів, кожен з яких виконує в середньому 3 
пошукові запити на день, денна економія становить: 200 × 3 × (6 хв − 0,5 хв) = 3300 
хвилин ≈ 55 людино-годин на день. 
Зниження витрат на обслуговування системи. Використання відкритого 
програмного забезпечення (Python, Flask, Vue.js, SQLite) із загальною вартістю 
ліцензій 0 грн забезпечує економію порівняно з комерційними аналогами, вартість 
ліцензування яких становить від 50 000 до 200 000 грн на рік для установ середнього 
розміру. 
Збільшення доступності бібліотечних ресурсів. Веб-додаток забезпечує 
доступність каталогу 24 години на добу, 7 днів на тиждень, що розширює аудиторію 
користувачів та підвищує ефективність використання бібліотечного фонду. 
30 
Модифікованість та масштабованість. Модульна архітектура (розділення на 
Flask API та Vue.js SPA) забезпечує можливість додавання нових функцій 
(повнотекстовий пошук, рекомендаційна система, система резервування 
примірників) без переробки існуючого коду, що суттєво знижує витрати на 
подальший розвиток системи. 
Порівняння з альтернативними варіантами. Створення власного веб-додатку 
на основі відкритих технологій істотно вигідніше порівняно з: а) придбанням 
комерційного рішення (вартість 50 000 - 200 000 грн/рік, обмежена кастомізація); б) 
розробкою монолітного додатку на основі Django (вищий час розробки на 20-30 % 
через надмірну функціональність фреймворку); в) використанням існуючих 
безкоштовних рішень (відсутність механізмів персоналізації, неможливість 
модифікації). 
Таким чином, техніко-економічне обґрунтування підтверджує доцільність 
розробки веб-додатку електронної бібліотеки на основі Flask, Vue.js та SQLite: 
нульові витрати на ліцензування, помірна трудомісткість розробки (60 людино-
днів), значний економічний ефект від економії часу користувачів та зниження 
витрат на обслуговування, а також висока модифікованість та масштабованість 
системи. 
Висновки до розділу 2 
Проведений системний аналіз об'єкта дослідження та предметної області 
дозволив отримати такі результати. 
По-перше, виявлено, що існуюча система надання бібліотечних послуг у 
цифровому форматі (as is) має істотні недоліки: лінійний потік даних без зворотного 
зв'язку від користувача, відсутність підсистеми персоналізації, обмежені можливості 
пошуку та фільтрації, а також монолітна та жорстка архітектура існуючих рішень. 
Проблему класифіковано як складну з множинними взаємозв'язками та 
невизначеністю вимог. 
31 
По-друге, на основі порівняльного аналізу трьох архітектурних альтернатив 
обґрунтовано вибір архітектури клієнт-сервер з REST API та SPA-клієнтом як 
оптимальної для задачі середнього масштабу. 
По-третє, розроблено концептуальну модель системи «to be»: визначено вхідні 
та вихідні дані, описано п'ять основних бізнес-процесів та формалізовано 
архітектуру на основі Flask + Vue.js + SQLite. 
По-четверте, сформульовано 14 функціональних та 8 нефункціональних вимог 
до системи, що складають однозначний перелік критеріїв для верифікації та 
тестування. 
По-п'яте, техніко-економічне обґрунтування підтвердило доцільність 
розробки: нульові витрати на ліцензування, помірна трудомісткість (60 людино-
днів), значний економічний ефект від економії часу користувачів та переваги над 
комерційними альтернативами. 
Отримані результати складають основу для подальшого проєктування та 
реалізації системи: вимоги FR-01 - FR-14 та NFR-01 - NFR-08 будуть покладено в 
основу об'єктно-орієнтованого аналізу (розділ 4), а обґрунтований вибір архітектури 
та технологій буде деталізовано в розділі 3. 
 
 
 
 
 
 
 
 
 
 
 
 
 
32 
3 МЕТОДИ ТА ЗАСОБИ ВИРІШЕННЯ ПРОБЛЕМИ 
У даному розділі розглядаються методи та інструментальні засоби, обрані для 
вирішення сформульованої в розділі 2 проблеми розробки веб-додатку електронної 
бібліотеки. Проводиться порівняльний аналіз альтернативних підходів, 
обґрунтовується вибір конкретних технологій та алгоритмів, а також доводиться їх 
правильність та придатність для реалізації поставлених вимог. 
3.1 Вибір та обґрунтування методів вирішення проблеми 
Специфіка розроблюваної системи. Веб-додаток електронної бібліотеки 
відноситься до класу інформаційних систем з архітектурою клієнт-сервер, що 
характеризується такими особливостями: наявністю серверної частини, яка обробляє 
бізнес-логіку та взаємодіє з базою даних; клієнтської частини у вигляді 
односторінкового додатку (SPA), яка забезпечує інтерактивний інтерфейс; 
асинхронною взаємодією між клієнтом та сервером через RESTful API; наявністю 
функцій реєстрації, авторизації та персоналізації для різних категорій користувачів; 
необхідністю обробки пошукових запитів з фільтрацією, сортуванням та пагінацією 
в реальному часі. 
Вимоги до системи, сформульовані в розділі 2, включають 14 функціональних 
вимог (FR-01 – FR-14) та 8 нефункціональних вимог (NFR-01 – NFR-08). З точки 
зору методів вирішення problem виконання функціональних вимог потребує методів 
проєктування інформаційних систем, а забезпечення нефункціональних вимог – 
методів архітектурного та алгоритмічного забезпечення. 
Для вирішення поставленої задачі застосовано комплекс методів, кожен з яких 
відповідає певному аспекту життєвого циклу створення програмного забезпечення. 
Порівняльний аналіз обраних методів з альтернативними варіантами наведено 
нижче. 
Метод об'єктно-орієнтованого аналізу та проєктування (ООАП). Даний метод 
обрано як основний підхід до моделювання предметної області та розробки 
архітектури системи. ООАП дозволяє виділити ключові сутності (класи), їхні 
33 
атрибути, методи та зв'язки, що забезпечує адекватне відображення структури 
системи. Для формалізації результатів використовується уніфікована мова 
моделювання UML. 
Порівняння з альтернативами. Альтернативними методами моделювання є 
структурний аналіз (SADT/IDEF0) та функціональне моделювання (DFD). 
Структурний аналіз орієнтований на функціональну декомпозицію системи і менш 
ефективний для систем зі складною об'єктною структурою з багатьма 
взаємозв'язками між сутностями (як у разі з таблицями users, books, user_books). 
Функціональне моделювання добре відображає потоки даних, проте не забезпечує 
належним чином моделювання станів та поведінки об'єктів. Метод ООАП є 
переважним для даної системи, оскільки вона має чітко виражену об'єктну 
структуру (користувачі, книги, зв'язки між ними) і буде реалізована об'єктно-
орієнтованими мовами програмування (Python на сервері, JavaScript на клієнті). 
Шаблон архітектурного проєктування Model-View-Controller (MVC). Даний 
шаблон обрано для організації взаємодії між компонентами системи. MVC 
передбачає розділення додатку на три складових: Model (модель даних) - відповідає 
за зберігання та обробку даних у базі; View (представлення) - відповідає за 
відображення даних користувачу; Controller (контролер) - відповідає за обробку 
запитів та координацію між Model та View. У контексті розроблюваного додатку 
серверна частина Flask реалізує функції Controller та Model, а клієнтська частина 
Vue.js - функції View. 
Порівняння з альтернативами. Альтернативними архітектурними шаблонами є 
Model-View-ViewModel (MVVM) та Model-View-Presenter (MVP). Шаблон MVVM 
частіше застосовується у десктопних додатках з двостороннім зв'язуванням даних і є 
надмірним для веб-додатку з REST API. Шаблон MVP передбачає жорсткий 
інтерфейс між Presenter та View, що збільшує кількість коду «клею». Для системи з 
REST API, де клієнт та сервер спілкуються через JSON-запити, MVC є найбільш 
природним та економічним вибором. 
Метод ітераційного прототипування. Даний метод обрано як підхід до 
управління процесом розробки. Ітераційний характер прототипування дозволяє 
34 
поступово уточнювати вимоги до системи та перевіряти працездатність окремих 
функціональних блоків на ранніх етапах. Кожна ітерація включає проєктування, 
реалізацію та тестування певної підсистеми: перша ітерація – реєстрація та 
авторизація; друга – каталог книг з пошуком та фільтрацією; третя – персональний 
кабінет; четверта – читання книг. 
Порівняння з альтернативами. Альтернативними є каскадна модель (Waterfall) 
та гнучка методологія (Agile/Scrum). Каскадна модель передбачає послідовне 
проходження етапів без можливості повернення, що є неприйнятним для проекту з 
невизначеними на початковому етапі вимогами до інтерфейсу. Agile/Scrum 
передбачає роботу в крос-функціональній команді зі спринтами тривалістю 2-4 
тижні, що є надмірним для індивідуального бакалаврського проекту. Ітераційне 
прототипування є оптимальним вибором, оскільки дозволяє поєднати планомірність 
розробки з можливістю коригування вимог після кожної ітерації. 
Метод функціонального тестування (чорний ящик). Даний метод обрано для 
верифікації відповідності розробленого додатку сформульованим функціональним 
вимогам FR-01 – FR-14. Тестування проводиться шляхом формування вхідних 
наборів даних та перевірки відповідності отриманих результатів очікуваним 
значенням без дослідження внутрішньої структури програмного забезпечення. 
Перевага даного методу полягає в тому, що він дозволяє перевірити систему з 
позиції користувача, що відповідає вимогам NFR-04 (коректна обробка помилкових 
даних). 
Порівняння з альтернативами. Альтернативними методами тестування є 
тестування «білий ящик» (з аналізом внутрішньої структури коду) та інтеграційне 
тестування. Тестування «білий ящик» є більш трудомістким і доцільним для 
верифікації окремих алгоритмів, але не системи в цілому. Інтеграційне тестування є 
складовою функціонального тестування, але самостійно не забезпечує перевірки 
відповідності бізнес-вимогам. Для верифікації відповідності вимогам FR-01 – FR-14 
функціональне тестування «чорний ящик» є найбільш доцільним вибором. 
 
35 
3.2 Вибір та обґрунтування засобів розробки 
Вибір інструментальних засобів розробки здійснювався на основі аналізу їхніх 
функціональних характеристик, доступності документації, поширеності у 
промисловій практиці та відповідності сформульованим у розділі 2 вимогам до 
системи (FR-01 - FR-14, NFR-01 - NFR-08). 
3.2.1 Мова програмування Python та фреймворк Flask. Мова Python обрана 
для реалізації серверної частини додатку. Python є високорівневою мовою 
загального призначення з динамічною типізацією, що забезпечує високу швидкість 
розробки, читабельність коду та широку екосистему бібліотек. Flask є 
мікрофреймворком для Python, розробленим Арміном Ронахером (Armin Ronacher), 
що надає маршрутизацію URL, обробку запитів та відповідей, підтримку шаблонів 
Jinja2, управління сесіями та розширень через Flask-CORS. Порівняння з 
альтернативами наведено у таблиці 3.1. 
Таблиця 3.1 – Порівняння серверних фреймворків 
Критерій Flask Django Express.js 
Тип Мікрофреймворк Повний Мікрофреймворк 
фреймворк 
Мова Python Python JavaScript 
Розмір конфігурації Мінімальний Великий Мінімальний 
Вбудований адмін- Ні Так Ні 
інтерфейс 
Вбудований ORM Ні Так (Django Ні 
ORM) 
Гнучкість структури Висока Низька Висока 
проекту (конвенція) 
Швидкість розробки Висока Середня Висока 
API 
Відповідність Висока Надмірна Висока 
36 
масштабу проекту 
Кількість залежностей Мінімальна Значна Мінімальна 
Ліцензія BSD BSD MIT 
Як видно з таблиці 3.1, Flask є оптимальним вибором для проекту середнього 
масштабу з трьома таблицями бази даних та десятьма маршрутами API. Django 
надає надлишкову функціональність (вбудований адмін-інтерфейс, ORM, 
аутентификація), яка не потрібна для розроблюваного додатку, але суттєво збільшує 
кількість залежностей та складність конфігурації. Express.js є рівноцінною 
альтернативою за архітектурою, проте вибір Python замість JavaScript на сервері 
обґрунтований більшою читабельністю коду та широкою екосистемою бібліотек для 
обробки даних. 
3.2.2 Мова програмування JavaScript та фреймворк Vue.js. Мова JavaScript 
обрана як стандартна мова для розробки інтерактивних веб-інтерфейсів. Vue.js є 
прогресивним JavaScript-фреймворком, створеним Еваном Ю (Evan You) у 2014 
році, що надає реактивну систему зв'язування даних, компонентну архітектуру та 
офіційну бібліотеку маршрутизації Vue Router. Порівняння з альтернативами 
наведено у таблиці 3.2. 
Таблиця 3.2 – Порівняння клієнтських фреймворків 
Критерій Vue.js React Angular 
Розмір (стиснутий) ~33 КБ ~42 КБ ~143 КБ 
Поріг входження Низький Середній Високий 
Синтаксис шаблонів HTML- JSX HTML-подібний + 
подібний TypeScript 
Двостороннє Вбудоване Потрібне Вбудоване 
зв'язування додаткові 
бібліотеки 
Офіційний Vue Router React Router Angular Router 
маршрутизатор 
37 
Управління станом Vue.reactive() Redux / RxJS / NgRx 
Context 
Крива навчання Полога Середня Крута 
Відповідність Висока Висока Надмірна 
масштабу проекту 
Ліцензія MIT MIT MIT 
Як видно з таблиці 3.2, Vue.js має найменший розмір результуючого додатку 
(~33 КБ), найнижчий поріг входження та вбудоване двостороннє зв'язування даних, 
що є критично важливим для інтерфейсу з пошуком у реальному часі (FR-05), 
фільтрацією (FR-06) та сортуванням (FR-07). React вимагає вивчення додаткових 
концепцій (JSX, хуки) та зовнішніх бібліотек для управління станом. Angular є 
надмірно складним для проекту середнього масштабу з круткою кривою навчання. 
Крім того, Vue.js забезпечує реактивне оновлення DOM при зміні стану додатку, що 
задовольняє вимогу NFR-01 (час відповідності інтерфейсу до 500 мс). 
3.2.3 Система управління базами даних SQLite. SQLite обрана як СУБД для 
зберігання даних додатку. SQLite є вбудованою реляційною СУБД, яка зберігає всю 
базу даних у єдиному файлі на диску, не потребує окремого серверного процесу та 
забезпечує повну підтримку стандарту SQL-92. 
Аналіз бази даних майбутньої системи. База даних розроблюваного додатку 
містить три таблиці: users (5 полів), books (9 полів) та user_books (4 поля). Зв'язки 
між таблицями: user_books.user_id - users.id (один-до-багатьох), user_books.book_id - 
books.id (один-до-багатьох). Обмеження цілісності: UNIQUE(user_id, book_id) у 
таблиці user_books, UNIQUE(email) у таблиці users. Кількісні характеристики: 
близько 20 книг у початковому наповненні, очікувана кількість користувачів – до 
1000, обсяг даних – до 10 МБ. Якісні характеристики: переважно операції читання 
(перегляд каталогу, пошук), помірна кількість операцій запису (реєстрація, 
додавання книг до кабінету), відсутність транзакцій, що вимагають розподіленого 
оброблення. Порівняння з альтернативами наведено у таблиці 3.3. 
Таблиця 3.3 – Порівняння систем управління базами даних 
38 
Критерій SQLite PostgreSQL MySQL 
Тип Вбудована Клієнт-серверна Клієнт-серверна 
Необхідність серв. Ні Так Так 
Конфігурація Нульова Значна Значна 
Підтримка SQL SQL-92 SQL:2016 SQL:2016 
Продуктивність Висока Висока Висока 
читання 
Конкурентні 1 одночасно Необмежено Необмежено 
записи 
Розмір БД До 281 ТБ Необмежено Необмежено 
Розгортання Копіювання файлу Інсталяція + Інсталяція + 
конфігурація конфігурація 
ACID-сумісність Так Так Так 
Відповідність Висока Надмірна Надмірна 
масштабу проекту 
Як видно з таблиці 3.3, SQLite є оптимальним вибором для розроблюваного 
додатку з огляду на: нульову конфігурацію розгортання (вимога NFR-08 – 
модифікованість), відсутність необхідності адміністрування сервера БД, високу 
продуктивність читання (вимога NFR-01 – час відповіді до 500 мс), достатню 
підтримку конкурентних записів для очікуваного навантаження до 100 одночасних 
користувачів (вимога NFR-02). Для масштабування системи в майбутньому 
передбачена можливість міграції на PostgreSQL з мінімальними змінами в коді 
завдяки використанню стандартів SQL. 
3.2.4 Додаткові технології. Для стилізації користувацького інтерфейсу обрано 
утилітарний CSS-фреймворк Tailwind CSS, що забезпечує швидке створення 
адаптивних макетів (вимога NFR-06) без необхідності написання окремих CSS-
файлів. Для обробки крос-доменних запитів використовується розширення Flask-
CORS, що дозволяє клієнтській частині звертатися до API з інших доменів. 
39 
Механізм автентифікації реалізовано на основі серверних сесій Flask з 
використанням cookie-файлів з секретним ключем (вимога NFR-03). Взаємодія між 
клієнтом та сервером здійснюється через RESTful API з форматом обміну JSON, що 
забезпечує сумісність з різними клієнтськими платформами (вимога NFR-05). 
3.3 Описання та обґрунтування алгоритмів 
У даному підрозділі розглядаються основні алгоритми, що забезпечують 
функціонування веб-додатку електронної бібліотеки. Для кожного алгоритму 
наводиться опис логіки роботи, аналіз існуючих рішень та доведення правильності. 
Алгоритм 1: Реєстрація користувача. Даний алгоритм реалізує функціональну 
вимогу FR-01. 
Аналіз існуючих рішень. Реєстрація користувачів є стандартною функцією 
більшості веб-додатків. Типовий підхід включає: отримання даних від користувача, 
валідацію, перевірку унікальності, хешування пароля, створення запису в БД та 
ініціалізацію сесії. У розроблюваному додатку паролі зберігаються у відкритому 
вигляді, що є допустимим для навчального проекту, проте в продукційному 
середовищі необхідно застосовувати хешування (bcrypt, argon2). 
Опис алгоритму: 
1. Отримати POST-запит на маршрут /api/auth/register з параметрами name, 
email, password. 
2. Виконати валідацію вхідних даних: 
   a) перевірити непорожність усіх полів (name, email, password); 
   b) якщо хоча б одне поле порожнє – повернути помилку «Заповніть всі 
поля».  
3. Перевірити унікальність email: 
   a) виконати запит SELECT id FROM users WHERE email = ?; 
   b) якщо запис знайдено – повернути помилку «Email вже зареєстровано». 
4. Створити запис у таблиці users: 
   a) виконати INSERT INTO users (name, email, password) VALUES (?, ?, ?); 
   b) отримати ідентифікатор створеного запису. 
40 
5. Ініціалізувати сесію: зберегти user_id у сесії. 
6. Повернути JSON-відповідь з даними користувача: {ok: true, user: {id, name, 
email}}. 
Доведення правильності. Алгоритм гарантує:  
а) неможливість реєстрації з порожніми полями – крок 2;  
б) неможливість дублювання email – крок 3 та обмеження UNIQUE у таблиці 
users;  
в) атомарність операції – створення запису та ініціалізація сесії виконуються 
послідовно, при помилці жодних побічних ефектів не виникає;  
г) параметризований запит на кроці 3 та 4 запобігає SQL-ін'єкції – вимога 
NFR-03. 
Алгоритм 2: Авторизація користувача. Даний алгоритм реалізує 
функціональну вимогу FR-02. 
Аналіз існуючих рішень. Авторизація в веб-додатках зазвичай реалізується 
через перевірку облікових даних та створення сесії (cookie-based) або токена (JWT-
based). У розроблюваному додатку обрано сесійний підхід, оскільки клієнтська та 
серверна частини працюють на одному домені та не потребують крос-доменної 
авторизації. Опис алгоритму: 
1. Отримати POST-запит на маршрут /api/auth/login з параметрами email, 
password. 
2. Виконати запит до БД: SELECT * FROM users WHERE email = ? AND 
password = ?. 
3. Якщо запис не знайдено – повернути помилку «Невірний email або пароль» 
(401). 
4. Якщо запис знайдено – ініціалізувати сесію: зберегти user_id у сесії. 
5. Повернути JSON-відповідь з даними користувача: {ok: true, user: {id, name, 
email}}. 
Доведення правильності. Алгоритм гарантує:  
а) неможливість авторизації з невірними обліковими даними - крок 2 повертає 
результат лише при точному співпадінні email та password;  
41 
б) безпека передачі пароля – використовується POST-запит, дані не 
передаються в URL (вимога NFR-03);  
в) сесія створюється лише при успішній авторизації – крок 4 виконується 
виключно після підтвердження кроку 3;  
г) узагальнене повідомлення про помилку без вказівки, саме email чи пароль 
невірний, що запобігає перебору облікових даних. 
Алгоритм 3: Пошук, фільтрація та пагінація книг. Даний алгоритм реалізує 
функціональні вимоги FR-04, FR-05, FR-06, FR-07. 
Аналіз існуючих рішень. Пошук з фільтрацією та пагінацією є стандартною 
задачею для інформаційних систем. Типовий підхід включає: побудову динамічного 
SQL-запиту з умовами WHERE для фільтрації, ORDER BY для сортування та 
LIMIT/OFFSET для пагінації. Дебаунс-затримка на клієнтській стороні зменшує 
кількість запитів до сервера при введенні тексту пошуку. Опис алгоритму: 
1. Отримати GET-запит на маршрут /api/books з параметрами: search, genre, 
sort, page. 
2. Ініціалізувати базовий запит: SELECT * FROM books WHERE 1=1. 
3. Якщо search не порожній - додати умову: AND (title LIKE ? OR author LIKE 
?), параметри: %search%, %search%. 
4. Якщо genre не порожній - додати умову: AND genre = ?, параметр: genre. 
5. Додати сортування: ORDER BY {sort_field}, де sort_field обирається з map: 
title - title ASC, author - author ASC, year_desc - year DESC, year_asc - year ASC, pages 
- pages ASC. 
6. Виконати запит без пагінації для отримання загальної кількості: total = 
len(all_rows). 
7. Обчислити зміщення: offset = (page - 1) × 9. 
8. Отримати підмножину записів: rows = all_rows[offset : offset + 9]. 
9. Якщо користувач авторизований – отримати список ідентифікаторів книг у 
кабінеті та позначити saved. 
10. Повернути JSON: {books: [...], total, page, pages: ceil(total/9)}. 
Доведення правильності. Алгоритм гарантує:  
42 
а) повноту пошуку - LIKE з символами % забезпечує пошук підрядка в назві 
або імені автора (FR-05);  
б) коректність фільтрації – додаткова умова AND genre = ? не впливає на інші 
критерії та може комбінуватися з пошуком (FR-06);  
в) коректність сортування – map обмежує допустимі значення sort_field, що 
запобігає ін'єкції в ORDER BY;  
г) коректність пагінації – загальна кількість сторінок обчислюється як 
ceil(total/9), а зміщення - як (page-1)×9, що забезпечує рівномірний розподіл по 
сторінках (FR-04);  
д) параметризовані запити на кроках 3 та 4 запобігають SQL-ін'єкції (NFR-03); 
е) продуктивність - при базі з 1000 книг операція SELECT без складних JOIN 
виконується менш ніж за 100 мс (NFR-01). 
Алгоритм 4: Управління персональним кабінетом. Даний алгоритм реалізує 
функціональні вимоги FR-09, FR-10, FR-11. Опис алгоритму додавання книги до 
кабінету: 
1. Отримати POST-запит на маршрут /api/cabinet/add/<book_id>. 
2. Перевірити авторизацію: якщо user_id відсутній у сесії – повернути помилку 
401. 
3. Спробувати виконати INSERT INTO user_books (user_id, book_id) VALUES 
(?, ?). 
4. Якщо вставка успішна – повернути {ok: true}. 
5. Якщо виникла помилка унікальності (UNIQUE constraint) – повернути 
помилку «Книга вже в кабінеті» (400). 
Опис алгоритму видалення книги з кабінету: 
1. Отримати DELETE-запит на портал /api/cabinet/remove/<book_id>. 
2. Перевірити авторизацію: якщо user_id відсутній у сесії – повернути помилку 
401. 
3. Виконати DELETE FROM user_books WHERE user_id = ? AND book_id = ?. 
4. Повернути {ok: true}. 
Опис алгоритму отримання списку книг кабінету: 
43 
1. Отримати GET-запит на портал /api/cabinet. 
2. Перевірити авторизацію: якщо user_id відсутній у сесії – повернути помилку 
401. 
3. Виконати запит: SELECT b.*, ub.added_at FROM books b JOIN user_books ub 
ON b.id = ub.book_id WHERE ub.user_id = ? ORDER BY ub.added_at DESC. 
4. Повернути JSON-масив книг з позначкою saved = true. 
Доведення правильності. Алгоритми гарантують:  
а) захист від несанкціонованого доступу – крок 2 у всіх трьох операціях 
перевіряє наявність user_id у сесії (FR-14);  
б) неможливість дублювання записів - обмеження UNIQUE(user_id, book_id) у 
таблиці user_books та обробка помилки на кроці 5 алгоритму додавання (FR-09);  
в) цілісність зв'язків – зовнішні ключі user_id - users.id та book_id - books.id 
гарантують, що в кабінеті можуть бути лише існуючі книги та існуючі користувачі; 
г) коректність списку – JOIN-запит повертає повну інформацію про книги з 
датою додавання (FR-11). 
Алгоритм 5: Дебаунс-затримка пошуку на клієнтській стороні. Даний 
алгоритм забезпечує вимоги NFR-01 (продуктивність) та FR-05 (пошук книг). 
Аналіз існуючих рішень. Дебаунс (debounce) – це стандартна техніка в 
розробці користувацьких інтерфейсів, що запобігає надмірній кількості викликів 
функції при швидкому введенні тексту. Замість відправки запиту при кожній зміні 
поля пошуку, клієнтська частина чекає паузу тривалістю 350 мс перед відправкою 
запиту. Опис алгоритму: 
1. Встановити значення debounceDelay = 350 мс. 
2. При кожній зміні поля пошуку скасувати попередній таймер (clearTimeout). 
3. Ініціалізувати новий таймер setTimeout(sendRequest, debounceDelay). 
4. Якщо протягом 350 мс не відбулося нових змін – викликати sendRequest з 
поточним значенням пошуку. 
5. Якщо протягом 350 мс відбулися нові зміни – повернутися до кроку 2. 
Доведення правильності. Алгоритм гарантує:  
44 
а) мінімальну кількість запитів до сервера – запит відправляється лише після 
зупинки введення на 350 мс;  
б) актуальність результатів – запит завжди відправляється з останнім 
значенням поля пошуку; 
в) відсутність гонкових умов – скасування попереднього таймеру на кроці 2 
запобігає відправці застарілих запитів; 
г) вимога NFR-01 – при середній кількості 5 запитів на секунду без дебаунсу, з 
дебаунсом 350 мс кількість запитів зменшується щонайменше у 5 разів. 
Висновки до розділу 3 
У даному розділі обрано та обґрунтовано методи та засоби вирішення 
проблеми розробки веб-додатку електронної бібліотеки, що дозволяє зробити такі 
висновки. 
Обрано комплекс методів (ООАП, MVC, ітераційне прототипування, 
функціональне тестування «чорний ящик»). Кожен метод обрано за результатами 
порівняння з альтернативами та має чітке обґрунтування переваг для даної задачі. 
Технологічний стек обґрунтовано за результатами порівняльного аналізу 
(таблиці 3.1, 3.2, 3.3) та відповідає всім нефункціональним вимогам NFR-01 - NFR-
08. Обрані технології є відкритими, безкоштовними та мають мінімальну кількість 
залежностей, що забезпечує вимогу NFR-08 (модифікованість). 
Описано та доведено правильність п'яти ключових алгоритмів: реєстрація, 
авторизація, пошук з фільтрацією та пагінацією, управління кабінетом та дебаунс-
затримка пошуку. Для кожного алгоритму наведено покроковий опис, аналіз 
існуючих рішень та формальне доведення правильності. 
Отримані результати складають методичну та технологічну основу для 
переходу до етапу проєктування та практичної реалізації системи в розділах 4 та 5. 
обраний комплекс методів дозволяє перейти від концептуальної моделі до 
детального проєктування на основі ООАП з використанням UML-діаграм, а обраний 
технологічний стек забезпечує достатню продуктивність, безпеку та 
модифікованість для реалізації всіх сформульованих вимог. 
45 
4 ПРОЄКТУВАННЯ СИСТЕМИ 
У даному розділі на основі вимог, сформульованих у розділі 2, та metod, 
обраних у розділі 3, проводиться об'єктно-орієнтований аналіз та проєктування 
системи з використанням мови UML. Етап аналізу визначає, «що система має 
робити», а етап проєктування - «як система це реалізує». Результатом є платформо-
незалежна (PIM) та платформо-залежна (PSM) моделі системи, а також опис 
динамічної поведінки, що складають основу для практичної реалізації в розділі 5. 
4.1 Об'єктно-орієнтований аналіз вимог 
Етап об'єктно-орієнтованого аналізу спрямований на дослідження системних 
вимог без пошуку шляхів їх технічного рішення. Метою етапу є визначення того, 
«що система має робити», а не «як вона це робитиме». 
4.1.1 Межі системи та коло користувачів. Межами системи є: з боку 
користувача – веб-браузер як точка входу; з боку сервера – REST API як інтерфейс 
взаємодії між клієнтською та серверною частинами. Коло користувачів системи 
включає два типи акторів. 
Актор «Неавторизований користувач» – відвідувач системи, який не пройшов 
процедуру реєстрації або авторизації. Такий користувач має доступ лише до 
функцій перегляду каталогу книг, пошуку, фільтрації, сортування та перегляду 
детальної інформації про книгу. Функції кабінету та читання йому недоступні. 
Актор «Авторизований користувач» – відвідувач системи, який пройшов 
реєстрацію та авторизацію. Такий користувач має повний доступ до всіх функцій 
системи, включаючи додавання та видалення книг з персонального кабінету, 
перегляд кабінету та читання книг. 
4.1.2 Словник предметної області. Для забезпечення однозначного 
тлумачення термінів, що використовуються в моделях системи, складено словник 
предметної області (таблиця 4.1). 
Таблиця 4.1 – Словник предметної області 
46 
Термін Англійський еквівалент Визначення 
Користувач User Фізична особа, яка взаємодіє з 
системою через веб-браузер 
Каталог книг Book Catalog Сукупність записів про книги, 
що доступні для перегляду та 
пошуку в системі 
Книга Book Запис у базі даних, що містить 
інформацію про літературне 
видання: назву, автора, жанр, 
рік, кількість сторінок, опис, 
зображення обкладинки, мову 
Жанр Genre Категорія літературного 
видання, що використовується 
для класифікації книг 
Персональний кабінет Personal Cabinet Сукупність книг, доданих 
авторизованим користувачем 
для подальшого перегляду та 
читання 
Реєстрація Registration Процес створення нового 
облікового запису користувача 
в системі шляхом введення 
імені, електронної пошти та 
пароля 
Авторизація Authentication Процес перевірки облікових 
даних користувача (email, 
пароль) та надання доступу до 
захищених функцій системи 
Сесія Session Серверний механізм 
збереження стану авторизації 
користувача між HTTP-
47 
запитами 
Пошук Search Процес знаходження книг у 
каталозі за назвою або 
автором 
Фільтрація Filtering Процес відбору книг у 
каталозі за визначеним 
жанром 
Сортування Sorting Процес впорядкування 
результатів пошуку за 
визначеним критерієм (назва, 
автор, рік, сторінки) 
Пагінація Pagination Процес розподілу результатів 
пошуку на сторінки зі сталою 
кількістю записів 
REST API REST API Архітектурний стиль взаємодії 
між клієнтською та серверною 
частинами системи через 
HTTP-запити з форматом 
даних JSON 
SPA Single Page Application Односторінковий веб-додаток, 
що завантажується один раз та 
динамічно оновлює вміст без 
перезавантаження сторінки 
4.1.3 Варіанти використання. На основі аналізу предметної області та вимог, 
сформульованих у розділі 2, виділено варіанти використання системи. Діаграма 
варіантів використання (Use Case Diagram) відображає взаємодію акторів із 
системою. Варіанти використання для неавторизованого користувача:  
UC-01: Перегляд каталогу книг – неавторизований користувач переглядає 
список книг з посторінковим відображенням (9 книг на сторінку).  
48 
UC-02: Пошук книг – неавторизований користувач здійснює пошук за назвою 
або автором з дебаунс-затримкою 350 мс. 
UC-03: Фільтрація книг за жанром – неавторизований користувач обирає жанр 
зі списку доступних для фільтрації каталогу. 
UC-04: Сортування книг – неавторизований користувач обирає тип сортування 
(за назвою, автором, роком видання, кількістю сторінок). 
UC-05: Перегляд деталей книги – неавторизований користувач переглядає 
повну інформацію про обрану книгу. 
UC-06: Реєстрація – неавторизований користувач створює новий обліковий 
запис шляхом введення імені, email та пароля. 
UC-07: Авторизація – неавторизований користувач входить до системи за 
допомогою email та пароля. 
Варіанти використання для авторизованого користувача (додатково до UC-01 - 
UC-05): 
UC-08: Додавання книги до кабінету – авторизований користувач додає 
обрану книгу до персонального кабінету. 
UC-09: Видалення книги з кабінету – авторизований користувач видаляє книгу 
зі свого персонального кабінету. 
UC-10: Перегляд персонального кабінету – авторизований користувач 
переглядає список збережених книг зі статистикою. 
UC-11: Читання книги – авторизований користувач здійснює читання книги з 
навігацією по сторінках та індикатором прогресу. 
UC-12: Деавторизація – авторизований користувач виходить із системи. 
Відношення між варіантами використання: 
– UC-08 «Додавання книги до кабінету» включає UC-07 «Авторизація» 
(include), оскільки додавання до кабінету вимагає попередньої авторизації; 
– UC-09 «Видалення книги з кабінету» включає UC-07 «Авторизація» 
(include); 
– UC-11 «Читання книги» включає UC-07 «Авторизація» (include); 
– UC-10 «Перегляд кабінету» включає UC-07 «Авторизація» (include). 
49 
4.1.4 Потоки даних для бізнес-процесів. Для кожного основного варіанта 
використання описано потік даних у вигляді послідовності кроків, що визначають, 
які дані надходять до системи, як обробляються та які результати повертаються. 
UC-06 «Реєстрація». Передумова: користувач не авторизований. Основний 
потік: 
1. Користувач натискає кнопку «Реєстрація» на будь-якій сторінці додатку. 
2. Система відображає модальне вікно з формою реєстрації (поля: ім'я, email, 
пароль, підтвердження пароля). 
3. Користувач заповнює форму та натискає кнопку «Зареєструватись». 
4. Клієнтська частина формує POST-запит на /api/auth/register з даними: {name, 
email, password}. 
5. Серверна частина здійснює валідацію: перевіряє непорожність полів та 
унікальність email. 
6. Якщо валідація пройшла успішно – створюється запис у таблиці users, 
ініціалізується сесія, повертається {ok: true, user: {id, name, email}}. 
7. Якщо валідація не пройшла – повертається повідомлення про помилку. 
8. Клієнтська частина відображає повідомлення про успіх або помилку. 
Альтернативний потік: якщо email вже зареєстровано – повертається помилка 
«Email вже зареєстровано».  
UC-02 «Пошук книг». Передумова: відсутня. Основний потік: 
1. Користувач вводить текст у поле пошуку на головній сторінці. 
2. Клієнтська частина ініціює debounce-таймер (350 мс). 
3. Якщо протягом 350 мс не було нових змін - формується GET-запит на 
/api/books?search=…. 
4. Серверна частина формує SQL-запит: SELECT * FROM books WHERE title 
LIKE ? OR author LIKE ?. 
5. Серверна частина повертає JSON: {books: [...], total, page, pages}. 
6. Клієнтська частина оновлює список книг на сторінці без перезавантаження. 
Альтернативний потік: якщо результатів не знайдено – відображається 
повідомлення «Нічого не знайдено». 
50 
UC-08 «Додавання книги до кабінету». Передумова: користувач 
авторизований. Основний потік: 
1. Користувач натискає кнопку «Додати до кабінету» на сторінці книги. 
2. Клієнтська частина формує POST-запит на /api/cabinet/add/{book_id}. 
3. Серверна частина перевіряє наявність user_id у сесії (авторизація). 
4. Серверна частина перевіряє відсутність дублювання (UNIQUE-обмеження). 
5. Якщо перевірки пройшли – створюється запис у таблиці user_books. 
6. Повертається {ok: true}. 
7. Клієнтська частина оновлює стан кнопки на «У вашому кабінеті». 
Альтернативний потік: якщо користувач не авторизований – клієнтська 
частина пропонує реєстрацію; якщо книга вже в кабінеті - повертається помилка 
«Книга вже в кабінеті». 
UC-10 «Перегляд персонального кабінету». Передумова: користувач 
авторизований. Основний потік: 
1. Користувач натискає кнопку «Кабінет» у навігаційній панелі. 
2. Клієнтська частина формує GET-запит на /api/cabinet. 
3. Серверна частина перевіряє авторизацію та виконує SQL-запит з JOIN між 
books та user_books. 
4. Повертається JSON-масив книг з датою додавання. 
5. Клієнтська частина відображає список книг у кабінеті зі статистикою: 
кількість книг, загальна кількість сторінок, кількість жанрів. 
UC-11 «Читання книги». Передумова: користувач авторизований, книга 
додана до кабінету. Основний потік: 
1. Користувач натискає кнопку «Читати» у кабінеті або на сторінці книги. 
2. Клієнтська частина перевіряє, що книга знаходиться в кабінеті (saved = 
true). 
3. Клієнтська частина переходить на сторінку читання та відображає текст 
книги з навігацією по сторінках. 
4. Індикатор прогресу відображає поточну позицію читання. 
51 
 5. Користувач може переходити між сторінками книги за допомогою кнопок 
«Попередня» / «Наступна» або вибору номеру сторінки. 
Таблиця 4.2 – Функціональні та нефункціональні вимоги 
Ідентифікатор Тип Вимога Відповідний Use Case 
FR-01 Функціональна Реєстрація користувача з UC-06 
валідацією 
FR-02 Функціональна Авторизація через UC-07 
механізм сесій 
FR-03 Функціональна Деавторизація з UC-12 
очищенням сесії 
FR-04 Функціональна Перегляд каталогу з UC-01 
пагінацією (9 на сторінку) 
FR-05 Функціональна Пошук книг за назвою або UC-02 
автором 
FR-06 Функціональна Фільтрація книг за UC-03 
жанром 
FR-07 Функціональна Сортування за назвою, UC-04 
автором, роком, 
сторінками 
FR-08 Функціональна Детальна інформація про UC-05 
книгу 
FR-09 Функціональна Додавання книги до UC-08 
кабінету 
FR-10 Функціональна Видалення книги з UC-09 
кабінету 
FR-11 Функціональна Перегір кабінету зі UC-10 
статистикою 
FR-12 Функціональна Читання книги з UC-11 
навігацією 
FR-13 Функціональна Отримання списку жанрів UC-03 
FR-14 Функціональна Перевірка авторизації для UC-07, UC-08 
захищених функцій 
NFR-01 Нефункціональна Час відповіді сервера не - 
більше 500 мс 
NFR-02 Нефункціональна Підтримка до 100 - 
одночасних користувачів 
NFR-03 Нефункціональна Безпека: параметризовані - 
запити, сесії, POST для 
паролів 
NFR-04 Нефункціональна Обробка помилкових - 
вхідних даних 
NFR-05 Нефункціональна Сумісність з Chrome, - 
52 
Firefox, Safari, Edge 
NFR-06 Нефункціональна Адаптивний інтерфейс - 
(320-1920 px) 
NFR-07 Нефункціональна UX/UI-стандарти, - 
сповіщення про 
результати дій 
NFR-08 Нефункціональна Модульність архітектури, - 
можливість розширення 
4.2 Об'єктно-орієнтоване проєктування 
На етапі проєктування розроблено концептуальне рішення, що забезпечує 
виконання всіх вимог, сформульованих у підрозділі 4.1. Проєктування здійснено на 
двох рівнях деталізації: платформо-незалежна модель (PIM) та платформо-залежна 
модель (PSM). 
4.2.1 Платформо-незалежна модель. PIM-модель описує склад, структуру та 
поведінку системи без прив'язки до конкретних мов програмування, операційних 
систем чи технологічних платформ. Модель містить такі основні класи з їхніми 
атрибутами, методами та зв'язками. 
Клас User (Користувач). Атрибути: id (унікальний ідентифікатор), name (ім'я), 
email (електронна пошта), password (пароль), created_at (дата реєстрації). Методи: 
register() – реєстрація нового користувача; login() – авторизація користувача; logout() 
– деавторизація; getProfile() – отримання даних профілю. 
Клас Book (Книга). Атрибути: id (унікальний ідентифікатор), title (назва), 
author (автор), genre (жанр), year (рік видання), pages (кількість сторінок), description 
(опис), cover (зображення обкладинки), language (мова). Методи: getDetails() – 
отримання детальної інформації; search(query) – пошук за назвою або автором; 
filter(genre) – фільтрація за жанром; sort(criterion) – сортування за критерієм. 
Клас UserBook (Зв'язок користувач-книга). Атрибути: id (унікальний 
ідентифікатор), user_id (ідентифікатор користувача – зовнішній ключ), book_id 
(ідентифікатор книги – зовнішній ключ), added_at (дата додавання). Методи: add() – 
додавання книги до кабінету; remove() – видалення книги з кабінету; getCabinet() – 
отримання списку книг кабінету. 
53 
Клас Catalog (Каталог). Методи: getBooks(search, genre, sort, page) – отримання 
пагінованого списку книг з фільтрацією та сортуванням; getGenres() – отримання 
списку унікальних жанрів. 
Клас Session (Сесія). Атрибути: user_id (ідентифікатор авторизованого 
користувача). Методи: create(user_id) – створення сесії; validate() – перевірка 
валідності сесії; destroy() – знищення сесії. 
Зв'язки між класами PIM-моделі: 
 User-UserBook: зв'язок «один до багатьох» (один користувач може мати багато 
записів у кабінеті); 
 Book-UserBook: зв'язок «один до багатьох» (одна книга може бути додана 
багатьма користувачами); 
 User-Session: зв'язок «один до одного» (один користувач має одну активну 
сесію); 
 Catalog-Book: зв'язок «один до багатьох» (каталог містить багато книг); 
 UserBook-User: зв'язок «багато до одного» (залежність від користувача); 
 UserBook-Book: зв'язок «багато до одного» (залежність від книги). 
4.2.2 Платформо-залежна модель. PSM-модель деталізує PIM з урахуванням 
обраних у розділі 3 технологій реалізації: Python (Flask) для серверної частини, 
JavaScript (Vue.js) для клієнтської частини, SQLite як СУБД. 
Відображення класів PIM у компоненти PSM: 
 Клас User – таблиця users у СУБД SQLite та маршрут Flask: /api/auth/register, 
/api/auth/login, /api/auth/logout, /api/auth/me; 
 Клас Book – таблиця books у СУБД SQLite та маршрут Flask: /api/books, 
/api/books/genres, /api/books/<id>; 
 Клас UserBook – таблиця user_books у СУБД SQLite та маршрут Flask: 
/api/cabinet, /api/cabinet/add/<id>, /api/cabinet/remove/<id>; 
 Клас Catalog – тип звернення до /api/books з параметрами пошуку, фільтрації 
та пагінації; 
54 
 Клас Session – механізм сесій Flask (серверні cookie-файли з секретним 
ключем). 
 Клієнтська частина PSM-моделі реалізується у вигляді SPA на основі Vue.js та 
складається з таких компонентів: 
 Navbar – навігаційна панель з кнопками авторизації та переходу в кабінет; 
 AuthModal – модальне вікно реєстрації та авторизації; 
 BookCard – картка книги для відображення у каталозі; 
 FilterSidebar – бічна панель з фільтрами за жанром та сортуванням; 
 HomePage – головна сторінка з каталогом книг, пошуком та фільтрацією; 
 BookDetailPage – сторінка детальної інформації про книгу; 
 CabinetPage – сторінка персонального кабінету; 
 ReadingPage – сторінка читання книги; 
 Store – реактивний об'єкт стану додатку (дані користувача, методи API, 
сповіщення). 
4.2.3 Структура системи. Логічна структура системи поділяється на три 
підсистеми: підсистема автентифікації (модулі register, login, logout, me), підсистема 
каталогу (модулі books, genres, book_detail) та підсистема кабінету (модулі cabinet, 
add_book, remove_book). 
Фізична структура системи реалізується у вигляді двох основних компонентів 
розгортання: 
 серверний компонент (app.py, database.py, seed.py) – розгортається на веб-
сервері з підтримкою Python і забезпечує обробку HTTP-запитів, взаємодію з 
БД та управління сесіями; 
 клієнтський компонент (frontend/) – розгортається на тому ж веб-сервері як 
статичні файли (index.html, app.js, store.js, компоненти, сторінки, стилі) і 
забезпечує відображення інтерфейсу користувача. 
База даних (library.db) зберігається у вигляді одного файлу на сервері і містить 
три таблиці: users, books, user_books. Структура файлів проекту: 
55 
 app.py – головний модуль серверної частини (маршрутизація, обробка 
запитів); 
 database.py – модуль ініціалізації та підключення до БД; 
 seed.py – модуль початкового заповнення БД тестовими даними; 
 frontend/index.html – головна HTML-сторінка; 
 frontend/app.js – конфігурація маршрутизації та монтування додатку; 
 frontend/store.js – реактивний стан додатку та методи API; 
 frontend/components/ – повторно використовувані Vue-компоненти (Navbar, 
AuthModal, BookCard, FilterSidebar); 
 frontend/pages/ – сторінки додатку (Home, BookDetail, Cabinet, Reading); 
 frontend/assets/style.css – користувацькі стилі. 
4.3 Динамічна поведінка системи 
У даному підрозділі описується динамічна поведінка системи – життєві цикли 
об'єктів та процеси їхньої взаємодії для забезпечення необхідної функціональності. 
Для моделювання динамічної поведінки використовуються послідовності взаємодій 
(interaction sequences) та діаграми станів (state diagrams) мовою UML. 
4.3.1 Послідовність взаємодій для бізнес-процесу «Реєстрація 
користувача». 
1. Користувач натискає кнопку «Реєстрація» – клієнтська частина (AuthModal) 
відображає модальне вікно з формою. 
2. Користувач заповнює поля (name, email, password, confirm) та натискає 
кнопку «Зареєструватись». 
3. AuthModal формує об'єкт {name, email, password} та викликає метод 
store.register(). 
4. Store ініціює POST-запит на /api/auth/register з тілом запиту у форматі JSON. 
5. Серверний маршрут /api/auth/register виконує валідацію: перевіряє 
непорожність полів. 
56 
6. Якщо поля заповнені - серверний маршрут виконує SELECT id FROM users 
WHERE email = ? для перевірки унікальності. 
7. Якщо email унікальний – виконує INSERT INTO users (name, email, 
password) VALUES (?, ?, ?). 
8. Отримує ідентифікатор створеного запису, зберігає user_id у сесії, повертає 
JSON {ok: true, user: {id, name, email}}. 
9. Store оновлює реактивний стан: store.user = {id, name, email}, store.showAuth 
= false. 
10. AuthModal закривається, Navbar оновлюється: відображається ім'я 
користувача та кнопка «Кабінет». 
11. Відображається сповіщення: «Реєстрація успішна! Ласкаво просимо, 
[name]». 
4.3.2 Послідовність взаємодій для бізнес-процесу «Пошук та перегляд 
каталогу». 
1. Користувач відкриває головну сторінку – HomePage викликає метод 
store.getBooks() та store.getGenres(). 
2. Store ініціює GET-запит на /api/books?page=1 та /api/books/genres. 
3. Серверний маршрут /api/books виконує SELECT * FROM books (з 
параметрами фільтрації та пагінації). 
4. Сервер повертає JSON {books: [...], total: 20, page: 1, pages: 3}. 
5. Серверний маршрут /api/books/genres виконує SELECT DISTINCT genre 
FROM books ORDER BY genre. 
6. Сервер повертає JSON-масив жанрів. 
7. HomePage відображає сітку карток книг (BookCard) та бічну панель фільтрів 
(FilterSidebar). 
8. Користувач вводить текст у поле пошуку - HomePage ініціює debounce-
таймер (350 мс). 
9. Після закінчення таймеру HomePage формує GET-запит на 
/api/books?search=…&page=1. 
57 
10. Користувач обирає жанр у FilterSidebar - HomePage формує GET-запит на 
/api/books?genre=…&page=1. 
11. Користувач обирає тип сортування - HomePage формує GET-запит на 
/api/books?sort=…&page=1. 
12. Сервер обробляє кожен запит, повертає оновлений список книг, HomePage 
перемальовує сітку без перезавантаження сторінки. 
4.3.3 Послідовність взаємодій для бізнес-процесу «Управління 
персональним кабінетом». 
1. Авторизований користувач натискає кнопку «Додати до кабінету» на 
сторінці BookDetailPage. 
2. Store перевіряє наявність авторизації: якщо store.user дорівнює null – 
відображає AuthModal з пропозицією зареєструватися. 
3. Якщо користувач авторизований – Store ініціює POST-запит на 
/api/cabinet/add/<book_id>. 
4. Серверний маршрут перевіряє наявність user_id у сесії. 
5. Сервер виконує INSERT INTO user_books (user_id, book_id) VALUES (?, ?). 
6. Якщо вставка успішна – повертається {ok: true}. 
7. Якщо порушено UNIQUE-обмеження – повертається помилка «Книга вже в 
кабінеті». 
8. Store оновлює стан: book.saved = true, відображає сповіщення «Книгу 
додано до кабінету». 
9. При переході в CabinetPage – Store ініціює GET-запит на /api/cabinet. 
10. Сервер виконує SELECT b.*, ub.added_at FROM books b JOIN user_books 
ub ON b.id = ub.book_id WHERE ub.user_id = ? ORDER BY ub.added_at DESC. 
11. Сервер повертає JSON-масив книг зі позначкою saved = true. 
12. CabinetPage відображає список книг, статистику (кількість книг, сторінок, 
жанрів) та кнопки «Читати» та «Видалити». 
13. При натисканні «Видалити» – Store ініціює DELETE-запит на 
/api/cabinet/remove/<book_id>, сервер видаляє запис, CabinetPage оновлює список. 
58 
4.3.4 Діаграма станів об'єкта «Користувач». Життєвий цикл користувача в 
системі описується такими станами та переходами між ними: 
1. Стан S1 «Неавторизований» – початковий стан, користувач не має доступу до 
захищених функцій; дозволені: UC-01 (перегляд каталогу), UC-02 (пошук), 
UC-03 (фільтрація), UC-04 (сортування), UC-05 (деталі книги), UC-06 
(реєстрація), UC-07 (авторизація). 
2. Перехід S1 – S2: подія «Успішна реєстрація» (UC-06) або «Успішна 
авторизація» (UC-07). 
3. Стан S2 «Авторизований» – користувач має повний доступ до всіх функцій; 
додатково дозволені: UC-08 (додавання книги), UC-09 (видалення книги), UC-
10 (кабінет), UC-11 (читання), UC-12 (деавторизація). 
4. Перехід S2 – S1: подія «Деавторизація» (UC-12) – сесія знищується, store.user 
= null. 
5. Перехід S1 – S2: подія «Помилка авторизації» – користувач залишається в 
стані S1, відображається повідомлення про помилку. 
4.3.5 Діаграма станів об'єкта «Книга в кабінеті». Життєвий цикл книги в 
контексті кабінету користувача описується такими станами: 
1. Стан S1 «Не в кабінеті» – книга не додана до персонального кабінету; кнопка 
«Додати до кабінету» активна. 
2. Перехід S1 – S2: подія «Додавання книги до кабінету» (UC-08) – створюється 
запис у таблиці user_books. 
3. Стан S2 «В кабінеті» – книга додана до персонального кабінету; кнопка «У 
вашому кабінеті» неактивна для повторного додавання; кнопка «Читати» 
активна. 
4. Перехід S2 – S1: подія «Видалення книги з кабінету» (UC-09) – запис із 
таблиці user_books видаляється. 
5. Перехід між станами можливий лише для авторизованого користувача (вимога 
FR-14). 
 
59 
Висновки до розділу 4 
У даному розділі здійснено об'єктно-орієнтований аналіз та проєктування веб-
додатку електронної бібліотеки з використанням мови UML, що дозволило 
отримати такі результати. 
По-перше, на етапі аналізу визначено межі системи та коло користувачів 
(неавторизований та авторизований), складено словник предметної області з 14 
термінами (таблиця 4.1), виділено 12 варіантів використання (UC-01 - UC-12) з 
описом потоків даних та відношень між ними, а також сформульовано технічне 
завдання у вигляді 14 функціональних та 8 нефункціональних вимог (таблиця 4.2). 
По-друге, на етапі проєктування розроблено платформо-незалежну модель 
(PIM), що містить п'ять класів (User, Book, UserBook, Catalog, Session) з атрибутами, 
методами та зв'язками, та платформо-залежну модель (PSM), що деталізує PIM з 
урахуванням обраних технологій (Flask маршрути, таблиці SQLite, Vue.js 
компоненти, стан додатку Store). 
По-третє, описано динамічну поведінку системи через послідовності 
взаємодій для трьох основних бізнес-процесів (реєстрація, пошук каталогу, 
управління кабінетом) та діаграми станів для двох ключових об'єктів (користувач та 
книга в кабінеті). Це забезпечує повне розуміння того, як система функціонує в часі 
та як об'єкти реагують на зовнішні події. 
Отримані результати проєктування створюють повну основу для переходу до 
етапу практичної реалізації: PIM-модель визначає, «що система має робити», PSM-
модель - «як система це реалізує» з урахуванням обраних технологій (Flask, Vue.js, 
SQLite), а опис динамічної поведінки - «в якій послідовності об'єкти взаємодіють». 
Ці моделі будуть безпосередньо використані для написання програмного коду в 
розділі 5. 
 
 
 
 
60 
5 ПРАКТИЧНА РЕАЛІЗАЦІЯ ТА АНАЛІЗ РЕЗУЛЬТАТІВ 
У даному розділі описується практична реалізація веб-додатку електронної 
бібліотеки, розробленого на основі моделей та алгоритмів, обґрунтованих у розділах 
3 та 4. Наводиться детальний опис структури бази даних, складу та функцій 
програмних модулів, взаємодії компонентів системи та користувацького інтерфейсу, 
а також приводяться результати тестування, що підтверджують працездатність 
розробленого додатку та відповідність сформульованим вимогам. 
5.1 Опис реалізації завдання 
5.1.1 Структура проекту. Розроблений веб-додаток має модульну 
архітектуру, що відповідає PSM-моделі, описаній у підрозділі 4.2. Структура файлів 
проекту наведена нижче. 
Таблиця 5.1 – Структура файлів проекту 
Файл / каталог Призначення Рядків коду 
app.py Головний модуль серверної частини: 227 
маршрутизація, обробка запитів, ініціалізація 
database.py Модуль роботи з БД: підключення, ініціалізація 52 
таблиць 
seed.py Модуль початкового заповнення БД тестовими 229 
даними (20 книг) 
frontend/index.html Головна HTML-сторінка: підключення Vue.js, Vue 66 
Router, Tailwind CSS 
frontend/app.js Конфігурація маршрутизації та монтування Vue- 63 
додатку 
frontend/store.js Реактивний стан додатку: методи API, авторизація, 113 
сповіщення 
frontend/components/Navbar.j Компонент навігаційної панелі ~80 
s 
frontend/components/AuthMo Компонент модального вікна реєстрації/авторизації ~99 
dal.js 
frontend/components/BookCar Компонент картки книги для каталогу ~50 
d.js 
frontend/components/FilterSid Компонент бічної панелі фільтрів ~60 
ebar.js 
frontend/pages/Home.js Сторінка головної сторінки з каталогом, пошуком, ~168 
фільтрацією 
61 
frontend/pages/BookDetail.js Сторінка детальної інформації про книгу ~153 
frontend/pages/Cabinet.js Сторінка персонального кабінету користувача ~137 
frontend/pages/Reading.js Сторінка читання книги з навігацією ~115 
frontend/assets/style.css Користувацькі стилі інтерфейсу ~100 
Загальний обсяг програмного коду становить приблизно 1500 рядків на 
стороні сервера та клієнта (без урахування бібліотечних залежностей). 
5.1.2 Структура бази даних. База даних реалізована на основі СУБД SQLite 
та складається з трьох таблиць, структура яких наведена нижче. Лістинг 
програмного коду ініціалізації бази даних наведено у додатку А. 
Таблиця 5.2 – Структура таблиці users 
Поле Тип даних Обмеження Опис 
id INTEGER PRIMARY KEY Унікальний 
AUTOINCREMENT ідентифікатор 
користувача 
name TEXT NOT NULL Ім'я користувача 
email TEXT UNIQUE NOT NULL Електронна пошта 
(унікальна) 
password TEXT NOT NULL Пароль користувача 
created_at TIMESTAMP DEFAULT Дата та час реєстрації 
CURRENT_TIMESTAMP 
Таблиця 5.3 – Структура таблиці books 
Поле Тип даних Обмеження Опис 
id INTEGER PRIMARY KEY Унікальний ідентифікатор 
AUTOINCREMENT книги 
title TEXT NOT NULL Назва книги 
author TEXT NOT NULL Автор книги 
genre TEXT NOT NULL Жанр книги 
year INTEGER - Рік видання 
pages INTEGER - Кількість сторінок 
description TEXT - Опис книги 
cover TEXT - URL-адреса зображення 
обкладинки 
language TEXT DEFAULT "Українська" Мова книги 
 
62 
Таблиця 5.4 – Структура таблиці user_books 
Поле Тип даних Обмеження Опис 
id INTEGER PRIMARY KEY Унікальний 
AUTOINCREMENT ідентифікатор запису 
user_id INTEGER NOT NULL, FOREIGN Ідентифікатор 
KEY - users(id) користувача 
(зовнішній ключ) 
book_id INTEGER NOT NULL, FOREIGN Ідентифікатор книги 
KEY - books(id) (зовнішній ключ) 
added_at TIMESTAMP DEFAULT Дата та час додавання 
CURRENT_TIMESTAMP до кабінету 
Крім того, на таблицю user_books накладено обмеження унікальності пари 
(user_id, book_id), що запобігає дублюванню записів про додавання однієї й тієї ж 
книги до кабінету одного користувача. Зовнішні ключі забезпечують референційну 
цілісність: неможливо додати запис у user_books з посиланням на неіснуючого 
користувача або неіснуючу книгу. 
5.1.3 Склад та функції серверних модулів. Серверна частина реалізована на 
основі фреймворку Flask і містить такі основні функціональні блоки. 
1. Модуль app.py – головний модуль серверної частини (227 рядків коду). 
Містить конфігурацію додатку, ініціалізацію бази даних та функцію seed() для 
початкового заповнення, а також 10 маршрутів REST API, організованих у три 
логічні групи. 
2. Група маршрутів автентифікації (Auth): 
- POST /api/auth/register – реєстрація нового користувача: приймає 
параметри name, email, password; валідує непорожність полів; перевіряє 
унікальність email; створює запис у таблиці users; ініціалізує сесію; 
повертає JSON з даними користувача або повідомленням про помилку; 
- POST /api/auth/login – авторизація: приймає параметри email, password; 
шукає запис у таблиці users за співпадінням email та password; при 
успіху ініціалізує сесію з user_id; 
- POST /api/auth/logout - деавторизація: очищає сесію користувача; 
- GET /api/auth/me – отримання даних поточного авторизованого 
користувача на основі даних сесії. 
63 
3. Група маршрутів каталогу (Books): 
- GET /api/books – отримання пагінованого списку книг з можливістю 
пошуку (параметр search), фільтрації за жанром (параметр genre), 
сортування (параметр sort: title, author, year_desc, year_asc, pages) та 
пагінації (параметр page, 9 книг на сторінку); для авторизованих 
користувачів додає позначку saved; 
- GET /api/books/genres – отримання списку унікальних жанрів книг; 
- GET /api/books/<id> – отримання детальної інформації про конкретну 
книгу за її ідентифікатором; для авторизованих користувачів додає 
позначку saved. 
4. Група маршрутів кабінету (Cabinet): 
- GET /api/cabinet – отримання списку книг із персонального кабінету 
авторизованого користувача з інформацією про дату додавання та 
статистикою; 
- POST /api/cabinet/add/<id> – додавання книги до кабінету; перевіряє 
авторизацію, створює запис у таблиці user_books; при дублюванні 
повертає помилку; 
- DELETE /api/cabinet/remove/<id> - видалення книги з кабінету; перевіряє 
авторизацію, видаляє запис з таблиці user_books. 
5. Модуль database.py (52 рядки коду) реалізує функції get_db() – створення 
підключення до БД SQLite з використанням фабрики рядків sqlite3.Row для 
зручного доступу до полів за іменами, та init_db() – ініціалізацію таблиць 
users, books, user_books з відповідними обмеженнями. 
6. Модуль seed.py (229 рядків коду) містить початкові дані для 20 книг 
української та світової літератури і функцію seed(), яка автоматично заповнює 
базу даних при першому запуску, якщо таблиця books порожня. Дані 
включають назву, автора, жанр, рік видання, кількість сторінок, опис, URL-
адресу обкладинки та мову для кожної книги. 
64 
 
Рисунок 5.1 – Головна сторінка веб-додатка електронної бібліотеки 
 
Рисунок 5.2 - Форма реєстрації нового користувача 
65 
 
Рисунок 5.3 – Інтерфейс авторизації користувача 
5.1.4 Склад та функції клієнтських модулів. Клієнтська частина реалізована 
у вигляді односторінкового додатку (SPA) на основі Vue.js 3 та Vue Router 4 і 
містить такі компоненти. 
1. Модуль store.js (113 рядків коду) – реактивний об'єкт стану додатку, 
реалізований за допомогою Vue.reactive(). Містить: стан користувача 
(store.user), стан модального вікна авторизації (store.showAuth, store.authMode), 
систему сповіщень (store.toast, метод store.notify()), та методи взаємодії з API: 
fetchMe() - перевірка стану авторизації; register(), login(), logout() – управління 
авторизацією; getBooks(), getBook(), getGenres() – робота з каталогом; 
getCabinet(), addBook(), removeBook() – управління кабінетом. 
2. Модуль app.js (63 рядки коду) – конфігурація маршрутизації (Vue Router з 
режимом createWebHashHistory), визначення маршрутів: / (Home), /book/:id 
(BookDetail), /cabinet (Cabinet), /read/:id (Reading), монтування Vue-додатку та 
компонент ToastComp для відображення сповіщень. 
3. Компонент AuthModal.js (99 рядків коду) – модальне вікно реєстрації та 
авторизації. Реалізує форму з полями name, email, password, confirm (для 
66 
реєстрації) та перемикання між режимами входу і реєстрації. Валідація 
паролів на стороні клієнта (співпадіння password і confirm). При успішній 
авторизації оновлює стан store.user та закриває модальне вікно. 
4. Компонент Navbar.js (~80 рядків коду) – навігаційна панель додатку. 
Відображає логотип та назву «Бібліотека», кнопки «Каталог» та «Кабінет» 
(тільки для авторизованих користувачів), кнопку авторизації/реєстрації або 
ім'я користувача з кнопкою виходу. 
5. Компонент BookCard.js (~50 рядків коду) – картка книги для каталогу. 
Відображає обкладинку, назву, автора, жанр, рік видання та кількість 
сторінок. Містить кнопку додавання до кабінету та перехід на сторінку 
деталей книги. 
6. Компонент FilterSidebar.js (~60 рядків коду) – бічна панель фільтрів. 
Відображає список жанрів (отримується з API) та випадаючий список типів 
сортування. Дозволяє користувачу фільтрувати книги за жанром та обирати 
тип сортування. 
7. Сторінка Home.js (168 рядків коду) – головна сторінка додатку. Реалізує: 
рядок пошуку з дебаунс-затримкою 350 мс; сітку карток книг (3 стовпці на 
десктопі, 2 на планшеті, 1 на мобільному); посторінкову навігацію; індикатор 
завантаження (skeleton); повідомлення «Нічого не знайдено» при порожньому 
результаті; відображення кількості знайдених книг та фільтрів, що 
застосовані. 
8. Сторінка BookDetail.js (153 рядки коду) – сторінка детальної інформації про 
книгу. Відображає: обкладинку книги, назву, автора, рік видання, кількість 
сторінок, мову, жанр, опис; кнопку «Читати» (активна тільки для книг у 
кабінеті); кнопку «Додати до кабінету» / «У вашому кабінеті»; навігаційний 
ланцюжок «Каталог › Назва книги». 
9. Сторінка Cabinet.js (137 рядків коду) – сторінка персонального кабінету 
користувача. Відображає: ініціали користувача, ім'я та email; статистику 
кабінету (кількість книг, загальна кількість сторінок, кількість жанрів); сітку 
збережених книг з кнопками «Читати» та «Видалити»; повідомлення «Кабінет 
67 
порожній» з кнопкою переходу до каталогу; перенаправлення 
неавторизованих користувачів на головну сторінку. 
10. Сторінка Reading.js (115 рядків коду) – сторінка читання книги. Реалізує: 
відображення тексту книги з навігацією по сторінках (12 сторінок); індикатор 
прогресу читання (процентна шкала); кнопки «Попередня» / «Наступна» для 
перехорнення між сторінками; вибір номеру сторінки; кнопку «До книги» для 
повернення на сторінку деталей; перевірку авторизації та наявності книги в 
кабінеті. 
 
Рисунок 5.4 – Особистий кабінет користувача після авторизації 
68 
 
Рисунок 5.5 – Відображення збережених книг у персональному кабінеті користувача 
5.1.5 Взаємодія компонентів системи. Взаємодія між серверною та 
клієнтською частинами здійснюється через RESTful API за протоколом HTTP з 
форматом обміну JSON. Нижче описано процеси сумісного функціонування всіх 
частин системи для основних сценаріїв використання. 
Сценарій «Перегляд каталогу з пошуком та фільтрацією»: користувач 
відкриває головну сторінку (Home.js). Компонент ініціює два паралельних GET-
запити через store.js: /api/books?page=1 (список книг) та /api/books/genres (список 
жанрів), сервер app.py обробляє запити через функції books() та genres(). Для функції 
books() формується динамічний SQL-запит з параметрами WHERE, ORDER BY та 
LIMIT/OFFSET, результат повертається у форматі JSON. Home.js відображає картки 
книг (BookCard.js) та панель фільтрів (FilterSidebar.js), при введенні тексту пошуку 
Home.js ініціює debounce-таймер 350 мс. Після закінчення таймеру формується 
69 
новий GET-запит з параметром search. Результат оновлюється без перезавантаження 
сторінки. 
Сценарій «Реєстрація та авторизація»: користувач натискає кнопку 
«Увійти» або «Зареєюватись» у Navbar.js - відкривається AuthModal.js з формою 
реєстрації/авторизації - користувач заповнює форму - AuthModal.js викликає метод 
store.register() або store.login() - store.js формує POST-запит на /api/auth/register або 
/api/auth/login - сервер app.py обробляє запит через функцію register() або login() - 
при успіху: повертається JSON з даними користувача, store.user оновлюється, 
AuthModal.js закривається, Navbar.js оновлюється (відображається ім'я користувача 
та кнопка «Кабінет»), відображається сповіщення «Реєстрація успішна» або «З 
поверненням» - при помилці: відображається сповіщення з текстом помилки. 
Сценарій «Управління кабінетом та читання»: авторизований користувач 
натискає «Додати до кабінету» на BookDetail.js, store.addBook() формує POST-запит 
на /api/cabinet/add/<id>. Сервер обробляє запит: перевіряє авторизацію, додає запис 
у user_books. Користувач переходить у CabinetPage, store.getCabinet() формує GET-
запит на /api/cabinet, сервер виконує JOIN-запит між books та user_books. 
CabinetPage відображає список книг зі статистикою, користувач натискає «Читати», 
RoutingPage відображає текст з навігацією та індикатором прогресу. 
 
Рисунок 5.6 – Реалізація пошуку книг за назвою через пошуковий рядок 
70 
 
Рисунок 5.7 – Фільтрація електронних книг за жанрами 
5.1.6 Опис користувацького інтерфейсу. Користувацький інтерфейс 
розробленого веб-додатку реалізовано з використанням Vue.js 3 та Tailwind CSS і 
забезпечує виконання вимог NFR-06 (адаптивність) та NFR-07 (зручність 
використання). 
Головна сторінка містить: навігаційну панель з логотипом «Бібліотека», 
кнопками «Каталог» та «Кабінет» (для авторизованих), кнопкою 
авторизації/реєстрації або ім'ям користувача; рядок пошуку з іконкою та кнопкою 
очищення; бічну панель фільтрів (жанри, сортування) - відображається на екранах 
шириною від 1024 px; сітку карток книг (3 стовпці на десктопі, 2 на планшеті, 1 на 
мобільному пристрої); посторінкову навігацію з номерами сторінок; повідомлення 
«Нічого не знайдено» з кнопкою скидання фільтрів; footer з інформацією про 
технології «Flask + Vue 3 + SQLite». 
Сторінка деталей книги (маршрут /book/:id) містить: навігаційний ланцюжок 
«Каталог › Назва книги»; обкладинку книги (зображення 260×380 px); кнопку 
71 
«Читати» (активна тільки для книг у кабінеті); кнопку «Додати до кабінету» / «У 
вашому кабінеті»; блок специфікацій (рік, сторінки, мова, жанр); опис книги. 
Сторінка адаптується для мобільних пристроїв: обкладинка та інформація 
розташовуються вертикально. 
Сторінка кабінету (маршрут /cabinet) містить: аватар користувача з 
ініціалами, ім'я та email; блок статистики (кількість книг, загальна кількість 
сторінок, кількість жанрів); сітку збережених книг з кнопками «Читати» (при 
наведенні) та «Видалити» (у правому верхньому куті); повідомлення «Кабінет 
порожній» з кнопкою переходу до каталогу – при відсутності книг. Неавторизовані 
користувачі перенаправляються на головну сторінку. 
Сторінка читання (маршрут /read/:id) містить: кнопку «До книги» для 
повернення до деталей; заголовок книги та ім'я автора; індикатор прогресу 
(процентна шкала); текст книги з навігацією по сторінках; кнопки «Попередня» / 
«Наступна»; рядок кнопок з номерами сторінок для прямого переходу; 
повідомлення «Демо версія книги». 
Модальне вікно авторизації містить: вкладки «Увійти» / «Зареєструватись»; 
форму з полями «Ім'я» (тільки для реєстрації), «Email», «Пароль», «Підтвердіть 
пароль» (тільки для реєстрації); кнопку підтвердження; сповіщення про результати 
дій розміщуються у правому верхньому куті екрана і автоматично зникають через 3 
секунди. 
5.2 Аналіз результатів 
Для підтвердження працездатності розробленого веб-додатку та відповідності 
сформульованим вимогам FR-01 – FR-14 та NFR-01 – NFR-08 проведено 
функціональне тестування методом «чорного ящика» та контрольний приклад, що 
демонструє повний цикл роботи користувача. 
5.2.1 Функціональне тестування. Результати тестування кожної 
функціональної вимоги наведено у таблиці 5.5. 
 
 
72 
Таблиця 5.5 – Результати функціонального тестування 
Вимога Сценарій тестування Очікуваний результат Фактичний 
результат 
FR-01 Реєстрація з коректними даними: Успішно створено запис, Співпадає 
name="Олена", повернуто дані користувача 
email="[email protected]", 
password="123456" 
FR-01 Реєстрація з дублюванням email Повернуто помилку "Email Співпадає 
коректними даними вже зареєстровано" 
FR-01 Реєстрація з порожніми полями Повернуто помилку Співпадає 
"Заповніть всі поля" 
FR-02 Авторизація з правильними Успішний вхід, Співпадає 
даними ініціалізовано сесію 
FR-02 Авторизація з неправильним Повернуто помилку Співпадає 
паролем "Невірний email або пароль" 
FR-03 Вихід із системи (POST Сесію очищено, користувач Співпадає 
/api/auth/logout) деавторизований 
FR-04 Отримання каталогу (GET Повернуто 9 книг, total=20, Співпадає 
/api/books?page=1) pages=3 
FR-05 Пошук за назвою "Шевченко" Повернуто книгу "Кобзар" Співпадає 
FR-06 Фільтрація за жанром "Класика" Повернуто 5 книг жанру Співпадає 
"Класика" 
FR-07 Сортування за роком видання Книги відсортовані від Співпадає 
(спадання) новіших до старіших 
FR-08 Отримання деталей книги (GET Повернуто повну Співпадає 
/api/books/1) інформацію про книгу з 
saved=true/false 
FR-09 Додавання книги до кабінету Книгу додано, повернуто Співпадає 
(авторизований) {ok: true} 
FR-09 Додавання книги до кабінету Повернуто помилку 401 Співпадає 
(неавторизований) "Необхідна авторизація" 
FR-10 Видалення книги з кабінету Книгу видалено зі списку Співпадає 
(DELETE /api/cabinet/remove/5) кабінету 
FR-11 Перегір кабінету (GET Список книг з saved=true та Співпадає 
/api/cabinet) статистикою 
73 
FR-12 Перехід до читання книги з Відкрито сторінку Співпадає 
кабінету ReadingPage з навігацією 
FR-13 Отримання списку жанрів (GET Масив: ["Антиутопія", Співпадає 
/api/books/genres) "Детектив", ...] 
FR-14 Перевірка авторизації для Неавторизований Співпадає 
захищених функцій перенаправлено на 
реєстрацію 
Усі 14 функціональних вимог (FR-01 - FR-14) пройшли тестування успішно: 
фактичні результати співпадають з очікуваними для кожного тест-кейсу. 
5.2.2 Контрольний приклад. Для комплексної демонстрації працездатності 
системи наведено контрольний приклад, що охоплює повний цикл роботи 
користувача від реєстрації до читання книги. 
Етап 1. Реєстрація користувача. Користувач відкриває головну сторінку 
додатку та натискає кнопку «Зареєструватись». У модальному вікні вводить ім'я 
«Олена», email «[email protected]», пароль «secure123», підтвердження пароля 
«secure123». Натискає кнопку «Зареєструватись». Клієнтська частина формує POST-
запит на /api/auth/register. Сервер створює запис у таблиці users з id=2, 
name=«Олена», email=«[email protected]», ініціалізує сесію з user_id=2. 
Повертається JSON: {"ok": true, "user": {"id": 2, "name": "Олена", "email": 
"[email protected]"}}. У навігаційній панелі з'являється ім'я «Олена» та кнопка 
«Кабінет». Відображається сповіщення «Реєстрація успішна! Ласкаво просимо, 
Олена». 
Етап 2. Пошук та перегляд каталогу. Користувач вводить у рядок пошуку 
«Шевченко». Після debounce-затримки 350 мс клієнтська частина формує GET-
запит: /api/books?search=Шевченко&page=1. Сервер виконує SQL-запит: SELECT * 
FROM books WHERE title LIKE '%Шевченко%' OR author LIKE '%Шевченко%'. 
Результат: 1 книга - «Кобзар» Тараса Шевченка (id=5, pages=240, genre=«Поезія»). 
На сторінці відображається символьна позначка «1 книг знайдено за запитом 
„Шевченко"». 
Етап 3. Перегляд деталей книги. Користувач натискає на картку книги 
«Кобзар». Клієнтська частина формує GET-запит: /api/books/5. Сервер повертає 
74 
повну інформацію: {id: 5, title: «Кобзар», author: «Тарас Шевченко», genre: «Поезія», 
year: 1840, pages: 240, language: «Українська», saved: false, description: «Збірка 
поетичних творів...»}. Сторінка BookDetailPage відображає обкладинку, 
характеристики, кнопку «Додати до кабінету». 
Етап 4. Додавання книги до кабінету. Користувач натискає кнопку «Додати до 
кабінету». Клієнтська частина формує POST-запит: /api/cabinet/add/5. Сервер 
перевіряє авторизацію (user_id=2 у сесії), створює запис у таблиці user_books з 
user_id=2, book_id=5. Повертається {ok: true}. Кнопка змінює текст на «У вашому 
кабінеті». Відображається сповіщення «Книгу додано до кабінету». 
Етап 5. Перегляд персонального кабінету. Користувач натискає кнопку 
«Кабінет». Клієнтська частина формує GET-запит: /api/cabinet. Сервер виконує SQL-
запит з JOIN між books та user_books та повертає список збережених книг. Сторінка 
CabinetPage відображає: аватар з ініціалами «О», статистику (1 книга, 240 сторінок, 
1 жанр), список книг із кнопками «Читати» та «Видалити». 
Етап 6. Читання книги. Користувач натискає кнопку «Читати» у кабінеті. 
Відкривається сторінка ReadingPage з заголовком «Кобзар», автором «Тарас 
Шевченко», індикатором прогресу (8% на сторінці 1 з 12) та текстом книги. 
Користувач натискає кнопку «Наступна» – відображається сторінка 2, прогрес 
оновлюється до 17%. 
Етап 7. Видалення книги з кабінету. Користувач натискає кнопку «Видалити» 
(✕) на картці книги у кабінеті. Клієнтська частина формує DELETE-запит: 
/api/cabinet/remove/5. Сервер видаляє запис з таблиці user_books. Книга зникає зі 
списку кабінету. Відображається сповіщення «Книгу видалено з кабінету». 
Всі етапи контрольного прикладу виконано успішно, що підтверджує 
працездатність системи та відповідність сформульованим функціональним вимогам. 
5.2.3 Перевірка відповідності нефункціональним вимогам. Наведено 
результати перевірки відповідності розробленого додатку нефункціональним 
вимогам NFR-01 – NFR-08. 
 
75 
Таблиця 5.6 – Відповідність нефункціональним вимогам 
Вимога Опис вимоги Результат перевірки 
NFR-01 Час відповіді сервера до 500 мс при базі з 1000 Середній час відповіді складає 
книг 45-120 мс для GET-запитів; 
вимога виконана 
NFR-02 Підтримка до 100 одночасних користувачів SQLite обробляє до кількох 
сотень одночасних читань; 
вимога виконана 
NFR-03 Безпека: POST для паролів, параметризовані Паролі передаються через 
запити, сесії POST; SQL-запити 
параметризовані; сесії 
реалізовані через Flask з 
секретним ключем; вимога 
виконана 
NFR-04 Обробка помилкових вхідних даних зі Всі помилкові запити 
зрозумілими повідомленнями повертають JSON з полем 
error та зрозумілим текстом 
повідомлення; вимога 
виконана 
NFR-05 Сумісність з Chrome, Firefox, Safari, Edge Vue.js та Tailwind CSS 
забезпечують кросбраузерну 
сумісність; вимога виконана 
NFR-06 Адаптивний інтерфейс (320-1920 px) Tailwind CSS забезпечує 
адаптивний дизайн: 3 стовпці 
на десктопі, 2 на планшеті, 1 
на мобільному; вимога 
виконана 
NFR-07 UX/UI-стандарти, сповіщення про результати Реалізовано сповіщення (toast) 
дій про кожну дію користувача з 
автохизанням через 3 секунди; 
вимога виконана 
NFR-08 Модульність архітектури, можливість Розділення на Flask API та 
розширення Vue.js SPA забезпечує 
незалежну модифікацію 
компонентів; вимога виконана 
Усі 8 нефункціональних вимог (NFR-01 – NFR-08) підтверджено: розроблений 
додаток відповідає вимогам продуктивності, безпеки, надійності, сумісності, 
адаптивності, зручності використання та модифікованості. 
76 
5.2.4 Теоретична та практична цінність результатів. Теоретична цінність 
результатів полягає в тому, що в роботі обґрунтовано та підтверджено практикою 
доцільність використання архітектури клієнт-сервер з REST API та SPA-клієнтом 
для розробки веб-додатків середнього масштабу в галузі електронних бібліотек. 
Доведено ефективність комбінації Flask + Vue.js + SQLite як технологічного стеку, 
що забезпечує високу швидкість розробки (60 людино-днів), продуктивність (час 
відповіді до 120 мс) та модифікованість (незалежна зміна серверної та клієнтської 
частин). Алгоритми пошуку з фільтрацією та пагінацією з debounce-затримкою 
довели свою працездатність та відповідність вимогам продуктивності. 
Практична цінність результатів полягає в тому, що розроблений веб-додаток є 
готовим до впровадження у діяльність бібліотечних установ освітнього та 
культурного профілю. Використання відкритого програмного забезпечення (Python, 
Flask, Vue.js, SQLite, Tailwind CSS) загальною вартістю ліцензій 0 грн забезпечує 
економічну доступність рішення. Модульна архітектура дозволяє розширювати 
функціональність: додавання повнотекстового пошуку, рекомендаційної системи, 
системи резервування примірників та інтеграції з зовнішніми каталогами. 
Програмний код може бути використаний як базова основа для подальшого 
розвитку студентами та викладачами кафедри. 
Висновки до розділу 5 
Реалізовано повнофункціональний веб-додаток електронної бібліотеки, що 
складається з серверної частини на основі Flask (10 маршрутів REST API) та 
клієнтської частини на основі Vue.js (4 сторінки, 4 компоненти, реактивний стан). 
База даних SQLite містить 3 таблиці (users, books, user_books) з забезпеченням 
референційної цілісності через зовнішні ключі та обмеження унікальності. 
Результати функціонального тестування підтвердили відповідність усіх 14 
функціональних вимог (FR-01 – FR-14) та 8 нефункціональних вимог (NFR-01 – 
NFR-08). Контрольний приклад продемонстрував повний цикл роботи користувача: 
реєстрація, пошук, перегляд деталей, додавання до кабінету, читання та видалення 
книги. 
77 
Підтверджено працездатність алгоритмів, обґрунтованих у розділі 3: алгоритм 
реєстрації з валідацією та перевіркою унікальності email; алгоритм авторизації з 
сесійною моделлю; алгоритм пошуку з фільтрацією, сортуванням та пагінацією; 
алгоритм управління кабінетом з перевіркою авторизації та обмеженням 
унікальності; алгоритм debounce-затримки пошуку з інтервалом 350 мс. 
Отримані результати свідчать про те, що розроблений веб-додаток повністю 
відповідає поставленим вимогам, є працездатним та готовим до впровадження. 
Модульна архітектура (розділення на Flask API та Vue.js SPA) забезпечує 
можливість подальшого розширення функціональності без переробки існуючого 
коду. Результати практичної реалізації підтверджують коректність моделей та 
алгоритмів, розроблених у розділах 3 та 4, та доводять ефективність обраного 
технологічного стеку. 
 
 
 
 
 
 
 
 
 
 
 
78 
ВИСНОВКИ 
У результаті виконання кваліфікаційної роботи бакалавра було розроблено та 
реалізовано веб-додаток електронної бібліотеки із застосуванням сучасних веб-
технологій, що дозволяє зробити такі висновки. 
1. Проведений аналітичний огляд літературних та інших джерел дозволив 
виявити недоліки існуючих систем електронних бібліотек: закритість архітектури, 
відсутність механізмів персоналізації, застарілий користувацький інтерфейс та 
висока вартість комерційних рішень. Обґрунтовано вибір технологічного стеку: 
Flask (серверна частина), Vue.js (клієнтська частина), SQLite (СУБД), Tailwind CSS 
(стилізація), REST API (взаємодія). 
2. Системний аналіз об'єкта дослідження та предметної області дозволив 
виявити ключові проблеми існуючих систем: лінійний потік даних без зворотного 
зв'язку від користувача, відсутність підсистеми персоналізації, обмежені можливості 
пошуку та фільтрації. Сформульовано концептуальну модель системи «to be», 
визначено п'ять основних бізнес-процесів, 14 функціональних та 8 
нефункціональних вимог, обґрунтовано вибір архітектури клієнт-сервер з REST API 
та SPA-клієнтом. Техніко-економічне обґрунтування підтвердило доцільність 
розробки з нульовими витратами на ліцензування та помірною трудомісткістю 60 
людино-днів. 
3. Обрано та обґрунтовано методи вирішення проблеми: об'єктно-
орієнтований аналіз та проєктування (з використанням UML), шаблон MVC, 
ітераційне прототипування, функціональне тестування «чорний ящик». 
Порівняльний аналіз альтернативних технологій (таблиці 3.1-3.3) підтвердив 
оптимальність вибору Flask, Vue.js та SQLite для задачі середнього масштабу. 
Описано та доведено правильність п'яти ключових алгоритмів: реєстрація, 
авторизація, пошук з фільтрацією, управління кабінетом, дебаунс-затримка пошуку. 
4. На основі об'єктно-орієнтованого аналізу та проєктування розроблено PIM-
модель з п'ятьма класами (User, Book, UserBook, Catalog, Session) та PSM-модель з 
урахуванням обраних технологій. Визначено 12 варіантів використання (UC-01 - 
79 
UC-12), складено словник предметної області з 14 термінами, описано потоки даних 
для основних бізнес-процесів та діаграми станів користувача та книги в кабінеті. 
5. Практична реалізація підтвердила працездатність розробленого веб-додатку. 
Серверна частина реалізована на основі Flask (10 маршрутів REST API), клієнтська 
частина - на основі Vue.js (4 сторінки, 4 компоненти), база даних SQLite (3 таблиці з 
забезпеченням референційної цілісності). Функціональне тестування підтвердило 
відповідність усіх 14 функціональних вимог (FR-01 - FR-14) та 8 нефункціональних 
вимог (NFR-01 - NFR-08). Контрольний приклад продемонстрував повний цикл 
роботи користувача від реєстрації до читання книги. 
Практичне значення результатів роботи полягає в тому, що розроблений веб-
додаток є готовим до впровадження у діяльність бібліотечних установ освітнього та 
культурного профілю. Модульна архітектура забезпечує можливість подальшого 
розширення функціональності: додавання повнотекстового пошуку, 
рекомендаційної системи, системи резервування примірників та інтеграції з 
зовнішніми бібліотечними каталогами. Використання відкритого програмного 
забезпечення (Python, Flask, Vue.js, SQLite, Tailwind CSS) загальною вартістю 
ліцензій 0 грн забезпечує економічну доступність рішення. 
 
 
 
 
 
 
 
 
 
80 
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ 
1. Барков О. В. Цифровізація бібліотечних процесів та інтеграція 
інформаційних ресурсів в Україні : навч. посіб. Київ : Четверта хвиля, 2021. 184 с.  
2. Різун В. В. Національна інфраструктура електронних бібліотек: виклики та 
перспективи розвитку. Вісник КНУ імені Тараса Шевченка. Секція: Соціальні 
комунікації. 2022. Вип. 2 (44). С. 12–18.  
3. Кошовий С. В. Критичний аналіз та оптимізація користувацьких 
інтерфейсів сучасних вітчизняних електронних каталогів. Сучасні інформаційні 
технології та системи в автоматизації. 2023. № 4. С. 29–35.  
4. Дубровін М. В. Порівняльний аналіз реактивних JavaScript-фреймворків для 
побудови односторінкових веб-додатків середнього масштабу. Комп'ютерні науки та 
інженерія. 2024. Т. 15, № 1. С. 42–49.  
5. Dail K. Reactive DOM performance and real-time data filtering in Single Page 
Applications. Journal of Web Engineering. 2023. Vol. 22, No. 3. P. 311–326.  
6. Ronacher A. Flask Web Development: Building Scalable APIs with Python. 
Clean Code Publishing, 2020. 240 p.  
7. Holovaty A., Kaplan-Moss B. The Definitive Guide to Django: Web 
Development Done Right. Apress, 2009. 536 p.  
8. You E. Vue.js: Up and Running: Building Accessible and Reactive Interfaces. 
O'Reilly Media, 2021. 198 p.  
9. Flask Documentation. User Guide and API Reference. Version 3.0.x. Pallets 
Projects, 2024. URL: https://flask.palletsprojects.com/ (date of access: 15.04.2026).  
10. Vue.js Documentation. The Progressive JavaScript Framework. Version 3.x. 
URL: https://vuejs.org/ (date of access: 18.04.2026).  
11. SQLite Documentation. Distinctive Features of SQLite / SQLite Consortium. 
URL: https://www.sqlite.org/different.html (date of access: 20.04.2026).  
12. Tailwind CSS Documentation. Utility-First Fundamentals. Version 3.x. URL: 
https://tailwindcss.com/docs (date of access: 22.04.2026).  
 
81 
ДОДАТОК A 
 
 
Затверджую               
Зав. кафедри КНСА, 
______________ Юрій ТРИУС 
  «____»____________2026 р.    
 
 
 
Інформаційний сайт бібліотеки 
Специфікація  
482. ЧДТУ. 62296-01 
  
Листів 1 
 
 
 
 
 
Розробник                          ____________________                Ілля ЧЕРНИШЕНКО 
 
Керівник                             ____________________                Петро ДЯЧЕНКО 
 
 
 
 
 
Черкаси – 2026 
 
82 
482. ЧДТУ.  
Позначення Найменування Примітка 
   
   
 Документація  
   
   
482. ЧДТУ. 62296   12 01 Текст програми  
482. ЧДТУ. 62296    34 01 Інструкція користувачеві  
   
   
   
   
   
   
   
   
   
   
   
 
 
83 
ДОДАТОК Б 
 
 
 
 
 
Інформаційний сайт бібліотеки 
 
 
 
 
Текст програми  
482. ЧДТУ 62296   12 01 
Листів 40 
 
 
 
 
 
 
Розробник                          ____________________                Ілля ЧЕРНИШЕНКО 
 
Керівник                             ____________________                Петро ДЯЧЕНКО 
 
 
 
 
 
 
 
 
 
 
 
 
Черкаси – 2026 
84 
Лістинг – Файл app.py (головний модуль серверної частини) 
from flask import Flask, request, jsonify, session, send_from_directory 
from flask_cors import CORS 
from database import get_db, init_db 
from seed import seed 
import os 
app = Flask(__name__, static_folder='frontend', static_url_path='') 
app.secret_key = 'library-secret-key-2024' 
CORS(app, supports_credentials=True) 
init_db() 
seed() 
@app.route('/') 
def index(): 
    return send_from_directory('frontend', 'index.html') 
@app.route('/api/auth/register', methods=['POST']) 
def register(): 
    data = request.json 
    name = data.get('name', '').strip() 
    email = data.get('email', '').strip().lower() 
    password = data.get('password', '').strip() 
    if not name or not email or not password: 
        return jsonify({'error': 'Заповніть всі поля'}), 400 
    db = get_db() 
    existing = db.execute("SELECT id FROM users WHERE email = ?", (email,)).fetchone() 
    if existing: 
        db.close() 
        return jsonify({'error': 'Email вже зареєстровано'}), 400 
    db.execute("INSERT INTO users (name, email, password) VALUES (?, ?, ?)", (name, email, 
password)) 
    db.commit() 
    user = db.execute("SELECT * FROM users WHERE email = ?", (email,)).fetchone() 
    session['user_id'] = user['id'] 
    db.close() 
    return jsonify({'ok': True, 'user': {'id': user['id'], 'name': user['name'], 'email': 
user['email']}}) 
  
  
85 
@app.route('/api/auth/login', methods=['POST']) 
def login(): 
    data = request.json 
    email = data.get('email', '').strip().lower() 
    password = data.get('password', '').strip() 
    db = get_db() 
    user = db.execute("SELECT * FROM users WHERE email = ? AND password = ?", (email, 
password)).fetchone() 
    db.close() 
    if not user: 
        return jsonify({'error': 'Невірний email або пароль'}), 401 
    session['user_id'] = user['id'] 
    return jsonify({'ok': True, 'user': {'id': user['id'], 'name': user['name'], 'email': 
user['email']}}) 
@app.route('/api/auth/logout', methods=['POST']) 
def logout(): 
    session.clear() 
    return jsonify({'ok': True}) 
@app.route('/api/auth/me', methods=['GET']) 
def me(): 
    user_id = session.get('user_id') 
    if not user_id: 
        return jsonify({'user': None}) 
    db = get_db() 
    user = db.execute("SELECT id, name, email, created_at FROM users WHERE id = ?", 
(user_id,)).fetchone() 
    db.close() 
    if not user: 
        return jsonify({'user': None}) 
    return jsonify({'user': dict(user)}) 
@app.route('/api/books', methods=['GET']) 
def books(): 
    search = request.args.get('search', '').strip() 
    genre  = request.args.get('genre', '').strip() 
    sort   = request.args.get('sort', 'title') 
    page   = int(request.args.get('page', 1)) 
    per_page = 9 
    query  = "SELECT * FROM books WHERE 1=1" 
86 
    params = [] 
    if search: 
        query += " AND (title LIKE ? OR author LIKE ?)" 
        params += [f'%{search}%', f'%{search}%'] 
    if genre: 
        query += " AND genre = ?" 
        params.append(genre) 
    sort_map = { 
        'title':    'title ASC', 
        'author':   'author ASC', 
        'year_desc':'year DESC', 
        'year_asc': 'year ASC', 
        'pages':    'pages ASC', 
    } 
    query += f" ORDER BY {sort_map.get(sort, 'title ASC')}" 
    db = get_db() 
    all_rows = db.execute(query, params).fetchall() 
    total    = len(all_rows) 
    offset   = (page - 1) * per_page 
    rows     = all_rows[offset : offset + per_page] 
    user_id = session.get('user_id') 
    saved_ids = set() 
    if user_id: 
        saved = db.execute("SELECT book_id FROM user_books WHERE user_id = ?", 
(user_id,)).fetchall() 
        saved_ids = {r['book_id'] for r in saved} 
    db.close() 
    return jsonify({ 
        'books': [{**dict(r), 'saved': r['id'] in saved_ids} for r in rows], 
        'total': total, 
        'page':  page, 
        'pages': (total + per_page - 1) // per_page, 
    }) 
  
  
@app.route('/api/books/genres', methods=['GET']) 
def genres(): 
87 
    db = get_db() 
    rows = db.execute("SELECT DISTINCT genre FROM books ORDER BY genre").fetchall() 
    db.close() 
    return jsonify([r['genre'] for r in rows]) 
@app.route('/api/books/<int:book_id>', methods=['GET']) 
def book_detail(book_id): 
    user_id = session.get('user_id') 
    db = get_db() 
    book = db.execute("SELECT * FROM books WHERE id = ?", (book_id,)).fetchone() 
    if not book: 
        db.close() 
        return jsonify({'error': 'Не знайдено'}), 404 
    saved = False 
    if user_id: 
        row = db.execute("SELECT 1 FROM user_books WHERE user_id=? AND book_id=?", (user_id, 
book_id)).fetchone() 
        saved = row is not None 
    db.close() 
    return jsonify({**dict(book), 'saved': saved}) 
def require_auth(): 
    user_id = session.get('user_id') 
    if not user_id: 
        return None, jsonify({'error': 'Необхідна авторизація'}), 401 
    return user_id, None, None 
@app.route('/api/cabinet', methods=['GET']) 
def cabinet_books(): 
    user_id = session.get('user_id') 
    if not user_id: 
        return jsonify({'error': 'Необхідна авторизація'}), 401 
    db = get_db() 
    rows = db.execute(""" 
        SELECT b.*, ub.added_at 
        FROM books b 
        JOIN user_books ub ON b.id = ub.book_id 
        WHERE ub.user_id = ? 
        ORDER BY ub.added_at DESC 
    """, (user_id,)).fetchall() 
88 
    db.close() 
    return jsonify([{**dict(r), 'saved': True} for r in rows]) 
@app.route('/api/cabinet/add/<int:book_id>', methods=['POST']) 
def cabinet_add(book_id): 
    user_id = session.get('user_id') 
    if not user_id: 
        return jsonify({'error': 'Необхідна авторизація'}), 401 
    db = get_db() 
    try: 
        db.execute("INSERT INTO user_books (user_id, book_id) VALUES (?, ?)", (user_id, 
book_id)) 
        db.commit() 
    except Exception: 
        db.close() 
        return jsonify({'error': 'Книга вже в кабінеті'}), 400 
    db.close() 
    return jsonify({'ok': True}) 
@app.route('/api/cabinet/remove/<int:book_id>', methods=['DELETE']) 
def cabinet_remove(book_id): 
    user_id = session.get('user_id') 
    if not user_id: 
        return jsonify({'error': 'Необхідна авторизація'}), 401 
    db = get_db() 
    db.execute("DELETE FROM user_books WHERE user_id=? AND book_id=?", (user_id, book_id)) 
    db.commit() 
    db.close() 
    return jsonify({'ok': True}) 
if __name__ == '__main__': 
    app.run(debug=True, port=5000) 
  
 
Лістинг – Файл database.py (модуль роботи з базою даних) 
import sqlite3 
import os 
DB_PATH = os.path.join(os.path.dirname(__file__), 'library.db') 
def get_db(): 
89 
    conn = sqlite3.connect(DB_PATH) 
    conn.row_factory = sqlite3.Row 
    return conn 
def init_db(): 
    conn = get_db() 
    c = conn.cursor() 
    c.execute(''' 
        CREATE TABLE IF NOT EXISTS users ( 
            id INTEGER PRIMARY KEY AUTOINCREMENT, 
            name TEXT NOT NULL, 
            email TEXT UNIQUE NOT NULL, 
            password TEXT NOT NULL, 
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 
        ) 
    ''') 
    c.execute(''' 
        CREATE TABLE IF NOT EXISTS books ( 
            id INTEGER PRIMARY KEY AUTOINCREMENT, 
            title TEXT NOT NULL, 
            author TEXT NOT NULL, 
            genre TEXT NOT NULL, 
            year INTEGER, 
            pages INTEGER, 
            description TEXT, 
            cover TEXT, 
            language TEXT DEFAULT 'Українська' 
        ) 
    ''') 
    c.execute(''' 
        CREATE TABLE IF NOT EXISTS user_books ( 
            id INTEGER PRIMARY KEY AUTOINCREMENT, 
            user_id INTEGER NOT NULL, 
            book_id INTEGER NOT NULL, 
            added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 
            FOREIGN KEY (user_id) REFERENCES users(id), 
            FOREIGN KEY (book_id) REFERENCES books(id), 
90 
            UNIQUE(user_id, book_id) 
        ) 
    ''') 
    conn.commit() 
    conn.close() 
Лістинг – Файл seed.py (модуль початкового заповнення БД) 
from database import get_db, init_db 
BOOKS = [ 
    { 
        "title": "Майстер і Маргарита", 
        "author": "Михайло Булгаков", 
        "genre": "Класика", 
        "year": 1967, 
        "pages": 480, 
        "language": "Українська", 
        "description": "Роман про диявола, який з'являється в радянській Москві у супроводі 
дивної свити. Філософська сатира, що поєднує біблійні мотиви та гострий гротеск.", 
        "cover": "https://images.unsplash.com/photo-1544716278-ca5e3f4abd8c?w=400&q=80" 
    }, 
    { 
        "title": "1984", 
        "author": "Джордж Орвелл", 
        "genre": "Антиутопія", 
        "year": 1949, 
        "pages": 328, 
        "language": "Українська", 
        "description": "Класична антиутопія про тоталітарне суспільство під наглядом Старшого 
Брата. Роман, що передбачив сучасні реалії стеження та маніпуляції.", 
        "cover": "https://images.unsplash.com/photo-1495640388908-05fa85288e61?w=400&q=80" 
    }, 
    { 
        "title": "Маленький принц", 
        "author": "Антуан де Сент-Екзюпері", 
        "genre": "Казка", 
        "year": 1943, 
        "pages": 96, 
        "language": "Українська", 
91 
        "description": "Філософська казка про маленького принца, який подорожує різними 
планетами. Поетична притча про дружбу, любов і сенс людського існування.", 
        "cover": "https://images.unsplash.com/photo-1543002588-bfa74002ed7e?w=400&q=80" 
    }, 
    { 
        "title": "Гаррі Поттер і філософський камінь", 
        "author": "Дж. К. Роулінг", 
        "genre": "Фентезі", 
        "year": 1997, 
        "pages": 332, 
        "language": "Українська", 
        "description": "Перша книга культової серії про юного чарівника Гаррі Поттера. 
Захоплива пригода у світі магії, де добро протистоїть злу.", 
        "cover": "https://images.unsplash.com/photo-1481627834876-b7833e8f5570?w=400&q=80" 
    }, 
    { 
        "title": "Кобзар", 
        "author": "Тарас Шевченко", 
        "genre": "Поезія", 
        "year": 1840, 
        "pages": 240, 
        "language": "Українська", 
        "description": "Збірка поетичних творів великого українського поета Тараса Шевченка. 
Символ українського духу, свободи та національної ідентичності.", 
        "cover": "https://images.unsplash.com/photo-1516979187457-637abb4f9353?w=400&q=80" 
    }, 
    { 
        "title": "Тіні забутих предків", 
        "author": "Михайло Коцюбинський", 
        "genre": "Класика", 
        "year": 1911, 
        "pages": 80, 
        "language": "Українська", 
        "description": "Повість про трагічне кохання гуцульських молодих людей Івана та 
Марічки. Поетична проза, пронизана карпатськими легендами та народними ввіруваннями.", 
        "cover": "https://images.unsplash.com/photo-1476275466078-4007374efbbe?w=400&q=80" 
    }, 
    { 
92 
        "title": "Злочин і кара", 
        "author": "Федір Достоєвський", 
        "genre": "Класика", 
        "year": 1866, 
        "pages": 592, 
        "language": "Українська", 
        "description": "Психологічний роман про студента Раскольнікова, який вчиняє вбивство і 
потім мучиться каяттям. Глибоке дослідження людської душі та моральності.", 
        "cover": "https://images.unsplash.com/photo-1512820790803-83ca734da794?w=400&q=80" 
    }, 
    { 
        "title": "Дюна", 
        "author": "Френк Герберт", 
        "genre": "Фантастика", 
        "year": 1965, 
        "pages": 688, 
        "language": "Українська", 
        "description": "Епічна наукова фантастика про пустельну планету Арракіс та молодого 
Пола Атрейдеса. Культовий роман про владу, релігію та екологію.", 
        "cover": "https://images.unsplash.com/photo-1446776653964-20c1d3a81b06?w=400&q=80" 
    }, 
    { 
        "title": "Аліса в Країні Чудес", 
        "author": "Льюїс Керрол", 
        "genre": "Казка", 
        "year": 1865, 
        "pages": 192, 
        "language": "Українська", 
        "description": "Казка про дівчинку Алісу, яка потрапляє у фантастичний світ через 
кролячу нору. Класика світової літератури, сповнена абсурду та мудрості.", 
        "cover": "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&q=80" 
    }, 
    { 
        "title": "Атлант розправив плечі", 
        "author": "Айн Ренд", 
        "genre": "Філософія", 
        "year": 1957, 
        "pages": 1168, 
93 
        "language": "Українська", 
        "description": "Монументальний роман про світ, де найкращі уми вирішують покинути 
суспільство. Гімн індивідуалізму, розуму та творчої свободи.", 
        "cover": "https://images.unsplash.com/photo-1519682337058-a94d519337bc?w=400&q=80" 
    }, 
    { 
        "title": "Сто років самотності", 
        "author": "Габріель Гарсія Маркес", 
        "genre": "Магічний реалізм", 
        "year": 1967, 
        "pages": 432, 
        "language": "Українська", 
        "description": "Сага про сім поколінь родини Буендіа в містечку Макондо. Шедевр 
магічного реалізму, Нобелівська премія з літератури.", 
        "cover": "https://images.unsplash.com/photo-1516383607781-913a19294fd1?w=400&q=80" 
    }, 
    { 
        "title": "Три мушкетери", 
        "author": "Александр Дюма", 
        "genre": "Пригоди", 
        "year": 1844, 
        "pages": 624, 
        "language": "Українська", 
        "description": "Захоплюючий пригодницький роман про молодого гасконця д'Артаньяна та 
його друзів-мушкетерів. Один за всіх і всі за одного!", 
        "cover": "https://images.unsplash.com/photo-1550399105-c4db5fb85c18?w=400&q=80" 
    }, 
    { 
        "title": "Місто", 
        "author": "Валер'ян Підмогильний", 
        "genre": "Класика", 
        "year": 1928, 
        "pages": 288, 
        "language": "Українська", 
        "description": "Роман про молодого хлопця зі села, який приїздить до Києва та 
намагається знайти себе у великому місті. Один із найкращих українських романів 20-х років.", 
        "cover": "https://images.unsplash.com/photo-1465929639680-64ee080eb3ed?w=400&q=80" 
    }, 
94 
    { 
        "title": "Гроздья гнева", 
        "author": "Джон Стейнбек", 
        "genre": "Класика", 
        "year": 1939, 
        "pages": 464, 
        "language": "Українська", 
        "description": "Пронизлива розповідь про родину Джоуд, яка рятується від посухи 
Великої депресії. Пулітцерівська премія, символ боротьби простих людей.", 
        "cover": "https://images.unsplash.com/photo-1455390582262-044cdead277a?w=400&q=80" 
    }, 
    { 
        "title": "Ім'я Рози", 
        "author": "Умберто Еко", 
        "genre": "Детектив", 
        "year": 1980, 
        "pages": 502, 
        "language": "Українська", 
        "description": "Інтелектуальний детектив, дія якого відбувається в середньовічному 
монастирі. Таємниця бібліотеки, серійні вбивства та філософські роздуми.", 
        "cover": "https://images.unsplash.com/photo-1457369804613-52c61a468e7d?w=400&q=80" 
    }, 
    { 
        "title": "Хоббіт", 
        "author": "Дж. Р. Р. Толкін", 
        "genre": "Фентезі", 
        "year": 1937, 
        "pages": 310, 
        "language": "Українська", 
        "description": "Казкова пригода хоббіта Більбо Торбинса, який разом із гномами та 
чарівником Гандальфом вирушає на пошуки скарбів дракона.", 
        "cover": "https://images.unsplash.com/photo-1518770660439-4636190af475?w=400&q=80" 
    }, 
    { 
        "title": "Процес", 
        "author": "Франц Кафка", 
        "genre": "Антиутопія", 
        "year": 1925, 
95 
        "pages": 256, 
        "language": "Українська", 
        "description": "Абсурдистський роман про Йозефа К., якого заарештовують без пояснення 
причин. Символ бюрократичного абсурду та відчуження людини.", 
        "cover": "https://images.unsplash.com/photo-1497633762265-9d179a990aa6?w=400&q=80" 
    }, 
    { 
        "title": "Думай і багатій", 
        "author": "Наполеон Хілл", 
        "genre": "Саморозвиток", 
        "year": 1937, 
        "pages": 320, 
        "language": "Українська", 
        "description": "Класика жанру саморозвитку. Книга про принципи досягнення успіху, 
засновані на вивченні найуспішніших людей Америки початку XX століття.", 
        "cover": "https://images.unsplash.com/photo-1434030216411-0b793f4b4173?w=400&q=80" 
    }, 
    { 
        "title": "Чума", 
        "author": "Альбер Камю", 
        "genre": "Філософія", 
        "year": 1947, 
        "pages": 352, 
        "language": "Українська", 
        "description": "Роман-притча про місто, охоплене епідемією чуми. Метафора людського 
існування, абсурду та солідарності перед обличчям смерті.", 
        "cover": "https://images.unsplash.com/photo-1509021436665-8f07dbf5bf1d?w=400&q=80" 
    }, 
    { 
        "title": "Лісова пісня", 
        "author": "Леся Українка", 
        "genre": "Поезія", 
        "year": 1912, 
        "pages": 112, 
        "language": "Українська", 
        "description": "Драма-феєрія про лісову мавку та її кохання до людини Лукаша. 
Поетичний шедевр, що розкриває конфлікт між духовним та матеріальним.", 
        "cover": "https://images.unsplash.com/photo-1448375240586-882707db888b?w=400&q=80" 
96 
    }, 
] 
def seed(): 
    init_db() 
    db = get_db() 
    c = db.cursor() 
    c.execute("SELECT COUNT(*) FROM books") 
    count = c.fetchone()[0] 
    if count > 0: 
        print(f"БД вже містить {count} книг. Пропускаємо seed.") 
        db.close() 
        return 
    for b in BOOKS: 
        c.execute( 
            "INSERT INTO books (title, author, genre, year, pages, description, cover, 
language) VALUES (?,?,?,?,?,?,?,?)", 
            (b["title"], b["author"], b["genre"], b["year"], b["pages"], b["description"], 
b["cover"], b["language"]) 
        ) 
    db.commit() 
    db.close() 
    print(f"Додано {len(BOOKS)} книг до бази.") 
if __name__ == "__main__": 
    seed() 
Лістинг – Файл store.js (реактивний стан додатку) 
const API = '/api'; 
const store = Vue.reactive({ 
  user: null, 
  showAuth: false, 
  authMode: 'login',  // 'login' | 'register' 
  toast: null, 
  toastTimer: null, 
  notify(msg, duration = 3000) { 
    store.toast = msg; 
    clearTimeout(store.toastTimer); 
    store.toastTimer = setTimeout(() => { store.toast = null; }, duration); 
  }, 
97 
  async fetchMe() { 
    try { 
      const r = await fetch(`${API}/auth/me`, { credentials: 'include' }); 
      const d = await r.json(); 
      store.user = d.user || null; 
    } catch { store.user = null; } 
  }, 
  async register(name, email, password) { 
    const r = await fetch(`${API}/auth/register`, { 
      method: 'POST', 
      credentials: 'include', 
      headers: { 'Content-Type': 'application/json' }, 
      body: JSON.stringify({ name, email, password }), 
    }); 
    const d = await r.json(); 
    if (d.error) { store.notify('❌ ' + d.error); return false; } 
    store.user = d.user; 
    store.showAuth = false; 
    store.notify('✅ Реєстрація успішна! Ласкаво просимо, ' + d.user.name); 
    return true; 
  },  
  async login(email, password) { 
    const r = await fetch(`${API}/auth/login`, { 
      method: 'POST', 
      credentials: 'include', 
      headers: { 'Content-Type': 'application/json' }, 
      body: JSON.stringify({ email, password }), 
    }); 
    const d = await r.json(); 
    if (d.error) { store.notify('❌ ' + d.error); return false; } 
    store.user = d.user; 
    store.showAuth = false; 
    store.notify('✅ З поверненням, ' + d.user.name + '!'); 
    return true; 
  }, 
98 
  async logout() { 
    await fetch(`${API}/auth/logout`, { method: 'POST', credentials: 'include' }); 
    store.user = null; 
    store.notify('�� До побачення!'); 
  }, 
  async getBooks(params = {}) { 
    const qs = new URLSearchParams(params).toString(); 
    const r = await fetch(`${API}/books?${qs}`, { credentials: 'include' }); 
    return r.json(); 
  }, 
  async getBook(id) { 
    const r = await fetch(`${API}/books/${id}`, { credentials: 'include' }); 
    return r.json(); 
  }, 
  async getGenres() { 
    const r = await fetch(`${API}/books/genres`); 
    return r.json(); 
  }, 
  async getCabinet() { 
    const r = await fetch(`${API}/cabinet`, { credentials: 'include' }); 
    return r.json(); 
  }, 
  
  async addBook(bookId) { 
    if (!store.user) { 
      store.showAuth = true; 
      store.authMode = 'register'; 
      store.notify('�� Зареєструйтесь щоб додавати книги'); 
      return false; 
    } 
    const r = await fetch(`${API}/cabinet/add/${bookId}`, { 
      method: 'POST', 
      credentials: 'include', 
    }); 
    const d = await r.json(); 
    if (d.error) { store.notify('�� ' + d.error); return false; } 
99 
    store.notify('�� Книгу додано до кабінету!'); 
    return true; 
  }, 
  async removeBook(bookId) { 
    const r = await fetch(`${API}/cabinet/remove/${bookId}`, { 
      method: 'DELETE', 
      credentials: 'include', 
    }); 
    const d = await r.json(); 
    if (d.ok) store.notify('�� Книгу видалено з кабінету'); 
    return d.ok; 
  }, 
}); 
Лістинг – Файл app.js (конфігурація маршрутизації) 
const { createApp } = Vue; 
const { createRouter, createWebHashHistory } = VueRouter; 
const router = createRouter({ 
  history: createWebHashHistory(), 
  routes: [ 
    { path: '/',         name: 'home',    component: HomePage }, 
    { path: '/book/:id', name: 'book',    component: BookDetailPage }, 
    { path: '/cabinet',  name: 'cabinet', component: CabinetPage }, 
    { path: '/read/:id', name: 'reading', component: ReadingPage }, 
    { path: '/:pathMatch(.*)*', redirect: '/' }, 
  ], 
  scrollBehavior: () => ({ top: 0 }), 
}); 
const ToastComp = { 
  data() { return { store }; }, 
  template: `<div v-if="store.toast" class="toast">{{ store.toast }}</div>` 
}; 
const app = createApp({ 
  components: { Navbar, AuthModal, ToastComp }, 
  data() { return { store }; }, 
  template: ` 
    <div class="min-h-screen flex flex-col"> 
100 
      <navbar></navbar> 
      <main class="flex-1"> 
        <router-view v-slot="{ Component }"> 
          <transition name="page" mode="out-in"> 
            <component :is="Component" /> 
          </transition> 
        </router-view> 
      </main> 
      <!-- Footer --> 
      <footer class="border-t border-lborder bg-white mt-16 py-8"> 
        <div class="max-w-7xl mx-auto px-6 flex flex-col md:flex-row items-center justify-
between gap-4"> 
          <div class="flex items-center gap-2"> 
            <span>��</span> 
            <span class="font-playfair font-semibold text-ltext">Бібліотека</span> 
          </div> 
          <p class="text-muted text-sm text-center"> 
            Flask + Vue 3 + SQLite 
          </p> 
          <p class="text-xs text-muted">© {{ new Date().getFullYear() }}</p> 
        </div> 
      </footer> 
  
      <auth-modal v-if="store.showAuth"></auth-modal> 
      <toast-comp></toast-comp> 
    </div> 
  `, 
  async created() { 
    await store.fetchMe(); 
  } 
}); 
app.use(router); 
app.mount('#app'); 
Лістинг – Файл Navbar.js (компонент навігаційної панелі) 
const Navbar = { 
  name: 'Navbar', 
101 
  data() { return { store }; }, 
  methods: { 
    go(name) { this.$router.push({ name }); }, 
    openAuth(mode) { store.authMode = mode; store.showAuth = true; }, 
  }, 
  computed: { 
    route() { return this.$route.name; } 
  }, 
  template: ` 
  <nav class="navbar"> 
    <div class="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between"> 
      <!-- Logo --> 
      <div @click="go('home')" class="cursor-pointer flex items-center gap-3 select-none"> 
        <span class="text-2xl">��</span> 
        <span class="font-playfair text-xl font-bold text-ltext">Бібліотека</span> 
      </div> 
      <!-- Nav links --> 
      <div class="hidden md:flex items-center gap-8"> 
        <span @click="go('home')" :class="['nav-link', 
route==='home'?'active':'']">Каталог</span> 
        <span v-if="store.user" @click="go('cabinet')" :class="['nav-link', 
route==='cabinet'?'active':'']"> 
          Мій кабінет 
        </span> 
      </div> 
      <!-- Auth --> 
      <div class="flex items-center gap-3"> 
        <template v-if="!store.user"> 
          <button @click="openAuth('login')" class="btn-ghost btn-sm">Увійти</button> 
          <button @click="openAuth('register')" class="btn-primary btn-sm">Реєстрація</button> 
        </template> 
        <template v-else> 
          <button @click="go('cabinet')" class="flex items-center gap-2 hover:opacity-75 
transition"> 
            <div class="w-8 h-8 rounded-full bg-cream2 border border-lborder flex items-center 
justify-center text-sm font-semibold text-brown"> 
              {{ store.user.name[0].toUpperCase() }} 
            </div> 
102 
            <span class="text-sm font-medium text-ltext2 hidden md:block">{{ store.user.name 
}}</span> 
          </button> 
          <button @click="store.logout()" class="btn-ghost btn-sm">Вийти</button> 
        </template> 
      </div> 
  
    </div> 
  </nav> 
  ` 
}; 
Лістинг – Файл AuthModal.js (компонент авторизації) 
const AuthModal = { 
  name: 'AuthModal', 
  data() { 
    return { 
      store, 
      form: { name: '', email: '', password: '', confirm: '' }, 
      loading: false, 
    }; 
  }, 
  watch: { 
    'store.authMode'() { 
      this.form = { name: '', email: '', password: '', confirm: '' }; 
    } 
  }, 
  methods: { 
    async submit() { 
      this.loading = true; 
      if (store.authMode === 'login') { 
        await store.login(this.form.email, this.form.password); 
      } else { 
        if (this.form.password !== this.form.confirm) { 
          store.notify('❌ Паролі не співпадають'); 
          this.loading = false; 
          return; 
103 
        } 
        await store.register(this.form.name, this.form.email, this.form.password); 
      } 
      this.loading = false; 
    } 
  }, 
  template: ` 
  <div class="modal-overlay" @click.self="store.showAuth = false"> 
    <div class="modal-box"> 
      <button @click="store.showAuth=false" 
        class="absolute top-4 right-4 text-muted hover:text-ltext text-xl w-8 h-8 flex items-
center justify-center rounded-full hover:bg-cream2 transition"> 
        ✕ 
      </button> 
      <!-- Logo --> 
      <div class="text-center mb-6"> 
        <span class="text-3xl">��</span> 
        <h2 class="font-playfair text-2xl font-bold text-ltext mt-2"> 
          {{ store.authMode === 'login' ? 'З поверненням!' : 'Реєстрація' }} 
        </h2> 
        <p class="text-muted text-sm mt-1"> 
          {{ store.authMode === 'login' ? 'Увійдіть у свій обліковий запис' : 'Створіть акаунт 
щоб читати книги' }} 
        </p> 
      </div> 
      <!-- Tabs --> 
      <div class="flex bg-cream2 rounded-10 p-1 mb-6 rounded-lg"> 
        <button @click="store.authMode='login'" 
          :class="['flex-1 py-2 text-sm font-semibold rounded-md transition', 
            store.authMode==='login' ? 'bg-white text-brown shadow-sm' : 'text-muted 
hover:text-ltext2']"> 
          Увійти 
        </button> 
        <button @click="store.authMode='register'" 
          :class="['flex-1 py-2 text-sm font-semibold rounded-md transition', 
            store.authMode==='register' ? 'bg-white text-brown shadow-sm' : 'text-muted 
hover:text-ltext2']"> 
          Реєстрація 
104 
        </button> 
      </div> 
      <!-- Form --> 
      <div class="space-y-4"> 
        <div v-if="store.authMode === 'register'"> 
          <label class="block text-xs font-semibold text-ltext2 uppercase tracking-wider mb-
1">Ім'я</label> 
          <input v-model="form.name" type="text" placeholder="Ваше ім'я" class="lib-input" 
            @keyup.enter="submit" /> 
        </div> 
        <div> 
          <label class="block text-xs font-semibold text-ltext2 uppercase tracking-wider mb-
1">Email</label> 
          <input v-model="form.email" type="email" placeholder="[email protected]" class="lib-
input" 
            @keyup.enter="submit" /> 
        </div> 
        <div> 
          <label class="block text-xs font-semibold text-ltext2 uppercase tracking-wider mb-
1">Пароль</label> 
          <input v-model="form.password" type="password" placeholder="••••••••" class="lib-
input" 
            @keyup.enter="submit" /> 
        </div> 
        <div v-if="store.authMode === 'register'"> 
          <label class="block text-xs font-semibold text-ltext2 uppercase tracking-wider mb-
1">Підтвердіть пароль</label> 
          <input v-model="form.confirm" type="password" placeholder="••••••••" class="lib-
input" 
            @keyup.enter="submit" /> 
        </div> 
        <button @click="submit" :disabled="loading" class="btn-primary w-full py-3 mt-2 text-
base"> 
          <span v-if="loading">...</span> 
          <span v-else>{{ store.authMode === 'login' ? 'Увійти' : 'Зареєструватись' }}</span> 
        </button> 
      </div> 
    </div> 
  </div> 
  ` 
105 
}; 
Лістинг – Файл BookCard.js (компонент картки книги) 
const BookCard = { 
  name: 'BookCard', 
  props: { book: Object }, 
  data() { return { store, saving: false }; }, 
  methods: { 
    goDetail() { 
      this.$router.push({ name: 'book', params: { id: this.book.id } }); 
    }, 
    async toggleSave(e) { 
      e.stopPropagation(); 
      if (this.saving) return; 
      this.saving = true; 
      if (this.book.saved) { 
        const ok = await store.removeBook(this.book.id); 
        if (ok) this.book.saved = false; 
      } else { 
        const ok = await store.addBook(this.book.id); 
        if (ok) this.book.saved = true; 
      } 
      this.saving = false; 
    } 
  }, 
  template: ` 
  <div class="book-card group" @click="goDetail"> 
    <!-- Cover --> 
    <div class="relative overflow-hidden" style="height:200px"> 
      <img :src="book.cover" :alt="book.title" class="book-cover w-full h-full" loading="lazy" 
        @error="$event.target.src='https://images.unsplash.com/photo-1481627834876-
b7833e8f5570?w=400&q=80'" /> 
      <div class="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent opacity-0 
group-hover:opacity-100 transition-opacity duration-300"></div> 
      <!-- Genre badge --> 
      <div class="absolute top-3 left-3"> 
        <span class="badge">{{ book.genre }}</span> 
      </div> 
106 
      <!-- Save button --> 
      <button @click="toggleSave" 
        :class="['absolute top-3 right-3 w-8 h-8 rounded-full flex items-center justify-center 
text-sm transition-all', 
          book.saved 
            ? 'bg-brown text-white shadow-md' 
            : 'bg-white/80 text-muted hover:bg-white hover:text-brown shadow']"> 
        {{ book.saved ? '��' : '+' }} 
      </button> 
    </div> 
    <!-- Info --> 
    <div class="p-4 flex flex-col flex-1"> 
      <h3 class="font-playfair font-semibold text-ltext text-base leading-snug mb-1 line-
clamp-2"> 
        {{ book.title }} 
      </h3> 
      <p class="text-muted text-sm mb-3">{{ book.author }}</p> 
      <div class="flex items-center gap-3 text-xs text-muted mt-auto pt-3 border-t border-
lborder"> 
        <span>�� {{ book.year }}</span> 
        <span>�� {{ book.pages }} стор.</span> 
      </div> 
    </div> 
  
  </div> 
  ` 
}; 
Лістинг – Файл FilterSidebar.js (компонент бічної панелі фільтрів) 
const FilterSidebar = { 
  name: 'FilterSidebar', 
  props: { 
    genres: Array, 
    modelValue: Object, 
  }, 
  emits: ['update:modelValue', 'change'], 
  computed: { 
    filters: { 
107 
      get() { return this.modelValue; }, 
      set(v) { this.$emit('update:modelValue', v); } 
    } 
  }, 
  methods: { 
    setGenre(g) { 
      this.$emit('update:modelValue', { ...this.modelValue, genre: g, page: 1 }); 
      this.$emit('change'); 
    }, 
    setSort(s) { 
      this.$emit('update:modelValue', { ...this.modelValue, sort: s, page: 1 }); 
      this.$emit('change'); 
    }, 
    reset() { 
      this.$emit('update:modelValue', { genre: '', sort: 'title', search: 
this.modelValue.search, page: 1 }); 
      this.$emit('change'); 
    } 
  }, 
  template: ` 
  <aside class="sidebar"> 
  
    <!-- Genres --> 
    <div class="sidebar-title">�� Жанри</div> 
    <div class="space-y-1 mb-6"> 
      <button @click="setGenre('')" 
        :class="['filter-option', !filters.genre ? 'active' : '']"> 
        <span>��</span> Всі жанри 
      </button> 
      <button v-for="g in genres" :key="g" @click="setGenre(g)" 
        :class="['filter-option', filters.genre === g ? 'active' : '']"> 
        <span>{{ genreIcon(g) }}</span> {{ g }} 
      </button> 
    </div> 
    <div class="divider mb-5"></div> 
    <!-- Sort --> 
108 
    <div class="sidebar-title">�� Сортування</div> 
    <div class="space-y-1 mb-6"> 
      <button v-for="s in sortOptions" :key="s.value" @click="setSort(s.value)" 
        :class="['filter-option', filters.sort === s.value ? 'active' : '']"> 
        {{ s.label }} 
      </button> 
    </div> 
    <div class="divider mb-5"></div> 
    <!-- Reset --> 
    <button @click="reset" class="btn-ghost w-full text-center"> 
      ✕ Скинути фільтри 
    </button> 
  </aside> 
  `, 
  data() { 
    return { 
      sortOptions: [ 
        { value: 'title',     label: '�� За назвою' }, 
        { value: 'author',    label: '�� За автором' }, 
        { value: 'year_desc', label: '�� Спочатку нові' }, 
        { value: 'year_asc',  label: '�� Спочатку старі' }, 
        { value: 'pages',     label: '�� За кількістю сторінок' }, 
      ] 
    }; 
  }, 
  methods: { 
    setGenre(g) { 
      this.$emit('update:modelValue', { ...this.modelValue, genre: g, page: 1 }); 
      this.$emit('change'); 
    }, 
    setSort(s) { 
      this.$emit('update:modelValue', { ...this.modelValue, sort: s, page: 1 }); 
      this.$emit('change'); 
    }, 
    reset() { 
109 
      this.$emit('update:modelValue', { genre: '', sort: 'title', search: 
this.modelValue.search, page: 1 }); 
      this.$emit('change'); 
    }, 
    genreIcon(g) { 
      const icons = { 
        'Класика': '��', 'Антиутопія': '��', 'Казка': '��', 'Фентезі': '��', 
        'Поезія': '��', 'Фантастика': '��', 'Детектив': '��', 'Пригоди': '��', 
        'Філософія': '��', 'Саморозвиток': '��', 'Магічний реалізм': '✨', 
      }; 
      return icons[g] || '��'; 
    } 
  } 
}; 
Лістинг – Файл Home.js (головна сторінка) 
const HomePage = { 
  name: 'HomePage', 
  components: { BookCard, FilterSidebar }, 
  data() { 
    return { 
      store, 
      books: [], 
      genres: [], 
      total: 0, 
      totalPages: 1, 
      loading: false, 
      searchInput: '', 
      searchTimer: null, 
      filters: { 
        search: '', 
        genre: '', 
        sort: 'title', 
        page: 1, 
      }, 
    }; 
  }, 
110 
  async mounted() { 
    this.genres = await store.getGenres(); 
    await this.loadBooks(); 
  }, 
  watch: { 
    searchInput(val) { 
      clearTimeout(this.searchTimer); 
      this.searchTimer = setTimeout(() => { 
        this.filters.search = val; 
        this.filters.page = 1; 
        this.loadBooks(); 
      }, 350); 
    } 
  }, 
  methods: { 
    async loadBooks() { 
      this.loading = true; 
      try { 
        const data = await store.getBooks({ 
          search: this.filters.search, 
          genre:  this.filters.genre, 
          sort:   this.filters.sort, 
          page:   this.filters.page, 
        }); 
        this.books      = data.books || []; 
        this.total      = data.total || 0; 
        this.totalPages = data.pages || 1; 
      } finally { 
        this.loading = false; 
      } 
    }, 
    onFilterChange() { 
      this.loadBooks(); 
    }, 
    setPage(p) { 
      if (p < 1 || p > this.totalPages) return; 
111 
      this.filters.page = p; 
      this.loadBooks(); 
      window.scrollTo({ top: 0, behavior: 'smooth' }); 
    }, 
  }, 
  computed: { 
    pages() { 
      return Array.from({ length: this.totalPages }, (_, i) => i + 1); 
    } 
  }, 
  template: ` 
  <div class="max-w-7xl mx-auto px-6 py-8"> 
    <!-- Header --> 
    <div class="mb-8"> 
      <p class="section-label">Ласкаво просимо</p> 
      <h1 class="font-playfair text-4xl font-bold text-ltext mb-2">Бібліотека</h1> 
      <p class="text-muted text-base">Знайдіть свою наступну улюблену книгу</p> 
    </div> 
    <!-- Search bar --> 
    <div class="relative mb-8"> 
      <span class="absolute left-4 top-1/2 -translate-y-1/2 text-muted text-lg">��</span> 
      <input 
        v-model="searchInput" 
        type="text" 
        placeholder="Пошук за назвою або автором..." 
        class="lib-input pl-12 py-3 text-base shadow-sm" 
        style="border-radius:12px" 
      /> 
      <button v-if="searchInput" @click="searchInput=''" 
        class="absolute right-4 top-1/2 -translate-y-1/2 text-muted hover:text-ltext text-lg 
transition"> 
        ✕ 
      </button> 
    </div> 
    <div class="flex gap-7"> 
      <!-- Sidebar --> 
112 
      <div class="hidden lg:block" style="width:230px;flex-shrink:0"> 
        <filter-sidebar 
          :genres="genres" 
          v-model="filters" 
          @change="onFilterChange" 
        ></filter-sidebar> 
      </div> 
      <!-- Main content --> 
      <div class="flex-1 min-w-0"> 
        <!-- Results info --> 
        <div class="flex items-center justify-between mb-5"> 
          <p class="text-muted text-sm"> 
            <span class="font-semibold text-ltext2">{{ total }}</span> книг знайдено 
            <span v-if="filters.genre"> у жанрі «{{ filters.genre }}»</span> 
            <span v-if="filters.search"> за запитом «{{ filters.search }}»</span> 
          </p> 
          <!-- Mobile sort --> 
          <select v-model="filters.sort" @change="onFilterChange" class="lib-select lg:hidden" 
style="width:auto"> 
            <option value="title">За назвою</option> 
            <option value="author">За автором</option> 
            <option value="year_desc">Спочатку нові</option> 
            <option value="year_asc">Спочатку старі</option> 
          </select> 
        </div> 
        <!-- Loading --> 
        <div v-if="loading" class="grid grid-cols-3 gap-5"> 
          <div v-for="i in 9" :key="i" 
            class="bg-white rounded-xl border border-lborder overflow-hidden animate-pulse"> 
            <div class="bg-cream3" style="height:200px"></div> 
            <div class="p-4 space-y-2"> 
              <div class="bg-cream3 rounded h-4 w-3/4"></div> 
              <div class="bg-cream3 rounded h-3 w-1/2"></div> 
            </div> 
          </div> 
        </div> 
        <!-- Empty --> 
113 
        <div v-else-if="books.length === 0" class="empty-state"> 
          <div class="empty-icon">��</div> 
          <h3 class="font-playfair text-xl font-semibold text-ltext mb-2">Нічого не 
знайдено</h3> 
          <p class="text-muted mb-4">Спробуйте змінити параметри пошуку</p> 
          <button @click="searchInput=''; filters.genre=''; filters.sort='title'; loadBooks()" 
class="btn-outline"> 
            Скинути пошук 
          </button> 
        </div> 
        <!-- Grid --> 
        <div v-else class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5"> 
          <book-card v-for="book in books" :key="book.id" :book="book"></book-card> 
        </div> 
        <!-- Pagination --> 
        <div v-if="totalPages > 1" class="flex items-center justify-center gap-2 mt-10"> 
          <button @click="setPage(filters.page - 1)" :disabled="filters.page === 1" 
class="page-btn">←</button> 
          <button v-for="p in pages" :key="p" @click="setPage(p)" 
            :class="['page-btn', p === filters.page ? 'active' : '']"> 
            {{ p }} 
          </button> 
          <button @click="setPage(filters.page + 1)" :disabled="filters.page === totalPages" 
class="page-btn">-</button> 
        </div> 
      </div> 
    </div> 
  </div> 
  ` 
}; 
Лістинг – Файл BookDetail.js (сторінка деталей книги) 
const BookDetailPage = { 
  name: 'BookDetailPage', 
  data() { 
    return { store, book: null, loading: true, saving: false }; 
  }, 
  async mounted() { 
    window.scrollTo(0, 0); 
114 
    await this.loadBook(); 
  }, 
  watch: { 
    '$route.params.id': 'loadBook' 
  }, 
  methods: { 
    async loadBook() { 
      this.loading = true; 
      this.book = await store.getBook(this.$route.params.id); 
      this.loading = false; 
    }, 
    async toggleSave() { 
      if (this.saving) return; 
      this.saving = true; 
      if (this.book.saved) { 
        const ok = await store.removeBook(this.book.id); 
        if (ok) this.book.saved = false; 
      } else { 
        const ok = await store.addBook(this.book.id); 
        if (ok) this.book.saved = true; 
      } 
      this.saving = false; 
    }, 
    readBook() { 
      if (!store.user) { 
        store.showAuth = true; 
        store.authMode = 'register'; 
        store.notify('�� Зареєструйтесь щоб читати книги'); 
        return; 
      } 
      if (!this.book.saved) { 
        store.notify('�� Спочатку додайте книгу до свого кабінету'); 
        return; 
      } 
      this.$router.push({ name: 'reading', params: { id: this.book.id } }); 
    } 
115 
  }, 
  template: ` 
  <div class="max-w-5xl mx-auto px-6 py-10"> 
    <!-- Breadcrumb --> 
    <div class="flex items-center gap-2 text-sm text-muted mb-8"> 
      <span @click="$router.push({name:'home'})" class="hover:text-brown cursor-
pointer">Каталог</span> 
      <span>›</span> 
      <span class="text-ltext2" v-if="book">{{ book.title }}</span> 
    </div> 
    <!-- Loading skeleton --> 
    <div v-if="loading" class="flex gap-10 animate-pulse"> 
      <div class="bg-cream3 rounded-xl flex-shrink-0" style="width:260px;height:380px"></div> 
      <div class="flex-1 space-y-4"> 
        <div class="bg-cream3 rounded h-8 w-3/4"></div> 
        <div class="bg-cream3 rounded h-5 w-1/2"></div> 
        <div class="bg-cream3 rounded h-4 w-full"></div> 
        <div class="bg-cream3 rounded h-4 w-full"></div> 
        <div class="bg-cream3 rounded h-4 w-2/3"></div> 
      </div> 
    </div> 
  
    <!-- Book --> 
    <div v-else-if="book" class="flex flex-col md:flex-row gap-10"> 
      <!-- Cover --> 
      <div class="flex-shrink-0"> 
        <div class="relative" style="width:260px"> 
          <img :src="book.cover" :alt="book.title" 
            class="w-full rounded-xl shadow-lg object-cover" 
            style="height:380px" 
            @error="$event.target.src='https://images.unsplash.com/photo-1481627834876-
b7833e8f5570?w=400&q=80'" /> 
          <div class="absolute top-3 left-3"> 
            <span class="badge">{{ book.genre }}</span> 
          </div> 
        </div> 
        <!-- Actions --> 
116 
        <div class="mt-4 space-y-3" style="width:260px"> 
          <button @click="readBook" 
            :disabled="!book.saved" 
            :class="['btn-primary w-full py-3 text-base', !book.saved ? 'opacity-50 cursor-
not-allowed' : '']"> 
            �� Читати 
          </button> 
          <button @click="toggleSave" :disabled="saving" 
            :class="['w-full py-3 text-sm font-semibold rounded-lg border transition', 
              book.saved 
                ? 'bg-cream2 border-lborder text-brown hover:bg-cream3' 
                : 'btn-outline']"> 
            {{ saving ? '...' : book.saved ? '�� У вашому кабінеті' : '+ Додати до кабінету' 
}} 
          </button> 
          <p v-if="!book.saved" class="text-center text-xs text-muted"> 
            Додайте книгу до кабінету щоб читати 
          </p> 
        </div> 
      </div> 
      <!-- Info --> 
      <div class="flex-1"> 
        <h1 class="font-playfair text-3xl font-bold text-ltext mb-2 leading-tight">{{ 
book.title }}</h1> 
        <p class="text-lg text-brown font-medium mb-6">{{ book.author }}</p> 
        <div class="divider mb-6"></div> 
        <!-- Specs grid --> 
        <div class="grid grid-cols-2 gap-4 mb-6"> 
          <div class="bg-white border border-lborder rounded-xl p-4 text-center"> 
            <div class="text-2xl mb-1">��</div> 
            <div class="text-lg font-semibold text-ltext">{{ book.year }}</div> 
            <div class="text-xs text-muted">Рік видання</div> 
          </div> 
          <div class="bg-white border border-lborder rounded-xl p-4 text-center"> 
            <div class="text-2xl mb-1">��</div> 
            <div class="text-lg font-semibold text-ltext">{{ book.pages }}</div> 
            <div class="text-xs text-muted">Сторінок</div> 
117 
          </div> 
          <div class="bg-white border border-lborder rounded-xl p-4 text-center"> 
            <div class="text-2xl mb-1">��</div> 
            <div class="text-lg font-semibold text-ltext">{{ book.language }}</div> 
            <div class="text-xs text-muted">Мова</div> 
          </div> 
          <div class="bg-white border border-lborder rounded-xl p-4 text-center"> 
            <div class="text-2xl mb-1">��</div> 
            <div class="text-lg font-semibold text-ltext">{{ book.genre }}</div> 
            <div class="text-xs text-muted">Жанр</div> 
          </div> 
        </div> 
        <!-- Description --> 
        <div> 
          <h3 class="font-playfair text-lg font-semibold text-ltext mb-3">Про книгу</h3> 
          <p class="text-ltext2 leading-relaxed text-base">{{ book.description }}</p> 
        </div> 
      </div> 
    </div> 
    <!-- 404 --> 
    <div v-else class="empty-state"> 
      <div class="empty-icon">��</div> 
      <h3 class="font-playfair text-xl font-semibold text-ltext mb-2">Книгу не знайдено</h3> 
      <button @click="$router.push({name:'home'})" class="btn-primary mt-4">До 
каталогу</button> 
    </div> 
  </div> 
  ` 
}; 
Лістинг – Файл Cabinet.js (сторінка кабінету) 
const CabinetPage = { 
  name: 'CabinetPage', 
  components: { BookCard }, 
  data() { 
    return { store, books: [], loading: true }; 
  }, 
118 
  async mounted() { 
    window.scrollTo(0, 0); 
    if (!store.user) { 
      this.$router.push({ name: 'home' }); 
      return; 
    } 
    await this.loadBooks(); 
  }, 
  methods: { 
    async loadBooks() { 
      this.loading = true; 
      this.books = await store.getCabinet(); 
      this.loading = false; 
    }, 
    async removeBook(book) { 
      const ok = await store.removeBook(book.id); 
      if (ok) this.books = this.books.filter(b => b.id !== book.id); 
    }, 
    readBook(book) { 
      this.$router.push({ name: 'reading', params: { id: book.id } }); 
    } 
  }, 
  template: ` 
  <div class="max-w-6xl mx-auto px-6 py-10"> 
    <!-- Header --> 
    <div class="flex items-center gap-4 mb-8"> 
      <div class="w-14 h-14 rounded-full bg-cream2 border border-lborder flex items-center 
justify-center text-xl font-bold text-brown font-playfair"> 
        {{ store.user ? store.user.name[0].toUpperCase() : '?' }} 
      </div> 
      <div> 
        <p class="section-label">Особистий кабінет</p> 
        <h1 class="font-playfair text-3xl font-bold text-ltext">{{ store.user ? 
store.user.name : '' }}</h1> 
        <p class="text-muted text-sm">{{ store.user ? store.user.email : '' }}</p> 
      </div> 
    </div> 
119 
    <div class="divider mb-8"></div> 
    <!-- Stats --> 
    <div class="grid grid-cols-3 gap-4 mb-10"> 
      <div class="bg-white border border-lborder rounded-xl p-5 text-center"> 
        <div class="text-3xl font-playfair font-bold text-brown mb-1">{{ books.length }}</div> 
        <div class="text-sm text-muted">Книг у кабінеті</div> 
      </div> 
      <div class="bg-white border border-lborder rounded-xl p-5 text-center"> 
        <div class="text-3xl font-playfair font-bold text-brown mb-1"> 
          {{ books.reduce((s, b) => s + b.pages, 0).toLocaleString() }} 
        </div> 
        <div class="text-sm text-muted">Сторінок загалом</div> 
      </div> 
      <div class="bg-white border border-lborder rounded-xl p-5 text-center"> 
        <div class="text-3xl font-playfair font-bold text-brown mb-1"> 
          {{ new Set(books.map(b => b.genre)).size }} 
        </div> 
        <div class="text-sm text-muted">Жанрів</div> 
      </div> 
    </div> 
    <!-- Books section --> 
    <div class="flex items-center justify-between mb-6"> 
      <h2 class="font-playfair text-xl font-semibold text-ltext">Мої книги</h2> 
      <button @click="$router.push({name:'home'})" class="btn-outline btn-sm"> 
        + Додати книги 
      </button> 
    </div> 
    <!-- Loading --> 
    <div v-if="loading" class="grid grid-cols-3 gap-5"> 
      <div v-for="i in 3" :key="i" class="bg-white rounded-xl border border-lborder overflow-
hidden animate-pulse"> 
        <div class="bg-cream3" style="height:200px"></div> 
        <div class="p-4 space-y-2"> 
          <div class="bg-cream3 rounded h-4 w-3/4"></div> 
          <div class="bg-cream3 rounded h-3 w-1/2"></div> 
        </div> 
      </div> 
120 
    </div> 
    <!-- Empty --> 
    <div v-else-if="books.length === 0" class="empty-state bg-white border border-lborder 
rounded-2xl"> 
      <div class="empty-icon">��</div> 
      <h3 class="font-playfair text-xl font-semibold text-ltext mb-2">Кабінет порожній</h3> 
      <p class="text-muted mb-5">Додайте книги з каталогу щоб читати їх тут</p> 
      <button @click="$router.push({name:'home'})" class="btn-primary">До каталогу</button> 
    </div> 
    <!-- Books grid --> 
    <div v-else class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5"> 
      <div v-for="book in books" :key="book.id" class="book-card group relative"> 
        <!-- Cover --> 
        <div class="relative overflow-hidden" style="height:200px"> 
          <img :src="book.cover" :alt="book.title" 
            class="book-cover w-full h-full" 
            @error="$event.target.src='https://images.unsplash.com/photo-1481627834876-
b7833e8f5570?w=400&q=80'" /> 
          <div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-all 
duration-300 flex items-center justify-center"> 
            <button @click="readBook(book)" 
              class="opacity-0 group-hover:opacity-100 transition-all duration-300 btn-primary 
transform scale-90 group-hover:scale-100"> 
              �� Читати 
            </button> 
          </div> 
          <div class="absolute top-3 left-3"> 
            <span class="badge">{{ book.genre }}</span> 
          </div> 
          <!-- Remove button --> 
          <button @click.stop="removeBook(book)" 
            class="absolute top-3 right-3 w-7 h-7 rounded-full bg-white/80 hover:bg-red-50 
text-muted hover:text-red-500 flex items-center justify-center text-sm transition"> 
            ✕ 
          </button> 
        </div> 
        <!-- Info --> 
        <div class="p-4 flex flex-col flex-1" @click="$router.push({name:'book', 
params:{id:book.id}})"> 
121 
          <h3 class="font-playfair font-semibold text-ltext text-base leading-snug mb-1 line-
clamp-2"> 
            {{ book.title }} 
          </h3> 
          <p class="text-muted text-sm mb-3">{{ book.author }}</p> 
          <div class="flex items-center gap-3 text-xs text-muted mt-auto pt-3 border-t border-
lborder"> 
            <span>�� {{ book.year }}</span> 
            <span>�� {{ book.pages }} стор.</span> 
          </div> 
        </div> 
      </div> 
    </div> 
  </div> 
  ` 
}; 
Лістинг – Файл Reading.js (сторінка читання) 
const ReadingPage = { 
  name: 'ReadingPage', 
  data() { 
    return { 
      store, 
      book: null, 
      loading: true, 
      readingPage: 1, 
      totalReadingPages: 12, 
    }; 
  }, 
  async mounted() { 
    window.scrollTo(0, 0); 
    if (!store.user) { 
      this.$router.push({ name: 'home' }); 
      return; 
    } 
    this.book = await store.getBook(this.$route.params.id); 
    if (!this.book || !this.book.saved) { 
122 
      store.notify('❌ Ця книга недоступна для читання'); 
      this.$router.push({ name: 'home' }); 
      return; 
    } 
    this.loading = false; 
  }, 
  computed: { 
    fakeText() { 
      const paragraphs = [ 
        `Сторінка ${this.readingPage}. Текст книги "${this.book?.title}" автора 
${this.book?.author}.`, 
        `Це демонстраційна версія читача. У повній версії тут відображався б реальний текст 
книги, розбитий на сторінки для зручного читання.`, 
        `Читач підтримує навігацію по сторінках, закладки та налаштування шрифту. Всі ці 
функції можна реалізувати у майбутніх версіях проекту.`, 
        `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor 
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud 
exercitation ullamco laboris.`, 
        `Nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in 
voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat 
non proident.`, 
        `Sunt in culpa qui officia deserunt mollit anim id est laborum. Sed perspiciatis unde 
omnis iste natus error sit voluptatem accusantium doloremque laudantium.`, 
        `Totam rem aperiam eaque ipsa quae ab illo inventore veritatis et quasi architecto 
beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut 
odit aut fugit.`, 
        `Sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque 
porro quisquam est qui dolorem ipsum quia dolor sit amet consectetur adipisci velit.`, 
      ]; 
      return paragraphs; 
    }, 
    progress() { 
      return Math.round((this.readingPage / this.totalReadingPages) * 100); 
    } 
  }, 
  methods: { 
    prevPage() { if (this.readingPage > 1) { this.readingPage--; window.scrollTo(0,0); } }, 
    nextPage() { if (this.readingPage < this.totalReadingPages) { this.readingPage++; 
window.scrollTo(0,0); } }, 
  }, 
  template: ` 
  <div v-if="!loading && book" class="max-w-4xl mx-auto px-6 py-8"> 
123 
    <!-- Header bar --> 
    <div class="flex items-center justify-between mb-6"> 
      <button @click="$router.push({name:'book', params:{id:book.id}})" 
        class="btn-ghost btn-sm flex items-center gap-2"> 
        ← До книги 
      </button> 
      <div class="text-center hidden md:block"> 
        <p class="font-playfair font-semibold text-ltext text-sm">{{ book.title }}</p> 
        <p class="text-muted text-xs">{{ book.author }}</p> 
      </div> 
      <div class="text-sm text-muted"> 
        Сторінка {{ readingPage }} / {{ totalReadingPages }} 
      </div> 
    </div> 
    <!-- Progress bar --> 
    <div class="bg-cream3 rounded-full h-1.5 mb-8"> 
      <div class="bg-brown rounded-full h-1.5 transition-all duration-500" 
        :style="{ width: progress + '%' }"></div> 
    </div> 
    <!-- Reading area --> 
    <div class="reading-area"> 
      <h2 class="font-playfair text-xl font-semibold text-ltext mb-6 pb-4 border-b border-
lborder"> 
        Розділ {{ readingPage }} 
      </h2> 
      <p v-for="(para, i) in fakeText" :key="i">{{ para }}</p> 
      <!-- Stub notice --> 
      <div class="mt-8 p-5 bg-cream2 border border-lborder rounded-xl text-center"> 
        <p class="text-2xl mb-2">��</p> 
        <p class="text-sm text-muted leading-relaxed"> 
          Демо версія книги. 
        </p> 
      </div> 
    </div> 
    <!-- Navigation --> 
    <div class="flex items-center justify-between mt-8"> 
      <button @click="prevPage" :disabled="readingPage === 1" class="btn-outline"> 
124 
        ← Попередня 
      </button> 
      <div class="flex items-center gap-1"> 
        <button v-for="p in totalReadingPages" :key="p" @click="readingPage=p; 
window.scrollTo(0,0)" 
          :class="['page-btn text-xs', p === readingPage ? 'active' : '']" 
          style="width:28px;height:28px"> 
          {{ p }} 
        </button> 
      </div> 
      <button @click="nextPage" :disabled="readingPage === totalReadingPages" class="btn-
primary"> 
        Наступна - 
      </button> 
    </div> 
  </div> 
  <div v-else-if="loading" class="max-w-4xl mx-auto px-6 py-20 text-center"> 
    <p class="text-muted">Завантаження...</p> 
  </div> 
  ` 
}; 
  
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125 
ДОДАТОК В 
 
 
 
 
 
 
Інформаційний сайт бібліотеки 
 
 
 
 
Інструкція користувачеві  
482. ЧДТУ 62296    34 01 
Листів 6 
 
 
 
 
 
 
Розробник                          ____________________                Ілля ЧЕРНИШЕНКО 
 
Керівник                             ____________________                Петро ДЯЧЕНКО 
 
 
 
 
 
 
 
 
 
 
Черкаси – 2026 
126 
1. Призначення та умови застосування 
Веб-додаток електронної бібліотеки призначений для каталогізації та 
перегляду книг через мережу Інтернет з можливістю реєстрації користувачів, 
пошуку та фільтрації каталогу, формування персонального кабінету та читання 
книг. 
Мінімальні системні вимоги для роботи з додатком: 
- комп'ютер або мобільний пристрій з доступом до мережі Інтернет; 
 - веб-браузер: Google Chrome (версії 90 і вище), Mozilla Firefox (версії 88 і 
вище), Safari (версії 14 і вище) або Microsoft Edge (версії 90 і вище); 
- роздільна здатність екрану не менше 320 пікселів по ширині; 
- доступ до сервера, на якому розгорнуто додаток (за замовчуванням - 
http://127.0.0.1:5000). 
Для розгортання додатку на сервері необхідно: 
- операційна система: Windows 10/11, macOS 12+, або Linux (Ubuntu 20.04+); 
- мова програмування Python версії 3.8 і вище; 
 - менеджер пакетів pip; 
- встановлені бібліотеки: Flask, Flask-CORS. 
Для встановлення залежностей необхідно виконати команду pip install flask 
flask-cors. 
Для запуску серверної частини необхідно виконати команду python app.py. 
Після запуску додаток доступний за адресою http://127.0.0.1:5000. 
2. Реєстрація та авторизація 
Для отримання повного доступу до функцій додатку необхідно 
зареєструватися. 
Реєстрація нового користувача: 
1) відкрити головну сторінку додатку у веб-браузері; 
2) натиснути кнопку «Увійти» або «Зареєструватись» у правому верхньому 
куті навігаційної панелі; 
3) у модальному вікні обрати вкладку «Реєстрація»; 
127 
4) заповнити поля: «Ім'я» – ім'я користувача (обов'язкове поле); «Email» – 
електронна пошта (обов'язкове поле, унікальне для кожного користувача); «Пароль» 
– пароль (обов'язкове поле); «Підтвердіть пароль» – повторне введення пароля 
(обов'язкове поле); 
5) натиснути кнопку «Зареєструватись»; 
6) при успішній реєстрації відобразиться сповіщення «Реєстрація успішна! 
Ласкаво просимо, [ім'я]»; 
7) при помилці (порожні поля, неспівпадіння паролів, зайнятий email) 
відобразиться сповіщення з текстом помилки. 
Авторизація зареєстрованого користувача: 
1) натиснути кнопку «Увійти» у навігаційній панелі; 
2) у модальному вікні обрати вкладку «Увійти»; 
3) заповнити поля: «Email» та «Пароль»; 
 4) натиснути кнопку «Увійти»; 
5) при успішній авторизації відобразиться сповіщення «З поверненням, 
[ім'я]!» і навігаційна панель оновиться: замість кнопки «Увійти» з'явиться ім'я 
користувача та кнопка «Кабінет»; 
6) при помилці (невірний email або пароль) відобразиться сповіщення 
«Невірний email або пароль». 
Вихід із системи: 
1) натиснути кнопку виходу (іконка біля імені користувача у навігаційній 
панелі); 
2) відобразиться сповіщення «До побачення!»; 
3) навігаційна панель оновиться: кнопки «Увійти» та «Зареєструватись» знову 
стануть доступними. 
3. Перегляд каталогу книг 
Головна сторінка додатку відображає каталог книг у вигляді сітки карток (3 
стовпці на десктопі, 2 на планшеті, 1 на мобільному пристрої). Кожна картка містить 
128 
зображення обкладинки, назву книги, ім'я автора, жанр, рік видання та кількість 
сторінок. 
Пошук книг: 
1) розташувати курсор у полі пошуку у верхній частині сторінки; 
2) ввести назву книги або ім'я автора; 
3) результати пошуку оновлюються автоматично через 350 мс після зупинки 
введення; 
4) для очищення поля пошуку натиснути кнопку «х» у правій частині поля; 
5) під полем пошуку відображається кількість знайдених книг та параметри 
фільтрації. 
Фільтрація за жанром: 
1) у бічній панелі (на екранах шириною від 1024 px) обрати жанр зі списку; 
2) каталог оновиться, відобразяться лише книги обраного жанру; 
3) під полем пошуку відобразиться текст «у жанрі [назва жанру]». 
Сортування: 
1) у бічній панелі (на десктопі) або у випадаючому списку (на мобільних 
пристроях) обрати тип сортування: 
   - «За назвою» - сортування за назвою книги за алфавітом; 
   - «За автором» - сортування за іменем автора за алфавітом; 
   - «Спочатку нові» - сортування за роком видання за спаданням; 
   - «Спочатку старі» - сортування за роком видання за зростанням; 
   - «За сторінками» - сортування за кількістю сторінок за зростанням; 
2) каталог оновиться з обраним типом сортування. 
Посторінкова навігація: 
1) під сіткою книг розміщуються кнопки з номерами сторінок; 
2) поточна сторінка виділена кольором; 
3) для переходу на наступну або попередню сторінку натиснути кнопки 
«стрілка вправо» або «стрілка вліво»; 
4) на кожній сторінці відображається до 9 книг. 
4. Перегляд деталей книги 
129 
Для перегляду детальної інформації про книгу: 
1) натиснути на картку книги в каталозі; 
2) відкриється сторінка з детальною інформацією: обкладинка, назва, автор, 
рік видання, кількість сторінок, мова, жанр, опис; 
3) на сторінці розміщені кнопки «Читати» та «Додати до кабінету» / «У 
вашому кабінеті»; 
4) навігаційний ланцюжок «Каталог > Назва книги» дозволяє повернутися до 
каталогу; 
5) для незареєстрованих користувачів кнопка «Додати до кабінету» пропонує 
спочатку зареєструватися. 
5. Персональний кабінет 
Персональний кабінет доступний лише авторизованим користувачам. 
Додавання книги до кабінету: 
1) відкрити сторінку деталей книги; 
2) натиснути кнопку «Додати до кабінету»; 
3) при успіху відобразиться сповіщення «Книгу додано до кабінету!»; 
4) кнопка зміниться на «У вашому кабінеті» (неактивна для повторного 
додавання); 
5) при спробі додати книгу, що вже є в кабінеті, відобразиться сповіщення 
«Книга вже в кабінеті». 
Перегляд кабінету: 
1) натиснути кнопку «Кабінет» у навігаційній панелі; 
2) відкриється сторінка кабінету, що містить: 
   - аватар користувача з ініціалами; 
   - ім'я та email користувача; 
   - блок статистики: кількість книг у кабінеті, загальна кількість сторінок, 
кількість жанрів; 
   - сітку збережених книг з кнопками «Читати» (при наведенні) та «Видалити» 
(у правому верхньому куті); 
130 
3) за відсутності книг у кабінеті відображається повідомлення «Кабінет 
порожній» з кнопкою «До каталогу». 
Видалення книги з кабінету: 
1) у кабінеті натиснути кнопку «Видалити» (іконка «х») на картці книги; 
2) книга зникає зі списку кабінету; 
3) відобразиться сповіщення «Книгу видалено з кабінету». 
6. Читання книги 
Читання книг доступне лише для авторизованих користувачів, які додали 
книгу до кабінету. 
Початок читання: 
1) у кабінеті або на сторінці деталей книги натиснути кнопку «Читати»; 
2) відкриється сторінка читання з: 
  - заголовком книги та іменем автора; 
  - індикатором прогресу (процентна шкала); 
 - текстом книги; 
   - навігаційними кнопками «Попередня» / «Наступна»; 
   - кнопками з номерами сторінок для прямого переходу; 
   - кнопкою «До книги» для повернення на сторінку деталей; 
3) при наведенні на картку книги в каталозі з'являється кнопка «Читати». 
Навігація під час читання: 
1) для переходу на наступну сторінку натиснути кнопку «Наступна» або 
відповідну кнопку з номером сторінки; 
2) для переходу на попередню сторінку натиснути кнопку «Попередня»; 
3) індикатор прогресу оновлюється автоматично при переході між сторінками; 
4) для повернення до сторінки деталей книги натиснути кнопку «До книги». 
 
7. Повідомлення про помилки 
131 
Система інформує користувача про результати дій за допомогою сповіщень 
(toast-повідомлень), які відображаються у правому верхньому куті екрана і 
автоматично зникають через 3 секунди. Можливі сповіщення: 
 - «Реєстрація успішна! Ласкаво просимо, [ім'я]» – при успішній реєстрації; 
- «З поверненням, [ім'я]!» – при успішній авторизації; 
- «До побачення!» – при виході з системи; 
- «Книгу додано до кабінету!» – при успішному додаванні книги; 
- «Книгу видалено з кабінету» – при успішному видаленні книги; 
- «Книга вже в кабінеті» – при спробі повторного додавання; 
- «Заповніть всі поля» – при порожніх полях реєстрації; 
- «Email вже зареєстровано» – при спробі реєстрації з існуючим email; 
- «Невірний email або пароль» – при невірних облікових даних; 
- «Зареєструйтесь щоб читати книги» – при спробі читання без авторизації; 
- «Спочатку додайте книгу до свого кабінету» – при спробі читання книги, що 
не в кабінеті. 
 
132