Please use this identifier to cite or link to this item: https://er.chdtu.edu.ua/handle/ChSTU/6030
Full metadata record
DC FieldValueLanguage
dc.contributor.advisorОксамитна, Любов Павлівна-
dc.contributor.authorДащук, Олег Віталійович-
dc.date.accessioned2025-11-13T15:47:59Z-
dc.date.available2025-11-13T15:47:59Z-
dc.date.issued2025-06-11-
dc.identifier.urihttps://er.chdtu.edu.ua/handle/ChSTU/6030-
dc.description.abstractАктуальність теми полягає у тому, що на сьогоднішній день сфера оренди житла посідає одне з найважливіших місць у житті людей. Це зумовлено постійною потребою у комфортному та безпечному помешканні, чи то для постійного проживання, чи для тимчасового перебування. Ефективна організація процесу оренди житла є надзвичайно важливою, адже вона впливає на добробут та стабільність життя людини. Метою кваліфікаційної роботи бакалавра є розробка та впровадження сучасної веб-платформи для автоматизації та оптимізації процесу пошуку та оренди житла, використовуючи технології React/Next.js та Golang. Об’єкт дослідження: процеси взаємодії між потенційними орендарями та власниками житла на ринку нерухомості. Предмет дослідження: веб-застосунок для оренди житла. У ході написання кваліфікаційної роботи бакалавра було реалізовано веб-застосунок для оренди житла.uk_UA
dc.language.isoukuk_UA
dc.subjectОРЕНДА ЖИТЛА,uk_UA
dc.subjectВЕБ-ЗАСТОСУНОК,uk_UA
dc.subjectПРОЄКТУВАННЯ,uk_UA
dc.subjectУПРАВЛІННЯ,uk_UA
dc.subjectФУНКЦІОНАЛЬНІСТЬ,uk_UA
dc.subjectОПТИМІЗАЦІЯ.uk_UA
dc.titleВеб-застосунок для оренди житлаuk_UA
dc.typeBachelor Thesisuk_UA
Appears in Collections:122 Комп’ютерні науки (Комп’ютерні науки та прикладне програмування)

Files in This Item:
File Description SizeFormat 
Пояснювальна записка_Дащук Олег_КН-2101_2024-2025.pdf
  Restricted Access
2.83 MBAdobe PDFView/Open Request a copy


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

Extracted text
 
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ 
ЧЕРКАСЬКИЙ ДЕРЖАВНИЙ ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ 
 
Факультет інформаційних технологій і систем 
 
Кафедра комп’ютерних наук та системного аналізу 
 
 
 
 
 
 
Пояснювальна записка 
до кваліфікаційної роботи 
бакалавра 
(освітньо-кваліфікаційний рівень) 
 
на тему: «Веб-застосунок для оренди житла» 
 
 
 
 
 
    Виконав: студент 4 курсу, групи КН-2101 
                                                              
спеціальності  122 − «Комп’ютерні науки» 
                                                                         (шифр і назва спеціальності) 
освітня програма «Комп’ютерні науки та                                                                                                                           
  (назва освітньої програми) 
прикладне програмування 
                                                                     
 
 
Дащук Олег Віталійович 
 
Керівник __________   Оксамитна Л.П.     
                                                                          (прізвище та ініціали) 
 
Рецензент __________   Стась С.В.     
                                                                          (прізвище та ініціали) 
    
 
 
 
 
 
 
 
 
Черкаси 2025 рік 
 
Бланк завдання на кваліфікаційну роботу бакалавра студенту 
 
Черкаський державний технологічний університет 
Факультет Інформаційних технологій і систем 
Кафедра Комп’ютерних наук та системного аналізу 
Освітньо-кваліфікаційний рівень Бакалавр 
Спеціальність 122  – Комп’ютерні науки 
Освітня програма Комп’ютерні науки та прикладне програмування 
 
 
 
ЗАТВЕРДЖУЮ 
Завідувач кафедри КНСА  
_______________ Юрій ТРИУС 
«____» _____________ 2025 р. 
 
 
 
ЗАВДАННЯ 
на кваліфікаційну роботу бакалавра студенту 
Дащуку Олегу Віталійовичу 
(прізвище, ім’я, по батькові) 
1. Тема роботи  Веб-застосунок для оренди житла 
 
Керівник роботи     Оксамитна Л.П., к.т.н., доцент                    
(прізвище, ім’я, по батькові, науковий ступінь, вчене звання) 
затверджені наказом університету від «25» лютого 2025 р. №53/03-03. 
 
2. Строк подання студентом роботи до 11 червня 2025 року. 
3. Вихідні дані до роботи:  
Практичні навички роботи з інформаційними ресурсами. Робота з базами даних. 
Звіт з виробничої практики. Звіт з переддипломної практики. 
4. Зміст пояснювальної записки (перелік питань, що їх належить розробити): 
Вступ 
4.1.   Дослідження предметної області. 
4.2. Проєктування та розробка веб-застосунку для оренди житла. 
4.3. Програмна реалізація. 
Висновки.  
5. Перелік додатків (з точним зазначенням назв додатків): 
5.1. Додаток А. Специфікація 482.ЧДТУ. 52109-01. 
5.2. Додаток Б. Текст програми. 
5.4. Презентація у вигляді 20 слайдів. 
 
 
6. Консультанти розділів роботи 
Прізвище, ініціали та Підпис, дата 
Розділ посада 
завдання видав завдання прийняв 
консультанта 
    
    
 
7. Дата видачі завдання 17.12.2024 
  
 
КАЛЕНДАРНИЙ ПЛАН 
Строк 
№ з/п Назва етапів кваліфікаційної роботи бакалавра виконання Примітка 
етапів роботи 
1 Видача завдання на кваліфікаційну роботу 
17.12.2024 Виконано 
бакалавра. 
2 Аналіз літературних джерел, об’єкту та предмету 
до 12.01.2025 Виконано 
дослідження. 
3 Написання теоретичного розділу кваліфікаційної 
до 18.03.2025 Виконано 
роботи бакалавра. 
4 Написання аналітичного розділу кваліфікаційної 
до 05.04.2025 Виконано 
роботи бакалавра. 
5 Написання практичних розділів й висновків до 
до 01.05.2025 Виконано 
кваліфікаційної роботи бакалавра. 
6 Передзахист кваліфікаційної роботи бакалавра 
26.05.2025 Виконано 
на засіданні кафедри КНСА. 
7 Подання роботи завідувачу кафедри КНСА. до 11.06.2025 Виконано 
8 Захист кваліфікаційної роботи бакалавра. 11.06.2025 Виконано 
    
    
    
 
 
Студент                                   _____________________________     Олег ДАЩУК 
(підпис)                                                                     
 
Керівник роботи                     ____________________________       Любов ОКСАМИТНА  
                                           (підпис)      
 
 
                                                               
  
 
РЕФЕРАТ 
Кваліфікаційна робота бакалавра містить: 84 с., 19 рис., 5 таблиць, 6 
використаних джерел, 2 додатки. 
Актуальність теми. Актуальність теми полягає у тому, що на сьогоднішній 
день сфера оренди житла посідає одне з найважливіших місць у житті людей. Це 
зумовлено постійною потребою у комфортному та безпечному помешканні, чи то для 
постійного проживання, чи для тимчасового перебування. Ефективна організація 
процесу оренди житла є надзвичайно важливою, адже вона впливає на добробут та 
стабільність життя людини. 
Мета роботи і задачі дослідження. Метою кваліфікаційної роботи бакалавра є 
розробка та впровадження сучасної веб-платформи для автоматизації та оптимізації 
процесу пошуку та оренди житла, використовуючи технології React/Next.js та Golang. 
Об’єкт дослідження: процеси взаємодії між потенційними орендарями та 
власниками житла на ринку нерухомості. 
Предмет дослідження: веб-застосунок для оренди житла. 
Методи дослідження. Для досягнення поставленої мети в кваліфікаційній 
роботі бакалавра використовувалися наступні методи: 
- теоретичний метод (аналіз літератури з проблеми дослідження);  
- емпіричний – полягав у спостереженні веб-застосунків для оренжи житла;  
- порівняльний – для здійснення порівнянь існуючих аналогів веб-застосунків. 
Перелік ключових слів: ОРЕНДА ЖИТЛА, ВЕБ-ЗАСТОСУНОК, РОЗРОБКА, 
БАЗИ ДАНИХ, ПРОЄКТУВАННЯ, УПРАВЛІННЯ, ТЕСТУВАННЯ, 
ФУНКЦІОНАЛЬНІСТЬ, ОПТИМІЗАЦІЯ.  
 
ABSTRACT 
Work contains. 84 pages, 19 figures, 5 tables,  6 sources used,  2 attachments. 
Actuality of theme. The relevance of the topic arises from the fact that today the 
sphere of housing rental occupies one of the separate places in people's lives. This is due to 
the constant need for comfortable and safe living, whether for permanent or temporary stay. 
Effective organization of the housing rental process is extremely important, as it affects the 
well-being and stability of a person's life. 
Purpose and tasks of the research. The purpose of the bachelor's qualification work 
is to develop and implement a modern web platform to automate and optimize the process 
of searching for and renting housing, using React/Next.js and Golang technologies. 
The object of research. processes of interaction between potential tenants and 
homeowners in the real estate market. 
The subject of research: web application for renting housing. 
Research methods: To achieve the purpose of the bachelor’s qualification work, the 
following methods were applied. 
- theoretical method (analysis of literature on the research problem); 
- empirical – consisted in observing web applications to renting housing; 
- comparative – to make comparisons of existing analogues of web applications 
The list of keywords: HOUSING RENTAL, WEB APPLICATION, 
DEVELOPMENT, DATABASES, DESIGN, MANAGEMENT, TESTING, 
FUNCTIONALITY, OPTIMIZATION.
7 
ЗМІСТ 
ПЕРЕЛІК УМОВНИХ ПОЗНАЧЕНЬ, СИМВОЛІВ, СКОРОЧЕНЬ І ТЕРМІНІВ 8 
ВСТУП 9 
1 АНАЛІЗ ПРЕДМЕТНОЇ ОБЛАСТІ 11 
1.1  Огляд існуючих рішень 11 
    1.1.1 Аналіз платформи «flatfy» 11 
    1.1.2 Аналіз платформи «OLX.ua» 12 
    1.1.3 Аналіз веб-сайту «Booking.com» 14 
1.2 Розробка вимог до веб-застосунку оренди житла   16 
Висновки до розділу 1 17 
2 ПРОЄКТУВАННЯ  ВЕБ-ЗАСТОСУНКУ ДЛЯ ОРЕНДИ ЖИТЛА                         18 
   2.1 Опис технологій для розробки                                                                               18 
2.2 Проєктування архітектури бази даних 19 
2.3 Проєктування сторінок веб-застосунку  24 
Висновки до розділу 2 27 
3 ПРОГРАМНА РЕАЛІЗАЦІЯ 28 
3.1 Реалізація функцій веб-застосунку оренди житла 28 
3.2  Реалізація зовнішнього UI/UX – дизайну 32                                                                                                                   
Висновки до розділу 3 34 
ВИСНОВКИ 35 
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ 36 
Додаток А. Специфікація 482.ЧДТУ. 52109-01 37 
Додаток Б. Текст програми 39  
8 
ПЕРЕЛІК УМОВНИХ ПОЗНАЧЕНЬ, СИМВОЛІВ, СКОРОЧЕНЬ І 
ТЕРМІНІВ 
БД –      база даних. 
CSS – мова стилізації, яка визначає зовнішній вигляд елементів 
веб-сторінок. 
JavaScript – мова програмування для додавання інтерактивності до 
веб-сторінок. 
PostgreSQL – це потужна об'єктно-реляційна система баз даних з 
відкритим кодом. 
UI – користувацький інтерфейс для взаємодії користувача із 
системою. 
UX – досвід користувача, який відображає враження від 
взаємодії з додатком. 
HTTP – протокол передачі даних для обміну інформацією між 
клієнтом і сервером. 
API – інтерфейс для взаємодії між різними компонентами 
програмного забезпечення. 
SQL – мова для запитів до баз даних, яка використовується для 
управління та маніпуляції даними. 
9 
ВСТУП 
Сфера оренди житла є надзвичайно актуальною та динамічною, охоплюючи 
інтереси широкого кола користувачів – від студентів і туристів до власників 
нерухомості та агентств. Розробка сучасного веб-застосунку для оренди житла має 
на меті не лише спростити процес пошуку та розміщення оголошень, а й надати 
інструменти для ефективного аналізу ринку, уподобань користувачів та оптимізації 
взаємодії між орендодавцями й орендарями. 
У сфері безпеки та довіри, веб-застосунок може допомагати виявляти фейкові 
оголошення, запобігати шахрайству та забезпечувати захист персональних даних 
користувачів. 
Для власників житла та агентів з нерухомості аналітика користувацької 
активності дозволяє краще зрозуміти попит, встановлювати конкурентоспроможні 
ціни, адаптувати пропозиції під очікування цільової аудиторії. 
Для орендарів – це можливість швидше знаходити релевантні варіанти завдяки 
системі рекомендацій, фільтрів, рейтингових оцінок та відгуків. 
У бізнес-контексті такий застосунок може стати джерелом цінної інформації 
про ринкові тенденції, сезонні коливання попиту, популярні локації тощо. Глибокий 
аналіз користувацької поведінки дозволяє розробникам і власникам платформи 
постійно вдосконалювати функціональність, оптимізувати сервіс та підвищувати 
рівень користувацького досвіду. 
Попри те, що ринок орендних платформ активно розвивається, більшість 
наявних рішень усе ще мають обмеження у функціональності, зручності інтерфейсу 
та адаптивності до потреб різних категорій користувачів. Часто інтерфейси 
перевантажені, а можливості фільтрації та взаємодії з сервісом – обмежені або 
складні для новачків. 
Існуючі платформи не завжди відповідають очікуванням щодо простоти, 
швидкодії, гнучкості та доступності, що ускладнює використання таких сервісів 
звичайними користувачами. Це створює потребу у розробці нового веб-застосунку 
для оренди житла, який поєднує інтуїтивно зрозумілий інтерфейс, гнучкі 
10 
можливості налаштування, адаптивний дизайн та потужні інструменти аналітики. 
Застосування інноваційних підходів до обробки даних у поєднанні з 
сучасними методами візуалізації дозволить ефективно виявляти тенденції ринку, 
адаптувати пропозиції до потреб користувачів і забезпечити зручний та зрозумілий 
процес взаємодії з платформою для орендодавців і орендарів. 
Актуальність кваліфікаційної роботи бакалавра. 
Актуальність теми полягає у тому, що на сьогоднішній день сфера оренди 
житла посідає одне з найважливіших місць у житті людей. Це зумовлено постійною 
потребою у комфортному та безпечному помешканні, чи то для постійного 
проживання, чи для тимчасового перебування. Ефективна організація процесу 
оренди житла є надзвичайно важливою, адже вона впливає на добробут та 
стабільність життя людини. 
Метою кваліфікаційної роботи бакалавра є розробка та впровадження 
сучасної веб-платформи для автоматизації та оптимізації процесу пошуку та оренди 
житла, використовуючи технології React/Next.js та Golang. 
Об’єкт дослідження: процеси взаємодії між потенційними орендарями та 
власниками житла на ринку нерухомості. 
Предмет дослідження: веб-застосунок для оренди житла. 
Методи дослідження. Для досягнення поставленої мети в кваліфікаційній 
роботі бакалавра використовувалися наступні методи: 
− теоретичний метод (аналіз літератури з проблеми дослідження);  
− емпіричний – полягав у спостереженні веб-застосунків для оренди житла;  
− порівняльний – для здійснення порівнянь існуючих аналогів веб-застосунків. 
 
 
 
 
 
 
11 
1 АНАЛІЗ ПРЕДМЕТНОЇ ОБЛАСТІ 
1.1 Огляд існуючих рішень 
На ринку існує кілька популярних рішень, які пропонують сервіси для оренди 
житла. Найбільш популярними серед таких систем вважають «Flatfy.ua», «OLX.ua», 
«Booking.com». 
1.1.1 Аналіз платформи «flatfy» 
Flatfy.ua [1] – це агрегатор оголошень про нерухомість в Україні, який збирає 
інформацію з різноманітних джерел (ріелторські компанії, дошки оголошень, 
приватні оголошення). Він доступний через веб-версію, а також має мобільні 
додатки для iOS та Android. Flatfy.ua вирізняється своїм комплексним підходом до 
пошуку нерухомості, об'єднуючи пропозиції купівлі, продажу та оренди житла. Він 
спрямований на спрощення процесу пошуку нерухомості для потенційних покупців 
та орендарів, надаючи централізований доступ до великої кількості оголошень. 
Цей сервіс ідеально підходить для тих, хто шукає нерухомість, дозволяючи 
користувачам швидко знайти потрібний варіант за допомогою зручних фільтрів (тип 
нерухомості, ціна, розташування, кількість кімнат). Крім того, він часто пропонує 
додаткову інформацію, таку як розташування об'єкта на карті, фотографії та 
контактні дані продавця/орендодавця. Однак, оскільки Flatfy.ua є агрегатором, 
точність та актуальність деяких оголошень може залежати від вихідних джерел, а 
також спеціалізовані функції для оформлення документів або юридичної підтримки 
відсутні. 
Хоча Flatfy.ua є потужним інструментом для пошуку нерухомості, він також має 
певні недоліки: 
− Наявність неактуальних оголошень: оскільки Flatfy.ua агрегує 
інформацію з багатьох джерел, іноді трапляються оголошення, які вже неактуальні 
або є дублікатами. 
− Якість оголошень може відрізнятися: інформація в оголошеннях може 
бути неповною або неточною, оскільки вона залежить від оригінального джерела. 
12 
− Відсутність прямого зв'язку з власниками: Flatfy.ua не є платформою для 
прямого зв'язку між орендарями/покупцями та власниками. Для контакту, зазвичай, 
потрібно переходити на оригінальне джерело оголошення. 
− Обмежені фільтри для специфічних потреб: хоча основні фільтри 
присутні, для дуже специфічних запитів (наприклад, житло з доступом для людей з 
інвалідністю або певні архітектурні особливості) може бракувати деталізації. 
 
 
Рисунок 1.1 – Головна сторінка платформи «Flatfy.ua» [1] 
1.1.2  Аналіз платформи «OLX.ua» 
OLX.ua [2] – це один із найпопулярніших онлайн-сервісів для розміщення 
оголошень в Україні. Він доступний через веб-версію, а також має мобільні додатки 
для iOS та Android. OLX.ua вирізняється своїм широким спектром категорій, від 
нерухомості та автомобілів до електроніки та дитячих товарів, а також зручними 
функціями для пошуку та продажу. Він спрямований на спрощення процесу купівлі-
продажу та оренди товарів та послуг між приватними особами. 
Цей сервіс ідеально підходить для тих, хто шукає або пропонує товари чи 
послуги локально, дозволяючи користувачам швидко знайти потрібне оголошення у 
своєму регіоні. Крім того, він пропонує зручні інструменти для комунікації між 
покупцями та продавцями. Однак, як і на будь-якій великій платформі оголошень, 
13 
іноді можуть траплятися недобросовісні продавці, а функціональність безкоштовної 
версії може мати певні обмеження щодо кількості оголошень або просування. 
 
 
Рисунок 1.2 – Головна сторінка сайту «OLX.ua» [2] 
 
Цей сервіс ідеально підходить для тих, хто шукає або пропонує товари чи 
послуги локально, дозволяючи користувачам швидко знайти потрібне оголошення у 
своєму регіоні. Крім того, він пропонує зручні інструменти для комунікації між 
покупцями та продавцями. 
OLX.ua має і недоліки: 
− Відсутність перевірки послуг та товарів.  
− Перевантажений дизайн. 
1.1.3 Аналіз веб-сайту «Booking.com» 
Booking.com [3] – це один із найпопулярніших у світі онлайн-сервісів для 
бронювання житла. Він доступний через веб-версію, а також має зручні мобільні 
додатки для iOS та Android. Booking.com вирізняється своїм широким вибором 
пропозицій, що охоплює готелі, апартаменти, вілли та інші типи помешкань по 
14 
всьому світу. Він спрямований на спрощення процесу пошуку та бронювання житла 
для подорожей та відряджень. 
Цей сервіс ідеально підходить для тих, хто планує подорожі, дозволяючи 
користувачам швидко знайти та забронювати помешкання, яке відповідає їхнім 
потребам та бюджету. Крім того, він пропонує зручні інструменти для фільтрації 
результатів за різними критеріями (ціна, рейтинг, розташування, зручності) та 
інтегрується з системами онлайн-оплати. Однак, іноді можуть виникати проблеми з 
комунікацією між гостями та об'єктами розміщення, а також функціональність 
безкоштовної версії може бути обмежена порівняно з програмами лояльності або 
преміум-підписками. 
Переваги Booking.com: 
− Широкий вибір житла: величезна база готелів, апартаментів, хостелів та 
інших типів помешкань по всьому світу. 
− Зручні фільтри пошуку: детальні параметри для швидкого пошуку 
житла за ціною, рейтингом, розташуванням, зручностями та іншими критеріями. 
− Відгуки та рейтинги: доступ до мільйонів відгуків від реальних 
користувачів, що допомагає зробити поінформований вибір. 
− Мобільний додаток: повноцінний функціонал у зручному мобільному 
додатку, що дозволяє бронювати на ходу та отримувати сповіщення. 
− Безкоштовна відміна: можливість безкоштовної відміни бронювання 
для багатьох об'єктів, що забезпечує гнучкість у плануванні подорожей. 
− Цінові пропозиції та акції: часті знижки, спеціальні пропозиції та 
програма лояльності Genius для постійних клієнтів. 
Недоліки Booking.com: 
− Приховані комісії: деякі додаткові збори можуть бути не відразу 
помітно, що може призвести до несподіваних витрат. 
− Проблеми з підтримкою: іноді користувачі стикаються з труднощами 
при вирішенні питань з технічною підтримкою або об'єктами розміщення. 
15 
− Залежність від об'єктів розміщення: якість послуг може сильно 
відрізнятися залежно від конкретного готелю або власника апартаментів. 
− Перевантажений інтерфейс: для деяких користувачів велика кількість 
інформації та опцій може здатися занадто насиченою, що ускладнює навігацію. 
 
Рисунок 1.3 – Головна сторінка веб-сайту «Booking.com» [3] 
1.2 Розробка вимог до веб-застосунку для оренди житла  
Веб-додаток для оренди житла є потужним інструментом у розпорядженні 
сучасного користувача. Його можливості надавати зручний доступ до пропозицій, 
аналізувати попит, ціни та відгуки орендарів дозволяють значно покращити процес 
пошуку та вибору житла. Водночас важливо зважати як на переваги таких рішень, 
так і на можливі виклики, пов’язані з їх використанням. 
Багато існуючих платформ для оренди житла є дорогими або надто складними 
для новачків. Їхній інтерфейс часто перевантажений функціями й даними, які не 
завжди відповідають потребам користувачів. Відсутність чіткого структурування та 
пріоритетів у поданні інформації може призводити до плутанини та ускладнювати 
процес пошуку житла. 
16 
Детальний аналіз існуючих проблем і переваг дозволяє сформулювати 
ключові вимоги до веб-застосунку для оренди житла, що розробляється. Серед 
основних функціональних і технічних характеристик: 
- Можливість вводу та виведення інформації про оренду житла. 
- Інтуїтивно зрозумілий інтерфейс. 
- Обробка та аналіз інформації. 
- Використання облікових записів та хешування паролів для забезпечення 
безпеки веб-застосунку. 
- Можливість масштабування без обмежень щодо кількості користувачів або 
інтегрованих сервісів. 
- Використання сучасних технологій для розробки веб-застосунку для 
забезпечення стабільності, швидкодії та зручності підтримки веб-
застосунку. 
Висновки до розділу 1 
У даному розділі детально проаналізовано провідні системи, платформи та 
ресурси для оренди житла для визначення основних переваг та недоліків як 
комерційних, так і безкоштовних сервісів. Поглиблений аналіз дозволяє 
узагальнити, що більшість функцій систем є надлишковими, а користувачі віддають 
перевагу більш «простому». 
Також було сформовано основні функціональні вимоги до веб-застосунку для 
оренди житла.      
 
 
 
 
 
 
17 
2 ПРОЄКТУВАННЯ  ВЕБ-ЗАСТОСУНКУ ДЛЯ ОРЕНДИ ЖИТЛА   
2.1  Опис технологій для розробки  
Для забезпечення максимальної якості розробки веб-застосунку для оренди 
житла були  застосовані стек технології, що дозволяють швидко розробляти 
функціональні рішення з належним інтерфейсом. Основними мовами 
програмування, які використовувалися при розробці, були: Golang, React, Next.js, 
JavaScript, HTML, CSS. 
 Основою для розробки веб-застосунку став легкий та зручний 
мікрофреймворк для веб-розробки на Golang Gorilla/mux.  
Gorilla/mux – це мікро фреймворк для розробки веб-застосунків на Golang, 
який відрізняється своєю простотою та високою гнучкістю. Він дозволяє швидко 
створювати як невеликі прототипи, так і повноцінні веб-застосунки, не покладаючи 
суворих обмежень на архітектуру проєкту. Завдяки використанню шаблонізатора 
створювати динамічні HTML-сторінки стає простим і інтуїтивно зрозумілим 
завданням. Вбудований сервер розробки дозволяє працювати в режимі 
налагодження, що значно полегшує виправлення помилок під час створення 
проєкту.  
Використання Gorilla/mux та бібліотек для бекенду дозволило  оптимізувати 
роботу веб-застосунку, зокрема: 
- sqlc: забезпечує генерацію коду на основі sql схем та запитів. 
- database/sql: стандартна бібліотека для роботи із базами даних в Golang. 
- pgx: драйвер PostgreSQL для database/sql. 
- air: інструмент для перезавантаження програм Go, який автоматично 
перезавантажує сервер щоразу, коли вносяться зміни до коду, покращуючи робочий 
процес розробки. 
- clerk-sdk-go: інтезрація Clerk для Golang. 
18 
Для створення,  сучасних, зручних сторінок веб-застосунку  (фронтенду) 
використовувалися: 
- React  –  JavaScript-бібліотека фронтенду, яка має на меті зробити 
створення користувацьких інтерфейсів на основі компонентів. 
- Tailwind CSS    –  безкоштовного фреймворку для розробки інтерфейсів, 
який створює список "допоміжних" CSS-класів, які можна використовувати для 
стилізації кожного елемента шляхом змішування та зіставлення.  
- shadcn/ui — набір гарно розроблених, доступних компонентів та 
платформа для розповсюдження коду. Працює з вашими улюбленими фреймворками 
та моделями штучного інтелекту. Відкритий вихідний код.  
- react query — потужна бібліотека для надсилання запитів на сервер та 
кешування даних. 
Одним із обов'язкових елементів будь-якого веб-застосунку є база даних (БД),  
а саме PostgreSQL. PostgreSQL – це безкоштовна система керування реляційними 
базами даних з відкритим вихідним кодом, яка робить акцент на розширюваності та 
відповідності SQL. PostgreSQL пропонує транзакції з атомарністю, узгодженістю, 
ізоляцією, властивостями довговічності, автоматично оновлюваними 
представленнями, матеріалізованими представленнями, тригерами, зовнішніми 
ключами та збереженими процедурами. Вона підтримується всіма основними 
операційними системами, включаючи Windows, Linux, macOS, FreeBSD та 
OpenBSD, і обробляє широкий спектр робочих навантажень — від окремих машин 
до сховищ даних, озер даних або веб-сервісів з багатьма одночасними 
користувачами. 
2.2 Проєктування архітектури бази даних 
Для реалізації всіх функцій, пов’язаних зі збором, обробкою та відображенням 
інформації, критично важливо спроєктувати та інтегрувати надійну базу даних. Вона 
повинна забезпечувати зберігання облікових записів користувачів, даних про їхню 
активність, а також пов'язану інформацію — оголошення про оренду, зображення і 
19 
взаємодій. Така структура дозволить ефективно управляти контентом, забезпечити 
швидкий доступ до потрібної інформації та підтримувати персоналізований досвід 
користувача. 
 Спроєктована база даних складається з достатньої кількості об’єктів, зокрема 
наступних таблиць: 
Таблиця: «User» зберігає інформацію про користувачів системи, що дозволяє 
їм реєструватися, входити та керувати власними даними. 
Таблиця 2.1 – Структура таблиці «User» 
Назва елемента  Тип даних  Опис  
id UUID Первинний ключ. Унікальний 
ідентифікатор користувача. 
created_at TIMESTAMPTZ Час створення запису 
updated_at TIMESTAMPTZ Останній час, коли було оновлено 
дані 
clerk_id VARCHAR(100) ID користувача в Clerk 
Таблиця «Apartment» зберігає деталі про житло. 
Таблиця 2.2 – Структура таблиці «Apartment» 
Назва елемента  Тип даних  Опис  
id UUID Унікальний ідентифікатор посту. 
created_at TIMESTAMPTZ Час створення запису 
updated_at TIMESTAMPTZ Останній час, коли було оновлено дані 
user_id Integer Зовнішній ключ, який посилається на id 
користувача у таблиці user. Вказує, хто 
створив пост. 
name TEXT Назва житла 
description TEXT Опис житла 
address TEXT Адреса 
image_urls TEXT[] Картинки житла 
max_guests Integer Максимальна кількість людей для 
заселення. 
owner_id UUID Зовнішній ключ, який посилається на id 
користувача у таблиці user. Вказує, хто 
створив житло. 
 
20 
Таблиця «Booking» зберігає дані про бронювання житла. 
Таблиця 2.3 – Структура таблиці  «Booking» 
Назва Тип даних Опис 
елемента 
id UUID Унікальний ідентифікатор  
user_id UUID Зовнішній ключ, який посилається на id 
користувача у таблиці user. Вказує, хто 
забронював житло. 
created_at TIMESTAMPTZ Час створення запису 
updated_at TIMESTAMPTZ Останній час, коли було оновлено дані 
start_date TIMESTAMPTZ Дата публікації відео (рядок 'YYYY-MM-DD') 
end_date TIMESTAMPTZ Назва відеоплатформи (наприклад, "YouTube", 
"TikTok") 
apartment_id UUID Зовнішній ключ, який посилається на id 
користувача у таблиці apartment. Вказує, яке 
житло було орендовано. 
Таблиця «apartment_ratings» зберігає дані про оцінки користувачів до 
відповідного житла. 
Таблиця 2.4 – Структура таблиці «apartment_ratings» 
Назва елемента        Тип даних  Опис 
id UUID Унікальний ідентифікатор 
user_id UUID Зовнішній ключ, який посилається на 
id користувача у таблиці user. Вказує, 
хто поставив оцінку. 
created_at TIMESTAMPTZ Час створення запису 
updated_at TIMESTAMPTZ Останній час, коли було оновлено 
дані 
rating Small Integer Оцінка, яку надав користувач 
apartment_id UUID Зовнішній ключ, який посилається на 
id користувача у таблиці apartment. 
Вказує житло, якому поставили 
оцінку. 
Таблиця «apartment_comments» зберігає записи про відгуки та коментарі, яку надали 
користувачі до відповідного житла. 
 
 
 
21 
Таблиця 2.5 – Структура таблиці «apartment_comments» 
  Назва елемента        Тип даних  Опис 
id Integer Унікальний ідентифікатор 
user_id Integer Зовнішній ключ, який посилається на id 
користувача у таблиці user. Вказує, хто 
залишив коментар. 
created_at TIMESTAMPTZ Час створення запису 
updated_at TIMESTAMPTZ Останній час, коли було оновлено дані 
content TEXT Коментар користувача 
apartment_id UUID Зовнішній ключ, який посилається на id 
користувача у таблиці apartment. Вказує 
житло, якому написали коментра. 
 
Після проєктування таблиць, можна сформувати діаграму зв’язків бази даних. 
При створенні веб-застосунку за допомогою фреймворка є певна особливість 
взаємодії. Flask-застосунок використовує SQLAlchemy як ORM (Object-Relational 
Mapper) та SQLite як базу даних. Це популярне поєднання для невеликих та середніх 
проєктів, оскільки SQLite є легкою, файловою базою даних, що не потребує 
окремого сервера. 
Інформація про базу даних таблиці та поля генерується безпосередньо з файлів  
sql/migration/*.sql.   Кожена структура в цьому файлі (наприклад, User, Apartment, 
ApartmentComment, ApartmentRating, Booking) відповідає таблиці в базі даних, з 
доданою частиною коду (рис. 2.1). 
Для ініціалізації бази даних  використовується файл __init__.py. Цей файл 
відповідає за створення та налаштування Flask-додатку, включаючи ініціалізацію 
бази даних. 
 
22 
 
Рисунок 2.1 – Вигляд структури з доданою частиною коду 
conn, err := pgxpool.New(ctx, fmt.Sprintf("postgres://%s:%s@%s:%d", 
cfg.Postgresql.User, cfg.Postgresql.Password, cfg.Postgresql.Host, cfg.Postgresql.Port)): 
Це найважливіший рядок для конфігурації бази даних. Він вказує pgx, як 
підєднатися до бази даних. Після створення формується файл бази даних із 
вказаними параметрами у models.go, з’являється можливість переглянути зв'язки 
(рис. 2.2). 
 
Рисунок 2.2 – Діаграма зв’язків  бази даних 
23 
2.3 Проєктування сторінок веб-застосунку  
Проєктування сторінок є важливим етапом для зовнішнього вигляду та 
розуміння функцій, які будуть виконуватися. 
    Для реалізації проєкту було створено сторінки: «home.tsx», «registration.tsx», 
«login.tsx», «listing.tsx». 
Сторінка «home.tsx» –  головна сторінка сайту. 
На сторінці відображаються: 
хедер із лінками; 
футер з копірайтом та контактами; 
− форма; 
− форма. 
Сторінка «login.html» –  сторінка, призначена для входу користувачів в систему. 
На сторінці відображаються: 
В центрі сторінки – форма авторизації. 
Всередині форми: 
− Поле логіну (username). 
− Поле паролю (password). 
− Кнопка “Увійти”. 
− Посилання “Створити акаунт”, яке веде на сторінку реєстрації (/register) 
− Що виконується: 
− Після натискання кнопки “Увійти”: 
− Дані передаються на POST /login. 
− Якщо логін і пароль правильні – відбувається авторизація (login_user). 
− Користувача перекидає на /dashboard. 
 Сторінка «dashboard.html» – Головна сторінка після входу (Дашборд). 
На сторінці відображаються: 
Заголовок: «Аналітика». 
Перший блок: 4 картки зі статистикою: 
− Кількість постів. 
24 
− Кількість відео. 
− Кількість сторіс. 
− Кількість підписників. 
Далі є 12 інтерактивних графіків (Chart.js): 
За днями: 
− динаміка постів, лайків, репостів; 
− динаміка відео, переглядів, лайків, репостів; 
− динаміка сторіс, переглядів, відповідей; 
− динаміка підписників. 
− За платформами кругові діаграми для: 
− постів; 
− відео; 
− сторіс; 
− підписників. 
Що виконується: 
− Після завантаження сторінки запускається JavaScript: 
− Графіки створюються через Chart.js з даних, переданих у chart_data. 
− Якщо в даних є значення – графік створюється. 
− Якщо немає – виводиться повідомлення “Немає даних для діаграми ...” 
Сторінка «statistics.html» – Сторінка статистики. 
На сторінці відображаються: 
− Заголовок: «Статистика за даними». 
− Вгорі – форма з фільтром типу контенту: 
− Обирається один з: пости / відео / сторіс / підписники. 
Під фільтром таблиця вибраними даними з відповідними колонками: 
Що виконується: 
При зміні фільтра (типу контенту) – форма автоматично надсилається 
(onchange="this.form.submit()"). 
У відповідь сервер повертає оновлену таблицю з даними. 
Сторінка «register.html» – сторінка реєстрації нового користувача. 
25 
Що відображається: 
Заголовок: «Створення акаунту». 
В центрі – форма з полями: 
− Ім’я. 
− Прізвище. 
− Електронна пошта. 
− Логін. 
− Пароль. 
− Кнопка “Створити акаунт”. 
Що виконується: 
Коли користувач заповнює форму та натискає кнопку: 
Дані відправляються на POST /register. 
У бекенді: 
− створюється об’єкт User; 
− пароль хешується через generate_password_hash; 
− новий користувач зберігається у базу; 
− Після реєстрації – редірект на /login з повідомленням "Користувач створений". 
Сторінка «add.html» – Сторінка додавання нових записів. 
Що відображається: 
Заголовок сторінки: "Додати дані - Статистика Соцмереж".  
Заголовок: "Додати дані".  
 Чотири окремі секції/картки.  
 Картка з синім заголовком. Містить форму для додавання посту:  Поля: 
"Дата", "Платформа", "Заголовок", "Лайки", "Репости". Кнопка "Додати пост". 
 Картка із зеленим заголовком. Містить форму для додавання відео:  
Поля: "Дата", "Платформа", "Заголовок", "Перегляди", "Лайки", "Репости". Кнопка 
"Додати відео".  
  Картка з помаранчевим заголовком. Містить форму для додавання 
сторіс:  Поля: "Дата", "Платформа", "Заголовок (опціонально)", "Перегляди", 
"Відповіді". Кнопка "Додати сторіс". 
26 
  Картка з блакитним заголовком. Містить форму для додавання даних 
про підписників:  Поля: "Дата", "Платформа", "Кількість". Кнопка "Додати 
підписників". 
 Кнопка “Зберегти” відправляє форму на POST /add. 
Висновки до розділу 2 
У процесі розробки веб-застосунку для оренди житла детально  розглянуто 
стек технологій, що дозволяє швидко розробляти функціональні рішення з 
належним інтерфейсом.  
Розглянутий Gorilla/mux мікро фреймворк для розробки веб-застосунків на 
Golang та його білітіотук: sqlc.  
Розглянуто взаємодію з Gorilla/mux з базою даних за допомогою database/sql. 
Спроєктована реляційна база даних для реалізації веб-застосунку. 
          Розглянуто використання бази даних з фреймворком. 
Детально спроєктовані сторінки веб-застосунку для оренди житла з описом 
елементів та функцій. 
27 
3 ПРОГРАМНА РЕАЛІЗАЦІЯ 
3.1  Реалізація функцій веб-застосунку для оренди житла 
Початковим етапом програмної реалізації є створення проєкту базової 
структури папок середовища. 
Далі створюємо модуль командою go mod init github.com/SerhioTis/book-
apartment-app. 
Далі встановлюємо бібліотеки «go mod tidy». 
Створення основного файлу веб-застосунку main.go – це центральний файл 
для створення та конфігурації Golang-застосунку, а також ініціалізації залежностей. 
Визначаємо моделі бази даних models.py та створюємо файл.  
Прописуємо значення для таблиць бази даних. 
 
Рисунок 3.1 – Код моделі житла 
 
 
28 
 
Рисунок 3.2 – Код моделі коментарів 
 
Рисунок 3.3 – Код моделі оцінки житла 
 
Рисунок 3.4 – Код моделі бронювань 
Обов’язковим є визначення маршрутів та логіки веб-застосунку, описавши їх 
в handlers.go. Цей файл міститиме всі маршрути (URL-адреси) веб-застосунку та 
логіку обробки запитів.  
29 
 
Рисунок 3.6 – Код структури маршрутів 
 
Рисунок 3.7 – Код маршрутів житла 
30 
 
Рисунок 3.8 – Код маршрутів бронювання житла 
 Також був створений веб хук, який буде приймати запити із Clerk, щоб 
асинхронно отримувати дані про користувачів від Clerk. 
 
Рисунок 3.9 – Код веб хука Clerk 
31 
3.2  Реалізація зовнішнього UI/UX – дизайну  
Головна сторінка веб-застосунку для оренди житла зображена на рисунку 3.10. 
Для перегляду веб-застосунку  потрібно авторизуватися. 
 
Рисунок 3.10 – Вікно авторизації веб-застосунку  
Якщо немає облікового запису, то потрібно перейти на сторінку реєстрації та 
ввести дані для реєстрації (рис. 3.11). 
 
Рисунок 3.11 – Вікно реєстрації  
Після авторизації відкриється головна сторінка  (рис. 3.12). 
32 
 
Рисунок 3.12 –  Головна сторінка веб-застосунку для оренди житла 
 На головній сторінці можна перейти на сторінку із списком житла. 
 
 
Рисунок 3.13 – Сторінка із списком житла 
Обравши житло, можна переглянути деталі та оформити оренду. 
33 
 
Рисунок 3.14 – Сторінка перегляду та замовлення житла 
Висновки до розділу 3 
У даному розділі було детально описано процес реалізації серверної та 
клієнтської частини проєкту, налаштування проєкту на базі фреймворка  
Gorilla/mux.  
Розглянуто реалізацію функцій та зовнішнього UI/UX – дизайну веб-
застосунку для оренди житла. 
 
 
 
  
34 
ВИСНОВКИ 
У ході написання кваліфікаційної роботи бакалавра було реалізовано веб-
застосунок для оренди житла. 
У кваліфікаційній роботі бакалавра детально проаналізовано провідні системи 
для оренди житла з метою визначення основних переваг та недоліків, як 
комерційних, так і безкоштовних сервісів. Поглиблений аналіз дозволив зробити 
висновки, що більшість функцій систем є надлишковими, а користувачі віддають 
перевагу більш «простому».  
Також було сформовано основні функціональні вимоги до веб-застосунку для 
оренди житла.    
Розглянутий Gorilla/mux мікро фреймворк для розробки веб-застосунків на 
Golang, а також  взаємодію з Gorilla/mux  з базою даних за допомогою sqlc та pgx. 
Спроєктована реляційна база даних для реалізації веб-застосунку та 
розглянуто використання її з фреймворком. 
   Детально спроєктовані сторінки веб-застосунку для оренди житла з описом 
елементів та функцій. 
Мета роботи досягнута. У результаті виконання роботи були досягнуті всі цілі, 
визначені на етапі розробки вимог і проєктування та розроблено готовий додаток 
для використання. 
 
 
 
 
 
 
 
 
 
35 
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ 
1. Документація clerk. URL: https://clerk.com/docs (дата звернення 15.05.2025). 
2. Мануал golang. URL: https://go.dev/learn/ (дата звернення 15.05.2025). 
3. React документація. URL: https://react.dev/learn (дата звернення 15.05.2025). 
4. Документація Next.js.  URL: https://nextjs.org/docs (дата звернення 15.05.2025). 
5. Вікіпедія вільна енциклопедія. URL: 
https://en.wikipedia.org/wiki/React_(software) (дата звернення 15.05.2025). 
6. Специфікація OpenAPI. URL: https://spec.openapis.org/oas/v3.1.1.html (дата 
звернення 15.05.2025). 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36 
ДОДАТОК А 
 
 
        Затверджую               
Зав. кафедри КНСА, 
______________ Юрій ТРИУС 
«____»____________2025 р.                                                                                                                                                                              
 
 
 
 
 
 
 
ВЕБ-ЗАСТОСУНОК ДЛЯ ОРЕНДИ ЖИТЛА     
 
Специфікація  
482.ЧДТУ. 52109-01 
 
Листів 2 
 
 
 
 
 
Розробник                          ____________________                 Олег ДАЩУК 
 
Керівник                             ____________________                Любов ОКСАМИТНА 
 
 
 
 
 
 
 
 
 
 
 
 
Черкаси – 2025  
37 
482.ЧДТУ. 52109-01 
Позначення Найменування Примітка 
   
   
 Документація  
   
   
482.ЧДТУ. 52109-01    12 01 Текст програми  
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
38 
ДОДАТОК Б 
 
 
 
 
 
ВЕБ-ЗАСТОСУНОК ДЛЯ ОРЕНДИ ЖИТЛА                
 
 
 
Текст програми 
482.ЧДТУ. 52109-01 12 01 
 
Листів 45 
 
 
 
 
 
 
Розробник    _____________           Олег ДАЩУК 
 
 
 
 
 
 
 
 
Черкаси – 2025 
main.go  User     string `mapstructure:"user"` 
  Password string `mapstructure:"password"` 
package main } 
  
import "github.com/SerhioTis/book-apartment- func ReadConfig(path string) (Config, error) { 
app/pkg/app"  v := viper.New() 
  v.SetConfigFile(path) 
func main() {  if err := v.ReadInConfig(); err != nil { 
 app.Run()  return Config{}, err 
}  } 
  
config.go  var config Config 
  if err := v.Unmarshal(&config); err != nil { 
package config  return Config{}, err 
  } 
import (  
 "github.com/spf13/viper"  return config, nil 
) } 
  
type Config struct { dto.go 
 HTTP       HTTPConfig  
 Postgresql PostgresqlConfig package dto 
 Clerk      ClerkConfig  
} type ClerkWebhookEventType string 
  
type HTTPConfig struct { const ( 
 Host string `mapstructure:"host"`  ClerkWebhookTypeUserCreated 
 Port int    `mapstructure:"port"` ClerkWebhookEventType = "user.created" 
}  ClerkWebhookTypeUserUpdated 
 ClerkWebhookEventType = "user.updated" 
type ClerkConfig struct {  ClerkWebhookTypeUserDeleted 
 SecretKey            string ClerkWebhookEventType = "user.deleted" 
`mapstructure:"secret_key"` ) 
 PublicKey            string  
`mapstructure:"public_key"` type ClerkWebhookRequest struct { 
 WebhookSigningSecret string  Data      ClerkWebhookData      `json:"data"` 
`mapstructure:"webhook_signing_secret"`  Timestamp int64                 `json:"timestamp"` 
}  Type      ClerkWebhookEventType `json:"type"` 
 } 
type PostgresqlConfig struct {  
 Host     string `mapstructure:"host"` type ClerkWebhookData struct { 
 Port     int    `mapstructure:"port"`  ID string `json:"id"` 
40 
} len(apartments)) 
  for i, apartment := range apartments { 
apartment_commet_handler.go  res[i] = api.ApartmentComment{ 
   Id:          apartment.ID, 
package handler   CreatedAt:   
 apartment.CreatedAt, 
import (   UpdatedAt:   
 "context" apartment.UpdatedAt, 
   UserId:      apartment.UserID, 
 "github.com/SerhioTis/book-apartment-app/api"   ApartmentId: 
 "github.com/SerhioTis/book-apartment- apartment.ApartmentID, 
app/internal/repository"   Content:     apartment.Content, 
 "github.com/clerk/clerk-sdk-go/v2"  } 
 "github.com/google/uuid"  } 
)  return res, nil 
 } 
func (h *Handler) ListApartmentComments(ctx  
context.Context, request func (h *Handler) CreateApartmentComment(ctx 
api.ListApartmentCommentsRequestObject) context.Context, request 
(api.ListApartmentCommentsResponseObject, error) { api.CreateApartmentCommentRequestObject) 
 limit := int32(10) (api.CreateApartmentCommentResponseObject, error) { 
 if request.Params.Limit != nil {  claims, ok := 
 limit = *request.Params.Limit clerk.SessionClaimsFromContext(ctx) 
 }  if !ok { 
 offset := int32(0)  return 
 if request.Params.Page != nil { api.CreateApartmentComment401JSONResponse{ 
 offset = *request.Params.Page * limit  
 }  UnauthorizedErrorJSONResponse: 
 api.UnauthorizedErrorJSONResponse{ 
 apartments, err :=    Access: 
h.service.ListApartmentComments(ctx, "unauthorized", 
repository.ListApartmentCommentsParams{   }, 
 ApartmentID: request.ApartmentId,  }, nil 
 Limit:       limit,  } 
 Offset:      offset,  
 })  clerkID, err := uuid.Parse(claims.Subject) 
 if err != nil {  if err != nil { 
 return nil, err  return nil, err 
 }  } 
  
 res :=  createdApartmentComment, err := 
make(api.ListApartmentComments200JSONResponse, h.service.CreateApartmentComment(ctx, 
41 
repository.CreateApartmentCommentParams{  
 ClerkID:     clerkID,  clerkID, err := uuid.Parse(claims.Subject) 
 ApartmentID: request.ApartmentId,  if err != nil { 
 Content:     request.Body.Comment,  return nil, err 
 })  } 
 if err != nil {  
 return nil, err  updatedApartmentComment, err := 
 } h.service.UpdateApartmentCommentByID(ctx, 
 repository.UpdateApartmentCommentByIDParams{ 
 return  ID:      request.CommentId, 
api.CreateApartmentComment200JSONResponse{  ClerkID: clerkID, 
 Id:          createdApartmentComment.ID,  Content: request.Body.Comment, 
 CreatedAt:    }) 
createdApartmentComment.CreatedAt,  if err != nil { 
 UpdatedAt:    return nil, err 
createdApartmentComment.UpdatedAt,  } 
 UserId:       
createdApartmentComment.UserID,  return 
 ApartmentId: api.UpdateCommentByID200JSONResponse{ 
createdApartmentComment.ApartmentID,  Id:          
 Content:     updatedApartmentComment.ID, 
createdApartmentComment.Content,  CreatedAt:   
 }, nil updatedApartmentComment.CreatedAt, 
}  UpdatedAt:   
 updatedApartmentComment.UpdatedAt, 
func (h Handler) UpdateCommentByID(ctx  UserId:      
context.Context, request updatedApartmentComment.UserID, 
api.UpdateCommentByIDRequestObject)  ApartmentId: 
(api.UpdateCommentByIDResponseObject, error) { updatedApartmentComment.ApartmentID, 
 claims, ok :=  Content:     
clerk.SessionClaimsFromContext(ctx) updatedApartmentComment.Content, 
 if !ok {  }, nil 
 return } 
api.UpdateCommentByID401JSONResponse{  
 func (h *Handler) DeleteCommentByID(ctx 
 UnauthorizedErrorJSONResponse: context.Context, request 
api.UnauthorizedErrorJSONResponse{ api.DeleteCommentByIDRequestObject) 
   Access: (api.DeleteCommentByIDResponseObject, error) { 
"unauthorized",  claims, ok := 
  }, clerk.SessionClaimsFromContext(ctx) 
 }, nil  if !ok { 
 }  return 
42 
api.DeleteCommentByID401JSONResponse{  
 package handler 
 UnauthorizedErrorJSONResponse:  
api.UnauthorizedErrorJSONResponse{ import ( 
   Access:  "context" 
"unauthorized",  
  },  "github.com/SerhioTis/book-apartment-app/api" 
 }, nil  "github.com/SerhioTis/book-apartment-
 } app/internal/repository" 
  "github.com/clerk/clerk-sdk-go/v2" 
 clerkID, err := uuid.Parse(claims.Subject)  "github.com/google/uuid" 
 if err != nil {  "github.com/jackc/pgx/v5/pgtype" 
 return nil, err ) 
 }  
 func (h *Handler) CreateApartment(ctx 
 deletedApartmentComment, err := context.Context, request 
h.service.DeleteApartmentCommentByID(ctx, api.CreateApartmentRequestObject) 
repository.DeleteApartmentCommentByIDParams{ (api.CreateApartmentResponseObject, error) { 
 ID:      request.CommentId,  claims, ok := 
 ClerkID: clerkID, clerk.SessionClaimsFromContext(ctx) 
 })  if !ok { 
 if err != nil {  return 
 return nil, err api.CreateApartment401JSONResponse{ 
 }  
  UnauthorizedErrorJSONResponse: 
 return api.UnauthorizedErrorJSONResponse{ 
api.DeleteCommentByID200JSONResponse{    Access: 
 Id:          deletedApartmentComment.ID, "unauthorized", 
 CreatedAt:     }, 
deletedApartmentComment.CreatedAt,  }, nil 
 UpdatedAt:    } 
deletedApartmentComment.UpdatedAt,  
 UserId:       clerkID, err := uuid.Parse(claims.Subject) 
deletedApartmentComment.UserID,  if err != nil { 
 ApartmentId:  return nil, err 
deletedApartmentComment.ApartmentID,  } 
 Content:      
deletedApartmentComment.Content,  createdApartment, err := 
 }, nil h.service.CreateApartment(ctx, 
} repository.CreateApartmentParams{ 
  ClerkID:     clerkID, 
apartment_handler.go  Name:        request.Body.Name, 
43 
 Description: request.Body.Description,  }, nil 
 Address:     request.Body.Address,  } 
 ImageUrls:   request.Body.ImageUrls,  
 MaxGuests:    clerkID, err := uuid.Parse(claims.Subject) 
int32(request.Body.MaxGuests),  if err != nil { 
 })  return nil, err 
 if err != nil {  } 
 return nil, err  
 }  updatedApartment, err := 
 h.service.UpdateApartmentByID(ctx, 
 return api.CreateApartment200JSONResponse{ repository.UpdateApartmentByIDParams{ 
 Id:          createdApartment.ID,  ClerkID:     clerkID, 
 CreatedAt:    ID:          request.ApartmentId, 
createdApartment.CreatedAt,  Name:        request.Body.Name, 
 UpdatedAt:    Description: request.Body.Description, 
createdApartment.UpdatedAt,  Address:     request.Body.Address, 
 Name:        createdApartment.Name,  ImageUrls:   request.Body.ImageUrls, 
 Description:  MaxGuests:   
createdApartment.Description, int32(request.Body.MaxGuests), 
 Address:     createdApartment.Address,  }) 
 ImageUrls:    if err != nil { 
createdApartment.ImageUrls,  return nil, err 
 MaxGuests:    } 
int(createdApartment.MaxGuests),  
 }, nil  return 
} api.UpdateApartmentByID200JSONResponse{ 
  Id:          updatedApartment.ID, 
func (h *Handler) UpdateApartmentByID(ctx  CreatedAt:   
context.Context, request updatedApartment.CreatedAt, 
api.UpdateApartmentByIDRequestObject)  UpdatedAt:   
(api.UpdateApartmentByIDResponseObject, error) { updatedApartment.UpdatedAt, 
 claims, ok :=  Name:        updatedApartment.Name, 
clerk.SessionClaimsFromContext(ctx)  Description: 
 if !ok { updatedApartment.Description, 
 return  Address:     updatedApartment.Address, 
api.UpdateApartmentByID401JSONResponse{  ImageUrls:   
 updatedApartment.ImageUrls, 
 UnauthorizedErrorJSONResponse:  MaxGuests:   
api.UnauthorizedErrorJSONResponse{ int(updatedApartment.MaxGuests), 
   Access:  }, nil 
"unauthorized", } 
  },  
44 
func (h *Handler) DeleteApartmentByID(ctx deletedApartment.Description, 
context.Context, request  Address:     deletedApartment.Address, 
api.DeleteApartmentByIDRequestObject)  ImageUrls:   
(api.DeleteApartmentByIDResponseObject, error) { deletedApartment.ImageUrls, 
 claims, ok :=  MaxGuests:   
clerk.SessionClaimsFromContext(ctx) int(deletedApartment.MaxGuests), 
 if !ok {  }, nil 
 return } 
api.DeleteApartmentByID401JSONResponse{  
 func (h *Handler) GetApartmentByID(ctx 
 UnauthorizedErrorJSONResponse: context.Context, request 
api.UnauthorizedErrorJSONResponse{ api.GetApartmentByIDRequestObject) 
   Access: (api.GetApartmentByIDResponseObject, error) { 
"unauthorized",  apartment, err := 
  }, h.service.GetApartmentByID(ctx, request.ApartmentId) 
 }, nil  if err != nil { 
 }  return nil, err 
  } 
 clerkID, err := uuid.Parse(claims.Subject)  return 
 if err != nil { api.GetApartmentByID200JSONResponse{ 
 return nil, err  Id:          apartment.ID, 
 }  CreatedAt:   apartment.CreatedAt, 
  UpdatedAt:   apartment.UpdatedAt, 
 deletedApartment, err :=  Name:        apartment.Name, 
h.service.DeleteApartmentByID(ctx,  Description: apartment.Description, 
repository.DeleteApartmentByIDParams{  Address:     apartment.Address, 
 ClerkID: clerkID,  ImageUrls:   apartment.ImageUrls, 
 ID:      request.ApartmentId,  MaxGuests:   int(apartment.MaxGuests), 
 })  }, nil 
 if err != nil { } 
 return nil, err  
 } func (h *Handler) ListApartments(ctx 
 context.Context, request 
 return api.ListApartmentsRequestObject) 
api.DeleteApartmentByID200JSONResponse{ (api.ListApartmentsResponseObject, error) { 
 Id:          deletedApartment.ID,  limit := int32(10) 
 CreatedAt:    if request.Params.Limit != nil { 
deletedApartment.CreatedAt,  limit = int32(*request.Params.Limit) 
 UpdatedAt:    } 
deletedApartment.UpdatedAt,  offset := int32(0) 
 Name:        deletedApartment.Name,  if request.Params.Page != nil { 
 Description:  offset = int32(*request.Params.Page) * 
45 
limit  return api.ListApartments200JSONResponse{ 
 }  Apartments: &res, 
 var fromDate, toDate pgtype.Date  }, nil 
 if request.Params.AvailableDuration != nil { } 
 fromDate =  
convertTimeToPgTypeDate(request.Params.AvailableDur apartment_rating_handler.go 
ation.FromDate)  
 toDate = package handler 
convertTimeToPgTypeDate(request.Params.AvailableDur  
ation.ToDate) import ( 
 }  "context" 
  
 apartments, err := h.service.ListApartments(ctx,  "github.com/SerhioTis/book-apartment-app/api" 
repository.ListApartmentsParams{  "github.com/SerhioTis/book-apartment-
 Name:      request.Params.Name, app/internal/repository" 
 Address:   request.Params.Address,  "github.com/clerk/clerk-sdk-go/v2" 
 MaxGuests: request.Params.MaxGuests,  "github.com/google/uuid" 
 FromDate:  fromDate, ) 
 ToDate:    toDate,  
 Limit:     limit, func (h *Handler) 
 Offset:    offset, RateApartmentByApartmentID(ctx context.Context, 
 }) request api.RateApartmentByApartmentIDRequestObject) 
 if err != nil { (api.RateApartmentByApartmentIDResponseObject, error) 
 return nil, err { 
 }  claims, ok := 
 clerk.SessionClaimsFromContext(ctx) 
 res := make([]api.ListApartmentsResponseItem,  if !ok { 
len(apartments))  return 
 for i, apartment := range apartments { api.RateApartmentByApartmentID401JSONResponse{ 
 res[i] =  
api.ListApartmentsResponseItem{  UnauthorizedErrorJSONResponse: 
  Id:          apartment.ID, api.UnauthorizedErrorJSONResponse{ 
  Name:        apartment.Name,    Access: 
  Description: "unauthorized", 
apartment.Description,   }, 
  Address:     apartment.Address,  }, nil 
  ImageUrls:    } 
apartment.ImageUrls,  
  MaxGuests:    clerkID, err := uuid.Parse(claims.Subject) 
int(apartment.MaxGuests),  if err != nil { 
 }  return nil, err 
 }  } 
46 
  if err != nil { 
 rating, err := h.service.RateApartment(ctx,  return nil, err 
repository.RateApartmentParams{  } 
 ClerkID:     clerkID,  
 ApartmentID: request.ApartmentId,  rating, err := 
 Rating:      int16(request.Body.Rating), h.service.GetUserApartmentRating(ctx, 
 }) repository.GetUserApartmentRatingParams{ 
 if err != nil {  ClerkID:     clerkID, 
 return nil, err  ApartmentID: request.ApartmentId, 
 }  }) 
  if err != nil { 
 return  return nil, err 
api.RateApartmentByApartmentID200JSONResponse{  } 
 Id:          rating.ID,  
 CreatedAt:   rating.CreatedAt,  return 
 UpdatedAt:   rating.UpdatedAt, api.GetUserRatingByApartmentID200JSONResponse{ 
 ApartmentId: rating.ApartmentID,  Id:          rating.ID, 
 Rating:      int(rating.Rating),  CreatedAt:   rating.CreatedAt, 
 UserId:      rating.UserID,  UpdatedAt:   rating.UpdatedAt, 
 }, nil  ApartmentId: rating.ApartmentID, 
}  Rating:      int(rating.Rating), 
  UserId:      rating.UserID, 
func (h *Handler)  }, nil 
GetUserRatingByApartmentID(ctx context.Context, } 
request api.GetUserRatingByApartmentIDRequestObject)  
(api.GetUserRatingByApartmentIDResponseObject, error) func (h *Handler) 
{ GetApartmentAverageRating(ctx context.Context, request 
 claims, ok := api.GetApartmentAverageRatingRequestObject) 
clerk.SessionClaimsFromContext(ctx) (api.GetApartmentAverageRatingResponseObject, error) { 
 if !ok {  rating, err := h.service.GetApartmentRating(ctx, 
 return request.ApartmentId) 
api.GetUserRatingByApartmentID401JSONResponse{  if err != nil { 
  return nil, err 
 UnauthorizedErrorJSONResponse:  } 
api.UnauthorizedErrorJSONResponse{  
   Access:  return 
"unauthorized", api.GetApartmentAverageRating200JSONResponse{ 
  },  Rating: rating.Avg, 
 }, nil  Count:  rating.Count, 
 }  }, nil 
 } 
 clerkID, err := uuid.Parse(claims.Subject)  
47 
booking_handler.go repository.CreateBookingParams{ 
  ClerkID:     clerkID, 
package handler  ApartmentID: request.ApartmentId, 
  StartDate:   request.Body.StartDate, 
import (  EndDate:     request.Body.EndDate, 
 "context"  }) 
  if err != nil { 
 "github.com/SerhioTis/book-apartment-app/api"  return nil, err 
 "github.com/SerhioTis/book-apartment-  } 
app/internal/repository"  
 "github.com/clerk/clerk-sdk-go/v2"  return 
 "github.com/google/uuid" api.BookApartmentByApartmentID200JSONResponse{ 
 "github.com/jackc/pgx/v5/pgtype"  Id:          createdBooking.ID, 
)  CreatedAt:   createdBooking.CreatedAt, 
  UpdatedAt:   
func (h *Handler) createdBooking.UpdatedAt, 
BookApartmentByApartmentID(ctx context.Context,  StartDate:   createdBooking.StartDate, 
request  EndDate:     createdBooking.EndDate, 
api.BookApartmentByApartmentIDRequestObject)  UserId:      createdBooking.UserID, 
(api.BookApartmentByApartmentIDResponseObject,  ApartmentId: 
error) { createdBooking.ApartmentID, 
 claims, ok :=  }, nil 
clerk.SessionClaimsFromContext(ctx) } 
 if !ok {  
 return func (h *Handler) UpdateBookingByID(ctx 
api.BookApartmentByApartmentID401JSONResponse{ context.Context, request 
 api.UpdateBookingByIDRequestObject) 
 UnauthorizedErrorJSONResponse: (api.UpdateBookingByIDResponseObject, error) { 
api.UnauthorizedErrorJSONResponse{  claims, ok := 
   Access: clerk.SessionClaimsFromContext(ctx) 
"unauthorized",  if !ok { 
  },  return 
 }, nil api.UpdateBookingByID401JSONResponse{ 
 }  
  UnauthorizedErrorJSONResponse: 
 clerkID, err := uuid.Parse(claims.Subject) api.UnauthorizedErrorJSONResponse{ 
 if err != nil {    Access: 
 return nil, err "unauthorized", 
 }   }, 
  }, nil 
 createdBooking, err :=  } 
h.service.BookApartment(ctx,  
48 
 clerkID, err := uuid.Parse(claims.Subject) api.UnauthorizedErrorJSONResponse{ 
 if err != nil {    Access: 
 return nil, err "unauthorized", 
 }   }, 
  }, nil 
 updatedBooking, err :=  } 
h.service.UpdateBookingByID(ctx,  
repository.UpdateBookingByIDParams{  clerkID, err := uuid.Parse(claims.Subject) 
 ClerkID:   clerkID,  if err != nil { 
 ID:        request.BookingId,  return nil, err 
 StartDate: request.Body.StartDate,  } 
 EndDate:   request.Body.EndDate,  
 })  deletedBooking, err := 
 if err != nil { h.service.DeleteBookingByID(ctx, 
 return nil, err repository.DeleteBookingByIDParams{ 
 }  ClerkID: clerkID, 
  ID:      request.BookingId, 
 return  }) 
api.UpdateBookingByID200JSONResponse{  if err != nil { 
 Id:          updatedBooking.ID,  return nil, err 
 CreatedAt:   updatedBooking.CreatedAt,  } 
 UpdatedAt:    
updatedBooking.UpdatedAt,  return 
 StartDate:   updatedBooking.StartDate, api.DeleteBookingByID200JSONResponse{ 
 EndDate:     updatedBooking.EndDate,  Id:          deletedBooking.ID, 
 UserId:      updatedBooking.UserID,  CreatedAt:   deletedBooking.CreatedAt, 
 ApartmentId:  UpdatedAt:   
updatedBooking.ApartmentID, deletedBooking.UpdatedAt, 
 }, nil  StartDate:   deletedBooking.StartDate, 
}  EndDate:     deletedBooking.EndDate, 
  UserId:      deletedBooking.UserID, 
func (h *Handler) DeleteBookingByID(ctx  ApartmentId: 
context.Context, request deletedBooking.ApartmentID, 
api.DeleteBookingByIDRequestObject)  }, nil 
(api.DeleteBookingByIDResponseObject, error) { } 
 claims, ok :=  
clerk.SessionClaimsFromContext(ctx) func (h *Handler) ListUsersBooking(ctx 
 if !ok { context.Context, request 
 return api.ListUsersBookingRequestObject) 
api.DeleteBookingByID401JSONResponse{ (api.ListUsersBookingResponseObject, error) { 
  claims, ok := 
 UnauthorizedErrorJSONResponse: clerk.SessionClaimsFromContext(ctx) 
49 
 if !ok {  
 return func (h *Handler) ListApartmentBookings(ctx 
api.ListUsersBooking401JSONResponse{ context.Context, request 
 api.ListApartmentBookingsRequestObject) 
 UnauthorizedErrorJSONResponse: (api.ListApartmentBookingsResponseObject, error) { 
api.UnauthorizedErrorJSONResponse{  var fromDate, toDate pgtype.Date 
   Access:  if request.Params.AvailableDuration != nil { 
"unauthorized",  fromDate = 
  }, convertTimeToPgTypeDate(request.Params.AvailableDur
 }, nil ation.FromDate) 
 }  toDate = 
 convertTimeToPgTypeDate(request.Params.AvailableDur
 clerkID, err := uuid.Parse(claims.Subject) ation.ToDate) 
 if err != nil {  } 
 return nil, err  
 }  apartments, err := 
 h.service.ListApartmentBookings(ctx, 
 bookings, err := h.service.ListUserBookings(ctx, repository.ListApartmentBookingsParams{ 
clerkID)  ApartmentID: request.ApartmentId, 
 if err != nil {  FromDate:    fromDate, 
 return nil, err  ToDate:      toDate, 
 }  }) 
  if err != nil { 
 res :=  return nil, err 
make(api.ListUsersBooking200JSONResponse,  } 
len(bookings))  
 for i, booking := range bookings {  res := 
 res[i] = api.Booking{ make(api.ListApartmentBookings200JSONResponse, 
  Id:          booking.ID, len(apartments)) 
  CreatedAt:    for i, apartment := range apartments { 
booking.CreatedAt,  res[i] = api.Booking{ 
  UpdatedAt:     Id:          apartment.ID, 
booking.UpdatedAt,   CreatedAt:   
  StartDate:   booking.StartDate, apartment.CreatedAt, 
  EndDate:     booking.EndDate,   UpdatedAt:   
  UserId:      booking.UserID, apartment.UpdatedAt, 
  ApartmentId:   StartDate:   
booking.ApartmentID, apartment.StartDate, 
 }   EndDate:     
 } apartment.EndDate, 
 return res, nil   UserId:      apartment.UserID, 
}   ApartmentId: 
50 
apartment.ApartmentID, body", slog.Any("err", err)) 
 }  w.WriteHeader(http.StatusBadRequest) 
 }  return 
 return res, nil  } 
}  
  switch request.Type { 
clerk_webhook.go  case dto.ClerkWebhookTypeUserCreated: 
  if err := 
package handler h.service.CreateUser(r.Context(), request.Data.ID); err != 
 nil { 
import (   h.log.Error("failed to create 
 "encoding/json" user", slog.Any("err", err)) 
 "io"  
 "log/slog"  w.WriteHeader(http.StatusInternalServerError) 
 "net/http"   return 
  } 
 "github.com/SerhioTis/book-apartment-  case dto.ClerkWebhookTypeUserDeleted: 
app/internal/dto"  if err := 
) h.service.DeleteUser(r.Context(), request.Data.ID); err != 
 nil { 
func (h *Handler) WebhooksClerk(w   h.log.Error("failed to delete 
http.ResponseWriter, r *http.Request) { user", slog.Any("err", err)) 
 payload, err := io.ReadAll(r.Body)  
 if err != nil {  w.WriteHeader(http.StatusInternalServerError) 
 h.log.Error("failed to read request body",   return 
slog.Any("err", err))  } 
 w.WriteHeader(http.StatusBadRequest)  } 
 return  
 }  w.WriteHeader(http.StatusOK) 
 } 
 if err := h.svixWebhookVerifier.Verify(payload,  
r.Header); err != nil { handler.go 
 h.log.Error("failed to verify webhook",  
slog.Any("err", err)) package handler 
 w.WriteHeader(http.StatusBadRequest)  
 return import ( 
 }  "context" 
  "log/slog" 
 var request dto.ClerkWebhookRequest  "strings" 
 if err := json.Unmarshal(payload, &request); err  "time" 
!= nil {  
 h.log.Error("failed to unmarshal request  "github.com/SerhioTis/book-apartment-
51 
app/internal/service"  ai.RequestValidationInput.Request = 
 "github.com/clerk/clerk-sdk-go/v2" ai.RequestValidationInput.Request.WithContext(clerk.Con
 "github.com/clerk/clerk-sdk-go/v2/jwt" textWithSessionClaims(ctx, claims)) 
 "github.com/getkin/kin-openapi/openapi3filter"  return nil 
 "github.com/jackc/pgx/v5/pgtype" } 
 svix "github.com/svix/svix-webhooks/go"  
) func convertTimeToPgTypeDate(t *time.Time) 
 pgtype.Date { 
type Handler struct {  if t == nil || t.IsZero() { 
 log                 *slog.Logger  return pgtype.Date{} 
 service             *service.Service  } 
 svixWebhookVerifier *svix.Webhook  
}  return pgtype.Date{Time: *t, Valid: true} 
 } 
func NewHandler(  
 log *slog.Logger, apartment_comment.go 
 service *service.Service,  
 svixWebhookVerifier *svix.Webhook, package service 
) *Handler {  
 return &Handler{ import ( 
 log:                 log,  "context" 
 service:             service,  "log/slog" 
 svixWebhookVerifier:  
svixWebhookVerifier,  "github.com/SerhioTis/book-apartment-
 } app/internal/repository" 
}  "github.com/google/uuid" 
 ) 
func (h *Handler) AuthMiddleware(ctx  
context.Context, ai *openapi3filter.AuthenticationInput) func (s *Service) CreateApartmentComment(ctx 
error { context.Context, arg 
 authHeader := repository.CreateApartmentCommentParams) 
ai.RequestValidationInput.Request.Header.Get("Authoriza (repository.ApartmentComment, error) { 
tion")  apartmentComment, err := 
 sessionToken := strings.TrimPrefix(authHeader, s.repo.CreateApartmentComment(ctx, arg) 
"Bearer ")  if err != nil { 
 claims, err := jwt.Verify(ctx, &jwt.VerifyParams{  s.log.Error("failed to create apartment 
 Token: sessionToken, comment", slog.Any("err", err)) 
 })  return repository.ApartmentComment{}, 
 if err != nil { err 
 return ai.NewError(err)  } 
 }  
  return apartmentComment, nil 
52 
} comments", slog.Any("err", err)) 
  return nil, err 
func (s *Service)  } 
UpdateApartmentCommentByID(ctx context.Context, arg  
repository.UpdateApartmentCommentByIDParams)  return apartmentComments, nil 
(repository.ApartmentComment, error) { } 
 apartmentComment, err :=  
s.repo.UpdateApartmentCommentByID(ctx, arg) func (s *Service) 
 if err != nil { GetApartmentCommentByID(ctx context.Context, id 
 s.log.Error("failed to update apartment uuid.UUID) (repository.ApartmentComment, error) { 
comment", slog.Any("err", err))  apartmentComment, err := 
 return repository.ApartmentComment{}, s.repo.GetApartmentCommentByID(ctx, id) 
err  if err != nil { 
 }  s.log.Error("failed to get apartment 
 comment", slog.Any("err", err)) 
 return apartmentComment, nil  return repository.ApartmentComment{}, 
} err 
  } 
func (s *Service)  
DeleteApartmentCommentByID(ctx context.Context, arg  return apartmentComment, nil 
repository.DeleteApartmentCommentByIDParams) } 
(repository.ApartmentComment, error) {  
 apartmentComment, err := apartment_rating.go 
s.repo.DeleteApartmentCommentByID(ctx, arg)  
 if err != nil { package service 
 s.log.Error("failed to delete apartment  
comment", slog.Any("err", err)) import ( 
 return repository.ApartmentComment{},  "context" 
err  "log/slog" 
 }  
  "github.com/SerhioTis/book-apartment-
 return apartmentComment, nil app/internal/repository" 
}  "github.com/google/uuid" 
 ) 
func (s *Service) ListApartmentComments(ctx  
context.Context, arg func (s *Service) RateApartment(ctx 
repository.ListApartmentCommentsParams) context.Context, arg repository.RateApartmentParams) 
([]repository.ApartmentComment, error) { (repository.ApartmentRating, error) { 
 apartmentComments, err :=  rating, err := s.repo.RateApartment(ctx, arg) 
s.repo.ListApartmentComments(ctx, arg)  if err != nil { 
 if err != nil {  s.log.Error("failed to create apartment 
 s.log.Error("failed to get apartment rating", slog.Any("err", err)) 
53 
 return repository.ApartmentRating{}, err  if err != nil { 
 }  s.log.Error("failed to get apartment 
 ratings", slog.Any("err", err)) 
 return rating, nil  return 
} repository.GetAverageApartmentRatingRow{}, err 
  } 
func (s *Service)  
RemoveApartmentRatingByID(ctx context.Context, arg  return rating, nil 
repository.DeleteApartmentRatingByIDParams) } 
(repository.ApartmentRating, error) {  
 rating, err := func (s *Service) GetUserApartmentRating(ctx 
s.repo.DeleteApartmentRatingByID(ctx, arg) context.Context, arg 
 if err != nil { repository.GetUserApartmentRatingParams) 
 s.log.Error("failed to delete apartment (repository.ApartmentRating, error) { 
rating", slog.Any("err", err))  rating, err := s.repo.GetUserApartmentRating(ctx, 
 return repository.ApartmentRating{}, err arg) 
 }  if err != nil { 
  s.log.Error("failed to get user apartment 
 return rating, nil rating", slog.Any("err", err)) 
}  return repository.ApartmentRating{}, err 
  } 
func (s *Service)  
RemoveApartmentRatingByApartmentID(ctx  return rating, nil 
context.Context, arg } 
repository.DeleteApartmentRatingByApartmentIDParams)  
(repository.ApartmentRating, error) { apartment.go 
 rating, err :=  
s.repo.DeleteApartmentRatingByApartmentID(ctx, arg) package service 
 if err != nil {  
 s.log.Error("failed to delete apartment import ( 
rating", slog.Any("err", err))  "context" 
 return repository.ApartmentRating{}, err  "log/slog" 
 }  
  "github.com/SerhioTis/book-apartment-
 return rating, nil app/internal/repository" 
}  "github.com/google/uuid" 
 ) 
func (s *Service) GetApartmentRating(ctx  
context.Context, apartmentID uuid.UUID) func (s *Service) CreateApartment(ctx 
(repository.GetAverageApartmentRatingRow, error) { context.Context, apartmentParams 
 rating, err := repository.CreateApartmentParams) 
s.repo.GetAverageApartmentRating(ctx, apartmentID) (repository.Apartment, error) { 
54 
 apartment, err := s.repo.CreateApartment(ctx, context.Context, id uuid.UUID) (repository.Apartment, 
apartmentParams) error) { 
 if err != nil {  apartment, err := s.repo.GetApartmentByID(ctx, 
 s.log.Error("failed to create apartment", id) 
slog.Any("err", err))  if err != nil { 
 return repository.Apartment{}, err  s.log.Error("failed to get apartment", 
 } slog.Any("err", err)) 
  return repository.Apartment{}, err 
 return apartment, nil  } 
}  
  return apartment, nil 
func (s *Service) UpdateApartmentByID(ctx } 
context.Context, arg  
repository.UpdateApartmentByIDParams) func (s *Service) ListApartments(ctx 
(repository.Apartment, error) { context.Context, arg repository.ListApartmentsParams) 
 apartment, err := ([]repository.ListApartmentsRow, error) { 
s.repo.UpdateApartmentByID(ctx, arg)  apartments, err := s.repo.ListApartments(ctx, arg) 
 if err != nil {  if err != nil { 
 s.log.Error("failed to update apartment",  s.log.Error("failed to get apartments", 
slog.String("id", arg.ID.String()), slog.Any("err", err)) slog.Any("err", err)) 
 return repository.Apartment{}, err  return nil, err 
 }  } 
  
 return apartment, nil  return apartments, nil 
} } 
  
func (s *Service) DeleteApartmentByID(ctx func (s *Service) ListApartmentsByOwnerID(ctx 
context.Context, arg context.Context, ownerID uuid.UUID) 
repository.DeleteApartmentByIDParams) ([]repository.ListUsersApartmentsRow, error) { 
(repository.Apartment, error) {  apartments, err := 
 deletedApartment, err := s.repo.ListUsersApartments(ctx, ownerID) 
s.repo.DeleteApartmentByID(ctx, arg)  if err != nil { 
 if err != nil {  s.log.Error("failed to get apartments", 
 s.log.Error("failed to delete apartment", slog.Any("err", err)) 
slog.String("id", arg.ID.String()), slog.Any("err", err))  return nil, err 
 return repository.Apartment{}, err  } 
 }  
  return apartments, nil 
 return deletedApartment, nil } 
}  
 booking.go 
func (s *Service) GetApartmentByID(ctx  
55 
package service repository.DeleteBookingByIDParams) 
 (repository.Booking, error) { 
import (  deletedBooking, err := 
 "context" s.repo.DeleteBookingByID(ctx, arg) 
 "log/slog"  if err != nil { 
  s.log.Error("failed to cancel booking", 
 "github.com/SerhioTis/book-apartment- slog.Any("err", err)) 
app/internal/repository"  return repository.Booking{}, err 
 "github.com/google/uuid"  } 
)  
  return deletedBooking, nil 
func (s *Service) BookApartment(ctx } 
context.Context, arg repository.CreateBookingParams)  
(repository.Booking, error) { func (s *Service) ListUserBookings(ctx 
 apartmentBooking, err := context.Context, clerkID uuid.UUID) 
s.repo.CreateBooking(ctx, arg) ([]repository.Booking, error) { 
 if err != nil {  bookings, err := s.repo.ListUsersBookings(ctx, 
 s.log.Error("failed to book apartment", clerkID) 
slog.Any("err", err))  if err != nil { 
 return repository.Booking{}, err  s.log.Error("failed to get user bookings", 
 } slog.Any("err", err)) 
  return nil, err 
 return apartmentBooking, nil  } 
}  
  return bookings, nil 
func (s *Service) UpdateBookingByID(ctx } 
context.Context, arg  
repository.UpdateBookingByIDParams) func (s *Service) ListApartmentBookings(ctx 
(repository.Booking, error) { context.Context, arg 
 booking, err := s.repo.UpdateBookingByID(ctx, repository.ListApartmentBookingsParams) 
arg) ([]repository.Booking, error) { 
 if err != nil {  bookings, err := 
 s.log.Error("failed to update booking", s.repo.ListApartmentBookings(ctx, arg) 
slog.Any("err", err))  if err != nil { 
 return repository.Booking{}, err  s.log.Error("failed to get apartment 
 } bookings", slog.Any("err", err)) 
  return nil, err 
 return booking, nil  } 
}  
  return bookings, nil 
func (s *Service) DeleteBookingByID(ctx } 
context.Context, arg  
56 
service.go { 
  s.log.Error("failed to create user", 
package service slog.Any("err", err)) 
  return err 
import (  } 
 "log/slog"  
  s.log.Info("user created", slog.String("clerk_id", 
 "github.com/SerhioTis/book-apartment- clerkID)) 
app/internal/repository"  return nil 
 "github.com/clerk/clerk-sdk-go/v2/user" } 
)  
 func (s *Service) DeleteUser(ctx context.Context, 
type Service struct { clerkID string) error { 
 log             *slog.Logger  if err := s.repo.DeleteUserByClerkID(ctx, 
 repo            *repository.Queries clerkID); err != nil { 
 clerkUserClient *user.Client  s.log.Error("failed to delete user", 
} slog.Any("err", err)) 
  return err 
func NewService(  } 
 log *slog.Logger,  
 repo *repository.Queries,  s.log.Info("user deleted", slog.String("clerk_id", 
 clerkUserClient *user.Client, clerkID)) 
) *Service {  return nil 
 return &Service{ } 
 log:             log,  
 repo:            repo, app.go 
 clerkUserClient: clerkUserClient,  
 } package app 
}  
 import ( 
user.go  "context" 
  "errors" 
package service  "fmt" 
  "log/slog" 
import (  "net" 
 "context"  "net/http" 
 "log/slog"  "os" 
)  "os/signal" 
  "strconv" 
func (s *Service) CreateUser(ctx context.Context,  "syscall" 
clerkID string) error {  "time" 
 if err := s.repo.CreateUser(ctx, clerkID); err != nil  
57 
 "github.com/SerhioTis/book-apartment-  conn, err := pgxpool.New(ctx, connString) 
app/config"  if err != nil { 
 "github.com/SerhioTis/book-apartment-  logger.Error("failed to connect to 
app/internal/handler" database", slog.Any("err", err)) 
 "github.com/SerhioTis/book-apartment-  return 
app/internal/repository"  } 
 "github.com/SerhioTis/book-apartment-  defer conn.Close() 
app/internal/service"  
 httpserver "github.com/SerhioTis/book-  if err := conn.Ping(ctx); err != nil { 
apartment-app/pkg/server/http"  logger.Error("failed to ping database", 
 slog.Any("err", err)) 
 "github.com/clerk/clerk-sdk-go/v2"  return 
 "github.com/clerk/clerk-sdk-go/v2/user"  } 
 "github.com/jackc/pgx/v5/pgxpool"  
 svix "github.com/svix/svix-webhooks/go"  clerkClientConfig := &clerk.ClientConfig{ 
)  BackendConfig: clerk.BackendConfig{ 
   Key: &cfg.Clerk.SecretKey, 
func Run() {  }, 
 ctx, cancel :=  } 
signal.NotifyContext(context.Background(), os.Interrupt,  clerkUserClient := 
syscall.SIGINT, syscall.SIGTERM) user.NewClient(clerkClientConfig) 
 defer cancel()  
  svixWebhookVerifier, err := 
 logger := svix.NewWebhook(cfg.Clerk.WebhookSigningSecret) 
slog.New(slog.NewJSONHandler(os.Stdout,  if err != nil { 
&slog.HandlerOptions{  logger.Error("failed to create svix 
 Level: slog.LevelDebug, webhook verifier", slog.Any("err", err)) 
 }))  return 
 slog.SetDefault(logger)  } 
  
 cfg, err :=  repo := repository.New(conn) 
config.ReadConfig("config/config.yaml")  service := service.NewService(logger, repo, 
 if err != nil { clerkUserClient) 
 logger.Error("failed to read config",  handler := handler.NewHandler(logger, service, 
slog.Any("err", err)) svixWebhookVerifier) 
 return  
 }  httpAddr := net.JoinHostPort(cfg.HTTP.Host, 
 strconv.Itoa(cfg.HTTP.Port)) 
 connString :=  httpServer, err := httpserver.NewServer(httpAddr, 
fmt.Sprintf("postgres://%s:%s@%s:%d", handler) 
cfg.Postgresql.User, cfg.Postgresql.Password,  if err != nil { 
cfg.Postgresql.Host, cfg.Postgresql.Port)  logger.Error("failed to create http 
58 
server", slog.Any("err", err))  "net/http" 
 return  "time" 
 }  
  "github.com/SerhioTis/book-apartment-app/api" 
 go func() {  "github.com/SerhioTis/book-apartment-
 logger.Info("starting http server", "addr", app/internal/handler" 
httpAddr)  "github.com/getkin/kin-openapi/openapi3" 
 if err := httpServer.Run(); err != nil {  "github.com/getkin/kin-openapi/openapi3filter" 
  cancel()  "github.com/gorilla/mux" 
  if !errors.Is(err,  nethttpmiddleware "github.com/oapi-
http.ErrServerClosed) { codegen/nethttp-middleware" 
   logger.Error("failed to ) 
run http server", slog.Any("err", err))  
  } type Server struct { 
  s *http.Server 
  return } 
 }  
 }() func NewServer(addr string, h *handler.Handler) 
 (*Server, error) { 
 <-ctx.Done()  spec, err := 
 openapi3.NewLoader().LoadFromFile("./api/openapi.yaml
 // graceful shutdown ") 
 ctx, cancel =  if err != nil { 
context.WithTimeout(context.Background(),  return nil, err 
5*time.Second)  } 
 defer cancel()  spec.Servers = nil 
 slog.Info("graceful shutdown")  
  r := mux.NewRouter() 
 if err := httpServer.Stop(ctx); err != nil {  r.HandleFunc("/webhooks/clerk", 
 logger.Error("failed to stop http server", h.WebhooksClerk) 
slog.Any("err", err))  r.Use(mux.CORSMethodMiddleware(r)) 
 }  mw := 
 nethttpmiddleware.OapiRequestValidatorWithOptions(spe
 conn.Close() c, &nethttpmiddleware.Options{ 
}  Options: openapi3filter.Options{ 
   AuthenticationFunc: 
http.go h.AuthMiddleware, 
  }, 
package httpserver  }) 
  r.Use(mw) 
import (  
 "context"  si := api.NewStrictHandler(h, nil) 
59 
 handler := api.HandlerFromMux(si, r) 20250508121053_create_users_table.sql 
 s := &http.Server{  
 Addr:         addr, -- +goose Up 
 Handler:      handler, -- +goose StatementBegin 
 WriteTimeout: 15 * time.Second, CREATE TABLE users ( 
 ReadTimeout:  15 * time.Second,     id UUID PRIMARY KEY DEFAULT 
 IdleTimeout:  60 * time.Second, gen_random_uuid(), 
 }     created_at TIMESTAMPTZ NOT NULL 
 DEFAULT NOW(), 
 return &Server{     updated_at TIMESTAMPTZ NOT NULL 
 s: s, DEFAULT NOW(), 
 }, nil     clerk_id VARCHAR(100) NOT NULL 
} UNIQUE 
 ); 
func (s *Server) Run() error {  
 return s.s.ListenAndServe() CREATE TRIGGER update_users_updated_at 
} BEFORE UPDATE ON users 
 FOR EACH ROW 
func (s *Server) Stop(ctx context.Context) error { EXECUTE PROCEDURE 
 return s.s.Shutdown(ctx) update_updated_at_column(); 
}  
 -- +goose StatementEnd 
20250508111929_create_update_updated_at_colu  
mn_function.sql -- +goose Down 
 -- +goose StatementBegin 
-- +goose Up DROP TRIGGER update_users_updated_at ON 
-- +goose StatementBegin users; 
CREATE FUNCTION DROP TABLE users; 
update_updated_at_column() -- +goose StatementEnd 
RETURNS TRIGGER AS $$  
BEGIN 20250508184238_create_apartment_table.sql 
    NEW.updated_at = now();  
    RETURN NEW; -- +goose Up 
END; -- +goose StatementBegin 
$$ LANGUAGE plpgsql; CREATE TABLE apartments ( 
-- +goose StatementEnd     id UUID PRIMARY KEY DEFAULT 
 gen_random_uuid(), 
-- +goose Down     created_at TIMESTAMPTZ NOT NULL 
-- +goose StatementBegin DEFAULT NOW(), 
DROP FUNCTION update_updated_at_column;     updated_at TIMESTAMPTZ NOT NULL 
-- +goose StatementEnd DEFAULT NOW(), 
     name TEXT NOT NULL, 
60 
    description TEXT NOT NULL, apartments(id) ON DELETE CASCADE NOT NULL, 
    address TEXT NOT NULL,     CONSTRAINT 
    image_urls TEXT[], unique_user_apartment_rating_constraint UNIQUE 
    max_guests INTEGER NOT NULL, (user_id, apartment_id) 
    owner_id UUID REFERENCES users(id) ON ); 
DELETE CASCADE NOT NULL  
); CREATE TRIGGER 
 update_apartment_ratings_updated_at 
CREATE TRIGGER BEFORE UPDATE ON apartment_ratings 
update_apartments_updated_at FOR EACH ROW 
BEFORE UPDATE ON apartments EXECUTE PROCEDURE 
FOR EACH ROW update_updated_at_column(); 
EXECUTE PROCEDURE -- +goose StatementEnd 
update_updated_at_column();  
-- +goose StatementEnd -- +goose Down 
 -- +goose StatementBegin 
-- +goose Down DROP TRIGGER 
-- +goose StatementBegin update_apartment_ratings_updated_at ON 
DROP TRIGGER update_apartments_updated_at apartment_ratings; 
ON apartments; DROP TABLE apartment_ratings; 
DROP TABLE apartments; -- +goose StatementEnd 
-- +goose StatementEnd  
 20250522190543_create_apartment_comments_t
20250508184511_create_booking_table.sql able.sql 
  
20250509002723_create_apartment_ratings_table -- +goose Up 
.sql -- +goose StatementBegin 
 CREATE TABLE apartment_comments ( 
-- +goose Up     id UUID PRIMARY KEY DEFAULT 
-- +goose StatementBegin gen_random_uuid(), 
CREATE TABLE apartment_ratings (     created_at TIMESTAMPTZ NOT NULL 
    id UUID PRIMARY KEY DEFAULT DEFAULT NOW(), 
gen_random_uuid(),     updated_at TIMESTAMPTZ NOT NULL 
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 
DEFAULT NOW(),     content TEXT NOT NULL, 
    updated_at TIMESTAMPTZ NOT NULL     user_id UUID REFERENCES users(id) ON 
DEFAULT NOW(), DELETE CASCADE NOT NULL, 
    rating SMALLINT NOT NULL CHECK     apartment_id UUID REFERENCES 
(rating >= 1 AND rating <= 5), apartments(id) ON DELETE CASCADE NOT NULL 
    user_id UUID REFERENCES users(id) ON ); 
DELETE CASCADE NOT NULL,  
    apartment_id UUID REFERENCES CREATE TRIGGER 
61 
update_apartment_comments_updated_at -- name: ListApartmentComments :many 
BEFORE UPDATE ON apartment_comments SELECT * FROM apartment_comments 
FOR EACH ROW WHERE apartment_id = $1 
EXECUTE PROCEDURE LIMIT $2 
update_updated_at_column(); OFFSET $3; 
-- +goose StatementEnd  
 -- name: GetApartmentCommentByID :one 
-- +goose Down SELECT * FROM apartment_comments 
-- +goose StatementBegin WHERE id = $1; 
DROP TRIGGER  
update_apartment_comments_updated_at ON apartment_rating.sql 
apartment_comments;  
DROP TABLE apartment_comments; -- name: RateApartment :one 
-- +goose StatementEnd INSERT INTO apartment_ratings (rating, 
 apartment_id, user_id) 
apartment_comment.sql SELECT $1, $2, users.id 
 FROM users 
-- name: CreateApartmentComment :one WHERE users.clerk_id = @clerk_id::UUID 
INSERT INTO apartment_comments (content, ON CONFLICT 
apartment_id, user_id) (unique_user_apartment_rating_constraint) DO UPDATE 
SELECT $1, $2, users.id SET rating = EXCLUDED.rating 
FROM users RETURNING *; 
WHERE users.clerk_id = @clerk_id::UUID  
RETURNING *; -- name: DeleteApartmentRatingByID :one 
 DELETE FROM apartment_ratings 
-- name: UpdateApartmentCommentByID :one USING users  
UPDATE apartment_comments WHERE apartment_ratings.id = $1 AND 
SET content = $2 users.clerk_id = @clerk_id::UUID AND 
FROM users apartment_ratings.user_id = users.id 
WHERE apartment_comments.id = $1 RETURNING *; 
AND users.clerk_id = @clerk_id::UUID AND  
apartment_comments.user_id = users.id -- name: DeleteApartmentRatingByApartmentID 
RETURNING apartment_comments.*; :one 
 DELETE FROM apartment_ratings 
-- name: DeleteApartmentCommentByID :one USING users  
DELETE FROM apartment_comments WHERE apartment_ratings.apartment_id = $1 
USING users  AND users.clerk_id = @clerk_id::UUID AND 
WHERE apartment_comments.id = $1 AND apartment_ratings.user_id = users.id 
users.clerk_id = @clerk_id::UUID AND RETURNING *; 
apartment_comments.user_id = users.id  
RETURNING *; -- name: GetAverageApartmentRating :one 
 SELECT apartment_id, AVG(rating), COUNT(*) 
62 
FROM apartment_ratings  -- name: ListApartments :many 
WHERE apartment_id = $1; SELECT id, name, description, address, 
 image_urls, owner_id, max_guests FROM apartments  
-- name: GetUserApartmentRating :one WHERE (sqlc.narg(name)::VARCHAR IS NOT 
SELECT apartment_ratings.* FROM NULL OR name ILIKE '%' || 
apartment_ratings sqlc.narg(name)::VARCHAR || '%') 
JOIN users ON users.id = AND (sqlc.narg(address)::VARCHAR IS NOT 
apartment_ratings.user_id NULL OR address ILIKE '%' || @address::VARCHAR || 
WHERE apartment_ratings.apartment_id = $1 '%') 
AND users.clerk_id = @clerk_id::UUID; AND (sqlc.narg(max_guests)::INT IS NOT 
 NULL OR max_guests >= sqlc.narg(max_guests)::INT) 
apartment.sql AND ( 
         (sqlc.narg(from_date)::DATE IS NULL 
-- name: CreateApartment :one AND sqlc.narg(to_date)::DATE IS NULL) 
INSERT INTO apartments (name, description,         OR NOT EXISTS ( 
address, image_urls, max_guests, owner_id)             SELECT 1 
SELECT $1, $2, $3, $4, $5, users.id             FROM bookings b 
FROM users             WHERE b.apartment_id = apartments.id 
WHERE clerk_id = @clerk_id::UUID             AND (b.start_date, b.end_date) 
RETURNING *; OVERLAPS (sqlc.narg(from_date)::DATE, 
 sqlc.narg(to_date)::DATE) 
-- name: UpdateApartmentByID :one         ) 
UPDATE apartments     ) 
SET name = $2, description = $3, address = $4, ORDER BY created_at 
image_urls = $5, max_guests = $6 LIMIT $1 
FROM users OFFSET $2; 
WHERE apartments.id = $1  
AND users.clerk_id = @clerk_id::UUID AND -- name: ListUsersApartments :many 
apartments.owner_id = users.id SELECT apartments.id, apartments.name, 
RETURNING apartments.*; apartments.description, apartments.address, 
 apartments.image_urls, apartments.owner_id FROM 
-- name: DeleteApartmentByID :one apartments 
DELETE FROM apartments JOIN users ON users.id = apartments.owner_id 
USING users  WHERE users.clerk_id = @clerk_id::UUID AND 
WHERE apartments.id = $1 apartments.owner_id = users.id; 
AND users.clerk_id = @clerk_id::UUID AND  
apartments.owner_id = users.id booking.sql 
RETURNING *;  
 -- name: CreateBooking :one 
-- name: GetApartmentByID :one INSERT INTO bookings (start_date, end_date, 
SELECT * FROM apartments WHERE id = $1; apartment_id, user_id) 
 SELECT $1, $2, $3, users.id 
63 
FROM users INSERT INTO users (clerk_id) 
WHERE clerk_id = @clerk_id::UUID VALUES ($1); 
RETURNING *;  
 -- name: DeleteUserByClerkID :exec 
-- name: UpdateBookingByID :one DELETE FROM users 
UPDATE bookings WHERE clerk_id = $1; 
SET start_date = $2, end_date = $3  
FROM users apartment_comment.sql.go 
WHERE bookings.id = $1  
AND users.clerk_id = @clerk_id::UUID AND // Code generated by sqlc. DO NOT EDIT. 
apartments.owner_id = users.id // versions: 
RETURNING bookings.*; //   sqlc v1.28.0 
 // source: apartment_comment.sql 
-- name: DeleteBookingByID :one  
DELETE FROM bookings package repository 
USING users   
WHERE bookings.id = $1 import ( 
AND users.clerk_id = @clerk_id::UUID AND  "context" 
bookings.user_id = users.id  
RETURNING *;  "github.com/google/uuid" 
 ) 
-- name: GetBookingByID :one  
SELECT id, start_date, end_date, apartment_id, const createApartmentComment = `-- name: 
user_id FROM bookings WHERE id = $1; CreateApartmentComment :one 
 INSERT INTO apartment_comments (content, 
-- name: ListUsersBookings :many apartment_id, user_id) 
SELECT bookings.* FROM bookings SELECT $1, $2, users.id 
JOIN users FROM users 
ON bookings.user_id = users.id WHERE users.clerk_id = $3::UUID 
WHERE users.clerk_id = @clerk_id::UUID; RETURNING id, created_at, updated_at, content, 
 user_id, apartment_id 
-- name: ListApartmentBookings :many ` 
SELECT bookings.* FROM bookings  
WHERE apartment_id = $1 type CreateApartmentCommentParams struct { 
AND (start_date, end_date) OVERLAPS  Content     string    `db:"content" json:"content"` 
(COALESCE(sqlc.narg(from_date)::DATE, NOW() -  ApartmentID uuid.UUID `db:"apartment_id" 
INTERVAL '1 month'), json:"apartment_id"` 
COALESCE(sqlc.narg(to_date)::DATE, NOW()));  ClerkID     uuid.UUID `db:"clerk_id" 
 json:"clerk_id"` 
user.sql } 
  
-- name: CreateUser :exec func (q *Queries) CreateApartmentComment(ctx 
64 
context.Context, arg CreateApartmentCommentParams)  err := row.Scan( 
(ApartmentComment, error) {  &i.ID, 
 row := q.db.QueryRow(ctx,  &i.CreatedAt, 
createApartmentComment, arg.Content, arg.ApartmentID,  &i.UpdatedAt, 
arg.ClerkID)  &i.Content, 
 var i ApartmentComment  &i.UserID, 
 err := row.Scan(  &i.ApartmentID, 
 &i.ID,  ) 
 &i.CreatedAt,  return i, err 
 &i.UpdatedAt, } 
 &i.Content,  
 &i.UserID, const getApartmentCommentByID = `-- name: 
 &i.ApartmentID, GetApartmentCommentByID :one 
 ) SELECT id, created_at, updated_at, content, 
 return i, err user_id, apartment_id FROM apartment_comments 
} WHERE id = $1 
 ` 
const deleteApartmentCommentByID = `-- name:  
DeleteApartmentCommentByID :one func (q *Queries) 
DELETE FROM apartment_comments GetApartmentCommentByID(ctx context.Context, id 
USING users  uuid.UUID) (ApartmentComment, error) { 
WHERE apartment_comments.id = $1 AND  row := q.db.QueryRow(ctx, 
users.clerk_id = $2::UUID AND getApartmentCommentByID, id) 
apartment_comments.user_id = users.id  var i ApartmentComment 
RETURNING id, created_at, updated_at, content,  err := row.Scan( 
user_id, apartment_id  &i.ID, 
`  &i.CreatedAt, 
  &i.UpdatedAt, 
type DeleteApartmentCommentByIDParams  &i.Content, 
struct {  &i.UserID, 
 ID      uuid.UUID `db:"id" json:"id"`  &i.ApartmentID, 
 ClerkID uuid.UUID `db:"clerk_id"  ) 
json:"clerk_id"`  return i, err 
} } 
  
func (q *Queries) const listApartmentComments = `-- name: 
DeleteApartmentCommentByID(ctx context.Context, arg ListApartmentComments :many 
DeleteApartmentCommentByIDParams) SELECT id, created_at, updated_at, content, 
(ApartmentComment, error) { user_id, apartment_id FROM apartment_comments 
 row := q.db.QueryRow(ctx, WHERE apartment_id = $1 
deleteApartmentCommentByID, arg.ID, arg.ClerkID) LIMIT $2 
 var i ApartmentComment OFFSET $3 
65 
` UPDATE apartment_comments 
 SET content = $2 
type ListApartmentCommentsParams struct { FROM users 
 ApartmentID uuid.UUID `db:"apartment_id" WHERE apartment_comments.id = $1 
json:"apartment_id"` AND users.clerk_id = $3::UUID AND 
 Limit       int32     `db:"limit" json:"limit"` apartment_comments.user_id = users.id 
 Offset      int32     `db:"offset" json:"offset"` RETURNING apartment_comments.id, 
} apartment_comments.created_at, 
 apartment_comments.updated_at, 
func (q *Queries) ListApartmentComments(ctx apartment_comments.content, 
context.Context, arg ListApartmentCommentsParams) apartment_comments.user_id, 
([]ApartmentComment, error) { apartment_comments.apartment_id 
 rows, err := q.db.Query(ctx, ` 
listApartmentComments, arg.ApartmentID, arg.Limit,  
arg.Offset) type UpdateApartmentCommentByIDParams 
 if err != nil { struct { 
 return nil, err  ID      uuid.UUID `db:"id" json:"id"` 
 }  Content string    `db:"content" json:"content"` 
 defer rows.Close()  ClerkID uuid.UUID `db:"clerk_id" 
 var items []ApartmentComment json:"clerk_id"` 
 for rows.Next() { } 
 var i ApartmentComment  
 if err := rows.Scan( func (q *Queries) 
  &i.ID, UpdateApartmentCommentByID(ctx context.Context, arg 
  &i.CreatedAt, UpdateApartmentCommentByIDParams) 
  &i.UpdatedAt, (ApartmentComment, error) { 
  &i.Content,  row := q.db.QueryRow(ctx, 
  &i.UserID, updateApartmentCommentByID, arg.ID, arg.Content, 
  &i.ApartmentID, arg.ClerkID) 
 ); err != nil {  var i ApartmentComment 
  return nil, err  err := row.Scan( 
 }  &i.ID, 
 items = append(items, i)  &i.CreatedAt, 
 }  &i.UpdatedAt, 
 if err := rows.Err(); err != nil {  &i.Content, 
 return nil, err  &i.UserID, 
 }  &i.ApartmentID, 
 return items, nil  ) 
}  return i, err 
 } 
const updateApartmentCommentByID = `--  
name: UpdateApartmentCommentByID :one apartment_rating.sql.go 
66 
  err := row.Scan( 
// Code generated by sqlc. DO NOT EDIT.  &i.ID, 
// versions:  &i.CreatedAt, 
//   sqlc v1.28.0  &i.UpdatedAt, 
// source: apartment_rating.sql  &i.Rating, 
  &i.UserID, 
package repository  &i.ApartmentID, 
  ) 
import (  return i, err 
 "context" } 
  
 "github.com/google/uuid" const deleteApartmentRatingByID = `-- name: 
) DeleteApartmentRatingByID :one 
 DELETE FROM apartment_ratings 
const deleteApartmentRatingByApartmentID = `- USING users  
- name: DeleteApartmentRatingByApartmentID :one WHERE apartment_ratings.id = $1 AND 
DELETE FROM apartment_ratings users.clerk_id = $2::UUID AND 
USING users  apartment_ratings.user_id = users.id 
WHERE apartment_ratings.apartment_id = $1 RETURNING id, created_at, updated_at, rating, 
AND users.clerk_id = $2::UUID AND user_id, apartment_id 
apartment_ratings.user_id = users.id ` 
RETURNING id, created_at, updated_at, rating,  
user_id, apartment_id type DeleteApartmentRatingByIDParams struct { 
`  ID      uuid.UUID `db:"id" json:"id"` 
  ClerkID uuid.UUID `db:"clerk_id" 
type json:"clerk_id"` 
DeleteApartmentRatingByApartmentIDParams struct { } 
 ApartmentID uuid.UUID `db:"apartment_id"  
json:"apartment_id"` func (q *Queries) 
 ClerkID     uuid.UUID `db:"clerk_id" DeleteApartmentRatingByID(ctx context.Context, arg 
json:"clerk_id"` DeleteApartmentRatingByIDParams) (ApartmentRating, 
} error) { 
  row := q.db.QueryRow(ctx, 
func (q *Queries) deleteApartmentRatingByID, arg.ID, arg.ClerkID) 
DeleteApartmentRatingByApartmentID(ctx  var i ApartmentRating 
context.Context, arg  err := row.Scan( 
DeleteApartmentRatingByApartmentIDParams)  &i.ID, 
(ApartmentRating, error) {  &i.CreatedAt, 
 row := q.db.QueryRow(ctx,  &i.UpdatedAt, 
deleteApartmentRatingByApartmentID, arg.ApartmentID,  &i.Rating, 
arg.ClerkID)  &i.UserID, 
 var i ApartmentRating  &i.ApartmentID, 
67 
 )  
 return i, err type GetUserApartmentRatingParams struct { 
}  ApartmentID uuid.UUID `db:"apartment_id" 
 json:"apartment_id"` 
const getAverageApartmentRating = `-- name:  ClerkID     uuid.UUID `db:"clerk_id" 
GetAverageApartmentRating :one json:"clerk_id"` 
SELECT apartment_id, AVG(rating), COUNT(*) } 
FROM apartment_ratings   
WHERE apartment_id = $1 func (q *Queries) GetUserApartmentRating(ctx 
` context.Context, arg GetUserApartmentRatingParams) 
 (ApartmentRating, error) { 
type GetAverageApartmentRatingRow struct {  row := q.db.QueryRow(ctx, 
 ApartmentID uuid.UUID `db:"apartment_id" getUserApartmentRating, arg.ApartmentID, arg.ClerkID) 
json:"apartment_id"`  var i ApartmentRating 
 Avg         float64   `db:"avg" json:"avg"`  err := row.Scan( 
 Count       int64     `db:"count" json:"count"`  &i.ID, 
}  &i.CreatedAt, 
  &i.UpdatedAt, 
func (q *Queries)  &i.Rating, 
GetAverageApartmentRating(ctx context.Context,  &i.UserID, 
apartmentID uuid.UUID)  &i.ApartmentID, 
(GetAverageApartmentRatingRow, error) {  ) 
 row := q.db.QueryRow(ctx,  return i, err 
getAverageApartmentRating, apartmentID) } 
 var i GetAverageApartmentRatingRow  
 err := row.Scan(&i.ApartmentID, &i.Avg, const rateApartment = `-- name: RateApartment 
&i.Count) :one 
 return i, err INSERT INTO apartment_ratings (rating, 
} apartment_id, user_id) 
 SELECT $1, $2, users.id 
const getUserApartmentRating = `-- name: FROM users 
GetUserApartmentRating :one WHERE users.clerk_id = $3::UUID 
SELECT apartment_ratings.id, ON CONFLICT 
apartment_ratings.created_at, (unique_user_apartment_rating_constraint) DO UPDATE 
apartment_ratings.updated_at, apartment_ratings.rating, SET rating = EXCLUDED.rating 
apartment_ratings.user_id, apartment_ratings.apartment_id RETURNING id, created_at, updated_at, rating, 
FROM apartment_ratings user_id, apartment_id 
JOIN users ON users.id = ` 
apartment_ratings.user_id  
WHERE apartment_ratings.apartment_id = $1 type RateApartmentParams struct { 
AND users.clerk_id = $2::UUID  Rating      int16     `db:"rating" json:"rating"` 
`  ApartmentID uuid.UUID `db:"apartment_id" 
68 
json:"apartment_id"` SELECT $1, $2, $3, $4, $5, users.id 
 ClerkID     uuid.UUID `db:"clerk_id" FROM users 
json:"clerk_id"` WHERE clerk_id = $6::UUID 
} RETURNING id, created_at, updated_at, name, 
 description, address, image_urls, max_guests, owner_id 
func (q *Queries) RateApartment(ctx ` 
context.Context, arg RateApartmentParams)  
(ApartmentRating, error) { type CreateApartmentParams struct { 
 row := q.db.QueryRow(ctx, rateApartment,  Name        string    `db:"name" json:"name"` 
arg.Rating, arg.ApartmentID, arg.ClerkID)  Description string    `db:"description" 
 var i ApartmentRating json:"description"` 
 err := row.Scan(  Address     string    `db:"address" json:"address"` 
 &i.ID,  ImageUrls   []string  `db:"image_urls" 
 &i.CreatedAt, json:"image_urls"` 
 &i.UpdatedAt,  MaxGuests   int32     `db:"max_guests" 
 &i.Rating, json:"max_guests"` 
 &i.UserID,  ClerkID     uuid.UUID `db:"clerk_id" 
 &i.ApartmentID, json:"clerk_id"` 
 ) } 
 return i, err  
} func (q *Queries) CreateApartment(ctx 
 context.Context, arg CreateApartmentParams) (Apartment, 
apartment.sql.go error) { 
  row := q.db.QueryRow(ctx, createApartment, 
// Code generated by sqlc. DO NOT EDIT.  arg.Name, 
// versions:  arg.Description, 
//   sqlc v1.28.0  arg.Address, 
// source: apartment.sql  arg.ImageUrls, 
  arg.MaxGuests, 
package repository  arg.ClerkID, 
  ) 
import (  var i Apartment 
 "context"  err := row.Scan( 
  &i.ID, 
 "github.com/google/uuid"  &i.CreatedAt, 
 "github.com/jackc/pgx/v5/pgtype"  &i.UpdatedAt, 
)  &i.Name, 
  &i.Description, 
const createApartment = `-- name:  &i.Address, 
CreateApartment :one  &i.ImageUrls, 
INSERT INTO apartments (name, description,  &i.MaxGuests, 
address, image_urls, max_guests, owner_id)  &i.OwnerID, 
69 
 ) GetApartmentByID :one 
 return i, err SELECT id, created_at, updated_at, name, 
} description, address, image_urls, max_guests, owner_id 
 FROM apartments WHERE id = $1 
const deleteApartmentByID = `-- name: ` 
DeleteApartmentByID :one  
DELETE FROM apartments func (q *Queries) GetApartmentByID(ctx 
USING users  context.Context, id uuid.UUID) (Apartment, error) { 
WHERE apartments.id = $1  row := q.db.QueryRow(ctx, getApartmentByID, 
AND users.clerk_id = $2::UUID AND id) 
apartments.owner_id = users.id  var i Apartment 
RETURNING id, created_at, updated_at, name,  err := row.Scan( 
description, address, image_urls, max_guests, owner_id  &i.ID, 
`  &i.CreatedAt, 
  &i.UpdatedAt, 
type DeleteApartmentByIDParams struct {  &i.Name, 
 ID      uuid.UUID `db:"id" json:"id"`  &i.Description, 
 ClerkID uuid.UUID `db:"clerk_id"  &i.Address, 
json:"clerk_id"`  &i.ImageUrls, 
}  &i.MaxGuests, 
  &i.OwnerID, 
func (q *Queries) DeleteApartmentByID(ctx  ) 
context.Context, arg DeleteApartmentByIDParams)  return i, err 
(Apartment, error) { } 
 row := q.db.QueryRow(ctx,  
deleteApartmentByID, arg.ID, arg.ClerkID) const listApartments = `-- name: ListApartments 
 var i Apartment :many 
 err := row.Scan( SELECT id, name, description, address, 
 &i.ID, image_urls, owner_id, max_guests FROM apartments  
 &i.CreatedAt, WHERE ($3::VARCHAR IS NOT NULL OR 
 &i.UpdatedAt, name ILIKE '%' || $3::VARCHAR || '%') 
 &i.Name, AND ($4::VARCHAR IS NOT NULL OR 
 &i.Description, address ILIKE '%' || $4::VARCHAR || '%') 
 &i.Address, AND ($5::INT IS NOT NULL OR max_guests 
 &i.ImageUrls, >= $5::INT) 
 &i.MaxGuests, AND ( 
 &i.OwnerID,         ($6::DATE IS NULL AND $7::DATE IS 
 ) NULL) 
 return i, err         OR NOT EXISTS ( 
}             SELECT 1 
             FROM bookings b 
const getApartmentByID = `-- name:             WHERE b.apartment_id = apartments.id 
70 
            AND (b.start_date, b.end_date)  arg.Name, 
OVERLAPS ($6::DATE, $7::DATE)  arg.Address, 
        )  arg.MaxGuests, 
    )  arg.FromDate, 
ORDER BY created_at  arg.ToDate, 
LIMIT $1  ) 
OFFSET $2  if err != nil { 
`  return nil, err 
  } 
type ListApartmentsParams struct {  defer rows.Close() 
 Limit     int32       `db:"limit" json:"limit"`  var items []ListApartmentsRow 
 Offset    int32       `db:"offset" json:"offset"`  for rows.Next() { 
 Name      *string     `db:"name" json:"name"`  var i ListApartmentsRow 
 Address   *string     `db:"address" json:"address"`  if err := rows.Scan( 
 MaxGuests *int32      `db:"max_guests"   &i.ID, 
json:"max_guests"`   &i.Name, 
 FromDate  pgtype.Date `db:"from_date"   &i.Description, 
json:"from_date"`   &i.Address, 
 ToDate    pgtype.Date `db:"to_date"   &i.ImageUrls, 
json:"to_date"`   &i.OwnerID, 
}   &i.MaxGuests, 
  ); err != nil { 
type ListApartmentsRow struct {   return nil, err 
 ID          uuid.UUID `db:"id" json:"id"`  } 
 Name        string    `db:"name" json:"name"`  items = append(items, i) 
 Description string    `db:"description"  } 
json:"description"`  if err := rows.Err(); err != nil { 
 Address     string    `db:"address" json:"address"`  return nil, err 
 ImageUrls   []string  `db:"image_urls"  } 
json:"image_urls"`  return items, nil 
 OwnerID     uuid.UUID `db:"owner_id" } 
json:"owner_id"`  
 MaxGuests   int32     `db:"max_guests" const listUsersApartments = `-- name: 
json:"max_guests"` ListUsersApartments :many 
} SELECT apartments.id, apartments.name, 
 apartments.description, apartments.address, 
func (q *Queries) ListApartments(ctx apartments.image_urls, apartments.owner_id FROM 
context.Context, arg ListApartmentsParams) apartments 
([]ListApartmentsRow, error) { JOIN users ON users.id = apartments.owner_id 
 rows, err := q.db.Query(ctx, listApartments, WHERE users.clerk_id = $1::UUID AND 
 arg.Limit, apartments.owner_id = users.id 
 arg.Offset, ` 
71 
  
type ListUsersApartmentsRow struct { const updateApartmentByID = `-- name: 
 ID          uuid.UUID `db:"id" json:"id"` UpdateApartmentByID :one 
 Name        string    `db:"name" json:"name"` UPDATE apartments 
 Description string    `db:"description" SET name = $2, description = $3, address = $4, 
json:"description"` image_urls = $5, max_guests = $6 
 Address     string    `db:"address" json:"address"` FROM users 
 ImageUrls   []string  `db:"image_urls" WHERE apartments.id = $1 
json:"image_urls"` AND users.clerk_id = $7::UUID AND 
 OwnerID     uuid.UUID `db:"owner_id" apartments.owner_id = users.id 
json:"owner_id"` RETURNING apartments.id, 
} apartments.created_at, apartments.updated_at, 
 apartments.name, apartments.description, 
func (q *Queries) ListUsersApartments(ctx apartments.address, apartments.image_urls, 
context.Context, clerkID uuid.UUID) apartments.max_guests, apartments.owner_id 
([]ListUsersApartmentsRow, error) { ` 
 rows, err := q.db.Query(ctx, listUsersApartments,  
clerkID) type UpdateApartmentByIDParams struct { 
 if err != nil {  ID          uuid.UUID `db:"id" json:"id"` 
 return nil, err  Name        string    `db:"name" json:"name"` 
 }  Description string    `db:"description" 
 defer rows.Close() json:"description"` 
 var items []ListUsersApartmentsRow  Address     string    `db:"address" json:"address"` 
 for rows.Next() {  ImageUrls   []string  `db:"image_urls" 
 var i ListUsersApartmentsRow json:"image_urls"` 
 if err := rows.Scan(  MaxGuests   int32     `db:"max_guests" 
  &i.ID, json:"max_guests"` 
  &i.Name,  ClerkID     uuid.UUID `db:"clerk_id" 
  &i.Description, json:"clerk_id"` 
  &i.Address, } 
  &i.ImageUrls,  
  &i.OwnerID, func (q *Queries) UpdateApartmentByID(ctx 
 ); err != nil { context.Context, arg UpdateApartmentByIDParams) 
  return nil, err (Apartment, error) { 
 }  row := q.db.QueryRow(ctx, 
 items = append(items, i) updateApartmentByID, 
 }  arg.ID, 
 if err := rows.Err(); err != nil {  arg.Name, 
 return nil, err  arg.Description, 
 }  arg.Address, 
 return items, nil  arg.ImageUrls, 
}  arg.MaxGuests, 
72 
 arg.ClerkID, start_date, end_date, apartment_id, user_id 
 ) ` 
 var i Apartment  
 err := row.Scan( type CreateBookingParams struct { 
 &i.ID,  StartDate   time.Time `db:"start_date" 
 &i.CreatedAt, json:"start_date"` 
 &i.UpdatedAt,  EndDate     time.Time `db:"end_date" 
 &i.Name, json:"end_date"` 
 &i.Description,  ApartmentID uuid.UUID `db:"apartment_id" 
 &i.Address, json:"apartment_id"` 
 &i.ImageUrls,  ClerkID     uuid.UUID `db:"clerk_id" 
 &i.MaxGuests, json:"clerk_id"` 
 &i.OwnerID, } 
 )  
 return i, err func (q *Queries) CreateBooking(ctx 
} context.Context, arg CreateBookingParams) (Booking, 
 error) { 
booking.sql.go  row := q.db.QueryRow(ctx, createBooking, 
  arg.StartDate, 
// Code generated by sqlc. DO NOT EDIT.  arg.EndDate, 
// versions:  arg.ApartmentID, 
//   sqlc v1.28.0  arg.ClerkID, 
// source: booking.sql  ) 
  var i Booking 
package repository  err := row.Scan( 
  &i.ID, 
import (  &i.CreatedAt, 
 "context"  &i.UpdatedAt, 
 "time"  &i.StartDate, 
  &i.EndDate, 
 "github.com/google/uuid"  &i.ApartmentID, 
 "github.com/jackc/pgx/v5/pgtype"  &i.UserID, 
)  ) 
  return i, err 
const createBooking = `-- name: CreateBooking } 
:one  
INSERT INTO bookings (start_date, end_date, const deleteBookingByID = `-- name: 
apartment_id, user_id) DeleteBookingByID :one 
SELECT $1, $2, $3, users.id DELETE FROM bookings 
FROM users USING users  
WHERE clerk_id = $4::UUID WHERE bookings.id = $1 
RETURNING id, created_at, updated_at, AND users.clerk_id = $2::UUID AND 
73 
bookings.user_id = users.id json:"apartment_id"` 
RETURNING id, created_at, updated_at,  UserID      uuid.UUID `db:"user_id" 
start_date, end_date, apartment_id, user_id json:"user_id"` 
` } 
  
type DeleteBookingByIDParams struct { func (q *Queries) GetBookingByID(ctx 
 ID      uuid.UUID `db:"id" json:"id"` context.Context, id uuid.UUID) (GetBookingByIDRow, 
 ClerkID uuid.UUID `db:"clerk_id" error) { 
json:"clerk_id"`  row := q.db.QueryRow(ctx, getBookingByID, id) 
}  var i GetBookingByIDRow 
  err := row.Scan( 
func (q *Queries) DeleteBookingByID(ctx  &i.ID, 
context.Context, arg DeleteBookingByIDParams)  &i.StartDate, 
(Booking, error) {  &i.EndDate, 
 row := q.db.QueryRow(ctx, deleteBookingByID,  &i.ApartmentID, 
arg.ID, arg.ClerkID)  &i.UserID, 
 var i Booking  ) 
 err := row.Scan(  return i, err 
 &i.ID, } 
 &i.CreatedAt,  
 &i.UpdatedAt, const listApartmentBookings = `-- name: 
 &i.StartDate, ListApartmentBookings :many 
 &i.EndDate, SELECT bookings.id, bookings.created_at, 
 &i.ApartmentID, bookings.updated_at, bookings.start_date, 
 &i.UserID, bookings.end_date, bookings.apartment_id, 
 ) bookings.user_id FROM bookings 
 return i, err WHERE apartment_id = $1 
} AND (start_date, end_date) OVERLAPS 
 (COALESCE($2::DATE, NOW() - INTERVAL '1 
const getBookingByID = `-- name: month'), COALESCE($3::DATE, NOW())) 
GetBookingByID :one ` 
SELECT id, start_date, end_date, apartment_id,  
user_id FROM bookings WHERE id = $1 type ListApartmentBookingsParams struct { 
`  ApartmentID uuid.UUID   `db:"apartment_id" 
 json:"apartment_id"` 
type GetBookingByIDRow struct {  FromDate    pgtype.Date `db:"from_date" 
 ID          uuid.UUID `db:"id" json:"id"` json:"from_date"` 
 StartDate   time.Time `db:"start_date"  ToDate      pgtype.Date `db:"to_date" 
json:"start_date"` json:"to_date"` 
 EndDate     time.Time `db:"end_date" } 
json:"end_date"`  
 ApartmentID uuid.UUID `db:"apartment_id" func (q *Queries) ListApartmentBookings(ctx 
74 
context.Context, arg ListApartmentBookingsParams) func (q *Queries) ListUsersBookings(ctx 
([]Booking, error) { context.Context, clerkID uuid.UUID) ([]Booking, error) { 
 rows, err := q.db.Query(ctx,  rows, err := q.db.Query(ctx, listUsersBookings, 
listApartmentBookings, arg.ApartmentID, arg.FromDate, clerkID) 
arg.ToDate)  if err != nil { 
 if err != nil {  return nil, err 
 return nil, err  } 
 }  defer rows.Close() 
 defer rows.Close()  var items []Booking 
 var items []Booking  for rows.Next() { 
 for rows.Next() {  var i Booking 
 var i Booking  if err := rows.Scan( 
 if err := rows.Scan(   &i.ID, 
  &i.ID,   &i.CreatedAt, 
  &i.CreatedAt,   &i.UpdatedAt, 
  &i.UpdatedAt,   &i.StartDate, 
  &i.StartDate,   &i.EndDate, 
  &i.EndDate,   &i.ApartmentID, 
  &i.ApartmentID,   &i.UserID, 
  &i.UserID,  ); err != nil { 
 ); err != nil {   return nil, err 
  return nil, err  } 
 }  items = append(items, i) 
 items = append(items, i)  } 
 }  if err := rows.Err(); err != nil { 
 if err := rows.Err(); err != nil {  return nil, err 
 return nil, err  } 
 }  return items, nil 
 return items, nil } 
}  
 const updateBookingByID = `-- name: 
const listUsersBookings = `-- name: UpdateBookingByID :one 
ListUsersBookings :many UPDATE bookings 
SELECT bookings.id, bookings.created_at, SET start_date = $2, end_date = $3 
bookings.updated_at, bookings.start_date, FROM users 
bookings.end_date, bookings.apartment_id, WHERE bookings.id = $1 
bookings.user_id FROM bookings AND users.clerk_id = $4::UUID AND 
JOIN users apartments.owner_id = users.id 
ON bookings.user_id = users.id RETURNING bookings.id, bookings.created_at, 
WHERE users.clerk_id = $1::UUID bookings.updated_at, bookings.start_date, 
` bookings.end_date, bookings.apartment_id, 
 bookings.user_id 
75 
` import ( 
  "context" 
type UpdateBookingByIDParams struct {  
 ID        uuid.UUID `db:"id" json:"id"`  "github.com/jackc/pgx/v5" 
 StartDate time.Time `db:"start_date"  "github.com/jackc/pgx/v5/pgconn" 
json:"start_date"` ) 
 EndDate   time.Time `db:"end_date"  
json:"end_date"` type DBTX interface { 
 ClerkID   uuid.UUID `db:"clerk_id"  Exec(context.Context, string, ...interface{}) 
json:"clerk_id"` (pgconn.CommandTag, error) 
}  Query(context.Context, string, ...interface{}) 
 (pgx.Rows, error) 
func (q *Queries) UpdateBookingByID(ctx  QueryRow(context.Context, string, ...interface{}) 
context.Context, arg UpdateBookingByIDParams) pgx.Row 
(Booking, error) { } 
 row := q.db.QueryRow(ctx, updateBookingByID,  
 arg.ID, func New(db DBTX) *Queries { 
 arg.StartDate,  return &Queries{db: db} 
 arg.EndDate, } 
 arg.ClerkID,  
 ) type Queries struct { 
 var i Booking  db DBTX 
 err := row.Scan( } 
 &i.ID,  
 &i.CreatedAt, func (q *Queries) WithTx(tx pgx.Tx) *Queries { 
 &i.UpdatedAt,  return &Queries{ 
 &i.StartDate,  db: tx, 
 &i.EndDate,  } 
 &i.ApartmentID, } 
 &i.UserID, models.go 
 )  
 return i, err // Code generated by sqlc. DO NOT EDIT. 
} // versions: 
 //   sqlc v1.28.0 
db.go  
 package repository 
// Code generated by sqlc. DO NOT EDIT.  
// versions: import ( 
//   sqlc v1.28.0  "time" 
  
package repository  "github.com/google/uuid" 
 ) 
76 
 json:"apartment_id"` 
type Apartment struct { } 
 ID          uuid.UUID `db:"id" json:"id"`  
 CreatedAt   time.Time `db:"created_at" type Booking struct { 
json:"created_at"`  ID          uuid.UUID `db:"id" json:"id"` 
 UpdatedAt   time.Time `db:"updated_at"  CreatedAt   time.Time `db:"created_at" 
json:"updated_at"` json:"created_at"` 
 Name        string    `db:"name" json:"name"`  UpdatedAt   time.Time `db:"updated_at" 
 Description string    `db:"description" json:"updated_at"` 
json:"description"`  StartDate   time.Time `db:"start_date" 
 Address     string    `db:"address" json:"address"` json:"start_date"` 
 ImageUrls   []string  `db:"image_urls"  EndDate     time.Time `db:"end_date" 
json:"image_urls"` json:"end_date"` 
 MaxGuests   int32     `db:"max_guests"  ApartmentID uuid.UUID `db:"apartment_id" 
json:"max_guests"` json:"apartment_id"` 
 OwnerID     uuid.UUID `db:"owner_id"  UserID      uuid.UUID `db:"user_id" 
json:"owner_id"` json:"user_id"` 
} } 
  
type ApartmentComment struct { src/app/page.tsx 
 ID          uuid.UUID `db:"id" json:"id"`  
 CreatedAt   time.Time `db:"created_at" import Link from "next/link"; 
json:"created_at"` import { Header } from "@/components/header"; 
 UpdatedAt   time.Time `db:"updated_at" import SearchForm from "@/components/search-
json:"updated_at"` form"; 
 Content     string    `db:"content" json:"content"`  
 UserID      uuid.UUID `db:"user_id" export default function Page() { 
json:"user_id"`   return ( 
 ApartmentID uuid.UUID `db:"apartment_id"     <div className="flex min-h-screen flex-col 
json:"apartment_id"` pl-6"> 
}       <Header /> 
       <main className="flex-1"> 
type ApartmentRating struct {         <section className="from-background to-
 ID          uuid.UUID `db:"id" json:"id"` muted relative bg-gradient-to-b"> 
 CreatedAt   time.Time `db:"created_at"           <div className="container flex flex-col 
json:"created_at"` items-center justify-center space-y-6 py-16 text-center 
 UpdatedAt   time.Time `db:"updated_at" md:py-24"> 
json:"updated_at"`             <h1 className="text-3xl font-bold 
 Rating      int16     `db:"rating" json:"rating"` tracking-tighter sm:text-4xl md:text-5xl lg:text-6xl"> 
 UserID      uuid.UUID `db:"user_id"               Find Your Perfect <span 
json:"user_id"` className="text-primary">Apartment</span> 
 ApartmentID uuid.UUID `db:"apartment_id"             </h1> 
77 
            <p className="text-muted-foreground rounded-lg border p-6 shadow-sm"> 
mx-auto max-w-[700px] md:text-xl">                 <h3 className="text-xl font-
              Browse thousands of apartments and find bold">Verified Listings</h3> 
your new home with ease.                 <p className="text-muted-foreground 
            </p> mt-2"> 
            <div className="mt-6 w-full max-w-                   All our listings are verified to ensure 
3xl"> you get exactly what 
              <SearchForm />                   you see. 
            </div>                 </p> 
          </div>               </div> 
        </section>             </div> 
        <section className="py-12 md:py-24">           </div> 
          <div className="container">         </section> 
            <h2 className="mb-8 text-center text-2xl       </main> 
font-bold tracking-tighter md:text-3xl">       <footer className="border-t py-6 md:py-8"> 
              Why Choose Us         <div className="container flex flex-col 
            </h2> items-center justify-between gap-4 md:flex-row"> 
            <div className="grid gap-6 md:grid-           <div className="flex items-center gap-2 
cols-2 lg:grid-cols-3"> text-lg font-bold"> 
              <div className="bg-background             <span className="text-
rounded-lg border p-6 shadow-sm"> primary">Apartment</span> 
                <h3 className="text-xl font-             <span>Finder</span> 
bold">Wide Selection</h3>           </div> 
                <p className="text-muted-foreground           <p className="text-muted-foreground 
mt-2"> text-center text-sm"> 
                  Browse thousands of apartments             © {new Date().getFullYear()} 
across different locations, ApartmentFinder. All rights reserved. 
                  sizes, and price ranges.           </p> 
                </p>           <div className="flex gap-4"> 
              </div>             <Link 
              <div className="bg-background               href="/about" 
rounded-lg border p-6 shadow-sm">               className="text-muted-foreground 
                <h3 className="text-xl font- hover:text-foreground text-sm" 
bold">Easy Booking</h3>             > 
                <p className="text-muted-foreground               About 
mt-2">             </Link> 
                  Book your apartment with just a few             <Link 
clicks and secure your new               href="/contact" 
                  home instantly.               className="text-muted-foreground 
                </p> hover:text-foreground text-sm" 
              </div>             > 
              <div className="bg-background               Contact 
78 
            </Link>             <Link 
            <Link               href="/" 
              href="/terms"               className="flex items-center gap-2 text-
              className="text-muted-foreground xl font-bold" 
hover:text-foreground text-sm"             > 
            >               <span className="text-
              Terms primary">Apartment</span> 
            </Link>               <span className="text-
            <Link secondary">Finder</span> 
              href="/privacy"             </Link> 
              className="text-muted-foreground           </div> 
hover:text-foreground text-sm"           <nav className="hidden items-center gap-
            > 6 md:flex"> 
              Privacy             <Link 
            </Link>               href="/listings" 
          </div>               className="hover:text-primary text-sm 
        </div> font-medium transition-colors" 
      </footer>             > 
    </div>               Browse Apartments 
  );             </Link> 
}             <Link href="/about" className="text-
 primary text-sm font-medium"> 
src/app/about/page.tsx               About Us 
             </Link> 
import Image from "next/image";             <Link 
import Link from "next/link";               href="/contact" 
import { Users, Home, Shield, Award, Clock,               className="hover:text-primary text-sm 
MapPin } from "lucide-react"; font-medium transition-colors" 
             > 
import { Button } from               Contact 
"@/components/ui/button";             </Link> 
import { MobileNav } from           </nav> 
"@/components/mobile-nav";           <div className="flex items-center gap-4"> 
             <Link 
export default function Page() {               href="/login" 
  return (               className="hover:text-primary hidden 
    <div className="flex min-h-screen flex-col"> text-sm font-medium md:inline-flex" 
      <header className="border-b">             > 
        <div className="container flex h-16 items-               Log in 
center justify-between py-4">             </Link> 
          <div className="flex items-center gap-2">             <Button asChild> 
            <MobileNav />               <Link href="/signup">Sign up</Link> 
79 
            </Button> Us</Link> 
          </div>                 </Button> 
        </div>               </div> 
      </header>             </div> 
      <main className="flex-1">           </div> 
        <section className="relative overflow-         </section> 
hidden py-20 md:py-32">         <section className="bg-muted/30 py-16 
          <div className="absolute inset-0 z-0"> md:py-24"> 
            <Image           <div className="container"> 
                          <div className="grid items-center gap-
src="/placeholder.svg?height=1080&width=1920" 12 md:grid-cols-2"> 
              alt="Modern apartment building"               <div> 
              fill                 <h2 className="mb-6 text-2xl font-
              className="object-cover" bold md:text-3xl"> 
              priority                   Our Story 
            />                 </h2> 
            <div className="from-background/90 to-                 <div className="text-muted-
background/70 absolute inset-0 bg-gradient-to-r" /> foreground space-y-4"> 
          </div>                   <p> 
          <div className="relative z-10 container">                     Founded in 2020, ApartmentFinder 
            <div className="max-w-2xl"> was born from a simple 
              <h1 className="mb-6 text-3xl font-bold                     idea: finding a place to live should be 
tracking-tight md:text-5xl"> exciting, not 
                Connecting People with Their Perfect                     exhausting. Our founders 
Homes experienced firsthand the 
              </h1>                     challenges of apartment hunting – 
              <p className="text-muted-foreground from misleading listings 
mb-8 text-lg md:text-xl">                     to complicated booking processes. 
                ApartmentFinder is on a mission to                   </p> 
make finding and booking                   <p> 
                apartments a seamless, enjoyable                     We set out to create a platform that 
experience for everyone. brings transparency, 
              </p>                     trust, and simplicity to the apartment 
              <div className="flex flex-wrap gap-4"> rental process. What 
                <Button asChild size="lg"                     started as a small team with big 
className="btn-primary"> dreams has grown into a 
                  <Link href="/listings">Browse                     trusted platform connecting 
Apartments</Link> thousands of people with their 
                </Button>                     perfect homes. 
                <Button asChild variant="outline"                   </p> 
size="lg">                   <p> 
                  <Link href="/contact">Contact                     Today, we're proud to offer a curated 
80 
selection of quality                 <div className="feature-icon mb-4"> 
                    apartments across major cities, with                   <Shield className="h-6 w-6" /> 
verified listings,                 </div> 
                    seamless booking, and exceptional                 <h3 className="mb-2 text-xl font-
customer support at every semibold"> 
                    step of your journey.                   Trust & Transparency 
                  </p>                 </h3> 
                </div>                 <p className="text-muted-
              </div> foreground"> 
              <div className="relative h-[400px]                   We verify all listings and provide 
overflow-hidden rounded-lg shadow-lg"> honest, detailed 
                <Image                   information so you can make confident 
                  decisions. 
src="/placeholder.svg?height=800&width=600"                 </p> 
                  alt="Our team"               </div> 
                  fill  
                  className="object-cover"               <div className="bg-card rounded-lg 
                /> border p-6 shadow-sm"> 
              </div>                 <div className="feature-icon-
            </div> secondary mb-4"> 
          </div>                   <Users className="h-6 w-6" /> 
        </section>                 </div> 
        <section className="py-16 md:py-24">                 <h3 className="mb-2 text-xl font-
          <div className="container"> semibold">Customer First</h3> 
            <div className="mb-16 text-center">                 <p className="text-muted-
              <h2 className="mb-4 text-2xl font-bold foreground"> 
md:text-3xl">                   Your satisfaction drives everything we 
                Our Values do. We're committed to 
              </h2>                   providing exceptional service at every 
              <p className="text-muted-foreground step. 
mx-auto max-w-2xl">                 </p> 
                These core principles guide everything               </div> 
we do, from how we build  
                our platform to how we interact with               <div className="bg-card rounded-lg 
our customers and partners. border p-6 shadow-sm"> 
              </p>                 <div className="feature-icon-accent 
            </div> mb-4"> 
                   <Home className="h-6 w-6" /> 
            <div className="grid gap-8 sm:grid-cols-                 </div> 
2 lg:grid-cols-3">                 <h3 className="mb-2 text-xl font-
              <div className="bg-card rounded-lg semibold">Quality Living</h3> 
border p-6 shadow-sm">                 <p className="text-muted-
81 
foreground">                   <Award className="h-6 w-6" /> 
                  We believe everyone deserves a great                 </div> 
place to live, and we're                 <h3 className="mb-2 text-xl font-
                  dedicated to making that possible. semibold">Innovation</h3> 
                </p>                 <p className="text-muted-
              </div> foreground"> 
                   We continuously improve our platform 
              <div className="bg-card rounded-lg with new features and 
border p-6 shadow-sm">                   technologies to enhance your 
                <div className="feature-icon mb-4"> experience. 
                  <Clock className="h-6 w-6" />                 </p> 
                </div>               </div> 
                <h3 className="mb-2 text-xl font-             </div> 
semibold">Efficiency</h3>           </div> 
                <p className="text-muted-         </section> 
foreground">         <section className="bg-muted/30 py-16 
                  We streamline the apartment hunting md:py-24"> 
process, saving you time           <div className="container"> 
                  and reducing stress.             <div className="mb-16 text-center"> 
                </p>               <h2 className="mb-4 text-2xl font-bold 
              </div> md:text-3xl"> 
                 Meet Our Team 
              <div className="bg-card rounded-lg               </h2> 
border p-6 shadow-sm">               <p className="text-muted-foreground 
                <div className="feature-icon- mx-auto max-w-2xl"> 
secondary mb-4">                 The passionate people behind 
                  <MapPin className="h-6 w-6" /> ApartmentFinder who are dedicated 
                </div>                 to transforming how you find your next 
                <h3 className="mb-2 text-xl font- home. 
semibold">Local Expertise</h3>               </p> 
                <p className="text-muted-             </div> 
foreground">             <div className="grid gap-8 sm:grid-cols-
                  Our team has deep knowledge of local 2 lg:grid-cols-4"> 
markets to help you find               {[ 
                  the perfect neighborhood.                 { 
                </p>                   name: "Sarah Johnson", 
              </div>                   role: "Founder & CEO", 
                   image: 
              <div className="bg-card rounded-lg "/placeholder.svg?height=300&width=300", 
border p-6 shadow-sm">                 }, 
                <div className="feature-icon-accent                 { 
mb-4">                   name: "Michael Chen", 
82 
                  role: "CTO",               <div className="text-center"> 
                  image:                 <div className="text-primary mb-2 
"/placeholder.svg?height=300&width=300", text-4xl font-bold"> 
                },                   10,000+ 
                {                 </div> 
                  name: "Emily Rodriguez",                 <p className="text-muted-
                  role: "Head of Operations", foreground">Apartments Listed</p> 
                  image:               </div> 
"/placeholder.svg?height=300&width=300",               <div className="text-center"> 
                },                 <div className="text-primary mb-2 
                { text-4xl font-bold">50+</div> 
                  name: "David Kim",                 <p className="text-muted-
                  role: "Lead Designer", foreground">Cities Covered</p> 
                  image:               </div> 
"/placeholder.svg?height=300&width=300",               <div className="text-center"> 
                },                 <div className="text-primary mb-2 
              ].map((member, index) => ( text-4xl font-bold"> 
                <div key={index} className="text-                   25,000+ 
center">                 </div> 
                  <div className="relative mx-auto                 <p className="text-muted-
mb-4 h-48 w-48 overflow-hidden rounded-full"> foreground">Happy Customers</p> 
                    <Image               </div> 
                      src={member.image ||               <div className="text-center"> 
"/placeholder.svg"}                 <div className="text-primary mb-2 
                      alt={member.name} text-4xl font-bold">4.9</div> 
                      fill                 <p className="text-muted-
                      className="object-cover" foreground">Average Rating</p> 
                    />               </div> 
                  </div>             </div> 
                  <h3 className="text-lg font-           </div> 
semibold">{member.name}</h3>         </section> 
                  <p className="text-muted-         <section className="bg-primary text-
foreground">{member.role}</p> primary-foreground py-16 md:py-24"> 
                </div>           <div className="container text-center"> 
              ))}             <h2 className="mb-4 text-2xl font-bold 
            </div> md:text-3xl"> 
          </div>               Ready to Find Your Perfect Apartment? 
        </section>             </h2> 
        <section className="py-16 md:py-24">             <p className="text-primary-
          <div className="container"> foreground/80 mx-auto mb-8 max-w-2xl"> 
            <div className="grid gap-8 sm:grid-cols-               Join thousands of satisfied customers 
2 lg:grid-cols-4"> who have found their dream 
83 
              homes through ApartmentFinder.                     className="text-secondary-100 
            </p> transition-colors hover:text-white" 
            <Button asChild size="lg"                   > 
variant="secondary">                     About Us 
              <Link href="/listings">Start Browsing                   </Link> 
Now</Link>                 </li> 
            </Button>                 <li> 
          </div>                   <Link 
        </section>                     href="/contact" 
      </main>                     className="text-secondary-100 
      <footer className="bg-secondary-900 py-12 transition-colors hover:text-white" 
text-white">                   > 
        <div className="container">                     Contact 
          <div className="grid grid-cols-1 gap-8                   </Link> 
md:grid-cols-4">                 </li> 
            <div>               </ul> 
              <h3 className="mb-4 text-lg font-bold             </div> 
text-white">             <div> 
                ApartmentFinder               <h3 className="mb-4 text-lg font-bold 
              </h3> text-white">Legal</h3> 
              <p className="text-secondary-100">               <ul className="space-y-2"> 
                Find your perfect apartment with ease.                 <li> 
Browse thousands of                   <Link 
                verified listings.                     href="/terms" 
              </p>                     className="text-secondary-100 
            </div> transition-colors hover:text-white" 
            <div>                   > 
              <h3 className="mb-4 text-lg font-bold                     Terms of Service 
text-white">Quick Links</h3>                   </Link> 
              <ul className="space-y-2">                 </li> 
                <li>                 <li> 
                  <Link                   <Link 
                    href="/listings"                     href="/privacy" 
                    className="text-secondary-100                     className="text-secondary-100 
transition-colors hover:text-white" transition-colors hover:text-white" 
                  >                   > 
                    Browse Apartments                     Privacy Policy 
                  </Link>                   </Link> 
                </li>                 </li> 
                <li>                 <li> 
                  <Link                   <Link 
                    href="/about"                     href="/cookies" 
84 
                    className="text-secondary-100  
transition-colors hover:text-white" 
                  > 
                    Cookie Policy 
                  </Link> 
                </li> 
              </ul> 
            </div> 
            <div> 
              <h3 className="mb-4 text-lg font-bold 
text-white">Newsletter</h3> 
              <p className="text-secondary-100 mb-
4"> 
                Subscribe to our newsletter for the latest 
updates and offers. 
              </p> 
              <form className="flex gap-2"> 
                <input 
                  type="email" 
                  placeholder="Your email" 
                  className="bg-secondary-800 
border-secondary-700 placeholder:text-secondary-400 
focus:border-primary-400 max-w-[220px] rounded-md p-2 
text-white" 
                /> 
                <Button type="submit" 
className="btn-primary"> 
                  Subscribe 
                </Button> 
              </form> 
            </div> 
          </div> 
          <div className="border-secondary-700 
text-secondary-300 mt-8 border-t pt-8 text-center"> 
            <p>© 2023 ApartmentFinder. All rights 
reserved.</p> 
          </div> 
        </div> 
      </footer> 
    </div> 
  ); 
}