[{"data":1,"prerenderedAt":46799},["ShallowReactive",2],{"navigation_docs":3,"-client-server-architecture-layered-architecture-and-middleware-pipelines":5180,"-client-server-architecture-layered-architecture-and-middleware-pipelines-surround":46794},[4,1707,2084,2556,2737,2863,3070,3192,3242,3299,3333,3459,3536,3550,3689,3983,4060,4793,4968,5061,5114,5176],{"title":5,"icon":6,"path":7,"stem":8,"children":9},"C#","i-devicon-csharp","\u002Fcsharp","01.csharp",[10,13,60,90,120,202,219,253,379,404,457,650,1364,1654,1703],{"title":11,"path":7,"stem":12},"C# та .NET","01.csharp\u002Findex",{"title":14,"icon":15,"path":16,"stem":17,"children":18,"page":59},"Fundamentals","i-lucide-book-open","\u002Fcsharp\u002Ffundamentals","01.csharp\u002F01.fundamentals",[19,23,27,31,35,39,43,47,51,55],{"title":20,"path":21,"stem":22},"Вступ до екосистеми .NET","\u002Fcsharp\u002Ffundamentals\u002Fintroduction-to-ecosystem","01.csharp\u002F01.fundamentals\u002F01.introduction-to-ecosystem",{"title":24,"path":25,"stem":26},"Структура програми на C#","\u002Fcsharp\u002Ffundamentals\u002Fprogram-structure","01.csharp\u002F01.fundamentals\u002F02.program-structure",{"title":28,"path":29,"stem":30},"Змінні та Типи Даних","\u002Fcsharp\u002Ffundamentals\u002Fvariables-data-types","01.csharp\u002F01.fundamentals\u002F03.variables-data-types",{"title":32,"path":33,"stem":34},"Масиви","\u002Fcsharp\u002Ffundamentals\u002Farrays","01.csharp\u002F01.fundamentals\u002F04.arrays",{"title":36,"path":37,"stem":38},"Strings & Text Handling","\u002Fcsharp\u002Ffundamentals\u002Fstrings-text-handling","01.csharp\u002F01.fundamentals\u002F05.strings-text-handling",{"title":40,"path":41,"stem":42},"Дати і Час","\u002Fcsharp\u002Ffundamentals\u002Fdates-time-handling","01.csharp\u002F01.fundamentals\u002F06.dates-time-handling",{"title":44,"path":45,"stem":46},"Потік Керування","\u002Fcsharp\u002Ffundamentals\u002Fcontrol-flow","01.csharp\u002F01.fundamentals\u002F07.control-flow",{"title":48,"path":49,"stem":50},"Методи","\u002Fcsharp\u002Ffundamentals\u002Fmethods","01.csharp\u002F01.fundamentals\u002F08.methods",{"title":52,"path":53,"stem":54},"Основи Відлагодження","\u002Fcsharp\u002Ffundamentals\u002Fdebugging-basics","01.csharp\u002F01.fundamentals\u002F09.debugging-basics",{"title":56,"path":57,"stem":58},"Інтерактивна Консоль (Classic)","\u002Fcsharp\u002Ffundamentals\u002Finteractive-console","01.csharp\u002F01.fundamentals\u002F10.interactive-console",false,{"title":61,"icon":62,"path":63,"stem":64,"children":65,"page":59},"OOP","i-lucide-box","\u002Fcsharp\u002Foop","01.csharp\u002F02.oop",[66,70,74,78,82,86],{"title":67,"path":68,"stem":69},"Package Management (Управління Пакетами)","\u002Fcsharp\u002Foop\u002Fpackage-management","01.csharp\u002F02.oop\u002F01.package-management",{"title":71,"path":72,"stem":73},"Класи та Об'єкти","\u002Fcsharp\u002Foop\u002Fclasses-objects","01.csharp\u002F02.oop\u002F02.classes-objects",{"title":75,"path":76,"stem":77},"Властивості та Поля","\u002Fcsharp\u002Foop\u002Fproperties-fields","01.csharp\u002F02.oop\u002F03.properties-fields",{"title":79,"path":80,"stem":81},"Стовпи ООП","\u002Fcsharp\u002Foop\u002Foop-pillars","01.csharp\u002F02.oop\u002F04.oop-pillars",{"title":83,"path":84,"stem":85},"Advanced Types","\u002Fcsharp\u002Foop\u002Fadvanced-types","01.csharp\u002F02.oop\u002F05.advanced-types",{"title":87,"path":88,"stem":89},"Namespaces (Простори Імен)","\u002Fcsharp\u002Foop\u002Fnamespaces","01.csharp\u002F02.oop\u002F06.namespaces",{"title":91,"icon":92,"path":93,"stem":94,"children":95,"page":59},"Advanced Core","i-lucide-zap","\u002Fcsharp\u002Fadvanced-core","01.csharp\u002F03.advanced-core",[96,100,104,108,112,116],{"title":97,"path":98,"stem":99},"Generics (Узагальнення)","\u002Fcsharp\u002Fadvanced-core\u002Fgenerics","01.csharp\u002F03.advanced-core\u002F01.generics",{"title":101,"path":102,"stem":103},"Делегати, Події та Лямбда-вирази","\u002Fcsharp\u002Fadvanced-core\u002Fdelegates-events-lambdas","01.csharp\u002F03.advanced-core\u002F02.delegates-events-lambdas",{"title":105,"path":106,"stem":107},"Interfaces Deep Dive (Інтерфейси: Поглиблений Розгляд)","\u002Fcsharp\u002Fadvanced-core\u002Finterfaces-deep-dive","01.csharp\u002F03.advanced-core\u002F03.interfaces-deep-dive",{"title":109,"path":110,"stem":111},"Обробка Винятків","\u002Fcsharp\u002Fadvanced-core\u002Fexception-handling","01.csharp\u002F03.advanced-core\u002F04.exception-handling",{"title":113,"path":114,"stem":115},"Pattern Matching","\u002Fcsharp\u002Fadvanced-core\u002Fpattern-matching","01.csharp\u002F03.advanced-core\u002F05.pattern-matching",{"title":117,"path":118,"stem":119},"Додаткові Можливості C#","\u002Fcsharp\u002Fadvanced-core\u002Fadditional-features","01.csharp\u002F03.advanced-core\u002F06.additional-features",{"title":121,"icon":122,"path":123,"stem":124,"children":125,"page":59},"Architecture Best Practices","i-lucide-building-2","\u002Fcsharp\u002Farchitecture-best-practices","01.csharp\u002F04.architecture-best-practices",[126,130,149,153,157,161,165,169],{"title":127,"path":128,"stem":129},"Software Design Principles (Частина 1)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fsoftware-design-principles","01.csharp\u002F04.architecture-best-practices\u002F01.software-design-principles",{"title":131,"icon":132,"path":133,"stem":134,"children":135,"page":59},"Design Patterns","i-lucide-folder","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns",[136],{"title":137,"icon":132,"path":138,"stem":139,"children":140,"page":59},"Creational","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns\u002Fcreational","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns\u002Fcreational",[141,145],{"title":142,"path":143,"stem":144},"Singleton (Одинак)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns\u002Fcreational\u002Fsingleton","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns\u002Fcreational\u002F01.singleton",{"title":146,"path":147,"stem":148},"Builder (Будівельник)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns\u002Fcreational\u002Fbuilder","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns\u002Fcreational\u002F02.builder",{"title":150,"path":151,"stem":152},"Building Professional CLIs","\u002Fcsharp\u002Farchitecture-best-practices\u002Fbuilding-professional-clis","01.csharp\u002F04.architecture-best-practices\u002F03.building-professional-clis",{"title":154,"path":155,"stem":156},"Validation & Flow Control","\u002Fcsharp\u002Farchitecture-best-practices\u002Fvalidation-flow-control","01.csharp\u002F04.architecture-best-practices\u002F04.validation-flow-control",{"title":158,"path":159,"stem":160},"The Modern .NET Host (Microsoft.Extensions)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fmodern-dotnet-host","01.csharp\u002F04.architecture-best-practices\u002F05.modern-dotnet-host",{"title":162,"path":163,"stem":164},"Data Mapper: Repository та DAO патерни (Частина 1)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdata-mapper-part1","01.csharp\u002F04.architecture-best-practices\u002F06.data-mapper-part1",{"title":166,"path":167,"stem":168},"Data Mapper: Repository та DAO патерни (Частина 2)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdata-mapper-part2","01.csharp\u002F04.architecture-best-practices\u002F07.data-mapper-part2",{"title":170,"icon":132,"path":171,"stem":172,"children":173,"page":59},"Di Ioc","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc",[174,178,182,186,190,194,198],{"title":175,"path":176,"stem":177},"Проблема залежностей та Інверсія Контролю","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fthe-dependency-problem","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F01.the-dependency-problem",{"title":179,"path":180,"stem":181},"Будуємо власний Service Container","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fbuild-your-own-container","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F02.build-your-own-container",{"title":183,"path":184,"stem":185},"Service Locator: Паттерн та Анти-паттерн","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fservice-locator-pattern","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F03.service-locator-pattern",{"title":187,"path":188,"stem":189},"Паттерни Dependency Injection","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fdependency-injection-patterns","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F04.dependency-injection-patterns",{"title":191,"path":192,"stem":193},"Microsoft DI: IServiceCollection та IServiceProvider","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fmicrosoft-di-deep-dive","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F05.microsoft-di-deep-dive",{"title":195,"path":196,"stem":197},"Service Lifetimes та Scopes","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fservice-lifetimes-and-scopes","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F06.service-lifetimes-and-scopes",{"title":199,"path":200,"stem":201},"DI Анти-паттерни та Найкращі Практики","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fdi-anti-patterns-and-best-practices","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F07.di-anti-patterns-and-best-practices",{"title":203,"icon":132,"path":204,"stem":205,"children":206,"page":59},"Standard Library","\u002Fcsharp\u002Fstandard-library","01.csharp\u002F05.standard-library",[207,211,215],{"title":208,"path":209,"stem":210},"Collections (Колекції)","\u002Fcsharp\u002Fstandard-library\u002Fcollections","01.csharp\u002F05.standard-library\u002F01.collections",{"title":212,"path":213,"stem":214},"High Performance Types (Високопродуктивні Типи)","\u002Fcsharp\u002Fstandard-library\u002Fhigh-performance-types","01.csharp\u002F05.standard-library\u002F02.high-performance-types",{"title":216,"path":217,"stem":218},"LINQ (Language Integrated Query)","\u002Fcsharp\u002Fstandard-library\u002Flinq","01.csharp\u002F05.standard-library\u002F03.linq",{"title":220,"icon":221,"path":222,"stem":223,"children":224,"page":59},"System Internals Concurrency","i-lucide-server","\u002Fcsharp\u002Fsystem-internals-concurrency","01.csharp\u002F06.system-internals-concurrency",[225,229,233,237,241,245,249],{"title":226,"path":227,"stem":228},"Memory Management","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fmemory-management","01.csharp\u002F06.system-internals-concurrency\u002F01.memory-management",{"title":230,"path":231,"stem":232},"Reflection API: System.Type та Метадані","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Freflection-fundamentals","01.csharp\u002F06.system-internals-concurrency\u002F02.reflection-fundamentals",{"title":234,"path":235,"stem":236},"Attributes та Dynamic Language Runtime","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fattributes-dynamic","01.csharp\u002F06.system-internals-concurrency\u002F03.attributes-dynamic",{"title":238,"path":239,"stem":240},"Expression Trees: Швидка Альтернатива Рефлексії","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fexpression-trees-compiled","01.csharp\u002F06.system-internals-concurrency\u002F04.expression-trees-compiled",{"title":242,"path":243,"stem":244},"Source Generators: Compile-Time Code Generation","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fsource-generators","01.csharp\u002F06.system-internals-concurrency\u002F05.source-generators",{"title":246,"path":247,"stem":248},"Multithreading Fundamentals","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fmultithreading-fundamentals","01.csharp\u002F06.system-internals-concurrency\u002F06.multithreading-fundamentals",{"title":250,"path":251,"stem":252},"Synchronization Primitives","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fsynchronization-primitives","01.csharp\u002F06.system-internals-concurrency\u002F07.synchronization-primitives",{"title":254,"icon":255,"path":256,"stem":257,"children":258,"page":59},"System Programming Windows","i-lucide-cpu","\u002Fcsharp\u002Fsystem-programming-windows","01.csharp\u002F07.system-programming-windows",[259,263,267,271,275,279,283,287,291,295,299,303,307,311,315,319,323,327,331,335,339,343,347,351,355,359,363,367,371,375],{"title":260,"path":261,"stem":262},"Як Працює Операційна Система","\u002Fcsharp\u002Fsystem-programming-windows\u002Fhow-os-works","01.csharp\u002F07.system-programming-windows\u002F01.how-os-works",{"title":264,"path":265,"stem":266},"Процеси в .NET — API та Запуск","\u002Fcsharp\u002Fsystem-programming-windows\u002Fprocesses-in-dotnet","01.csharp\u002F07.system-programming-windows\u002F02.processes-in-dotnet",{"title":268,"path":269,"stem":270},"Процеси в .NET — IPC та Міжпроцесна Комунікація","\u002Fcsharp\u002Fsystem-programming-windows\u002F02a.processes-ipc","01.csharp\u002F07.system-programming-windows\u002F02a.processes-ipc",{"title":272,"path":273,"stem":274},"Application Domains та Збірки — AppDomain і AssemblyLoadContext","\u002Fcsharp\u002Fsystem-programming-windows\u002Fappdomains-assemblies","01.csharp\u002F07.system-programming-windows\u002F03.appdomains-assemblies",{"title":276,"path":277,"stem":278},"Application Domains та Збірки — Plug-in Система з Hot-Reload","\u002Fcsharp\u002Fsystem-programming-windows\u002F03a.appdomains-plugin-system","01.csharp\u002F07.system-programming-windows\u002F03a.appdomains-plugin-system",{"title":280,"path":281,"stem":282},"Потоки — Основи та API Thread","\u002Fcsharp\u002Fsystem-programming-windows\u002Fthread-fundamentals","01.csharp\u002F07.system-programming-windows\u002F04.thread-fundamentals",{"title":284,"path":285,"stem":286},"Потоки — Lifecycle, Пріоритети та Безпечне Завершення","\u002Fcsharp\u002Fsystem-programming-windows\u002F04a.thread-lifecycle-priorities","01.csharp\u002F07.system-programming-windows\u002F04a.thread-lifecycle-priorities",{"title":288,"path":289,"stem":290},"Проблеми Спільного Стану — Race Condition та Data Race","\u002Fcsharp\u002Fsystem-programming-windows\u002Fshared-state-problems","01.csharp\u002F07.system-programming-windows\u002F05.shared-state-problems",{"title":292,"path":293,"stem":294},"Проблеми Спільного Стану — Memory Model та volatile","\u002Fcsharp\u002Fsystem-programming-windows\u002F05a.shared-state-memory-model","01.csharp\u002F07.system-programming-windows\u002F05a.shared-state-memory-model",{"title":296,"path":297,"stem":298},"Синхронізація — Monitor, lock та еволюція примітивів","\u002Fcsharp\u002Fsystem-programming-windows\u002Fsynchronization-fundamentals","01.csharp\u002F07.system-programming-windows\u002F06.synchronization-fundamentals",{"title":300,"path":301,"stem":302},"Синхронізація — Наскрізний Приклад та Deadlock Detection","\u002Fcsharp\u002Fsystem-programming-windows\u002F06a.synchronization-walkthrough","01.csharp\u002F07.system-programming-windows\u002F06a.synchronization-walkthrough",{"title":304,"path":305,"stem":306},"Синхронізація — Mutex, Semaphore та Event-Based Primitives","\u002Fcsharp\u002Fsystem-programming-windows\u002Fsynchronization-advanced","01.csharp\u002F07.system-programming-windows\u002F07.synchronization-advanced",{"title":308,"path":309,"stem":310},"Синхронізація — Interlocked, Volatile та Lock-Free Структури","\u002Fcsharp\u002Fsystem-programming-windows\u002F07a.synchronization-advanced-walkthrough","01.csharp\u002F07.system-programming-windows\u002F07a.synchronization-advanced-walkthrough",{"title":312,"path":313,"stem":314},"Interlocked, CAS та Lock-Free Структури","\u002Fcsharp\u002Fsystem-programming-windows\u002Finterlocked-cas-lockfree","01.csharp\u002F07.system-programming-windows\u002F08.interlocked-cas-lockfree",{"title":316,"path":317,"stem":318},"Volatile, Memory Model та Spinning","\u002Fcsharp\u002Fsystem-programming-windows\u002F08a.volatile-memory-model","01.csharp\u002F07.system-programming-windows\u002F08a.volatile-memory-model",{"title":320,"path":321,"stem":322},"ThreadPool — Пул Потоків для Ефективного Виконання","\u002Fcsharp\u002Fsystem-programming-windows\u002Fthread-pool","01.csharp\u002F07.system-programming-windows\u002F09.thread-pool",{"title":324,"path":325,"stem":326},"ThreadPool — Просунуті Сценарії та Внутрішня Будова","\u002Fcsharp\u002Fsystem-programming-windows\u002F09a.thread-pool-advanced","01.csharp\u002F07.system-programming-windows\u002F09a.thread-pool-advanced",{"title":328,"path":329,"stem":330},"Concurrent та Immutable Collections","\u002Fcsharp\u002Fsystem-programming-windows\u002Fconcurrent-collections","01.csharp\u002F07.system-programming-windows\u002F10.concurrent-collections",{"title":332,"path":333,"stem":334},"TPL, Task та Композиція — Від Thread до Task","\u002Fcsharp\u002Fsystem-programming-windows\u002Ftpl-parallel-plinq","01.csharp\u002F07.system-programming-windows\u002F11.tpl-parallel-plinq",{"title":336,"path":337,"stem":338},"Parallel Class та PLINQ — Data Parallelism","\u002Fcsharp\u002Fsystem-programming-windows\u002F11a.tpl-parallel-plinq-advanced","01.csharp\u002F07.system-programming-windows\u002F11a.tpl-parallel-plinq-advanced",{"title":340,"path":341,"stem":342},"Async\u002FAwait — Фундамент Асинхронного Програмування","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-fundamentals","01.csharp\u002F07.system-programming-windows\u002F12.async-fundamentals",{"title":344,"path":345,"stem":346},"SynchronizationContext та ConfigureAwait — Контекст Виконання","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-context-configureawait","01.csharp\u002F07.system-programming-windows\u002F13.async-context-configureawait",{"title":348,"path":349,"stem":350},"Async — Просунуті Паттерни","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-advanced","01.csharp\u002F07.system-programming-windows\u002F14.async-advanced",{"title":352,"path":353,"stem":354},"System.Threading.Channels — Async Producer-Consumer","\u002Fcsharp\u002Fsystem-programming-windows\u002Fchannels","01.csharp\u002F07.system-programming-windows\u002F15.channels",{"title":356,"path":357,"stem":358},"Асинхронна Синхронізація","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-synchronization","01.csharp\u002F07.system-programming-windows\u002F16.async-synchronization",{"title":360,"path":361,"stem":362},"Unsafe Code та Вказівники","\u002Fcsharp\u002Fsystem-programming-windows\u002Funsafe-code","01.csharp\u002F07.system-programming-windows\u002F17.unsafe-code",{"title":364,"path":365,"stem":366},"P\u002FInvoke та Windows API — Міст між .NET та Native Code","\u002Fcsharp\u002Fsystem-programming-windows\u002Fpinvoke-winapi","01.csharp\u002F07.system-programming-windows\u002F18.pinvoke-winapi",{"title":368,"path":369,"stem":370},"Реєстр Windows — Центральна База Конфігурації Системи","\u002Fcsharp\u002Fsystem-programming-windows\u002Fwindows-registry","01.csharp\u002F07.system-programming-windows\u002F19.windows-registry",{"title":372,"path":373,"stem":374},"Windows Hooks, Hotkeys та Services — Глибока Інтеграція з ОС","\u002Fcsharp\u002Fsystem-programming-windows\u002Fwindows-hooks-services","01.csharp\u002F07.system-programming-windows\u002F20.windows-hooks-services",{"title":376,"path":377,"stem":378},"Системне Програмування C# (Windows) — 07.system-programming-windows","\u002Fcsharp\u002Fsystem-programming-windows\u002Fimplementation_plan","01.csharp\u002F07.system-programming-windows\u002Fimplementation_plan",{"title":380,"icon":132,"path":381,"stem":382,"children":383,"page":59},"Io","\u002Fcsharp\u002Fio","01.csharp\u002F08.io",[384,388,392,396,400],{"title":385,"path":386,"stem":387},"8.1.1. Основи роботи з файловою системою","\u002Fcsharp\u002Fio\u002Ffile-system-basics","01.csharp\u002F08.io\u002F01.file-system-basics",{"title":389,"path":390,"stem":391},"8.1.2. Потоки (Streams) та Серіалізація Даних","\u002Fcsharp\u002Fio\u002Fstreams-serialization","01.csharp\u002F08.io\u002F02.streams-serialization",{"title":393,"path":394,"stem":395},"8.2.1. JSON Serialization з System.Text.Json","\u002Fcsharp\u002Fio\u002Fjson-serialization","01.csharp\u002F08.io\u002F03.json-serialization",{"title":397,"path":398,"stem":399},"8.2.2. XML Serialization та LINQ to XML","\u002Fcsharp\u002Fio\u002Fxml-serialization","01.csharp\u002F08.io\u002F04.xml-serialization",{"title":401,"path":402,"stem":403},"8.2.3. Binary Serialization: MessagePack та Protocol Buffers","\u002Fcsharp\u002Fio\u002Fbinary-serialization","01.csharp\u002F08.io\u002F05.binary-serialization",{"title":405,"icon":132,"path":406,"stem":407,"children":408,"page":59},"Ado Net","\u002Fcsharp\u002Fado-net","01.csharp\u002F09.ado-net",[409,413,417,421,425,429,433,437,441,445,449,453],{"title":410,"path":411,"stem":412},"9.1. Введення в ADO.NET","\u002Fcsharp\u002Fado-net\u002Fintroduction-to-adonet","01.csharp\u002F09.ado-net\u002F01.introduction-to-adonet",{"title":414,"path":415,"stem":416},"9.2. Клас DbConnection — з'єднання з базою даних","\u002Fcsharp\u002Fado-net\u002Fconnection","01.csharp\u002F09.ado-net\u002F02.connection",{"title":418,"path":419,"stem":420},"9.3. Клас DbCommand — виконання SQL-запитів","\u002Fcsharp\u002Fado-net\u002Fcommand-and-queries","01.csharp\u002F09.ado-net\u002F03.command-and-queries",{"title":422,"path":423,"stem":424},"9.4. Клас DbDataReader — ефективне читання даних","\u002Fcsharp\u002Fado-net\u002Fdatareader","01.csharp\u002F09.ado-net\u002F04.datareader",{"title":426,"path":427,"stem":428},"9.5. Параметризовані запити та захист від SQL Injection","\u002Fcsharp\u002Fado-net\u002Fparameters-and-sql-injection","01.csharp\u002F09.ado-net\u002F05.parameters-and-sql-injection",{"title":430,"path":431,"stem":432},"9.6. Транзакції в ADO.NET","\u002Fcsharp\u002Fado-net\u002Ftransactions","01.csharp\u002F09.ado-net\u002F06.transactions",{"title":434,"path":435,"stem":436},"9.7. DbProviderFactory — провайдер-незалежний код","\u002Fcsharp\u002Fado-net\u002Fprovider-factory","01.csharp\u002F09.ado-net\u002F07.provider-factory",{"title":438,"path":439,"stem":440},"9.8. Асинхронний доступ до даних","\u002Fcsharp\u002Fado-net\u002Fasync-data-access","01.csharp\u002F09.ado-net\u002F08.async-data-access",{"title":442,"path":443,"stem":444},"9.9. Від'єднаний режим: DataSet, DataTable, DataRow","\u002Fcsharp\u002Fado-net\u002Fdisconnected-mode-dataset","01.csharp\u002F09.ado-net\u002F09.disconnected-mode-dataset",{"title":446,"path":447,"stem":448},"9.10. DataAdapter — міст між DataSet та базою даних","\u002Fcsharp\u002Fado-net\u002Fdata-adapter","01.csharp\u002F09.ado-net\u002F10.data-adapter",{"title":450,"path":451,"stem":452},"9.11. Data Mapper та Repository: Архітектура доступу до даних","\u002Fcsharp\u002Fado-net\u002Fdata-mapper-repository","01.csharp\u002F09.ado-net\u002F11.data-mapper-repository",{"title":454,"path":455,"stem":456},"9.12. Identity Map, Unit of Work та Specification Pattern","\u002Fcsharp\u002Fado-net\u002Fadvanced-patterns","01.csharp\u002F09.ado-net\u002F12.advanced-patterns",{"title":458,"icon":255,"path":459,"stem":460,"children":461,"page":59},"Ef Core","\u002Fcsharp\u002Fef-core","01.csharp\u002F10.ef-core",[462,466,470,474,478,482,486,490,494,498,502,506,510,514,518,522,526,532,538,542,546,550,554,558,562,566,570,574,578,582,586,590,594,598,602,606,610,614,618,622,626,630,634,638,642,646],{"title":463,"path":464,"stem":465},"Що таке ORM? Від SQL до об'єктів","\u002Fcsharp\u002Fef-core\u002Fwhat-is-orm","01.csharp\u002F10.ef-core\u002F01.what-is-orm",{"title":467,"path":468,"stem":469},"Перший проєкт — від нуля до CRUD","\u002Fcsharp\u002Fef-core\u002Ffirst-project","01.csharp\u002F10.ef-core\u002F02.first-project",{"title":471,"path":472,"stem":473},"DbContext — Серце EF Core","\u002Fcsharp\u002Fef-core\u002Fdbcontext-deep-dive","01.csharp\u002F10.ef-core\u002F03.dbcontext-deep-dive",{"title":475,"path":476,"stem":477},"Провайдери баз даних — Архітектура та Вибір СУБД","\u002Fcsharp\u002Fef-core\u002Fdatabase-providers","01.csharp\u002F10.ef-core\u002F04.database-providers",{"title":479,"path":480,"stem":481},"Конвенції EF Core — Магія без конфігурації","\u002Fcsharp\u002Fef-core\u002Fconventions","01.csharp\u002F10.ef-core\u002F05.conventions",{"title":483,"path":484,"stem":485},"Fluent API та Data Annotations — Явна конфігурація моделі","\u002Fcsharp\u002Fef-core\u002Ffluent-api-vs-annotations","01.csharp\u002F10.ef-core\u002F06.fluent-api-vs-annotations",{"title":487,"path":488,"stem":489},"Зв'язки — One-to-One та One-to-Many","\u002Fcsharp\u002Fef-core\u002Frelationships-basics","01.csharp\u002F10.ef-core\u002F07.relationships-basics",{"title":491,"path":492,"stem":493},"Зв'язки Advanced — Many-to-Many та Складні Сценарії","\u002Fcsharp\u002Fef-core\u002Frelationships-advanced","01.csharp\u002F10.ef-core\u002F08.relationships-advanced",{"title":495,"path":496,"stem":497},"Властивості — Типи, Конвертери, Компаратори (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fproperty-configuration-part1","01.csharp\u002F10.ef-core\u002F09.property-configuration-part1",{"title":499,"path":500,"stem":501},"Властивості — Value Comparers, Generators, Shadow Properties (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fproperty-configuration-part2","01.csharp\u002F10.ef-core\u002F09.property-configuration-part2",{"title":503,"path":504,"stem":505},"Складні типи — Owned Types та Complex Types (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fcomplex-types-owned-part1","01.csharp\u002F10.ef-core\u002F10.complex-types-owned-part1",{"title":507,"path":508,"stem":509},"Складні типи — Complex Types, Keyless Entities, Порівняння (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fcomplex-types-owned-part2","01.csharp\u002F10.ef-core\u002F10.complex-types-owned-part2",{"title":511,"path":512,"stem":513},"JSON Columns — Складні дані у JSON (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fjson-columns-part1","01.csharp\u002F10.ef-core\u002F11.json-columns-part1",{"title":515,"path":516,"stem":517},"JSON Columns — Value Comparers, Індекси, Провайдери (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fjson-columns-part2","01.csharp\u002F10.ef-core\u002F11.json-columns-part2",{"title":519,"path":520,"stem":521},"Успадкування — Абстрактні класи та TPH (Частина 1)","\u002Fcsharp\u002Fef-core\u002Finheritance-part1","01.csharp\u002F10.ef-core\u002F12.inheritance-part1",{"title":523,"path":524,"stem":525},"Успадкування — TPT, TPC та Порівняння Стратегій (Частина 2)","\u002Fcsharp\u002Fef-core\u002Finheritance-part2","01.csharp\u002F10.ef-core\u002F12.inheritance-part2",{"title":527,"path":528,"stem":529,"children":530},"Індекси, Обмеження та Схема (Частина 1)","\u002Fcsharp\u002Fef-core\u002Findexes-constraints-part1","01.csharp\u002F10.ef-core\u002F13.indexes-constraints-part1",[531],{"title":527,"path":528,"stem":529},{"title":533,"path":534,"stem":535,"children":536},"Індекси, Обмеження та Схема (Частина 2)","\u002Fcsharp\u002Fef-core\u002Findexes-constraints-part2","01.csharp\u002F10.ef-core\u002F13.indexes-constraints-part2",[537],{"title":533,"path":534,"stem":535},{"title":539,"path":540,"stem":541},"Seed Data — Початкові Дані (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fseeding-part1","01.csharp\u002F10.ef-core\u002F14.seeding-part1",{"title":543,"path":544,"stem":545},"Seed Data — SQL-скрипти, Bogus та Стратегії (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fseeding-part2","01.csharp\u002F10.ef-core\u002F14.seeding-part2",{"title":547,"path":548,"stem":549},"Global Query Filters — Глобальні Фільтри (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fglobal-query-filters-part1","01.csharp\u002F10.ef-core\u002F15.global-query-filters-part1",{"title":551,"path":552,"stem":553},"Global Query Filters — Підводні камені та Інтеграція (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fglobal-query-filters-part2","01.csharp\u002F10.ef-core\u002F15.global-query-filters-part2",{"title":555,"path":556,"stem":557},"LINQ-запити в EF Core (Частина 1)","\u002Fcsharp\u002Fef-core\u002Flinq-queries-part1","01.csharp\u002F10.ef-core\u002F16.linq-queries-part1",{"title":559,"path":560,"stem":561},"LINQ-запити в EF Core (Частина 2)","\u002Fcsharp\u002Fef-core\u002Flinq-queries-part2","01.csharp\u002F10.ef-core\u002F16.linq-queries-part2",{"title":563,"path":564,"stem":565},"Завантаження Пов'язаних Даних (Частина 1)","\u002Fcsharp\u002Fef-core\u002Floading-related-data-part1","01.csharp\u002F10.ef-core\u002F17.loading-related-data-part1",{"title":567,"path":568,"stem":569},"Завантаження Пов'язаних Даних (Частина 2)","\u002Fcsharp\u002Fef-core\u002Floading-related-data-part2","01.csharp\u002F10.ef-core\u002F17.loading-related-data-part2",{"title":571,"path":572,"stem":573},"Raw SQL, Views та Stored Procedures (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fraw-sql-part1","01.csharp\u002F10.ef-core\u002F18.raw-sql-part1",{"title":575,"path":576,"stem":577},"Raw SQL — Stored Procedures, DbFunction та Bulk Operations (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fraw-sql-part2","01.csharp\u002F10.ef-core\u002F18.raw-sql-part2",{"title":579,"path":580,"stem":581},"Продвинуті Запити — Compiled Queries, Bulk та Оптимізація (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fadvanced-queries-part1","01.csharp\u002F10.ef-core\u002F19.advanced-queries-part1",{"title":583,"path":584,"stem":585},"Продвинуті Запити — Query Tags, Bulk та Interceptors (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fadvanced-queries-part2","01.csharp\u002F10.ef-core\u002F19.advanced-queries-part2",{"title":587,"path":588,"stem":589},"Change Tracker — Відстеження Змін (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fchange-tracking-part1","01.csharp\u002F10.ef-core\u002F20.change-tracking-part1",{"title":591,"path":592,"stem":593},"Change Tracker — Графи Об'єктів та Disconnected (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fchange-tracking-part2","01.csharp\u002F10.ef-core\u002F20.change-tracking-part2",{"title":595,"path":596,"stem":597},"Збереження Даних та Транзакції (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fsaving-data-part1","01.csharp\u002F10.ef-core\u002F21.saving-data-part1",{"title":599,"path":600,"stem":601},"Збереження Даних — Concurrency та Outbox (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fsaving-data-part2","01.csharp\u002F10.ef-core\u002F21.saving-data-part2",{"title":603,"path":604,"stem":605},"Конкурентність та Блокування (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fconcurrency-part1","01.csharp\u002F10.ef-core\u002F22.concurrency-part1",{"title":607,"path":608,"stem":609},"Конкурентність — Дедлоки та Queue Processing (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fconcurrency-part2","01.csharp\u002F10.ef-core\u002F22.concurrency-part2",{"title":611,"path":612,"stem":613},"Міграції в EF Core — Основи (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fmigrations-basics-part1","01.csharp\u002F10.ef-core\u002F23.migrations-basics-part1",{"title":615,"path":616,"stem":617},"Міграції в EF Core — Основи (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fmigrations-basics-part2","01.csharp\u002F10.ef-core\u002F23.migrations-basics-part2",{"title":619,"path":620,"stem":621},"Міграції — Просунуті Сценарії (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fmigrations-advanced-part1","01.csharp\u002F10.ef-core\u002F24.migrations-advanced-part1",{"title":623,"path":624,"stem":625},"Міграції — Просунуті Сценарії (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fmigrations-advanced-part2","01.csharp\u002F10.ef-core\u002F24.migrations-advanced-part2",{"title":627,"path":628,"stem":629},"Управління Схемою та Database-First (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fschema-management-part1","01.csharp\u002F10.ef-core\u002F25.schema-management-part1",{"title":631,"path":632,"stem":633},"Управління Схемою та Database-First (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fschema-management-part2","01.csharp\u002F10.ef-core\u002F25.schema-management-part2",{"title":635,"path":636,"stem":637},"Продуктивність EF Core — Основи (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fperformance-fundamentals-part1","01.csharp\u002F10.ef-core\u002F26.performance-fundamentals-part1",{"title":639,"path":640,"stem":641},"Interceptors в EF Core (Частина 1)","\u002Fcsharp\u002Fef-core\u002Finterceptors-part1","01.csharp\u002F10.ef-core\u002F29.interceptors-part1",{"title":643,"path":644,"stem":645},"Interceptors в EF Core — Connection, Transaction та Materialization (Частина 2)","\u002Fcsharp\u002Fef-core\u002Finterceptors-part2","01.csharp\u002F10.ef-core\u002F29.interceptors-part2",{"title":647,"path":648,"stem":649},"План вивчення Entity Framework Core — Повний курс","\u002Fcsharp\u002Fef-core\u002Fimplementation_plan","01.csharp\u002F10.ef-core\u002Fimplementation_plan",{"title":651,"icon":652,"path":653,"stem":654,"children":655,"page":59},"ASP.NET","i-devicon-dotnetcore","\u002Fcsharp\u002Faspnet","01.csharp\u002F11.aspnet",[656,730,791,869,927,941,967,1057,1111,1182,1212,1289,1346],{"title":657,"icon":658,"path":659,"stem":660,"children":661,"page":59},"Minimal API","i-lucide-network","\u002Fcsharp\u002Faspnet\u002Fminimal-api","01.csharp\u002F11.aspnet\u002F01.minimal-api",[662,666,670,674,678,682,686,690,694,698,702,706,710,714,718,722,726],{"title":663,"path":664,"stem":665},"Вступ до ASP.NET та еволюція фреймворку","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fintroduction","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F01.introduction",{"title":667,"path":668,"stem":669},"Перший додаток на ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Ffirst-application","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F02.first-application",{"title":671,"path":672,"stem":673},"WebApplication, Builder та Dependency Injection","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fwebapplication-builder","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F03.webapplication-builder",{"title":675,"path":676,"stem":677},"Конвеєр запитів та Middleware","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Frequest-pipeline-middleware","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F04.request-pipeline-middleware",{"title":679,"path":680,"stem":681},"Маршрутизація в ASP.NET Core: Основи","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Frouting-basics","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F05.routing-basics",{"title":683,"path":684,"stem":685},"Маршрутизація в ASP.NET Core: Розширені можливості","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Frouting-advanced","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F06.routing-advanced",{"title":687,"path":688,"stem":689},"Статичні файли в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fstatic-files","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F07.static-files",{"title":691,"path":692,"stem":693},"Статичні Активи: MapStaticAssets (ASP.NET Core 9.0)","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fstatic-assets","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F08.static-assets",{"title":695,"path":696,"stem":697},"Конфігурація в ASP.NET Core: Основи","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fconfiguration-fundamentals","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F09.configuration-fundamentals",{"title":699,"path":700,"stem":701},"Конфігурація: Паттерн Options","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fconfiguration-options","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F10.configuration-options",{"title":703,"path":704,"stem":705},"Логування в ASP.NET Core: Основи","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Flogging-basics","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F11.logging-basics",{"title":707,"path":708,"stem":709},"Логування: Serilog та Middleware","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Flogging-advanced","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F12.logging-advanced",{"title":711,"path":712,"stem":713},"Управління станом: HttpContext.Items та Cookies","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fstate-management","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F13.state-management",{"title":715,"path":716,"stem":717},"Стан сесії: Sessions","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fsession-state","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F14.session-state",{"title":719,"path":720,"stem":721},"Структура проєкту: від хаосу до архітектури","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fproject-structure","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F15.project-structure",{"title":723,"path":724,"stem":725},"Scalar у Minimal API: повний проєкт і Fluent OpenAPI","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fscalar-openapi-fluent","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F16.scalar-openapi-fluent",{"title":727,"path":728,"stem":729},"Swagger \u002F Swashbuckle у Minimal API: окремий класичний шлях","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fswagger-swashbuckle","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F17.swagger-swashbuckle",{"title":731,"icon":658,"path":732,"stem":733,"children":734,"page":59},"API","\u002Fcsharp\u002Faspnet\u002Fapi","01.csharp\u002F11.aspnet\u002F02.api",[735,739,743,747,751,755,759,763,767,771,775,779,783,787],{"title":736,"path":737,"stem":738},"Що таке API. Клієнт-серверна архітектура","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fwhat-is-api","01.csharp\u002F11.aspnet\u002F02.api\u002F01.what-is-api",{"title":740,"path":741,"stem":742},"Формати даних: JSON, XML, TOML та бінарні формати","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fdata-formats","01.csharp\u002F11.aspnet\u002F02.api\u002F02.data-formats",{"title":744,"path":745,"stem":746},"Парадигми API та концепція REST","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-paradigms-rest","01.csharp\u002F11.aspnet\u002F02.api\u002F03.api-paradigms-rest",{"title":748,"path":749,"stem":750},"HTTP-методи, статус-коди та заголовки","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fhttp-methods-status-codes","01.csharp\u002F11.aspnet\u002F02.api\u002F04.http-methods-status-codes",{"title":752,"path":753,"stem":754},"Організація HTTP API за принципами REST","\u002Fcsharp\u002Faspnet\u002Fapi\u002Frest-organizing","01.csharp\u002F11.aspnet\u002F02.api\u002F05.rest-organizing",{"title":756,"path":757,"stem":758},"Номенклатура URL та CRUD-операції","\u002Fcsharp\u002Faspnet\u002Fapi\u002Furl-nomenclature-crud","01.csharp\u002F11.aspnet\u002F02.api\u002F06.url-nomenclature-crud",{"title":760,"path":761,"stem":762},"Правила дизайну: іменування та стандарти","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-design-naming","01.csharp\u002F11.aspnet\u002F02.api\u002F07.api-design-naming",{"title":764,"path":765,"stem":766},"Валідація, ліміти та обробка помилок","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-design-validation","01.csharp\u002F11.aspnet\u002F02.api\u002F08.api-design-validation",{"title":768,"path":769,"stem":770},"Обробка помилок у Minimal API","\u002Fcsharp\u002Faspnet\u002Fapi\u002Ferror-handling-http","01.csharp\u002F11.aspnet\u002F02.api\u002F09.error-handling-http",{"title":772,"path":773,"stem":774},"Ідемпотентність та синхронізація стану","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fidempotency-sync","01.csharp\u002F11.aspnet\u002F02.api\u002F10.idempotency-sync",{"title":776,"path":777,"stem":778},"Пагінація та організація списків","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fpagination-lists","01.csharp\u002F11.aspnet\u002F02.api\u002F11.pagination-lists",{"title":780,"path":781,"stem":782},"Безпека API, кешування та інтернаціоналізація","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fsecurity-auth","01.csharp\u002F11.aspnet\u002F02.api\u002F12.security-auth",{"title":784,"path":785,"stem":786},"Процес проєктування API та документування","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-design-process","01.csharp\u002F11.aspnet\u002F02.api\u002F13.api-design-process",{"title":788,"path":789,"stem":790},"OpenAPI: контракт, специфікація та документація API","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fopenapi","01.csharp\u002F11.aspnet\u002F02.api\u002F14.openapi",{"title":792,"icon":793,"path":794,"stem":795,"children":796,"page":59},"Auth","i-lucide-shield-check","\u002Fcsharp\u002Faspnet\u002Fauth","01.csharp\u002F11.aspnet\u002F03.auth",[797,801,805,809,813,817,821,825,829,833,837,841,845,849,853,857,861,865],{"title":798,"path":799,"stem":800},"Основи аутентифікації та авторизації","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fauth-fundamentals","01.csharp\u002F11.aspnet\u002F03.auth\u002F01.auth-fundamentals",{"title":802,"path":803,"stem":804},"JWT-аутентифікація","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fjwt-authentication","01.csharp\u002F11.aspnet\u002F03.auth\u002F02.jwt-authentication",{"title":806,"path":807,"stem":808},"Авторизація: ролі, політики та resource-based доступ","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fauthorization-policies","01.csharp\u002F11.aspnet\u002F03.auth\u002F03.authorization-policies",{"title":810,"path":811,"stem":812},"Cookie-аутентифікація та ASP.NET Core Identity","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fcookie-auth-identity","01.csharp\u002F11.aspnet\u002F03.auth\u002F04.cookie-auth-identity",{"title":814,"path":815,"stem":816},"JWT + Refresh Tokens (HttpOnly Cookie)","\u002Fcsharp\u002Faspnet\u002Fauth\u002F04b.identity-auth-jwt","01.csharp\u002F11.aspnet\u002F03.auth\u002F04b.identity-auth-jwt",{"title":818,"path":819,"stem":820},"Identity: Підтвердження Email та Скидання Пароля","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fidentity-email-confirmation","01.csharp\u002F11.aspnet\u002F03.auth\u002F05.identity-email-confirmation",{"title":822,"path":823,"stem":824},"Identity: Двофакторна Аутентифікація (2FA)","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fidentity-two-factor","01.csharp\u002F11.aspnet\u002F03.auth\u002F06.identity-two-factor",{"title":826,"path":827,"stem":828},"Identity: Внутрішня Архітектура та Кастомізація","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fidentity-internals","01.csharp\u002F11.aspnet\u002F03.auth\u002F07.identity-internals",{"title":830,"path":831,"stem":832},"OAuth 2.0 та зовнішні провайдери","\u002Fcsharp\u002Faspnet\u002Fauth\u002Foauth-external-providers","01.csharp\u002F11.aspnet\u002F03.auth\u002F08.oauth-external-providers",{"title":834,"path":835,"stem":836},"Безпека на практиці: CORS, HTTPS та захист від атак","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fsecurity-hardening","01.csharp\u002F11.aspnet\u002F03.auth\u002F09.security-hardening",{"title":838,"path":839,"stem":840},"Теорія OAuth 2.0: Поняття, Аналогії та Флоу","\u002Fcsharp\u002Faspnet\u002Fauth\u002Foauth-theory","01.csharp\u002F11.aspnet\u002F03.auth\u002F10.oauth-theory",{"title":842,"path":843,"stem":844},"OIDC, OAuth 2.0 та Keycloak в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Foidc-keycloak","01.csharp\u002F11.aspnet\u002F03.auth\u002F10.oidc-keycloak",{"title":846,"path":847,"stem":848},"API Keys аутентифікація в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fapi-keys","01.csharp\u002F11.aspnet\u002F03.auth\u002F11.api-keys",{"title":850,"path":851,"stem":852},"Rate Limiting та Throttling в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Frate-limiting","01.csharp\u002F11.aspnet\u002F03.auth\u002F12.rate-limiting",{"title":854,"path":855,"stem":856},"Refresh Token Rotation в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Frefresh-token-rotation","01.csharp\u002F11.aspnet\u002F03.auth\u002F13.refresh-token-rotation",{"title":858,"path":859,"stem":860},"Certificate Authentication та mTLS в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fcertificate-auth","01.csharp\u002F11.aspnet\u002F03.auth\u002F14.certificate-auth",{"title":862,"path":863,"stem":864},"RBAC, ABAC та ReBAC в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Frbac-abac-rebac","01.csharp\u002F11.aspnet\u002F03.auth\u002F15.rbac-abac-rebac",{"title":866,"path":867,"stem":868},"Multi-tenancy та ізоляція даних в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fmulti-tenancy","01.csharp\u002F11.aspnet\u002F03.auth\u002F16.multi-tenancy",{"title":870,"icon":871,"path":872,"stem":873,"children":874,"page":59},"Нотифікації","i-lucide-bell","\u002Fcsharp\u002Faspnet\u002Fnotifications","01.csharp\u002F11.aspnet\u002F04.notifications",[875,879,883,887,891,895,899,903,907,911,915,919,923],{"title":876,"path":877,"stem":878},"In-App нотифікації через базу даних","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fin-app-database-notifications","01.csharp\u002F11.aspnet\u002F04.notifications\u002F01.in-app-database-notifications",{"title":880,"path":881,"stem":882},"Polling: Регулярний запит оновлень","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fpolling","01.csharp\u002F11.aspnet\u002F04.notifications\u002F02.polling",{"title":884,"path":885,"stem":886},"Server-Sent Events: Однострімовий push від сервера","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fserver-sent-events","01.csharp\u002F11.aspnet\u002F04.notifications\u002F03.server-sent-events",{"title":888,"path":889,"stem":890},"WebSockets: Двостороннє з'єднання в реальному часі","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fwebsockets","01.csharp\u002F11.aspnet\u002F04.notifications\u002F04.websockets",{"title":892,"path":893,"stem":894},"SignalR: Абстракція над транспортами реального часу","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fsignalr","01.csharp\u002F11.aspnet\u002F04.notifications\u002F05.signalr",{"title":896,"path":897,"stem":898},"Background Services: Фонові задачі в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fbackground-services","01.csharp\u002F11.aspnet\u002F04.notifications\u002F06.background-services",{"title":900,"path":901,"stem":902},"Web Push нотифікації","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fweb-push","01.csharp\u002F11.aspnet\u002F04.notifications\u002F07.web-push",{"title":904,"path":905,"stem":906},"Email нотифікації","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Femail-notifications","01.csharp\u002F11.aspnet\u002F04.notifications\u002F08.email-notifications",{"title":908,"path":909,"stem":910},"Порівняння підходів: Як вибрати правильну технологію нотифікацій","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fchoosing-the-right-approach","01.csharp\u002F11.aspnet\u002F04.notifications\u002F09.choosing-the-right-approach",{"title":912,"path":913,"stem":914},"Hangfire: Надійне планування фонових задач","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fhangfire","01.csharp\u002F11.aspnet\u002F04.notifications\u002F10.hangfire",{"title":916,"path":917,"stem":918},"Практика: Конвертація зображень у WebP через Hangfire","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fhangfire-image-webp","01.csharp\u002F11.aspnet\u002F04.notifications\u002F11.hangfire-image-webp",{"title":920,"path":921,"stem":922},"Практика: Підготовка відео до HLS-стрімінгу через Hangfire","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fhangfire-video-hls","01.csharp\u002F11.aspnet\u002F04.notifications\u002F12.hangfire-video-hls",{"title":924,"path":925,"stem":926},"Telegram-нотифікації: від одного повідомлення до масових розсилок і мульти-канального підходу","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Ftelegram-notifications","01.csharp\u002F11.aspnet\u002F04.notifications\u002F13.telegram-notifications",{"title":928,"icon":929,"path":930,"stem":931,"children":932,"page":59},"Інтернаціоналізація","i-lucide-languages","\u002Fcsharp\u002Faspnet\u002Fi18n","01.csharp\u002F11.aspnet\u002F05.i18n",[933,937],{"title":934,"path":935,"stem":936},"Інтернаціоналізація (i18n) у Minimal API: від A до Я","\u002Fcsharp\u002Faspnet\u002Fi18n\u002Finternationalization","01.csharp\u002F11.aspnet\u002F05.i18n\u002F01.internationalization",{"title":938,"path":939,"stem":940},"Humanizer: людиномовні рядки у .NET","\u002Fcsharp\u002Faspnet\u002Fi18n\u002Fhumanizer","01.csharp\u002F11.aspnet\u002F05.i18n\u002F02.humanizer",{"title":942,"icon":943,"path":944,"stem":945,"children":946,"page":59},"Кешування","i-lucide-layers","\u002Fcsharp\u002Faspnet\u002Fcaching","01.csharp\u002F11.aspnet\u002F06.caching",[947,951,955,959,963],{"title":948,"path":949,"stem":950},"Огляд кешування: чотири рівні і коли що обирати","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fcaching","01.csharp\u002F11.aspnet\u002F06.caching\u002F01.caching",{"title":952,"path":953,"stem":954},"IMemoryCache: кеш в оперативній пам'яті","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fmemory-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F02.memory-cache",{"title":956,"path":957,"stem":958},"IDistributedCache і Redis: розподілений кеш","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fdistributed-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F03.distributed-cache",{"title":960,"path":961,"stem":962},"Response Cache: HTTP-кешування через Cache-Control","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fresponse-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F04.response-cache",{"title":964,"path":965,"stem":966},"Output Cache: серверний кеш HTTP-відповідей (.NET 7+)","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Foutput-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F05.output-cache",{"title":968,"icon":969,"path":970,"stem":971,"children":972,"page":59},"Тестування","i-lucide-test-tube","\u002Fcsharp\u002Faspnet\u002Ftesting","01.csharp\u002F11.aspnet\u002F07.testing",[973,977,981,985,989,993,997,1001,1005,1009,1013,1017,1021,1025,1029,1033,1037,1041,1045,1049,1053],{"title":974,"path":975,"stem":976},"Що таке тестування? Від інтуїції до науки","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fwhat-is-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F01.what-is-testing",{"title":978,"path":979,"stem":980},"Піраміда тестування — Стратегія, а не Догма","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftesting-pyramid","01.csharp\u002F11.aspnet\u002F07.testing\u002F02.testing-pyramid",{"title":982,"path":983,"stem":984},"Дві Школи Тестування — Лондон проти Детройту","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftesting-schools","01.csharp\u002F11.aspnet\u002F07.testing\u002F03.testing-schools",{"title":986,"path":987,"stem":988},"TDD та BDD — Тести як Дизайн-інструмент","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftdd-and-bdd","01.csharp\u002F11.aspnet\u002F07.testing\u002F04.tdd-and-bdd",{"title":990,"path":991,"stem":992},"Що саме тестувати — Техніки аналізу та Циклomatична складність","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fwhat-to-test","01.csharp\u002F11.aspnet\u002F07.testing\u002F05.what-to-test",{"title":994,"path":995,"stem":996},"Тестові Фреймворки — Навіщо вони і що всередині","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftest-frameworks","01.csharp\u002F11.aspnet\u002F07.testing\u002F06.test-frameworks",{"title":998,"path":999,"stem":1000},"xUnit — Факти, Теорії та Lifecycle тестів","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fxunit-basics","01.csharp\u002F11.aspnet\u002F07.testing\u002F07.xunit-basics",{"title":1002,"path":1003,"stem":1004},"xUnit Advanced — Fixtures, Кастомізація та Розширення","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fxunit-advanced","01.csharp\u002F11.aspnet\u002F07.testing\u002F08.xunit-advanced",{"title":1006,"path":1007,"stem":1008},"Moq — Глибоке занурення в мокування","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fmocking-with-moq","01.csharp\u002F11.aspnet\u002F07.testing\u002F09.mocking-with-moq",{"title":1010,"path":1011,"stem":1012},"Тестування Баз Даних — EF Core, SQLite та Testcontainers","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fdatabase-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F10.database-testing",{"title":1014,"path":1015,"stem":1016},"Integration Testing — Частина 1 [Теорія та WebApplicationFactory]","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fintegration-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F11.integration-testing",{"title":1018,"path":1019,"stem":1020},"Інтеграційне тестування — Практика","\u002Fcsharp\u002Faspnet\u002Ftesting\u002F11a.integration-testing-practice","01.csharp\u002F11.aspnet\u002F07.testing\u002F11a.integration-testing-practice",{"title":1022,"path":1023,"stem":1024},"Integration Testing — Частина 2 [Просунуті Сценарії та Testcontainers]","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fintegration-testing-advanced","01.csharp\u002F11.aspnet\u002F07.testing\u002F12.integration-testing-advanced",{"title":1026,"path":1027,"stem":1028},"Професійний Postman: Колекції, Змінні та GitHub Інтеграція","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fpostman-professional","01.csharp\u002F11.aspnet\u002F07.testing\u002F13.postman-professional",{"title":1030,"path":1031,"stem":1032},"HttpClient у Тестах Частина 1: Архітектура та MockHttpMessageHandler","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fhttpclient-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F14.httpclient-testing",{"title":1034,"path":1035,"stem":1036},"HttpClient у Тестах Частина 2: WireMock.Net та Resilience","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fwiremock-net","01.csharp\u002F11.aspnet\u002F07.testing\u002F15.wiremock-net",{"title":1038,"path":1039,"stem":1040},"Патерни та Анти-патерни Тестування: Test Smells","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftesting-patterns","01.csharp\u002F11.aspnet\u002F07.testing\u002F16.testing-patterns",{"title":1042,"path":1043,"stem":1044},"Просунуті інструменти: Time, Snapshots та Властивості","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fadvanced-testing-tools","01.csharp\u002F11.aspnet\u002F07.testing\u002F17.advanced-testing-tools",{"title":1046,"path":1047,"stem":1048},"Тестування Архітектури з NetArchTest","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Farchitecture-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F18.architecture-testing",{"title":1050,"path":1051,"stem":1052},"Тестування Продуктивності: BenchmarkDotNet, NBomber та k6","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fperformance-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F19.performance-testing",{"title":1054,"path":1055,"stem":1056},"Залишок плану для курсу \"Тестування ASP.NET Minimal API\"","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fremaining_plan","01.csharp\u002F11.aspnet\u002F07.testing\u002Fremaining_plan",{"title":1058,"icon":1059,"path":1060,"stem":1061,"children":1062,"page":59},"Платежі","i-lucide-credit-card","\u002Fcsharp\u002Faspnet\u002Fpayments","01.csharp\u002F11.aspnet\u002F08.payments",[1063,1067,1071,1075,1079,1083,1087,1091,1095,1099,1103,1107],{"title":1064,"path":1065,"stem":1066},"Основи платіжної інфраструктури","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpayment-fundamentals","01.csharp\u002F11.aspnet\u002F08.payments\u002F01.payment-fundamentals",{"title":1068,"path":1069,"stem":1070},"Методи оплати в Україні","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpayment-methods-ukraine","01.csharp\u002F11.aspnet\u002F08.payments\u002F02.payment-methods-ukraine",{"title":1072,"path":1073,"stem":1074},"PCI DSS та безпека платежів","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpci-dss-security","01.csharp\u002F11.aspnet\u002F08.payments\u002F03.pci-dss-security",{"title":1076,"path":1077,"stem":1078},"Архітектура платіжної підсистеми","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpayment-architecture","01.csharp\u002F11.aspnet\u002F08.payments\u002F04.payment-architecture",{"title":1080,"path":1081,"stem":1082},"Інтеграція LiqPay (ПриватБанк)","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fliqpay-integration","01.csharp\u002F11.aspnet\u002F08.payments\u002F05.liqpay-integration",{"title":1084,"path":1085,"stem":1086},"Інтеграція Monobank Acquiring API","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fmonobank-acquiring","01.csharp\u002F11.aspnet\u002F08.payments\u002F06.monobank-acquiring",{"title":1088,"path":1089,"stem":1090},"Інтеграція Stripe","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fstripe-integration","01.csharp\u002F11.aspnet\u002F08.payments\u002F07.stripe-integration",{"title":1092,"path":1093,"stem":1094},"Webhooks — глибоке занурення","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fwebhooks-deep-dive","01.csharp\u002F11.aspnet\u002F08.payments\u002F08.webhooks-deep-dive",{"title":1096,"path":1097,"stem":1098},"Підписки та рекурентні платежі","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fsubscriptions-recurring","01.csharp\u002F11.aspnet\u002F08.payments\u002F09.subscriptions-recurring",{"title":1100,"path":1101,"stem":1102},"Повернення коштів та диспути","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Frefunds-disputes","01.csharp\u002F11.aspnet\u002F08.payments\u002F10.refunds-disputes",{"title":1104,"path":1105,"stem":1106},"Тестування платіжних інтеграцій","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Ftesting-payments","01.csharp\u002F11.aspnet\u002F08.payments\u002F11.testing-payments",{"title":1108,"path":1109,"stem":1110},"Чекліст виходу в Production","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fproduction-checklist","01.csharp\u002F11.aspnet\u002F08.payments\u002F12.production-checklist",{"title":1112,"icon":1113,"items":1114,"path":1127,"stem":1128,"children":1129,"page":59},"Популярні бібліотеки","lucide:box",[1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126],"01.fluent-validation","02.mapster","03.erroror-result-pattern","04.serilog","05.mediatr","06.polly","07.health-checks","08.feature-management","09.fluent-email","10.quest-pdf","11.bogus","12.humanizer-guard","\u002Fcsharp\u002Faspnet\u002Flibraries","01.csharp\u002F11.aspnet\u002F09.libraries",[1130,1134,1138,1142,1146,1150,1154,1158,1162,1166,1170,1174,1178],{"title":1131,"path":1132,"stem":1133},"Валідація з FluentValidation в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ffluent-validation","01.csharp\u002F11.aspnet\u002F09.libraries\u002F01.fluent-validation",{"title":1135,"path":1136,"stem":1137},"Маппінг об","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fmapster","01.csharp\u002F11.aspnet\u002F09.libraries\u002F02.mapster",{"title":1139,"path":1140,"stem":1141},"Обробка помилок з ErrorOr та Result Pattern в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ferroror-result-pattern","01.csharp\u002F11.aspnet\u002F09.libraries\u002F03.erroror-result-pattern",{"title":1143,"path":1144,"stem":1145},"Структуроване логування з Serilog в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fserilog","01.csharp\u002F11.aspnet\u002F09.libraries\u002F04.serilog",{"title":1147,"path":1148,"stem":1149},"CQRS та Mediator з MediatR в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fmediatr","01.csharp\u002F11.aspnet\u002F09.libraries\u002F05.mediatr",{"title":1151,"path":1152,"stem":1153},"Відмовостійкість з Polly в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fpolly","01.csharp\u002F11.aspnet\u002F09.libraries\u002F06.polly",{"title":1155,"path":1156,"stem":1157},"Health Checks в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fhealth-checks","01.csharp\u002F11.aspnet\u002F09.libraries\u002F07.health-checks",{"title":1159,"path":1160,"stem":1161},"Feature Management та Feature Flags в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ffeature-management","01.csharp\u002F11.aspnet\u002F09.libraries\u002F08.feature-management",{"title":1163,"path":1164,"stem":1165},"Відправка Email з FluentEmail в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ffluent-email","01.csharp\u002F11.aspnet\u002F09.libraries\u002F09.fluent-email",{"title":1167,"path":1168,"stem":1169},"Генерація PDF з QuestPDF в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fquest-pdf","01.csharp\u002F11.aspnet\u002F09.libraries\u002F10.quest-pdf",{"title":1171,"path":1172,"stem":1173},"Генерація тестових даних з Bogus в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fbogus","01.csharp\u002F11.aspnet\u002F09.libraries\u002F11.bogus",{"title":1175,"path":1176,"stem":1177},"Humanizer та Guard Clauses в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fhumanizer-guard","01.csharp\u002F11.aspnet\u002F09.libraries\u002F12.humanizer-guard",{"title":1179,"path":1180,"stem":1181},"План модуля 10.libraries — Популярні бібліотеки ASP.NET","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fplan","01.csharp\u002F11.aspnet\u002F09.libraries\u002Fplan",{"title":1183,"icon":1184,"path":1185,"stem":1186,"children":1187,"page":59},"Razor Pages","i-lucide-layout-template","\u002Fcsharp\u002Faspnet\u002Frazor-pages","01.csharp\u002F11.aspnet\u002F10.razor-pages",[1188,1192,1196,1200,1204,1208],{"title":1189,"path":1190,"stem":1191},"Від Minimal API до Razor Pages: концептуальний перехід","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Ffrom-minimal-api","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F01.from-minimal-api",{"title":1193,"path":1194,"stem":1195},"PageModel: логіка сторінки Razor Pages","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Fpage-model","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F02.page-model",{"title":1197,"path":1198,"stem":1199},"Razor синтаксис: шаблонізатор у .cshtml","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Frazor-syntax","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F03.razor-syntax",{"title":1201,"path":1202,"stem":1203},"Tag Helpers: типізований HTML","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Ftag-helpers","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F04.tag-helpers",{"title":1205,"path":1206,"stem":1207},"Форми і валідація: повний цикл обробки даних","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Fforms-validation","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F05.forms-validation",{"title":1209,"path":1210,"stem":1211},"Практичний проєкт: TaskManager на Razor Pages","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Fproject-task-manager","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F06.project-task-manager",{"title":1213,"path":1214,"stem":1215,"children":1216,"page":59},"ASP.NET Core MVC","\u002Fcsharp\u002Faspnet\u002Fmvc","01.csharp\u002F11.aspnet\u002F11.mvc",[1217,1221,1225,1229,1233,1237,1241,1245,1249,1253,1257,1261,1265,1269,1273,1277,1281,1285],{"title":1218,"path":1219,"stem":1220},"Патерн MVC: архітектура, що змінила веб","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fmvc-pattern","01.csharp\u002F11.aspnet\u002F11.mvc\u002F01.mvc-pattern",{"title":1222,"path":1223,"stem":1224},"Від Razor Pages до MVC: концептуальний перехід","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Ffrom-razor-pages","01.csharp\u002F11.aspnet\u002F11.mvc\u002F02.from-razor-pages",{"title":1226,"path":1227,"stem":1228},"Controllers та Actions: серце MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fcontrollers-actions","01.csharp\u002F11.aspnet\u002F11.mvc\u002F03.controllers-actions",{"title":1230,"path":1231,"stem":1232},"Маршрутизація в MVC: Convention vs Attribute Routing","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Frouting-mvc","01.csharp\u002F11.aspnet\u002F11.mvc\u002F04.routing-mvc",{"title":1234,"path":1235,"stem":1236},"Model Binding: від HTTP до C#","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fmodel-binding","01.csharp\u002F11.aspnet\u002F11.mvc\u002F05.model-binding",{"title":1238,"path":1239,"stem":1240},"Views, ViewData, ViewBag, TempData і ViewModel","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fviews-viewdata-tempdata","01.csharp\u002F11.aspnet\u002F11.mvc\u002F06.views-viewdata-tempdata",{"title":1242,"path":1243,"stem":1244},"Filters: аспектно-орієнтоване програмування в MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Ffilters","01.csharp\u002F11.aspnet\u002F11.mvc\u002F07.filters",{"title":1246,"path":1247,"stem":1248},"Areas: структурування великих застосунків","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fareas","01.csharp\u002F11.aspnet\u002F11.mvc\u002F08.areas",{"title":1250,"path":1251,"stem":1252},"View Components: повторювані незалежні блоки UI","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fview-components","01.csharp\u002F11.aspnet\u002F11.mvc\u002F09.view-components",{"title":1254,"path":1255,"stem":1256},"Display та Editor Templates","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fdisplay-editor-templates","01.csharp\u002F11.aspnet\u002F11.mvc\u002F10.display-editor-templates",{"title":1258,"path":1259,"stem":1260},"Валідація: IValidatableObject та FluentValidation","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fvalidation-advanced","01.csharp\u002F11.aspnet\u002F11.mvc\u002F11.validation-advanced",{"title":1262,"path":1263,"stem":1264},"HTMX: інтерактивність через HTML-атрибути","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fhtmx","01.csharp\u002F11.aspnet\u002F11.mvc\u002F12.htmx",{"title":1266,"path":1267,"stem":1268},"HTMX у ASP.NET Core MVC: серверна інтеграція","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fajax-htmx-mvc","01.csharp\u002F11.aspnet\u002F11.mvc\u002F13.ajax-htmx-mvc",{"title":1270,"path":1271,"stem":1272},"Практичний проєкт: Каталог товарів з HTMX","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fhtmx-project","01.csharp\u002F11.aspnet\u002F11.mvc\u002F14.htmx-project",{"title":1274,"path":1275,"stem":1276},"Завантаження та обробка файлів","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Ffile-upload","01.csharp\u002F11.aspnet\u002F11.mvc\u002F15.file-upload",{"title":1278,"path":1279,"stem":1280},"Глобалізація та Локалізація MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fglobalization-localization","01.csharp\u002F11.aspnet\u002F11.mvc\u002F16.globalization-localization",{"title":1282,"path":1283,"stem":1284},"Підсумковий проєкт: Блог-платформа","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fmvc-project","01.csharp\u002F11.aspnet\u002F11.mvc\u002F17.mvc-project",{"title":1286,"path":1287,"stem":1288},"План курсу: ASP.NET Core MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fplan","01.csharp\u002F11.aspnet\u002F11.mvc\u002Fplan",{"title":1290,"path":1291,"stem":1292,"children":1293,"page":59},"Web Api","\u002Fcsharp\u002Faspnet\u002Fweb-api","01.csharp\u002F11.aspnet\u002F12.web-api",[1294,1298,1302,1306,1310,1314,1318,1322,1326,1330,1334,1338,1342],{"title":1295,"path":1296,"stem":1297},"Від Minimal API до Controller-based API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Ffrom-minimal-api-to-controllers","01.csharp\u002F11.aspnet\u002F12.web-api\u002F01.from-minimal-api-to-controllers",{"title":1299,"path":1300,"stem":1301},"ControllerBase, ActionResult\u003CT> та Response Types","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fcontroller-base-actionresult","01.csharp\u002F11.aspnet\u002F12.web-api\u002F02.controller-base-actionresult",{"title":1303,"path":1304,"stem":1305},"Content Negotiation - JSON, XML та власні форматери","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fcontent-negotiation","01.csharp\u002F11.aspnet\u002F12.web-api\u002F03.content-negotiation",{"title":1307,"path":1308,"stem":1309},"Версіонування API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fapi-versioning","01.csharp\u002F11.aspnet\u002F12.web-api\u002F04.api-versioning",{"title":1311,"path":1312,"stem":1313},"ProblemDetails та структурована обробка помилок","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fproblemdetails-error-handling","01.csharp\u002F11.aspnet\u002F12.web-api\u002F05.problemdetails-error-handling",{"title":1315,"path":1316,"stem":1317},"Фільтри у Web API контексті","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Ffilters-for-api","01.csharp\u002F11.aspnet\u002F12.web-api\u002F06.filters-for-api",{"title":1319,"path":1320,"stem":1321},"Пагінація, фільтрація та сортування","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fpagination-filtering-sorting","01.csharp\u002F11.aspnet\u002F12.web-api\u002F07.pagination-filtering-sorting",{"title":1323,"path":1324,"stem":1325},"HATEOAS та Resource Expansion","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fhateoas-resource-expansion","01.csharp\u002F11.aspnet\u002F12.web-api\u002F08.hateoas-resource-expansion",{"title":1327,"path":1328,"stem":1329},"Гібридна архітектура - Minimal API + Controllers","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fminimal-api-vs-controllers-hybrid","01.csharp\u002F11.aspnet\u002F12.web-api\u002F09.minimal-api-vs-controllers-hybrid",{"title":1331,"path":1332,"stem":1333},"Документація API - Swashbuckle, NSwag та генерація клієнтів","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fapi-documentation-generation","01.csharp\u002F11.aspnet\u002F12.web-api\u002F10.api-documentation-generation",{"title":1335,"path":1336,"stem":1337},"Health Checks та моніторинг API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fhealth-checks-monitoring","01.csharp\u002F11.aspnet\u002F12.web-api\u002F11.health-checks-monitoring",{"title":1339,"path":1340,"stem":1341},"Підсумковий проєкт - Production-Ready REST API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fweb-api-project","01.csharp\u002F11.aspnet\u002F12.web-api\u002F12.web-api-project",{"title":1343,"path":1344,"stem":1345},"План курсу: ASP.NET Core Web API (Controllers)","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fplan","01.csharp\u002F11.aspnet\u002F12.web-api\u002Fplan",{"title":1347,"icon":1348,"path":1349,"stem":1350,"children":1351,"page":59},"Моніторинг","i-lucide-activity","\u002Fcsharp\u002Faspnet\u002Fmonitoring","01.csharp\u002F11.aspnet\u002F13.monitoring",[1352,1356,1360],{"title":1353,"path":1354,"stem":1355},"Спостережуваність: від console.log до production-систем","\u002Fcsharp\u002Faspnet\u002Fmonitoring\u002Fobservability-intro","01.csharp\u002F11.aspnet\u002F13.monitoring\u002F01.observability-intro",{"title":1357,"path":1358,"stem":1359},"Health Checks: перший рівень observability","\u002Fcsharp\u002Faspnet\u002Fmonitoring\u002Fhealth-checks","01.csharp\u002F11.aspnet\u002F13.monitoring\u002F02.health-checks",{"title":1361,"path":1362,"stem":1363},"Вбудовані метрики .NET 10 та System.Diagnostics.Metrics","\u002Fcsharp\u002Faspnet\u002Fmonitoring\u002Fdotnet-metrics","01.csharp\u002F11.aspnet\u002F13.monitoring\u002F03.dotnet-metrics",{"title":1365,"icon":1366,"path":1367,"stem":1368,"children":1369,"page":59},"Desktop UI","i-lucide-app-window","\u002Fcsharp\u002Fdesktop-ui","01.csharp\u002F12.desktop-ui",[1370,1374,1378,1382,1386,1390,1394,1398,1402,1406,1410,1414,1418,1422,1426,1430,1434,1438,1442,1446,1450,1454,1458,1462,1466,1470,1474,1478,1482,1486,1490,1494,1498,1502,1506,1510,1514,1518,1522,1526,1530,1534,1538,1542,1546,1550,1554,1558,1562,1566,1570,1574,1578,1582,1586,1590,1594,1598,1602,1606,1610,1614,1618,1622,1626,1630,1634,1638,1642,1646,1650],{"title":1371,"path":1372,"stem":1373},"Що таке десктопна розробка?","\u002Fcsharp\u002Fdesktop-ui\u002Fwhat-is-desktop-dev","01.csharp\u002F12.desktop-ui\u002F01.what-is-desktop-dev",{"title":1375,"path":1376,"stem":1377},"Архітектура WPF — як влаштований графічний інтерфейс","\u002Fcsharp\u002Fdesktop-ui\u002Fwpf-architecture","01.csharp\u002F12.desktop-ui\u002F02.wpf-architecture",{"title":1379,"path":1380,"stem":1381},"Перший WPF-проєкт — від нуля до вікна","\u002Fcsharp\u002Fdesktop-ui\u002Ffirst-wpf-app","01.csharp\u002F12.desktop-ui\u002F03.first-wpf-app",{"title":1383,"path":1384,"stem":1385},"Перший Avalonia-проєкт: WPF для всіх платформ","\u002Fcsharp\u002Fdesktop-ui\u002F03a.first-avalonia-app","01.csharp\u002F12.desktop-ui\u002F03a.first-avalonia-app",{"title":1387,"path":1388,"stem":1389},"XAML: декларативний інтерфейс","\u002Fcsharp\u002Fdesktop-ui\u002Fxaml-basics","01.csharp\u002F12.desktop-ui\u002F04.xaml-basics",{"title":1391,"path":1392,"stem":1393},"Fluent UI у WPF — сучасний дизайн Windows 11","\u002Fcsharp\u002Fdesktop-ui\u002F04a.wpf-fluent-ui","01.csharp\u002F12.desktop-ui\u002F04a.wpf-fluent-ui",{"title":1395,"path":1396,"stem":1397},"WPF UI — сучасна бібліотека Fluent контролів","\u002Fcsharp\u002Fdesktop-ui\u002F04b.wpf-ui-library","01.csharp\u002F12.desktop-ui\u002F04b.wpf-ui-library",{"title":1399,"path":1400,"stem":1401},"HandyControl — велика бібліотека UI контролів для WPF","\u002Fcsharp\u002Fdesktop-ui\u002F04c.handycontrol-library","01.csharp\u002F12.desktop-ui\u002F04c.handycontrol-library",{"title":1403,"path":1404,"stem":1405},"Простори імен та ресурси XAML","\u002Fcsharp\u002Fdesktop-ui\u002Fxaml-namespaces-resources","01.csharp\u002F12.desktop-ui\u002F05.xaml-namespaces-resources",{"title":1407,"path":1408,"stem":1409},"XAML в Avalonia: ключові відмінності від WPF","\u002Fcsharp\u002Fdesktop-ui\u002F05a.avalonia-xaml-differences","01.csharp\u002F12.desktop-ui\u002F05a.avalonia-xaml-differences",{"title":1411,"path":1412,"stem":1413},"Розширення розмітки XAML (Markup Extensions)","\u002Fcsharp\u002Fdesktop-ui\u002Fxaml-markup-extensions","01.csharp\u002F12.desktop-ui\u002F06.xaml-markup-extensions",{"title":1415,"path":1416,"stem":1417},"Панелі Layout: StackPanel, WrapPanel, DockPanel","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-panels-part1","01.csharp\u002F12.desktop-ui\u002F07.layout-panels-part1",{"title":1419,"path":1420,"stem":1421},"Grid, Canvas, UniformGrid","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-panels-part2","01.csharp\u002F12.desktop-ui\u002F07.layout-panels-part2",{"title":1423,"path":1424,"stem":1425},"Просунуті техніки Layout","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-advanced","01.csharp\u002F12.desktop-ui\u002F08.layout-advanced",{"title":1427,"path":1428,"stem":1429},"Адаптивний Layout та найкращі практики","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-responsive","01.csharp\u002F12.desktop-ui\u002F09.layout-responsive",{"title":1431,"path":1432,"stem":1433},"Layout в Avalonia: відмінності та нові можливості","\u002Fcsharp\u002Fdesktop-ui\u002F09a.layout-avalonia","01.csharp\u002F12.desktop-ui\u002F09a.layout-avalonia",{"title":1435,"path":1436,"stem":1437},"Button, Image, ProgressBar та інші базові контроли","\u002Fcsharp\u002Fdesktop-ui\u002Fbasic-controls","01.csharp\u002F12.desktop-ui\u002F10.basic-controls",{"title":1439,"path":1440,"stem":1441},"Контроли в Avalonia: відмінності від WPF","\u002Fcsharp\u002Fdesktop-ui\u002F10a.controls-avalonia","01.csharp\u002F12.desktop-ui\u002F10a.controls-avalonia",{"title":1443,"path":1444,"stem":1445},"Текстові контроли — TextBlock, TextBox, RichTextBox","\u002Fcsharp\u002Fdesktop-ui\u002Ftext-controls","01.csharp\u002F12.desktop-ui\u002F11.text-controls",{"title":1447,"path":1448,"stem":1449},"Контроли вибору — CheckBox, RadioButton, ComboBox, ListBox, DatePicker","\u002Fcsharp\u002Fdesktop-ui\u002Fselection-controls","01.csharp\u002F12.desktop-ui\u002F12.selection-controls",{"title":1451,"path":1452,"stem":1453},"Content Model — GroupBox, Expander, TabControl, StatusBar","\u002Fcsharp\u002Fdesktop-ui\u002Fcontent-controls","01.csharp\u002F12.desktop-ui\u002F13.content-controls",{"title":1455,"path":1456,"stem":1457},"UI\u002FUX принципи десктопних застосунків","\u002Fcsharp\u002Fdesktop-ui\u002F13a.ui-ux-principles","01.csharp\u002F12.desktop-ui\u002F13a.ui-ux-principles",{"title":1459,"path":1460,"stem":1461},"Dependency Properties — Концепція та Value Resolution","\u002Fcsharp\u002Fdesktop-ui\u002Fdependency-properties-part1","01.csharp\u002F12.desktop-ui\u002F14.dependency-properties-part1",{"title":1463,"path":1464,"stem":1465},"Avalonia Property System — StyledProperty та DirectProperty","\u002Fcsharp\u002Fdesktop-ui\u002F14a.avalonia-property-system","01.csharp\u002F12.desktop-ui\u002F14a.avalonia-property-system",{"title":1467,"path":1468,"stem":1469},"Attached Properties — Властивості без меж","\u002Fcsharp\u002Fdesktop-ui\u002Fattached-properties","01.csharp\u002F12.desktop-ui\u002F15.attached-properties",{"title":1471,"path":1472,"stem":1473},"Routed Events — Маршрутизація подій у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Frouted-events","01.csharp\u002F12.desktop-ui\u002F16.routed-events",{"title":1475,"path":1476,"stem":1477},"Data Binding — Від Code-Behind до Декларативності","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-binding-basics-part1","01.csharp\u002F12.desktop-ui\u002F17.data-binding-basics-part1",{"title":1479,"path":1480,"stem":1481},"INotifyPropertyChanged — Живе оновлення UI","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-binding-basics-part2","01.csharp\u002F12.desktop-ui\u002F17.data-binding-basics-part2",{"title":1483,"path":1484,"stem":1485},"Compiled Bindings в Avalonia — Безпека на етапі компіляції","\u002Fcsharp\u002Fdesktop-ui\u002F17a.avalonia-compiled-bindings","01.csharp\u002F12.desktop-ui\u002F17a.avalonia-compiled-bindings",{"title":1487,"path":1488,"stem":1489},"Просунутий Data Binding — ElementName, RelativeSource, MultiBinding","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-binding-advanced","01.csharp\u002F12.desktop-ui\u002F18.data-binding-advanced",{"title":1491,"path":1492,"stem":1493},"Value Converters — Перетворення типів даних у Data Binding","\u002Fcsharp\u002Fdesktop-ui\u002Fvalue-converters","01.csharp\u002F12.desktop-ui\u002F19.value-converters",{"title":1495,"path":1496,"stem":1497},"Data Templates — Візуалізація об'єктів у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-templates","01.csharp\u002F12.desktop-ui\u002F20.data-templates",{"title":1499,"path":1500,"stem":1501},"Collections Binding Part 1 — ObservableCollection та ItemsControl","\u002Fcsharp\u002Fdesktop-ui\u002Fcollections-binding-part1","01.csharp\u002F12.desktop-ui\u002F21.collections-binding-part1",{"title":1503,"path":1504,"stem":1505},"Collections Binding Part 2 — ICollectionView, Filtering, Sorting та Virtualization","\u002Fcsharp\u002Fdesktop-ui\u002Fcollections-binding-part2","01.csharp\u002F12.desktop-ui\u002F21.collections-binding-part2",{"title":1507,"path":1508,"stem":1509},"MVVM Pattern — Від Spaghetti Code до архітектури","\u002Fcsharp\u002Fdesktop-ui\u002Fmvvm-pattern","01.csharp\u002F12.desktop-ui\u002F22.mvvm-pattern",{"title":1511,"path":1512,"stem":1513},"ViewModel Implementation — Від BaseViewModel до валідації","\u002Fcsharp\u002Fdesktop-ui\u002Fviewmodel-implementation","01.csharp\u002F12.desktop-ui\u002F23.viewmodel-implementation",{"title":1515,"path":1516,"stem":1517},"Commands — Від event handlers до декларативних команд","\u002Fcsharp\u002Fdesktop-ui\u002Fcommands","01.csharp\u002F12.desktop-ui\u002F24.commands",{"title":1519,"path":1520,"stem":1521},"MVVM Toolkit — MVVM без boilerplate через Source Generators","\u002Fcsharp\u002Fdesktop-ui\u002Fmvvm-toolkit","01.csharp\u002F12.desktop-ui\u002F25.mvvm-toolkit",{"title":1523,"path":1524,"stem":1525},"Messenger Pattern — Комунікація між ViewModel без прямих посилань","\u002Fcsharp\u002Fdesktop-ui\u002Fmessenger-pattern","01.csharp\u002F12.desktop-ui\u002F26.messenger-pattern",{"title":1527,"path":1528,"stem":1529},"Стилі WPF — CSS для десктопу","\u002Fcsharp\u002Fdesktop-ui\u002Fstyles-basics","01.csharp\u002F12.desktop-ui\u002F27.styles-basics",{"title":1531,"path":1532,"stem":1533},"CSS-like стилі Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002F27a.avalonia-css-styling","01.csharp\u002F12.desktop-ui\u002F27a.avalonia-css-styling",{"title":1535,"path":1536,"stem":1537},"Control Templates — Частина 1. Концепція та TemplateBinding","\u002Fcsharp\u002Fdesktop-ui\u002Fcontrol-templates-part1","01.csharp\u002F12.desktop-ui\u002F28.control-templates-part1",{"title":1539,"path":1540,"stem":1541},"Control Templates — Частина 2. Named Parts та ContentPresenter","\u002Fcsharp\u002Fdesktop-ui\u002Fcontrol-templates-part2","01.csharp\u002F12.desktop-ui\u002F28.control-templates-part2",{"title":1543,"path":1544,"stem":1545},"Control Themes в Avalonia — нова ера стилізації","\u002Fcsharp\u002Fdesktop-ui\u002F28a.avalonia-control-themes","01.csharp\u002F12.desktop-ui\u002F28a.avalonia-control-themes",{"title":1547,"path":1548,"stem":1549},"Triggers та Visual State Manager у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Ftriggers-visual-states","01.csharp\u002F12.desktop-ui\u002F29.triggers-visual-states",{"title":1551,"path":1552,"stem":1553},"Pseudo-classes в Avalonia — замість WPF Triggers","\u002Fcsharp\u002Fdesktop-ui\u002F29a.avalonia-pseudo-classes","01.csharp\u002F12.desktop-ui\u002F29a.avalonia-pseudo-classes",{"title":1555,"path":1556,"stem":1557},"Теми та ресурсні словники у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fresources-themes","01.csharp\u002F12.desktop-ui\u002F30.resources-themes",{"title":1559,"path":1560,"stem":1561},"Avalonia Themes — Fluent Design та система тематизації","\u002Fcsharp\u002Fdesktop-ui\u002F30a.avalonia-themes-fluent","01.csharp\u002F12.desktop-ui\u002F30a.avalonia-themes-fluent",{"title":1563,"path":1564,"stem":1565},"Контроли колекцій — глибоке занурення","\u002Fcsharp\u002Fdesktop-ui\u002Fcollection-controls","01.csharp\u002F12.desktop-ui\u002F31.collection-controls",{"title":1567,"path":1568,"stem":1569},"DataGrid — колонки та базове відображення","\u002Fcsharp\u002Fdesktop-ui\u002Fdatagrid-part1","01.csharp\u002F12.desktop-ui\u002F32.datagrid-part1",{"title":1571,"path":1572,"stem":1573},"DataGrid — сортування, фільтрація, редагування","\u002Fcsharp\u002Fdesktop-ui\u002Fdatagrid-part2","01.csharp\u002F12.desktop-ui\u002F32.datagrid-part2",{"title":1575,"path":1576,"stem":1577},"TreeView та GridView","\u002Fcsharp\u002Fdesktop-ui\u002Ftreeview-listview","01.csharp\u002F12.desktop-ui\u002F33.treeview-listview",{"title":1579,"path":1580,"stem":1581},"Меню, Toolbar, ContextMenu, StatusBar","\u002Fcsharp\u002Fdesktop-ui\u002Fmenus-toolbars","01.csharp\u002F12.desktop-ui\u002F34.menus-toolbars",{"title":1583,"path":1584,"stem":1585},"Навігація та керування вікнами. Частина 1: вікна та сторінки","\u002Fcsharp\u002Fdesktop-ui\u002Fnavigation-windows-part1","01.csharp\u002F12.desktop-ui\u002F35.navigation-windows-part1",{"title":1587,"path":1588,"stem":1589},"Навігація та керування вікнами. Частина 2: MVVM-навігація","\u002Fcsharp\u002Fdesktop-ui\u002Fnavigation-windows-part2","01.csharp\u002F12.desktop-ui\u002F35.navigation-windows-part2",{"title":1591,"path":1592,"stem":1593},"Avalonia — Навігація та діалоги","\u002Fcsharp\u002Fdesktop-ui\u002F35a.avalonia-navigation-dialogs","01.csharp\u002F12.desktop-ui\u002F35a.avalonia-navigation-dialogs",{"title":1595,"path":1596,"stem":1597},"Діалоги та File Pickers у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fdialogs-file-pickers","01.csharp\u002F12.desktop-ui\u002F36.dialogs-file-pickers",{"title":1599,"path":1600,"stem":1601},"UserControl: компонентний підхід у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fuser-controls","01.csharp\u002F12.desktop-ui\u002F37.user-controls",{"title":1603,"path":1604,"stem":1605},"Custom Controls: Lookless Controls у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fcustom-controls","01.csharp\u002F12.desktop-ui\u002F38.custom-controls",{"title":1607,"path":1608,"stem":1609},"Avalonia TemplatedControl — Lookless Controls","\u002Fcsharp\u002Fdesktop-ui\u002F38a.avalonia-templated-controls","01.csharp\u002F12.desktop-ui\u002F38a.avalonia-templated-controls",{"title":1611,"path":1612,"stem":1613},"Анімації у WPF: Storyboard та Easing Functions","\u002Fcsharp\u002Fdesktop-ui\u002Fanimations-transitions","01.csharp\u002F12.desktop-ui\u002F39.animations-transitions",{"title":1615,"path":1616,"stem":1617},"Анімації в Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002F39a.avalonia-animations","01.csharp\u002F12.desktop-ui\u002F39a.avalonia-animations",{"title":1619,"path":1620,"stem":1621},"2D Графіка та Мультимедіа у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fmedia-graphics","01.csharp\u002F12.desktop-ui\u002F40.media-graphics",{"title":1623,"path":1624,"stem":1625},"Dependency Injection у WPF та Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002Fdi-integration","01.csharp\u002F12.desktop-ui\u002F41.di-integration",{"title":1627,"path":1628,"stem":1629},"SQLite та EF Core у десктопних додатках","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-persistence-part1","01.csharp\u002F12.desktop-ui\u002F42.data-persistence-part1",{"title":1631,"path":1632,"stem":1633},"Repository Pattern та Unit of Work","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-persistence-part2","01.csharp\u002F12.desktop-ui\u002F43.data-persistence-part2",{"title":1635,"path":1636,"stem":1637},"Тестування ViewModels","\u002Fcsharp\u002Fdesktop-ui\u002Fviewmodel-testing","01.csharp\u002F12.desktop-ui\u002F44.viewmodel-testing",{"title":1639,"path":1640,"stem":1641},"Avalonia Headless Testing — тестування UI без вікон","\u002Fcsharp\u002Fdesktop-ui\u002F44a.avalonia-headless-testing","01.csharp\u002F12.desktop-ui\u002F44a.avalonia-headless-testing",{"title":1643,"path":1644,"stem":1645},"Кросплатформна розробка з Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002Favalonia-cross-platform","01.csharp\u002F12.desktop-ui\u002F45.avalonia-cross-platform",{"title":1647,"path":1648,"stem":1649},"Пакування та розгортання Avalonia додатків","\u002Fcsharp\u002Fdesktop-ui\u002Favalonia-packaging-deployment","01.csharp\u002F12.desktop-ui\u002F46.avalonia-packaging-deployment",{"title":1651,"path":1652,"stem":1653},"Розгортання WPF застосунків","\u002Fcsharp\u002Fdesktop-ui\u002Fwpf-packaging-deployment","01.csharp\u002F12.desktop-ui\u002F47.wpf-packaging-deployment",{"title":1655,"icon":658,"path":1656,"stem":1657,"children":1658,"page":59},"Network Programming","\u002Fcsharp\u002Fnetwork-programming","01.csharp\u002F13.network-programming",[1659,1663,1667,1671,1675,1679,1683,1687,1691,1695,1699],{"title":1660,"path":1661,"stem":1662},"Основи комп'ютерних мереж","\u002Fcsharp\u002Fnetwork-programming\u002Ffoundations","01.csharp\u002F13.network-programming\u002F01.foundations",{"title":1664,"path":1665,"stem":1666},"Модель OSI та стек TCP\u002FIP","\u002Fcsharp\u002Fnetwork-programming\u002Fosi-model","01.csharp\u002F13.network-programming\u002F02.osi-model",{"title":1668,"path":1669,"stem":1670},"IP-протокол та адресація","\u002Fcsharp\u002Fnetwork-programming\u002Fip-addressing","01.csharp\u002F13.network-programming\u002F03.ip-addressing",{"title":1672,"path":1673,"stem":1674},"UDP — протокол без з'єднання","\u002Fcsharp\u002Fnetwork-programming\u002Fudp","01.csharp\u002F13.network-programming\u002F05.udp",{"title":1676,"path":1677,"stem":1678},"UDP Broadcast та Multicast","\u002Fcsharp\u002Fnetwork-programming\u002Fudp-broadcast-multicast","01.csharp\u002F13.network-programming\u002F06.udp-broadcast-multicast",{"title":1680,"path":1681,"stem":1682},"HTTP — протокол вебу","\u002Fcsharp\u002Fnetwork-programming\u002Fhttp-fundamentals","01.csharp\u002F13.network-programming\u002F07.http-fundamentals",{"title":1684,"path":1685,"stem":1686},"HttpListener — вбудований HTTP-сервер .NET","\u002Fcsharp\u002Fnetwork-programming\u002F07a.http-listener","01.csharp\u002F13.network-programming\u002F07a.http-listener",{"title":1688,"path":1689,"stem":1690},"HTTP Advanced — cookies, аутентифікація та HTTPS","\u002Fcsharp\u002Fnetwork-programming\u002Fhttp-advanced","01.csharp\u002F13.network-programming\u002F08.http-advanced",{"title":1692,"path":1693,"stem":1694},"SMTP та протоколи електронної пошти","\u002Fcsharp\u002Fnetwork-programming\u002Fsmtp","01.csharp\u002F13.network-programming\u002F09.smtp",{"title":1696,"path":1697,"stem":1698},"WebSocket — повнодуплексний протокол реального часу","\u002Fcsharp\u002Fnetwork-programming\u002Fwebsockets","01.csharp\u002F13.network-programming\u002F10.websockets",{"title":1700,"path":1701,"stem":1702},"TLS\u002FSSL — криптографічний захист мережевих з'єднань","\u002Fcsharp\u002Fnetwork-programming\u002Ftls-ssl","01.csharp\u002F13.network-programming\u002F11.tls-ssl",{"title":1704,"path":1705,"stem":1706},"C# & .NET: The Ultimate Roadmap","\u002Fcsharp\u002Froadmap","01.csharp\u002Froadmap",{"title":1708,"icon":1709,"path":1710,"stem":1711,"children":1712,"page":59},"C++","i-devicon-cplusplus","\u002Fcpp","02.cpp",[1713,1717,1721,1725,1729,1733,1737,1741,1745,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080],{"title":1714,"path":1715,"stem":1716},"Вступ у програмування та алгоритми","\u002Fcpp\u002Fintro-algorithms","02.cpp\u002F01.intro-algorithms",{"title":1718,"path":1719,"stem":1720},"Code Style: угоди про оформлення коду","\u002Fcpp\u002Fcode-style","02.cpp\u002F02.code-style",{"title":1722,"path":1723,"stem":1724},"Середовище розробки та перший проєкт","\u002Fcpp\u002Fide-setup","02.cpp\u002F03.ide-setup",{"title":1726,"path":1727,"stem":1728},"Вивід даних на екран","\u002Fcpp\u002Fdata-output","02.cpp\u002F04.data-output",{"title":1730,"path":1731,"stem":1732},"Типи даних, змінні та константи","\u002Fcpp\u002Fdata-types-variables","02.cpp\u002F05.data-types-variables",{"title":1734,"path":1735,"stem":1736},"Ввід даних з клавіатури","\u002Fcpp\u002Fdata-input","02.cpp\u002F06.data-input",{"title":1738,"path":1739,"stem":1740},"Оператори, перетворення типів та логічні операції","\u002Fcpp\u002Foperators-type-conversion","02.cpp\u002F07.operators-type-conversion",{"title":1742,"path":1743,"stem":1744},"Цикли","\u002Fcpp\u002Floops","02.cpp\u002F08.loops",{"title":32,"path":1746,"stem":1747},"\u002Fcpp\u002Farrays","02.cpp\u002F09.arrays",{"title":1749,"path":1750,"stem":1751},"Алгоритми сортування та аналіз складності","\u002Fcpp\u002Fsorting","02.cpp\u002F10.sorting",{"title":1753,"path":1754,"stem":1755},"Алгоритми пошуку","\u002Fcpp\u002Fsearching","02.cpp\u002F11.searching",{"title":1757,"path":1758,"stem":1759},"Функції: основи","\u002Fcpp\u002Ffunctions-basics","02.cpp\u002F12.functions-basics",{"title":1761,"path":1762,"stem":1763},"Функції: прототипи, область видимості та додаткові можливості","\u002Fcpp\u002Ffunctions-scope","02.cpp\u002F13.functions-scope",{"title":1765,"path":1766,"stem":1767},"Функції: перевантаження та шаблони","\u002Fcpp\u002Ffunctions-overloading-templates","02.cpp\u002F14.functions-overloading-templates",{"title":1769,"path":1770,"stem":1771},"Вказівники: основи","\u002Fcpp\u002Fpointers-basics","02.cpp\u002F15.pointers-basics",{"title":1773,"path":1774,"stem":1775},"Посилання (References)","\u002Fcpp\u002Freferences","02.cpp\u002F16.references",{"title":1777,"path":1778,"stem":1779},"Вказівники, const і масиви","\u002Fcpp\u002Fpointers-const-arrays","02.cpp\u002F17.pointers-const-arrays",{"title":1781,"path":1782,"stem":1783},"Адресна арифметика","\u002Fcpp\u002Fpointer-arithmetic","02.cpp\u002F18.pointer-arithmetic",{"title":1785,"path":1786,"stem":1787},"Динамічна пам'ять","\u002Fcpp\u002Fdynamic-memory","02.cpp\u002F19.dynamic-memory",{"title":1789,"path":1790,"stem":1791},"Вказівники типу void","\u002Fcpp\u002Fvoid-pointers","02.cpp\u002F20.void-pointers",{"title":1793,"path":1794,"stem":1795},"Вказівники на вказівники","\u002Fcpp\u002Fpointers-to-pointers","02.cpp\u002F21.pointers-to-pointers",{"title":1797,"path":1798,"stem":1799},"Оператор доступу до членів через вказівник (->)","\u002Fcpp\u002Fmember-access-operator","02.cpp\u002F22.member-access-operator",{"title":1801,"path":1802,"stem":1803},"Цикл for-each (Range-based for)","\u002Fcpp\u002Fforeach-loop","02.cpp\u002F23.foreach-loop",{"title":1805,"path":1806,"stem":1807},"Вказівники на функції","\u002Fcpp\u002Ffunction-pointers","02.cpp\u002F24.function-pointers",{"title":1809,"path":1810,"stem":1811},"Лямбда-вирази","\u002Fcpp\u002Flambdas","02.cpp\u002F25.lambdas",{"title":1813,"path":1814,"stem":1815},"Лямбда-захоплення","\u002Fcpp\u002Flambda-captures","02.cpp\u002F26.lambda-captures",{"title":1817,"path":1818,"stem":1819},"Еліпсис","\u002Fcpp\u002Fellipsis","02.cpp\u002F27.ellipsis",{"title":1821,"path":1822,"stem":1823},"Безпечні альтернативи еліпсису","\u002Fcpp\u002F27a.ellipsis","02.cpp\u002F27a.ellipsis",{"title":1825,"path":1826,"stem":1827},"Аргументи командного рядка","\u002Fcpp\u002Fcommand-line-arguments","02.cpp\u002F28.command-line-arguments",{"title":1829,"path":1830,"stem":1831},"Перерахування (enum)","\u002Fcpp\u002Fenum","02.cpp\u002F29.enum",{"title":1833,"path":1834,"stem":1835},"Класи-перерахування (enum class)","\u002Fcpp\u002Fenum-class","02.cpp\u002F30.enum-class",{"title":1837,"path":1838,"stem":1839},"Псевдоніми типів (typedef і using)","\u002Fcpp\u002Ftype-aliases","02.cpp\u002F31.type-aliases",{"title":1841,"path":1842,"stem":1843},"Системи числення та двійкова арифметика","\u002Fcpp\u002Fnumber-systems","02.cpp\u002F32.number-systems",{"title":1845,"path":1846,"stem":1847},"Структури (struct): агрегування даних","\u002Fcpp\u002Fstruct","02.cpp\u002F33.struct",{"title":1849,"path":1850,"stem":1851},"Структури у функціях","\u002Fcpp\u002Fstruct-functions","02.cpp\u002F34.struct-functions",{"title":1853,"path":1854,"stem":1855},"Масиви структур і вкладені структури","\u002Fcpp\u002Fstruct-arrays","02.cpp\u002F35.struct-arrays",{"title":1857,"path":1858,"stem":1859},"Патерни struct та межі застосування","\u002Fcpp\u002Fstruct-patterns","02.cpp\u002F36.struct-patterns",{"title":1861,"path":1862,"stem":1863},"Символи та таблиця ASCII","\u002Fcpp\u002Fascii-characters","02.cpp\u002F37.ascii-characters",{"title":1865,"path":1866,"stem":1867},"Unicode та кодування UTF","\u002Fcpp\u002Funicode-utf","02.cpp\u002F38.unicode-utf",{"title":1869,"path":1870,"stem":1871},"C-style рядки","\u002Fcpp\u002Fc-strings","02.cpp\u002F39.c-strings",{"title":1873,"path":1874,"stem":1875},"Вступ до std::string","\u002Fcpp\u002Fstd-string-intro","02.cpp\u002F40.std-string-intro",{"title":1877,"path":1878,"stem":1879},"Довжина, ємність та доступ до символів std::string","\u002Fcpp\u002Fstd-string-capacity-access","02.cpp\u002F41.std-string-capacity-access",{"title":1881,"path":1882,"stem":1883},"Модифікація std::string: присвоювання, додавання, вставка, видалення та заміна","\u002Fcpp\u002Fstd-string-modification","02.cpp\u002F42.std-string-modification",{"title":1885,"path":1886,"stem":1887},"Пошук у std::string: find, npos та практичні патерни","\u002Fcpp\u002Fstd-string-search","02.cpp\u002F43.std-string-search",{"title":1889,"path":1890,"stem":1891},"std::string_view: невласницький погляд на рядок без копіювання","\u002Fcpp\u002Fstd-string-view","02.cpp\u002F44.std-string-view",{"title":1893,"path":1894,"stem":1895},"Об'єднання (union): один блок пам'яті, кілька інтерпретацій","\u002Fcpp\u002Funion","02.cpp\u002F45.union",{"title":1897,"path":1898,"stem":1899},"Організація коду: файли, препроцесор, простори імен","\u002Fcpp\u002Fmultifile-programs","02.cpp\u002F46.multifile-programs",{"title":1901,"path":1902,"stem":1903},"Робота з файлами: C-стиль (stdio.h)","\u002Fcpp\u002Fc-style-files","02.cpp\u002F47.c-style-files",{"title":1905,"path":1906,"stem":1907},"Робота з файлами: C++-стиль (fstream)","\u002Fcpp\u002Fcpp-style-files","02.cpp\u002F48.cpp-style-files",{"title":1909,"path":1910,"stem":1911},"Введення в ООП: від процедур до об'єктів","\u002Fcpp\u002Foop-intro","02.cpp\u002F49.oop-intro",{"title":1913,"path":1914,"stem":1915},"Класи, об'єкти та методи","\u002Fcpp\u002Fclasses-objects-methods","02.cpp\u002F50.classes-objects-methods",{"title":1917,"path":1918,"stem":1919},"Специфікатори доступу та інкапсуляція","\u002Fcpp\u002Faccess-specifiers-encapsulation","02.cpp\u002F51.access-specifiers-encapsulation",{"title":1921,"path":1922,"stem":1923},"Конструктори: гарантована ініціалізація об'єктів","\u002Fcpp\u002Fconstructors","02.cpp\u002F52.constructors",{"title":1925,"path":1926,"stem":1927},"Списки ініціалізації та делегуючі конструктори","\u002Fcpp\u002Finit-lists-delegating","02.cpp\u002F53.init-lists-delegating",{"title":1929,"path":1930,"stem":1931},"Деструктори та ідіома RAII","\u002Fcpp\u002Fdestructors-raii","02.cpp\u002F54.destructors-raii",{"title":1933,"path":1934,"stem":1935},"Прихований покажчик this","\u002Fcpp\u002Fthis-pointer","02.cpp\u002F55.this-pointer",{"title":1937,"path":1938,"stem":1939},"Розподіл класу на заголовковий файл та файл реалізації","\u002Fcpp\u002Fclass-header-source","02.cpp\u002F56.class-header-source",{"title":1941,"path":1942,"stem":1943},"Класи та const","\u002Fcpp\u002Fconst-classes","02.cpp\u002F57.const-classes",{"title":1945,"path":1946,"stem":1947},"Статичні члени класу","\u002Fcpp\u002Fstatic-members","02.cpp\u002F58.static-members",{"title":1949,"path":1950,"stem":1951},"Конструктор копіювання та запобігання копіюванню","\u002Fcpp\u002Fcopy-constructor","02.cpp\u002F59.copy-constructor",{"title":1953,"path":1954,"stem":1955},"Дружні функції та класи","\u002Fcpp\u002Ffriend-functions-classes","02.cpp\u002F60.friend-functions-classes",{"title":1957,"path":1958,"stem":1959},"Анонімні об'єкти та вкладені типи","\u002Fcpp\u002Fanonymous-objects-nested-types","02.cpp\u002F61.anonymous-objects-nested-types",{"title":1961,"path":1962,"stem":1963},"Вимірювання часу виконання: RAII-таймер","\u002Fcpp\u002Ftiming-raii-timer","02.cpp\u002F62.timing-raii-timer",{"title":1965,"path":1966,"stem":1967},"Перевантаження операторів: основи та арифметика","\u002Fcpp\u002Foperator-overloading-basics","02.cpp\u002F63.operator-overloading-basics",{"title":1969,"path":1970,"stem":1971},"Перевантаження операторів: методи класу та спеціальні оператори","\u002Fcpp\u002Foperator-overloading-methods","02.cpp\u002F64.operator-overloading-methods",{"title":1973,"path":1974,"stem":1975},"Приведення типів, explicit та delete","\u002Fcpp\u002Ftypecast-explicit-delete","02.cpp\u002F65.typecast-explicit-delete",{"title":1977,"path":1978,"stem":1979},"Глибоке копіювання та оператор присвоювання","\u002Fcpp\u002Fdeep-copy-assignment","02.cpp\u002F66.deep-copy-assignment",{"title":1981,"path":1982,"stem":1983},"Зв'язки між об'єктами: композиція та агрегація","\u002Fcpp\u002Fcomposition-aggregation","02.cpp\u002F67.composition-aggregation",{"title":1985,"path":1986,"stem":1987},"Асоціація та залежність між об","\u002Fcpp\u002Fassociation-dependency","02.cpp\u002F68.association-dependency",{"title":1989,"path":1990,"stem":1991},"Контейнерні класи та `std::initializer_list`","\u002Fcpp\u002Fcontainer-classes-initializer-list","02.cpp\u002F69.container-classes-initializer-list",{"title":1993,"path":1994,"stem":1995},"Вступ до наслідування: базові поняття та відношення «is-a»","\u002Fcpp\u002Finheritance-intro","02.cpp\u002F70.inheritance-intro",{"title":1997,"path":1998,"stem":1999},"Конструктори, деструктори та порядок ініціалізації в ієрархіях","\u002Fcpp\u002Finheritance-constructors-destructors","02.cpp\u002F71.inheritance-constructors-destructors",{"title":2001,"path":2002,"stem":2003},"Специфікатор `protected` та види наслідування","\u002Fcpp\u002Finheritance-access-specifiers","02.cpp\u002F72.inheritance-access-specifiers",{"title":2005,"path":2006,"stem":2007},"Перевизначення методів та приховування імен","\u002Fcpp\u002Fmethod-overriding-hiding","02.cpp\u002F73.method-overriding-hiding",{"title":2009,"path":2010,"stem":2011},"Множинне наслідування та проблема ромба","\u002Fcpp\u002Fmultiple-inheritance-diamond","02.cpp\u002F74.multiple-inheritance-diamond",{"title":2013,"path":2014,"stem":2015},"Вказівники на базовий клас та віртуальні функції","\u002Fcpp\u002Fvirtual-functions-polymorphism","02.cpp\u002F75.virtual-functions-polymorphism",{"title":2017,"path":2018,"stem":2019},"Специфікатори override, final та віртуальні деструктори","\u002Fcpp\u002Foverride-final-virtual-destructors","02.cpp\u002F76.override-final-virtual-destructors",{"title":2021,"path":2022,"stem":2023},"Внутрішні механізми поліморфізму: vtable, vptr та зв'язування","\u002Fcpp\u002Fvirtual-table-binding","02.cpp\u002F77.virtual-table-binding",{"title":2025,"path":2026,"stem":2027},"Абстрактні класи та чисті віртуальні функції","\u002Fcpp\u002Fabstract-classes-interfaces","02.cpp\u002F78.abstract-classes-interfaces",{"title":2029,"path":2030,"stem":2031},"Зрізка об'єктів, віртуальне наслідування та поліморфний вивід","\u002Fcpp\u002Fslicing-virtual-base-io","02.cpp\u002F79.slicing-virtual-base-io",{"title":2033,"path":2034,"stem":2035},"Динамічне приведення типів (`dynamic_cast`) та RTTI","\u002Fcpp\u002Fdynamic-cast-rtti","02.cpp\u002F80.dynamic-cast-rtti",{"title":2037,"path":2038,"stem":2039},"Шаблони функцій: узагальнене програмування","\u002Fcpp\u002Ffunction-templates","02.cpp\u002F81.function-templates",{"title":2041,"path":2042,"stem":2043},"Шаблони класів та NTTP-параметри","\u002Fcpp\u002Fclass-templates-nttp","02.cpp\u002F82.class-templates-nttp",{"title":2045,"path":2046,"stem":2047},"Повна (явна) спеціалізація шаблонів","\u002Fcpp\u002Ffull-template-specialization","02.cpp\u002F83.full-template-specialization",{"title":2049,"path":2050,"stem":2051},"Часткова спеціалізація шаблонів класів","\u002Fcpp\u002Fpartial-template-specialization","02.cpp\u002F84.partial-template-specialization",{"title":2053,"path":2054,"stem":2055},"Механізм винятків: try, catch, throw та розкручування стеку","\u002Fcpp\u002Fexceptions-basics","02.cpp\u002F85.exceptions-basics",{"title":2057,"path":2058,"stem":2059},"Ієрархія std::exception та власні класи винятків","\u002Fcpp\u002Fexception-hierarchy-custom","02.cpp\u002F86.exception-hierarchy-custom",{"title":2061,"path":2062,"stem":2063},"Повторне генерування винятків та функціональний try-блок","\u002Fcpp\u002Frethrow-function-try","02.cpp\u002F87.rethrow-function-try",{"title":2065,"path":2066,"stem":2067},"Безпека винятків, noexcept та деструктори","\u002Fcpp\u002Fsafety-noexcept","02.cpp\u002F88.safety-noexcept",{"title":2069,"path":2070,"stem":2071},"Стаття 89 — Власні структури даних: стек та черга","\u002Fcpp\u002Fcustom-stack-queue","02.cpp\u002F89.custom-stack-queue",{"title":2073,"path":2074,"stem":2075},"Стаття 90 — Власні структури даних: однозв'язаний список","\u002Fcpp\u002Flinked-list","02.cpp\u002F90.linked-list",{"title":2077,"path":2078,"stem":2079},"Стаття 91 — Власні структури даних: бінарне дерево пошуку","\u002Fcpp\u002Fbinary-search-tree","02.cpp\u002F91.binary-search-tree",{"title":2081,"path":2082,"stem":2083},"Стаття 92 — Власні структури даних: хеш-таблиця","\u002Fcpp\u002Fhash-table","02.cpp\u002F92.hash-table",{"title":2085,"icon":2086,"path":2087,"stem":2088,"children":2089,"page":59},"JavaScript","i-devicon-javascript","\u002Fjavascript","03.javascript",[2090,2116,2170,2192,2496,2534,2552],{"title":2091,"icon":2092,"path":2093,"stem":2094,"children":2095,"page":59},"Events","i-lucide-mouse-pointer-click","\u002Fjavascript\u002Fevents","03.javascript\u002F01.events",[2096,2100,2104,2108,2112],{"title":2097,"path":2098,"stem":2099},"Вступ до подій браузера","\u002Fjavascript\u002Fevents\u002Fintro","03.javascript\u002F01.events\u002F01.intro",{"title":2101,"path":2102,"stem":2103},"Бульбашковий механізм (Bubbling) та занурення (Capturing)","\u002Fjavascript\u002Fevents\u002Fbubbling-capturing","03.javascript\u002F01.events\u002F02.bubbling-capturing",{"title":2105,"path":2106,"stem":2107},"Делегування подій (Event Delegation)","\u002Fjavascript\u002Fevents\u002Fdelegate-events","03.javascript\u002F01.events\u002F03.delegate-events",{"title":2109,"path":2110,"stem":2111},"Типові дії браузера та preventDefault()","\u002Fjavascript\u002Fevents\u002Fprevent-default","03.javascript\u002F01.events\u002F04.prevent-default",{"title":2113,"path":2114,"stem":2115},"Запуск користувацьких подій (Custom Events)","\u002Fjavascript\u002Fevents\u002Fcustom-events","03.javascript\u002F01.events\u002F05.custom-events",{"title":2117,"icon":2118,"path":2119,"stem":2120,"children":2121,"page":59},"Network","i-lucide-globe","\u002Fjavascript\u002Fnetwork","03.javascript\u002F02.network",[2122,2126,2130,2134,2138,2142,2146,2150,2154,2158,2162,2166],{"title":2123,"path":2124,"stem":2125},"Fetch API - Сучасний підхід до HTTP-запитів","\u002Fjavascript\u002Fnetwork\u002F01-fetch-api","03.javascript\u002F02.network\u002F01-fetch-api",{"title":2127,"path":2128,"stem":2129},"FormData - Робота з формами та файлами","\u002Fjavascript\u002Fnetwork\u002F02-formdata","03.javascript\u002F02.network\u002F02-formdata",{"title":2131,"path":2132,"stem":2133},"Відстеження прогресу завантаження","\u002Fjavascript\u002Fnetwork\u002F03-download-progress","03.javascript\u002F02.network\u002F03-download-progress",{"title":2135,"path":2136,"stem":2137},"Переривання fetch-запитів","\u002Fjavascript\u002Fnetwork\u002F04-abort-requests","03.javascript\u002F02.network\u002F04-abort-requests",{"title":2139,"path":2140,"stem":2141},"CORS - Запити між різними джерелами","\u002Fjavascript\u002Fnetwork\u002F05-cors","03.javascript\u002F02.network\u002F05-cors",{"title":2143,"path":2144,"stem":2145},"Fetch API - Повний довідник опцій","\u002Fjavascript\u002Fnetwork\u002F06-fetch-options","03.javascript\u002F02.network\u002F06-fetch-options",{"title":2147,"path":2148,"stem":2149},"URL Objects - Робота з посиланнями","\u002Fjavascript\u002Fnetwork\u002F07-url-objects","03.javascript\u002F02.network\u002F07-url-objects",{"title":2151,"path":2152,"stem":2153},"XMLHttpRequest - AJAX та низькорівневі запити","\u002Fjavascript\u002Fnetwork\u002F08-xmlhttprequest","03.javascript\u002F02.network\u002F08-xmlhttprequest",{"title":2155,"path":2156,"stem":2157},"Відновлюване завантаження файлів","\u002Fjavascript\u002Fnetwork\u002F09-resumable-upload","03.javascript\u002F02.network\u002F09-resumable-upload",{"title":2159,"path":2160,"stem":2161},"Cookies, document.cookie та світ після \"Cookiepocalypse\"","\u002Fjavascript\u002Fnetwork\u002F10-cookies","03.javascript\u002F02.network\u002F10-cookies",{"title":2163,"path":2164,"stem":2165},"js-cookie: Керування Cookies без Болю","\u002Fjavascript\u002Fnetwork\u002F11-js-cookie","03.javascript\u002F02.network\u002F11-js-cookie",{"title":2167,"path":2168,"stem":2169},"Axios: Потужний HTTP-клієнт для JavaScript","\u002Fjavascript\u002Fnetwork\u002F12-axios","03.javascript\u002F02.network\u002F12-axios",{"title":2171,"icon":2172,"path":2173,"stem":2174,"children":2175,"page":59},"Bom","i-lucide-monitor","\u002Fjavascript\u002Fbom","03.javascript\u002F03.bom",[2176,2180,2184,2188],{"title":2177,"path":2178,"stem":2179},"LocalStorage, SessionStorage та patterns збереження даних","\u002Fjavascript\u002Fbom\u002F01-localstorage","03.javascript\u002F03.bom\u002F01-localstorage",{"title":2181,"path":2182,"stem":2183},"Location Object - Керування адресою сторінки","\u002Fjavascript\u002Fbom\u002F02-location-object","03.javascript\u002F03.bom\u002F02-location-object",{"title":2185,"path":2186,"stem":2187},"History API - Керування історією браузера","\u002Fjavascript\u002Fbom\u002F03-history-api","03.javascript\u002F03.bom\u002F03-history-api",{"title":2189,"path":2190,"stem":2191},"Navigator Object - Ідентифікація та Можливості Пристрою","\u002Fjavascript\u002Fbom\u002F04-navigator-object","03.javascript\u002F03.bom\u002F04-navigator-object",{"title":2193,"icon":2194,"path":2195,"stem":2196,"children":2197},"React","i-devicon-react","\u002Fjavascript\u002Freact","03.javascript\u002F04.react\u002Findex",[2198,2199,2203,2207,2211,2215,2278,2313,2465],{"title":2193,"path":2195,"stem":2196},{"title":2200,"path":2201,"stem":2202},"Робота з Формами в React","\u002Fjavascript\u002Freact\u002Freact-forms","03.javascript\u002F04.react\u002F01.react-forms",{"title":2204,"path":2205,"stem":2206},"React Hook Form: Професійна Робота з Формами","\u002Fjavascript\u002Freact\u002Freact-hook-form","03.javascript\u002F04.react\u002F02.react-hook-form",{"title":2208,"path":2209,"stem":2210},"React Hook Form: Глибоке Розуміння Архітектури та Оптимізації","\u002Fjavascript\u002Freact\u002Freact-hook-form-new","03.javascript\u002F04.react\u002F02.react-hook-form-new",{"title":2212,"path":2213,"stem":2214},"Axios та React: Професійна Архітектура Запитів","\u002Fjavascript\u002Freact\u002Fdata-fetching-axios","03.javascript\u002F04.react\u002F03.data-fetching-axios",{"title":2216,"icon":132,"path":2217,"stem":2218,"children":2219},"Tanstack Query","\u002Fjavascript\u002Freact\u002Ftanstack-query","03.javascript\u002F04.react\u002F04.tanstack-query\u002Findex",[2220,2222,2226,2230,2234,2238,2242,2246,2250,2254,2258,2262,2266,2270,2274],{"title":2221,"path":2217,"stem":2218},"TanStack Query: Майстерність Керування Станом Сервера",{"title":2223,"path":2224,"stem":2225},"Парадигма Server State: Чому useEffect недостатньо","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fserver-state-paradigm","03.javascript\u002F04.react\u002F04.tanstack-query\u002F01.server-state-paradigm",{"title":2227,"path":2228,"stem":2229},"Встановлення та Налаштування: Фундамент","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Finstallation-and-devtools","03.javascript\u002F04.react\u002F04.tanstack-query\u002F02.installation-and-devtools",{"title":2231,"path":2232,"stem":2233},"Основи Запитів та Магія Ключів","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fquery-basics-and-keys","03.javascript\u002F04.react\u002F04.tanstack-query\u002F03.query-basics-and-keys",{"title":2235,"path":2236,"stem":2237},"Синхронізація Даних: Життєвий Цикл Запиту","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fdata-synchronization","03.javascript\u002F04.react\u002F04.tanstack-query\u002F04.data-synchronization",{"title":2239,"path":2240,"stem":2241},"Мутації та Інвалідація: Зміна Даних","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fmutations-and-invalidation","03.javascript\u002F04.react\u002F04.tanstack-query\u002F05.mutations-and-invalidation",{"title":2243,"path":2244,"stem":2245},"Оптимістичні Оновлення: Швидше за Світло","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Foptimistic-updates","03.javascript\u002F04.react\u002F04.tanstack-query\u002F06.optimistic-updates",{"title":2247,"path":2248,"stem":2249},"Пагінація та Infinite Scroll","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fpagination-and-load-more","03.javascript\u002F04.react\u002F04.tanstack-query\u002F07.pagination-and-load-more",{"title":2251,"path":2252,"stem":2253},"Просунуті Патерни та Оптимізація","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fadvanced-patterns","03.javascript\u002F04.react\u002F04.tanstack-query\u002F08.advanced-patterns",{"title":2255,"path":2256,"stem":2257},"Архітектура та Best Practices","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Farchitecture-and-best-practices","03.javascript\u002F04.react\u002F04.tanstack-query\u002F09.architecture-and-best-practices",{"title":2259,"path":2260,"stem":2261},"Server-Side Rendering (SSR) та Гідратація","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fserver-side-rendering","03.javascript\u002F04.react\u002F04.tanstack-query\u002F10.server-side-rendering",{"title":2263,"path":2264,"stem":2265},"Стратегії Тестування","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Ftesting-strategies","03.javascript\u002F04.react\u002F04.tanstack-query\u002F11.testing-strategies",{"title":2267,"path":2268,"stem":2269},"Аутентифікація та Обробка Помилок","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fauthentication-and-errors","03.javascript\u002F04.react\u002F04.tanstack-query\u002F12.authentication-and-errors",{"title":2271,"path":2272,"stem":2273},"React Suspense та Майбутнє","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Freact-suspense","03.javascript\u002F04.react\u002F04.tanstack-query\u002F13.react-suspense",{"title":2275,"path":2276,"stem":2277},"Глибоке Занурення в Продуктивність","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fperformance-deep-dive","03.javascript\u002F04.react\u002F04.tanstack-query\u002F14.performance-deep-dive",{"title":2279,"icon":2194,"path":2280,"stem":2281,"children":2282},"React Router","\u002Fjavascript\u002Freact\u002Freact-router","03.javascript\u002F04.react\u002F05.react-router\u002Findex",[2283,2285,2289,2293,2297,2301,2305,2309],{"title":2284,"path":2280,"stem":2281},"React Router: Навігаційна система сучасного вебу",{"title":2286,"path":2287,"stem":2288},"Налаштування та Базовий Роутинг","\u002Fjavascript\u002Freact\u002Freact-router\u002Fsetup-and-basic-routing","03.javascript\u002F04.react\u002F05.react-router\u002F01.setup-and-basic-routing",{"title":2290,"path":2291,"stem":2292},"Динамічна Навігація","\u002Fjavascript\u002Freact\u002Freact-router\u002Fnavigation-and-links","03.javascript\u002F04.react\u002F05.react-router\u002F02.navigation-and-links",{"title":2294,"path":2295,"stem":2296},"Вкладені Маршрути та Макети","\u002Fjavascript\u002Freact\u002Freact-router\u002Fnested-routes-and-layouts","03.javascript\u002F04.react\u002F05.react-router\u002F03.nested-routes-and-layouts",{"title":2298,"path":2299,"stem":2300},"Динамічні Маршрути та Параметри","\u002Fjavascript\u002Freact\u002Freact-router\u002Fdynamic-routing","03.javascript\u002F04.react\u002F05.react-router\u002F04.dynamic-routing",{"title":2302,"path":2303,"stem":2304},"Data APIs: Loaders та Actions","\u002Fjavascript\u002Freact\u002Freact-router\u002Fdata-loading","03.javascript\u002F04.react\u002F05.react-router\u002F05.data-loading",{"title":2306,"path":2307,"stem":2308},"Просунуті Патерни","\u002Fjavascript\u002Freact\u002Freact-router\u002Fadvanced-patterns","03.javascript\u002F04.react\u002F05.react-router\u002F06.advanced-patterns",{"title":2310,"path":2311,"stem":2312},"Legacy Routing: Компонентний підхід","\u002Fjavascript\u002Freact\u002Freact-router\u002Flegacy-routing","03.javascript\u002F04.react\u002F05.react-router\u002F07.legacy-routing",{"title":2314,"icon":132,"path":2315,"stem":2316,"children":2317},"Redux","\u002Fjavascript\u002Freact\u002Fredux","03.javascript\u002F04.react\u002F06.redux\u002Findex",[2318,2320,2336,2365,2374,2395,2411,2440],{"title":2319,"path":2315,"stem":2316},"Redux: Еволюція управління станом",{"title":14,"icon":15,"path":2321,"stem":2322,"children":2323,"page":59},"\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals",[2324,2328,2332],{"title":2325,"path":2326,"stem":2327},"Вступ до State Management","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fintro-state-management","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F01.intro-state-management",{"title":2329,"path":2330,"stem":2331},"Філософія Redux та Три Принципи","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fredux-philosophy","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F02.redux-philosophy",{"title":2333,"path":2334,"stem":2335},"Чисті функції та Іммутабельність","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fpure-functions-immutability","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F03.pure-functions-immutability",{"title":2337,"icon":132,"path":2338,"stem":2339,"children":2340,"page":59},"Classic Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux",[2341,2345,2349,2353,2357,2361],{"title":2342,"path":2343,"stem":2344},"Створення Store (Classic Redux)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fstore-setup","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F01.store-setup",{"title":2346,"path":2347,"stem":2348},"Actions, Constants та Action Creators","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Factions-constants","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F02.actions-constants",{"title":2350,"path":2351,"stem":2352},"Логіка Reducers","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Freducers","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F03.reducers",{"title":2354,"path":2355,"stem":2356},"Комбінування Reducers (Root Reducer)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fdata-flow","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F04.data-flow",{"title":2358,"path":2359,"stem":2360},"Підключення до React (React-Redux)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Freact-redux-connection","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F05.react-redux-connection",{"title":2362,"path":2363,"stem":2364},"Middleware та Асинхронність (Redux Thunk)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fmiddleware-thunk","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F06.middleware-thunk",{"title":2366,"icon":132,"path":2367,"stem":2368,"children":2369,"page":59},"Transition To Rtk","\u002Fjavascript\u002Freact\u002Fredux\u002Ftransition-to-rtk","03.javascript\u002F04.react\u002F06.redux\u002F03.transition-to-rtk",[2370],{"title":2371,"path":2372,"stem":2373},"Проблеми класичного Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Ftransition-to-rtk\u002Fproblems-with-classic","03.javascript\u002F04.react\u002F06.redux\u002F03.transition-to-rtk\u002F01.problems-with-classic",{"title":2375,"icon":132,"path":2376,"stem":2377,"children":2378,"page":59},"Redux Toolkit","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit",[2379,2383,2387,2391],{"title":2380,"path":2381,"stem":2382},"Налаштування Store з configureStore","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fconfigure-store","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F01.configure-store",{"title":2384,"path":2385,"stem":2386},"createSlice: Революція в Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fcreate-slice","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F02.create-slice",{"title":2388,"path":2389,"stem":2390},"Асинхронність з createAsyncThunk","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fasync-thunks","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F03.async-thunks",{"title":2392,"path":2393,"stem":2394},"04. Entity Adapter: Керування нормалізованим станом","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fentity-adapter","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F04.entity-adapter",{"title":2396,"icon":92,"path":2397,"stem":2398,"children":2399,"page":59},"Advanced","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced",[2400,2404,2408],{"title":2401,"path":2402,"stem":2403},"Мемоізація та Селектори: Повний Гайд по Reselect","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Fselectors-reselect","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F01.selectors-reselect",{"title":2405,"path":2406,"stem":2407},"RTK Query: Архітектура Серверного Кешу","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Frtk-query-intro","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F02.rtk-query-intro",{"title":2255,"path":2409,"stem":2410},"\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Farchitecture-best-practices","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F03.architecture-best-practices",{"title":2412,"icon":132,"path":2413,"stem":2414,"children":2415,"page":59},"Project Kanban","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban",[2416,2420,2424,2428,2432,2436],{"title":2417,"path":2418,"stem":2419},"Проєкт: Kanban Board (Trello Clone)","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fproject-overview","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F01.project-overview",{"title":2421,"path":2422,"stem":2423},"Налаштування та Типізація","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fsetup-and-types","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F02.setup-and-types",{"title":2425,"path":2426,"stem":2427},"Board Slice: Серце Дошки","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fboard-slice","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F03.board-slice",{"title":2429,"path":2430,"stem":2431},"Логіка Drag & Drop","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fdrag-and-drop-logic","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F04.drag-and-drop-logic",{"title":2433,"path":2434,"stem":2435},"Інтеграція з RTK Query","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Frtk-query-integration","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F05.rtk-query-integration",{"title":2437,"path":2438,"stem":2439},"Optimistic Updates","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Foptimistic-updates","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F06.optimistic-updates",{"title":2441,"icon":132,"path":2442,"stem":2443,"children":2444,"page":59},"Testing","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting","03.javascript\u002F04.react\u002F06.redux\u002F07.testing",[2445,2449,2453,2457,2461],{"title":2446,"path":2447,"stem":2448},"Тестування Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Fintro-testing","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F01.intro-testing",{"title":2450,"path":2451,"stem":2452},"Тестування Reducers","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-reducers","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F02.testing-reducers",{"title":2454,"path":2455,"stem":2456},"Тестування Селекторів","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-selectors","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F03.testing-selectors",{"title":2458,"path":2459,"stem":2460},"Тестування Компонентів (Integration)","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-components","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F04.testing-components",{"title":2462,"path":2463,"stem":2464},"Тестування Async Thunks","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-thunks","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F05.testing-thunks",{"title":2466,"icon":132,"path":2467,"stem":2468,"children":2469},"Ui Libraries","\u002Fjavascript\u002Freact\u002Fui-libraries","03.javascript\u002F04.react\u002F07.ui-libraries\u002Findex",[2470,2472,2476,2480,2484,2488,2492],{"title":2471,"path":2467,"stem":2468},"UI Бібліотеки в React",{"title":2473,"path":2474,"stem":2475},"Вступ до UI Бібліотек: Навіщо Винаходити Велосипед Двічі?","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fintroduction-to-ui-libraries","03.javascript\u002F04.react\u002F07.ui-libraries\u002F01.introduction-to-ui-libraries",{"title":2477,"path":2478,"stem":2479},"Філософія shadcn\u002Fui: \"Not a Component Library\"","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-philosophy","03.javascript\u002F04.react\u002F07.ui-libraries\u002F02.shadcn-philosophy",{"title":2481,"path":2482,"stem":2483},"Установка та Налаштування shadcn\u002Fui","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-installation","03.javascript\u002F04.react\u002F07.ui-libraries\u002F03.shadcn-installation",{"title":2485,"path":2486,"stem":2487},"Базові Компоненти shadcn\u002Fui: Фундамент Інтерфейсу","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-basics","03.javascript\u002F04.react\u002F07.ui-libraries\u002F04.shadcn-components-basics",{"title":2489,"path":2490,"stem":2491},"Компоненти Форм: Побудова Інтерактивних Form","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-forms","03.javascript\u002F04.react\u002F07.ui-libraries\u002F05.shadcn-components-forms",{"title":2493,"path":2494,"stem":2495},"Складні Компоненти: Dialog, Dropdown, Table та Command","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-advanced","03.javascript\u002F04.react\u002F07.ui-libraries\u002F06.shadcn-components-advanced",{"title":2497,"icon":2498,"path":2499,"stem":2500,"children":2501,"page":59},"TypeScript","i-devicon-typescript","\u002Fjavascript\u002Ftypescript","03.javascript\u002F05.typescript",[2502,2506,2510,2514,2518,2522,2526,2530],{"title":2503,"path":2504,"stem":2505},"TypeScript: Броня для вашого коду","\u002Fjavascript\u002Ftypescript\u002Fintro-and-basic-types","03.javascript\u002F05.typescript\u002F01.intro-and-basic-types",{"title":2507,"path":2508,"stem":2509},"Майстерність Моделювання Даних: Інтерфейси та Просунуті Типи","\u002Fjavascript\u002Ftypescript\u002Finterfaces-and-advanced-types","03.javascript\u002F05.typescript\u002F02.interfaces-and-advanced-types",{"title":2511,"path":2512,"stem":2513},"Алхімія Типів: Generics та Utility Types","\u002Fjavascript\u002Ftypescript\u002Fgenerics-and-utilities","03.javascript\u002F05.typescript\u002F03.generics-and-utilities",{"title":2515,"path":2516,"stem":2517},"Архітектура та Шаблони: Класи в TypeScript","\u002Fjavascript\u002Ftypescript\u002Fclasses-and-oop","03.javascript\u002F05.typescript\u002F04.classes-and-oop",{"title":2519,"path":2520,"stem":2521},"Продакшн та Екосистема: Advanced Config & Workflow","\u002Fjavascript\u002Ftypescript\u002Fadvanced-patterns-and-config","03.javascript\u002F05.typescript\u002F05.advanced-patterns-and-config",{"title":2523,"path":2524,"stem":2525},"TypeScript у світі React","\u002Fjavascript\u002Ftypescript\u002Freact-basics","03.javascript\u002F05.typescript\u002F06.react-basics",{"title":2527,"path":2528,"stem":2529},"React + TypeScript: Продвинуті патерни","\u002Fjavascript\u002Ftypescript\u002Freact-advanced","03.javascript\u002F05.typescript\u002F07.react-advanced",{"title":2531,"path":2532,"stem":2533},"React + TypeScript: Екосистема та бібліотеки","\u002Fjavascript\u002Ftypescript\u002Freact-ecosystem","03.javascript\u002F05.typescript\u002F08.react-ecosystem",{"title":2535,"icon":2536,"path":2537,"stem":2538,"children":2539,"page":59},"Асинхронність","i-lucide-refresh-cw","\u002Fjavascript\u002Fasync-programming","03.javascript\u002F06.async-programming",[2540,2544,2548],{"title":2541,"path":2542,"stem":2543},"Event Loop: серце асинхронності в JavaScript","\u002Fjavascript\u002Fasync-programming\u002Fevent-loop-fundamentals","03.javascript\u002F06.async-programming\u002F01.event-loop-fundamentals",{"title":2545,"path":2546,"stem":2547},"Task Queue та Microtask Queue: пріоритети виконання","\u002Fjavascript\u002Fasync-programming\u002Ftask-queues","03.javascript\u002F06.async-programming\u002F02.task-queues",{"title":2549,"path":2550,"stem":2551},"Асинхронні патерни: від Callbacks до Async\u002FAwait","\u002Fjavascript\u002Fasync-programming\u002Fasync-patterns","03.javascript\u002F06.async-programming\u002F03.async-patterns",{"title":2553,"path":2554,"stem":2555},"Atomic Design","\u002Fjavascript\u002Fatomic-design","03.javascript\u002F2.atomic-design",{"title":2557,"icon":2558,"path":2559,"stem":2560,"children":2561,"page":59},"Java","i-devicon-java","\u002Fjava","04.java",[2562,2565,2568,2572,2576,2580,2584],{"title":162,"path":2563,"stem":2564},"\u002Fjava\u002Fdata-mapper-part1","04.java\u002F01.data-mapper-part1",{"title":166,"path":2566,"stem":2567},"\u002Fjava\u002Fdata-mapper-part2","04.java\u002F02.data-mapper-part2",{"title":2569,"path":2570,"stem":2571},"Service Layer: Організація бізнес-логіки","\u002Fjava\u002Fservice-layer","04.java\u002F03.service-layer",{"title":2573,"path":2574,"stem":2575},"Rich Domain Model та State Pattern","\u002Fjava\u002Frich-domain-model","04.java\u002F04.rich-domain-model",{"title":2577,"path":2578,"stem":2579},"Патерни для складної бізнес-логіки","\u002Fjava\u002Fbusiness-logic-patterns","04.java\u002F05.business-logic-patterns",{"title":2581,"path":2582,"stem":2583},"Обробка помилок та валідація","\u002Fjava\u002Ferror-handling-validation","04.java\u002F06.error-handling-validation",{"title":2585,"path":2586,"stem":2587,"children":2588,"page":59},"Проектування баз даних","\u002Fjava\u002Fpr2","04.java\u002Fpr2",[2589,2593,2597,2601,2605,2609,2613,2617,2621,2625,2629,2633,2637,2641,2645,2649,2653,2657,2661,2665,2669,2673,2677,2681,2685,2689,2693,2697,2701,2705,2709,2713,2717,2721,2725,2729,2733],{"title":2590,"path":2591,"stem":2592},"Концептуальне моделювання: Мистецтво розуміння предметної області","\u002Fjava\u002Fpr2\u002Fconceptual-modeling","04.java\u002Fpr2\u002F01.conceptual-modeling",{"title":2594,"path":2595,"stem":2596},"Логічне моделювання: Від бізнес-ідей до структур даних","\u002Fjava\u002Fpr2\u002Flogical-modeling","04.java\u002Fpr2\u002F02.logical-modeling",{"title":2598,"path":2599,"stem":2600},"Нормалізація: Гігієна даних та боротьба з аномаліями","\u002Fjava\u002Fpr2\u002Fnormalization","04.java\u002Fpr2\u002F03.normalization",{"title":2602,"path":2603,"stem":2604},"Фізична схема: Від абстракції до DDL","\u002Fjava\u002Fpr2\u002Fphysical-schema","04.java\u002Fpr2\u002F04.physical-schema",{"title":2606,"path":2607,"stem":2608},"Архітектурна класифікація таблиць","\u002Fjava\u002Fpr2\u002Ftable-classification","04.java\u002Fpr2\u002F05.table-classification",{"title":2610,"path":2611,"stem":2612},"Database Migrations: Версіонування схеми з Flyway","\u002Fjava\u002Fpr2\u002Fdatabase-migrations","04.java\u002Fpr2\u002F06.database-migrations",{"title":2614,"path":2615,"stem":2616},"А що, якби це була не реляційна БД?","\u002Fjava\u002Fpr2\u002Fbeyond-relational","04.java\u002Fpr2\u002F07.beyond-relational",{"title":2618,"path":2619,"stem":2620},"Object-Relational Impedance Mismatch: Два світи, що не хочуть дружити","\u002Fjava\u002Fpr2\u002Fimpedance-mismatch","04.java\u002Fpr2\u002F09.impedance-mismatch",{"title":2622,"path":2623,"stem":2624},"JDBC: Перший контакт із базою даних","\u002Fjava\u002Fpr2\u002Fjdbc-fundamentals","04.java\u002Fpr2\u002F10.jdbc-fundamentals",{"title":2626,"path":2627,"stem":2628},"Якість коду: Spotless, SpotBugs та SonarQube","\u002Fjava\u002Fpr2\u002F10a.code-quality","04.java\u002Fpr2\u002F10a.code-quality",{"title":2630,"path":2631,"stem":2632},"Connection Pool: Патерн Object Pool для JDBC-з'єднань","\u002Fjava\u002Fpr2\u002Fconnection-pool","04.java\u002Fpr2\u002F11.connection-pool",{"title":2634,"path":2635,"stem":2636},"Row Data Gateway: Об'єкт як обгортка рядка таблиці","\u002Fjava\u002Fpr2\u002Frow-data-gateway","04.java\u002Fpr2\u002F12.row-data-gateway",{"title":2638,"path":2639,"stem":2640},"Table Data Gateway: Фасад таблиці як архітектурний відступ","\u002Fjava\u002Fpr2\u002Ftable-data-gateway","04.java\u002Fpr2\u002F13.table-data-gateway",{"title":2642,"path":2643,"stem":2644},"Repository + Data Mapper: Правильна шарова архітектура з JDBC","\u002Fjava\u002Fpr2\u002Frepository-data-mapper","04.java\u002Fpr2\u002F14.repository-data-mapper",{"title":2646,"path":2647,"stem":2648},"Identity Map: Кешування сутностей у рамках сесії","\u002Fjava\u002Fpr2\u002Fidentity-map","04.java\u002Fpr2\u002F15.identity-map",{"title":2650,"path":2651,"stem":2652},"Unit of Work: Відстеження змін і координація JDBC-транзакцій","\u002Fjava\u002Fpr2\u002Funit-of-work","04.java\u002Fpr2\u002F16.unit-of-work",{"title":2654,"path":2655,"stem":2656},"Strategy: Замінювані SQL-стратегії для підтримки різних СУБД","\u002Fjava\u002Fpr2\u002Fstrategy-sql","04.java\u002Fpr2\u002F17.strategy-sql",{"title":2658,"path":2659,"stem":2660},"Proxy: Lazy Loading для One-To-Many колекцій","\u002Fjava\u002Fpr2\u002Fproxy-lazy-loading","04.java\u002Fpr2\u002F18.proxy-lazy-loading",{"title":2662,"path":2663,"stem":2664},"Generic Repository через Java Reflection: анотації та динамічний SQL","\u002Fjava\u002Fpr2\u002Fgeneric-repository-reflection","04.java\u002Fpr2\u002F19.generic-repository-reflection",{"title":2666,"path":2667,"stem":2668},"Specification Pattern: Композиція бізнес-правил для складних запитів","\u002Fjava\u002Fpr2\u002Fspecification-pattern","04.java\u002Fpr2\u002F20.specification-pattern",{"title":2670,"path":2671,"stem":2672},"Розширені можливості Specification Pattern: підзапити, агрегації та гібридний підхід","\u002Fjava\u002Fpr2\u002F20a.advanced-specifications","04.java\u002Fpr2\u002F20a.advanced-specifications",{"title":2674,"path":2675,"stem":2676},"Асинхронність у JDBC: Від блокуючих викликів до CompletableFuture","\u002Fjava\u002Fpr2\u002Fasynchronous-jdbc","04.java\u002Fpr2\u002F21.asynchronous-jdbc",{"title":2678,"path":2679,"stem":2680},"Інтеграційне тестування JDBC-репозиторіїв: Embedded H2 та патерн AAA","\u002Fjava\u002Fpr2\u002Fintegration-testing-h2","04.java\u002Fpr2\u002F22.integration-testing-h2",{"title":2682,"path":2683,"stem":2684},"Testcontainers: Тестування з реальною PostgreSQL у Docker-контейнерах","\u002Fjava\u002Fpr2\u002Fintegration-testing-testcontainers","04.java\u002Fpr2\u002F23.integration-testing-testcontainers",{"title":2686,"path":2687,"stem":2688},"Google Guice: Впровадження залежностей у JavaFX-проєкті","\u002Fjava\u002Fpr2\u002Fdependency-injection-guice","04.java\u002Fpr2\u002F24.dependency-injection-guice",{"title":2690,"path":2691,"stem":2692},"JavaFX: Основи побудови графічних інтерфейсів","\u002Fjava\u002Fpr2\u002Fjavafx-fundamentals","04.java\u002Fpr2\u002F25.javafx-fundamentals",{"title":2694,"path":2695,"stem":2696},"Properties та Bindings: Реактивність у JavaFX","\u002Fjava\u002Fpr2\u002Fjavafx-properties-bindings","04.java\u002Fpr2\u002F26.javafx-properties-bindings",{"title":2698,"path":2699,"stem":2700},"MVC vs MVP vs MVVM: Еволюція архітектурних патернів UI","\u002Fjava\u002Fpr2\u002Fui-architecture-patterns","04.java\u002Fpr2\u002F27.ui-architecture-patterns",{"title":2702,"path":2703,"stem":2704},"MVVM на практиці: Побудова ViewModel","\u002Fjava\u002Fpr2\u002Fmvvm-viewmodel-implementation","04.java\u002Fpr2\u002F28.mvvm-viewmodel-implementation",{"title":2706,"path":2707,"stem":2708},"View та Controller: Зв'язування з ViewModel через FXML","\u002Fjava\u002Fpr2\u002Fmvvm-view-controller","04.java\u002Fpr2\u002F29.mvvm-view-controller",{"title":2710,"path":2711,"stem":2712},"Інтеграція MVVM з Guice: Автоматична ін'єкція залежностей","\u002Fjava\u002Fpr2\u002Fmvvm-guice-integration","04.java\u002Fpr2\u002F30.mvvm-guice-integration",{"title":2714,"path":2715,"stem":2716},"Валідація та обробка помилок у MVVM","\u002Fjava\u002Fpr2\u002Fmvvm-validation-error-handling","04.java\u002Fpr2\u002F31.mvvm-validation-error-handling",{"title":2718,"path":2719,"stem":2720},"Навігація та управління екранами у JavaFX MVVM","\u002Fjava\u002Fpr2\u002Fmvvm-navigation-screen-management","04.java\u002Fpr2\u002F32.mvvm-navigation-screen-management",{"title":2722,"path":2723,"stem":2724},"Тестування JavaFX MVVM-додатків","\u002Fjava\u002Fpr2\u002Fmvvm-testing","04.java\u002Fpr2\u002F33.mvvm-testing",{"title":2726,"path":2727,"stem":2728},"Стилізація та теми у JavaFX: CSS та User Experience","\u002Fjava\u002Fpr2\u002Fjavafx-styling-themes","04.java\u002Fpr2\u002F34.javafx-styling-themes",{"title":2730,"path":2731,"stem":2732},"AtlantaFX: Сучасні теми для JavaFX додатків","\u002Fjava\u002Fpr2\u002Fatlantafx-modern-themes","04.java\u002Fpr2\u002F35.atlantafx-modern-themes",{"title":2734,"path":2735,"stem":2736},"Пакування та розповсюдження JavaFX-додатків","\u002Fjava\u002Fpr2\u002Fjar-packaging-distribution","04.java\u002Fpr2\u002F36.jar-packaging-distribution",{"title":2738,"icon":2739,"path":2740,"stem":2741,"children":2742,"page":59},"Python","i-devicon-python","\u002Fpython","05.python",[2743,2747,2750,2754,2758,2762,2766,2770,2774,2778,2782,2786,2790,2794,2798,2859],{"title":2744,"path":2745,"stem":2746},"Модулі, Пакети та Віртуальні Середовища","\u002Fpython\u002Fmodules-packages-venv","05.python\u002F00.modules-packages-venv",{"title":71,"path":2748,"stem":2749},"\u002Fpython\u002Fclasses-objects","05.python\u002F01.classes-objects",{"title":2751,"path":2752,"stem":2753},"Інкапсуляція, Керування Доступом та Властивості","\u002Fpython\u002Fencapsulation","05.python\u002F02.encapsulation",{"title":2755,"path":2756,"stem":2757},"Наслідування, MRO та суперсила super()","\u002Fpython\u002Finheritance-mro","05.python\u002F03.inheritance-mro",{"title":2759,"path":2760,"stem":2761},"Абстракція — ABC проти Статичних Протоколів (PEP 544)","\u002Fpython\u002Fabstraction-protocols","05.python\u002F04.abstraction-protocols",{"title":2763,"path":2764,"stem":2765},"Магічні методи (Dunder) та Емуляція протоколів","\u002Fpython\u002Fdunder-methods","05.python\u002F05.dunder-methods",{"title":2767,"path":2768,"stem":2769},"Декоратори та Керування життєвим циклом методів","\u002Fpython\u002Fdecorators-static-class","05.python\u002F06.decorators-static-class",{"title":2771,"path":2772,"stem":2773},"Дескриптори — Магія доступу до атрибутів","\u002Fpython\u002Fdescriptors","05.python\u002F07.descriptors",{"title":2775,"path":2776,"stem":2777},"Метакласи — Динамічне створення класів під капотом CPython","\u002Fpython\u002Fmetaclasses","05.python\u002F08.metaclasses",{"title":2779,"path":2780,"stem":2781},"Dataclasses, NamedTuple та сучасні контейнери Python","\u002Fpython\u002Fmodern-containers","05.python\u002F09.modern-containers",{"title":2783,"path":2784,"stem":2785},"GIL та модель конкурентності CPython — фундамент перед потоками і процесами","\u002Fpython\u002Fgil-concurrency-intro","05.python\u002F11.gil-concurrency-intro",{"title":2787,"path":2788,"stem":2789},"Threading — конкурентність для I\u002FO-bound задач","\u002Fpython\u002Fthreading","05.python\u002F12.threading",{"title":2791,"path":2792,"stem":2793},"Multiprocessing — справжній паралелізм для CPU-bound задач","\u002Fpython\u002Fmultiprocessing","05.python\u002F13.multiprocessing",{"title":2795,"path":2796,"stem":2797},"asyncio — кооперативна конкурентність та event loop","\u002Fpython\u002Fasyncio","05.python\u002F14.asyncio",{"title":2799,"icon":92,"path":2800,"stem":2801,"children":2802,"page":59},"FastAPI","\u002Fpython\u002Ffastapi","05.python\u002Ffastapi",[2803,2807,2811,2815,2819,2823,2827,2831,2835,2839,2843,2847,2851,2855],{"title":2804,"path":2805,"stem":2806},"Глибокий Typing та Pydantic v2 — від анотацій до валідації","\u002Fpython\u002Ffastapi\u002Ftyping-pydantic","05.python\u002Ffastapi\u002F15.typing-pydantic",{"title":2808,"path":2809,"stem":2810},"WSGI, ASGI та Python Web-екосистема","\u002Fpython\u002Ffastapi\u002Fwsgi-asgi-ecosystem","05.python\u002Ffastapi\u002F16.wsgi-asgi-ecosystem",{"title":2812,"path":2813,"stem":2814},"FastAPI: Перший додаток, Uvicorn та OpenAPI","\u002Fpython\u002Ffastapi\u002Ffastapi-intro","05.python\u002Ffastapi\u002F17.fastapi-intro",{"title":2816,"path":2817,"stem":2818},"Маршрутизація, параметри запитів та APIRouter","\u002Fpython\u002Ffastapi\u002Ffastapi-routing-params","05.python\u002Ffastapi\u002F18.fastapi-routing-params",{"title":2820,"path":2821,"stem":2822},"Pydantic v2 у FastAPI — схеми, валідація та серіалізація","\u002Fpython\u002Ffastapi\u002Ffastapi-pydantic-schemas","05.python\u002Ffastapi\u002F19.fastapi-pydantic-schemas",{"title":2824,"path":2825,"stem":2826},"Dependency Injection — серце архітектури FastAPI","\u002Fpython\u002Ffastapi\u002Ffastapi-dependency-injection","05.python\u002Ffastapi\u002F20.fastapi-dependency-injection",{"title":2828,"path":2829,"stem":2830},"Обробка помилок, Middleware та CORS у FastAPI","\u002Fpython\u002Ffastapi\u002Ffastapi-errors-middleware","05.python\u002Ffastapi\u002F21.fastapi-errors-middleware",{"title":2832,"path":2833,"stem":2834},"SQLAlchemy 2.0 — ORM, Core та Async Engine","\u002Fpython\u002Ffastapi\u002Fsqlalchemy-orm","05.python\u002Ffastapi\u002F22.sqlalchemy-orm",{"title":2836,"path":2837,"stem":2838},"Alembic — міграції бази даних","\u002Fpython\u002Ffastapi\u002Falembic-migrations","05.python\u002Ffastapi\u002F23.alembic-migrations",{"title":2840,"path":2841,"stem":2842},"Repository Pattern та Clean Architecture у FastAPI","\u002Fpython\u002Ffastapi\u002Ffastapi-repository-clean-arch","05.python\u002Ffastapi\u002F24.fastapi-repository-clean-arch",{"title":2844,"path":2845,"stem":2846},"Автентифікація та авторизація — JWT, OAuth2, RBAC","\u002Fpython\u002Ffastapi\u002Ffastapi-auth-security","05.python\u002Ffastapi\u002F25.fastapi-auth-security",{"title":2848,"path":2849,"stem":2850},"Тестування FastAPI — pytest, httpx, fixtures","\u002Fpython\u002Ffastapi\u002Ffastapi-testing","05.python\u002Ffastapi\u002F26.fastapi-testing",{"title":2852,"path":2853,"stem":2854},"Async на повну — Background Tasks, WebSockets, SSE та Celery","\u002Fpython\u002Ffastapi\u002Ffastapi-async-advanced","05.python\u002Ffastapi\u002F27.fastapi-async-advanced",{"title":2856,"path":2857,"stem":2858},"Deployment — Docker, CI\u002FCD, Monitoring","\u002Fpython\u002Ffastapi\u002Ffastapi-deployment","05.python\u002Ffastapi\u002F28.fastapi-deployment",{"title":2860,"path":2861,"stem":2862},"[object Object]","\u002Fpython\u002Foop-plan","05.python\u002Foop-plan",{"title":2864,"icon":2865,"path":2866,"stem":2867,"children":2868,"page":59},"Бази даних","i-lucide-database","\u002Fdatabases","06.databases",[2869,2899,2922,2959,2988,3006,3040,3052,3061],{"title":2870,"icon":2871,"path":2872,"stem":2873,"children":2874,"page":59},"Intro","i-lucide-play","\u002Fdatabases\u002Fintro","06.databases\u002F01.intro",[2875,2879,2883,2887,2891,2895],{"title":2876,"path":2877,"stem":2878},"Введення в теорію баз даних","\u002Fdatabases\u002Fintro\u002Fintroduction-to-databases","06.databases\u002F01.intro\u002F01.introduction-to-databases",{"title":2880,"path":2881,"stem":2882},"Реляційна модель даних","\u002Fdatabases\u002Fintro\u002Frelational-model-theory","06.databases\u002F01.intro\u002F02.relational-model-theory",{"title":2884,"path":2885,"stem":2886},"ER-моделювання","\u002Fdatabases\u002Fintro\u002Fer-modeling","06.databases\u002F01.intro\u002F03.er-modeling",{"title":2888,"path":2889,"stem":2890},"Логічне проектування БД","\u002Fdatabases\u002Fintro\u002Flogical-schema","06.databases\u002F01.intro\u002F04.logical-schema",{"title":2892,"path":2893,"stem":2894},"Класифікація таблиць","\u002Fdatabases\u002Fintro\u002Ftable-classification","06.databases\u002F01.intro\u002F05.table-classification",{"title":2896,"path":2897,"stem":2898},"PlantUML для баз даних","\u002Fdatabases\u002Fintro\u002Fplantuml-diagrams","06.databases\u002F01.intro\u002F06.plantuml-diagrams",{"title":2900,"icon":2865,"path":2901,"stem":2902,"children":2903,"page":59},"MS SQL Server Start","\u002Fdatabases\u002Fms-sql-server-start","06.databases\u002F02.ms-sql-server-start",[2904,2908,2914,2918],{"title":2905,"path":2906,"stem":2907},"Типи даних у MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fdata-types","06.databases\u002F02.ms-sql-server-start\u002F01.data-types",{"title":2909,"path":2910,"stem":2911,"children":2912},"Індекси у MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fsql-indexes","06.databases\u002F02.ms-sql-server-start\u002F02.sql-indexes",[2913],{"title":2909,"path":2910,"stem":2911},{"title":2915,"path":2916,"stem":2917},"Системні бази даних MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fsystem-databases","06.databases\u002F02.ms-sql-server-start\u002F03.system-databases",{"title":2919,"path":2920,"stem":2921},"Огляд мови SQL та запитів","\u002Fdatabases\u002Fms-sql-server-start\u002Fsql-queries-overview","06.databases\u002F02.ms-sql-server-start\u002F04.sql-queries-overview",{"title":2923,"icon":2865,"path":2924,"stem":2925,"children":2926,"page":59},"SQL","\u002Fdatabases\u002Fsql","06.databases\u002F03.sql",[2927,2931,2935,2939,2943,2947,2951,2955],{"title":2928,"path":2929,"stem":2930},"Налаштування демонстраційної бази даних","\u002Fdatabases\u002Fsql\u002Fsample-database-setup","06.databases\u002F03.sql\u002F00.sample-database-setup",{"title":2932,"path":2933,"stem":2934},"DDL - Створення таблиць (CREATE TABLE)","\u002Fdatabases\u002Fsql\u002Fddl-create-table","06.databases\u002F03.sql\u002F01.ddl-create-table",{"title":2936,"path":2937,"stem":2938},"DDL - Зміна та видалення таблиць (ALTER, DROP)","\u002Fdatabases\u002Fsql\u002Fddl-alter-drop-table","06.databases\u002F03.sql\u002F02.ddl-alter-drop-table",{"title":2940,"path":2941,"stem":2942},"SELECT запити - Основи","\u002Fdatabases\u002Fsql\u002Fselect-queries-fundamentals","06.databases\u002F03.sql\u002F03.select-queries-fundamentals",{"title":2944,"path":2945,"stem":2946},"SELECT запити - Розширені можливості","\u002Fdatabases\u002Fsql\u002Fselect-queries-advanced","06.databases\u002F03.sql\u002F04.select-queries-advanced",{"title":2948,"path":2949,"stem":2950},"INSERT запити - Додавання даних","\u002Fdatabases\u002Fsql\u002Finsert-queries","06.databases\u002F03.sql\u002F05.insert-queries",{"title":2952,"path":2953,"stem":2954},"UPDATE та DELETE запити","\u002Fdatabases\u002Fsql\u002Fupdate-delete-queries","06.databases\u002F03.sql\u002F06.update-delete-queries",{"title":2956,"path":2957,"stem":2958},"Транзакції в SQL","\u002Fdatabases\u002Fsql\u002Ftransactions","06.databases\u002F03.sql\u002F07.transactions",{"title":2960,"icon":2865,"path":2961,"stem":2962,"children":2963,"page":59},"Multi Table Databases","\u002Fdatabases\u002Fmulti-table-databases","06.databases\u002F04.multi-table-databases",[2964,2968,2972,2976,2980,2984],{"title":2965,"path":2966,"stem":2967},"Зв'язки та нормалізація БД","\u002Fdatabases\u002Fmulti-table-databases\u002Frelationships-and-normalization","06.databases\u002F04.multi-table-databases\u002F00.relationships-and-normalization",{"title":2969,"path":2970,"stem":2971},"INNER JOIN - З'єднання таблиць","\u002Fdatabases\u002Fmulti-table-databases\u002Finner-join","06.databases\u002F04.multi-table-databases\u002F01.inner-join",{"title":2973,"path":2974,"stem":2975},"OUTER JOINs - LEFT, RIGHT, FULL","\u002Fdatabases\u002Fmulti-table-databases\u002Fouter-joins","06.databases\u002F04.multi-table-databases\u002F02.outer-joins",{"title":2977,"path":2978,"stem":2979},"CROSS та SELF JOINs","\u002Fdatabases\u002Fmulti-table-databases\u002Fcross-self-joins","06.databases\u002F04.multi-table-databases\u002F03.cross-self-joins",{"title":2981,"path":2982,"stem":2983},"Підзапити (Subqueries)","\u002Fdatabases\u002Fmulti-table-databases\u002Fsubqueries","06.databases\u002F04.multi-table-databases\u002F04.subqueries",{"title":2985,"path":2986,"stem":2987},"Агрегації з JOIN","\u002Fdatabases\u002Fmulti-table-databases\u002Faggregations-with-joins","06.databases\u002F04.multi-table-databases\u002F05.aggregations-with-joins",{"title":2989,"icon":2990,"path":2991,"stem":2992,"children":2993,"page":59},"Aggregate Functions","i-lucide-calculator","\u002Fdatabases\u002Faggregate-functions","06.databases\u002F05.aggregate-functions",[2994,2998,3002],{"title":2995,"path":2996,"stem":2997},"Функції агрегування в MS SQL Server","\u002Fdatabases\u002Faggregate-functions\u002Fintroduction-aggregate-functions","06.databases\u002F05.aggregate-functions\u002F01.introduction-aggregate-functions",{"title":2999,"path":3000,"stem":3001},"Групування даних в MS SQL Server","\u002Fdatabases\u002Faggregate-functions\u002Fgrouping-data","06.databases\u002F05.aggregate-functions\u002F02.grouping-data",{"title":3003,"path":3004,"stem":3005},"Підзапити з агрегатними функціями","\u002Fdatabases\u002Faggregate-functions\u002Fsubqueries-aggregates","06.databases\u002F05.aggregate-functions\u002F03.subqueries-aggregates",{"title":3007,"icon":3008,"path":3009,"stem":3010,"children":3011,"page":59},"Тригери та зберігаємі процедури","i-lucide-database-zap","\u002Fdatabases\u002Ftriggers-stored-procedures","06.databases\u002F07.triggers-stored-procedures",[3012,3016,3020,3024,3028,3032,3036],{"title":3013,"path":3014,"stem":3015},"DML-тригери","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fdml-triggers","06.databases\u002F07.triggers-stored-procedures\u002F01.dml-triggers",{"title":3017,"path":3018,"stem":3019},"DDL-тригери","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fddl-triggers","06.databases\u002F07.triggers-stored-procedures\u002F02.ddl-triggers",{"title":3021,"path":3022,"stem":3023},"Transact-SQL розширення","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Ftransact-sql-extensions","06.databases\u002F07.triggers-stored-procedures\u002F03.transact-sql-extensions",{"title":3025,"path":3026,"stem":3027},"Транзакції","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Ftransactions","06.databases\u002F07.triggers-stored-procedures\u002F04.transactions",{"title":3029,"path":3030,"stem":3031},"Зберігаємі процедури","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fstored-procedures","06.databases\u002F07.triggers-stored-procedures\u002F05.stored-procedures",{"title":3033,"path":3034,"stem":3035},"Користувацькі функції","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fuser-defined-functions","06.databases\u002F07.triggers-stored-procedures\u002F06.user-defined-functions",{"title":3037,"path":3038,"stem":3039},"Безпека баз даних","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fsecurity","06.databases\u002F07.triggers-stored-procedures\u002F08.security",{"title":3037,"icon":793,"path":3041,"stem":3042,"children":3043,"page":59},"\u002Fdatabases\u002Fsecurity","06.databases\u002F08.security",[3044,3048],{"title":3045,"path":3046,"stem":3047},"Вступ до безпеки баз даних","\u002Fdatabases\u002Fsecurity\u002Fintroduction","06.databases\u002F08.security\u002F01.introduction",{"title":3049,"path":3050,"stem":3051},"Системні представлення та метадані","\u002Fdatabases\u002Fsecurity\u002Fsystem-views","06.databases\u002F08.security\u002F02.system-views",{"title":3053,"icon":3054,"path":3055,"stem":3056,"children":3057,"page":59},"Резервне копіювання та відновлення","i-lucide-database-backup","\u002Fdatabases\u002Fbackup-recovery","06.databases\u002F09.backup-recovery",[3058],{"title":3053,"path":3059,"stem":3060},"\u002Fdatabases\u002Fbackup-recovery\u002Fbackup-restore","06.databases\u002F09.backup-recovery\u002F01.backup-restore",{"title":3062,"icon":3063,"path":3064,"stem":3065,"children":3066,"page":59},"Повнотекстовий пошук","i-lucide-search","\u002Fdatabases\u002Ffull-text-search","06.databases\u002F10.full-text-search",[3067],{"title":3062,"path":3068,"stem":3069},"\u002Fdatabases\u002Ffull-text-search\u002Ffull-text-search","06.databases\u002F10.full-text-search\u002F01.full-text-search",{"title":3071,"icon":3072,"path":3073,"stem":3074,"children":3075,"page":59},"Tools","i-lucide-wrench","\u002Ftools","07.tools",[3076,3152],{"title":3077,"icon":3078,"path":3079,"stem":3080,"children":3081},"Docker","i-simple-icons-docker","\u002Ftools\u002Fdocker","07.tools\u002F01.docker\u002Findex",[3082,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148],{"title":3083,"path":3079,"stem":3080},"Docker: від нуля до production",{"title":3085,"path":3086,"stem":3087},"Контейнеризація — від проблеми до рішення","\u002Ftools\u002Fdocker\u002Fcontainerization-concept","07.tools\u002F01.docker\u002F01.containerization-concept",{"title":3089,"path":3090,"stem":3091},"Docker — що це і навіщо?","\u002Ftools\u002Fdocker\u002Fdocker-what-and-why","07.tools\u002F01.docker\u002F02.docker-what-and-why",{"title":3093,"path":3094,"stem":3095},"Архітектура Docker Engine","\u002Ftools\u002Fdocker\u002Fdocker-architecture","07.tools\u002F01.docker\u002F03.docker-architecture",{"title":3097,"path":3098,"stem":3099},"Встановлення Docker","\u002Ftools\u002Fdocker\u002Finstallation","07.tools\u002F01.docker\u002F04.installation",{"title":3101,"path":3102,"stem":3103},"Перший контейнер — docker run","\u002Ftools\u002Fdocker\u002Ffirst-container","07.tools\u002F01.docker\u002F05.first-container",{"title":3105,"path":3106,"stem":3107},"Життєвий цикл контейнера","\u002Ftools\u002Fdocker\u002Fcontainer-lifecycle","07.tools\u002F01.docker\u002F06.container-lifecycle",{"title":3109,"path":3110,"stem":3111},"Docker Images — фундаментальні концепції","\u002Ftools\u002Fdocker\u002Fdocker-images-fundamentals","07.tools\u002F01.docker\u002F07.docker-images-fundamentals",{"title":3113,"path":3114,"stem":3115},"Dockerfile — основи","\u002Ftools\u002Fdocker\u002Fdockerfile-basics","07.tools\u002F01.docker\u002F08.dockerfile-basics",{"title":3117,"path":3118,"stem":3119},"Dockerfile — просунуті техніки","\u002Ftools\u002Fdocker\u002Fdockerfile-advanced","07.tools\u002F01.docker\u002F09.dockerfile-advanced",{"title":3121,"path":3122,"stem":3123},"Build Context та кешування шарів","\u002Ftools\u002Fdocker\u002Fbuild-context-and-cache","07.tools\u002F01.docker\u002F10.build-context-and-cache",{"title":3125,"path":3126,"stem":3127},"Реєстри Docker-образів","\u002Ftools\u002Fdocker\u002Fimage-registries","07.tools\u002F01.docker\u002F11.image-registries",{"title":3129,"path":3130,"stem":3131},"Контейнеризація .NET додатків","\u002Ftools\u002Fdocker\u002Fdotnet-containerization","07.tools\u002F01.docker\u002F12.dotnet-containerization",{"title":3133,"path":3134,"stem":3135},"Томи та збереження даних","\u002Ftools\u002Fdocker\u002Fvolumes-and-data","07.tools\u002F01.docker\u002F13.volumes-and-data",{"title":3137,"path":3138,"stem":3139},"Основи мережі в Docker","\u002Ftools\u002Fdocker\u002Fnetworking-basics","07.tools\u002F01.docker\u002F14.networking-basics",{"title":3141,"path":3142,"stem":3143},"Змінні оточення та конфігурація","\u002Ftools\u002Fdocker\u002Fenvironment-and-configuration","07.tools\u002F01.docker\u002F15.environment-and-configuration",{"title":3145,"path":3146,"stem":3147},"Docker Compose — оркестрація контейнерів","\u002Ftools\u002Fdocker\u002Fdocker-compose-basics","07.tools\u002F01.docker\u002F16.docker-compose-basics",{"title":3149,"path":3150,"stem":3151},"Docker Compose — Multi-Service застосунки","\u002Ftools\u002Fdocker\u002Fcompose-multi-service","07.tools\u002F01.docker\u002F17.compose-multi-service",{"title":3153,"icon":3154,"path":3155,"stem":3156,"children":3157},"Kubernetes","simple-icons:kubernetes","\u002Ftools\u002Fkubernetes","07.tools\u002F02.kubernetes\u002Findex",[3158,3160,3164,3168,3172,3176,3180,3184,3188],{"title":3159,"path":3155,"stem":3156},"Kubernetes: від розробки до production",{"title":3161,"path":3162,"stem":3163},"Kubernetes — коли Docker Compose більше не вистачає","\u002Ftools\u002Fkubernetes\u002Fwhy-kubernetes","07.tools\u002F02.kubernetes\u002F01.why-kubernetes",{"title":3165,"path":3166,"stem":3167},"Архітектура Kubernetes — анатомія кластера","\u002Ftools\u002Fkubernetes\u002Fkubernetes-architecture","07.tools\u002F02.kubernetes\u002F02.kubernetes-architecture",{"title":3169,"path":3170,"stem":3171},"Локальне середовище — minikube, kind та k3s","\u002Ftools\u002Fkubernetes\u002Flocal-environment","07.tools\u002F02.kubernetes\u002F03.local-environment",{"title":3173,"path":3174,"stem":3175},"Pod — атомарна одиниця Kubernetes","\u002Ftools\u002Fkubernetes\u002Fpods-and-containers","07.tools\u002F02.kubernetes\u002F04.pods-and-containers",{"title":3177,"path":3178,"stem":3179},"Патерни використання Pod","\u002Ftools\u002Fkubernetes\u002Fpod-patterns","07.tools\u002F02.kubernetes\u002F05.pod-patterns",{"title":3181,"path":3182,"stem":3183},"Deployment — декларативне управління Pod","\u002Ftools\u002Fkubernetes\u002Fdeployment-basics","07.tools\u002F02.kubernetes\u002F06.deployment-basics",{"title":3185,"path":3186,"stem":3187},"Rolling Updates та управління життєвим циклом Deployment","\u002Ftools\u002Fkubernetes\u002Fdeployment-rolling-updates","07.tools\u002F02.kubernetes\u002F07.deployment-rolling-updates",{"title":3189,"path":3190,"stem":3191},"Service — мережева абстракція для Pod","\u002Ftools\u002Fkubernetes\u002Fservices-networking","07.tools\u002F02.kubernetes\u002F08.services-networking",{"title":3193,"icon":3194,"path":3195,"stem":3196,"children":3197,"page":59},"Software Engineering","i-lucide-code-2","\u002Fsoftware-engineering","09.software-engineering",[3198,3202,3206,3210,3214,3218,3222,3226,3230,3234,3238],{"title":3199,"path":3200,"stem":3201},"1. Аналіз предметної області. Експертні знання та складність","\u002Fsoftware-engineering\u002Fintro-subdomains","09.software-engineering\u002F01.intro-subdomains",{"title":3203,"path":3204,"stem":3205},"2. Обмежені контексти. Інтеграція обмежених контекстів","\u002Fsoftware-engineering\u002Fintegrating-limited-contexts","09.software-engineering\u002F02.integrating-limited-contexts",{"title":3207,"path":3208,"stem":3209},"3. Реалізація простої бізнес-логіки","\u002Fsoftware-engineering\u002Fsimple","09.software-engineering\u002F03.simple",{"title":3211,"path":3212,"stem":3213},"4. Опрацювання складної бізнес-логіки","\u002Fsoftware-engineering\u002Fcomplex-business-logic","09.software-engineering\u002F04.complex-business-logic",{"title":3215,"path":3216,"stem":3217},"5. Моделювання фактора часу. Подієво-орієнтована архітектура.","\u002Fsoftware-engineering\u002Fmodelling-the-time-factor","09.software-engineering\u002F05.modelling-the-time-factor",{"title":3219,"path":3220,"stem":3221},"6. Архітектурні патерни","\u002Fsoftware-engineering\u002Farchitectural-patterns","09.software-engineering\u002F06.architectural-patterns",{"title":3223,"path":3224,"stem":3225},"Паттерни взаємодії","\u002Fsoftware-engineering\u002Fpatterns-of-interaction","09.software-engineering\u002F07.patterns-of-interaction",{"title":3227,"path":3228,"stem":3229},"Евристика проєктування","\u002Fsoftware-engineering\u002Fdesign-heuristics","09.software-engineering\u002F08.design-heuristics",{"title":3231,"path":3232,"stem":3233},"Еволюція проєктних рішень","\u002Fsoftware-engineering\u002Fevolution-of-design-solutions","09.software-engineering\u002F09.evolution-of-design-solutions",{"title":3235,"path":3236,"stem":3237},"EventStorming","\u002Fsoftware-engineering\u002Feventstorming","09.software-engineering\u002F10.eventstorming",{"title":3239,"path":3240,"stem":3241},"DDD на практиці","\u002Fsoftware-engineering\u002Fddd-in-practice","09.software-engineering\u002F11.ddd-in-practice",{"title":3243,"icon":943,"path":3244,"stem":3245,"children":3246,"page":59},"DDD","\u002Fddd","10.ddd",[3247,3251,3255,3259,3263,3267,3271,3275,3279,3283,3287,3291,3295],{"title":3248,"path":3249,"stem":3250},"Аналіз предметної області","\u002Fddd\u002Fdomain-analysis","10.ddd\u002F01.domain-analysis",{"title":3252,"path":3253,"stem":3254},"Експертні знання про предметну область","\u002Fddd\u002Fdomain-expert-knowledge","10.ddd\u002F02.domain-expert-knowledge",{"title":3256,"path":3257,"stem":3258},"Як осмислити складність предметної області","\u002Fddd\u002Fmanaging-domain-complexity","10.ddd\u002F03.managing-domain-complexity",{"title":3260,"path":3261,"stem":3262},"Інтеграція обмежених контекстів","\u002Fddd\u002Fbounded-context-integration","10.ddd\u002F04.bounded-context-integration",{"title":3264,"path":3265,"stem":3266},"Реалізація простої бізнес-логіки","\u002Fddd\u002Fsimple-business-logic","10.ddd\u002F05.simple-business-logic",{"title":3268,"path":3269,"stem":3270},"Обробка складної бізнес-логіки","\u002Fddd\u002Fcomplex-business-logic","10.ddd\u002F06.complex-business-logic",{"title":3272,"path":3273,"stem":3274},"Моделювання фактора часу","\u002Fddd\u002Ftime-modeling","10.ddd\u002F07.time-modeling",{"title":3276,"path":3277,"stem":3278},"Глава 8. Архітектурні Патерни","\u002Fddd\u002Farchitectural-patterns","10.ddd\u002F08.architectural-patterns",{"title":3280,"path":3281,"stem":3282},"Глава 9. Патерни Взаємодії","\u002Fddd\u002Finteraction-patterns","10.ddd\u002F09.interaction-patterns",{"title":3284,"path":3285,"stem":3286},"Глава 10. Проектні Евристики","\u002Fddd\u002Fdesign-heuristics","10.ddd\u002F10.design-heuristics",{"title":3288,"path":3289,"stem":3290},"Глава 11. Еволюція Проектних Рішень","\u002Fddd\u002Fevolution-of-design-decisions","10.ddd\u002F11.evolution-of-design-decisions",{"title":3292,"path":3293,"stem":3294},"Глава 12. EventStorming","\u002Fddd\u002Fevent-storming","10.ddd\u002F12.event-storming",{"title":3296,"path":3297,"stem":3298},"Глава 13. DDD на Практиці","\u002Fddd\u002Fddd-in-practice","10.ddd\u002F13.ddd-in-practice",{"title":3300,"icon":3301,"path":3302,"stem":3303,"children":3304,"page":59},"Media Streaming","i-lucide-video","\u002Fmedia-streaming","11.media-streaming",[3305,3309,3313,3317,3321,3325,3329],{"title":3306,"path":3307,"stem":3308},"01. Магія Стрімінгу: Що відбувається, коли ви натискаєте \"Play\"","\u002Fmedia-streaming\u002Fintroduction","11.media-streaming\u002F01.introduction",{"title":3310,"path":3311,"stem":3312},"02. Анатомія Медіа: Кодеки, Контейнери та Стиснення","\u002Fmedia-streaming\u002Faudio-video-anatomy","11.media-streaming\u002F02.audio-video-anatomy",{"title":3314,"path":3315,"stem":3316},"03. The Gym: FFmpeg Deep Dive","\u002Fmedia-streaming\u002Fffmpeg-gym","11.media-streaming\u002F03.ffmpeg-gym",{"title":3318,"path":3319,"stem":3320},"04. HLS Protocol: HTTP Live Streaming у Деталях","\u002Fmedia-streaming\u002Fhls-protocol","11.media-streaming\u002F04.hls-protocol",{"title":3322,"path":3323,"stem":3324},"05. DASH Protocol: Відкритий Стандарт","\u002Fmedia-streaming\u002Fdash-protocol","11.media-streaming\u002F05.dash-protocol",{"title":3326,"path":3327,"stem":3328},"06. Масштабування: CDN та Adaptive Bitrate","\u002Fmedia-streaming\u002Fcdn-and-adaptive-bitrate","11.media-streaming\u002F06.cdn-and-adaptive-bitrate",{"title":3330,"path":3331,"stem":3332},"07. Війна із Затримкою (Latency)","\u002Fmedia-streaming\u002Frealtime-latency","11.media-streaming\u002F07.realtime-latency",{"title":3334,"icon":3335,"path":3336,"stem":3337,"children":3338,"page":59},"HTML & CSS","i-devicon-html5","\u002Fhtml-css","12.html-css",[3339,3343,3347,3351,3355,3359,3363,3367,3371,3375,3379,3383,3387,3391,3395,3399,3403,3407,3411,3415,3419,3423,3427,3431,3435,3439,3443,3447,3451,3455],{"title":3340,"path":3341,"stem":3342},"Вступ до HTML. Структура документа","\u002Fhtml-css\u002Fintro-html-structure","12.html-css\u002F01.intro-html-structure",{"title":3344,"path":3345,"stem":3346},"Форматування тексту в HTML","\u002Fhtml-css\u002Fhtml-text-formatting","12.html-css\u002F02.html-text-formatting",{"title":3348,"path":3349,"stem":3350},"Посилання та зображення в HTML","\u002Fhtml-css\u002Fhtml-links-images","12.html-css\u002F03.html-links-images",{"title":3352,"path":3353,"stem":3354},"Списки та таблиці в HTML","\u002Fhtml-css\u002Fhtml-lists-tables","12.html-css\u002F04.html-lists-tables",{"title":3356,"path":3357,"stem":3358},"Форми в HTML","\u002Fhtml-css\u002Fhtml-forms","12.html-css\u002F05.html-forms",{"title":3360,"path":3361,"stem":3362},"Семантичні елементи HTML5","\u002Fhtml-css\u002Fhtml-semantic-elements","12.html-css\u002F06.html-semantic-elements",{"title":3364,"path":3365,"stem":3366},"Мультимедіа та розширені елементи HTML","\u002Fhtml-css\u002Fhtml-multimedia-advanced","12.html-css\u002F07.html-multimedia-advanced",{"title":3368,"path":3369,"stem":3370},"Мікророзмітка та SEO в HTML","\u002Fhtml-css\u002Fhtml-microdata-seo","12.html-css\u002F08.html-microdata-seo",{"title":3372,"path":3373,"stem":3374},"Вступ до CSS. Селектори та специфічність","\u002Fhtml-css\u002Fcss-intro-selectors","12.html-css\u002F09.css-intro-selectors",{"title":3376,"path":3377,"stem":3378},"Блокова модель CSS. Відступи. Box Sizing","\u002Fhtml-css\u002Fcss-box-model","12.html-css\u002F10.css-box-model",{"title":3380,"path":3381,"stem":3382},"Розміри у CSS: повний довідник одиниць і ключових слів","\u002Fhtml-css\u002F10a.css-sizing","12.html-css\u002F10a.css-sizing",{"title":3384,"path":3385,"stem":3386},"Типографіка в CSS. Шрифти та текст","\u002Fhtml-css\u002Fcss-typography","12.html-css\u002F11.css-typography",{"title":3388,"path":3389,"stem":3390},"Кольори та фони в CSS","\u002Fhtml-css\u002Fcss-colors-backgrounds","12.html-css\u002F12.css-colors-backgrounds",{"title":3392,"path":3393,"stem":3394},"Тіні та фільтри в CSS","\u002Fhtml-css\u002F12b.css-shadows-filters","12.html-css\u002F12b.css-shadows-filters",{"title":3396,"path":3397,"stem":3398},"CSS Flexbox: Фундамент гнучких макетів","\u002Fhtml-css\u002Fcss-flexbox-fundamentals","12.html-css\u002F13.css-flexbox-fundamentals",{"title":3400,"path":3401,"stem":3402},"CSS Flexbox: Вирівнювання та Позиціонування","\u002Fhtml-css\u002Fcss-flexbox-alignment-sizing-and-patterns","12.html-css\u002F14.css-flexbox-alignment-sizing-and-patterns",{"title":3404,"path":3405,"stem":3406},"CSS Grid. Двовимірний макет. Частина 1","\u002Fhtml-css\u002Fcss-layout-grid","12.html-css\u002F15.css-layout-grid",{"title":3408,"path":3409,"stem":3410},"CSS Grid. Двовимірний макет. Частина 2","\u002Fhtml-css\u002Fcss-layout-grid-advanced","12.html-css\u002F16.css-layout-grid-advanced",{"title":3412,"path":3413,"stem":3414},"Позиціонування в CSS. Z-index. Stacking Context","\u002Fhtml-css\u002Fcss-positioning","12.html-css\u002F17.css-positioning",{"title":3416,"path":3417,"stem":3418},"CSS Анімації та Переходи","\u002Fhtml-css\u002Fcss-animations-transitions","12.html-css\u002F18.css-animations-transitions",{"title":3420,"path":3421,"stem":3422},"Адаптивний дизайн. Media Queries. Частина 1","\u002Fhtml-css\u002Fcss-responsive-media-queries","12.html-css\u002F19.css-responsive-media-queries",{"title":3424,"path":3425,"stem":3426},"Адаптивний дизайн. Частина 2: clamp(), Container Queries, @layer","\u002Fhtml-css\u002Fcss-responsive-advanced","12.html-css\u002F20.css-responsive-advanced",{"title":3428,"path":3429,"stem":3430},"CSS Custom Properties. Методології. Сучасний CSS","\u002Fhtml-css\u002Fcss-variables-methodologies","12.html-css\u002F21.css-variables-methodologies",{"title":3432,"path":3433,"stem":3434},"Сучасний CSS 2023–2025: Нові можливості","\u002Fhtml-css\u002Fcss-modern-features","12.html-css\u002F22.css-modern-features",{"title":3436,"path":3437,"stem":3438},"CSS Nesting, @layer, @scope та @property: нативний препроцесор","\u002Fhtml-css\u002F22a.css-nesting-modern-syntax","12.html-css\u002F22a.css-nesting-modern-syntax",{"title":3440,"path":3441,"stem":3442},"CSS для форм та інтерактивних станів","\u002Fhtml-css\u002Fcss-forms-interactive-states","12.html-css\u002F23.css-forms-interactive-states",{"title":3444,"path":3445,"stem":3446},"Доступність у CSS (CSS Accessibility)","\u002Fhtml-css\u002Fcss-accessibility","12.html-css\u002F24.css-accessibility",{"title":3448,"path":3449,"stem":3450},"CSS-функції та сучасні sizing primitives","\u002Fhtml-css\u002Fcss-functions-sizing","12.html-css\u002F25.css-functions-sizing",{"title":3452,"path":3453,"stem":3454},"Rendering Pipeline і CSS Performance","\u002Fhtml-css\u002Fcss-rendering-performance","12.html-css\u002F26.css-rendering-performance",{"title":3456,"path":3457,"stem":3458},"CSS Best Practices: типові ситуації та правильні рішення","\u002Fhtml-css\u002Fcss-best-practices","12.html-css\u002F27.css-best-practices",{"title":3460,"path":3461,"stem":3462,"children":3463,"page":59},"AWS","\u002Faws","13.aws",[3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532],{"title":3465,"path":3466,"stem":3467},"Реєстрація AWS акаунту та студентські програми","\u002Faws\u002Faccount-registration","13.aws\u002F00.account-registration",{"title":3469,"path":3470,"stem":3471},"Вступ до хмарних обчислень та AWS","\u002Faws\u002Fintroduction-to-cloud","13.aws\u002F01.introduction-to-cloud",{"title":3473,"path":3474,"stem":3475},"AWS IAM — Identity and Access Management","\u002Faws\u002Fiam","13.aws\u002F02.iam",{"title":3477,"path":3478,"stem":3479},"AWS IAM CLI — Довідник команд","\u002Faws\u002F02a.iam-doc","13.aws\u002F02a.iam-doc",{"title":3481,"path":3482,"stem":3483},"Docker та контейнеризація в AWS — ECR, ECS та Fargate","\u002Faws\u002Fdocker-ecs","13.aws\u002F03.docker-ecs",{"title":3485,"path":3486,"stem":3487},"AWS ECR \u002F ECS CLI — Довідник команд","\u002Faws\u002F03a.docker-ecs-doc","13.aws\u002F03a.docker-ecs-doc",{"title":3489,"path":3490,"stem":3491},"Amazon EC2 — Elastic Compute Cloud","\u002Faws\u002Fec2","13.aws\u002F04.ec2",{"title":3493,"path":3494,"stem":3495},"AWS EC2 CLI — Довідник команд","\u002Faws\u002F04a.ec2-doc","13.aws\u002F04a.ec2-doc",{"title":3497,"path":3498,"stem":3499},"Elastic Load Balancing та Auto Scaling","\u002Faws\u002Falb-asg","13.aws\u002F05.alb-asg",{"title":3501,"path":3502,"stem":3503},"Amazon S3 — Simple Storage Service","\u002Faws\u002Fs3","13.aws\u002F06.s3",{"title":3505,"path":3506,"stem":3507},"Amazon CloudFront — Content Delivery Network","\u002Faws\u002Fcloudfront","13.aws\u002F07.cloudfront",{"title":3509,"path":3510,"stem":3511},"Amazon RDS — Relational Database Service","\u002Faws\u002Frds","13.aws\u002F08.rds",{"title":3513,"path":3514,"stem":3515},"Amazon DynamoDB — NoSQL Database","\u002Faws\u002Fdynamodb","13.aws\u002F09.dynamodb",{"title":3517,"path":3518,"stem":3519},"AWS Lambda та Serverless Compute","\u002Faws\u002Flambda","13.aws\u002F10.lambda",{"title":3521,"path":3522,"stem":3523},"Amazon Bedrock - Foundation Models, RAG та Agents","\u002Faws\u002Fbedrock","13.aws\u002F22.bedrock",{"title":3525,"path":3526,"stem":3527},"Amazon Rekognition - Комп'ютерний зір","\u002Faws\u002Frekognition","13.aws\u002F23.rekognition",{"title":3529,"path":3530,"stem":3531},"Amazon Textract - Інтелектуальний аналіз документів","\u002Faws\u002Ftextract","13.aws\u002F24.textract",{"title":3533,"path":3534,"stem":3535},"Amazon Polly, Transcribe, Comprehend та Translate","\u002Faws\u002Faudio-nlp-services","13.aws\u002F25.audio-nlp-services",{"title":3537,"path":3538,"stem":3539,"children":3540,"page":59},"Ai Development","\u002Fai-development","14.ai-development",[3541],{"title":3542,"path":3543,"stem":3544,"children":3545,"page":59},"Foundations","\u002Fai-development\u002Ffoundations","14.ai-development\u002F00.foundations",[3546],{"title":3547,"path":3548,"stem":3549},"Модуль 0.1. Що таке Large Language Model (LLM)","\u002Fai-development\u002Ffoundations\u002Fwhat-is-llm","14.ai-development\u002F00.foundations\u002F01.what-is-llm",{"title":3551,"path":3552,"stem":3553,"children":3554,"page":59},"React Native","\u002Freact-native","15.react-native",[3555,3576],{"title":3556,"path":3557,"stem":3558,"children":3559,"page":59},"Version1","\u002Freact-native\u002Fversion1","15.react-native\u002Fversion1",[3560,3564,3568,3572],{"title":3561,"path":3562,"stem":3563},"Що таке React Native","\u002Freact-native\u002Fversion1\u002Fwhat-is-react-native","15.react-native\u002Fversion1\u002F01.what-is-react-native",{"title":3565,"path":3566,"stem":3567},"Як влаштований React Native «під капотом»","\u002Freact-native\u002Fversion1\u002Freact-native-architecture","15.react-native\u002Fversion1\u002F02.react-native-architecture",{"title":3569,"path":3570,"stem":3571},"Від React до React Native: ключові відмінності","\u002Freact-native\u002Fversion1\u002Freact-native-vs-react","15.react-native\u002Fversion1\u002F03.react-native-vs-react",{"title":3573,"path":3574,"stem":3575},"View та Text — фундаментальні будівельні блоки","\u002Freact-native\u002Fversion1\u002Fview-and-text","15.react-native\u002Fversion1\u002F04.view-and-text",{"title":3577,"path":3578,"stem":3579,"children":3580,"page":59},"Version2","\u002Freact-native\u002Fversion2","15.react-native\u002Fversion2",[3581,3585,3589,3593,3597,3601,3605,3609,3613,3617,3621,3625,3629,3633,3637,3641,3645,3649,3653,3657,3661,3665,3669,3673,3677,3681,3685],{"title":3582,"path":3583,"stem":3584},"React Native — навіщо він потрібен","\u002Freact-native\u002Fversion2\u002Fwhy-react-native","15.react-native\u002Fversion2\u002F01.why-react-native",{"title":3586,"path":3587,"stem":3588},"Налаштування середовища розробки","\u002Freact-native\u002Fversion2\u002Fenvironment-setup","15.react-native\u002Fversion2\u002F02.environment-setup",{"title":3590,"path":3591,"stem":3592},"Expo і класичний React Native — два шляхи одного фреймворку","\u002Freact-native\u002Fversion2\u002Fexpo-vs-cli-landscape","15.react-native\u002Fversion2\u002F03.expo-vs-cli-landscape",{"title":3594,"path":3595,"stem":3596},"Середовище Expo — перший запуск","\u002Freact-native\u002Fversion2\u002Fexpo-setup-and-tooling","15.react-native\u002Fversion2\u002F04.expo-setup-and-tooling",{"title":3598,"path":3599,"stem":3600},"Структура проєкту та перші UI-примітиви","\u002Freact-native\u002Fversion2\u002Fproject-structure-and-conventions","15.react-native\u002Fversion2\u002F05.project-structure-and-conventions",{"title":3602,"path":3603,"stem":3604},"Базові компоненти React Native","\u002Freact-native\u002Fversion2\u002Fcore-components","15.react-native\u002Fversion2\u002F06.core-components",{"title":3606,"path":3607,"stem":3608},"Flexbox і layout у React Native","\u002Freact-native\u002Fversion2\u002Fflexbox-and-layout","15.react-native\u002Fversion2\u002F07.flexbox-and-layout",{"title":3610,"path":3611,"stem":3612},"StyleSheet і теми (light \u002F dark)","\u002Freact-native\u002Fversion2\u002Fstylesheet-and-theming","15.react-native\u002Fversion2\u002F08.stylesheet-and-theming",{"title":3614,"path":3615,"stem":3616},"Списки та віртуалізація","\u002Freact-native\u002Fversion2\u002Flists-and-virtualization","15.react-native\u002Fversion2\u002F09.lists-and-virtualization",{"title":3618,"path":3619,"stem":3620},"Форми, ввід і валідація","\u002Freact-native\u002Fversion2\u002Fforms-input-validation","15.react-native\u002Fversion2\u002F10.forms-input-validation",{"title":3622,"path":3623,"stem":3624},"Основи Expo Router","\u002Freact-native\u002Fversion2\u002Fexpo-router-basics","15.react-native\u002Fversion2\u002F11.expo-router-basics",{"title":3626,"path":3627,"stem":3628},"Вкладені навігатори та модалки","\u002Freact-native\u002Fversion2\u002Fnested-navigators-and-modals","15.react-native\u002Fversion2\u002F12.nested-navigators-and-modals",{"title":3630,"path":3631,"stem":3632},"Deep linking і параметри маршрутів","\u002Freact-native\u002Fversion2\u002Fdeep-linking-and-params","15.react-native\u002Fversion2\u002F13.deep-linking-and-params",{"title":3634,"path":3635,"stem":3636},"Робота з мережею та життєвий цикл застосунку","\u002Freact-native\u002Fversion2\u002Fnetworking-and-app-lifecycle","15.react-native\u002Fversion2\u002F14.networking-and-app-lifecycle",{"title":3638,"path":3639,"stem":3640},"Redux Toolkit на мобільних платформах","\u002Freact-native\u002Fversion2\u002Fredux-toolkit-on-mobile","15.react-native\u002Fversion2\u002F15.redux-toolkit-on-mobile",{"title":3642,"path":3643,"stem":3644},"RTK Query на мобільних платформах","\u002Freact-native\u002Fversion2\u002Frtk-query-mobile","15.react-native\u002Fversion2\u002F16.rtk-query-mobile",{"title":3646,"path":3647,"stem":3648},"Автентифікація та Безпечне Зберігання","\u002Freact-native\u002Fversion2\u002Fauth-secure-storage","15.react-native\u002Fversion2\u002F18.auth-secure-storage",{"title":3650,"path":3651,"stem":3652},"Локальне сховище — AsyncStorage, MMKV, SQLite","\u002Freact-native\u002Fversion2\u002Flocal-storage-mmkv-sqlite","15.react-native\u002Fversion2\u002F19.local-storage-mmkv-sqlite",{"title":3654,"path":3655,"stem":3656},"Офлайн-First і синхронізація","\u002Freact-native\u002Fversion2\u002Foffline-first-sync","15.react-native\u002Fversion2\u002F20.offline-first-sync",{"title":3658,"path":3659,"stem":3660},"Камера, медіа та файлова система","\u002Freact-native\u002Fversion2\u002Fcamera-media-filesystem","15.react-native\u002Fversion2\u002F21.camera-media-filesystem",{"title":3662,"path":3663,"stem":3664},"Карти та геолокація","\u002Freact-native\u002Fversion2\u002Fmaps-and-location","15.react-native\u002Fversion2\u002F22.maps-and-location",{"title":3666,"path":3667,"stem":3668},"Жести та дотики","\u002Freact-native\u002Fversion2\u002Fgestures-and-touch","15.react-native\u002Fversion2\u002F23.gestures-and-touch",{"title":3670,"path":3671,"stem":3672},"Анімації з Reanimated","\u002Freact-native\u002Fversion2\u002Freanimated-animations","15.react-native\u002Fversion2\u002F24.reanimated-animations",{"title":3674,"path":3675,"stem":3676},"Push і локальні сповіщення","\u002Freact-native\u002Fversion2\u002Fpush-and-local-notifications","15.react-native\u002Fversion2\u002F25.push-and-local-notifications",{"title":3678,"path":3679,"stem":3680},"Інтернаціоналізація та доступність","\u002Freact-native\u002Fversion2\u002Faccessibility-and-i18n","15.react-native\u002Fversion2\u002F26.accessibility-and-i18n",{"title":3682,"path":3683,"stem":3684},"EAS Build та профілі збірки","\u002Freact-native\u002Fversion2\u002Feas-build-and-profiles","15.react-native\u002Fversion2\u002F29.eas-build-and-profiles",{"title":3686,"path":3687,"stem":3688},"Публікація в App Store та Google Play","\u002Freact-native\u002Fversion2\u002Fapp-store-play-release","15.react-native\u002Fversion2\u002F30.app-store-play-release",{"title":3690,"path":3691,"stem":3692,"children":3693,"page":59},"Ai Python","\u002Fai-python","16.ai-python",[3694,3703,3712,3721,3750,3776,3801,3834,3851,3864,3873,3886,3895,3916,3929,3950],{"title":3695,"path":3696,"stem":3697,"children":3698,"page":59},"Intro To Ai","\u002Fai-python\u002Fintro-to-ai","16.ai-python\u002F01.intro-to-ai",[3699],{"title":3700,"path":3701,"stem":3702},"Вступ до штучного інтелекту та машинного навчання","\u002Fai-python\u002Fintro-to-ai\u002Fintro-to-ai","16.ai-python\u002F01.intro-to-ai\u002F01.intro-to-ai",{"title":3704,"path":3705,"stem":3706,"children":3707,"page":59},"Jupyter Notebook","\u002Fai-python\u002Fjupyter-notebook","16.ai-python\u002F02.jupyter-notebook",[3708],{"title":3709,"path":3710,"stem":3711},"Знайомство з Jupyter Notebook та JupyterLab","\u002Fai-python\u002Fjupyter-notebook\u002Fjupyter-notebook","16.ai-python\u002F02.jupyter-notebook\u002F02.jupyter-notebook",{"title":3713,"path":3714,"stem":3715,"children":3716,"page":59},"Numpy Pandas","\u002Fai-python\u002Fnumpy-pandas","16.ai-python\u002F03.numpy-pandas",[3717],{"title":3718,"path":3719,"stem":3720},"Робота з даними з використанням NumPy і pandas","\u002Fai-python\u002Fnumpy-pandas\u002Fnumpy-pandas","16.ai-python\u002F03.numpy-pandas\u002F03.numpy-pandas",{"title":3722,"path":3723,"stem":3724,"children":3725,"page":59},"Statistics Basics","\u002Fai-python\u002Fstatistics-basics","16.ai-python\u002F04.statistics-basics",[3726,3730,3734,3738,3742,3746],{"title":3727,"path":3728,"stem":3729},"Випадкові величини та їх типи","\u002Fai-python\u002Fstatistics-basics\u002Frandom-variables","16.ai-python\u002F04.statistics-basics\u002F01.random-variables",{"title":3731,"path":3732,"stem":3733},"Міри центральної тенденції","\u002Fai-python\u002Fstatistics-basics\u002Fcentral-tendency","16.ai-python\u002F04.statistics-basics\u002F02.central-tendency",{"title":3735,"path":3736,"stem":3737},"Міри варіації (розсіювання)","\u002Fai-python\u002Fstatistics-basics\u002Fvariability","16.ai-python\u002F04.statistics-basics\u002F03.variability",{"title":3739,"path":3740,"stem":3741},"Розподіли випадкових величин","\u002Fai-python\u002Fstatistics-basics\u002Fdistributions","16.ai-python\u002F04.statistics-basics\u002F04.distributions",{"title":3743,"path":3744,"stem":3745},"Кореляція та взаємозв'язки","\u002Fai-python\u002Fstatistics-basics\u002Fcorrelation","16.ai-python\u002F04.statistics-basics\u002F05.correlation",{"title":3747,"path":3748,"stem":3749},"Викиди та аномалії","\u002Fai-python\u002Fstatistics-basics\u002Foutliers","16.ai-python\u002F04.statistics-basics\u002F06.outliers",{"title":3751,"path":3752,"stem":3753,"children":3754},"Візуалізація даних за допомогою matplotlib","\u002Fai-python\u002Fdata-visualization","16.ai-python\u002F05.data-visualization\u002Findex",[3755,3756,3760,3764,3768,3772],{"title":3751,"path":3752,"stem":3753},{"title":3757,"path":3758,"stem":3759},"Основи matplotlib — Figure, Axes та перший графік","\u002Fai-python\u002Fdata-visualization\u002Fmatplotlib-basics","16.ai-python\u002F05.data-visualization\u002F01.matplotlib-basics",{"title":3761,"path":3762,"stem":3763},"Візуалізація розподілів — гістограми та box plots","\u002Fai-python\u002Fdata-visualization\u002Fdistributions","16.ai-python\u002F05.data-visualization\u002F02.distributions",{"title":3765,"path":3766,"stem":3767},"Візуалізація залежностей — scatter plots та кореляція","\u002Fai-python\u002Fdata-visualization\u002Frelationships","16.ai-python\u002F05.data-visualization\u002F03.relationships",{"title":3769,"path":3770,"stem":3771},"Візуалізація категоріальних даних — bar charts та pie charts","\u002Fai-python\u002Fdata-visualization\u002Fcategorical","16.ai-python\u002F05.data-visualization\u002F04.categorical",{"title":3773,"path":3774,"stem":3775},"Композиції та стилізація — subplots та багатопанельні дашборди","\u002Fai-python\u002Fdata-visualization\u002Fsubplots-styling","16.ai-python\u002F05.data-visualization\u002F05.subplots-styling",{"title":3777,"path":3778,"stem":3779,"children":3780,"page":59},"Linear Regression","\u002Fai-python\u002Flinear-regression","16.ai-python\u002F06.linear-regression",[3781,3785,3789,3793,3797],{"title":3782,"path":3783,"stem":3784},"Регресійна задача та лінійна регресія з однією змінною","\u002Fai-python\u002Flinear-regression\u002Fregression-task","16.ai-python\u002F06.linear-regression\u002F01.regression-task",{"title":3786,"path":3787,"stem":3788},"Метрики якості регресії — як оцінити модель","\u002Fai-python\u002Flinear-regression\u002Fquality-metrics","16.ai-python\u002F06.linear-regression\u002F02.quality-metrics",{"title":3790,"path":3791,"stem":3792},"Градієнтний спуск — як модель навчається","\u002Fai-python\u002Flinear-regression\u002Fgradient-descent","16.ai-python\u002F06.linear-regression\u002F03.gradient-descent",{"title":3794,"path":3795,"stem":3796},"Множинна лінійна регресія — модель з багатьма ознаками","\u002Fai-python\u002Flinear-regression\u002Fmultiple-regression","16.ai-python\u002F06.linear-regression\u002F04.multiple-regression",{"title":3798,"path":3799,"stem":3800},"Практичні завдання та резюме модуля","\u002Fai-python\u002Flinear-regression\u002Fpractice","16.ai-python\u002F06.linear-regression\u002F05.practice",{"title":3802,"path":3803,"stem":3804,"children":3805,"page":59},"Practical Regression","\u002Fai-python\u002Fpractical-regression","16.ai-python\u002F07.practical-regression",[3806,3810,3814,3818,3822,3826,3830],{"title":3807,"path":3808,"stem":3809},"Знайомство з scikit-learn","\u002Fai-python\u002Fpractical-regression\u002Fintro-scikit-learn","16.ai-python\u002F07.practical-regression\u002F01.intro-scikit-learn",{"title":3811,"path":3812,"stem":3813},"Exploratory Data Analysis датасету Housing Prices","\u002Fai-python\u002Fpractical-regression\u002Fhousing-dataset-eda","16.ai-python\u002F07.practical-regression\u002F02.housing-dataset-eda",{"title":3815,"path":3816,"stem":3817},"Попередня обробка даних (Data Preprocessing)","\u002Fai-python\u002Fpractical-regression\u002Fdata-preprocessing","16.ai-python\u002F07.practical-regression\u002F03.data-preprocessing",{"title":3819,"path":3820,"stem":3821},"Train\u002FTest Split та перша повноцінна модель","\u002Fai-python\u002Fpractical-regression\u002Ftrain-test-split","16.ai-python\u002F07.practical-regression\u002F04.train-test-split",{"title":3823,"path":3824,"stem":3825},"Перенавчання та недонавчання","\u002Fai-python\u002Fpractical-regression\u002Foverfitting-underfitting","16.ai-python\u002F07.practical-regression\u002F05.overfitting-underfitting",{"title":3827,"path":3828,"stem":3829},"Крос-валідація (Cross-Validation)","\u002Fai-python\u002Fpractical-regression\u002Fcross-validation","16.ai-python\u002F07.practical-regression\u002F06.cross-validation",{"title":3831,"path":3832,"stem":3833},"Гіперпараметри та їх оптимізація","\u002Fai-python\u002Fpractical-regression\u002Fhyperparameter-tuning","16.ai-python\u002F07.practical-regression\u002F07.hyperparameter-tuning",{"title":3835,"path":3836,"stem":3837,"children":3838,"page":59},"Polynomial Regression","\u002Fai-python\u002Fpolynomial-regression","16.ai-python\u002F08.polynomial-regression",[3839,3843,3847],{"title":3840,"path":3841,"stem":3842},"Нелінійні залежності та поліноміальна регресія","\u002Fai-python\u002Fpolynomial-regression\u002Fnonlinear-patterns","16.ai-python\u002F08.polynomial-regression\u002F01.nonlinear-patterns",{"title":3844,"path":3845,"stem":3846},"Регуляризація в регресії — Ridge та Lasso","\u002Fai-python\u002Fpolynomial-regression\u002Fregularization-methods","16.ai-python\u002F08.polynomial-regression\u002F02.regularization-methods",{"title":3848,"path":3849,"stem":3850},"Практика — поліноміальна регресія та регуляризація на Housing Prices","\u002Fai-python\u002Fpolynomial-regression\u002Fhousing-prices-practice","16.ai-python\u002F08.polynomial-regression\u002F03.housing-prices-practice",{"title":3852,"path":3853,"stem":3854,"children":3855,"page":59},"Decision Trees Random Forest","\u002Fai-python\u002Fdecision-trees-random-forest","16.ai-python\u002F09.decision-trees-random-forest",[3856,3860],{"title":3857,"path":3858,"stem":3859},"Дерева рішень — моделі з прозорою та зрозумілою логікою","\u002Fai-python\u002Fdecision-trees-random-forest\u002Fdecision-trees","16.ai-python\u002F09.decision-trees-random-forest\u002F01.decision-trees",{"title":3861,"path":3862,"stem":3863},"Random Forest — ансамблі дерев рішень","\u002Fai-python\u002Fdecision-trees-random-forest\u002Frandom-forest","16.ai-python\u002F09.decision-trees-random-forest\u002F02.random-forest",{"title":3865,"path":3866,"stem":3867,"children":3868,"page":59},"Gradient Boosting","\u002Fai-python\u002Fgradient-boosting","16.ai-python\u002F10.gradient-boosting",[3869],{"title":3870,"path":3871,"stem":3872},"Градієнтний бустинг та XGBoost — послідовне виправлення помилок","\u002Fai-python\u002Fgradient-boosting\u002Fgradient-boosting-xgboost","16.ai-python\u002F10.gradient-boosting\u002F01.gradient-boosting-xgboost",{"title":3874,"path":3875,"stem":3876,"children":3877,"page":59},"Logistic Regression Classification","\u002Fai-python\u002Flogistic-regression-classification","16.ai-python\u002F11.logistic-regression-classification",[3878,3882],{"title":3879,"path":3880,"stem":3881},"Основи класифікації та логістична регресія","\u002Fai-python\u002Flogistic-regression-classification\u002Fclassification-basics","16.ai-python\u002F11.logistic-regression-classification\u002F01.classification-basics",{"title":3883,"path":3884,"stem":3885},"Метрики якості класифікації","\u002Fai-python\u002Flogistic-regression-classification\u002Fclassification-metrics","16.ai-python\u002F11.logistic-regression-classification\u002F02.classification-metrics",{"title":3887,"path":3888,"stem":3889,"children":3890,"page":59},"Neural Networks Basics","\u002Fai-python\u002Fneural-networks-basics","16.ai-python\u002F12.neural-networks-basics",[3891],{"title":3892,"path":3893,"stem":3894},"Вступ до нейронних мереж — від персептрона до багатошарових мереж","\u002Fai-python\u002Fneural-networks-basics\u002Fperceptron-to-mlp","16.ai-python\u002F12.neural-networks-basics\u002F01.perceptron-to-mlp",{"title":3896,"path":3897,"stem":3898,"children":3899,"page":59},"Pytorch Intro","\u002Fai-python\u002Fpytorch-intro","16.ai-python\u002F13.pytorch-intro",[3900,3904,3908,3912],{"title":3901,"path":3902,"stem":3903},"Тензори та автоматичне диференціювання в PyTorch","\u002Fai-python\u002Fpytorch-intro\u002Ftensors-and-autograd","16.ai-python\u002F13.pytorch-intro\u002F01.tensors-and-autograd",{"title":3905,"path":3906,"stem":3907},"Побудова та тренування нейронних мереж у PyTorch","\u002Fai-python\u002Fpytorch-intro\u002Fbuilding-and-training","16.ai-python\u002F13.pytorch-intro\u002F02.building-and-training",{"title":3909,"path":3910,"stem":3911},"Робота з даними та регуляризація в PyTorch","\u002Fai-python\u002Fpytorch-intro\u002Fdata-and-regularization","16.ai-python\u002F13.pytorch-intro\u002F03.data-and-regularization",{"title":3913,"path":3914,"stem":3915},"Просунуті теми PyTorch — GPU, оптимізація та deployment","\u002Fai-python\u002Fpytorch-intro\u002Fadvanced-topics","16.ai-python\u002F13.pytorch-intro\u002F04.advanced-topics",{"title":3917,"path":3918,"stem":3919,"children":3920,"page":59},"Activation Functions Heart Disease","\u002Fai-python\u002Factivation-functions-heart-disease","16.ai-python\u002F14.activation-functions-heart-disease",[3921,3925],{"title":3922,"path":3923,"stem":3924},"Функції активації та побудова нейронної мережі для передбачення вад серця","\u002Fai-python\u002Factivation-functions-heart-disease\u002Fheart-disease-prediction","16.ai-python\u002F14.activation-functions-heart-disease\u002F01.heart-disease-prediction",{"title":3926,"path":3927,"stem":3928},"Бонус — Повний приклад Student Performance Prediction","\u002Fai-python\u002Factivation-functions-heart-disease\u002Fstudent-performance-bonus","16.ai-python\u002F14.activation-functions-heart-disease\u002F02.student-performance-bonus",{"title":3930,"path":3931,"stem":3932,"children":3933,"page":59},"Cnn Image Classification","\u002Fai-python\u002Fcnn-image-classification","16.ai-python\u002F15.cnn-image-classification",[3934,3938,3942,3946],{"title":3935,"path":3936,"stem":3937},"Основи згорткових нейронних мереж — чому не MLP?","\u002Fai-python\u002Fcnn-image-classification\u002Fcnn-fundamentals","16.ai-python\u002F15.cnn-image-classification\u002F01.cnn-fundamentals",{"title":3939,"path":3940,"stem":3941},"Практична імплементація CNN для класифікації MNIST","\u002Fai-python\u002Fcnn-image-classification\u002Fmnist-classification","16.ai-python\u002F15.cnn-image-classification\u002F02.mnist-classification",{"title":3943,"path":3944,"stem":3945},"Глибші CNN та аугментація даних для CIFAR-10","\u002Fai-python\u002Fcnn-image-classification\u002Fcifar10-and-data-augmentation","16.ai-python\u002F15.cnn-image-classification\u002F03.cifar10-and-data-augmentation",{"title":3947,"path":3948,"stem":3949},"Transfer Learning та візуалізація внутрішніх представлень CNN","\u002Fai-python\u002Fcnn-image-classification\u002Ftransfer-learning-and-visualization","16.ai-python\u002F15.cnn-image-classification\u002F04.transfer-learning-and-visualization",{"title":3951,"path":3952,"stem":3953,"children":3954,"page":59},"Batching Normalization Deep Networks","\u002Fai-python\u002Fbatching-normalization-deep-networks","16.ai-python\u002F16.batching-normalization-deep-networks",[3955,3959,3963,3967,3971,3975,3979],{"title":3956,"path":3957,"stem":3958},"Mini-batch Gradient Descent та ефективна обробка даних","\u002Fai-python\u002Fbatching-normalization-deep-networks\u002Fmini-batch-gradient-descent","16.ai-python\u002F16.batching-normalization-deep-networks\u002F01.mini-batch-gradient-descent",{"title":3960,"path":3961,"stem":3962},"Ініціалізація ваг — фундамент стабільного навчання","\u002Fai-python\u002Fbatching-normalization-deep-networks\u002Fweight-initialization","16.ai-python\u002F16.batching-normalization-deep-networks\u002F02.weight-initialization",{"title":3964,"path":3965,"stem":3966},"Batch Normalization — прорив у глибокому навчанні","\u002Fai-python\u002Fbatching-normalization-deep-networks\u002Fbatch-normalization","16.ai-python\u002F16.batching-normalization-deep-networks\u002F03.batch-normalization",{"title":3968,"path":3969,"stem":3970},"Dropout та комбіновані стратегії регуляризації","\u002Fai-python\u002Fbatching-normalization-deep-networks\u002Fdropout-and-regularization","16.ai-python\u002F16.batching-normalization-deep-networks\u002F04.dropout-and-regularization",{"title":3972,"path":3973,"stem":3974},"Проєктування глибоких мереж — від теорії до практики","\u002Fai-python\u002Fbatching-normalization-deep-networks\u002Fdeep-networks-architectures","16.ai-python\u002F16.batching-normalization-deep-networks\u002F05.deep-networks-architectures",{"title":3976,"path":3977,"stem":3978},"Планування learning rate — коли та як зменшувати крок навчання","\u002Fai-python\u002Fbatching-normalization-deep-networks\u002Flearning-rate-scheduling","16.ai-python\u002F16.batching-normalization-deep-networks\u002F06.learning-rate-scheduling",{"title":3980,"path":3981,"stem":3982},"Підсумковий проєкт — побудова сучасного класифікатора зображень","\u002Fai-python\u002Fbatching-normalization-deep-networks\u002Ffinal-project-deep-classifier","16.ai-python\u002F16.batching-normalization-deep-networks\u002F07.final-project-deep-classifier",{"title":3984,"icon":658,"path":3985,"stem":3986,"children":3987,"page":59},"Клієнт-серверна архітектура","\u002Fclient-server-architecture","17.client-server-architecture",[3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056],{"title":3989,"path":3990,"stem":3991},"Архітектура клієнт-серверних систем та основи Go","\u002Fclient-server-architecture\u002Fclient-server-architecture-and-go","17.client-server-architecture\u002F01.client-server-architecture-and-go",{"title":3993,"path":3994,"stem":3995},"Система типів та модель пам’яті мови Go","\u002Fclient-server-architecture\u002Ftype-system-and-memory-model","17.client-server-architecture\u002F02.type-system-and-memory-model",{"title":3997,"path":3998,"stem":3999},"Інтерфейси та модель конкурентності мови Go","\u002Fclient-server-architecture\u002Finterfaces-and-concurrency-model","17.client-server-architecture\u002F03.interfaces-and-concurrency-model",{"title":4001,"path":4002,"stem":4003},"Сокетне програмування та протокол TCP у Go","\u002Fclient-server-architecture\u002Ftcp-socket-programming","17.client-server-architecture\u002F04.tcp-socket-programming",{"title":4005,"path":4006,"stem":4007},"Протокол UDP та датаграмна передача даних","\u002Fclient-server-architecture\u002Fudp-datagram-transmission","17.client-server-architecture\u002F05.udp-datagram-transmission",{"title":4009,"path":4010,"stem":4011},"Криптографічний захист каналів зв","\u002Fclient-server-architecture\u002Ftls-cryptographic-channel-protection","17.client-server-architecture\u002F06.tls-cryptographic-channel-protection",{"title":4013,"path":4014,"stem":4015},"Прикладні мережеві протоколи DNS та SMTP","\u002Fclient-server-architecture\u002Fdns-smtp-application-protocols","17.client-server-architecture\u002F07.dns-smtp-application-protocols",{"title":4017,"path":4018,"stem":4019},"Протокол HTTP та стандартний вебсервер Go","\u002Fclient-server-architecture\u002Fhttp-protocol-and-web-server","17.client-server-architecture\u002F08.http-protocol-and-web-server",{"title":4021,"path":4022,"stem":4023},"Проєктування REST API та маршрутизація засобами Chi","\u002Fclient-server-architecture\u002Frest-api-design-and-chi-routing","17.client-server-architecture\u002F09.rest-api-design-and-chi-routing",{"title":4025,"path":4026,"stem":4027},"Лекція 10. Шарувата архітектура та конвеєри обробки запитів","\u002Fclient-server-architecture\u002Flayered-architecture-and-middleware-pipelines","17.client-server-architecture\u002F10.layered-architecture-and-middleware-pipelines",{"title":4029,"path":4030,"stem":4031},"Лекція 11. Архітектура та маршрутизація фреймворку Gin","\u002Fclient-server-architecture\u002Fgin-framework-architecture-and-routing","17.client-server-architecture\u002F11.gin-framework-architecture-and-routing",{"title":4033,"path":4034,"stem":4035},"Лекція 12. Валідація даних, безпека та документування у Gin","\u002Fclient-server-architecture\u002Fdata-validation-security-and-documentation-in-gin","17.client-server-architecture\u002F12.data-validation-security-and-documentation-in-gin",{"title":4037,"path":4038,"stem":4039},"Лекція 13. Взаємодія з реляційними СУБД та технології ORM","\u002Fclient-server-architecture\u002Frelational-databases-and-orm","17.client-server-architecture\u002F13.relational-databases-and-orm",{"title":4041,"path":4042,"stem":4043},"Потокова передача даних та протокол WebSocket","\u002Fclient-server-architecture\u002Fstreaming-data-and-websockets","17.client-server-architecture\u002F14.streaming-data-and-websockets",{"title":4045,"path":4046,"stem":4047},"Міжсервісна взаємодія на основі протоколу gRPC","\u002Fclient-server-architecture\u002Fgrpc-inter-service-communication","17.client-server-architecture\u002F15.grpc-inter-service-communication",{"title":4049,"path":4050,"stem":4051},"Асинхронний обмін повідомленнями та протоколи черг","\u002Fclient-server-architecture\u002Fasynchronous-message-exchange-and-queue-protocols","17.client-server-architecture\u002F16.asynchronous-message-exchange-and-queue-protocols",{"title":4053,"path":4054,"stem":4055},"Протоколи потокового медіа та технологія WebRTC","\u002Fclient-server-architecture\u002Fstreaming-media-protocols-and-webrtc","17.client-server-architecture\u002F17.streaming-media-protocols-and-webrtc",{"title":4057,"path":4058,"stem":4059},"Моніторинг, безпека та контейнеризація застосунків","\u002Fclient-server-architecture\u002Fmonitoring-security-and-containerization","17.client-server-architecture\u002F18.monitoring-security-and-containerization",{"title":4061,"icon":2118,"path":4062,"stem":4063,"children":4064,"page":59},"Розробка WEB-застосувань","\u002Fweb-development","18.web-development",[4065,4110,4148,4177,4206,4263,4313,4407,4473,4555,4588,4616,4658,4704,4747],{"title":4066,"icon":658,"path":4067,"stem":4068,"children":4069,"page":59},"Архітектура комп'ютерних мереж та мережеві протоколи","\u002Fweb-development\u002Fnetwork-architecture-and-protocols","18.web-development\u002F01.network-architecture-and-protocols",[4070,4074,4078,4082,4086,4090,4094,4098,4102,4106],{"title":4071,"path":4072,"stem":4073},"Еволюція веброзробки та модель клієнт-сервер","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fweb-evolution-client-server","18.web-development\u002F01.network-architecture-and-protocols\u002F01.web-evolution-client-server",{"title":4075,"path":4076,"stem":4077},"Еталонна модель OSI","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fosi-model","18.web-development\u002F01.network-architecture-and-protocols\u002F02.osi-model",{"title":4079,"path":4080,"stem":4081},"Стек протоколів TCP\u002FIP","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Ftcp-ip-stack","18.web-development\u002F01.network-architecture-and-protocols\u002F03.tcp-ip-stack",{"title":4083,"path":4084,"stem":4085},"IP-адресація, порти та сокети","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fip-addressing-ports-sockets","18.web-development\u002F01.network-architecture-and-protocols\u002F04.ip-addressing-ports-sockets",{"title":4087,"path":4088,"stem":4089},"Протоколи TCP та UDP","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Ftcp-udp-protocols","18.web-development\u002F01.network-architecture-and-protocols\u002F05.tcp-udp-protocols",{"title":4091,"path":4092,"stem":4093},"Система доменних імен DNS","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fdns-system","18.web-development\u002F01.network-architecture-and-protocols\u002F06.dns-system",{"title":4095,"path":4096,"stem":4097},"Протокол SMTP та поштові протоколи","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fsmtp-email-protocols","18.web-development\u002F01.network-architecture-and-protocols\u002F07.smtp-email-protocols",{"title":4099,"path":4100,"stem":4101},"Протокол WebSocket","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fwebsocket-protocol","18.web-development\u002F01.network-architecture-and-protocols\u002F08.websocket-protocol",{"title":4103,"path":4104,"stem":4105},"Протокол SSH","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fssh-protocol","18.web-development\u002F01.network-architecture-and-protocols\u002F09.ssh-protocol",{"title":4107,"path":4108,"stem":4109},"Огляд сучасних серверних технологій","\u002Fweb-development\u002Fnetwork-architecture-and-protocols\u002Fmodern-backend-technologies","18.web-development\u002F01.network-architecture-and-protocols\u002F10.modern-backend-technologies",{"title":4111,"icon":4112,"path":4113,"stem":4114,"children":4115,"page":59},"Протокол HTTP та захищена передача даних HTTPS","i-lucide-lock","\u002Fweb-development\u002Fhttp-and-https","18.web-development\u002F02.http-and-https",[4116,4120,4124,4128,4132,4136,4140,4144],{"title":4117,"path":4118,"stem":4119},"Вступ до протоколу HTTP та еволюція веб-технологій","\u002Fweb-development\u002Fhttp-and-https\u002Fintroduction-and-evolution","18.web-development\u002F02.http-and-https\u002F01.introduction-and-evolution",{"title":4121,"path":4122,"stem":4123},"Анатомія HTTP-запиту","\u002Fweb-development\u002Fhttp-and-https\u002Fhttp-request-structure","18.web-development\u002F02.http-and-https\u002F02.http-request-structure",{"title":4125,"path":4126,"stem":4127},"Анатомія HTTP-відповіді","\u002Fweb-development\u002Fhttp-and-https\u002Fhttp-response-structure","18.web-development\u002F02.http-and-https\u002F03.http-response-structure",{"title":4129,"path":4130,"stem":4131},"Методи HTTP та їхня семантика","\u002Fweb-development\u002Fhttp-and-https\u002Fhttp-methods","18.web-development\u002F02.http-and-https\u002F04.http-methods",{"title":4133,"path":4134,"stem":4135},"Коди статусу HTTP та їхнє призначення","\u002Fweb-development\u002Fhttp-and-https\u002Fhttp-status-codes","18.web-development\u002F02.http-and-https\u002F05.http-status-codes",{"title":4137,"path":4138,"stem":4139},"HTTP-заголовки та їхнє призначення","\u002Fweb-development\u002Fhttp-and-https\u002Fhttp-headers","18.web-development\u002F02.http-and-https\u002F06.http-headers",{"title":4141,"path":4142,"stem":4143},"CORS та механізми безпеки HTTP","\u002Fweb-development\u002Fhttp-and-https\u002Fcors-and-security","18.web-development\u002F02.http-and-https\u002F07.cors-and-security",{"title":4145,"path":4146,"stem":4147},"HTTPS та криптографічні основи TLS","\u002Fweb-development\u002Fhttp-and-https\u002Fhttps-and-tls","18.web-development\u002F02.http-and-https\u002F08.https-and-tls",{"title":4149,"icon":2498,"path":4150,"stem":4151,"children":4152,"page":59},"Система типів мови TypeScript","\u002Fweb-development\u002Ftypescript-type-system","18.web-development\u002F03.typescript-type-system",[4153,4157,4161,4165,4169,4173],{"title":4154,"path":4155,"stem":4156},"Знайомство з TypeScript Compiler","\u002Fweb-development\u002Ftypescript-type-system\u002Fintroduction-compiler-config","18.web-development\u002F03.typescript-type-system\u002F01.introduction-compiler-config",{"title":4158,"path":4159,"stem":4160},"Примітивні типи даних: string, number, boolean","\u002Fweb-development\u002Ftypescript-type-system\u002Fprimitive-special-types","18.web-development\u002F03.typescript-type-system\u002F02.primitive-special-types",{"title":4162,"path":4163,"stem":4164},"Масиви, кортежі та об","\u002Fweb-development\u002Ftypescript-type-system\u002Farrays-tuples-objects","18.web-development\u002F03.typescript-type-system\u002F03.arrays-tuples-objects",{"title":4166,"path":4167,"stem":4168},"Псевдоніми типів, Union типи, Intersection типи та літеральні типи","\u002Fweb-development\u002Ftypescript-type-system\u002Funion-intersection-literals","18.web-development\u002F03.typescript-type-system\u002F04.union-intersection-literals",{"title":4170,"path":4171,"stem":4172},"Type Narrowing (звуження типів)","\u002Fweb-development\u002Ftypescript-type-system\u002Ftype-narrowing","18.web-development\u002F03.typescript-type-system\u002F05.type-narrowing",{"title":4174,"path":4175,"stem":4176},"Типізація функцій","\u002Fweb-development\u002Ftypescript-type-system\u002Ffunctions-typing","18.web-development\u002F03.typescript-type-system\u002F06.functions-typing",{"title":4178,"icon":2498,"path":4179,"stem":4180,"children":4181,"page":59},"Розширені засоби мови TypeScript","\u002Fweb-development\u002Ftypescript-advanced","18.web-development\u002F04.typescript-advanced",[4182,4186,4190,4194,4198,4202],{"title":4183,"path":4184,"stem":4185},"Interfaces та Classes","\u002Fweb-development\u002Ftypescript-advanced\u002Finterfaces-classes","18.web-development\u002F04.typescript-advanced\u002F01.interfaces-classes",{"title":4187,"path":4188,"stem":4189},"Основи Generic типів","\u002Fweb-development\u002Ftypescript-advanced\u002Fgenerics-fundamentals","18.web-development\u002F04.typescript-advanced\u002F02.generics-fundamentals",{"title":4191,"path":4192,"stem":4193},"Enums та Decorators","\u002Fweb-development\u002Ftypescript-advanced\u002Fenums-decorators","18.web-development\u002F04.typescript-advanced\u002F03.enums-decorators",{"title":4195,"path":4196,"stem":4197},"Utility Types","\u002Fweb-development\u002Ftypescript-advanced\u002Futility-types","18.web-development\u002F04.typescript-advanced\u002F04.utility-types",{"title":4199,"path":4200,"stem":4201},"Mapped та Conditional Types","\u002Fweb-development\u002Ftypescript-advanced\u002Fmapped-conditional-types","18.web-development\u002F04.typescript-advanced\u002F05.mapped-conditional-types",{"title":4203,"path":4204,"stem":4205},"Модулі та Strict Mode","\u002Fweb-development\u002Ftypescript-advanced\u002Fmodules-strict-mode","18.web-development\u002F04.typescript-advanced\u002F06.modules-strict-mode",{"title":4207,"icon":4208,"path":4209,"stem":4210,"children":4211,"page":59},"Платформа Node.js як середовище виконання","i-devicon-nodejs","\u002Fweb-development\u002Fnodejs-platform","18.web-development\u002F05.nodejs-platform",[4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4255,4259],{"title":4213,"path":4214,"stem":4215},"Що таке Node.js та його архітектура","\u002Fweb-development\u002Fnodejs-platform\u002Fwhat-is-nodejs","18.web-development\u002F05.nodejs-platform\u002F01.what-is-nodejs",{"title":4217,"path":4218,"stem":4219},"Event Loop — серце Node.js","\u002Fweb-development\u002Fnodejs-platform\u002Fevent-loop","18.web-development\u002F05.nodejs-platform\u002F02.event-loop",{"title":4221,"path":4222,"stem":4223},"Модульні системи: CommonJS vs ES Modules","\u002Fweb-development\u002Fnodejs-platform\u002Fmodule-systems","18.web-development\u002F05.nodejs-platform\u002F03.module-systems",{"title":4225,"path":4226,"stem":4227},"npm та package.json","\u002Fweb-development\u002Fnodejs-platform\u002Fnpm-package-json","18.web-development\u002F05.nodejs-platform\u002F04.npm-package-json",{"title":4229,"path":4230,"stem":4231},"Глобальні об\\","\u002Fweb-development\u002Fnodejs-platform\u002Fglobal-objects","18.web-development\u002F05.nodejs-platform\u002F05.global-objects",{"title":4233,"path":4234,"stem":4235},"Вбудований модуль fs (File System)","\u002Fweb-development\u002Fnodejs-platform\u002Fbuilt-in-modules-fs","18.web-development\u002F05.nodejs-platform\u002F06.built-in-modules-fs",{"title":4237,"path":4238,"stem":4239},"Вбудований модуль path","\u002Fweb-development\u002Fnodejs-platform\u002Fbuilt-in-modules-path","18.web-development\u002F05.nodejs-platform\u002F07.built-in-modules-path",{"title":4241,"path":4242,"stem":4243},"Вбудований модуль http","\u002Fweb-development\u002Fnodejs-platform\u002Fbuilt-in-modules-http","18.web-development\u002F05.nodejs-platform\u002F08.built-in-modules-http",{"title":4245,"path":4246,"stem":4247},"Streams — потоки даних у Node.js","\u002Fweb-development\u002Fnodejs-platform\u002Fstreams","18.web-development\u002F05.nodejs-platform\u002F09.streams",{"title":4249,"path":4250,"stem":4251},"Вбудований модуль util (Utilities)","\u002Fweb-development\u002Fnodejs-platform\u002Fbuilt-in-modules-util","18.web-development\u002F05.nodejs-platform\u002F10.built-in-modules-util",{"title":3141,"path":4253,"stem":4254},"\u002Fweb-development\u002Fnodejs-platform\u002Fenvironment-variables","18.web-development\u002F05.nodejs-platform\u002F11.environment-variables",{"title":4256,"path":4257,"stem":4258},"Інструменти налагодження та розробки","\u002Fweb-development\u002Fnodejs-platform\u002Fdebugging-tools","18.web-development\u002F05.nodejs-platform\u002F12.debugging-tools",{"title":4260,"path":4261,"stem":4262},"Продуктивність та Best Practices","\u002Fweb-development\u002Fnodejs-platform\u002Fperformance-best-practices","18.web-development\u002F05.nodejs-platform\u002F13.performance-best-practices",{"title":4264,"icon":4265,"path":4266,"stem":4267,"children":4268,"page":59},"Асинхронне програмування у Node.js","i-lucide-clock","\u002Fweb-development\u002Fasync-programming-nodejs","18.web-development\u002F06.async-programming-nodejs",[4269,4273,4277,4281,4285,4289,4293,4297,4301,4305,4309],{"title":4270,"path":4271,"stem":4272},"Синхронний vs асинхронний код","\u002Fweb-development\u002Fasync-programming-nodejs\u002Fsync-vs-async","18.web-development\u002F06.async-programming-nodejs\u002F01.sync-vs-async",{"title":4274,"path":4275,"stem":4276},"Callbacks — перша парадигма асинхронності","\u002Fweb-development\u002Fasync-programming-nodejs\u002Fcallbacks","18.web-development\u002F06.async-programming-nodejs\u002F02.callbacks",{"title":4278,"path":4279,"stem":4280},"Promises — стандарт асинхронності","\u002Fweb-development\u002Fasync-programming-nodejs\u002Fpromises","18.web-development\u002F06.async-programming-nodejs\u002F03.promises",{"title":4282,"path":4283,"stem":4284},"Promise комбінатори","\u002Fweb-development\u002Fasync-programming-nodejs\u002Fpromise-combinators","18.web-development\u002F06.async-programming-nodejs\u002F04.promise-combinators",{"title":4286,"path":4287,"stem":4288},"Async\u002FAwait — синтаксичний цукор над Promises","\u002Fweb-development\u002Fasync-programming-nodejs\u002Fasync-await","18.web-development\u002F06.async-programming-nodejs\u002F05.async-await",{"title":4290,"path":4291,"stem":4292},"Паралельне vs послідовне виконання","\u002Fweb-development\u002Fasync-programming-nodejs\u002Fparallel-vs-sequential","18.web-development\u002F06.async-programming-nodejs\u002F06.parallel-vs-sequential",{"title":4294,"path":4295,"stem":4296},"EventEmitter — подієва модель Node.js","\u002Fweb-development\u002Fasync-programming-nodejs\u002Fevent-emitter","18.web-development\u002F06.async-programming-nodejs\u002F07.event-emitter",{"title":4298,"path":4299,"stem":4300},"Worker Threads — багатопотоковість у Node.js","\u002Fweb-development\u002Fasync-programming-nodejs\u002Fworker-threads","18.web-development\u002F06.async-programming-nodejs\u002F08.worker-threads",{"title":4302,"path":4303,"stem":4304},"Child Processes — запуск зовнішніх процесів","\u002Fweb-development\u002Fasync-programming-nodejs\u002Fchild-processes","18.web-development\u002F06.async-programming-nodejs\u002F09.child-processes",{"title":4306,"path":4307,"stem":4308},"Обробка помилок у асинхронному коді","\u002Fweb-development\u002Fasync-programming-nodejs\u002Ferror-handling","18.web-development\u002F06.async-programming-nodejs\u002F10.error-handling",{"title":4310,"path":4311,"stem":4312},"Порівняння Node.js фреймворків","\u002Fweb-development\u002Fasync-programming-nodejs\u002Fnodejs-frameworks","18.web-development\u002F06.async-programming-nodejs\u002F11.nodejs-frameworks",{"title":4314,"icon":4315,"path":4316,"stem":4317,"children":4318,"page":59},"Тема 7. Фреймворк NestJS. Контролери та маршрутизація","i-lucide-route","\u002Fweb-development\u002Fnestjs-controllers-routing","18.web-development\u002F07.nestjs-controllers-routing",[4319,4323,4327,4331,4335,4339,4343,4347,4351,4355,4359,4363,4367,4371,4375,4379,4383,4387,4391,4395,4399,4403],{"title":4320,"path":4321,"stem":4322},"Філософія та архітектура NestJS","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fnestjs-philosophy","18.web-development\u002F07.nestjs-controllers-routing\u002F01.nestjs-philosophy",{"title":4324,"path":4325,"stem":4326},"Три стовпи архітектури: модулі, контролери, провайдери","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fthree-pillars","18.web-development\u002F07.nestjs-controllers-routing\u002F02.three-pillars",{"title":4328,"path":4329,"stem":4330},"Інверсія керування (IoC) та ін","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fioc-and-di","18.web-development\u002F07.nestjs-controllers-routing\u002F03.ioc-and-di",{"title":4332,"path":4333,"stem":4334},"Встановлення NestJS CLI та створення проєкту","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fnestjs-cli-setup","18.web-development\u002F07.nestjs-controllers-routing\u002F04.nestjs-cli-setup",{"title":4336,"path":4337,"stem":4338},"Структура проєкту NestJS та конвенції","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fproject-structure","18.web-development\u002F07.nestjs-controllers-routing\u002F05.project-structure",{"title":4340,"path":4341,"stem":4342},"Контролери: основи маршрутизації","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fcontrollers-basics","18.web-development\u002F07.nestjs-controllers-routing\u002F06.controllers-basics",{"title":4344,"path":4345,"stem":4346},"Декоратори маршрутизації HTTP-методів","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Froute-decorators","18.web-development\u002F07.nestjs-controllers-routing\u002F07.route-decorators",{"title":4348,"path":4349,"stem":4350},"Параметри маршруту та декоратор @Param","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Froute-parameters","18.web-development\u002F07.nestjs-controllers-routing\u002F08.route-parameters",{"title":4352,"path":4353,"stem":4354},"Query-параметри та декоратор @Query","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fquery-parameters","18.web-development\u002F07.nestjs-controllers-routing\u002F09.query-parameters",{"title":4356,"path":4357,"stem":4358},"Тіло запиту та декоратор @Body","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Frequest-body","18.web-development\u002F07.nestjs-controllers-routing\u002F10.request-body",{"title":4360,"path":4361,"stem":4362},"Робота з заголовками запиту та відповіді","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fheaders-decorators","18.web-development\u002F07.nestjs-controllers-routing\u002F11.headers-decorators",{"title":4364,"path":4365,"stem":4366},"Data Transfer Objects (DTO): визначення та призначення","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fdto-introduction","18.web-development\u002F07.nestjs-controllers-routing\u002F12.dto-introduction",{"title":4368,"path":4369,"stem":4370},"Об'єкти Request та Response","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Frequest-response-objects","18.web-development\u002F07.nestjs-controllers-routing\u002F13.request-response-objects",{"title":4372,"path":4373,"stem":4374},"HTTP-статус коди та декоратор @HttpCode","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fhttp-status-codes","18.web-development\u002F07.nestjs-controllers-routing\u002F14.http-status-codes",{"title":4376,"path":4377,"stem":4378},"Перенаправлення (Redirects) та декоратор @Redirect","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fredirects","18.web-development\u002F07.nestjs-controllers-routing\u002F15.redirects",{"title":4380,"path":4381,"stem":4382},"Асинхронні обробники та Observables","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fasync-handlers","18.web-development\u002F07.nestjs-controllers-routing\u002F16.async-handlers",{"title":4384,"path":4385,"stem":4386},"Маршрутизація за піддоменами та версіонування API","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fsub-domain-routing","18.web-development\u002F07.nestjs-controllers-routing\u002F17.sub-domain-routing",{"title":4388,"path":4389,"stem":4390},"Області видимості контролерів (Scopes)","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fcontroller-scopes","18.web-development\u002F07.nestjs-controllers-routing\u002F18.controller-scopes",{"title":4392,"path":4393,"stem":4394},"Основні команди NestJS CLI","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fnestjs-cli-commands","18.web-development\u002F07.nestjs-controllers-routing\u002F19.nestjs-cli-commands",{"title":4396,"path":4397,"stem":4398},"Практичний приклад: створення CRUD-контролера","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fcrud-example","18.web-development\u002F07.nestjs-controllers-routing\u002F20.crud-example",{"title":4400,"path":4401,"stem":4402},"Тестування контролерів","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Ftesting-controllers","18.web-development\u002F07.nestjs-controllers-routing\u002F21.testing-controllers",{"title":4404,"path":4405,"stem":4406},"Best Practices для контролерів та маршрутизації","\u002Fweb-development\u002Fnestjs-controllers-routing\u002Fbest-practices","18.web-development\u002F07.nestjs-controllers-routing\u002F22.best-practices",{"title":4408,"icon":4409,"path":4410,"stem":4411,"children":4412,"page":59},"Тема 8. Провайдери, сервіси та модульна система NestJS","i-lucide-package","\u002Fweb-development\u002Fnestjs-providers-modules","18.web-development\u002F08.nestjs-providers-modules",[4413,4417,4421,4425,4429,4433,4437,4441,4445,4449,4453,4457,4461,4465,4469],{"title":4414,"path":4415,"stem":4416},"Концепція провайдерів у NestJS","\u002Fweb-development\u002Fnestjs-providers-modules\u002Fproviders-concept","18.web-development\u002F08.nestjs-providers-modules\u002F01.providers-concept",{"title":4418,"path":4419,"stem":4420},"Сервіси: інкапсуляція бізнес-логіки","\u002Fweb-development\u002Fnestjs-providers-modules\u002Fservices-business-logic","18.web-development\u002F08.nestjs-providers-modules\u002F02.services-business-logic",{"title":4422,"path":4423,"stem":4424},"Практика ін","\u002Fweb-development\u002Fnestjs-providers-modules\u002Fdependency-injection-practice","18.web-development\u002F08.nestjs-providers-modules\u002F03.dependency-injection-practice",{"title":4426,"path":4427,"stem":4428},"Custom Providers: useValue, useClass, useFactory, useExisting","\u002Fweb-development\u002Fnestjs-providers-modules\u002Fcustom-providers","18.web-development\u002F08.nestjs-providers-modules\u002F04.custom-providers",{"title":4430,"path":4431,"stem":4432},"Injection Tokens: рядкові та символьні токени","\u002Fweb-development\u002Fnestjs-providers-modules\u002Finjection-tokens","18.web-development\u002F08.nestjs-providers-modules\u002F05.injection-tokens",{"title":4434,"path":4435,"stem":4436},"Опціональні провайдери та декоратор @Optional","\u002Fweb-development\u002Fnestjs-providers-modules\u002Foptional-providers","18.web-development\u002F08.nestjs-providers-modules\u002F07.optional-providers",{"title":4438,"path":4439,"stem":4440},"Структура модулів: imports, controllers, providers, exports","\u002Fweb-development\u002Fnestjs-providers-modules\u002Fmodules-structure","18.web-development\u002F08.nestjs-providers-modules\u002F08.modules-structure",{"title":4442,"path":4443,"stem":4444},"Feature Modules: ізоляція функціональності","\u002Fweb-development\u002Fnestjs-providers-modules\u002Ffeature-modules","18.web-development\u002F08.nestjs-providers-modules\u002F09.feature-modules",{"title":4446,"path":4447,"stem":4448},"Shared Modules: перевикористання провайдерів","\u002Fweb-development\u002Fnestjs-providers-modules\u002Fshared-modules","18.web-development\u002F08.nestjs-providers-modules\u002F10.shared-modules",{"title":4450,"path":4451,"stem":4452},"Global Modules та декоратор @Global","\u002Fweb-development\u002Fnestjs-providers-modules\u002Fglobal-modules","18.web-development\u002F08.nestjs-providers-modules\u002F11.global-modules",{"title":4454,"path":4455,"stem":4456},"Dynamic Modules: конфігурація під час виконання","\u002Fweb-development\u002Fnestjs-providers-modules\u002Fdynamic-modules","18.web-development\u002F08.nestjs-providers-modules\u002F12.dynamic-modules",{"title":4458,"path":4459,"stem":4460},"Циклічні залежності та forwardRef","\u002Fweb-development\u002Fnestjs-providers-modules\u002Fcircular-dependencies","18.web-development\u002F08.nestjs-providers-modules\u002F13.circular-dependencies",{"title":4462,"path":4463,"stem":4464},"Lifecycle Hooks: хуки життєвого циклу","\u002Fweb-development\u002Fnestjs-providers-modules\u002Flifecycle-hooks","18.web-development\u002F08.nestjs-providers-modules\u002F14.lifecycle-hooks",{"title":4466,"path":4467,"stem":4468},"ModuleRef: динамічний доступ до провайдерів","\u002Fweb-development\u002Fnestjs-providers-modules\u002Fmodule-reference","18.web-development\u002F08.nestjs-providers-modules\u002F15.module-reference",{"title":4470,"path":4471,"stem":4472},"Тестування сервісів та провайдерів","\u002Fweb-development\u002Fnestjs-providers-modules\u002Ftesting-services","18.web-development\u002F08.nestjs-providers-modules\u002F16.testing-services",{"title":4474,"icon":4475,"path":4476,"stem":4477,"children":4478,"page":59},"Тема 9. Валідація даних та конвеєр обробки запитів у NestJS","i-lucide-filter","\u002Fweb-development\u002Fnestjs-validation-pipeline","18.web-development\u002F09.nestjs-validation-pipeline",[4479,4483,4487,4491,4495,4499,4503,4507,4511,4515,4519,4523,4527,4531,4535,4539,4543,4547,4551],{"title":4480,"path":4481,"stem":4482},"Request Pipeline: конвеєр обробки запитів","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Frequest-pipeline","18.web-development\u002F09.nestjs-validation-pipeline\u002F01.request-pipeline",{"title":4484,"path":4485,"stem":4486},"Pipes: концепція трансформації та валідації","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fpipes-concept","18.web-development\u002F09.nestjs-validation-pipeline\u002F02.pipes-concept",{"title":4488,"path":4489,"stem":4490},"Вбудовані pipes: ParseIntPipe, ParseBoolPipe, ParseUUIDPipe","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fbuilt-in-pipes","18.web-development\u002F09.nestjs-validation-pipeline\u002F03.built-in-pipes",{"title":4492,"path":4493,"stem":4494},"ValidationPipe: автоматична валідація DTO","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fvalidation-pipe","18.web-development\u002F09.nestjs-validation-pipeline\u002F04.validation-pipe",{"title":4496,"path":4497,"stem":4498},"Декоратори class-validator для DTO","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fclass-validator-decorators","18.web-development\u002F09.nestjs-validation-pipeline\u002F05.class-validator-decorators",{"title":4500,"path":4501,"stem":4502},"Створення кастомних валідаторів","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fcustom-validators","18.web-development\u002F09.nestjs-validation-pipeline\u002F06.custom-validators",{"title":4504,"path":4505,"stem":4506},"Створення власних Pipes","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fcustom-pipes","18.web-development\u002F09.nestjs-validation-pipeline\u002F07.custom-pipes",{"title":4508,"path":4509,"stem":4510},"Основи Middleware","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fmiddleware-basics","18.web-development\u002F09.nestjs-validation-pipeline\u002F08.middleware-basics",{"title":4512,"path":4513,"stem":4514},"Практичні приклади Middleware","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fmiddleware-examples","18.web-development\u002F09.nestjs-validation-pipeline\u002F09.middleware-examples",{"title":4516,"path":4517,"stem":4518},"Guards: захисні перехоплювачі доступу","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fguards-concept","18.web-development\u002F09.nestjs-validation-pipeline\u002F10.guards-concept",{"title":4520,"path":4521,"stem":4522},"Створення кастомних Guards","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fcustom-guards","18.web-development\u002F09.nestjs-validation-pipeline\u002F11.custom-guards",{"title":4524,"path":4525,"stem":4526},"Interceptors: перехоплення та трансформація відповідей","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Finterceptors-concept","18.web-development\u002F09.nestjs-validation-pipeline\u002F12.interceptors-concept",{"title":4528,"path":4529,"stem":4530},"Практичні приклади Interceptors","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Finterceptor-examples","18.web-development\u002F09.nestjs-validation-pipeline\u002F13.interceptor-examples",{"title":4532,"path":4533,"stem":4534},"Exception Filters: обробка виключень","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fexception-filters","18.web-development\u002F09.nestjs-validation-pipeline\u002F14.exception-filters",{"title":4536,"path":4537,"stem":4538},"Custom Exception Filters: спеціалізовані фільтри","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fcustom-exception-filters","18.web-development\u002F09.nestjs-validation-pipeline\u002F15.custom-exception-filters",{"title":4540,"path":4541,"stem":4542},"Execution Context: контекст виконання","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fexecution-context","18.web-development\u002F09.nestjs-validation-pipeline\u002F16.execution-context",{"title":4544,"path":4545,"stem":4546},"Custom Decorators: власні декоратори","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fcustom-decorators","18.web-development\u002F09.nestjs-validation-pipeline\u002F17.custom-decorators",{"title":4548,"path":4549,"stem":4550},"Global Components: глобальні компоненти","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fglobal-components","18.web-development\u002F09.nestjs-validation-pipeline\u002F18.global-components",{"title":4552,"path":4553,"stem":4554},"Pipeline Best Practices: найкращі практики","\u002Fweb-development\u002Fnestjs-validation-pipeline\u002Fpipeline-best-practices","18.web-development\u002F09.nestjs-validation-pipeline\u002F19.pipeline-best-practices",{"title":4556,"icon":2865,"path":4557,"stem":4558,"children":4559,"page":59},"Інтеграція реляційних баз даних засобами TypeORM","\u002Fweb-development\u002Ftypeorm-integration","18.web-development\u002F10.typeorm-integration",[4560,4564,4568,4572,4576,4580,4584],{"title":4561,"path":4562,"stem":4563},"Робота з базами даних у Node.js через нативні драйвери","\u002Fweb-development\u002Ftypeorm-integration\u002F01-nodejs-database-drivers","18.web-development\u002F10.typeorm-integration\u002F01-nodejs-database-drivers",{"title":4565,"path":4566,"stem":4567},"ORM та знайомство з TypeORM","\u002Fweb-development\u002Ftypeorm-integration\u002F02-orm-and-typeorm-intro","18.web-development\u002F10.typeorm-integration\u002F02-orm-and-typeorm-intro",{"title":4569,"path":4570,"stem":4571},"Налаштування TypeORM у NestJS проєкті","\u002Fweb-development\u002Ftypeorm-integration\u002F03-typeorm-setup-nestjs","18.web-development\u002F10.typeorm-integration\u002F03-typeorm-setup-nestjs",{"title":4573,"path":4574,"stem":4575},"Entity класи та декоратори TypeORM","\u002Fweb-development\u002Ftypeorm-integration\u002F04-entities-and-decorators","18.web-development\u002F10.typeorm-integration\u002F04-entities-and-decorators",{"title":4577,"path":4578,"stem":4579},"Repository Pattern та CRUD операції","\u002Fweb-development\u002Ftypeorm-integration\u002F05-repository-pattern-crud","18.web-development\u002F10.typeorm-integration\u002F05-repository-pattern-crud",{"title":4581,"path":4582,"stem":4583},"Пагінація та фільтрація даних","\u002Fweb-development\u002Ftypeorm-integration\u002F06-pagination-filtering","18.web-development\u002F10.typeorm-integration\u002F06-pagination-filtering",{"title":4585,"path":4586,"stem":4587},"QueryBuilder для складних запитів","\u002Fweb-development\u002Ftypeorm-integration\u002F07-query-builder","18.web-development\u002F10.typeorm-integration\u002F07-query-builder",{"title":4589,"icon":4590,"path":4591,"stem":4592,"children":4593,"page":59},"Зв'язки між сутностями, міграції та оптимізація запитів","i-lucide-git-branch","\u002Fweb-development\u002Fentity-relations-migrations","18.web-development\u002F11.entity-relations-migrations",[4594,4598,4602,4606,4610],{"title":4595,"path":4596,"stem":4597},"Зв'язки One-to-Many та Many-to-One","\u002Fweb-development\u002Fentity-relations-migrations\u002F01-one-to-many-many-to-one","18.web-development\u002F11.entity-relations-migrations\u002F01-one-to-many-many-to-one",{"title":4599,"path":4600,"stem":4601},"Зв'язки One-to-One та Many-to-Many","\u002Fweb-development\u002Fentity-relations-migrations\u002F02-one-to-one-many-to-many","18.web-development\u002F11.entity-relations-migrations\u002F02-one-to-one-many-to-many",{"title":4603,"path":4604,"stem":4605},"Керування міграціями бази даних","\u002Fweb-development\u002Fentity-relations-migrations\u002F03-migrations-management","18.web-development\u002F11.entity-relations-migrations\u002F03-migrations-management",{"title":4607,"path":4608,"stem":4609},"Транзакції у TypeORM","\u002Fweb-development\u002Fentity-relations-migrations\u002F04-transactions","18.web-development\u002F11.entity-relations-migrations\u002F04-transactions",{"title":4611,"path":4612,"stem":4613,"children":4614},"Індекси та оптимізація запитів","\u002Fweb-development\u002Fentity-relations-migrations\u002F05-indexes-optimization","18.web-development\u002F11.entity-relations-migrations\u002F05-indexes-optimization",[4615],{"title":4611,"path":4612,"stem":4613},{"title":4617,"icon":4618,"path":4619,"stem":4620,"children":4621,"page":59},"Механізми автентифікації у вебзастосунках","i-lucide-key","\u002Fweb-development\u002Fauthentication","18.web-development\u002F12.authentication",[4622,4626,4630,4634,4638,4642,4646,4650,4654],{"title":4623,"path":4624,"stem":4625},"Автентифікація vs Авторизація","\u002Fweb-development\u002Fauthentication\u002F01-authentication-vs-authorization","18.web-development\u002F12.authentication\u002F01-authentication-vs-authorization",{"title":4627,"path":4628,"stem":4629},"Стратегії автентифікації","\u002Fweb-development\u002Fauthentication\u002F02-authentication-strategies","18.web-development\u002F12.authentication\u002F02-authentication-strategies",{"title":4631,"path":4632,"stem":4633},"Автентифікація на основі сесій","\u002Fweb-development\u002Fauthentication\u002F03-session-based-authentication","18.web-development\u002F12.authentication\u002F03-session-based-authentication",{"title":4635,"path":4636,"stem":4637},"Структура JSON Web Token (JWT)","\u002Fweb-development\u002Fauthentication\u002F04-jwt-structure","18.web-development\u002F12.authentication\u002F04-jwt-structure",{"title":4639,"path":4640,"stem":4641},"Інтеграція JWT у NestJS","\u002Fweb-development\u002Fauthentication\u002F05-nestjs-jwt-integration","18.web-development\u002F12.authentication\u002F05-nestjs-jwt-integration",{"title":4643,"path":4644,"stem":4645},"Passport.js стратегії у NestJS","\u002Fweb-development\u002Fauthentication\u002F06-passport-strategies","18.web-development\u002F12.authentication\u002F06-passport-strategies",{"title":4647,"path":4648,"stem":4649},"Безпечне зберігання паролів: хешування та криптографічний захист","\u002Fweb-development\u002Fauthentication\u002F07-password-hashing","18.web-development\u002F12.authentication\u002F07-password-hashing",{"title":4651,"path":4652,"stem":4653},"Refresh Token та безпечне оновлення доступу","\u002Fweb-development\u002Fauthentication\u002F08-refresh-tokens","18.web-development\u002F12.authentication\u002F08-refresh-tokens",{"title":4655,"path":4656,"stem":4657},"OAuth 2.0 та OpenID Connect: делегована автентифікація","\u002Fweb-development\u002Fauthentication\u002F09-oauth2-openid","18.web-development\u002F12.authentication\u002F09-oauth2-openid",{"title":4659,"icon":4660,"path":4661,"stem":4662,"children":4663,"page":59},"Авторизація та безпека серверних застосунків","i-lucide-shield","\u002Fweb-development\u002Fauthorization-security","18.web-development\u002F13.authorization-security",[4664,4668,4672,4676,4680,4684,4688,4692,4696,4700],{"title":4665,"path":4666,"stem":4667},"Моделі авторизації","\u002Fweb-development\u002Fauthorization-security\u002F01-authorization-models","18.web-development\u002F13.authorization-security\u002F01-authorization-models",{"title":4669,"path":4670,"stem":4671},"Реалізація RBAC у NestJS","\u002Fweb-development\u002Fauthorization-security\u002F02-rbac-implementation","18.web-development\u002F13.authorization-security\u002F02-rbac-implementation",{"title":4673,"path":4674,"stem":4675},"Permission-Based Access Control","\u002Fweb-development\u002Fauthorization-security\u002F03-permission-based-access","18.web-development\u002F13.authorization-security\u002F03-permission-based-access",{"title":4677,"path":4678,"stem":4679},"ABAC та бібліотека CASL","\u002Fweb-development\u002Fauthorization-security\u002F04-abac-casl","18.web-development\u002F13.authorization-security\u002F04-abac-casl",{"title":4681,"path":4682,"stem":4683},"Кастомні декоратори та метадані","\u002Fweb-development\u002Fauthorization-security\u002F05-custom-decorators-metadata","18.web-development\u002F13.authorization-security\u002F05-custom-decorators-metadata",{"title":4685,"path":4686,"stem":4687},"Конфігурація CORS","\u002Fweb-development\u002Fauthorization-security\u002F06-cors-configuration","18.web-development\u002F13.authorization-security\u002F06-cors-configuration",{"title":4689,"path":4690,"stem":4691},"Захист через HTTP Security Headers (Helmet)","\u002Fweb-development\u002Fauthorization-security\u002F07-helmet-security-headers","18.web-development\u002F13.authorization-security\u002F07-helmet-security-headers",{"title":4693,"path":4694,"stem":4695},"Rate Limiting та Throttling","\u002Fweb-development\u002Fauthorization-security\u002F08-rate-limiting-throttling","18.web-development\u002F13.authorization-security\u002F08-rate-limiting-throttling",{"title":4697,"path":4698,"stem":4699},"Захист від поширених атак","\u002Fweb-development\u002Fauthorization-security\u002F09-common-attacks-protection","18.web-development\u002F13.authorization-security\u002F09-common-attacks-protection",{"title":4701,"path":4702,"stem":4703},"HTTPS у продакшені","\u002Fweb-development\u002Fauthorization-security\u002F10-https-production","18.web-development\u002F13.authorization-security\u002F10-https-production",{"title":4705,"icon":871,"path":4706,"stem":4707,"children":4708,"page":59},"Комунікація у реальному часі, нотифікації та фонові задачі","\u002Fweb-development\u002Frealtime-notifications-jobs","18.web-development\u002F14.realtime-notifications-jobs",[4709,4713,4717,4721,4725,4729,4732,4735,4739,4743],{"title":4710,"path":4711,"stem":4712},"Методи комунікації у реальному часі","\u002Fweb-development\u002Frealtime-notifications-jobs\u002F01-realtime-communication-methods","18.web-development\u002F14.realtime-notifications-jobs\u002F01-realtime-communication-methods",{"title":4714,"path":4715,"stem":4716},"WebSocket Gateway у NestJS","\u002Fweb-development\u002Frealtime-notifications-jobs\u002F02-websocket-gateway","18.web-development\u002F14.realtime-notifications-jobs\u002F02-websocket-gateway",{"title":4718,"path":4719,"stem":4720},"Lifecycle та Rooms у WebSocket","\u002Fweb-development\u002Frealtime-notifications-jobs\u002F03-websocket-lifecycle-rooms","18.web-development\u002F14.realtime-notifications-jobs\u002F03-websocket-lifecycle-rooms",{"title":4722,"path":4723,"stem":4724},"Server-Sent Events (SSE)","\u002Fweb-development\u002Frealtime-notifications-jobs\u002F04-server-sent-events","18.web-development\u002F14.realtime-notifications-jobs\u002F04-server-sent-events",{"title":4726,"path":4727,"stem":4728},"Архітектура системи нотифікацій","\u002Fweb-development\u002Frealtime-notifications-jobs\u002F05-notification-system","18.web-development\u002F14.realtime-notifications-jobs\u002F05-notification-system",{"title":904,"path":4730,"stem":4731},"\u002Fweb-development\u002Frealtime-notifications-jobs\u002F06-email-notifications","18.web-development\u002F14.realtime-notifications-jobs\u002F06-email-notifications",{"title":900,"path":4733,"stem":4734},"\u002Fweb-development\u002Frealtime-notifications-jobs\u002F07-web-push-notifications","18.web-development\u002F14.realtime-notifications-jobs\u002F07-web-push-notifications",{"title":4736,"path":4737,"stem":4738},"Фонові задачі та черги","\u002Fweb-development\u002Frealtime-notifications-jobs\u002F08-background-jobs-queues","18.web-development\u002F14.realtime-notifications-jobs\u002F08-background-jobs-queues",{"title":4740,"path":4741,"stem":4742},"Планувальники та Cron jobs","\u002Fweb-development\u002Frealtime-notifications-jobs\u002F09-schedulers-cron","18.web-development\u002F14.realtime-notifications-jobs\u002F09-schedulers-cron",{"title":4744,"path":4745,"stem":4746},"Практичні сценарії застосування","\u002Fweb-development\u002Frealtime-notifications-jobs\u002F10-practical-scenarios","18.web-development\u002F14.realtime-notifications-jobs\u002F10-practical-scenarios",{"title":4748,"icon":4749,"path":4750,"stem":4751,"children":4752,"page":59},"Тестування, документування та розгортання застосунків","i-lucide-rocket","\u002Fweb-development\u002Ftesting-documentation-deployment","18.web-development\u002F15.testing-documentation-deployment",[4753,4757,4761,4765,4769,4773,4777,4781,4785,4789],{"title":4754,"path":4755,"stem":4756},"Піраміда тестування та стратегія","\u002Fweb-development\u002Ftesting-documentation-deployment\u002F01-testing-pyramid-strategy","18.web-development\u002F15.testing-documentation-deployment\u002F01-testing-pyramid-strategy",{"title":4758,"path":4759,"stem":4760},"Основи Jest у NestJS","\u002Fweb-development\u002Ftesting-documentation-deployment\u002F02-jest-nestjs-basics","18.web-development\u002F15.testing-documentation-deployment\u002F02-jest-nestjs-basics",{"title":4762,"path":4763,"stem":4764},"Unit-тестування сервісів","\u002Fweb-development\u002Ftesting-documentation-deployment\u002F03-unit-testing-services","18.web-development\u002F15.testing-documentation-deployment\u002F03-unit-testing-services",{"title":4766,"path":4767,"stem":4768},"E2E тестування та покриття коду","\u002Fweb-development\u002Ftesting-documentation-deployment\u002F04-e2e-testing-coverage","18.web-development\u002F15.testing-documentation-deployment\u002F04-e2e-testing-coverage",{"title":4770,"path":4771,"stem":4772},"Swagger, OpenAPI, Scalar та Arazzo","\u002Fweb-development\u002Ftesting-documentation-deployment\u002F05-swagger-openapi","18.web-development\u002F15.testing-documentation-deployment\u002F05-swagger-openapi",{"title":4774,"path":4775,"stem":4776},"Керування конфігурацією","\u002Fweb-development\u002Ftesting-documentation-deployment\u002F06-configuration-management","18.web-development\u002F15.testing-documentation-deployment\u002F06-configuration-management",{"title":4778,"path":4779,"stem":4780},"Docker та контейнеризація","\u002Fweb-development\u002Ftesting-documentation-deployment\u002F07-docker-containerization","18.web-development\u002F15.testing-documentation-deployment\u002F07-docker-containerization",{"title":4782,"path":4783,"stem":4784},"CI\u002FCD Pipelines","\u002Fweb-development\u002Ftesting-documentation-deployment\u002F08-cicd-pipelines","18.web-development\u002F15.testing-documentation-deployment\u002F08-cicd-pipelines",{"title":4786,"path":4787,"stem":4788},"Логування та моніторинг","\u002Fweb-development\u002Ftesting-documentation-deployment\u002F09-logging-monitoring","18.web-development\u002F15.testing-documentation-deployment\u002F09-logging-monitoring",{"title":4790,"path":4791,"stem":4792},"Серіалізація та компресія","\u002Fweb-development\u002Ftesting-documentation-deployment\u002F10-serialization-compression","18.web-development\u002F15.testing-documentation-deployment\u002F10-serialization-compression",{"title":4794,"icon":4795,"path":4796,"stem":4797,"children":4798,"page":59},"AI Core: мислення AI","i-lucide-brain","\u002Fai-core","19.ai-core",[4799,4840,4889,4927],{"title":4800,"icon":4749,"path":4801,"stem":4802,"children":4803,"page":59},"Вхід у професію AI Native Developer","\u002Fai-core\u002Fai-native-developer","19.ai-core\u002F01.ai-native-developer",[4804,4808,4812,4816,4820,4824,4828,4832,4836],{"title":4805,"path":4806,"stem":4807},"Вступ до професії розробника цифрових продуктів","\u002Fai-core\u002Fai-native-developer\u002Fdigital-products","19.ai-core\u002F01.ai-native-developer\u002F01.digital-products",{"title":4809,"path":4810,"stem":4811},"Професія розробника програмного забезпечення","\u002Fai-core\u002Fai-native-developer\u002Fsoftware-developer","19.ai-core\u002F01.ai-native-developer\u002F02.software-developer",{"title":4813,"path":4814,"stem":4815},"Професія AI Native Developer","\u002Fai-core\u002Fai-native-developer\u002Fai-native-developer","19.ai-core\u002F01.ai-native-developer\u002F03.ai-native-developer",{"title":4817,"path":4818,"stem":4819},"Вплив AI на розробку програмного забезпечення","\u002Fai-core\u002Fai-native-developer\u002Fai-impact-on-development","19.ai-core\u002F01.ai-native-developer\u002F04.ai-impact-on-development",{"title":4821,"path":4822,"stem":4823},"Життєвий цикл цифрового продукту","\u002Fai-core\u002Fai-native-developer\u002Fproduct-lifecycle","19.ai-core\u002F01.ai-native-developer\u002F05.product-lifecycle",{"title":4825,"path":4826,"stem":4827},"Застосування AI на етапах створення цифрового продукту","\u002Fai-core\u002Fai-native-developer\u002Fai-in-product-creation","19.ai-core\u002F01.ai-native-developer\u002F06.ai-in-product-creation",{"title":4829,"path":4830,"stem":4831},"Розподіл завдань між людиною та AI","\u002Fai-core\u002Fai-native-developer\u002Fhuman-vs-ai-tasks","19.ai-core\u002F01.ai-native-developer\u002F07.human-vs-ai-tasks",{"title":4833,"path":4834,"stem":4835},"Компетенції AI Native Developer","\u002Fai-core\u002Fai-native-developer\u002Fcompetencies","19.ai-core\u002F01.ai-native-developer\u002F08.competencies",{"title":4837,"path":4838,"stem":4839},"Карта професійного розвитку AI Native Developer","\u002Fai-core\u002Fai-native-developer\u002Fcareer-roadmap","19.ai-core\u002F01.ai-native-developer\u002F09.career-roadmap",{"title":4841,"icon":255,"path":4842,"stem":4843,"children":4844,"page":59},"Штучний інтелект і машинне навчання","\u002Fai-core\u002Fai-and-machine-learning","19.ai-core\u002F02.ai-and-machine-learning",[4845,4849,4853,4857,4861,4865,4869,4873,4877,4881,4885],{"title":4846,"path":4847,"stem":4848},"Штучний інтелект","\u002Fai-core\u002Fai-and-machine-learning\u002Fwhat-is-ai","19.ai-core\u002F02.ai-and-machine-learning\u002F01.what-is-ai",{"title":4850,"path":4851,"stem":4852},"Відмінність AI від інших програмних систем","\u002Fai-core\u002Fai-and-machine-learning\u002Fai-vs-automation","19.ai-core\u002F02.ai-and-machine-learning\u002F02.ai-vs-automation",{"title":4854,"path":4855,"stem":4856},"Завдання штучного інтелекту","\u002Fai-core\u002Fai-and-machine-learning\u002Fai-tasks","19.ai-core\u002F02.ai-and-machine-learning\u002F03.ai-tasks",{"title":4858,"path":4859,"stem":4860},"Machine Learning","\u002Fai-core\u002Fai-and-machine-learning\u002Fmachine-learning","19.ai-core\u002F02.ai-and-machine-learning\u002F04.machine-learning",{"title":4862,"path":4863,"stem":4864},"Machine Learning Workflow","\u002Fai-core\u002Fai-and-machine-learning\u002Fml-workflow","19.ai-core\u002F02.ai-and-machine-learning\u002F05.ml-workflow",{"title":4866,"path":4867,"stem":4868},"Типи машинного навчання","\u002Fai-core\u002Fai-and-machine-learning\u002Fml-types","19.ai-core\u002F02.ai-and-machine-learning\u002F06.ml-types",{"title":4870,"path":4871,"stem":4872},"Generative AI","\u002Fai-core\u002Fai-and-machine-learning\u002Fgenerative-ai","19.ai-core\u002F02.ai-and-machine-learning\u002F07.generative-ai",{"title":4874,"path":4875,"stem":4876},"Типи контенту, створюваного AI","\u002Fai-core\u002Fai-and-machine-learning\u002Fai-content-types","19.ai-core\u002F02.ai-and-machine-learning\u002F08.ai-content-types",{"title":4878,"path":4879,"stem":4880},"Великі мовні моделі","\u002Fai-core\u002Fai-and-machine-learning\u002Flarge-language-models","19.ai-core\u002F02.ai-and-machine-learning\u002F09.large-language-models",{"title":4882,"path":4883,"stem":4884},"Можливості та обмеження Generative AI","\u002Fai-core\u002Fai-and-machine-learning\u002Fcapabilities-and-limitations","19.ai-core\u002F02.ai-and-machine-learning\u002F10.capabilities-and-limitations",{"title":4886,"path":4887,"stem":4888},"Сучасні AI-асистенти","\u002Fai-core\u002Fai-and-machine-learning\u002Fmodern-ai-assistants","19.ai-core\u002F02.ai-and-machine-learning\u002F11.modern-ai-assistants",{"title":4890,"icon":4891,"path":4892,"stem":4893,"children":4894,"page":59},"Постановка завдань для AI: принципи створення промптів","i-lucide-message-square-text","\u002Fai-core\u002Fprompt-engineering","19.ai-core\u002F03.prompt-engineering",[4895,4899,4903,4907,4911,4915,4919,4923],{"title":4896,"path":4897,"stem":4898},"Промпт як інструмент","\u002Fai-core\u002Fprompt-engineering\u002Fprompt-as-tool","19.ai-core\u002F03.prompt-engineering\u002F01.prompt-as-tool",{"title":4900,"path":4901,"stem":4902},"Принципи створення промптів","\u002Fai-core\u002Fprompt-engineering\u002Fprompt-principles","19.ai-core\u002F03.prompt-engineering\u002F02.prompt-principles",{"title":4904,"path":4905,"stem":4906},"Структура промпта","\u002Fai-core\u002Fprompt-engineering\u002Fprompt-structure","19.ai-core\u002F03.prompt-engineering\u002F03.prompt-structure",{"title":4908,"path":4909,"stem":4910},"Види промптів за типом задачі","\u002Fai-core\u002Fprompt-engineering\u002Fprompt-types","19.ai-core\u002F03.prompt-engineering\u002F04.prompt-types",{"title":4912,"path":4913,"stem":4914},"Прийоми посилення промпта","\u002Fai-core\u002Fprompt-engineering\u002Fprompt-enhancement","19.ai-core\u002F03.prompt-engineering\u002F05.prompt-enhancement",{"title":4916,"path":4917,"stem":4918},"Розбір і покращення промпта","\u002Fai-core\u002Fprompt-engineering\u002Fprompt-analysis","19.ai-core\u002F03.prompt-engineering\u002F06.prompt-analysis",{"title":4920,"path":4921,"stem":4922},"Типові помилки під час створення промптів","\u002Fai-core\u002Fprompt-engineering\u002Fcommon-mistakes","19.ai-core\u002F03.prompt-engineering\u002F07.common-mistakes",{"title":4924,"path":4925,"stem":4926},"Практикум: особиста бібліотека промптів","\u002Fai-core\u002Fprompt-engineering\u002Fprompt-library","19.ai-core\u002F03.prompt-engineering\u002F08.prompt-library",{"title":4928,"icon":793,"path":4929,"stem":4930,"children":4931,"page":59},"Перевірка результатів, безпека та AI Native Workflow","\u002Fai-core\u002Fverification-security-workflow","19.ai-core\u002F04.verification-security-workflow",[4932,4936,4940,4944,4948,4952,4956,4960,4964],{"title":4933,"path":4934,"stem":4935},"Межі можливостей AI","\u002Fai-core\u002Fverification-security-workflow\u002Fai-limitations","19.ai-core\u002F04.verification-security-workflow\u002F01.ai-limitations",{"title":4937,"path":4938,"stem":4939},"Галюцинації AI","\u002Fai-core\u002Fverification-security-workflow\u002Fai-hallucinations","19.ai-core\u002F04.verification-security-workflow\u002F02.ai-hallucinations",{"title":4941,"path":4942,"stem":4943},"Критичне мислення під час роботи з AI","\u002Fai-core\u002Fverification-security-workflow\u002Fcritical-thinking","19.ai-core\u002F04.verification-security-workflow\u002F03.critical-thinking",{"title":4945,"path":4946,"stem":4947},"Перевірка результатів роботи AI","\u002Fai-core\u002Fverification-security-workflow\u002Fresult-verification","19.ai-core\u002F04.verification-security-workflow\u002F04.result-verification",{"title":4949,"path":4950,"stem":4951},"Захист даних під час роботи з AI","\u002Fai-core\u002Fverification-security-workflow\u002Fdata-protection","19.ai-core\u002F04.verification-security-workflow\u002F05.data-protection",{"title":4953,"path":4954,"stem":4955},"Авторське право та навчальна доброчесність","\u002Fai-core\u002Fverification-security-workflow\u002Fcopyright-integrity","19.ai-core\u002F04.verification-security-workflow\u002F06.copyright-integrity",{"title":4957,"path":4958,"stem":4959},"Відповідальне використання AI","\u002Fai-core\u002Fverification-security-workflow\u002Fresponsible-use","19.ai-core\u002F04.verification-security-workflow\u002F07.responsible-use",{"title":4961,"path":4962,"stem":4963},"AI Native Workflow","\u002Fai-core\u002Fverification-security-workflow\u002Fai-native-workflow","19.ai-core\u002F04.verification-security-workflow\u002F08.ai-native-workflow",{"title":4965,"path":4966,"stem":4967},"Застосування AI Native Workflow під час створення цифрового продукту","\u002Fai-core\u002Fverification-security-workflow\u002Fworkflow-in-practice","19.ai-core\u002F04.verification-security-workflow\u002F09.workflow-in-practice",{"title":4969,"icon":4970,"path":4971,"stem":4972,"children":4973,"page":59},"AI Developer: Vibe Coding Sprint","i-lucide-code-xml","\u002Fai-developer","20.ai-developer",[4974,5000,5022,5043],{"title":4975,"icon":4976,"path":4977,"stem":4978,"children":4979,"page":59},"Проєктування і чорнова версія","i-lucide-pencil-ruler","\u002Fai-developer\u002Fdraft-creation","20.ai-developer\u002F01.draft-creation",[4980,4984,4988,4992,4996],{"title":4981,"path":4982,"stem":4983},"Парадигма Vibe Coding Workflow","\u002Fai-developer\u002Fdraft-creation\u002Fvibe-coding-workflow","20.ai-developer\u002F01.draft-creation\u002F01.vibe-coding-workflow",{"title":4985,"path":4986,"stem":4987},"AI-середовище швидкої розробки","\u002Fai-developer\u002Fdraft-creation\u002Fai-ide-environment","20.ai-developer\u002F01.draft-creation\u002F02.ai-ide-environment",{"title":4989,"path":4990,"stem":4991},"Користувацьке завдання та межі першої версії","\u002Fai-developer\u002Fdraft-creation\u002Fproduct-scope-and-mvp","20.ai-developer\u002F01.draft-creation\u002F03.product-scope-and-mvp",{"title":4993,"path":4994,"stem":4995},"Архітектура інтерфейсу та стани застосунку","\u002Fai-developer\u002Fdraft-creation\u002Fapplication-architecture-and-states","20.ai-developer\u002F01.draft-creation\u002F04.application-architecture-and-states",{"title":4997,"path":4998,"stem":4999},"Генерація та фіксація чорнової версії","\u002Fai-developer\u002Fdraft-creation\u002Fgenerating-draft-version","20.ai-developer\u002F01.draft-creation\u002F05.generating-draft-version",{"title":5001,"icon":5002,"path":5003,"stem":5004,"children":5005,"page":59},"Перша робоча версія","i-lucide-play-circle","\u002Fai-developer\u002Ffirst-working-version","20.ai-developer\u002F02.first-working-version",[5006,5010,5014,5018],{"title":5007,"path":5008,"stem":5009},"Уточнення контексту проєкту для AI","\u002Fai-developer\u002Ffirst-working-version\u002Frefining-project-context","20.ai-developer\u002F02.first-working-version\u002F01.refining-project-context",{"title":5011,"path":5012,"stem":5013},"Стратегія завдань для AI-розробки","\u002Fai-developer\u002Ffirst-working-version\u002Fai-prompting-strategy","20.ai-developer\u002F02.first-working-version\u002F02.ai-prompting-strategy",{"title":5015,"path":5016,"stem":5017},"Доопрацювання до першої робочої версії","\u002Fai-developer\u002Ffirst-working-version\u002Frefining-to-working-version","20.ai-developer\u002F02.first-working-version\u002F03.refining-to-working-version",{"title":5019,"path":5020,"stem":5021},"Перевірка першої робочої версії","\u002Fai-developer\u002Ffirst-working-version\u002Ftesting-first-working-version","20.ai-developer\u002F02.first-working-version\u002F04.testing-first-working-version",{"title":5023,"icon":3072,"path":5024,"stem":5025,"children":5026,"page":59},"Ітерації, дебагінг та відновлення","\u002Fai-developer\u002Fiteration-debugging-recovery","20.ai-developer\u002F03.iteration-debugging-recovery",[5027,5031,5035,5039],{"title":5028,"path":5029,"stem":5030},"Управління точковими змінами продукту","\u002Fai-developer\u002Fiteration-debugging-recovery\u002Fchange-management","20.ai-developer\u002F03.iteration-debugging-recovery\u002F01.change-management",{"title":5032,"path":5033,"stem":5034},"Надійність інтерфейсу та обробка помилок","\u002Fai-developer\u002Fiteration-debugging-recovery\u002Fscenario-reliability-and-errors","20.ai-developer\u002F03.iteration-debugging-recovery\u002F02.scenario-reliability-and-errors",{"title":5036,"path":5037,"stem":5038},"Керування контекстом і збереження версій","\u002Fai-developer\u002Fiteration-debugging-recovery\u002Fproject-context-and-versions","20.ai-developer\u002F03.iteration-debugging-recovery\u002F03.project-context-and-versions",{"title":5040,"path":5041,"stem":5042},"Збої AI-розробки, ліміти та аварійне відновлення","\u002Fai-developer\u002Fiteration-debugging-recovery\u002Fai-breakdowns-and-recovery","20.ai-developer\u002F03.iteration-debugging-recovery\u002F04.ai-breakdowns-and-recovery",{"title":5044,"icon":5045,"path":5046,"stem":5047,"children":5048,"page":59},"Перевірка та завершення продукту","i-lucide-check-check","\u002Fai-developer\u002Fverification-and-completion","20.ai-developer\u002F04.verification-and-completion",[5049,5053,5057],{"title":5050,"path":5051,"stem":5052},"Комплексний аудит та підсумкове тестування","\u002Fai-developer\u002Fverification-and-completion\u002Ffinal-product-testing","20.ai-developer\u002F04.verification-and-completion\u002F01.final-product-testing",{"title":5054,"path":5055,"stem":5056},"Підготовка підсумкової версії коду","\u002Fai-developer\u002Fverification-and-completion\u002Fpreparing-release-version","20.ai-developer\u002F04.verification-and-completion\u002F02.preparing-release-version",{"title":5058,"path":5059,"stem":5060},"Демонстрація, публікація та критерії оцінювання","\u002Fai-developer\u002Fverification-and-completion\u002Fproduct-showcase-and-publishing","20.ai-developer\u002F04.verification-and-completion\u002F03.product-showcase-and-publishing",{"title":5062,"path":5063,"stem":5064,"children":5065,"page":59},"Tailwind","\u002Ftailwind","21.tailwind",[5066,5070,5074,5078,5082,5086,5090,5094,5098,5102,5106,5110],{"title":5067,"path":5068,"stem":5069},"Що таке Tailwind CSS і навіщо він потрібен","\u002Ftailwind\u002Ftailwind-intro-philosophy","21.tailwind\u002F01.tailwind-intro-philosophy",{"title":5071,"path":5072,"stem":5073},"Встановлення та налаштування Tailwind CSS v4","\u002Ftailwind\u002Ftailwind-installation-setup","21.tailwind\u002F02.tailwind-installation-setup",{"title":5075,"path":5076,"stem":5077},"Utility-класи: основи та система Tailwind","\u002Ftailwind\u002Ftailwind-utility-classes-core","21.tailwind\u002F03.tailwind-utility-classes-core",{"title":5079,"path":5080,"stem":5081},"Layout: Flexbox та Grid через Tailwind","\u002Ftailwind\u002Ftailwind-flexbox-grid","21.tailwind\u002F04.tailwind-flexbox-grid",{"title":5083,"path":5084,"stem":5085},"Кастомізація теми через @theme у Tailwind v4","\u002Ftailwind\u002Ftailwind-theme-customization","21.tailwind\u002F05.tailwind-theme-customization",{"title":5087,"path":5088,"stem":5089},"Варіанти: hover, focus, responsive, dark mode та нові v4","\u002Ftailwind\u002Ftailwind-variants-states","21.tailwind\u002F06.tailwind-variants-states",{"title":5091,"path":5092,"stem":5093},"Типографіка та система кольорів у Tailwind v4","\u002Ftailwind\u002Ftailwind-typography-colors","21.tailwind\u002F07.tailwind-typography-colors",{"title":5095,"path":5096,"stem":5097},"Компоненти та повторюваність: @apply, @utility та патерни","\u002Ftailwind\u002Ftailwind-components-patterns","21.tailwind\u002F08.tailwind-components-patterns",{"title":5099,"path":5100,"stem":5101},"Темна тема та система дизайн-токенів у Tailwind v4","\u002Ftailwind\u002Ftailwind-dark-mode-theming","21.tailwind\u002F09.tailwind-dark-mode-theming",{"title":5103,"path":5104,"stem":5105},"Довільні значення та контейнерні запити у Tailwind v4","\u002Ftailwind\u002Ftailwind-arbitrary-container-queries","21.tailwind\u002F10.tailwind-arbitrary-container-queries",{"title":5107,"path":5108,"stem":5109},"Анімації, трансформації та 3D у Tailwind v4","\u002Ftailwind\u002Ftailwind-animations-transforms","21.tailwind\u002F11.tailwind-animations-transforms",{"title":5111,"path":5112,"stem":5113},"Tailwind CLI, PostCSS та інтеграція з фреймворками","\u002Ftailwind\u002Ftailwind-cli-tooling","21.tailwind\u002F12.tailwind-cli-tooling",{"title":5115,"path":5116,"stem":5117,"children":5118,"page":59},"React Typescript","\u002Freact-typescript","22.react-typescript",[5119,5144,5148,5152,5156,5160,5164,5168,5172],{"title":5120,"path":5121,"stem":5122,"children":5123,"page":59},"From Html To Project","\u002Freact-typescript\u002Ffrom-html-to-project","22.react-typescript\u002F01.from-html-to-project",[5124,5128,5132,5136,5140],{"title":5125,"path":5126,"stem":5127},"Від одного HTML-файлу до повноцінного проєкту","\u002Freact-typescript\u002Ffrom-html-to-project\u002Fevolution","22.react-typescript\u002F01.from-html-to-project\u002F01.evolution",{"title":5129,"path":5130,"stem":5131},"Що таке React","\u002Freact-typescript\u002Ffrom-html-to-project\u002Fwhat-is-react","22.react-typescript\u002F01.from-html-to-project\u002F02.what-is-react",{"title":5133,"path":5134,"stem":5135},"JSX та TSX — синтаксичний цукор, а не магія","\u002Freact-typescript\u002Ffrom-html-to-project\u002Fjsx-tsx-syntax","22.react-typescript\u002F01.from-html-to-project\u002F03.jsx-tsx-syntax",{"title":5137,"path":5138,"stem":5139},"Компоненти — будівельні блоки інтерфейсу","\u002Freact-typescript\u002Ffrom-html-to-project\u002Fcomponents","22.react-typescript\u002F01.from-html-to-project\u002F04.components",{"title":5141,"path":5142,"stem":5143},"JSX — розмітка всередині TypeScript","\u002Freact-typescript\u002Ffrom-html-to-project\u002Fjsx-expressions","22.react-typescript\u002F01.from-html-to-project\u002F05.jsx-expressions",{"title":5145,"path":5146,"stem":5147},"Хук useReducer: структуроване управління складним станом","\u002Freact-typescript\u002Fuse-reducer","22.react-typescript\u002F23.use-reducer",{"title":5149,"path":5150,"stem":5151},"Context API: передавання даних без «пропс-дрілінгу»","\u002Freact-typescript\u002Fcontext-api","22.react-typescript\u002F24.context-api",{"title":5153,"path":5154,"stem":5155},"Синхронізація з ефектами: хук useEffect та функції очищення","\u002Freact-typescript\u002Fuse-effect","22.react-typescript\u002F27.use-effect",{"title":5157,"path":5158,"stem":5159},"Можливо, вам не потрібен ефект: антипатерни та альтернативи","\u002Freact-typescript\u002Fyou-might-not-need-effect","22.react-typescript\u002F28.you-might-not-need-effect",{"title":5161,"path":5162,"stem":5163},"Життєвий цикл реактивних ефектів: мислення процесами синхронізації","\u002Freact-typescript\u002Flifecycle-of-reactive-effects","22.react-typescript\u002F29.lifecycle-of-reactive-effects",{"title":5165,"path":5166,"stem":5167},"Розділення подій та ефектів: реактивна проти нереактивної логіки","\u002Freact-typescript\u002Fseparating-events-from-effects","22.react-typescript\u002F30.separating-events-from-effects",{"title":5169,"path":5170,"stem":5171},"Видалення зайвих залежностей ефектів та оптимізація синхронізації","\u002Freact-typescript\u002Fremoving-effect-dependencies","22.react-typescript\u002F31.removing-effect-dependencies",{"title":5173,"path":5174,"stem":5175},"Кастомні хуки: повторне використання та композиція реактивної логіки","\u002Freact-typescript\u002Fcustom-hooks","22.react-typescript\u002F32.custom-hooks",{"title":5177,"path":5178,"stem":5179},"Тестування компонентів діаграм та пісочниць","\u002Ftest-components","98.test-components",{"id":5181,"title":4025,"body":5182,"description":46782,"extension":46783,"links":46784,"meta":46785,"navigation":5312,"path":4026,"seo":46792,"stem":4027,"__hash__":46793},"docs\u002F17.client-server-architecture\u002F10.layered-architecture-and-middleware-pipelines.md",{"type":5183,"value":5184,"toc":46635},"minimark",[5185,5253,5256,5261,5265,5270,5402,5409,5599,5603,5711,5728,5730,5734,5738,5741,6904,6909,6915,6930,6933,6955,6961,6963,6967,6971,6974,7264,7267,7282,7286,7289,7507,7510,7602,7619,7621,7625,7629,7641,7677,7905,7909,7912,7945,7948,7966,7976,7978,7982,7986,7991,8028,8035,8159,8164,8248,8251,8341,8361,8365,8368,8554,8559,8608,8612,8615,8860,8865,8887,8892,8908,8913,8929,8942,8944,8948,8951,9305,9309,9322,10009,10025,10029,10393,10398,10431,10436,10455,10458,10460,10464,10475,10757,10762,10792,10796,11130,11138,11154,11159,11194,11208,11210,11214,11218,11221,11276,11279,11456,11462,11570,11573,11641,11652,11654,11658,11662,11670,11676,11684,11690,11694,11704,11756,11759,11817,11821,12850,12854,13185,13190,13546,13549,13551,13555,13559,13568,13579,13582,13602,13606,13612,13625,14335,14339,14677,14682,14712,14724,14726,14730,14734,14741,14746,14757,14765,14769,14774,14794,14800,14821,14825,14832,14926,14936,14940,14943,15586,15590,16183,16187,16238,16330,16332,16336,16340,16345,16361,16365,16368,17175,17337,17344,17346,17350,17353,19536,19539,19541,19545,19549,19552,19971,19976,20008,20012,20015,20508,20511,20843,20849,20864,20866,20870,20874,20887,20907,20913,20917,20931,21154,21158,21167,21170,21184,21619,21630,21638,21644,21658,21665,21940,21943,22805,22810,22830,22838,22845,22850,23707,23712,24918,24924,24932,24935,24946,26277,26300,26302,26306,26322,27008,27169,27174,27200,27206,27208,27212,27216,27219,27245,27251,27254,27259,27273,27277,27987,27992,28057,28061,28066,28803,28806,30196,30250,30254,30257,32012,32058,32076,32078,32082,32086,32307,32312,32378,32393,32395,32399,32518,32522,32527,32544,32549,32563,32569,32571,32575,32582,32699,32702,32706,32788,32792,32798,33040,33044,33047,33228,33232,33237,33251,33256,33270,33272,33276,33761,33763,33767,33771,33776,34072,34139,34143,34146,34167,34170,34384,34386,34390,34393,34397,34400,34569,34573,34576,34851,34855,34860,34893,34898,34947,34951,35188,35192,35225,35264,35266,35270,35274,35288,35292,35295,35561,35565,35568,35895,35899,35905,36131,36135,36763,36767,37677,37681,37684,38279,38303,38305,38309,38313,38316,38320,38676,38680,39047,39051,39604,39608,39617,39622,39767,39772,39978,39982,40450,40455,40504,40508,40511,41211,41213,41217,41221,41225,41340,41344,41530,41533,41537,41541,41649,41653,41787,41791,41795,41897,41901,42032,42036,42040,42154,42158,42240,42244,42248,42346,42350,42557,42561,42565,42589,42593,42666,42670,42674,42712,42716,42778,42782,42786,42871,42875,42972,42974,42978,42982,42991,43527,43531,43534,43965,43968,44253,44257,44260,44493,44498,44661,44665,45573,45577,46219,46221,46225,46228,46427,46429,46433,46436,46440,46445,46471,46477,46481,46485,46507,46512,46515,46531,46533,46631],[5186,5187,5188,5219],"card-group",{},[5189,5190,5193],"card",{"icon":5191,"title":5192},"i-lucide-target","🎯 Мета лекції",[5194,5195,5196,5200,5203,5211],"ul",{},[5197,5198,5199],"li",{},"Опанувати концепцію Middleware як конвеєрної обробки HTTP-запитів у мові Go.",[5197,5201,5202],{},"Зрозуміти принципи шаруватої архітектури та Clean Architecture для серверних застосунків.",[5197,5204,5205,5206,5210],{},"Навчитися застосовувати ручне впровадження залежностей (",[5207,5208,5209],"em",{},"Dependency Injection",") для слабкої зв'язності компонентів.",[5197,5212,5213,5214,5218],{},"Набути практичних навичок модульного тестування HTTP-шару за допомогою пакета ",[5215,5216,5217],"code",{},"net\u002Fhttp\u002Fhttptest",".",[5189,5220,5222],{"icon":4618,"title":5221},"🔑 Ключові терміни",[5194,5223,5224,5235,5241,5247],{},[5197,5225,5226,5230,5231,5234],{},[5227,5228,5229],"strong",{},"Middleware:"," проміжний обробник запиту, що виконується перед основним ",[5207,5232,5233],{},"handler"," та може модифікувати запит, відповідь або припинити подальшу обробку.",[5197,5236,5237,5240],{},[5227,5238,5239],{},"Clean Architecture:"," архітектурний підхід, що ізолює бізнес-логіку від зовнішніх деталей доставки даних (HTTP, БД, UI).",[5197,5242,5243,5246],{},[5227,5244,5245],{},"Dependency Injection (DI):"," патерн передачі залежностей ззовні замість створення їх усередині компонента.",[5197,5248,5249,5252],{},[5227,5250,5251],{},"Repository Pattern:"," абстракція доступу до даних через інтерфейси, що дозволяє змінювати джерело даних без зміни бізнес-логіки.",[5254,5255],"hr",{},[5257,5258,5260],"h2",{"id":5259},"налаштування-проєкту-з-нуля","Налаштування проєкту з нуля",[5262,5263,5264],"p",{},"Перш ніж почати писати код, створимо правильну структуру Go-проєкту.",[5266,5267,5269],"h3",{"id":5268},"ініціалізація-go-модуля","Ініціалізація Go-модуля",[5271,5272,5277],"pre",{"className":5273,"code":5274,"language":5275,"meta":5276,"style":5276},"language-bash shiki shiki-themes light-plus dark-plus dark-plus","# Створюємо теку проєкту\nmkdir myapp\ncd myapp\n\n# Ініціалізуємо Go-модуль\ngo mod init myapp\n\n# Встановлюємо необхідні залежності\ngo get github.com\u002Fgo-chi\u002Fchi\u002Fv5\ngo get golang.org\u002Fx\u002Ftime\u002Frate\ngo get golang.org\u002Fx\u002Fcrypto\u002Fbcrypt\ngo get github.com\u002Flib\u002Fpq  # PostgreSQL драйвер\ngo get github.com\u002Fgoogle\u002Fuuid  # UUID generation\n","bash","",[5215,5278,5279,5288,5299,5307,5314,5320,5334,5339,5345,5356,5366,5376,5389],{"__ignoreMap":5276},[5280,5281,5284],"span",{"class":5282,"line":5283},"line",1,[5280,5285,5287],{"class":5286},"spJ8K","# Створюємо теку проєкту\n",[5280,5289,5291,5295],{"class":5282,"line":5290},2,[5280,5292,5294],{"class":5293},"s8Opu","mkdir",[5280,5296,5298],{"class":5297},"sbdoH"," myapp\n",[5280,5300,5302,5305],{"class":5282,"line":5301},3,[5280,5303,5304],{"class":5293},"cd",[5280,5306,5298],{"class":5297},[5280,5308,5310],{"class":5282,"line":5309},4,[5280,5311,5313],{"emptyLinePlaceholder":5312},true,"\n",[5280,5315,5317],{"class":5282,"line":5316},5,[5280,5318,5319],{"class":5286},"# Ініціалізуємо Go-модуль\n",[5280,5321,5323,5326,5329,5332],{"class":5282,"line":5322},6,[5280,5324,5325],{"class":5293},"go",[5280,5327,5328],{"class":5297}," mod",[5280,5330,5331],{"class":5297}," init",[5280,5333,5298],{"class":5297},[5280,5335,5337],{"class":5282,"line":5336},7,[5280,5338,5313],{"emptyLinePlaceholder":5312},[5280,5340,5342],{"class":5282,"line":5341},8,[5280,5343,5344],{"class":5286},"# Встановлюємо необхідні залежності\n",[5280,5346,5348,5350,5353],{"class":5282,"line":5347},9,[5280,5349,5325],{"class":5293},[5280,5351,5352],{"class":5297}," get",[5280,5354,5355],{"class":5297}," github.com\u002Fgo-chi\u002Fchi\u002Fv5\n",[5280,5357,5359,5361,5363],{"class":5282,"line":5358},10,[5280,5360,5325],{"class":5293},[5280,5362,5352],{"class":5297},[5280,5364,5365],{"class":5297}," golang.org\u002Fx\u002Ftime\u002Frate\n",[5280,5367,5369,5371,5373],{"class":5282,"line":5368},11,[5280,5370,5325],{"class":5293},[5280,5372,5352],{"class":5297},[5280,5374,5375],{"class":5297}," golang.org\u002Fx\u002Fcrypto\u002Fbcrypt\n",[5280,5377,5379,5381,5383,5386],{"class":5282,"line":5378},12,[5280,5380,5325],{"class":5293},[5280,5382,5352],{"class":5297},[5280,5384,5385],{"class":5297}," github.com\u002Flib\u002Fpq",[5280,5387,5388],{"class":5286},"  # PostgreSQL драйвер\n",[5280,5390,5392,5394,5396,5399],{"class":5282,"line":5391},13,[5280,5393,5325],{"class":5293},[5280,5395,5352],{"class":5297},[5280,5397,5398],{"class":5297}," github.com\u002Fgoogle\u002Fuuid",[5280,5400,5401],{"class":5286},"  # UUID generation\n",[5262,5403,5404,5405,5408],{},"Після виконання цих команд з'явиться файл ",[5215,5406,5407],{},"go.mod",":",[5271,5410,5413],{"className":5411,"code":5412,"language":5325,"meta":5276,"style":5276},"language-go shiki shiki-themes light-plus dark-plus dark-plus","\u002F\u002F go.mod\nmodule myapp\n\ngo 1.21\n\nrequire (\n    github.com\u002Fgo-chi\u002Fchi\u002Fv5 v5.0.11\n    github.com\u002Fgoogle\u002Fuuid v1.5.0\n    github.com\u002Flib\u002Fpq v1.10.9\n    golang.org\u002Fx\u002Fcrypto v0.17.0\n    golang.org\u002Fx\u002Ftime v0.5.0\n)\n",[5215,5414,5415,5420,5428,5432,5441,5445,5454,5492,5518,5543,5571,5594],{"__ignoreMap":5276},[5280,5416,5417],{"class":5282,"line":5283},[5280,5418,5419],{"class":5286},"\u002F\u002F go.mod\n",[5280,5421,5422,5426],{"class":5282,"line":5290},[5280,5423,5425],{"class":5424},"siwwj","module",[5280,5427,5298],{"class":5424},[5280,5429,5430],{"class":5282,"line":5301},[5280,5431,5313],{"emptyLinePlaceholder":5312},[5280,5433,5434,5437],{"class":5282,"line":5309},[5280,5435,5325],{"class":5436},"s8xlr",[5280,5438,5440],{"class":5439},"sJj4R"," 1.21\n",[5280,5442,5443],{"class":5282,"line":5316},[5280,5444,5313],{"emptyLinePlaceholder":5312},[5280,5446,5447,5450],{"class":5282,"line":5322},[5280,5448,5449],{"class":5424},"require",[5280,5451,5453],{"class":5452},"sHH4Y"," (\n",[5280,5455,5456,5459,5461,5464,5467,5469,5472,5475,5477,5479,5481,5484,5487,5489],{"class":5282,"line":5336},[5280,5457,5458],{"class":5424},"    github",[5280,5460,5218],{"class":5452},[5280,5462,5463],{"class":5424},"com",[5280,5465,5466],{"class":5452},"\u002F",[5280,5468,5325],{"class":5436},[5280,5470,5471],{"class":5452},"-",[5280,5473,5474],{"class":5424},"chi",[5280,5476,5466],{"class":5452},[5280,5478,5474],{"class":5424},[5280,5480,5466],{"class":5452},[5280,5482,5483],{"class":5424},"v5",[5280,5485,5486],{"class":5424}," v5",[5280,5488,5218],{"class":5452},[5280,5490,5491],{"class":5439},"0.11\n",[5280,5493,5494,5496,5498,5500,5502,5505,5507,5510,5513,5515],{"class":5282,"line":5341},[5280,5495,5458],{"class":5424},[5280,5497,5218],{"class":5452},[5280,5499,5463],{"class":5424},[5280,5501,5466],{"class":5452},[5280,5503,5504],{"class":5424},"google",[5280,5506,5466],{"class":5452},[5280,5508,5509],{"class":5424},"uuid",[5280,5511,5512],{"class":5424}," v1",[5280,5514,5218],{"class":5452},[5280,5516,5517],{"class":5439},"5.0\n",[5280,5519,5520,5522,5524,5526,5528,5531,5533,5536,5538,5540],{"class":5282,"line":5347},[5280,5521,5458],{"class":5424},[5280,5523,5218],{"class":5452},[5280,5525,5463],{"class":5424},[5280,5527,5466],{"class":5452},[5280,5529,5530],{"class":5424},"lib",[5280,5532,5466],{"class":5452},[5280,5534,5535],{"class":5424},"pq",[5280,5537,5512],{"class":5424},[5280,5539,5218],{"class":5452},[5280,5541,5542],{"class":5439},"10.9\n",[5280,5544,5545,5548,5550,5553,5555,5558,5560,5563,5566,5568],{"class":5282,"line":5358},[5280,5546,5547],{"class":5424},"    golang",[5280,5549,5218],{"class":5452},[5280,5551,5552],{"class":5424},"org",[5280,5554,5466],{"class":5452},[5280,5556,5557],{"class":5424},"x",[5280,5559,5466],{"class":5452},[5280,5561,5562],{"class":5424},"crypto",[5280,5564,5565],{"class":5424}," v0",[5280,5567,5218],{"class":5452},[5280,5569,5570],{"class":5439},"17.0\n",[5280,5572,5573,5575,5577,5579,5581,5583,5585,5588,5590,5592],{"class":5282,"line":5368},[5280,5574,5547],{"class":5424},[5280,5576,5218],{"class":5452},[5280,5578,5552],{"class":5424},[5280,5580,5466],{"class":5452},[5280,5582,5557],{"class":5424},[5280,5584,5466],{"class":5452},[5280,5586,5587],{"class":5424},"time",[5280,5589,5565],{"class":5424},[5280,5591,5218],{"class":5452},[5280,5593,5517],{"class":5439},[5280,5595,5596],{"class":5282,"line":5378},[5280,5597,5598],{"class":5452},")\n",[5266,5600,5602],{"id":5601},"створення-базової-структури-каталогів","Створення базової структури каталогів",[5271,5604,5606],{"className":5273,"code":5605,"language":5275,"meta":5276,"style":5276},"# Створюємо структуру проєкту\nmkdir -p cmd\u002Fserver\nmkdir -p internal\u002F{domain,usecase,repository\u002F{memory,postgres},delivery\u002Fhttp}\nmkdir -p middleware\nmkdir -p migrations\n\n# Створюємо основні файли\ntouch cmd\u002Fserver\u002Fmain.go\ntouch middleware\u002F{logger,recovery,cors,ratelimit,auth}.go\ntouch internal\u002Fdomain\u002F{user,user_repository,errors}.go\ntouch internal\u002Fusecase\u002Fuser_usecase.go\ntouch internal\u002Frepository\u002Fmemory\u002Fuser_repository.go\ntouch internal\u002Frepository\u002Fpostgres\u002Fuser_repository.go\ntouch internal\u002Fdelivery\u002Fhttp\u002Fuser_handler.go\n",[5215,5607,5608,5613,5624,5633,5642,5651,5655,5660,5668,5675,5682,5689,5696,5703],{"__ignoreMap":5276},[5280,5609,5610],{"class":5282,"line":5283},[5280,5611,5612],{"class":5286},"# Створюємо структуру проєкту\n",[5280,5614,5615,5617,5621],{"class":5282,"line":5290},[5280,5616,5294],{"class":5293},[5280,5618,5620],{"class":5619},"su1O8"," -p",[5280,5622,5623],{"class":5297}," cmd\u002Fserver\n",[5280,5625,5626,5628,5630],{"class":5282,"line":5301},[5280,5627,5294],{"class":5293},[5280,5629,5620],{"class":5619},[5280,5631,5632],{"class":5297}," internal\u002F{domain,usecase,repository\u002F{memory,postgres},delivery\u002Fhttp}\n",[5280,5634,5635,5637,5639],{"class":5282,"line":5309},[5280,5636,5294],{"class":5293},[5280,5638,5620],{"class":5619},[5280,5640,5641],{"class":5297}," middleware\n",[5280,5643,5644,5646,5648],{"class":5282,"line":5316},[5280,5645,5294],{"class":5293},[5280,5647,5620],{"class":5619},[5280,5649,5650],{"class":5297}," migrations\n",[5280,5652,5653],{"class":5282,"line":5322},[5280,5654,5313],{"emptyLinePlaceholder":5312},[5280,5656,5657],{"class":5282,"line":5336},[5280,5658,5659],{"class":5286},"# Створюємо основні файли\n",[5280,5661,5662,5665],{"class":5282,"line":5341},[5280,5663,5664],{"class":5293},"touch",[5280,5666,5667],{"class":5297}," cmd\u002Fserver\u002Fmain.go\n",[5280,5669,5670,5672],{"class":5282,"line":5347},[5280,5671,5664],{"class":5293},[5280,5673,5674],{"class":5297}," middleware\u002F{logger,recovery,cors,ratelimit,auth}.go\n",[5280,5676,5677,5679],{"class":5282,"line":5358},[5280,5678,5664],{"class":5293},[5280,5680,5681],{"class":5297}," internal\u002Fdomain\u002F{user,user_repository,errors}.go\n",[5280,5683,5684,5686],{"class":5282,"line":5368},[5280,5685,5664],{"class":5293},[5280,5687,5688],{"class":5297}," internal\u002Fusecase\u002Fuser_usecase.go\n",[5280,5690,5691,5693],{"class":5282,"line":5378},[5280,5692,5664],{"class":5293},[5280,5694,5695],{"class":5297}," internal\u002Frepository\u002Fmemory\u002Fuser_repository.go\n",[5280,5697,5698,5700],{"class":5282,"line":5391},[5280,5699,5664],{"class":5293},[5280,5701,5702],{"class":5297}," internal\u002Frepository\u002Fpostgres\u002Fuser_repository.go\n",[5280,5704,5706,5708],{"class":5282,"line":5705},14,[5280,5707,5664],{"class":5293},[5280,5709,5710],{"class":5297}," internal\u002Fdelivery\u002Fhttp\u002Fuser_handler.go\n",[5712,5713,5714,5717,5718,5720,5721,5724,5725,5218],"note",{},[5227,5715,5716],{},"Важливо:"," Назва модуля в ",[5215,5719,5407],{}," (",[5215,5722,5723],{},"myapp",") має відповідати імпортам у коді. У реальних проєктах використовуйте повний шлях, наприклад: ",[5215,5726,5727],{},"github.com\u002Fusername\u002Fmyapp",[5254,5729],{},[5257,5731,5733],{"id":5732},"проблема-дублювання-коду-в-http-обробниках","Проблема дублювання коду в HTTP-обробниках",[5266,5735,5737],{"id":5736},"початок-прості-обробники-без-структури","Початок: прості обробники без структури",[5262,5739,5740],{},"Припустимо, ми розробляємо API для управління користувачами. Почнемо з найпростішого підходу — напишемо кілька HTTP-обробників без будь-якої архітектури:",[5271,5742,5744],{"className":5411,"code":5743,"language":5325,"meta":5276,"style":5276},"\u002F\u002F cmd\u002Fsimple-server\u002Fmain.go\npackage main\n\nimport (\n    \"encoding\u002Fjson\"\n    \"fmt\"\n    \"log\"\n    \"net\u002Fhttp\"\n    \"time\"\n)\n\n\u002F\u002F Примітка: для запуску цього прикладу достатньо стандартної бібліотеки Go\n\n\u002F\u002F Глобальна змінна для зберігання користувачів (in-memory)\nvar users = make(map[string]string)\n\nfunc main() {\n    http.HandleFunc(\"\u002Fapi\u002Fusers\", handleUsers)\n    http.HandleFunc(\"\u002Fapi\u002Fhealth\", handleHealth)\n\n    fmt.Println(\"Server starting on :8080...\")\n    log.Fatal(http.ListenAndServe(\":8080\", nil))\n}\n\nfunc handleUsers(w http.ResponseWriter, r *http.Request) {\n    start := time.Now()\n    log.Printf(\"[%s] %s - started\", r.Method, r.URL.Path)\n\n    switch r.Method {\n    case http.MethodGet:\n        w.Header().Set(\"Content-Type\", \"application\u002Fjson\")\n        json.NewEncoder(w).Encode(users)\n        \n    case http.MethodPost:\n        var user struct {\n            Name  string `json:\"name\"`\n            Email string `json:\"email\"`\n        }\n        \n        if err := json.NewDecoder(r.Body).Decode(&user); err != nil {\n            http.Error(w, \"Invalid JSON\", http.StatusBadRequest)\n            log.Printf(\"[%s] %s - completed in %v (error: invalid JSON)\", \n                r.Method, r.URL.Path, time.Since(start))\n            return\n        }\n        \n        users[user.Email] = user.Name\n        w.WriteHeader(http.StatusCreated)\n        fmt.Fprintf(w, \"User %s created\", user.Name)\n        \n    default:\n        http.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n    }\n\n    log.Printf(\"[%s] %s - completed in %v\", r.Method, r.URL.Path, time.Since(start))\n}\n\nfunc handleHealth(w http.ResponseWriter, r *http.Request) {\n    start := time.Now()\n    log.Printf(\"[%s] %s - started\", r.Method, r.URL.Path)\n    \n    w.Header().Set(\"Content-Type\", \"application\u002Fjson\")\n    json.NewEncoder(w).Encode(map[string]string{\n        \"status\": \"ok\",\n        \"time\":   time.Now().Format(time.RFC3339),\n    })\n    \n    log.Printf(\"[%s] %s - completed in %v\", r.Method, r.URL.Path, time.Since(start))\n}\n",[5215,5745,5746,5751,5760,5764,5771,5776,5781,5786,5791,5796,5800,5804,5809,5813,5818,5852,5857,5869,5893,5914,5919,5937,5971,5977,5982,6021,6040,6091,6096,6112,6128,6157,6185,6191,6205,6219,6231,6243,6249,6254,6306,6337,6369,6407,6413,6418,6423,6448,6469,6505,6510,6518,6548,6554,6559,6619,6624,6629,6661,6676,6719,6725,6749,6782,6797,6829,6835,6840,6899],{"__ignoreMap":5276},[5280,5747,5748],{"class":5282,"line":5283},[5280,5749,5750],{"class":5286},"\u002F\u002F cmd\u002Fsimple-server\u002Fmain.go\n",[5280,5752,5753,5756],{"class":5282,"line":5290},[5280,5754,5755],{"class":5619},"package",[5280,5757,5759],{"class":5758},"sN1BT"," main\n",[5280,5761,5762],{"class":5282,"line":5301},[5280,5763,5313],{"emptyLinePlaceholder":5312},[5280,5765,5766,5769],{"class":5282,"line":5309},[5280,5767,5768],{"class":5436},"import",[5280,5770,5453],{"class":5452},[5280,5772,5773],{"class":5282,"line":5316},[5280,5774,5775],{"class":5297},"    \"encoding\u002Fjson\"\n",[5280,5777,5778],{"class":5282,"line":5322},[5280,5779,5780],{"class":5297},"    \"fmt\"\n",[5280,5782,5783],{"class":5282,"line":5336},[5280,5784,5785],{"class":5297},"    \"log\"\n",[5280,5787,5788],{"class":5282,"line":5341},[5280,5789,5790],{"class":5297},"    \"net\u002Fhttp\"\n",[5280,5792,5793],{"class":5282,"line":5347},[5280,5794,5795],{"class":5297},"    \"time\"\n",[5280,5797,5798],{"class":5282,"line":5358},[5280,5799,5598],{"class":5452},[5280,5801,5802],{"class":5282,"line":5368},[5280,5803,5313],{"emptyLinePlaceholder":5312},[5280,5805,5806],{"class":5282,"line":5378},[5280,5807,5808],{"class":5286},"\u002F\u002F Примітка: для запуску цього прикладу достатньо стандартної бібліотеки Go\n",[5280,5810,5811],{"class":5282,"line":5391},[5280,5812,5313],{"emptyLinePlaceholder":5312},[5280,5814,5815],{"class":5282,"line":5705},[5280,5816,5817],{"class":5286},"\u002F\u002F Глобальна змінна для зберігання користувачів (in-memory)\n",[5280,5819,5821,5824,5827,5830,5833,5836,5839,5842,5845,5848,5850],{"class":5282,"line":5820},15,[5280,5822,5823],{"class":5619},"var",[5280,5825,5826],{"class":5424}," users",[5280,5828,5829],{"class":5452}," = ",[5280,5831,5832],{"class":5293},"make",[5280,5834,5835],{"class":5452},"(",[5280,5837,5838],{"class":5619},"map",[5280,5840,5841],{"class":5452},"[",[5280,5843,5844],{"class":5758},"string",[5280,5846,5847],{"class":5452},"]",[5280,5849,5844],{"class":5758},[5280,5851,5598],{"class":5452},[5280,5853,5855],{"class":5282,"line":5854},16,[5280,5856,5313],{"emptyLinePlaceholder":5312},[5280,5858,5860,5863,5866],{"class":5282,"line":5859},17,[5280,5861,5862],{"class":5619},"func",[5280,5864,5865],{"class":5293}," main",[5280,5867,5868],{"class":5452},"() {\n",[5280,5870,5872,5875,5877,5880,5882,5885,5888,5891],{"class":5282,"line":5871},18,[5280,5873,5874],{"class":5424},"    http",[5280,5876,5218],{"class":5452},[5280,5878,5879],{"class":5293},"HandleFunc",[5280,5881,5835],{"class":5452},[5280,5883,5884],{"class":5297},"\"\u002Fapi\u002Fusers\"",[5280,5886,5887],{"class":5452},", ",[5280,5889,5890],{"class":5424},"handleUsers",[5280,5892,5598],{"class":5452},[5280,5894,5896,5898,5900,5902,5904,5907,5909,5912],{"class":5282,"line":5895},19,[5280,5897,5874],{"class":5424},[5280,5899,5218],{"class":5452},[5280,5901,5879],{"class":5293},[5280,5903,5835],{"class":5452},[5280,5905,5906],{"class":5297},"\"\u002Fapi\u002Fhealth\"",[5280,5908,5887],{"class":5452},[5280,5910,5911],{"class":5424},"handleHealth",[5280,5913,5598],{"class":5452},[5280,5915,5917],{"class":5282,"line":5916},20,[5280,5918,5313],{"emptyLinePlaceholder":5312},[5280,5920,5922,5925,5927,5930,5932,5935],{"class":5282,"line":5921},21,[5280,5923,5924],{"class":5424},"    fmt",[5280,5926,5218],{"class":5452},[5280,5928,5929],{"class":5293},"Println",[5280,5931,5835],{"class":5452},[5280,5933,5934],{"class":5297},"\"Server starting on :8080...\"",[5280,5936,5598],{"class":5452},[5280,5938,5940,5943,5945,5948,5950,5953,5955,5958,5960,5963,5965,5968],{"class":5282,"line":5939},22,[5280,5941,5942],{"class":5424},"    log",[5280,5944,5218],{"class":5452},[5280,5946,5947],{"class":5293},"Fatal",[5280,5949,5835],{"class":5452},[5280,5951,5952],{"class":5424},"http",[5280,5954,5218],{"class":5452},[5280,5956,5957],{"class":5293},"ListenAndServe",[5280,5959,5835],{"class":5452},[5280,5961,5962],{"class":5297},"\":8080\"",[5280,5964,5887],{"class":5452},[5280,5966,5967],{"class":5619},"nil",[5280,5969,5970],{"class":5452},"))\n",[5280,5972,5974],{"class":5282,"line":5973},23,[5280,5975,5976],{"class":5452},"}\n",[5280,5978,5980],{"class":5282,"line":5979},24,[5280,5981,5313],{"emptyLinePlaceholder":5312},[5280,5983,5985,5987,5990,5992,5995,5998,6000,6003,6005,6008,6011,6013,6015,6018],{"class":5282,"line":5984},25,[5280,5986,5862],{"class":5619},[5280,5988,5989],{"class":5293}," handleUsers",[5280,5991,5835],{"class":5452},[5280,5993,5994],{"class":5424},"w",[5280,5996,5997],{"class":5758}," http",[5280,5999,5218],{"class":5452},[5280,6001,6002],{"class":5758},"ResponseWriter",[5280,6004,5887],{"class":5452},[5280,6006,6007],{"class":5424},"r",[5280,6009,6010],{"class":5452}," *",[5280,6012,5952],{"class":5758},[5280,6014,5218],{"class":5452},[5280,6016,6017],{"class":5758},"Request",[5280,6019,6020],{"class":5452},") {\n",[5280,6022,6024,6027,6030,6032,6034,6037],{"class":5282,"line":6023},26,[5280,6025,6026],{"class":5424},"    start",[5280,6028,6029],{"class":5452}," := ",[5280,6031,5587],{"class":5424},[5280,6033,5218],{"class":5452},[5280,6035,6036],{"class":5293},"Now",[5280,6038,6039],{"class":5452},"()\n",[5280,6041,6043,6045,6047,6050,6052,6055,6058,6061,6063,6066,6068,6070,6072,6075,6077,6079,6081,6084,6086,6089],{"class":5282,"line":6042},27,[5280,6044,5942],{"class":5424},[5280,6046,5218],{"class":5452},[5280,6048,6049],{"class":5293},"Printf",[5280,6051,5835],{"class":5452},[5280,6053,6054],{"class":5297},"\"[",[5280,6056,6057],{"class":5424},"%s",[5280,6059,6060],{"class":5297},"] ",[5280,6062,6057],{"class":5424},[5280,6064,6065],{"class":5297}," - started\"",[5280,6067,5887],{"class":5452},[5280,6069,6007],{"class":5424},[5280,6071,5218],{"class":5452},[5280,6073,6074],{"class":5424},"Method",[5280,6076,5887],{"class":5452},[5280,6078,6007],{"class":5424},[5280,6080,5218],{"class":5452},[5280,6082,6083],{"class":5424},"URL",[5280,6085,5218],{"class":5452},[5280,6087,6088],{"class":5424},"Path",[5280,6090,5598],{"class":5452},[5280,6092,6094],{"class":5282,"line":6093},28,[5280,6095,5313],{"emptyLinePlaceholder":5312},[5280,6097,6099,6102,6105,6107,6109],{"class":5282,"line":6098},29,[5280,6100,6101],{"class":5436},"    switch",[5280,6103,6104],{"class":5424}," r",[5280,6106,5218],{"class":5452},[5280,6108,6074],{"class":5424},[5280,6110,6111],{"class":5452}," {\n",[5280,6113,6115,6118,6120,6122,6125],{"class":5282,"line":6114},30,[5280,6116,6117],{"class":5436},"    case",[5280,6119,5997],{"class":5424},[5280,6121,5218],{"class":5452},[5280,6123,6124],{"class":5424},"MethodGet",[5280,6126,6127],{"class":5452},":\n",[5280,6129,6131,6134,6136,6139,6142,6145,6147,6150,6152,6155],{"class":5282,"line":6130},31,[5280,6132,6133],{"class":5424},"        w",[5280,6135,5218],{"class":5452},[5280,6137,6138],{"class":5293},"Header",[5280,6140,6141],{"class":5452},"().",[5280,6143,6144],{"class":5293},"Set",[5280,6146,5835],{"class":5452},[5280,6148,6149],{"class":5297},"\"Content-Type\"",[5280,6151,5887],{"class":5452},[5280,6153,6154],{"class":5297},"\"application\u002Fjson\"",[5280,6156,5598],{"class":5452},[5280,6158,6160,6163,6165,6168,6170,6172,6175,6178,6180,6183],{"class":5282,"line":6159},32,[5280,6161,6162],{"class":5424},"        json",[5280,6164,5218],{"class":5452},[5280,6166,6167],{"class":5293},"NewEncoder",[5280,6169,5835],{"class":5452},[5280,6171,5994],{"class":5424},[5280,6173,6174],{"class":5452},").",[5280,6176,6177],{"class":5293},"Encode",[5280,6179,5835],{"class":5452},[5280,6181,6182],{"class":5424},"users",[5280,6184,5598],{"class":5452},[5280,6186,6188],{"class":5282,"line":6187},33,[5280,6189,6190],{"class":5452},"        \n",[5280,6192,6194,6196,6198,6200,6203],{"class":5282,"line":6193},34,[5280,6195,6117],{"class":5436},[5280,6197,5997],{"class":5424},[5280,6199,5218],{"class":5452},[5280,6201,6202],{"class":5424},"MethodPost",[5280,6204,6127],{"class":5452},[5280,6206,6208,6211,6214,6217],{"class":5282,"line":6207},35,[5280,6209,6210],{"class":5619},"        var",[5280,6212,6213],{"class":5424}," user",[5280,6215,6216],{"class":5619}," struct",[5280,6218,6111],{"class":5452},[5280,6220,6222,6225,6228],{"class":5282,"line":6221},36,[5280,6223,6224],{"class":5424},"            Name",[5280,6226,6227],{"class":5758},"  string",[5280,6229,6230],{"class":5297}," `json:\"name\"`\n",[5280,6232,6234,6237,6240],{"class":5282,"line":6233},37,[5280,6235,6236],{"class":5424},"            Email",[5280,6238,6239],{"class":5758}," string",[5280,6241,6242],{"class":5297}," `json:\"email\"`\n",[5280,6244,6246],{"class":5282,"line":6245},38,[5280,6247,6248],{"class":5452},"        }\n",[5280,6250,6252],{"class":5282,"line":6251},39,[5280,6253,6190],{"class":5452},[5280,6255,6257,6260,6263,6265,6268,6270,6273,6275,6277,6279,6282,6284,6287,6290,6293,6296,6299,6302,6304],{"class":5282,"line":6256},40,[5280,6258,6259],{"class":5436},"        if",[5280,6261,6262],{"class":5424}," err",[5280,6264,6029],{"class":5452},[5280,6266,6267],{"class":5424},"json",[5280,6269,5218],{"class":5452},[5280,6271,6272],{"class":5293},"NewDecoder",[5280,6274,5835],{"class":5452},[5280,6276,6007],{"class":5424},[5280,6278,5218],{"class":5452},[5280,6280,6281],{"class":5424},"Body",[5280,6283,6174],{"class":5452},[5280,6285,6286],{"class":5293},"Decode",[5280,6288,6289],{"class":5452},"(&",[5280,6291,6292],{"class":5424},"user",[5280,6294,6295],{"class":5452},"); ",[5280,6297,6298],{"class":5424},"err",[5280,6300,6301],{"class":5452}," != ",[5280,6303,5967],{"class":5619},[5280,6305,6111],{"class":5452},[5280,6307,6309,6312,6314,6317,6319,6321,6323,6326,6328,6330,6332,6335],{"class":5282,"line":6308},41,[5280,6310,6311],{"class":5424},"            http",[5280,6313,5218],{"class":5452},[5280,6315,6316],{"class":5293},"Error",[5280,6318,5835],{"class":5452},[5280,6320,5994],{"class":5424},[5280,6322,5887],{"class":5452},[5280,6324,6325],{"class":5297},"\"Invalid JSON\"",[5280,6327,5887],{"class":5452},[5280,6329,5952],{"class":5424},[5280,6331,5218],{"class":5452},[5280,6333,6334],{"class":5424},"StatusBadRequest",[5280,6336,5598],{"class":5452},[5280,6338,6340,6343,6345,6347,6349,6351,6353,6355,6357,6360,6363,6366],{"class":5282,"line":6339},42,[5280,6341,6342],{"class":5424},"            log",[5280,6344,5218],{"class":5452},[5280,6346,6049],{"class":5293},[5280,6348,5835],{"class":5452},[5280,6350,6054],{"class":5297},[5280,6352,6057],{"class":5424},[5280,6354,6060],{"class":5297},[5280,6356,6057],{"class":5424},[5280,6358,6359],{"class":5297}," - completed in ",[5280,6361,6362],{"class":5424},"%v",[5280,6364,6365],{"class":5297}," (error: invalid JSON)\"",[5280,6367,6368],{"class":5452},", \n",[5280,6370,6372,6375,6377,6379,6381,6383,6385,6387,6389,6391,6393,6395,6397,6400,6402,6405],{"class":5282,"line":6371},43,[5280,6373,6374],{"class":5424},"                r",[5280,6376,5218],{"class":5452},[5280,6378,6074],{"class":5424},[5280,6380,5887],{"class":5452},[5280,6382,6007],{"class":5424},[5280,6384,5218],{"class":5452},[5280,6386,6083],{"class":5424},[5280,6388,5218],{"class":5452},[5280,6390,6088],{"class":5424},[5280,6392,5887],{"class":5452},[5280,6394,5587],{"class":5424},[5280,6396,5218],{"class":5452},[5280,6398,6399],{"class":5293},"Since",[5280,6401,5835],{"class":5452},[5280,6403,6404],{"class":5424},"start",[5280,6406,5970],{"class":5452},[5280,6408,6410],{"class":5282,"line":6409},44,[5280,6411,6412],{"class":5436},"            return\n",[5280,6414,6416],{"class":5282,"line":6415},45,[5280,6417,6248],{"class":5452},[5280,6419,6421],{"class":5282,"line":6420},46,[5280,6422,6190],{"class":5452},[5280,6424,6426,6429,6431,6433,6435,6438,6441,6443,6445],{"class":5282,"line":6425},47,[5280,6427,6428],{"class":5424},"        users",[5280,6430,5841],{"class":5452},[5280,6432,6292],{"class":5424},[5280,6434,5218],{"class":5452},[5280,6436,6437],{"class":5424},"Email",[5280,6439,6440],{"class":5452},"] = ",[5280,6442,6292],{"class":5424},[5280,6444,5218],{"class":5452},[5280,6446,6447],{"class":5424},"Name\n",[5280,6449,6451,6453,6455,6458,6460,6462,6464,6467],{"class":5282,"line":6450},48,[5280,6452,6133],{"class":5424},[5280,6454,5218],{"class":5452},[5280,6456,6457],{"class":5293},"WriteHeader",[5280,6459,5835],{"class":5452},[5280,6461,5952],{"class":5424},[5280,6463,5218],{"class":5452},[5280,6465,6466],{"class":5424},"StatusCreated",[5280,6468,5598],{"class":5452},[5280,6470,6472,6475,6477,6480,6482,6484,6486,6489,6491,6494,6496,6498,6500,6503],{"class":5282,"line":6471},49,[5280,6473,6474],{"class":5424},"        fmt",[5280,6476,5218],{"class":5452},[5280,6478,6479],{"class":5293},"Fprintf",[5280,6481,5835],{"class":5452},[5280,6483,5994],{"class":5424},[5280,6485,5887],{"class":5452},[5280,6487,6488],{"class":5297},"\"User ",[5280,6490,6057],{"class":5424},[5280,6492,6493],{"class":5297}," created\"",[5280,6495,5887],{"class":5452},[5280,6497,6292],{"class":5424},[5280,6499,5218],{"class":5452},[5280,6501,6502],{"class":5424},"Name",[5280,6504,5598],{"class":5452},[5280,6506,6508],{"class":5282,"line":6507},50,[5280,6509,6190],{"class":5452},[5280,6511,6513,6516],{"class":5282,"line":6512},51,[5280,6514,6515],{"class":5436},"    default",[5280,6517,6127],{"class":5452},[5280,6519,6521,6524,6526,6528,6530,6532,6534,6537,6539,6541,6543,6546],{"class":5282,"line":6520},52,[5280,6522,6523],{"class":5424},"        http",[5280,6525,5218],{"class":5452},[5280,6527,6316],{"class":5293},[5280,6529,5835],{"class":5452},[5280,6531,5994],{"class":5424},[5280,6533,5887],{"class":5452},[5280,6535,6536],{"class":5297},"\"Method not allowed\"",[5280,6538,5887],{"class":5452},[5280,6540,5952],{"class":5424},[5280,6542,5218],{"class":5452},[5280,6544,6545],{"class":5424},"StatusMethodNotAllowed",[5280,6547,5598],{"class":5452},[5280,6549,6551],{"class":5282,"line":6550},53,[5280,6552,6553],{"class":5452},"    }\n",[5280,6555,6557],{"class":5282,"line":6556},54,[5280,6558,5313],{"emptyLinePlaceholder":5312},[5280,6560,6562,6564,6566,6568,6570,6572,6574,6576,6578,6580,6582,6585,6587,6589,6591,6593,6595,6597,6599,6601,6603,6605,6607,6609,6611,6613,6615,6617],{"class":5282,"line":6561},55,[5280,6563,5942],{"class":5424},[5280,6565,5218],{"class":5452},[5280,6567,6049],{"class":5293},[5280,6569,5835],{"class":5452},[5280,6571,6054],{"class":5297},[5280,6573,6057],{"class":5424},[5280,6575,6060],{"class":5297},[5280,6577,6057],{"class":5424},[5280,6579,6359],{"class":5297},[5280,6581,6362],{"class":5424},[5280,6583,6584],{"class":5297},"\"",[5280,6586,5887],{"class":5452},[5280,6588,6007],{"class":5424},[5280,6590,5218],{"class":5452},[5280,6592,6074],{"class":5424},[5280,6594,5887],{"class":5452},[5280,6596,6007],{"class":5424},[5280,6598,5218],{"class":5452},[5280,6600,6083],{"class":5424},[5280,6602,5218],{"class":5452},[5280,6604,6088],{"class":5424},[5280,6606,5887],{"class":5452},[5280,6608,5587],{"class":5424},[5280,6610,5218],{"class":5452},[5280,6612,6399],{"class":5293},[5280,6614,5835],{"class":5452},[5280,6616,6404],{"class":5424},[5280,6618,5970],{"class":5452},[5280,6620,6622],{"class":5282,"line":6621},56,[5280,6623,5976],{"class":5452},[5280,6625,6627],{"class":5282,"line":6626},57,[5280,6628,5313],{"emptyLinePlaceholder":5312},[5280,6630,6632,6634,6637,6639,6641,6643,6645,6647,6649,6651,6653,6655,6657,6659],{"class":5282,"line":6631},58,[5280,6633,5862],{"class":5619},[5280,6635,6636],{"class":5293}," handleHealth",[5280,6638,5835],{"class":5452},[5280,6640,5994],{"class":5424},[5280,6642,5997],{"class":5758},[5280,6644,5218],{"class":5452},[5280,6646,6002],{"class":5758},[5280,6648,5887],{"class":5452},[5280,6650,6007],{"class":5424},[5280,6652,6010],{"class":5452},[5280,6654,5952],{"class":5758},[5280,6656,5218],{"class":5452},[5280,6658,6017],{"class":5758},[5280,6660,6020],{"class":5452},[5280,6662,6664,6666,6668,6670,6672,6674],{"class":5282,"line":6663},59,[5280,6665,6026],{"class":5424},[5280,6667,6029],{"class":5452},[5280,6669,5587],{"class":5424},[5280,6671,5218],{"class":5452},[5280,6673,6036],{"class":5293},[5280,6675,6039],{"class":5452},[5280,6677,6679,6681,6683,6685,6687,6689,6691,6693,6695,6697,6699,6701,6703,6705,6707,6709,6711,6713,6715,6717],{"class":5282,"line":6678},60,[5280,6680,5942],{"class":5424},[5280,6682,5218],{"class":5452},[5280,6684,6049],{"class":5293},[5280,6686,5835],{"class":5452},[5280,6688,6054],{"class":5297},[5280,6690,6057],{"class":5424},[5280,6692,6060],{"class":5297},[5280,6694,6057],{"class":5424},[5280,6696,6065],{"class":5297},[5280,6698,5887],{"class":5452},[5280,6700,6007],{"class":5424},[5280,6702,5218],{"class":5452},[5280,6704,6074],{"class":5424},[5280,6706,5887],{"class":5452},[5280,6708,6007],{"class":5424},[5280,6710,5218],{"class":5452},[5280,6712,6083],{"class":5424},[5280,6714,5218],{"class":5452},[5280,6716,6088],{"class":5424},[5280,6718,5598],{"class":5452},[5280,6720,6722],{"class":5282,"line":6721},61,[5280,6723,6724],{"class":5452},"    \n",[5280,6726,6728,6731,6733,6735,6737,6739,6741,6743,6745,6747],{"class":5282,"line":6727},62,[5280,6729,6730],{"class":5424},"    w",[5280,6732,5218],{"class":5452},[5280,6734,6138],{"class":5293},[5280,6736,6141],{"class":5452},[5280,6738,6144],{"class":5293},[5280,6740,5835],{"class":5452},[5280,6742,6149],{"class":5297},[5280,6744,5887],{"class":5452},[5280,6746,6154],{"class":5297},[5280,6748,5598],{"class":5452},[5280,6750,6752,6755,6757,6759,6761,6763,6765,6767,6769,6771,6773,6775,6777,6779],{"class":5282,"line":6751},63,[5280,6753,6754],{"class":5424},"    json",[5280,6756,5218],{"class":5452},[5280,6758,6167],{"class":5293},[5280,6760,5835],{"class":5452},[5280,6762,5994],{"class":5424},[5280,6764,6174],{"class":5452},[5280,6766,6177],{"class":5293},[5280,6768,5835],{"class":5452},[5280,6770,5838],{"class":5619},[5280,6772,5841],{"class":5452},[5280,6774,5844],{"class":5758},[5280,6776,5847],{"class":5452},[5280,6778,5844],{"class":5758},[5280,6780,6781],{"class":5452},"{\n",[5280,6783,6785,6788,6791,6794],{"class":5282,"line":6784},64,[5280,6786,6787],{"class":5297},"        \"status\"",[5280,6789,6790],{"class":5452},": ",[5280,6792,6793],{"class":5297},"\"ok\"",[5280,6795,6796],{"class":5452},",\n",[5280,6798,6800,6803,6806,6808,6810,6812,6814,6817,6819,6821,6823,6826],{"class":5282,"line":6799},65,[5280,6801,6802],{"class":5297},"        \"time\"",[5280,6804,6805],{"class":5452},":   ",[5280,6807,5587],{"class":5424},[5280,6809,5218],{"class":5452},[5280,6811,6036],{"class":5293},[5280,6813,6141],{"class":5452},[5280,6815,6816],{"class":5293},"Format",[5280,6818,5835],{"class":5452},[5280,6820,5587],{"class":5424},[5280,6822,5218],{"class":5452},[5280,6824,6825],{"class":5424},"RFC3339",[5280,6827,6828],{"class":5452},"),\n",[5280,6830,6832],{"class":5282,"line":6831},66,[5280,6833,6834],{"class":5452},"    })\n",[5280,6836,6838],{"class":5282,"line":6837},67,[5280,6839,6724],{"class":5452},[5280,6841,6843,6845,6847,6849,6851,6853,6855,6857,6859,6861,6863,6865,6867,6869,6871,6873,6875,6877,6879,6881,6883,6885,6887,6889,6891,6893,6895,6897],{"class":5282,"line":6842},68,[5280,6844,5942],{"class":5424},[5280,6846,5218],{"class":5452},[5280,6848,6049],{"class":5293},[5280,6850,5835],{"class":5452},[5280,6852,6054],{"class":5297},[5280,6854,6057],{"class":5424},[5280,6856,6060],{"class":5297},[5280,6858,6057],{"class":5424},[5280,6860,6359],{"class":5297},[5280,6862,6362],{"class":5424},[5280,6864,6584],{"class":5297},[5280,6866,5887],{"class":5452},[5280,6868,6007],{"class":5424},[5280,6870,5218],{"class":5452},[5280,6872,6074],{"class":5424},[5280,6874,5887],{"class":5452},[5280,6876,6007],{"class":5424},[5280,6878,5218],{"class":5452},[5280,6880,6083],{"class":5424},[5280,6882,5218],{"class":5452},[5280,6884,6088],{"class":5424},[5280,6886,5887],{"class":5452},[5280,6888,5587],{"class":5424},[5280,6890,5218],{"class":5452},[5280,6892,6399],{"class":5293},[5280,6894,5835],{"class":5452},[5280,6896,6404],{"class":5424},[5280,6898,5970],{"class":5452},[5280,6900,6902],{"class":5282,"line":6901},69,[5280,6903,5976],{"class":5452},[5262,6905,6906],{},[5227,6907,6908],{},"Що ми бачимо в цьому коді?",[5262,6910,6911,6912,5408],{},"Кожен обробник містить ",[5227,6913,6914],{},"однаковий повторюваний код",[6916,6917,6918,6921,6927],"ol",{},[5197,6919,6920],{},"Логування початку запиту з часом старту",[5197,6922,6923,6924],{},"Встановлення заголовка ",[5215,6925,6926],{},"Content-Type",[5197,6928,6929],{},"Логування завершення запиту з тривалістю",[5262,6931,6932],{},"Це тільки два ендпоінти! Уявіть, що буде при 20-30 ендпоінтах. А що, якщо нам потрібно додати:",[5194,6934,6935,6942,6945,6948],{},[5197,6936,6937,6938,6941],{},"Перехоплення паніки (",[5207,6939,6940],{},"panic recovery",") щоб сервер не падав при помилках?",[5197,6943,6944],{},"CORS-заголовки для роботи з браузерними клієнтами?",[5197,6946,6947],{},"Автентифікацію через JWT токени?",[5197,6949,6950,6951,6954],{},"Обмеження частоти запитів (",[5207,6952,6953],{},"rate limiting",")?",[5262,6956,6957,6958],{},"Код стане абсолютно нечитабельним. ",[5227,6959,6960],{},"Саме цю проблему вирішує патерн Middleware.",[5254,6962],{},[5257,6964,6966],{"id":6965},"еволюція-виділення-спільної-логіки","Еволюція: виділення спільної логіки",[5266,6968,6970],{"id":6969},"спроба-1-винесення-логування-у-функцію","Спроба 1: винесення логування у функцію",[5262,6972,6973],{},"Спробуємо зменшити дублювання, створивши допоміжну функцію:",[5271,6975,6977],{"className":5411,"code":6976,"language":5325,"meta":5276,"style":5276},"func logRequest(method, path string, start time.Time, stage string) {\n    if stage == \"start\" {\n        log.Printf(\"[%s] %s - started\", method, path)\n    } else {\n        log.Printf(\"[%s] %s - completed in %v\", method, path, time.Since(start))\n    }\n}\n\nfunc handleUsers(w http.ResponseWriter, r *http.Request) {\n    start := time.Now()\n    logRequest(r.Method, r.URL.Path, start, \"start\")\n    defer logRequest(r.Method, r.URL.Path, start, \"end\")\n    \n    \u002F\u002F ... решта логіки\n}\n",[5215,6978,6979,7019,7035,7066,7076,7122,7126,7130,7134,7164,7178,7213,7251,7255,7260],{"__ignoreMap":5276},[5280,6980,6981,6983,6986,6988,6991,6993,6996,6998,7000,7002,7005,7007,7010,7012,7015,7017],{"class":5282,"line":5283},[5280,6982,5862],{"class":5619},[5280,6984,6985],{"class":5293}," logRequest",[5280,6987,5835],{"class":5452},[5280,6989,6990],{"class":5424},"method",[5280,6992,5887],{"class":5452},[5280,6994,6995],{"class":5424},"path",[5280,6997,6239],{"class":5758},[5280,6999,5887],{"class":5452},[5280,7001,6404],{"class":5424},[5280,7003,7004],{"class":5758}," time",[5280,7006,5218],{"class":5452},[5280,7008,7009],{"class":5758},"Time",[5280,7011,5887],{"class":5452},[5280,7013,7014],{"class":5424},"stage",[5280,7016,6239],{"class":5758},[5280,7018,6020],{"class":5452},[5280,7020,7021,7024,7027,7030,7033],{"class":5282,"line":5290},[5280,7022,7023],{"class":5436},"    if",[5280,7025,7026],{"class":5424}," stage",[5280,7028,7029],{"class":5452}," == ",[5280,7031,7032],{"class":5297},"\"start\"",[5280,7034,6111],{"class":5452},[5280,7036,7037,7040,7042,7044,7046,7048,7050,7052,7054,7056,7058,7060,7062,7064],{"class":5282,"line":5301},[5280,7038,7039],{"class":5424},"        log",[5280,7041,5218],{"class":5452},[5280,7043,6049],{"class":5293},[5280,7045,5835],{"class":5452},[5280,7047,6054],{"class":5297},[5280,7049,6057],{"class":5424},[5280,7051,6060],{"class":5297},[5280,7053,6057],{"class":5424},[5280,7055,6065],{"class":5297},[5280,7057,5887],{"class":5452},[5280,7059,6990],{"class":5424},[5280,7061,5887],{"class":5452},[5280,7063,6995],{"class":5424},[5280,7065,5598],{"class":5452},[5280,7067,7068,7071,7074],{"class":5282,"line":5309},[5280,7069,7070],{"class":5452},"    } ",[5280,7072,7073],{"class":5436},"else",[5280,7075,6111],{"class":5452},[5280,7077,7078,7080,7082,7084,7086,7088,7090,7092,7094,7096,7098,7100,7102,7104,7106,7108,7110,7112,7114,7116,7118,7120],{"class":5282,"line":5316},[5280,7079,7039],{"class":5424},[5280,7081,5218],{"class":5452},[5280,7083,6049],{"class":5293},[5280,7085,5835],{"class":5452},[5280,7087,6054],{"class":5297},[5280,7089,6057],{"class":5424},[5280,7091,6060],{"class":5297},[5280,7093,6057],{"class":5424},[5280,7095,6359],{"class":5297},[5280,7097,6362],{"class":5424},[5280,7099,6584],{"class":5297},[5280,7101,5887],{"class":5452},[5280,7103,6990],{"class":5424},[5280,7105,5887],{"class":5452},[5280,7107,6995],{"class":5424},[5280,7109,5887],{"class":5452},[5280,7111,5587],{"class":5424},[5280,7113,5218],{"class":5452},[5280,7115,6399],{"class":5293},[5280,7117,5835],{"class":5452},[5280,7119,6404],{"class":5424},[5280,7121,5970],{"class":5452},[5280,7123,7124],{"class":5282,"line":5322},[5280,7125,6553],{"class":5452},[5280,7127,7128],{"class":5282,"line":5336},[5280,7129,5976],{"class":5452},[5280,7131,7132],{"class":5282,"line":5341},[5280,7133,5313],{"emptyLinePlaceholder":5312},[5280,7135,7136,7138,7140,7142,7144,7146,7148,7150,7152,7154,7156,7158,7160,7162],{"class":5282,"line":5347},[5280,7137,5862],{"class":5619},[5280,7139,5989],{"class":5293},[5280,7141,5835],{"class":5452},[5280,7143,5994],{"class":5424},[5280,7145,5997],{"class":5758},[5280,7147,5218],{"class":5452},[5280,7149,6002],{"class":5758},[5280,7151,5887],{"class":5452},[5280,7153,6007],{"class":5424},[5280,7155,6010],{"class":5452},[5280,7157,5952],{"class":5758},[5280,7159,5218],{"class":5452},[5280,7161,6017],{"class":5758},[5280,7163,6020],{"class":5452},[5280,7165,7166,7168,7170,7172,7174,7176],{"class":5282,"line":5358},[5280,7167,6026],{"class":5424},[5280,7169,6029],{"class":5452},[5280,7171,5587],{"class":5424},[5280,7173,5218],{"class":5452},[5280,7175,6036],{"class":5293},[5280,7177,6039],{"class":5452},[5280,7179,7180,7183,7185,7187,7189,7191,7193,7195,7197,7199,7201,7203,7205,7207,7209,7211],{"class":5282,"line":5368},[5280,7181,7182],{"class":5293},"    logRequest",[5280,7184,5835],{"class":5452},[5280,7186,6007],{"class":5424},[5280,7188,5218],{"class":5452},[5280,7190,6074],{"class":5424},[5280,7192,5887],{"class":5452},[5280,7194,6007],{"class":5424},[5280,7196,5218],{"class":5452},[5280,7198,6083],{"class":5424},[5280,7200,5218],{"class":5452},[5280,7202,6088],{"class":5424},[5280,7204,5887],{"class":5452},[5280,7206,6404],{"class":5424},[5280,7208,5887],{"class":5452},[5280,7210,7032],{"class":5297},[5280,7212,5598],{"class":5452},[5280,7214,7215,7218,7220,7222,7224,7226,7228,7230,7232,7234,7236,7238,7240,7242,7244,7246,7249],{"class":5282,"line":5378},[5280,7216,7217],{"class":5436},"    defer",[5280,7219,6985],{"class":5293},[5280,7221,5835],{"class":5452},[5280,7223,6007],{"class":5424},[5280,7225,5218],{"class":5452},[5280,7227,6074],{"class":5424},[5280,7229,5887],{"class":5452},[5280,7231,6007],{"class":5424},[5280,7233,5218],{"class":5452},[5280,7235,6083],{"class":5424},[5280,7237,5218],{"class":5452},[5280,7239,6088],{"class":5424},[5280,7241,5887],{"class":5452},[5280,7243,6404],{"class":5424},[5280,7245,5887],{"class":5452},[5280,7247,7248],{"class":5297},"\"end\"",[5280,7250,5598],{"class":5452},[5280,7252,7253],{"class":5282,"line":5391},[5280,7254,6724],{"class":5452},[5280,7256,7257],{"class":5282,"line":5705},[5280,7258,7259],{"class":5286},"    \u002F\u002F ... решта логіки\n",[5280,7261,7262],{"class":5282,"line":5820},[5280,7263,5976],{"class":5452},[5262,7265,7266],{},"Це трохи краще, але:",[5194,7268,7269,7276,7279],{},[5197,7270,7271,7272,7275],{},"Треба пам'ятати викликати ",[5215,7273,7274],{},"logRequest"," у кожному обробнику",[5197,7277,7278],{},"Неможливо легко вимкнути логування для окремих ендпоінтів",[5197,7280,7281],{},"Код все одно дублюється",[5266,7283,7285],{"id":7284},"спроба-2-обгортаюча-функція","Спроба 2: обгортаюча функція",[5262,7287,7288],{},"Створимо функцію, що приймає обробник і повертає новий обробник з логуванням:",[5271,7290,7292],{"className":5411,"code":7291,"language":5325,"meta":5276,"style":5276},"func withLogging(next http.HandlerFunc) http.HandlerFunc {\n    return func(w http.ResponseWriter, r *http.Request) {\n        start := time.Now()\n        log.Printf(\"[%s] %s - started\", r.Method, r.URL.Path)\n        \n        \u002F\u002F Викликаємо оригінальний обробник\n        next(w, r)\n        \n        log.Printf(\"[%s] %s - completed in %v\", r.Method, r.URL.Path, time.Since(start))\n    }\n}\n",[5215,7293,7294,7324,7356,7371,7413,7417,7422,7437,7441,7499,7503],{"__ignoreMap":5276},[5280,7295,7296,7298,7301,7303,7306,7308,7310,7313,7316,7318,7320,7322],{"class":5282,"line":5283},[5280,7297,5862],{"class":5619},[5280,7299,7300],{"class":5293}," withLogging",[5280,7302,5835],{"class":5452},[5280,7304,7305],{"class":5424},"next",[5280,7307,5997],{"class":5758},[5280,7309,5218],{"class":5452},[5280,7311,7312],{"class":5758},"HandlerFunc",[5280,7314,7315],{"class":5452},") ",[5280,7317,5952],{"class":5758},[5280,7319,5218],{"class":5452},[5280,7321,7312],{"class":5758},[5280,7323,6111],{"class":5452},[5280,7325,7326,7329,7332,7334,7336,7338,7340,7342,7344,7346,7348,7350,7352,7354],{"class":5282,"line":5290},[5280,7327,7328],{"class":5436},"    return",[5280,7330,7331],{"class":5619}," func",[5280,7333,5835],{"class":5452},[5280,7335,5994],{"class":5424},[5280,7337,5997],{"class":5758},[5280,7339,5218],{"class":5452},[5280,7341,6002],{"class":5758},[5280,7343,5887],{"class":5452},[5280,7345,6007],{"class":5424},[5280,7347,6010],{"class":5452},[5280,7349,5952],{"class":5758},[5280,7351,5218],{"class":5452},[5280,7353,6017],{"class":5758},[5280,7355,6020],{"class":5452},[5280,7357,7358,7361,7363,7365,7367,7369],{"class":5282,"line":5301},[5280,7359,7360],{"class":5424},"        start",[5280,7362,6029],{"class":5452},[5280,7364,5587],{"class":5424},[5280,7366,5218],{"class":5452},[5280,7368,6036],{"class":5293},[5280,7370,6039],{"class":5452},[5280,7372,7373,7375,7377,7379,7381,7383,7385,7387,7389,7391,7393,7395,7397,7399,7401,7403,7405,7407,7409,7411],{"class":5282,"line":5309},[5280,7374,7039],{"class":5424},[5280,7376,5218],{"class":5452},[5280,7378,6049],{"class":5293},[5280,7380,5835],{"class":5452},[5280,7382,6054],{"class":5297},[5280,7384,6057],{"class":5424},[5280,7386,6060],{"class":5297},[5280,7388,6057],{"class":5424},[5280,7390,6065],{"class":5297},[5280,7392,5887],{"class":5452},[5280,7394,6007],{"class":5424},[5280,7396,5218],{"class":5452},[5280,7398,6074],{"class":5424},[5280,7400,5887],{"class":5452},[5280,7402,6007],{"class":5424},[5280,7404,5218],{"class":5452},[5280,7406,6083],{"class":5424},[5280,7408,5218],{"class":5452},[5280,7410,6088],{"class":5424},[5280,7412,5598],{"class":5452},[5280,7414,7415],{"class":5282,"line":5316},[5280,7416,6190],{"class":5452},[5280,7418,7419],{"class":5282,"line":5322},[5280,7420,7421],{"class":5286},"        \u002F\u002F Викликаємо оригінальний обробник\n",[5280,7423,7424,7427,7429,7431,7433,7435],{"class":5282,"line":5336},[5280,7425,7426],{"class":5293},"        next",[5280,7428,5835],{"class":5452},[5280,7430,5994],{"class":5424},[5280,7432,5887],{"class":5452},[5280,7434,6007],{"class":5424},[5280,7436,5598],{"class":5452},[5280,7438,7439],{"class":5282,"line":5341},[5280,7440,6190],{"class":5452},[5280,7442,7443,7445,7447,7449,7451,7453,7455,7457,7459,7461,7463,7465,7467,7469,7471,7473,7475,7477,7479,7481,7483,7485,7487,7489,7491,7493,7495,7497],{"class":5282,"line":5347},[5280,7444,7039],{"class":5424},[5280,7446,5218],{"class":5452},[5280,7448,6049],{"class":5293},[5280,7450,5835],{"class":5452},[5280,7452,6054],{"class":5297},[5280,7454,6057],{"class":5424},[5280,7456,6060],{"class":5297},[5280,7458,6057],{"class":5424},[5280,7460,6359],{"class":5297},[5280,7462,6362],{"class":5424},[5280,7464,6584],{"class":5297},[5280,7466,5887],{"class":5452},[5280,7468,6007],{"class":5424},[5280,7470,5218],{"class":5452},[5280,7472,6074],{"class":5424},[5280,7474,5887],{"class":5452},[5280,7476,6007],{"class":5424},[5280,7478,5218],{"class":5452},[5280,7480,6083],{"class":5424},[5280,7482,5218],{"class":5452},[5280,7484,6088],{"class":5424},[5280,7486,5887],{"class":5452},[5280,7488,5587],{"class":5424},[5280,7490,5218],{"class":5452},[5280,7492,6399],{"class":5293},[5280,7494,5835],{"class":5452},[5280,7496,6404],{"class":5424},[5280,7498,5970],{"class":5452},[5280,7500,7501],{"class":5282,"line":5358},[5280,7502,6553],{"class":5452},[5280,7504,7505],{"class":5282,"line":5368},[5280,7506,5976],{"class":5452},[5262,7508,7509],{},"Тепер можна обгорнути будь-який обробник:",[5271,7511,7513],{"className":5411,"code":7512,"language":5325,"meta":5276,"style":5276},"func main() {\n    http.HandleFunc(\"\u002Fapi\u002Fusers\", withLogging(handleUsers))\n    http.HandleFunc(\"\u002Fapi\u002Fhealth\", withLogging(handleHealth))\n    \n    log.Fatal(http.ListenAndServe(\":8080\", nil))\n}\n",[5215,7514,7515,7523,7546,7568,7572,7598],{"__ignoreMap":5276},[5280,7516,7517,7519,7521],{"class":5282,"line":5283},[5280,7518,5862],{"class":5619},[5280,7520,5865],{"class":5293},[5280,7522,5868],{"class":5452},[5280,7524,7525,7527,7529,7531,7533,7535,7537,7540,7542,7544],{"class":5282,"line":5290},[5280,7526,5874],{"class":5424},[5280,7528,5218],{"class":5452},[5280,7530,5879],{"class":5293},[5280,7532,5835],{"class":5452},[5280,7534,5884],{"class":5297},[5280,7536,5887],{"class":5452},[5280,7538,7539],{"class":5293},"withLogging",[5280,7541,5835],{"class":5452},[5280,7543,5890],{"class":5424},[5280,7545,5970],{"class":5452},[5280,7547,7548,7550,7552,7554,7556,7558,7560,7562,7564,7566],{"class":5282,"line":5301},[5280,7549,5874],{"class":5424},[5280,7551,5218],{"class":5452},[5280,7553,5879],{"class":5293},[5280,7555,5835],{"class":5452},[5280,7557,5906],{"class":5297},[5280,7559,5887],{"class":5452},[5280,7561,7539],{"class":5293},[5280,7563,5835],{"class":5452},[5280,7565,5911],{"class":5424},[5280,7567,5970],{"class":5452},[5280,7569,7570],{"class":5282,"line":5309},[5280,7571,6724],{"class":5452},[5280,7573,7574,7576,7578,7580,7582,7584,7586,7588,7590,7592,7594,7596],{"class":5282,"line":5316},[5280,7575,5942],{"class":5424},[5280,7577,5218],{"class":5452},[5280,7579,5947],{"class":5293},[5280,7581,5835],{"class":5452},[5280,7583,5952],{"class":5424},[5280,7585,5218],{"class":5452},[5280,7587,5957],{"class":5293},[5280,7589,5835],{"class":5452},[5280,7591,5962],{"class":5297},[5280,7593,5887],{"class":5452},[5280,7595,5967],{"class":5619},[5280,7597,5970],{"class":5452},[5280,7599,7600],{"class":5282,"line":5322},[5280,7601,5976],{"class":5452},[5262,7603,7604,7607,7608,7610,7611,7614,7615,7618],{},[5227,7605,7606],{},"Це вже набагато краще!"," Але є проблема: ",[5215,7609,7539],{}," приймає ",[5215,7612,7613],{},"http.HandlerFunc",", а не ",[5215,7616,7617],{},"http.Handler",". Це обмежує можливості композиції.",[5254,7620],{},[5257,7622,7624],{"id":7623},"middleware-стандартний-патерн-у-go","Middleware: стандартний патерн у Go",[5266,7626,7628],{"id":7627},"що-таке-middleware","Що таке Middleware?",[5262,7630,7631,5720,7634,5887,7637,7640],{},[5227,7632,7633],{},"Middleware",[5207,7635,7636],{},"проміжне програмне забезпечення",[5207,7638,7639],{},"проміжний обробник",") — це функція, яка:",[6916,7642,7643,7652,7661,7667],{},[5197,7644,7645,7648,7649,7651],{},[5227,7646,7647],{},"Приймає"," ",[5215,7650,7617],{}," (наступний обробник у ланцюжку)",[5197,7653,7654,7657,7658,7660],{},[5227,7655,7656],{},"Повертає"," новий ",[5215,7659,7617],{},", що обгортає переданий",[5197,7662,7663,7666],{},[5227,7664,7665],{},"Може виконувати код"," до та\u002Fабо після виклику наступного обробника",[5197,7668,7669,7672,7673,7676],{},[5227,7670,7671],{},"Може припинити"," подальшу обробку (не викликати ",[5215,7674,7675],{},"next.ServeHTTP",")",[7678,7679,7681],"plant-uml",{"alt":7680},"Концепція Middleware",[5271,7682,7686],{"className":7683,"code":7684,"language":7685,"meta":5276,"style":5276},"language-plantuml shiki shiki-themes light-plus dark-plus dark-plus","@startuml\nskinparam style plain\nskinparam backgroundColor #FFFFFF\nskinparam sequenceMessageAlign center\n\nparticipant \"HTTP Client\" as Client #DBEAFE\nparticipant \"Middleware 1\\n(Logger)\" as MW1 #FEF3C7\nparticipant \"Middleware 2\\n(Auth)\" as MW2 #FEF3C7\nparticipant \"Final Handler\\n(Business Logic)\" as Handler #DCFCE7\n\nClient -> MW1 : HTTP Request\nactivate MW1\nnote right of MW1 #F1F5F9\n  Логує запит\n  Записує час початку\nend note\n\nMW1 -> MW2 : next.ServeHTTP(w, r)\nactivate MW2\nnote right of MW2 #F1F5F9\n  Перевіряє JWT токен\n  Додає user_id у контекст\nend note\n\nMW2 -> Handler : next.ServeHTTP(w, r)\nactivate Handler\nnote right of Handler #F1F5F9\n  Виконує бізнес-логіку\n  Формує відповідь\nend note\n\nHandler --> MW2 : return\ndeactivate Handler\n\nMW2 --> MW1 : return\ndeactivate MW2\n\nnote right of MW1 #F1F5F9\n  Логує тривалість\n  Записує статус-код\nend note\n\nMW1 --> Client : HTTP Response\ndeactivate MW1\n\n@enduml\n","plantuml",[5215,7687,7688,7693,7698,7703,7708,7712,7717,7722,7727,7732,7736,7741,7746,7751,7756,7761,7766,7770,7775,7780,7785,7790,7795,7799,7803,7808,7813,7818,7823,7828,7832,7836,7841,7846,7850,7855,7860,7864,7868,7873,7878,7882,7886,7891,7896,7900],{"__ignoreMap":5276},[5280,7689,7690],{"class":5282,"line":5283},[5280,7691,7692],{},"@startuml\n",[5280,7694,7695],{"class":5282,"line":5290},[5280,7696,7697],{},"skinparam style plain\n",[5280,7699,7700],{"class":5282,"line":5301},[5280,7701,7702],{},"skinparam backgroundColor #FFFFFF\n",[5280,7704,7705],{"class":5282,"line":5309},[5280,7706,7707],{},"skinparam sequenceMessageAlign center\n",[5280,7709,7710],{"class":5282,"line":5316},[5280,7711,5313],{"emptyLinePlaceholder":5312},[5280,7713,7714],{"class":5282,"line":5322},[5280,7715,7716],{},"participant \"HTTP Client\" as Client #DBEAFE\n",[5280,7718,7719],{"class":5282,"line":5336},[5280,7720,7721],{},"participant \"Middleware 1\\n(Logger)\" as MW1 #FEF3C7\n",[5280,7723,7724],{"class":5282,"line":5341},[5280,7725,7726],{},"participant \"Middleware 2\\n(Auth)\" as MW2 #FEF3C7\n",[5280,7728,7729],{"class":5282,"line":5347},[5280,7730,7731],{},"participant \"Final Handler\\n(Business Logic)\" as Handler #DCFCE7\n",[5280,7733,7734],{"class":5282,"line":5358},[5280,7735,5313],{"emptyLinePlaceholder":5312},[5280,7737,7738],{"class":5282,"line":5368},[5280,7739,7740],{},"Client -> MW1 : HTTP Request\n",[5280,7742,7743],{"class":5282,"line":5378},[5280,7744,7745],{},"activate MW1\n",[5280,7747,7748],{"class":5282,"line":5391},[5280,7749,7750],{},"note right of MW1 #F1F5F9\n",[5280,7752,7753],{"class":5282,"line":5705},[5280,7754,7755],{},"  Логує запит\n",[5280,7757,7758],{"class":5282,"line":5820},[5280,7759,7760],{},"  Записує час початку\n",[5280,7762,7763],{"class":5282,"line":5854},[5280,7764,7765],{},"end note\n",[5280,7767,7768],{"class":5282,"line":5859},[5280,7769,5313],{"emptyLinePlaceholder":5312},[5280,7771,7772],{"class":5282,"line":5871},[5280,7773,7774],{},"MW1 -> MW2 : next.ServeHTTP(w, r)\n",[5280,7776,7777],{"class":5282,"line":5895},[5280,7778,7779],{},"activate MW2\n",[5280,7781,7782],{"class":5282,"line":5916},[5280,7783,7784],{},"note right of MW2 #F1F5F9\n",[5280,7786,7787],{"class":5282,"line":5921},[5280,7788,7789],{},"  Перевіряє JWT токен\n",[5280,7791,7792],{"class":5282,"line":5939},[5280,7793,7794],{},"  Додає user_id у контекст\n",[5280,7796,7797],{"class":5282,"line":5973},[5280,7798,7765],{},[5280,7800,7801],{"class":5282,"line":5979},[5280,7802,5313],{"emptyLinePlaceholder":5312},[5280,7804,7805],{"class":5282,"line":5984},[5280,7806,7807],{},"MW2 -> Handler : next.ServeHTTP(w, r)\n",[5280,7809,7810],{"class":5282,"line":6023},[5280,7811,7812],{},"activate Handler\n",[5280,7814,7815],{"class":5282,"line":6042},[5280,7816,7817],{},"note right of Handler #F1F5F9\n",[5280,7819,7820],{"class":5282,"line":6093},[5280,7821,7822],{},"  Виконує бізнес-логіку\n",[5280,7824,7825],{"class":5282,"line":6098},[5280,7826,7827],{},"  Формує відповідь\n",[5280,7829,7830],{"class":5282,"line":6114},[5280,7831,7765],{},[5280,7833,7834],{"class":5282,"line":6130},[5280,7835,5313],{"emptyLinePlaceholder":5312},[5280,7837,7838],{"class":5282,"line":6159},[5280,7839,7840],{},"Handler --> MW2 : return\n",[5280,7842,7843],{"class":5282,"line":6187},[5280,7844,7845],{},"deactivate Handler\n",[5280,7847,7848],{"class":5282,"line":6193},[5280,7849,5313],{"emptyLinePlaceholder":5312},[5280,7851,7852],{"class":5282,"line":6207},[5280,7853,7854],{},"MW2 --> MW1 : return\n",[5280,7856,7857],{"class":5282,"line":6221},[5280,7858,7859],{},"deactivate MW2\n",[5280,7861,7862],{"class":5282,"line":6233},[5280,7863,5313],{"emptyLinePlaceholder":5312},[5280,7865,7866],{"class":5282,"line":6245},[5280,7867,7750],{},[5280,7869,7870],{"class":5282,"line":6251},[5280,7871,7872],{},"  Логує тривалість\n",[5280,7874,7875],{"class":5282,"line":6256},[5280,7876,7877],{},"  Записує статус-код\n",[5280,7879,7880],{"class":5282,"line":6308},[5280,7881,7765],{},[5280,7883,7884],{"class":5282,"line":6339},[5280,7885,5313],{"emptyLinePlaceholder":5312},[5280,7887,7888],{"class":5282,"line":6371},[5280,7889,7890],{},"MW1 --> Client : HTTP Response\n",[5280,7892,7893],{"class":5282,"line":6409},[5280,7894,7895],{},"deactivate MW1\n",[5280,7897,7898],{"class":5282,"line":6415},[5280,7899,5313],{"emptyLinePlaceholder":5312},[5280,7901,7902],{"class":5282,"line":6420},[5280,7903,7904],{},"@enduml\n",[5266,7906,7908],{"id":7907},"стандартна-сигнатура-middleware","Стандартна сигнатура Middleware",[5262,7910,7911],{},"У Go прийнято визначати Middleware як тип:",[5271,7913,7915],{"className":5411,"code":7914,"language":5325,"meta":5276,"style":5276},"type Middleware func(http.Handler) http.Handler\n",[5215,7916,7917],{"__ignoreMap":5276},[5280,7918,7919,7922,7925,7927,7929,7931,7933,7936,7938,7940,7942],{"class":5282,"line":5283},[5280,7920,7921],{"class":5619},"type",[5280,7923,7924],{"class":5758}," Middleware",[5280,7926,7331],{"class":5619},[5280,7928,5835],{"class":5452},[5280,7930,5952],{"class":5758},[5280,7932,5218],{"class":5452},[5280,7934,7935],{"class":5758},"Handler",[5280,7937,7315],{"class":5452},[5280,7939,5952],{"class":5758},[5280,7941,5218],{"class":5452},[5280,7943,7944],{"class":5758},"Handler\n",[5262,7946,7947],{},"Це функція, що:",[5194,7949,7950,7958],{},[5197,7951,7952,7648,7955,7957],{},[5227,7953,7954],{},"Вхід:",[5215,7956,7617],{}," (наступний обробник)",[5197,7959,7960,7648,7963,7965],{},[5227,7961,7962],{},"Вихід:",[5215,7964,7617],{}," (новий обробник з додатковою логікою)",[5712,7967,7968,7969,7971,7972,7975],{},"Інтерфейс ",[5215,7970,7617],{}," має лише один метод: ",[5215,7973,7974],{},"ServeHTTP(ResponseWriter, *Request)",". Будь-яка структура чи функція, що реалізує цей метод, є валідним HTTP-обробником.",[5254,7977],{},[5257,7979,7981],{"id":7980},"створюємо-перший-middleware-з-нуля","Створюємо перший Middleware з нуля",[5266,7983,7985],{"id":7984},"крок-1-розуміємо-httphandler","Крок 1: Розуміємо http.Handler",[5262,7987,7988,7989,5408],{},"Щоб створити Middleware, спочатку потрібно зрозуміти інтерфейс ",[5215,7990,7617],{},[5271,7992,7994],{"className":5411,"code":7993,"language":5325,"meta":5276,"style":5276},"type Handler interface {\n    ServeHTTP(ResponseWriter, *Request)\n}\n",[5215,7995,7996,8008,8024],{"__ignoreMap":5276},[5280,7997,7998,8000,8003,8006],{"class":5282,"line":5283},[5280,7999,7921],{"class":5619},[5280,8001,8002],{"class":5758}," Handler",[5280,8004,8005],{"class":5619}," interface",[5280,8007,6111],{"class":5452},[5280,8009,8010,8013,8015,8017,8020,8022],{"class":5282,"line":5290},[5280,8011,8012],{"class":5293},"    ServeHTTP",[5280,8014,5835],{"class":5452},[5280,8016,6002],{"class":5758},[5280,8018,8019],{"class":5452},", *",[5280,8021,6017],{"class":5758},[5280,8023,5598],{"class":5452},[5280,8025,8026],{"class":5282,"line":5301},[5280,8027,5976],{"class":5452},[5262,8029,8030,8031,8034],{},"Будь-який тип, що має метод ",[5215,8032,8033],{},"ServeHTTP",", може бути обробником. Наприклад:",[5271,8036,8038],{"className":5411,"code":8037,"language":5325,"meta":5276,"style":5276},"\u002F\u002F Простий обробник як структура\ntype MyHandler struct{}\n\nfunc (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n    fmt.Fprintln(w, \"Hello from MyHandler\")\n}\n\n\u002F\u002F Використання\nhttp.Handle(\"\u002Ftest\", &MyHandler{})\n",[5215,8039,8040,8045,8057,8061,8104,8124,8128,8132,8137],{"__ignoreMap":5276},[5280,8041,8042],{"class":5282,"line":5283},[5280,8043,8044],{"class":5286},"\u002F\u002F Простий обробник як структура\n",[5280,8046,8047,8049,8052,8054],{"class":5282,"line":5290},[5280,8048,7921],{"class":5619},[5280,8050,8051],{"class":5758}," MyHandler",[5280,8053,6216],{"class":5619},[5280,8055,8056],{"class":5452},"{}\n",[5280,8058,8059],{"class":5282,"line":5301},[5280,8060,5313],{"emptyLinePlaceholder":5312},[5280,8062,8063,8065,8067,8070,8073,8076,8078,8080,8082,8084,8086,8088,8090,8092,8094,8096,8098,8100,8102],{"class":5282,"line":5309},[5280,8064,5862],{"class":5619},[5280,8066,5720],{"class":5452},[5280,8068,8069],{"class":5424},"h ",[5280,8071,8072],{"class":5452},"*",[5280,8074,8075],{"class":5758},"MyHandler",[5280,8077,7315],{"class":5452},[5280,8079,8033],{"class":5293},[5280,8081,5835],{"class":5452},[5280,8083,5994],{"class":5424},[5280,8085,5997],{"class":5758},[5280,8087,5218],{"class":5452},[5280,8089,6002],{"class":5758},[5280,8091,5887],{"class":5452},[5280,8093,6007],{"class":5424},[5280,8095,6010],{"class":5452},[5280,8097,5952],{"class":5758},[5280,8099,5218],{"class":5452},[5280,8101,6017],{"class":5758},[5280,8103,6020],{"class":5452},[5280,8105,8106,8108,8110,8113,8115,8117,8119,8122],{"class":5282,"line":5316},[5280,8107,5924],{"class":5424},[5280,8109,5218],{"class":5452},[5280,8111,8112],{"class":5293},"Fprintln",[5280,8114,5835],{"class":5452},[5280,8116,5994],{"class":5424},[5280,8118,5887],{"class":5452},[5280,8120,8121],{"class":5297},"\"Hello from MyHandler\"",[5280,8123,5598],{"class":5452},[5280,8125,8126],{"class":5282,"line":5322},[5280,8127,5976],{"class":5452},[5280,8129,8130],{"class":5282,"line":5336},[5280,8131,5313],{"emptyLinePlaceholder":5312},[5280,8133,8134],{"class":5282,"line":5341},[5280,8135,8136],{"class":5286},"\u002F\u002F Використання\n",[5280,8138,8139,8141,8143,8146,8148,8151,8154,8156],{"class":5282,"line":5347},[5280,8140,5952],{"class":5424},[5280,8142,5218],{"class":5452},[5280,8144,8145],{"class":5293},"Handle",[5280,8147,5835],{"class":5452},[5280,8149,8150],{"class":5297},"\"\u002Ftest\"",[5280,8152,8153],{"class":5452},", &",[5280,8155,8075],{"class":5758},[5280,8157,8158],{"class":5452},"{})\n",[5262,8160,8161,8162,5408],{},"Але є зручніший спосіб — тип ",[5215,8163,7613],{},[5271,8165,8167],{"className":5411,"code":8166,"language":5325,"meta":5276,"style":5276},"\u002F\u002F HandlerFunc — це адаптер, що перетворює функцію на Handler\ntype HandlerFunc func(ResponseWriter, *Request)\n\nfunc (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {\n    f(w, r)\n}\n",[5215,8168,8169,8174,8193,8197,8229,8244],{"__ignoreMap":5276},[5280,8170,8171],{"class":5282,"line":5283},[5280,8172,8173],{"class":5286},"\u002F\u002F HandlerFunc — це адаптер, що перетворює функцію на Handler\n",[5280,8175,8176,8178,8181,8183,8185,8187,8189,8191],{"class":5282,"line":5290},[5280,8177,7921],{"class":5619},[5280,8179,8180],{"class":5758}," HandlerFunc",[5280,8182,7331],{"class":5619},[5280,8184,5835],{"class":5452},[5280,8186,6002],{"class":5758},[5280,8188,8019],{"class":5452},[5280,8190,6017],{"class":5758},[5280,8192,5598],{"class":5452},[5280,8194,8195],{"class":5282,"line":5301},[5280,8196,5313],{"emptyLinePlaceholder":5312},[5280,8198,8199,8201,8203,8206,8208,8210,8212,8214,8216,8219,8221,8223,8225,8227],{"class":5282,"line":5309},[5280,8200,5862],{"class":5619},[5280,8202,5720],{"class":5452},[5280,8204,8205],{"class":5424},"f ",[5280,8207,7312],{"class":5758},[5280,8209,7315],{"class":5452},[5280,8211,8033],{"class":5293},[5280,8213,5835],{"class":5452},[5280,8215,5994],{"class":5424},[5280,8217,8218],{"class":5758}," ResponseWriter",[5280,8220,5887],{"class":5452},[5280,8222,6007],{"class":5424},[5280,8224,6010],{"class":5452},[5280,8226,6017],{"class":5758},[5280,8228,6020],{"class":5452},[5280,8230,8231,8234,8236,8238,8240,8242],{"class":5282,"line":5316},[5280,8232,8233],{"class":5293},"    f",[5280,8235,5835],{"class":5452},[5280,8237,5994],{"class":5424},[5280,8239,5887],{"class":5452},[5280,8241,6007],{"class":5424},[5280,8243,5598],{"class":5452},[5280,8245,8246],{"class":5282,"line":5322},[5280,8247,5976],{"class":5452},[5262,8249,8250],{},"Завдяки цьому можна писати так:",[5271,8252,8254],{"className":5411,"code":8253,"language":5325,"meta":5276,"style":5276},"func myHandler(w http.ResponseWriter, r *http.Request) {\n    fmt.Fprintln(w, \"Hello\")\n}\n\nhttp.Handle(\"\u002Ftest\", http.HandlerFunc(myHandler))\n",[5215,8255,8256,8287,8306,8310,8314],{"__ignoreMap":5276},[5280,8257,8258,8260,8263,8265,8267,8269,8271,8273,8275,8277,8279,8281,8283,8285],{"class":5282,"line":5283},[5280,8259,5862],{"class":5619},[5280,8261,8262],{"class":5293}," myHandler",[5280,8264,5835],{"class":5452},[5280,8266,5994],{"class":5424},[5280,8268,5997],{"class":5758},[5280,8270,5218],{"class":5452},[5280,8272,6002],{"class":5758},[5280,8274,5887],{"class":5452},[5280,8276,6007],{"class":5424},[5280,8278,6010],{"class":5452},[5280,8280,5952],{"class":5758},[5280,8282,5218],{"class":5452},[5280,8284,6017],{"class":5758},[5280,8286,6020],{"class":5452},[5280,8288,8289,8291,8293,8295,8297,8299,8301,8304],{"class":5282,"line":5290},[5280,8290,5924],{"class":5424},[5280,8292,5218],{"class":5452},[5280,8294,8112],{"class":5293},[5280,8296,5835],{"class":5452},[5280,8298,5994],{"class":5424},[5280,8300,5887],{"class":5452},[5280,8302,8303],{"class":5297},"\"Hello\"",[5280,8305,5598],{"class":5452},[5280,8307,8308],{"class":5282,"line":5301},[5280,8309,5976],{"class":5452},[5280,8311,8312],{"class":5282,"line":5309},[5280,8313,5313],{"emptyLinePlaceholder":5312},[5280,8315,8316,8318,8320,8322,8324,8326,8328,8330,8332,8334,8336,8339],{"class":5282,"line":5316},[5280,8317,5952],{"class":5424},[5280,8319,5218],{"class":5452},[5280,8321,8145],{"class":5293},[5280,8323,5835],{"class":5452},[5280,8325,8150],{"class":5297},[5280,8327,5887],{"class":5452},[5280,8329,5952],{"class":5424},[5280,8331,5218],{"class":5452},[5280,8333,7312],{"class":5293},[5280,8335,5835],{"class":5452},[5280,8337,8338],{"class":5424},"myHandler",[5280,8340,5970],{"class":5452},[8342,8343,8344,8347,8348,8350,8351,8353,8354,8357,8358,5218],"tip",{},[5215,8345,8346],{},"http.HandleFunc()"," робить це автоматично — перетворює функцію на ",[5215,8349,7617],{}," через ",[5215,8352,7613],{},". Тому ",[5215,8355,8356],{},"http.HandleFunc(\"\u002Fpath\", myFunc)"," еквівалентно ",[5215,8359,8360],{},"http.Handle(\"\u002Fpath\", http.HandlerFunc(myFunc))",[5266,8362,8364],{"id":8363},"крок-2-створюємо-найпростіший-middleware","Крок 2: Створюємо найпростіший Middleware",[5262,8366,8367],{},"Створимо Middleware, що просто виводить повідомлення до та після виконання наступного обробника:",[5271,8369,8371],{"className":5411,"code":8370,"language":5325,"meta":5276,"style":5276},"\u002F\u002F middleware\u002Fsimple.go\npackage middleware\n\nimport (\n    \"fmt\"\n    \"net\u002Fhttp\"\n)\n\n\u002F\u002F Simple — найпростіший Middleware для демонстрації концепції\nfunc Simple(next http.Handler) http.Handler {\n    \u002F\u002F Повертаємо нову функцію, що обгортає next\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        fmt.Println(\"BEFORE: Middleware executed before handler\")\n        \n        \u002F\u002F Викликаємо наступний обробник у ланцюжку\n        next.ServeHTTP(w, r)\n        \n        fmt.Println(\"AFTER: Middleware executed after handler\")\n    })\n}\n",[5215,8372,8373,8378,8384,8388,8394,8398,8402,8406,8410,8415,8442,8447,8485,8500,8504,8509,8527,8531,8546,8550],{"__ignoreMap":5276},[5280,8374,8375],{"class":5282,"line":5283},[5280,8376,8377],{"class":5286},"\u002F\u002F middleware\u002Fsimple.go\n",[5280,8379,8380,8382],{"class":5282,"line":5290},[5280,8381,5755],{"class":5619},[5280,8383,5641],{"class":5758},[5280,8385,8386],{"class":5282,"line":5301},[5280,8387,5313],{"emptyLinePlaceholder":5312},[5280,8389,8390,8392],{"class":5282,"line":5309},[5280,8391,5768],{"class":5436},[5280,8393,5453],{"class":5452},[5280,8395,8396],{"class":5282,"line":5316},[5280,8397,5780],{"class":5297},[5280,8399,8400],{"class":5282,"line":5322},[5280,8401,5790],{"class":5297},[5280,8403,8404],{"class":5282,"line":5336},[5280,8405,5598],{"class":5452},[5280,8407,8408],{"class":5282,"line":5341},[5280,8409,5313],{"emptyLinePlaceholder":5312},[5280,8411,8412],{"class":5282,"line":5347},[5280,8413,8414],{"class":5286},"\u002F\u002F Simple — найпростіший Middleware для демонстрації концепції\n",[5280,8416,8417,8419,8422,8424,8426,8428,8430,8432,8434,8436,8438,8440],{"class":5282,"line":5358},[5280,8418,5862],{"class":5619},[5280,8420,8421],{"class":5293}," Simple",[5280,8423,5835],{"class":5452},[5280,8425,7305],{"class":5424},[5280,8427,5997],{"class":5758},[5280,8429,5218],{"class":5452},[5280,8431,7935],{"class":5758},[5280,8433,7315],{"class":5452},[5280,8435,5952],{"class":5758},[5280,8437,5218],{"class":5452},[5280,8439,7935],{"class":5758},[5280,8441,6111],{"class":5452},[5280,8443,8444],{"class":5282,"line":5368},[5280,8445,8446],{"class":5286},"    \u002F\u002F Повертаємо нову функцію, що обгортає next\n",[5280,8448,8449,8451,8453,8455,8457,8459,8461,8463,8465,8467,8469,8471,8473,8475,8477,8479,8481,8483],{"class":5282,"line":5378},[5280,8450,7328],{"class":5436},[5280,8452,5997],{"class":5424},[5280,8454,5218],{"class":5452},[5280,8456,7312],{"class":5293},[5280,8458,5835],{"class":5452},[5280,8460,5862],{"class":5619},[5280,8462,5835],{"class":5452},[5280,8464,5994],{"class":5424},[5280,8466,5997],{"class":5758},[5280,8468,5218],{"class":5452},[5280,8470,6002],{"class":5758},[5280,8472,5887],{"class":5452},[5280,8474,6007],{"class":5424},[5280,8476,6010],{"class":5452},[5280,8478,5952],{"class":5758},[5280,8480,5218],{"class":5452},[5280,8482,6017],{"class":5758},[5280,8484,6020],{"class":5452},[5280,8486,8487,8489,8491,8493,8495,8498],{"class":5282,"line":5391},[5280,8488,6474],{"class":5424},[5280,8490,5218],{"class":5452},[5280,8492,5929],{"class":5293},[5280,8494,5835],{"class":5452},[5280,8496,8497],{"class":5297},"\"BEFORE: Middleware executed before handler\"",[5280,8499,5598],{"class":5452},[5280,8501,8502],{"class":5282,"line":5705},[5280,8503,6190],{"class":5452},[5280,8505,8506],{"class":5282,"line":5820},[5280,8507,8508],{"class":5286},"        \u002F\u002F Викликаємо наступний обробник у ланцюжку\n",[5280,8510,8511,8513,8515,8517,8519,8521,8523,8525],{"class":5282,"line":5854},[5280,8512,7426],{"class":5424},[5280,8514,5218],{"class":5452},[5280,8516,8033],{"class":5293},[5280,8518,5835],{"class":5452},[5280,8520,5994],{"class":5424},[5280,8522,5887],{"class":5452},[5280,8524,6007],{"class":5424},[5280,8526,5598],{"class":5452},[5280,8528,8529],{"class":5282,"line":5859},[5280,8530,6190],{"class":5452},[5280,8532,8533,8535,8537,8539,8541,8544],{"class":5282,"line":5871},[5280,8534,6474],{"class":5424},[5280,8536,5218],{"class":5452},[5280,8538,5929],{"class":5293},[5280,8540,5835],{"class":5452},[5280,8542,8543],{"class":5297},"\"AFTER: Middleware executed after handler\"",[5280,8545,5598],{"class":5452},[5280,8547,8548],{"class":5282,"line":5895},[5280,8549,6834],{"class":5452},[5280,8551,8552],{"class":5282,"line":5916},[5280,8553,5976],{"class":5452},[5262,8555,8556],{},[5227,8557,8558],{},"Покрокове пояснення:",[6916,8560,8561,8572,8581,8590,8600],{},[5197,8562,8563,8566,8567,8569,8570],{},[5215,8564,8565],{},"Simple"," — це функція, що приймає ",[5215,8568,7617],{}," та повертає ",[5215,8571,7617],{},[5197,8573,8574,8575,7648,8578],{},"Всередині ми створюємо ",[5227,8576,8577],{},"анонімну функцію",[5215,8579,8580],{},"func(w http.ResponseWriter, r *http.Request)",[5197,8582,8583,8584,8587,8588],{},"Обгортаємо цю функцію в ",[5215,8585,8586],{},"http.HandlerFunc()",", щоб перетворити на ",[5215,8589,7617],{},[5197,8591,8592,8595,8596,8599],{},[5227,8593,8594],{},"ДО"," виклику ",[5215,8597,8598],{},"next.ServeHTTP()"," виконується препроцесинг",[5197,8601,8602,8595,8605,8607],{},[5227,8603,8604],{},"ПІСЛЯ",[5215,8606,8598],{}," виконується постпроцесинг",[5266,8609,8611],{"id":8610},"крок-3-використовуємо-middleware","Крок 3: Використовуємо Middleware",[5262,8613,8614],{},"Створимо повний робочий приклад:",[5271,8616,8618],{"className":5411,"code":8617,"language":5325,"meta":5276,"style":5276},"\u002F\u002F cmd\u002Fmiddleware-demo\u002Fmain.go\npackage main\n\nimport (\n    \"fmt\"\n    \"log\"\n    \"net\u002Fhttp\"\n    \n    \"myapp\u002Fmiddleware\"  \u002F\u002F Наш власний пакет\n)\n\nfunc mainHandler(w http.ResponseWriter, r *http.Request) {\n    fmt.Fprintln(w, \"Hello from main handler!\")\n    fmt.Println(\"HANDLER: Main handler executing\")\n}\n\nfunc main() {\n    \u002F\u002F Обгортаємо handler у Middleware\n    wrappedHandler := middleware.Simple(http.HandlerFunc(mainHandler))\n    \n    http.Handle(\"\u002F\", wrappedHandler)\n    \n    fmt.Println(\"Server starting on :8080...\")\n    log.Fatal(http.ListenAndServe(\":8080\", nil))\n}\n",[5215,8619,8620,8625,8631,8635,8641,8645,8649,8653,8657,8665,8669,8673,8704,8723,8738,8742,8746,8754,8759,8788,8792,8812,8816,8830,8856],{"__ignoreMap":5276},[5280,8621,8622],{"class":5282,"line":5283},[5280,8623,8624],{"class":5286},"\u002F\u002F cmd\u002Fmiddleware-demo\u002Fmain.go\n",[5280,8626,8627,8629],{"class":5282,"line":5290},[5280,8628,5755],{"class":5619},[5280,8630,5759],{"class":5758},[5280,8632,8633],{"class":5282,"line":5301},[5280,8634,5313],{"emptyLinePlaceholder":5312},[5280,8636,8637,8639],{"class":5282,"line":5309},[5280,8638,5768],{"class":5436},[5280,8640,5453],{"class":5452},[5280,8642,8643],{"class":5282,"line":5316},[5280,8644,5780],{"class":5297},[5280,8646,8647],{"class":5282,"line":5322},[5280,8648,5785],{"class":5297},[5280,8650,8651],{"class":5282,"line":5336},[5280,8652,5790],{"class":5297},[5280,8654,8655],{"class":5282,"line":5341},[5280,8656,6724],{"class":5452},[5280,8658,8659,8662],{"class":5282,"line":5347},[5280,8660,8661],{"class":5297},"    \"myapp\u002Fmiddleware\"",[5280,8663,8664],{"class":5286},"  \u002F\u002F Наш власний пакет\n",[5280,8666,8667],{"class":5282,"line":5358},[5280,8668,5598],{"class":5452},[5280,8670,8671],{"class":5282,"line":5368},[5280,8672,5313],{"emptyLinePlaceholder":5312},[5280,8674,8675,8677,8680,8682,8684,8686,8688,8690,8692,8694,8696,8698,8700,8702],{"class":5282,"line":5378},[5280,8676,5862],{"class":5619},[5280,8678,8679],{"class":5293}," mainHandler",[5280,8681,5835],{"class":5452},[5280,8683,5994],{"class":5424},[5280,8685,5997],{"class":5758},[5280,8687,5218],{"class":5452},[5280,8689,6002],{"class":5758},[5280,8691,5887],{"class":5452},[5280,8693,6007],{"class":5424},[5280,8695,6010],{"class":5452},[5280,8697,5952],{"class":5758},[5280,8699,5218],{"class":5452},[5280,8701,6017],{"class":5758},[5280,8703,6020],{"class":5452},[5280,8705,8706,8708,8710,8712,8714,8716,8718,8721],{"class":5282,"line":5391},[5280,8707,5924],{"class":5424},[5280,8709,5218],{"class":5452},[5280,8711,8112],{"class":5293},[5280,8713,5835],{"class":5452},[5280,8715,5994],{"class":5424},[5280,8717,5887],{"class":5452},[5280,8719,8720],{"class":5297},"\"Hello from main handler!\"",[5280,8722,5598],{"class":5452},[5280,8724,8725,8727,8729,8731,8733,8736],{"class":5282,"line":5705},[5280,8726,5924],{"class":5424},[5280,8728,5218],{"class":5452},[5280,8730,5929],{"class":5293},[5280,8732,5835],{"class":5452},[5280,8734,8735],{"class":5297},"\"HANDLER: Main handler executing\"",[5280,8737,5598],{"class":5452},[5280,8739,8740],{"class":5282,"line":5820},[5280,8741,5976],{"class":5452},[5280,8743,8744],{"class":5282,"line":5854},[5280,8745,5313],{"emptyLinePlaceholder":5312},[5280,8747,8748,8750,8752],{"class":5282,"line":5859},[5280,8749,5862],{"class":5619},[5280,8751,5865],{"class":5293},[5280,8753,5868],{"class":5452},[5280,8755,8756],{"class":5282,"line":5871},[5280,8757,8758],{"class":5286},"    \u002F\u002F Обгортаємо handler у Middleware\n",[5280,8760,8761,8764,8766,8769,8771,8773,8775,8777,8779,8781,8783,8786],{"class":5282,"line":5895},[5280,8762,8763],{"class":5424},"    wrappedHandler",[5280,8765,6029],{"class":5452},[5280,8767,8768],{"class":5424},"middleware",[5280,8770,5218],{"class":5452},[5280,8772,8565],{"class":5293},[5280,8774,5835],{"class":5452},[5280,8776,5952],{"class":5424},[5280,8778,5218],{"class":5452},[5280,8780,7312],{"class":5293},[5280,8782,5835],{"class":5452},[5280,8784,8785],{"class":5424},"mainHandler",[5280,8787,5970],{"class":5452},[5280,8789,8790],{"class":5282,"line":5916},[5280,8791,6724],{"class":5452},[5280,8793,8794,8796,8798,8800,8802,8805,8807,8810],{"class":5282,"line":5921},[5280,8795,5874],{"class":5424},[5280,8797,5218],{"class":5452},[5280,8799,8145],{"class":5293},[5280,8801,5835],{"class":5452},[5280,8803,8804],{"class":5297},"\"\u002F\"",[5280,8806,5887],{"class":5452},[5280,8808,8809],{"class":5424},"wrappedHandler",[5280,8811,5598],{"class":5452},[5280,8813,8814],{"class":5282,"line":5939},[5280,8815,6724],{"class":5452},[5280,8817,8818,8820,8822,8824,8826,8828],{"class":5282,"line":5973},[5280,8819,5924],{"class":5424},[5280,8821,5218],{"class":5452},[5280,8823,5929],{"class":5293},[5280,8825,5835],{"class":5452},[5280,8827,5934],{"class":5297},[5280,8829,5598],{"class":5452},[5280,8831,8832,8834,8836,8838,8840,8842,8844,8846,8848,8850,8852,8854],{"class":5282,"line":5979},[5280,8833,5942],{"class":5424},[5280,8835,5218],{"class":5452},[5280,8837,5947],{"class":5293},[5280,8839,5835],{"class":5452},[5280,8841,5952],{"class":5424},[5280,8843,5218],{"class":5452},[5280,8845,5957],{"class":5293},[5280,8847,5835],{"class":5452},[5280,8849,5962],{"class":5297},[5280,8851,5887],{"class":5452},[5280,8853,5967],{"class":5619},[5280,8855,5970],{"class":5452},[5280,8857,8858],{"class":5282,"line":5984},[5280,8859,5976],{"class":5452},[5262,8861,8862],{},[5227,8863,8864],{},"Запускаємо сервер:",[8866,8867,8870,8883],"terminal-preview",{":cursor":8868,"title":8869},"true","go run cmd\u002Fmiddleware-demo\u002Fmain.go",[8871,8872,8874,7648,8879],"div",{"className":8873},[5282],[5280,8875,8878],{"className":8876},[8877],"opacity-40","$",[5227,8880,8869],{"className":8881},[8882],"font-bold",[8871,8884,8886],{"className":8885},[5282],"Server starting on :8080...",[5262,8888,8889],{},[5227,8890,8891],{},"Виконуємо запит:",[8866,8893,8895,8904],{":cursor":8868,"title":8894},"curl http:\u002F\u002Flocalhost:8080",[8871,8896,8898,7648,8901],{"className":8897},[5282],[5280,8899,8878],{"className":8900},[8877],[5227,8902,8894],{"className":8903},[8882],[8871,8905,8907],{"className":8906},[5282],"Hello from main handler!",[5262,8909,8910],{},[5227,8911,8912],{},"У логах сервера побачимо:",[8866,8914,8917,8921,8925],{":cursor":8915,"title":8916},"false","Лог виконання",[8871,8918,8920],{"className":8919},[5282],"BEFORE: Middleware executed before handler",[8871,8922,8924],{"className":8923},[5282],"HANDLER: Main handler executing",[8871,8926,8928],{"className":8927},[5282],"AFTER: Middleware executed after handler",[5712,8930,8931,8932,8937,8938,8941],{},"Ключовий момент: ",[5227,8933,8934,8935],{},"анонімна функція всередині Middleware має доступ до змінної ",[5215,8936,7305],{}," завдяки замиканню (",[5207,8939,8940],{},"closure","). Це дозволяє їй викликати наступний обробник пізніше, коли прийде HTTP-запит.",[5254,8943],{},[5257,8945,8947],{"id":8946},"реальний-приклад-middleware-логування","Реальний приклад: Middleware логування",[5262,8949,8950],{},"Тепер створимо корисний Middleware, що логує кожен HTTP-запит з деталями:",[5271,8952,8954],{"className":5411,"code":8953,"language":5325,"meta":5276,"style":5276},"\u002F\u002F middleware\u002Flogger.go\npackage middleware\n\nimport (\n    \"log\"\n    \"net\u002Fhttp\"\n    \"time\"\n)\n\n\u002F\u002F Logger — Middleware для логування HTTP-запитів\nfunc Logger(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        \u002F\u002F Записуємо час початку обробки\n        start := time.Now()\n        \n        \u002F\u002F Логуємо деталі запиту\n        log.Printf(\n            \"[START] Method=%s Path=%s RemoteAddr=%s\",\n            r.Method,\n            r.URL.Path,\n            r.RemoteAddr,\n        )\n        \n        \u002F\u002F Викликаємо наступний обробник\n        next.ServeHTTP(w, r)\n        \n        \u002F\u002F Обчислюємо тривалість обробки\n        duration := time.Since(start)\n        \n        \u002F\u002F Логуємо завершення\n        log.Printf(\n            \"[END] Method=%s Path=%s Duration=%v\",\n            r.Method,\n            r.URL.Path,\n            duration,\n        )\n    })\n}\n",[5215,8955,8956,8961,8967,8971,8977,8981,8985,8989,8993,8997,9002,9029,9067,9072,9086,9090,9095,9106,9127,9138,9152,9163,9168,9172,9177,9195,9199,9204,9223,9227,9232,9242,9262,9272,9286,9293,9297,9301],{"__ignoreMap":5276},[5280,8957,8958],{"class":5282,"line":5283},[5280,8959,8960],{"class":5286},"\u002F\u002F middleware\u002Flogger.go\n",[5280,8962,8963,8965],{"class":5282,"line":5290},[5280,8964,5755],{"class":5619},[5280,8966,5641],{"class":5758},[5280,8968,8969],{"class":5282,"line":5301},[5280,8970,5313],{"emptyLinePlaceholder":5312},[5280,8972,8973,8975],{"class":5282,"line":5309},[5280,8974,5768],{"class":5436},[5280,8976,5453],{"class":5452},[5280,8978,8979],{"class":5282,"line":5316},[5280,8980,5785],{"class":5297},[5280,8982,8983],{"class":5282,"line":5322},[5280,8984,5790],{"class":5297},[5280,8986,8987],{"class":5282,"line":5336},[5280,8988,5795],{"class":5297},[5280,8990,8991],{"class":5282,"line":5341},[5280,8992,5598],{"class":5452},[5280,8994,8995],{"class":5282,"line":5347},[5280,8996,5313],{"emptyLinePlaceholder":5312},[5280,8998,8999],{"class":5282,"line":5358},[5280,9000,9001],{"class":5286},"\u002F\u002F Logger — Middleware для логування HTTP-запитів\n",[5280,9003,9004,9006,9009,9011,9013,9015,9017,9019,9021,9023,9025,9027],{"class":5282,"line":5368},[5280,9005,5862],{"class":5619},[5280,9007,9008],{"class":5293}," Logger",[5280,9010,5835],{"class":5452},[5280,9012,7305],{"class":5424},[5280,9014,5997],{"class":5758},[5280,9016,5218],{"class":5452},[5280,9018,7935],{"class":5758},[5280,9020,7315],{"class":5452},[5280,9022,5952],{"class":5758},[5280,9024,5218],{"class":5452},[5280,9026,7935],{"class":5758},[5280,9028,6111],{"class":5452},[5280,9030,9031,9033,9035,9037,9039,9041,9043,9045,9047,9049,9051,9053,9055,9057,9059,9061,9063,9065],{"class":5282,"line":5378},[5280,9032,7328],{"class":5436},[5280,9034,5997],{"class":5424},[5280,9036,5218],{"class":5452},[5280,9038,7312],{"class":5293},[5280,9040,5835],{"class":5452},[5280,9042,5862],{"class":5619},[5280,9044,5835],{"class":5452},[5280,9046,5994],{"class":5424},[5280,9048,5997],{"class":5758},[5280,9050,5218],{"class":5452},[5280,9052,6002],{"class":5758},[5280,9054,5887],{"class":5452},[5280,9056,6007],{"class":5424},[5280,9058,6010],{"class":5452},[5280,9060,5952],{"class":5758},[5280,9062,5218],{"class":5452},[5280,9064,6017],{"class":5758},[5280,9066,6020],{"class":5452},[5280,9068,9069],{"class":5282,"line":5391},[5280,9070,9071],{"class":5286},"        \u002F\u002F Записуємо час початку обробки\n",[5280,9073,9074,9076,9078,9080,9082,9084],{"class":5282,"line":5705},[5280,9075,7360],{"class":5424},[5280,9077,6029],{"class":5452},[5280,9079,5587],{"class":5424},[5280,9081,5218],{"class":5452},[5280,9083,6036],{"class":5293},[5280,9085,6039],{"class":5452},[5280,9087,9088],{"class":5282,"line":5820},[5280,9089,6190],{"class":5452},[5280,9091,9092],{"class":5282,"line":5854},[5280,9093,9094],{"class":5286},"        \u002F\u002F Логуємо деталі запиту\n",[5280,9096,9097,9099,9101,9103],{"class":5282,"line":5859},[5280,9098,7039],{"class":5424},[5280,9100,5218],{"class":5452},[5280,9102,6049],{"class":5293},[5280,9104,9105],{"class":5452},"(\n",[5280,9107,9108,9111,9113,9116,9118,9121,9123,9125],{"class":5282,"line":5871},[5280,9109,9110],{"class":5297},"            \"[START] Method=",[5280,9112,6057],{"class":5424},[5280,9114,9115],{"class":5297}," Path=",[5280,9117,6057],{"class":5424},[5280,9119,9120],{"class":5297}," RemoteAddr=",[5280,9122,6057],{"class":5424},[5280,9124,6584],{"class":5297},[5280,9126,6796],{"class":5452},[5280,9128,9129,9132,9134,9136],{"class":5282,"line":5895},[5280,9130,9131],{"class":5424},"            r",[5280,9133,5218],{"class":5452},[5280,9135,6074],{"class":5424},[5280,9137,6796],{"class":5452},[5280,9139,9140,9142,9144,9146,9148,9150],{"class":5282,"line":5916},[5280,9141,9131],{"class":5424},[5280,9143,5218],{"class":5452},[5280,9145,6083],{"class":5424},[5280,9147,5218],{"class":5452},[5280,9149,6088],{"class":5424},[5280,9151,6796],{"class":5452},[5280,9153,9154,9156,9158,9161],{"class":5282,"line":5921},[5280,9155,9131],{"class":5424},[5280,9157,5218],{"class":5452},[5280,9159,9160],{"class":5424},"RemoteAddr",[5280,9162,6796],{"class":5452},[5280,9164,9165],{"class":5282,"line":5939},[5280,9166,9167],{"class":5452},"        )\n",[5280,9169,9170],{"class":5282,"line":5973},[5280,9171,6190],{"class":5452},[5280,9173,9174],{"class":5282,"line":5979},[5280,9175,9176],{"class":5286},"        \u002F\u002F Викликаємо наступний обробник\n",[5280,9178,9179,9181,9183,9185,9187,9189,9191,9193],{"class":5282,"line":5984},[5280,9180,7426],{"class":5424},[5280,9182,5218],{"class":5452},[5280,9184,8033],{"class":5293},[5280,9186,5835],{"class":5452},[5280,9188,5994],{"class":5424},[5280,9190,5887],{"class":5452},[5280,9192,6007],{"class":5424},[5280,9194,5598],{"class":5452},[5280,9196,9197],{"class":5282,"line":6023},[5280,9198,6190],{"class":5452},[5280,9200,9201],{"class":5282,"line":6042},[5280,9202,9203],{"class":5286},"        \u002F\u002F Обчислюємо тривалість обробки\n",[5280,9205,9206,9209,9211,9213,9215,9217,9219,9221],{"class":5282,"line":6093},[5280,9207,9208],{"class":5424},"        duration",[5280,9210,6029],{"class":5452},[5280,9212,5587],{"class":5424},[5280,9214,5218],{"class":5452},[5280,9216,6399],{"class":5293},[5280,9218,5835],{"class":5452},[5280,9220,6404],{"class":5424},[5280,9222,5598],{"class":5452},[5280,9224,9225],{"class":5282,"line":6098},[5280,9226,6190],{"class":5452},[5280,9228,9229],{"class":5282,"line":6114},[5280,9230,9231],{"class":5286},"        \u002F\u002F Логуємо завершення\n",[5280,9233,9234,9236,9238,9240],{"class":5282,"line":6130},[5280,9235,7039],{"class":5424},[5280,9237,5218],{"class":5452},[5280,9239,6049],{"class":5293},[5280,9241,9105],{"class":5452},[5280,9243,9244,9247,9249,9251,9253,9256,9258,9260],{"class":5282,"line":6159},[5280,9245,9246],{"class":5297},"            \"[END] Method=",[5280,9248,6057],{"class":5424},[5280,9250,9115],{"class":5297},[5280,9252,6057],{"class":5424},[5280,9254,9255],{"class":5297}," Duration=",[5280,9257,6362],{"class":5424},[5280,9259,6584],{"class":5297},[5280,9261,6796],{"class":5452},[5280,9263,9264,9266,9268,9270],{"class":5282,"line":6187},[5280,9265,9131],{"class":5424},[5280,9267,5218],{"class":5452},[5280,9269,6074],{"class":5424},[5280,9271,6796],{"class":5452},[5280,9273,9274,9276,9278,9280,9282,9284],{"class":5282,"line":6193},[5280,9275,9131],{"class":5424},[5280,9277,5218],{"class":5452},[5280,9279,6083],{"class":5424},[5280,9281,5218],{"class":5452},[5280,9283,6088],{"class":5424},[5280,9285,6796],{"class":5452},[5280,9287,9288,9291],{"class":5282,"line":6207},[5280,9289,9290],{"class":5424},"            duration",[5280,9292,6796],{"class":5452},[5280,9294,9295],{"class":5282,"line":6221},[5280,9296,9167],{"class":5452},[5280,9298,9299],{"class":5282,"line":6233},[5280,9300,6834],{"class":5452},[5280,9302,9303],{"class":5282,"line":6245},[5280,9304,5976],{"class":5452},[5266,9306,9308],{"id":9307},"проблема-як-дізнатися-статус-код-відповіді","Проблема: як дізнатися статус-код відповіді?",[5262,9310,9311,9312,9318,9319,5408],{},"Поточна реалізація не знає, який статус-код повернув обробник. Для цього потрібно ",[5227,9313,9314,9315],{},"обгорнути ",[5215,9316,9317],{},"http.ResponseWriter"," у власний тип, що перехоплює виклик ",[5215,9320,9321],{},"WriteHeader()",[5271,9323,9325],{"className":5411,"code":9324,"language":5325,"meta":5276,"style":5276},"\u002F\u002F middleware\u002Flogger.go (покращена версія)\npackage middleware\n\nimport (\n    \"log\"\n    \"net\u002Fhttp\"\n    \"time\"\n)\n\n\u002F\u002F responseWriter — обгортка для http.ResponseWriter, що зберігає статус-код\ntype responseWriter struct {\n    http.ResponseWriter\n    statusCode int\n    written    bool\n}\n\n\u002F\u002F WriteHeader перехоплює виклик та зберігає статус-код\nfunc (rw *responseWriter) WriteHeader(code int) {\n    if !rw.written {\n        rw.statusCode = code\n        rw.written = true\n        rw.ResponseWriter.WriteHeader(code)\n    }\n}\n\n\u002F\u002F Write перехоплює запис даних (якщо WriteHeader не викликано, статус 200)\nfunc (rw *responseWriter) Write(b []byte) (int, error) {\n    if !rw.written {\n        rw.statusCode = http.StatusOK\n        rw.written = true\n    }\n    return rw.ResponseWriter.Write(b)\n}\n\n\u002F\u002F Logger — Middleware з логуванням статус-коду\nfunc Logger(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        start := time.Now()\n        \n        \u002F\u002F Обгортаємо ResponseWriter\n        wrapped := &responseWriter{\n            ResponseWriter: w,\n            statusCode:     http.StatusOK, \u002F\u002F за замовчуванням 200\n            written:        false,\n        }\n        \n        log.Printf(\n            \"[→] %s %s from %s\",\n            r.Method,\n            r.URL.Path,\n            r.RemoteAddr,\n        )\n        \n        \u002F\u002F Викликаємо наступний обробник з обгорнутим writer\n        next.ServeHTTP(wrapped, r)\n        \n        duration := time.Since(start)\n        \n        log.Printf(\n            \"[←] %s %s → %d (%v)\",\n            r.Method,\n            r.URL.Path,\n            wrapped.statusCode,\n            duration,\n        )\n    })\n}\n",[5215,9326,9327,9332,9338,9342,9348,9352,9356,9360,9364,9368,9373,9384,9393,9401,9409,9413,9417,9422,9449,9466,9481,9494,9512,9516,9520,9524,9529,9570,9584,9601,9613,9617,9638,9642,9646,9651,9677,9715,9729,9733,9738,9750,9761,9781,9793,9797,9801,9811,9830,9840,9854,9864,9868,9872,9877,9896,9900,9918,9922,9932,9956,9966,9980,9991,9997,10001,10005],{"__ignoreMap":5276},[5280,9328,9329],{"class":5282,"line":5283},[5280,9330,9331],{"class":5286},"\u002F\u002F middleware\u002Flogger.go (покращена версія)\n",[5280,9333,9334,9336],{"class":5282,"line":5290},[5280,9335,5755],{"class":5619},[5280,9337,5641],{"class":5758},[5280,9339,9340],{"class":5282,"line":5301},[5280,9341,5313],{"emptyLinePlaceholder":5312},[5280,9343,9344,9346],{"class":5282,"line":5309},[5280,9345,5768],{"class":5436},[5280,9347,5453],{"class":5452},[5280,9349,9350],{"class":5282,"line":5316},[5280,9351,5785],{"class":5297},[5280,9353,9354],{"class":5282,"line":5322},[5280,9355,5790],{"class":5297},[5280,9357,9358],{"class":5282,"line":5336},[5280,9359,5795],{"class":5297},[5280,9361,9362],{"class":5282,"line":5341},[5280,9363,5598],{"class":5452},[5280,9365,9366],{"class":5282,"line":5347},[5280,9367,5313],{"emptyLinePlaceholder":5312},[5280,9369,9370],{"class":5282,"line":5358},[5280,9371,9372],{"class":5286},"\u002F\u002F responseWriter — обгортка для http.ResponseWriter, що зберігає статус-код\n",[5280,9374,9375,9377,9380,9382],{"class":5282,"line":5368},[5280,9376,7921],{"class":5619},[5280,9378,9379],{"class":5758}," responseWriter",[5280,9381,6216],{"class":5619},[5280,9383,6111],{"class":5452},[5280,9385,9386,9388,9390],{"class":5282,"line":5378},[5280,9387,5874],{"class":5758},[5280,9389,5218],{"class":5452},[5280,9391,9392],{"class":5758},"ResponseWriter\n",[5280,9394,9395,9398],{"class":5282,"line":5391},[5280,9396,9397],{"class":5424},"    statusCode",[5280,9399,9400],{"class":5758}," int\n",[5280,9402,9403,9406],{"class":5282,"line":5705},[5280,9404,9405],{"class":5424},"    written",[5280,9407,9408],{"class":5758},"    bool\n",[5280,9410,9411],{"class":5282,"line":5820},[5280,9412,5976],{"class":5452},[5280,9414,9415],{"class":5282,"line":5854},[5280,9416,5313],{"emptyLinePlaceholder":5312},[5280,9418,9419],{"class":5282,"line":5859},[5280,9420,9421],{"class":5286},"\u002F\u002F WriteHeader перехоплює виклик та зберігає статус-код\n",[5280,9423,9424,9426,9428,9431,9433,9436,9438,9440,9442,9444,9447],{"class":5282,"line":5871},[5280,9425,5862],{"class":5619},[5280,9427,5720],{"class":5452},[5280,9429,9430],{"class":5424},"rw ",[5280,9432,8072],{"class":5452},[5280,9434,9435],{"class":5758},"responseWriter",[5280,9437,7315],{"class":5452},[5280,9439,6457],{"class":5293},[5280,9441,5835],{"class":5452},[5280,9443,5215],{"class":5424},[5280,9445,9446],{"class":5758}," int",[5280,9448,6020],{"class":5452},[5280,9450,9451,9453,9456,9459,9461,9464],{"class":5282,"line":5895},[5280,9452,7023],{"class":5436},[5280,9454,9455],{"class":5452}," !",[5280,9457,9458],{"class":5424},"rw",[5280,9460,5218],{"class":5452},[5280,9462,9463],{"class":5424},"written",[5280,9465,6111],{"class":5452},[5280,9467,9468,9471,9473,9476,9478],{"class":5282,"line":5916},[5280,9469,9470],{"class":5424},"        rw",[5280,9472,5218],{"class":5452},[5280,9474,9475],{"class":5424},"statusCode",[5280,9477,5829],{"class":5452},[5280,9479,9480],{"class":5424},"code\n",[5280,9482,9483,9485,9487,9489,9491],{"class":5282,"line":5921},[5280,9484,9470],{"class":5424},[5280,9486,5218],{"class":5452},[5280,9488,9463],{"class":5424},[5280,9490,5829],{"class":5452},[5280,9492,9493],{"class":5619},"true\n",[5280,9495,9496,9498,9500,9502,9504,9506,9508,9510],{"class":5282,"line":5939},[5280,9497,9470],{"class":5424},[5280,9499,5218],{"class":5452},[5280,9501,6002],{"class":5424},[5280,9503,5218],{"class":5452},[5280,9505,6457],{"class":5293},[5280,9507,5835],{"class":5452},[5280,9509,5215],{"class":5424},[5280,9511,5598],{"class":5452},[5280,9513,9514],{"class":5282,"line":5973},[5280,9515,6553],{"class":5452},[5280,9517,9518],{"class":5282,"line":5979},[5280,9519,5976],{"class":5452},[5280,9521,9522],{"class":5282,"line":5984},[5280,9523,5313],{"emptyLinePlaceholder":5312},[5280,9525,9526],{"class":5282,"line":6023},[5280,9527,9528],{"class":5286},"\u002F\u002F Write перехоплює запис даних (якщо WriteHeader не викликано, статус 200)\n",[5280,9530,9531,9533,9535,9537,9539,9541,9543,9546,9548,9551,9554,9557,9560,9563,9565,9568],{"class":5282,"line":6042},[5280,9532,5862],{"class":5619},[5280,9534,5720],{"class":5452},[5280,9536,9430],{"class":5424},[5280,9538,8072],{"class":5452},[5280,9540,9435],{"class":5758},[5280,9542,7315],{"class":5452},[5280,9544,9545],{"class":5293},"Write",[5280,9547,5835],{"class":5452},[5280,9549,9550],{"class":5424},"b",[5280,9552,9553],{"class":5452}," []",[5280,9555,9556],{"class":5758},"byte",[5280,9558,9559],{"class":5452},") (",[5280,9561,9562],{"class":5758},"int",[5280,9564,5887],{"class":5452},[5280,9566,9567],{"class":5758},"error",[5280,9569,6020],{"class":5452},[5280,9571,9572,9574,9576,9578,9580,9582],{"class":5282,"line":6093},[5280,9573,7023],{"class":5436},[5280,9575,9455],{"class":5452},[5280,9577,9458],{"class":5424},[5280,9579,5218],{"class":5452},[5280,9581,9463],{"class":5424},[5280,9583,6111],{"class":5452},[5280,9585,9586,9588,9590,9592,9594,9596,9598],{"class":5282,"line":6098},[5280,9587,9470],{"class":5424},[5280,9589,5218],{"class":5452},[5280,9591,9475],{"class":5424},[5280,9593,5829],{"class":5452},[5280,9595,5952],{"class":5424},[5280,9597,5218],{"class":5452},[5280,9599,9600],{"class":5424},"StatusOK\n",[5280,9602,9603,9605,9607,9609,9611],{"class":5282,"line":6114},[5280,9604,9470],{"class":5424},[5280,9606,5218],{"class":5452},[5280,9608,9463],{"class":5424},[5280,9610,5829],{"class":5452},[5280,9612,9493],{"class":5619},[5280,9614,9615],{"class":5282,"line":6130},[5280,9616,6553],{"class":5452},[5280,9618,9619,9621,9624,9626,9628,9630,9632,9634,9636],{"class":5282,"line":6159},[5280,9620,7328],{"class":5436},[5280,9622,9623],{"class":5424}," rw",[5280,9625,5218],{"class":5452},[5280,9627,6002],{"class":5424},[5280,9629,5218],{"class":5452},[5280,9631,9545],{"class":5293},[5280,9633,5835],{"class":5452},[5280,9635,9550],{"class":5424},[5280,9637,5598],{"class":5452},[5280,9639,9640],{"class":5282,"line":6187},[5280,9641,5976],{"class":5452},[5280,9643,9644],{"class":5282,"line":6193},[5280,9645,5313],{"emptyLinePlaceholder":5312},[5280,9647,9648],{"class":5282,"line":6207},[5280,9649,9650],{"class":5286},"\u002F\u002F Logger — Middleware з логуванням статус-коду\n",[5280,9652,9653,9655,9657,9659,9661,9663,9665,9667,9669,9671,9673,9675],{"class":5282,"line":6221},[5280,9654,5862],{"class":5619},[5280,9656,9008],{"class":5293},[5280,9658,5835],{"class":5452},[5280,9660,7305],{"class":5424},[5280,9662,5997],{"class":5758},[5280,9664,5218],{"class":5452},[5280,9666,7935],{"class":5758},[5280,9668,7315],{"class":5452},[5280,9670,5952],{"class":5758},[5280,9672,5218],{"class":5452},[5280,9674,7935],{"class":5758},[5280,9676,6111],{"class":5452},[5280,9678,9679,9681,9683,9685,9687,9689,9691,9693,9695,9697,9699,9701,9703,9705,9707,9709,9711,9713],{"class":5282,"line":6233},[5280,9680,7328],{"class":5436},[5280,9682,5997],{"class":5424},[5280,9684,5218],{"class":5452},[5280,9686,7312],{"class":5293},[5280,9688,5835],{"class":5452},[5280,9690,5862],{"class":5619},[5280,9692,5835],{"class":5452},[5280,9694,5994],{"class":5424},[5280,9696,5997],{"class":5758},[5280,9698,5218],{"class":5452},[5280,9700,6002],{"class":5758},[5280,9702,5887],{"class":5452},[5280,9704,6007],{"class":5424},[5280,9706,6010],{"class":5452},[5280,9708,5952],{"class":5758},[5280,9710,5218],{"class":5452},[5280,9712,6017],{"class":5758},[5280,9714,6020],{"class":5452},[5280,9716,9717,9719,9721,9723,9725,9727],{"class":5282,"line":6245},[5280,9718,7360],{"class":5424},[5280,9720,6029],{"class":5452},[5280,9722,5587],{"class":5424},[5280,9724,5218],{"class":5452},[5280,9726,6036],{"class":5293},[5280,9728,6039],{"class":5452},[5280,9730,9731],{"class":5282,"line":6251},[5280,9732,6190],{"class":5452},[5280,9734,9735],{"class":5282,"line":6256},[5280,9736,9737],{"class":5286},"        \u002F\u002F Обгортаємо ResponseWriter\n",[5280,9739,9740,9743,9746,9748],{"class":5282,"line":6308},[5280,9741,9742],{"class":5424},"        wrapped",[5280,9744,9745],{"class":5452}," := &",[5280,9747,9435],{"class":5758},[5280,9749,6781],{"class":5452},[5280,9751,9752,9755,9757,9759],{"class":5282,"line":6339},[5280,9753,9754],{"class":5424},"            ResponseWriter",[5280,9756,6790],{"class":5452},[5280,9758,5994],{"class":5424},[5280,9760,6796],{"class":5452},[5280,9762,9763,9766,9769,9771,9773,9776,9778],{"class":5282,"line":6371},[5280,9764,9765],{"class":5424},"            statusCode",[5280,9767,9768],{"class":5452},":     ",[5280,9770,5952],{"class":5424},[5280,9772,5218],{"class":5452},[5280,9774,9775],{"class":5424},"StatusOK",[5280,9777,5887],{"class":5452},[5280,9779,9780],{"class":5286},"\u002F\u002F за замовчуванням 200\n",[5280,9782,9783,9786,9789,9791],{"class":5282,"line":6409},[5280,9784,9785],{"class":5424},"            written",[5280,9787,9788],{"class":5452},":        ",[5280,9790,8915],{"class":5619},[5280,9792,6796],{"class":5452},[5280,9794,9795],{"class":5282,"line":6415},[5280,9796,6248],{"class":5452},[5280,9798,9799],{"class":5282,"line":6420},[5280,9800,6190],{"class":5452},[5280,9802,9803,9805,9807,9809],{"class":5282,"line":6425},[5280,9804,7039],{"class":5424},[5280,9806,5218],{"class":5452},[5280,9808,6049],{"class":5293},[5280,9810,9105],{"class":5452},[5280,9812,9813,9816,9818,9821,9824,9826,9828],{"class":5282,"line":6450},[5280,9814,9815],{"class":5297},"            \"[→] ",[5280,9817,6057],{"class":5424},[5280,9819,9820],{"class":5424}," %s",[5280,9822,9823],{"class":5297}," from ",[5280,9825,6057],{"class":5424},[5280,9827,6584],{"class":5297},[5280,9829,6796],{"class":5452},[5280,9831,9832,9834,9836,9838],{"class":5282,"line":6471},[5280,9833,9131],{"class":5424},[5280,9835,5218],{"class":5452},[5280,9837,6074],{"class":5424},[5280,9839,6796],{"class":5452},[5280,9841,9842,9844,9846,9848,9850,9852],{"class":5282,"line":6507},[5280,9843,9131],{"class":5424},[5280,9845,5218],{"class":5452},[5280,9847,6083],{"class":5424},[5280,9849,5218],{"class":5452},[5280,9851,6088],{"class":5424},[5280,9853,6796],{"class":5452},[5280,9855,9856,9858,9860,9862],{"class":5282,"line":6512},[5280,9857,9131],{"class":5424},[5280,9859,5218],{"class":5452},[5280,9861,9160],{"class":5424},[5280,9863,6796],{"class":5452},[5280,9865,9866],{"class":5282,"line":6520},[5280,9867,9167],{"class":5452},[5280,9869,9870],{"class":5282,"line":6550},[5280,9871,6190],{"class":5452},[5280,9873,9874],{"class":5282,"line":6556},[5280,9875,9876],{"class":5286},"        \u002F\u002F Викликаємо наступний обробник з обгорнутим writer\n",[5280,9878,9879,9881,9883,9885,9887,9890,9892,9894],{"class":5282,"line":6561},[5280,9880,7426],{"class":5424},[5280,9882,5218],{"class":5452},[5280,9884,8033],{"class":5293},[5280,9886,5835],{"class":5452},[5280,9888,9889],{"class":5424},"wrapped",[5280,9891,5887],{"class":5452},[5280,9893,6007],{"class":5424},[5280,9895,5598],{"class":5452},[5280,9897,9898],{"class":5282,"line":6621},[5280,9899,6190],{"class":5452},[5280,9901,9902,9904,9906,9908,9910,9912,9914,9916],{"class":5282,"line":6626},[5280,9903,9208],{"class":5424},[5280,9905,6029],{"class":5452},[5280,9907,5587],{"class":5424},[5280,9909,5218],{"class":5452},[5280,9911,6399],{"class":5293},[5280,9913,5835],{"class":5452},[5280,9915,6404],{"class":5424},[5280,9917,5598],{"class":5452},[5280,9919,9920],{"class":5282,"line":6631},[5280,9921,6190],{"class":5452},[5280,9923,9924,9926,9928,9930],{"class":5282,"line":6663},[5280,9925,7039],{"class":5424},[5280,9927,5218],{"class":5452},[5280,9929,6049],{"class":5293},[5280,9931,9105],{"class":5452},[5280,9933,9934,9937,9939,9941,9944,9947,9949,9951,9954],{"class":5282,"line":6678},[5280,9935,9936],{"class":5297},"            \"[←] ",[5280,9938,6057],{"class":5424},[5280,9940,9820],{"class":5424},[5280,9942,9943],{"class":5297}," → ",[5280,9945,9946],{"class":5424},"%d",[5280,9948,5720],{"class":5297},[5280,9950,6362],{"class":5424},[5280,9952,9953],{"class":5297},")\"",[5280,9955,6796],{"class":5452},[5280,9957,9958,9960,9962,9964],{"class":5282,"line":6721},[5280,9959,9131],{"class":5424},[5280,9961,5218],{"class":5452},[5280,9963,6074],{"class":5424},[5280,9965,6796],{"class":5452},[5280,9967,9968,9970,9972,9974,9976,9978],{"class":5282,"line":6727},[5280,9969,9131],{"class":5424},[5280,9971,5218],{"class":5452},[5280,9973,6083],{"class":5424},[5280,9975,5218],{"class":5452},[5280,9977,6088],{"class":5424},[5280,9979,6796],{"class":5452},[5280,9981,9982,9985,9987,9989],{"class":5282,"line":6751},[5280,9983,9984],{"class":5424},"            wrapped",[5280,9986,5218],{"class":5452},[5280,9988,9475],{"class":5424},[5280,9990,6796],{"class":5452},[5280,9992,9993,9995],{"class":5282,"line":6784},[5280,9994,9290],{"class":5424},[5280,9996,6796],{"class":5452},[5280,9998,9999],{"class":5282,"line":6799},[5280,10000,9167],{"class":5452},[5280,10002,10003],{"class":5282,"line":6831},[5280,10004,6834],{"class":5452},[5280,10006,10007],{"class":5282,"line":6837},[5280,10008,5976],{"class":5452},[5712,10010,10011,5720,10014,7315,10017,10019,10020,10022,10023,5218],{},[5227,10012,10013],{},"Ембединг",[5207,10015,10016],{},"embedding",[5215,10018,9317],{}," у нашу структуру дозволяє автоматично делегувати всі інші методи інтерфейсу оригінальному writer. Ми перевизначаємо лише ",[5215,10021,6457],{}," та ",[5215,10024,9545],{},[5266,10026,10028],{"id":10027},"повний-робочий-приклад-з-логуванням","Повний робочий приклад з логуванням",[5271,10030,10032],{"className":5411,"code":10031,"language":5325,"meta":5276,"style":5276},"\u002F\u002F cmd\u002Flogger-server\u002Fmain.go\npackage main\n\nimport (\n    \"encoding\u002Fjson\"\n    \"fmt\"\n    \"log\"\n    \"net\u002Fhttp\"\n    \"myapp\u002Fmiddleware\"\n)\n\nfunc healthHandler(w http.ResponseWriter, r *http.Request) {\n    w.Header().Set(\"Content-Type\", \"application\u002Fjson\")\n    json.NewEncoder(w).Encode(map[string]string{\n        \"status\": \"healthy\",\n    })\n}\n\nfunc errorHandler(w http.ResponseWriter, r *http.Request) {\n    http.Error(w, \"Something went wrong\", http.StatusInternalServerError)\n}\n\nfunc main() {\n    \u002F\u002F Обгортаємо кожен handler у Logger Middleware\n    http.Handle(\"\u002Fhealth\", middleware.Logger(http.HandlerFunc(healthHandler)))\n    http.Handle(\"\u002Ferror\", middleware.Logger(http.HandlerFunc(errorHandler)))\n    \n    fmt.Println(\"Server with logging starting on :8080...\")\n    log.Fatal(http.ListenAndServe(\":8080\", nil))\n}\n",[5215,10033,10034,10039,10045,10049,10055,10059,10063,10067,10071,10076,10080,10084,10115,10137,10167,10178,10182,10186,10190,10221,10249,10253,10257,10265,10270,10308,10344,10348,10363,10389],{"__ignoreMap":5276},[5280,10035,10036],{"class":5282,"line":5283},[5280,10037,10038],{"class":5286},"\u002F\u002F cmd\u002Flogger-server\u002Fmain.go\n",[5280,10040,10041,10043],{"class":5282,"line":5290},[5280,10042,5755],{"class":5619},[5280,10044,5759],{"class":5758},[5280,10046,10047],{"class":5282,"line":5301},[5280,10048,5313],{"emptyLinePlaceholder":5312},[5280,10050,10051,10053],{"class":5282,"line":5309},[5280,10052,5768],{"class":5436},[5280,10054,5453],{"class":5452},[5280,10056,10057],{"class":5282,"line":5316},[5280,10058,5775],{"class":5297},[5280,10060,10061],{"class":5282,"line":5322},[5280,10062,5780],{"class":5297},[5280,10064,10065],{"class":5282,"line":5336},[5280,10066,5785],{"class":5297},[5280,10068,10069],{"class":5282,"line":5341},[5280,10070,5790],{"class":5297},[5280,10072,10073],{"class":5282,"line":5347},[5280,10074,10075],{"class":5297},"    \"myapp\u002Fmiddleware\"\n",[5280,10077,10078],{"class":5282,"line":5358},[5280,10079,5598],{"class":5452},[5280,10081,10082],{"class":5282,"line":5368},[5280,10083,5313],{"emptyLinePlaceholder":5312},[5280,10085,10086,10088,10091,10093,10095,10097,10099,10101,10103,10105,10107,10109,10111,10113],{"class":5282,"line":5378},[5280,10087,5862],{"class":5619},[5280,10089,10090],{"class":5293}," healthHandler",[5280,10092,5835],{"class":5452},[5280,10094,5994],{"class":5424},[5280,10096,5997],{"class":5758},[5280,10098,5218],{"class":5452},[5280,10100,6002],{"class":5758},[5280,10102,5887],{"class":5452},[5280,10104,6007],{"class":5424},[5280,10106,6010],{"class":5452},[5280,10108,5952],{"class":5758},[5280,10110,5218],{"class":5452},[5280,10112,6017],{"class":5758},[5280,10114,6020],{"class":5452},[5280,10116,10117,10119,10121,10123,10125,10127,10129,10131,10133,10135],{"class":5282,"line":5391},[5280,10118,6730],{"class":5424},[5280,10120,5218],{"class":5452},[5280,10122,6138],{"class":5293},[5280,10124,6141],{"class":5452},[5280,10126,6144],{"class":5293},[5280,10128,5835],{"class":5452},[5280,10130,6149],{"class":5297},[5280,10132,5887],{"class":5452},[5280,10134,6154],{"class":5297},[5280,10136,5598],{"class":5452},[5280,10138,10139,10141,10143,10145,10147,10149,10151,10153,10155,10157,10159,10161,10163,10165],{"class":5282,"line":5705},[5280,10140,6754],{"class":5424},[5280,10142,5218],{"class":5452},[5280,10144,6167],{"class":5293},[5280,10146,5835],{"class":5452},[5280,10148,5994],{"class":5424},[5280,10150,6174],{"class":5452},[5280,10152,6177],{"class":5293},[5280,10154,5835],{"class":5452},[5280,10156,5838],{"class":5619},[5280,10158,5841],{"class":5452},[5280,10160,5844],{"class":5758},[5280,10162,5847],{"class":5452},[5280,10164,5844],{"class":5758},[5280,10166,6781],{"class":5452},[5280,10168,10169,10171,10173,10176],{"class":5282,"line":5820},[5280,10170,6787],{"class":5297},[5280,10172,6790],{"class":5452},[5280,10174,10175],{"class":5297},"\"healthy\"",[5280,10177,6796],{"class":5452},[5280,10179,10180],{"class":5282,"line":5854},[5280,10181,6834],{"class":5452},[5280,10183,10184],{"class":5282,"line":5859},[5280,10185,5976],{"class":5452},[5280,10187,10188],{"class":5282,"line":5871},[5280,10189,5313],{"emptyLinePlaceholder":5312},[5280,10191,10192,10194,10197,10199,10201,10203,10205,10207,10209,10211,10213,10215,10217,10219],{"class":5282,"line":5895},[5280,10193,5862],{"class":5619},[5280,10195,10196],{"class":5293}," errorHandler",[5280,10198,5835],{"class":5452},[5280,10200,5994],{"class":5424},[5280,10202,5997],{"class":5758},[5280,10204,5218],{"class":5452},[5280,10206,6002],{"class":5758},[5280,10208,5887],{"class":5452},[5280,10210,6007],{"class":5424},[5280,10212,6010],{"class":5452},[5280,10214,5952],{"class":5758},[5280,10216,5218],{"class":5452},[5280,10218,6017],{"class":5758},[5280,10220,6020],{"class":5452},[5280,10222,10223,10225,10227,10229,10231,10233,10235,10238,10240,10242,10244,10247],{"class":5282,"line":5916},[5280,10224,5874],{"class":5424},[5280,10226,5218],{"class":5452},[5280,10228,6316],{"class":5293},[5280,10230,5835],{"class":5452},[5280,10232,5994],{"class":5424},[5280,10234,5887],{"class":5452},[5280,10236,10237],{"class":5297},"\"Something went wrong\"",[5280,10239,5887],{"class":5452},[5280,10241,5952],{"class":5424},[5280,10243,5218],{"class":5452},[5280,10245,10246],{"class":5424},"StatusInternalServerError",[5280,10248,5598],{"class":5452},[5280,10250,10251],{"class":5282,"line":5921},[5280,10252,5976],{"class":5452},[5280,10254,10255],{"class":5282,"line":5939},[5280,10256,5313],{"emptyLinePlaceholder":5312},[5280,10258,10259,10261,10263],{"class":5282,"line":5973},[5280,10260,5862],{"class":5619},[5280,10262,5865],{"class":5293},[5280,10264,5868],{"class":5452},[5280,10266,10267],{"class":5282,"line":5979},[5280,10268,10269],{"class":5286},"    \u002F\u002F Обгортаємо кожен handler у Logger Middleware\n",[5280,10271,10272,10274,10276,10278,10280,10283,10285,10287,10289,10292,10294,10296,10298,10300,10302,10305],{"class":5282,"line":5984},[5280,10273,5874],{"class":5424},[5280,10275,5218],{"class":5452},[5280,10277,8145],{"class":5293},[5280,10279,5835],{"class":5452},[5280,10281,10282],{"class":5297},"\"\u002Fhealth\"",[5280,10284,5887],{"class":5452},[5280,10286,8768],{"class":5424},[5280,10288,5218],{"class":5452},[5280,10290,10291],{"class":5293},"Logger",[5280,10293,5835],{"class":5452},[5280,10295,5952],{"class":5424},[5280,10297,5218],{"class":5452},[5280,10299,7312],{"class":5293},[5280,10301,5835],{"class":5452},[5280,10303,10304],{"class":5424},"healthHandler",[5280,10306,10307],{"class":5452},")))\n",[5280,10309,10310,10312,10314,10316,10318,10321,10323,10325,10327,10329,10331,10333,10335,10337,10339,10342],{"class":5282,"line":6023},[5280,10311,5874],{"class":5424},[5280,10313,5218],{"class":5452},[5280,10315,8145],{"class":5293},[5280,10317,5835],{"class":5452},[5280,10319,10320],{"class":5297},"\"\u002Ferror\"",[5280,10322,5887],{"class":5452},[5280,10324,8768],{"class":5424},[5280,10326,5218],{"class":5452},[5280,10328,10291],{"class":5293},[5280,10330,5835],{"class":5452},[5280,10332,5952],{"class":5424},[5280,10334,5218],{"class":5452},[5280,10336,7312],{"class":5293},[5280,10338,5835],{"class":5452},[5280,10340,10341],{"class":5424},"errorHandler",[5280,10343,10307],{"class":5452},[5280,10345,10346],{"class":5282,"line":6042},[5280,10347,6724],{"class":5452},[5280,10349,10350,10352,10354,10356,10358,10361],{"class":5282,"line":6093},[5280,10351,5924],{"class":5424},[5280,10353,5218],{"class":5452},[5280,10355,5929],{"class":5293},[5280,10357,5835],{"class":5452},[5280,10359,10360],{"class":5297},"\"Server with logging starting on :8080...\"",[5280,10362,5598],{"class":5452},[5280,10364,10365,10367,10369,10371,10373,10375,10377,10379,10381,10383,10385,10387],{"class":5282,"line":6098},[5280,10366,5942],{"class":5424},[5280,10368,5218],{"class":5452},[5280,10370,5947],{"class":5293},[5280,10372,5835],{"class":5452},[5280,10374,5952],{"class":5424},[5280,10376,5218],{"class":5452},[5280,10378,5957],{"class":5293},[5280,10380,5835],{"class":5452},[5280,10382,5962],{"class":5297},[5280,10384,5887],{"class":5452},[5280,10386,5967],{"class":5619},[5280,10388,5970],{"class":5452},[5280,10390,10391],{"class":5282,"line":6114},[5280,10392,5976],{"class":5452},[5262,10394,10395],{},[5227,10396,10397],{},"Виконуємо запити:",[8866,10399,10401,10410,10414,10417,10427],{":cursor":8868,"title":10400},"curl http:\u002F\u002Flocalhost:8080\u002Fhealth",[8871,10402,10404,7648,10407],{"className":10403},[5282],[5280,10405,8878],{"className":10406},[8877],[5227,10408,10400],{"className":10409},[8882],[8871,10411,10413],{"className":10412},[5282],"{\"status\":\"healthy\"}",[8871,10415],{"className":10416},[5282],[8871,10418,10420,7648,10423],{"className":10419},[5282],[5280,10421,8878],{"className":10422},[8877],[5227,10424,10426],{"className":10425},[8882],"curl http:\u002F\u002Flocalhost:8080\u002Ferror",[8871,10428,10430],{"className":10429},[5282],"Something went wrong",[5262,10432,10433],{},[5227,10434,10435],{},"Логи сервера:",[8866,10437,10439,10443,10447,10451],{":cursor":8915,"title":10438},"Логи сервера",[8871,10440,10442],{"className":10441},[5282],"[→] GET \u002Fhealth from 127.0.0.1:54321",[8871,10444,10446],{"className":10445},[5282],"[←] GET \u002Fhealth → 200 (342µs)",[8871,10448,10450],{"className":10449},[5282],"[→] GET \u002Ferror from 127.0.0.1:54322",[8871,10452,10454],{"className":10453},[5282],"[←] GET \u002Ferror → 500 (89µs)",[5262,10456,10457],{},"Чудово! Тепер у нас є робочий Middleware, що логує всі запити з статус-кодами та тривалістю обробки.",[5254,10459],{},[5257,10461,10463],{"id":10462},"middleware-для-перехоплення-паніки","Middleware для перехоплення паніки",[5262,10465,10466,10467,10470,10471,10474],{},"Якщо в обробнику виникає паніка (",[5215,10468,10469],{},"panic","), стандартна поведінка Go — завершити горутину і розірвати з'єднання. Це ",[5227,10472,10473],{},"неприйнятно"," для продакшн-систем. Створимо Middleware для перехоплення паніки:",[5271,10476,10478],{"className":5411,"code":10477,"language":5325,"meta":5276,"style":5276},"\u002F\u002F middleware\u002Frecovery.go\npackage middleware\n\nimport (\n    \"log\"\n    \"net\u002Fhttp\"\n    \"runtime\u002Fdebug\"\n)\n\n\u002F\u002F Recovery — Middleware для перехоплення паніки\nfunc Recovery(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        defer func() {\n            if err := recover(); err != nil {\n                \u002F\u002F Логуємо паніку та стек викликів\n                log.Printf(\"PANIC: %v\\n%s\", err, debug.Stack())\n                \n                \u002F\u002F Повертаємо клієнту статус 500\n                http.Error(w, \n                    \"Internal Server Error\", \n                    http.StatusInternalServerError)\n            }\n        }()\n        \n        \u002F\u002F Викликаємо наступний обробник\n        next.ServeHTTP(w, r)\n    })\n}\n",[5215,10479,10480,10485,10491,10495,10501,10505,10509,10514,10518,10522,10527,10554,10592,10601,10624,10629,10670,10675,10680,10695,10702,10713,10718,10723,10727,10731,10749,10753],{"__ignoreMap":5276},[5280,10481,10482],{"class":5282,"line":5283},[5280,10483,10484],{"class":5286},"\u002F\u002F middleware\u002Frecovery.go\n",[5280,10486,10487,10489],{"class":5282,"line":5290},[5280,10488,5755],{"class":5619},[5280,10490,5641],{"class":5758},[5280,10492,10493],{"class":5282,"line":5301},[5280,10494,5313],{"emptyLinePlaceholder":5312},[5280,10496,10497,10499],{"class":5282,"line":5309},[5280,10498,5768],{"class":5436},[5280,10500,5453],{"class":5452},[5280,10502,10503],{"class":5282,"line":5316},[5280,10504,5785],{"class":5297},[5280,10506,10507],{"class":5282,"line":5322},[5280,10508,5790],{"class":5297},[5280,10510,10511],{"class":5282,"line":5336},[5280,10512,10513],{"class":5297},"    \"runtime\u002Fdebug\"\n",[5280,10515,10516],{"class":5282,"line":5341},[5280,10517,5598],{"class":5452},[5280,10519,10520],{"class":5282,"line":5347},[5280,10521,5313],{"emptyLinePlaceholder":5312},[5280,10523,10524],{"class":5282,"line":5358},[5280,10525,10526],{"class":5286},"\u002F\u002F Recovery — Middleware для перехоплення паніки\n",[5280,10528,10529,10531,10534,10536,10538,10540,10542,10544,10546,10548,10550,10552],{"class":5282,"line":5368},[5280,10530,5862],{"class":5619},[5280,10532,10533],{"class":5293}," Recovery",[5280,10535,5835],{"class":5452},[5280,10537,7305],{"class":5424},[5280,10539,5997],{"class":5758},[5280,10541,5218],{"class":5452},[5280,10543,7935],{"class":5758},[5280,10545,7315],{"class":5452},[5280,10547,5952],{"class":5758},[5280,10549,5218],{"class":5452},[5280,10551,7935],{"class":5758},[5280,10553,6111],{"class":5452},[5280,10555,10556,10558,10560,10562,10564,10566,10568,10570,10572,10574,10576,10578,10580,10582,10584,10586,10588,10590],{"class":5282,"line":5378},[5280,10557,7328],{"class":5436},[5280,10559,5997],{"class":5424},[5280,10561,5218],{"class":5452},[5280,10563,7312],{"class":5293},[5280,10565,5835],{"class":5452},[5280,10567,5862],{"class":5619},[5280,10569,5835],{"class":5452},[5280,10571,5994],{"class":5424},[5280,10573,5997],{"class":5758},[5280,10575,5218],{"class":5452},[5280,10577,6002],{"class":5758},[5280,10579,5887],{"class":5452},[5280,10581,6007],{"class":5424},[5280,10583,6010],{"class":5452},[5280,10585,5952],{"class":5758},[5280,10587,5218],{"class":5452},[5280,10589,6017],{"class":5758},[5280,10591,6020],{"class":5452},[5280,10593,10594,10597,10599],{"class":5282,"line":5391},[5280,10595,10596],{"class":5436},"        defer",[5280,10598,7331],{"class":5619},[5280,10600,5868],{"class":5452},[5280,10602,10603,10606,10608,10610,10613,10616,10618,10620,10622],{"class":5282,"line":5705},[5280,10604,10605],{"class":5436},"            if",[5280,10607,6262],{"class":5424},[5280,10609,6029],{"class":5452},[5280,10611,10612],{"class":5293},"recover",[5280,10614,10615],{"class":5452},"(); ",[5280,10617,6298],{"class":5424},[5280,10619,6301],{"class":5452},[5280,10621,5967],{"class":5619},[5280,10623,6111],{"class":5452},[5280,10625,10626],{"class":5282,"line":5820},[5280,10627,10628],{"class":5286},"                \u002F\u002F Логуємо паніку та стек викликів\n",[5280,10630,10631,10634,10636,10638,10640,10643,10645,10649,10651,10653,10655,10657,10659,10662,10664,10667],{"class":5282,"line":5854},[5280,10632,10633],{"class":5424},"                log",[5280,10635,5218],{"class":5452},[5280,10637,6049],{"class":5293},[5280,10639,5835],{"class":5452},[5280,10641,10642],{"class":5297},"\"PANIC: ",[5280,10644,6362],{"class":5424},[5280,10646,10648],{"class":10647},"sjcCO","\\n",[5280,10650,6057],{"class":5424},[5280,10652,6584],{"class":5297},[5280,10654,5887],{"class":5452},[5280,10656,6298],{"class":5424},[5280,10658,5887],{"class":5452},[5280,10660,10661],{"class":5424},"debug",[5280,10663,5218],{"class":5452},[5280,10665,10666],{"class":5293},"Stack",[5280,10668,10669],{"class":5452},"())\n",[5280,10671,10672],{"class":5282,"line":5859},[5280,10673,10674],{"class":5452},"                \n",[5280,10676,10677],{"class":5282,"line":5871},[5280,10678,10679],{"class":5286},"                \u002F\u002F Повертаємо клієнту статус 500\n",[5280,10681,10682,10685,10687,10689,10691,10693],{"class":5282,"line":5895},[5280,10683,10684],{"class":5424},"                http",[5280,10686,5218],{"class":5452},[5280,10688,6316],{"class":5293},[5280,10690,5835],{"class":5452},[5280,10692,5994],{"class":5424},[5280,10694,6368],{"class":5452},[5280,10696,10697,10700],{"class":5282,"line":5916},[5280,10698,10699],{"class":5297},"                    \"Internal Server Error\"",[5280,10701,6368],{"class":5452},[5280,10703,10704,10707,10709,10711],{"class":5282,"line":5921},[5280,10705,10706],{"class":5424},"                    http",[5280,10708,5218],{"class":5452},[5280,10710,10246],{"class":5424},[5280,10712,5598],{"class":5452},[5280,10714,10715],{"class":5282,"line":5939},[5280,10716,10717],{"class":5452},"            }\n",[5280,10719,10720],{"class":5282,"line":5973},[5280,10721,10722],{"class":5452},"        }()\n",[5280,10724,10725],{"class":5282,"line":5979},[5280,10726,6190],{"class":5452},[5280,10728,10729],{"class":5282,"line":5984},[5280,10730,9176],{"class":5286},[5280,10732,10733,10735,10737,10739,10741,10743,10745,10747],{"class":5282,"line":6023},[5280,10734,7426],{"class":5424},[5280,10736,5218],{"class":5452},[5280,10738,8033],{"class":5293},[5280,10740,5835],{"class":5452},[5280,10742,5994],{"class":5424},[5280,10744,5887],{"class":5452},[5280,10746,6007],{"class":5424},[5280,10748,5598],{"class":5452},[5280,10750,10751],{"class":5282,"line":6042},[5280,10752,6834],{"class":5452},[5280,10754,10755],{"class":5282,"line":6093},[5280,10756,5976],{"class":5452},[5262,10758,10759],{},[5227,10760,10761],{},"Пояснення:",[5194,10763,10764,10777,10786],{},[5197,10765,10766,10769,10770,10773,10774,10776],{},[5215,10767,10768],{},"defer"," гарантує, що анонімна функція виконається ",[5227,10771,10772],{},"після"," завершення ",[5215,10775,8598],{}," (навіть якщо сталася паніка)",[5197,10778,10779,10782,10783],{},[5215,10780,10781],{},"recover()"," перехоплює паніку і повертає значення, передане у ",[5215,10784,10785],{},"panic()",[5197,10787,10788,10791],{},[5215,10789,10790],{},"debug.Stack()"," повертає повний стек викликів для діагностики",[5266,10793,10795],{"id":10794},"тестуємо-recovery-middleware","Тестуємо Recovery Middleware",[5271,10797,10799],{"className":5411,"code":10798,"language":5325,"meta":5276,"style":5276},"\u002F\u002F cmd\u002Frecovery-demo\u002Fmain.go\npackage main\n\nimport (\n    \"fmt\"\n    \"log\"\n    \"net\u002Fhttp\"\n    \"myapp\u002Fmiddleware\"\n)\n\nfunc panicHandler(w http.ResponseWriter, r *http.Request) {\n    \u002F\u002F Навмисна паніка для демонстрації\n    panic(\"Something terrible happened!\")\n}\n\nfunc normalHandler(w http.ResponseWriter, r *http.Request) {\n    fmt.Fprintln(w, \"Everything is fine here\")\n}\n\nfunc main() {\n    \u002F\u002F Обгортаємо handler у Recovery та Logger\n    http.Handle(\"\u002Fpanic\", \n        middleware.Logger(\n            middleware.Recovery(\n                http.HandlerFunc(panicHandler))))\n                \n    http.Handle(\"\u002Fnormal\", \n        middleware.Logger(\n            middleware.Recovery(\n                http.HandlerFunc(normalHandler))))\n    \n    fmt.Println(\"Server with panic recovery on :8080...\")\n    log.Fatal(http.ListenAndServe(\":8080\", nil))\n}\n",[5215,10800,10801,10806,10812,10816,10822,10826,10830,10834,10838,10842,10846,10877,10882,10894,10898,10902,10933,10952,10956,10960,10968,10973,10988,10999,11011,11027,11031,11046,11056,11066,11081,11085,11100,11126],{"__ignoreMap":5276},[5280,10802,10803],{"class":5282,"line":5283},[5280,10804,10805],{"class":5286},"\u002F\u002F cmd\u002Frecovery-demo\u002Fmain.go\n",[5280,10807,10808,10810],{"class":5282,"line":5290},[5280,10809,5755],{"class":5619},[5280,10811,5759],{"class":5758},[5280,10813,10814],{"class":5282,"line":5301},[5280,10815,5313],{"emptyLinePlaceholder":5312},[5280,10817,10818,10820],{"class":5282,"line":5309},[5280,10819,5768],{"class":5436},[5280,10821,5453],{"class":5452},[5280,10823,10824],{"class":5282,"line":5316},[5280,10825,5780],{"class":5297},[5280,10827,10828],{"class":5282,"line":5322},[5280,10829,5785],{"class":5297},[5280,10831,10832],{"class":5282,"line":5336},[5280,10833,5790],{"class":5297},[5280,10835,10836],{"class":5282,"line":5341},[5280,10837,10075],{"class":5297},[5280,10839,10840],{"class":5282,"line":5347},[5280,10841,5598],{"class":5452},[5280,10843,10844],{"class":5282,"line":5358},[5280,10845,5313],{"emptyLinePlaceholder":5312},[5280,10847,10848,10850,10853,10855,10857,10859,10861,10863,10865,10867,10869,10871,10873,10875],{"class":5282,"line":5368},[5280,10849,5862],{"class":5619},[5280,10851,10852],{"class":5293}," panicHandler",[5280,10854,5835],{"class":5452},[5280,10856,5994],{"class":5424},[5280,10858,5997],{"class":5758},[5280,10860,5218],{"class":5452},[5280,10862,6002],{"class":5758},[5280,10864,5887],{"class":5452},[5280,10866,6007],{"class":5424},[5280,10868,6010],{"class":5452},[5280,10870,5952],{"class":5758},[5280,10872,5218],{"class":5452},[5280,10874,6017],{"class":5758},[5280,10876,6020],{"class":5452},[5280,10878,10879],{"class":5282,"line":5378},[5280,10880,10881],{"class":5286},"    \u002F\u002F Навмисна паніка для демонстрації\n",[5280,10883,10884,10887,10889,10892],{"class":5282,"line":5391},[5280,10885,10886],{"class":5293},"    panic",[5280,10888,5835],{"class":5452},[5280,10890,10891],{"class":5297},"\"Something terrible happened!\"",[5280,10893,5598],{"class":5452},[5280,10895,10896],{"class":5282,"line":5705},[5280,10897,5976],{"class":5452},[5280,10899,10900],{"class":5282,"line":5820},[5280,10901,5313],{"emptyLinePlaceholder":5312},[5280,10903,10904,10906,10909,10911,10913,10915,10917,10919,10921,10923,10925,10927,10929,10931],{"class":5282,"line":5854},[5280,10905,5862],{"class":5619},[5280,10907,10908],{"class":5293}," normalHandler",[5280,10910,5835],{"class":5452},[5280,10912,5994],{"class":5424},[5280,10914,5997],{"class":5758},[5280,10916,5218],{"class":5452},[5280,10918,6002],{"class":5758},[5280,10920,5887],{"class":5452},[5280,10922,6007],{"class":5424},[5280,10924,6010],{"class":5452},[5280,10926,5952],{"class":5758},[5280,10928,5218],{"class":5452},[5280,10930,6017],{"class":5758},[5280,10932,6020],{"class":5452},[5280,10934,10935,10937,10939,10941,10943,10945,10947,10950],{"class":5282,"line":5859},[5280,10936,5924],{"class":5424},[5280,10938,5218],{"class":5452},[5280,10940,8112],{"class":5293},[5280,10942,5835],{"class":5452},[5280,10944,5994],{"class":5424},[5280,10946,5887],{"class":5452},[5280,10948,10949],{"class":5297},"\"Everything is fine here\"",[5280,10951,5598],{"class":5452},[5280,10953,10954],{"class":5282,"line":5871},[5280,10955,5976],{"class":5452},[5280,10957,10958],{"class":5282,"line":5895},[5280,10959,5313],{"emptyLinePlaceholder":5312},[5280,10961,10962,10964,10966],{"class":5282,"line":5916},[5280,10963,5862],{"class":5619},[5280,10965,5865],{"class":5293},[5280,10967,5868],{"class":5452},[5280,10969,10970],{"class":5282,"line":5921},[5280,10971,10972],{"class":5286},"    \u002F\u002F Обгортаємо handler у Recovery та Logger\n",[5280,10974,10975,10977,10979,10981,10983,10986],{"class":5282,"line":5939},[5280,10976,5874],{"class":5424},[5280,10978,5218],{"class":5452},[5280,10980,8145],{"class":5293},[5280,10982,5835],{"class":5452},[5280,10984,10985],{"class":5297},"\"\u002Fpanic\"",[5280,10987,6368],{"class":5452},[5280,10989,10990,10993,10995,10997],{"class":5282,"line":5973},[5280,10991,10992],{"class":5424},"        middleware",[5280,10994,5218],{"class":5452},[5280,10996,10291],{"class":5293},[5280,10998,9105],{"class":5452},[5280,11000,11001,11004,11006,11009],{"class":5282,"line":5979},[5280,11002,11003],{"class":5424},"            middleware",[5280,11005,5218],{"class":5452},[5280,11007,11008],{"class":5293},"Recovery",[5280,11010,9105],{"class":5452},[5280,11012,11013,11015,11017,11019,11021,11024],{"class":5282,"line":5984},[5280,11014,10684],{"class":5424},[5280,11016,5218],{"class":5452},[5280,11018,7312],{"class":5293},[5280,11020,5835],{"class":5452},[5280,11022,11023],{"class":5424},"panicHandler",[5280,11025,11026],{"class":5452},"))))\n",[5280,11028,11029],{"class":5282,"line":6023},[5280,11030,10674],{"class":5452},[5280,11032,11033,11035,11037,11039,11041,11044],{"class":5282,"line":6042},[5280,11034,5874],{"class":5424},[5280,11036,5218],{"class":5452},[5280,11038,8145],{"class":5293},[5280,11040,5835],{"class":5452},[5280,11042,11043],{"class":5297},"\"\u002Fnormal\"",[5280,11045,6368],{"class":5452},[5280,11047,11048,11050,11052,11054],{"class":5282,"line":6093},[5280,11049,10992],{"class":5424},[5280,11051,5218],{"class":5452},[5280,11053,10291],{"class":5293},[5280,11055,9105],{"class":5452},[5280,11057,11058,11060,11062,11064],{"class":5282,"line":6098},[5280,11059,11003],{"class":5424},[5280,11061,5218],{"class":5452},[5280,11063,11008],{"class":5293},[5280,11065,9105],{"class":5452},[5280,11067,11068,11070,11072,11074,11076,11079],{"class":5282,"line":6114},[5280,11069,10684],{"class":5424},[5280,11071,5218],{"class":5452},[5280,11073,7312],{"class":5293},[5280,11075,5835],{"class":5452},[5280,11077,11078],{"class":5424},"normalHandler",[5280,11080,11026],{"class":5452},[5280,11082,11083],{"class":5282,"line":6130},[5280,11084,6724],{"class":5452},[5280,11086,11087,11089,11091,11093,11095,11098],{"class":5282,"line":6159},[5280,11088,5924],{"class":5424},[5280,11090,5218],{"class":5452},[5280,11092,5929],{"class":5293},[5280,11094,5835],{"class":5452},[5280,11096,11097],{"class":5297},"\"Server with panic recovery on :8080...\"",[5280,11099,5598],{"class":5452},[5280,11101,11102,11104,11106,11108,11110,11112,11114,11116,11118,11120,11122,11124],{"class":5282,"line":6187},[5280,11103,5942],{"class":5424},[5280,11105,5218],{"class":5452},[5280,11107,5947],{"class":5293},[5280,11109,5835],{"class":5452},[5280,11111,5952],{"class":5424},[5280,11113,5218],{"class":5452},[5280,11115,5957],{"class":5293},[5280,11117,5835],{"class":5452},[5280,11119,5962],{"class":5297},[5280,11121,5887],{"class":5452},[5280,11123,5967],{"class":5619},[5280,11125,5970],{"class":5452},[5280,11127,11128],{"class":5282,"line":6193},[5280,11129,5976],{"class":5452},[5262,11131,11132],{},[5227,11133,11134,11135,5408],{},"Запит до ",[5215,11136,11137],{},"\u002Fpanic",[8866,11139,11141,11150],{":cursor":8868,"title":11140},"curl http:\u002F\u002Flocalhost:8080\u002Fpanic",[8871,11142,11144,7648,11147],{"className":11143},[5282],[5280,11145,8878],{"className":11146},[8877],[5227,11148,11140],{"className":11149},[8882],[8871,11151,11153],{"className":11152},[5282],"Internal Server Error",[5262,11155,11156],{},[5227,11157,11158],{},"Лог сервера (сервер не падає!):",[8866,11160,11162,11166,11170,11174,11178,11182,11186,11190],{":cursor":8915,"title":11161},"Лог сервера після паніки",[8871,11163,11165],{"className":11164},[5282],"[→] GET \u002Fpanic from 127.0.0.1:54323",[8871,11167,11169],{"className":11168},[5282],"PANIC: Something terrible happened!",[8871,11171,11173],{"className":11172},[5282],"goroutine 19 [running]:",[8871,11175,11177],{"className":11176},[5282],"runtime\u002Fdebug.Stack()",[8871,11179,11181],{"className":11180},[5282],"  \u002Fusr\u002Flocal\u002Fgo\u002Fsrc\u002Fruntime\u002Fdebug\u002Fstack.go:24 +0x65",[8871,11183,11185],{"className":11184},[5282],"myapp\u002Fmiddleware.Recovery.func1.1()",[8871,11187,11189],{"className":11188},[5282],"  \u002Fapp\u002Fmiddleware\u002Frecovery.go:14 +0x5e",[8871,11191,11193],{"className":11192},[5282],"[←] GET \u002Fpanic → 500 (127µs)",[11195,11196,11197,11200,11201,11203,11204,11207],"warning",{},[5227,11198,11199],{},"Паніки у продакшн-коді слід уникати!"," Використовуйте явне повернення помилок через тип ",[5215,11202,9567],{},". Recovery Middleware — це ",[5227,11205,11206],{},"останній рубіж захисту"," від непередбачених ситуацій, а не основний механізм обробки помилок.",[5254,11209],{},[5257,11211,11213],{"id":11212},"композиція-middleware-побудова-ланцюжків","Композиція Middleware: побудова ланцюжків",[5266,11215,11217],{"id":11216},"проблема-вкладеності","Проблема вкладеності",[5262,11219,11220],{},"Коли у нас є кілька Middleware, вкладення стає громіздким:",[5271,11222,11224],{"className":5411,"code":11223,"language":5325,"meta":5276,"style":5276},"handler := middleware.Logger(\n    middleware.Recovery(\n        middleware.CORS(\n            http.HandlerFunc(myHandler))))\n",[5215,11225,11226,11240,11251,11262],{"__ignoreMap":5276},[5280,11227,11228,11230,11232,11234,11236,11238],{"class":5282,"line":5283},[5280,11229,5233],{"class":5424},[5280,11231,6029],{"class":5452},[5280,11233,8768],{"class":5424},[5280,11235,5218],{"class":5452},[5280,11237,10291],{"class":5293},[5280,11239,9105],{"class":5452},[5280,11241,11242,11245,11247,11249],{"class":5282,"line":5290},[5280,11243,11244],{"class":5424},"    middleware",[5280,11246,5218],{"class":5452},[5280,11248,11008],{"class":5293},[5280,11250,9105],{"class":5452},[5280,11252,11253,11255,11257,11260],{"class":5282,"line":5301},[5280,11254,10992],{"class":5424},[5280,11256,5218],{"class":5452},[5280,11258,11259],{"class":5293},"CORS",[5280,11261,9105],{"class":5452},[5280,11263,11264,11266,11268,11270,11272,11274],{"class":5282,"line":5309},[5280,11265,6311],{"class":5424},[5280,11267,5218],{"class":5452},[5280,11269,7312],{"class":5293},[5280,11271,5835],{"class":5452},[5280,11273,8338],{"class":5424},[5280,11275,11026],{"class":5452},[5262,11277,11278],{},"Це важко читати і підтримувати. Створимо допоміжну функцію для композиції:",[5271,11280,11282],{"className":5411,"code":11281,"language":5325,"meta":5276,"style":5276},"\u002F\u002F middleware\u002Fchain.go\npackage middleware\n\nimport \"net\u002Fhttp\"\n\n\u002F\u002F Chain послідовно застосовує список Middleware до handler\nfunc Chain(h http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {\n    \u002F\u002F Застосовуємо Middleware у зворотному порядку,\n    \u002F\u002F щоб перший у списку виконувався першим\n    for i := len(middlewares) - 1; i >= 0; i-- {\n        h = middlewares[i](h)\n    }\n    return h\n}\n",[5215,11283,11284,11289,11295,11299,11306,11310,11315,11369,11374,11379,11421,11441,11445,11452],{"__ignoreMap":5276},[5280,11285,11286],{"class":5282,"line":5283},[5280,11287,11288],{"class":5286},"\u002F\u002F middleware\u002Fchain.go\n",[5280,11290,11291,11293],{"class":5282,"line":5290},[5280,11292,5755],{"class":5619},[5280,11294,5641],{"class":5758},[5280,11296,11297],{"class":5282,"line":5301},[5280,11298,5313],{"emptyLinePlaceholder":5312},[5280,11300,11301,11303],{"class":5282,"line":5309},[5280,11302,5768],{"class":5436},[5280,11304,11305],{"class":5297}," \"net\u002Fhttp\"\n",[5280,11307,11308],{"class":5282,"line":5316},[5280,11309,5313],{"emptyLinePlaceholder":5312},[5280,11311,11312],{"class":5282,"line":5322},[5280,11313,11314],{"class":5286},"\u002F\u002F Chain послідовно застосовує список Middleware до handler\n",[5280,11316,11317,11319,11322,11324,11327,11329,11331,11333,11335,11338,11341,11343,11345,11347,11349,11351,11353,11355,11357,11359,11361,11363,11365,11367],{"class":5282,"line":5336},[5280,11318,5862],{"class":5619},[5280,11320,11321],{"class":5293}," Chain",[5280,11323,5835],{"class":5452},[5280,11325,11326],{"class":5424},"h",[5280,11328,5997],{"class":5758},[5280,11330,5218],{"class":5452},[5280,11332,7935],{"class":5758},[5280,11334,5887],{"class":5452},[5280,11336,11337],{"class":5424},"middlewares",[5280,11339,11340],{"class":5452}," ...",[5280,11342,5862],{"class":5619},[5280,11344,5835],{"class":5452},[5280,11346,5952],{"class":5758},[5280,11348,5218],{"class":5452},[5280,11350,7935],{"class":5758},[5280,11352,7315],{"class":5452},[5280,11354,5952],{"class":5758},[5280,11356,5218],{"class":5452},[5280,11358,7935],{"class":5758},[5280,11360,7315],{"class":5452},[5280,11362,5952],{"class":5758},[5280,11364,5218],{"class":5452},[5280,11366,7935],{"class":5758},[5280,11368,6111],{"class":5452},[5280,11370,11371],{"class":5282,"line":5341},[5280,11372,11373],{"class":5286},"    \u002F\u002F Застосовуємо Middleware у зворотному порядку,\n",[5280,11375,11376],{"class":5282,"line":5347},[5280,11377,11378],{"class":5286},"    \u002F\u002F щоб перший у списку виконувався першим\n",[5280,11380,11381,11384,11387,11389,11392,11394,11396,11399,11402,11405,11408,11411,11414,11416,11418],{"class":5282,"line":5358},[5280,11382,11383],{"class":5436},"    for",[5280,11385,11386],{"class":5424}," i",[5280,11388,6029],{"class":5452},[5280,11390,11391],{"class":5293},"len",[5280,11393,5835],{"class":5452},[5280,11395,11337],{"class":5424},[5280,11397,11398],{"class":5452},") - ",[5280,11400,11401],{"class":5439},"1",[5280,11403,11404],{"class":5452},"; ",[5280,11406,11407],{"class":5424},"i",[5280,11409,11410],{"class":5452}," >= ",[5280,11412,11413],{"class":5439},"0",[5280,11415,11404],{"class":5452},[5280,11417,11407],{"class":5424},[5280,11419,11420],{"class":5452},"-- {\n",[5280,11422,11423,11426,11428,11430,11432,11434,11437,11439],{"class":5282,"line":5368},[5280,11424,11425],{"class":5424},"        h",[5280,11427,5829],{"class":5452},[5280,11429,11337],{"class":5293},[5280,11431,5841],{"class":5452},[5280,11433,11407],{"class":5758},[5280,11435,11436],{"class":5452},"](",[5280,11438,11326],{"class":5424},[5280,11440,5598],{"class":5452},[5280,11442,11443],{"class":5282,"line":5378},[5280,11444,6553],{"class":5452},[5280,11446,11447,11449],{"class":5282,"line":5391},[5280,11448,7328],{"class":5436},[5280,11450,11451],{"class":5424}," h\n",[5280,11453,11454],{"class":5282,"line":5705},[5280,11455,5976],{"class":5452},[5262,11457,11458,11461],{},[5227,11459,11460],{},"Чому зворотний порядок?"," Middleware застосовуються як обгортки:",[7678,11463,11465],{"alt":11464},"Порядок виконання Middleware",[5271,11466,11468],{"className":7683,"code":11467,"language":7685,"meta":5276,"style":5276},"@startuml\nskinparam style plain\nskinparam backgroundColor #FFFFFF\n\nrectangle \"Logger\" as L #FEF3C7 {\n  rectangle \"Recovery\" as R #DBEAFE {\n    rectangle \"CORS\" as C #E2E8F0 {\n      rectangle \"Handler\" as H #DCFCE7\n    }\n  }\n}\n\nnote right of L\n  1. Logger обгортає Recovery\n  2. Recovery обгортає CORS\n  3. CORS обгортає Handler\n  \n  Виконання:\n  Logger → Recovery → CORS → Handler\nend note\n\n@enduml\n",[5215,11469,11470,11474,11478,11482,11486,11491,11496,11501,11506,11510,11515,11519,11523,11528,11533,11538,11543,11548,11553,11558,11562,11566],{"__ignoreMap":5276},[5280,11471,11472],{"class":5282,"line":5283},[5280,11473,7692],{},[5280,11475,11476],{"class":5282,"line":5290},[5280,11477,7697],{},[5280,11479,11480],{"class":5282,"line":5301},[5280,11481,7702],{},[5280,11483,11484],{"class":5282,"line":5309},[5280,11485,5313],{"emptyLinePlaceholder":5312},[5280,11487,11488],{"class":5282,"line":5316},[5280,11489,11490],{},"rectangle \"Logger\" as L #FEF3C7 {\n",[5280,11492,11493],{"class":5282,"line":5322},[5280,11494,11495],{},"  rectangle \"Recovery\" as R #DBEAFE {\n",[5280,11497,11498],{"class":5282,"line":5336},[5280,11499,11500],{},"    rectangle \"CORS\" as C #E2E8F0 {\n",[5280,11502,11503],{"class":5282,"line":5341},[5280,11504,11505],{},"      rectangle \"Handler\" as H #DCFCE7\n",[5280,11507,11508],{"class":5282,"line":5347},[5280,11509,6553],{},[5280,11511,11512],{"class":5282,"line":5358},[5280,11513,11514],{},"  }\n",[5280,11516,11517],{"class":5282,"line":5368},[5280,11518,5976],{},[5280,11520,11521],{"class":5282,"line":5378},[5280,11522,5313],{"emptyLinePlaceholder":5312},[5280,11524,11525],{"class":5282,"line":5391},[5280,11526,11527],{},"note right of L\n",[5280,11529,11530],{"class":5282,"line":5705},[5280,11531,11532],{},"  1. Logger обгортає Recovery\n",[5280,11534,11535],{"class":5282,"line":5820},[5280,11536,11537],{},"  2. Recovery обгортає CORS\n",[5280,11539,11540],{"class":5282,"line":5854},[5280,11541,11542],{},"  3. CORS обгортає Handler\n",[5280,11544,11545],{"class":5282,"line":5859},[5280,11546,11547],{},"  \n",[5280,11549,11550],{"class":5282,"line":5871},[5280,11551,11552],{},"  Виконання:\n",[5280,11554,11555],{"class":5282,"line":5895},[5280,11556,11557],{},"  Logger → Recovery → CORS → Handler\n",[5280,11559,11560],{"class":5282,"line":5916},[5280,11561,7765],{},[5280,11563,11564],{"class":5282,"line":5921},[5280,11565,5313],{"emptyLinePlaceholder":5312},[5280,11567,11568],{"class":5282,"line":5939},[5280,11569,7904],{},[5262,11571,11572],{},"Тепер можна писати так:",[5271,11574,11576],{"className":5411,"code":11575,"language":5325,"meta":5276,"style":5276},"handler := middleware.Chain(\n    http.HandlerFunc(myHandler),\n    middleware.Logger,\n    middleware.Recovery,\n    middleware.CORS,\n)\n",[5215,11577,11578,11593,11607,11617,11627,11637],{"__ignoreMap":5276},[5280,11579,11580,11582,11584,11586,11588,11591],{"class":5282,"line":5283},[5280,11581,5233],{"class":5424},[5280,11583,6029],{"class":5452},[5280,11585,8768],{"class":5424},[5280,11587,5218],{"class":5452},[5280,11589,11590],{"class":5293},"Chain",[5280,11592,9105],{"class":5452},[5280,11594,11595,11597,11599,11601,11603,11605],{"class":5282,"line":5290},[5280,11596,5874],{"class":5424},[5280,11598,5218],{"class":5452},[5280,11600,7312],{"class":5293},[5280,11602,5835],{"class":5452},[5280,11604,8338],{"class":5424},[5280,11606,6828],{"class":5452},[5280,11608,11609,11611,11613,11615],{"class":5282,"line":5301},[5280,11610,11244],{"class":5424},[5280,11612,5218],{"class":5452},[5280,11614,10291],{"class":5424},[5280,11616,6796],{"class":5452},[5280,11618,11619,11621,11623,11625],{"class":5282,"line":5309},[5280,11620,11244],{"class":5424},[5280,11622,5218],{"class":5452},[5280,11624,11008],{"class":5424},[5280,11626,6796],{"class":5452},[5280,11628,11629,11631,11633,11635],{"class":5282,"line":5316},[5280,11630,11244],{"class":5424},[5280,11632,5218],{"class":5452},[5280,11634,11259],{"class":5424},[5280,11636,6796],{"class":5452},[5280,11638,11639],{"class":5282,"line":5322},[5280,11640,5598],{"class":5452},[8342,11642,11643,11644,11647,11648,11651],{},"Маршрутизатор ",[5227,11645,11646],{},"Chi"," має вбудований метод ",[5215,11649,11650],{},"r.Use()"," для групування Middleware, що ще зручніше. Але розуміння того, як працює Chain, допомагає зрозуміти внутрішню механіку.",[5254,11653],{},[5257,11655,11657],{"id":11656},"cors-middleware-дозвіл-крос-доменних-запитів","CORS Middleware: дозвіл крос-доменних запитів",[5266,11659,11661],{"id":11660},"що-таке-cors","Що таке CORS?",[5262,11663,11664,5720,11666,11669],{},[5227,11665,11259],{},[5207,11667,11668],{},"Cross-Origin Resource Sharing",", спільне використання ресурсів між різними джерелами) — це механізм безпеки браузерів, що контролює, які домени можуть виконувати запити до вашого API.",[5262,11671,11672,11675],{},[5227,11673,11674],{},"Проблема:"," браузер блокує AJAX-запити до API на іншому домені з міркувань безпеки:",[5271,11677,11682],{"className":11678,"code":11680,"language":11681},[11679],"language-text","Клієнт: https:\u002F\u002Fmyapp.com\nAPI:    https:\u002F\u002Fapi.myserver.com  ← Різні домени!\n","text",[5215,11683,11680],{"__ignoreMap":5276},[5262,11685,11686,11689],{},[5227,11687,11688],{},"Рішення:"," API має додавати спеціальні CORS-заголовки, що дозволяють крос-доменні запити.",[5266,11691,11693],{"id":11692},"preflight-запити","Preflight-запити",[5262,11695,11696,11697,11700,11701,5408],{},"Перед основним запитом браузер надсилає ",[5227,11698,11699],{},"preflight-запит"," з методом ",[5215,11702,11703],{},"OPTIONS",[5271,11705,11708],{"className":11706,"code":11707,"language":5952,"meta":5276,"style":5276},"language-http shiki shiki-themes light-plus dark-plus dark-plus","OPTIONS \u002Fapi\u002Fusers HTTP\u002F1.1\nOrigin: https:\u002F\u002Fmyapp.com\nAccess-Control-Request-Method: POST\nAccess-Control-Request-Headers: Content-Type, Authorization\n",[5215,11709,11710,11725,11736,11746],{"__ignoreMap":5276},[5280,11711,11712,11714,11717,11720,11722],{"class":5282,"line":5283},[5280,11713,11703],{"class":5436},[5280,11715,11716],{"class":5452}," \u002Fapi\u002Fusers ",[5280,11718,11719],{"class":5619},"HTTP",[5280,11721,5466],{"class":5452},[5280,11723,11724],{"class":5439},"1.1\n",[5280,11726,11727,11731,11733],{"class":5282,"line":5290},[5280,11728,11730],{"class":11729},"sKtos","Origin",[5280,11732,5408],{"class":5619},[5280,11734,11735],{"class":5297}," https:\u002F\u002Fmyapp.com\n",[5280,11737,11738,11741,11743],{"class":5282,"line":5301},[5280,11739,11740],{"class":11729},"Access-Control-Request-Method",[5280,11742,5408],{"class":5619},[5280,11744,11745],{"class":5297}," POST\n",[5280,11747,11748,11751,11753],{"class":5282,"line":5309},[5280,11749,11750],{"class":11729},"Access-Control-Request-Headers",[5280,11752,5408],{"class":5619},[5280,11754,11755],{"class":5297}," Content-Type, Authorization\n",[5262,11757,11758],{},"Сервер має відповісти, які методи та заголовки дозволені:",[5271,11760,11762],{"className":11706,"code":11761,"language":5952,"meta":5276,"style":5276},"HTTP\u002F1.1 204 No Content\nAccess-Control-Allow-Origin: https:\u002F\u002Fmyapp.com\nAccess-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS\nAccess-Control-Allow-Headers: Content-Type, Authorization\nAccess-Control-Max-Age: 86400\n",[5215,11763,11764,11779,11788,11798,11807],{"__ignoreMap":5276},[5280,11765,11766,11768,11770,11773,11776],{"class":5282,"line":5283},[5280,11767,11719],{"class":5619},[5280,11769,5466],{"class":5452},[5280,11771,11772],{"class":5439},"1.1",[5280,11774,11775],{"class":5439}," 204",[5280,11777,11778],{"class":5297}," No Content\n",[5280,11780,11781,11784,11786],{"class":5282,"line":5290},[5280,11782,11783],{"class":11729},"Access-Control-Allow-Origin",[5280,11785,5408],{"class":5619},[5280,11787,11735],{"class":5297},[5280,11789,11790,11793,11795],{"class":5282,"line":5301},[5280,11791,11792],{"class":11729},"Access-Control-Allow-Methods",[5280,11794,5408],{"class":5619},[5280,11796,11797],{"class":5297}," GET, POST, PUT, DELETE, OPTIONS\n",[5280,11799,11800,11803,11805],{"class":5282,"line":5309},[5280,11801,11802],{"class":11729},"Access-Control-Allow-Headers",[5280,11804,5408],{"class":5619},[5280,11806,11755],{"class":5297},[5280,11808,11809,11812,11814],{"class":5282,"line":5316},[5280,11810,11811],{"class":11729},"Access-Control-Max-Age",[5280,11813,5408],{"class":5619},[5280,11815,11816],{"class":5297}," 86400\n",[5266,11818,11820],{"id":11819},"реалізація-cors-middleware","Реалізація CORS Middleware",[5271,11822,11824],{"className":5411,"code":11823,"language":5325,"meta":5276,"style":5276},"\u002F\u002F middleware\u002Fcors.go\npackage middleware\n\nimport (\n    \"net\u002Fhttp\"\n    \"strings\"\n)\n\n\u002F\u002F CORSConfig — налаштування CORS\ntype CORSConfig struct {\n    AllowOrigins     []string \u002F\u002F Дозволені домени ([\"*\"] = всі)\n    AllowMethods     []string \u002F\u002F Дозволені HTTP-методи\n    AllowHeaders     []string \u002F\u002F Дозволені заголовки\n    ExposeHeaders    []string \u002F\u002F Заголовки, доступні клієнту\n    AllowCredentials bool     \u002F\u002F Дозволити cookies\n    MaxAge           int      \u002F\u002F Час кешування preflight (секунди)\n}\n\n\u002F\u002F DefaultCORSConfig — базова конфігурація\nfunc DefaultCORSConfig() CORSConfig {\n    return CORSConfig{\n        AllowOrigins:  []string{\"*\"},\n        AllowMethods:  []string{\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\", \"PATCH\"},\n        AllowHeaders:  []string{\"Origin\", \"Content-Type\", \"Accept\", \"Authorization\"},\n        ExposeHeaders: []string{},\n        AllowCredentials: false,\n        MaxAge:        86400, \u002F\u002F 24 години\n    }\n}\n\n\u002F\u002F CORS — Middleware для обробки крос-доменних запитів\nfunc CORS(config CORSConfig) func(http.Handler) http.Handler {\n    return func(next http.Handler) http.Handler {\n        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n            origin := r.Header.Get(\"Origin\")\n            \n            \u002F\u002F Перевіряємо, чи дозволений домен\n            allowedOrigin := \"\"\n            for _, allowed := range config.AllowOrigins {\n                if allowed == \"*\" || allowed == origin {\n                    allowedOrigin = allowed\n                    break\n                }\n            }\n            \n            \u002F\u002F Якщо домен дозволений, встановлюємо заголовки\n            if allowedOrigin != \"\" {\n                if allowedOrigin == \"*\" {\n                    w.Header().Set(\"Access-Control-Allow-Origin\", \"*\")\n                } else {\n                    w.Header().Set(\"Access-Control-Allow-Origin\", origin)\n                }\n                \n                w.Header().Set(\"Access-Control-Allow-Methods\", \n                    strings.Join(config.AllowMethods, \", \"))\n                w.Header().Set(\"Access-Control-Allow-Headers\", \n                    strings.Join(config.AllowHeaders, \", \"))\n                \n                if len(config.ExposeHeaders) > 0 {\n                    w.Header().Set(\"Access-Control-Expose-Headers\", \n                        strings.Join(config.ExposeHeaders, \", \"))\n                }\n                \n                if config.AllowCredentials {\n                    w.Header().Set(\"Access-Control-Allow-Credentials\", \"true\")\n                }\n                \n                if config.MaxAge > 0 {\n                    w.Header().Set(\"Access-Control-Max-Age\", \n                        fmt.Sprintf(\"%d\", config.MaxAge))\n                }\n            }\n            \n            \u002F\u002F Preflight-запит (OPTIONS) — відповідаємо одразу\n            if r.Method == http.MethodOptions {\n                w.WriteHeader(http.StatusNoContent)\n                return\n            }\n            \n            \u002F\u002F Інші запити — передаємо далі\n            next.ServeHTTP(w, r)\n        })\n    }\n}\n",[5215,11825,11826,11831,11837,11841,11847,11851,11856,11860,11864,11869,11880,11893,11905,11917,11930,11941,11952,11956,11960,11965,11980,11988,12007,12048,12078,12091,12102,12117,12121,12125,12129,12134,12170,12196,12235,12259,12264,12269,12279,12307,12331,12341,12346,12351,12355,12359,12364,12378,12390,12414,12423,12445,12449,12453,12473,12499,12518,12541,12545,12568,12587,12610,12614,12618,12631,12655,12659,12663,12681,12700,12729,12734,12739,12744,12750,12772,12792,12798,12803,12808,12814,12834,12840,12845],{"__ignoreMap":5276},[5280,11827,11828],{"class":5282,"line":5283},[5280,11829,11830],{"class":5286},"\u002F\u002F middleware\u002Fcors.go\n",[5280,11832,11833,11835],{"class":5282,"line":5290},[5280,11834,5755],{"class":5619},[5280,11836,5641],{"class":5758},[5280,11838,11839],{"class":5282,"line":5301},[5280,11840,5313],{"emptyLinePlaceholder":5312},[5280,11842,11843,11845],{"class":5282,"line":5309},[5280,11844,5768],{"class":5436},[5280,11846,5453],{"class":5452},[5280,11848,11849],{"class":5282,"line":5316},[5280,11850,5790],{"class":5297},[5280,11852,11853],{"class":5282,"line":5322},[5280,11854,11855],{"class":5297},"    \"strings\"\n",[5280,11857,11858],{"class":5282,"line":5336},[5280,11859,5598],{"class":5452},[5280,11861,11862],{"class":5282,"line":5341},[5280,11863,5313],{"emptyLinePlaceholder":5312},[5280,11865,11866],{"class":5282,"line":5347},[5280,11867,11868],{"class":5286},"\u002F\u002F CORSConfig — налаштування CORS\n",[5280,11870,11871,11873,11876,11878],{"class":5282,"line":5358},[5280,11872,7921],{"class":5619},[5280,11874,11875],{"class":5758}," CORSConfig",[5280,11877,6216],{"class":5619},[5280,11879,6111],{"class":5452},[5280,11881,11882,11885,11888,11890],{"class":5282,"line":5368},[5280,11883,11884],{"class":5424},"    AllowOrigins",[5280,11886,11887],{"class":5452},"     []",[5280,11889,5844],{"class":5758},[5280,11891,11892],{"class":5286}," \u002F\u002F Дозволені домени ([\"*\"] = всі)\n",[5280,11894,11895,11898,11900,11902],{"class":5282,"line":5378},[5280,11896,11897],{"class":5424},"    AllowMethods",[5280,11899,11887],{"class":5452},[5280,11901,5844],{"class":5758},[5280,11903,11904],{"class":5286}," \u002F\u002F Дозволені HTTP-методи\n",[5280,11906,11907,11910,11912,11914],{"class":5282,"line":5391},[5280,11908,11909],{"class":5424},"    AllowHeaders",[5280,11911,11887],{"class":5452},[5280,11913,5844],{"class":5758},[5280,11915,11916],{"class":5286}," \u002F\u002F Дозволені заголовки\n",[5280,11918,11919,11922,11925,11927],{"class":5282,"line":5705},[5280,11920,11921],{"class":5424},"    ExposeHeaders",[5280,11923,11924],{"class":5452},"    []",[5280,11926,5844],{"class":5758},[5280,11928,11929],{"class":5286}," \u002F\u002F Заголовки, доступні клієнту\n",[5280,11931,11932,11935,11938],{"class":5282,"line":5820},[5280,11933,11934],{"class":5424},"    AllowCredentials",[5280,11936,11937],{"class":5758}," bool",[5280,11939,11940],{"class":5286},"     \u002F\u002F Дозволити cookies\n",[5280,11942,11943,11946,11949],{"class":5282,"line":5854},[5280,11944,11945],{"class":5424},"    MaxAge",[5280,11947,11948],{"class":5758},"           int",[5280,11950,11951],{"class":5286},"      \u002F\u002F Час кешування preflight (секунди)\n",[5280,11953,11954],{"class":5282,"line":5859},[5280,11955,5976],{"class":5452},[5280,11957,11958],{"class":5282,"line":5871},[5280,11959,5313],{"emptyLinePlaceholder":5312},[5280,11961,11962],{"class":5282,"line":5895},[5280,11963,11964],{"class":5286},"\u002F\u002F DefaultCORSConfig — базова конфігурація\n",[5280,11966,11967,11969,11972,11975,11978],{"class":5282,"line":5916},[5280,11968,5862],{"class":5619},[5280,11970,11971],{"class":5293}," DefaultCORSConfig",[5280,11973,11974],{"class":5452},"() ",[5280,11976,11977],{"class":5758},"CORSConfig",[5280,11979,6111],{"class":5452},[5280,11981,11982,11984,11986],{"class":5282,"line":5921},[5280,11983,7328],{"class":5436},[5280,11985,11875],{"class":5758},[5280,11987,6781],{"class":5452},[5280,11989,11990,11993,11996,11998,12001,12004],{"class":5282,"line":5939},[5280,11991,11992],{"class":5424},"        AllowOrigins",[5280,11994,11995],{"class":5452},":  []",[5280,11997,5844],{"class":5758},[5280,11999,12000],{"class":5452},"{",[5280,12002,12003],{"class":5297},"\"*\"",[5280,12005,12006],{"class":5452},"},\n",[5280,12008,12009,12012,12014,12016,12018,12021,12023,12026,12028,12031,12033,12036,12038,12041,12043,12046],{"class":5282,"line":5973},[5280,12010,12011],{"class":5424},"        AllowMethods",[5280,12013,11995],{"class":5452},[5280,12015,5844],{"class":5758},[5280,12017,12000],{"class":5452},[5280,12019,12020],{"class":5297},"\"GET\"",[5280,12022,5887],{"class":5452},[5280,12024,12025],{"class":5297},"\"POST\"",[5280,12027,5887],{"class":5452},[5280,12029,12030],{"class":5297},"\"PUT\"",[5280,12032,5887],{"class":5452},[5280,12034,12035],{"class":5297},"\"DELETE\"",[5280,12037,5887],{"class":5452},[5280,12039,12040],{"class":5297},"\"OPTIONS\"",[5280,12042,5887],{"class":5452},[5280,12044,12045],{"class":5297},"\"PATCH\"",[5280,12047,12006],{"class":5452},[5280,12049,12050,12053,12055,12057,12059,12062,12064,12066,12068,12071,12073,12076],{"class":5282,"line":5979},[5280,12051,12052],{"class":5424},"        AllowHeaders",[5280,12054,11995],{"class":5452},[5280,12056,5844],{"class":5758},[5280,12058,12000],{"class":5452},[5280,12060,12061],{"class":5297},"\"Origin\"",[5280,12063,5887],{"class":5452},[5280,12065,6149],{"class":5297},[5280,12067,5887],{"class":5452},[5280,12069,12070],{"class":5297},"\"Accept\"",[5280,12072,5887],{"class":5452},[5280,12074,12075],{"class":5297},"\"Authorization\"",[5280,12077,12006],{"class":5452},[5280,12079,12080,12083,12086,12088],{"class":5282,"line":5984},[5280,12081,12082],{"class":5424},"        ExposeHeaders",[5280,12084,12085],{"class":5452},": []",[5280,12087,5844],{"class":5758},[5280,12089,12090],{"class":5452},"{},\n",[5280,12092,12093,12096,12098,12100],{"class":5282,"line":6023},[5280,12094,12095],{"class":5424},"        AllowCredentials",[5280,12097,6790],{"class":5452},[5280,12099,8915],{"class":5619},[5280,12101,6796],{"class":5452},[5280,12103,12104,12107,12109,12112,12114],{"class":5282,"line":6042},[5280,12105,12106],{"class":5424},"        MaxAge",[5280,12108,9788],{"class":5452},[5280,12110,12111],{"class":5439},"86400",[5280,12113,5887],{"class":5452},[5280,12115,12116],{"class":5286},"\u002F\u002F 24 години\n",[5280,12118,12119],{"class":5282,"line":6093},[5280,12120,6553],{"class":5452},[5280,12122,12123],{"class":5282,"line":6098},[5280,12124,5976],{"class":5452},[5280,12126,12127],{"class":5282,"line":6114},[5280,12128,5313],{"emptyLinePlaceholder":5312},[5280,12130,12131],{"class":5282,"line":6130},[5280,12132,12133],{"class":5286},"\u002F\u002F CORS — Middleware для обробки крос-доменних запитів\n",[5280,12135,12136,12138,12141,12143,12146,12148,12150,12152,12154,12156,12158,12160,12162,12164,12166,12168],{"class":5282,"line":6159},[5280,12137,5862],{"class":5619},[5280,12139,12140],{"class":5293}," CORS",[5280,12142,5835],{"class":5452},[5280,12144,12145],{"class":5424},"config",[5280,12147,11875],{"class":5758},[5280,12149,7315],{"class":5452},[5280,12151,5862],{"class":5619},[5280,12153,5835],{"class":5452},[5280,12155,5952],{"class":5758},[5280,12157,5218],{"class":5452},[5280,12159,7935],{"class":5758},[5280,12161,7315],{"class":5452},[5280,12163,5952],{"class":5758},[5280,12165,5218],{"class":5452},[5280,12167,7935],{"class":5758},[5280,12169,6111],{"class":5452},[5280,12171,12172,12174,12176,12178,12180,12182,12184,12186,12188,12190,12192,12194],{"class":5282,"line":6187},[5280,12173,7328],{"class":5436},[5280,12175,7331],{"class":5619},[5280,12177,5835],{"class":5452},[5280,12179,7305],{"class":5424},[5280,12181,5997],{"class":5758},[5280,12183,5218],{"class":5452},[5280,12185,7935],{"class":5758},[5280,12187,7315],{"class":5452},[5280,12189,5952],{"class":5758},[5280,12191,5218],{"class":5452},[5280,12193,7935],{"class":5758},[5280,12195,6111],{"class":5452},[5280,12197,12198,12201,12203,12205,12207,12209,12211,12213,12215,12217,12219,12221,12223,12225,12227,12229,12231,12233],{"class":5282,"line":6193},[5280,12199,12200],{"class":5436},"        return",[5280,12202,5997],{"class":5424},[5280,12204,5218],{"class":5452},[5280,12206,7312],{"class":5293},[5280,12208,5835],{"class":5452},[5280,12210,5862],{"class":5619},[5280,12212,5835],{"class":5452},[5280,12214,5994],{"class":5424},[5280,12216,5997],{"class":5758},[5280,12218,5218],{"class":5452},[5280,12220,6002],{"class":5758},[5280,12222,5887],{"class":5452},[5280,12224,6007],{"class":5424},[5280,12226,6010],{"class":5452},[5280,12228,5952],{"class":5758},[5280,12230,5218],{"class":5452},[5280,12232,6017],{"class":5758},[5280,12234,6020],{"class":5452},[5280,12236,12237,12240,12242,12244,12246,12248,12250,12253,12255,12257],{"class":5282,"line":6207},[5280,12238,12239],{"class":5424},"            origin",[5280,12241,6029],{"class":5452},[5280,12243,6007],{"class":5424},[5280,12245,5218],{"class":5452},[5280,12247,6138],{"class":5424},[5280,12249,5218],{"class":5452},[5280,12251,12252],{"class":5293},"Get",[5280,12254,5835],{"class":5452},[5280,12256,12061],{"class":5297},[5280,12258,5598],{"class":5452},[5280,12260,12261],{"class":5282,"line":6221},[5280,12262,12263],{"class":5452},"            \n",[5280,12265,12266],{"class":5282,"line":6233},[5280,12267,12268],{"class":5286},"            \u002F\u002F Перевіряємо, чи дозволений домен\n",[5280,12270,12271,12274,12276],{"class":5282,"line":6245},[5280,12272,12273],{"class":5424},"            allowedOrigin",[5280,12275,6029],{"class":5452},[5280,12277,12278],{"class":5297},"\"\"\n",[5280,12280,12281,12284,12287,12289,12292,12294,12297,12300,12302,12305],{"class":5282,"line":6251},[5280,12282,12283],{"class":5436},"            for",[5280,12285,12286],{"class":5424}," _",[5280,12288,5887],{"class":5452},[5280,12290,12291],{"class":5424},"allowed",[5280,12293,6029],{"class":5452},[5280,12295,12296],{"class":5436},"range",[5280,12298,12299],{"class":5424}," config",[5280,12301,5218],{"class":5452},[5280,12303,12304],{"class":5424},"AllowOrigins",[5280,12306,6111],{"class":5452},[5280,12308,12309,12312,12315,12317,12319,12322,12324,12326,12329],{"class":5282,"line":6256},[5280,12310,12311],{"class":5436},"                if",[5280,12313,12314],{"class":5424}," allowed",[5280,12316,7029],{"class":5452},[5280,12318,12003],{"class":5297},[5280,12320,12321],{"class":5452}," || ",[5280,12323,12291],{"class":5424},[5280,12325,7029],{"class":5452},[5280,12327,12328],{"class":5424},"origin",[5280,12330,6111],{"class":5452},[5280,12332,12333,12336,12338],{"class":5282,"line":6308},[5280,12334,12335],{"class":5424},"                    allowedOrigin",[5280,12337,5829],{"class":5452},[5280,12339,12340],{"class":5424},"allowed\n",[5280,12342,12343],{"class":5282,"line":6339},[5280,12344,12345],{"class":5436},"                    break\n",[5280,12347,12348],{"class":5282,"line":6371},[5280,12349,12350],{"class":5452},"                }\n",[5280,12352,12353],{"class":5282,"line":6409},[5280,12354,10717],{"class":5452},[5280,12356,12357],{"class":5282,"line":6415},[5280,12358,12263],{"class":5452},[5280,12360,12361],{"class":5282,"line":6420},[5280,12362,12363],{"class":5286},"            \u002F\u002F Якщо домен дозволений, встановлюємо заголовки\n",[5280,12365,12366,12368,12371,12373,12376],{"class":5282,"line":6425},[5280,12367,10605],{"class":5436},[5280,12369,12370],{"class":5424}," allowedOrigin",[5280,12372,6301],{"class":5452},[5280,12374,12375],{"class":5297},"\"\"",[5280,12377,6111],{"class":5452},[5280,12379,12380,12382,12384,12386,12388],{"class":5282,"line":6450},[5280,12381,12311],{"class":5436},[5280,12383,12370],{"class":5424},[5280,12385,7029],{"class":5452},[5280,12387,12003],{"class":5297},[5280,12389,6111],{"class":5452},[5280,12391,12392,12395,12397,12399,12401,12403,12405,12408,12410,12412],{"class":5282,"line":6471},[5280,12393,12394],{"class":5424},"                    w",[5280,12396,5218],{"class":5452},[5280,12398,6138],{"class":5293},[5280,12400,6141],{"class":5452},[5280,12402,6144],{"class":5293},[5280,12404,5835],{"class":5452},[5280,12406,12407],{"class":5297},"\"Access-Control-Allow-Origin\"",[5280,12409,5887],{"class":5452},[5280,12411,12003],{"class":5297},[5280,12413,5598],{"class":5452},[5280,12415,12416,12419,12421],{"class":5282,"line":6507},[5280,12417,12418],{"class":5452},"                } ",[5280,12420,7073],{"class":5436},[5280,12422,6111],{"class":5452},[5280,12424,12425,12427,12429,12431,12433,12435,12437,12439,12441,12443],{"class":5282,"line":6512},[5280,12426,12394],{"class":5424},[5280,12428,5218],{"class":5452},[5280,12430,6138],{"class":5293},[5280,12432,6141],{"class":5452},[5280,12434,6144],{"class":5293},[5280,12436,5835],{"class":5452},[5280,12438,12407],{"class":5297},[5280,12440,5887],{"class":5452},[5280,12442,12328],{"class":5424},[5280,12444,5598],{"class":5452},[5280,12446,12447],{"class":5282,"line":6520},[5280,12448,12350],{"class":5452},[5280,12450,12451],{"class":5282,"line":6550},[5280,12452,10674],{"class":5452},[5280,12454,12455,12458,12460,12462,12464,12466,12468,12471],{"class":5282,"line":6556},[5280,12456,12457],{"class":5424},"                w",[5280,12459,5218],{"class":5452},[5280,12461,6138],{"class":5293},[5280,12463,6141],{"class":5452},[5280,12465,6144],{"class":5293},[5280,12467,5835],{"class":5452},[5280,12469,12470],{"class":5297},"\"Access-Control-Allow-Methods\"",[5280,12472,6368],{"class":5452},[5280,12474,12475,12478,12480,12483,12485,12487,12489,12492,12494,12497],{"class":5282,"line":6561},[5280,12476,12477],{"class":5424},"                    strings",[5280,12479,5218],{"class":5452},[5280,12481,12482],{"class":5293},"Join",[5280,12484,5835],{"class":5452},[5280,12486,12145],{"class":5424},[5280,12488,5218],{"class":5452},[5280,12490,12491],{"class":5424},"AllowMethods",[5280,12493,5887],{"class":5452},[5280,12495,12496],{"class":5297},"\", \"",[5280,12498,5970],{"class":5452},[5280,12500,12501,12503,12505,12507,12509,12511,12513,12516],{"class":5282,"line":6621},[5280,12502,12457],{"class":5424},[5280,12504,5218],{"class":5452},[5280,12506,6138],{"class":5293},[5280,12508,6141],{"class":5452},[5280,12510,6144],{"class":5293},[5280,12512,5835],{"class":5452},[5280,12514,12515],{"class":5297},"\"Access-Control-Allow-Headers\"",[5280,12517,6368],{"class":5452},[5280,12519,12520,12522,12524,12526,12528,12530,12532,12535,12537,12539],{"class":5282,"line":6626},[5280,12521,12477],{"class":5424},[5280,12523,5218],{"class":5452},[5280,12525,12482],{"class":5293},[5280,12527,5835],{"class":5452},[5280,12529,12145],{"class":5424},[5280,12531,5218],{"class":5452},[5280,12533,12534],{"class":5424},"AllowHeaders",[5280,12536,5887],{"class":5452},[5280,12538,12496],{"class":5297},[5280,12540,5970],{"class":5452},[5280,12542,12543],{"class":5282,"line":6631},[5280,12544,10674],{"class":5452},[5280,12546,12547,12549,12552,12554,12556,12558,12561,12564,12566],{"class":5282,"line":6663},[5280,12548,12311],{"class":5436},[5280,12550,12551],{"class":5293}," len",[5280,12553,5835],{"class":5452},[5280,12555,12145],{"class":5424},[5280,12557,5218],{"class":5452},[5280,12559,12560],{"class":5424},"ExposeHeaders",[5280,12562,12563],{"class":5452},") > ",[5280,12565,11413],{"class":5439},[5280,12567,6111],{"class":5452},[5280,12569,12570,12572,12574,12576,12578,12580,12582,12585],{"class":5282,"line":6678},[5280,12571,12394],{"class":5424},[5280,12573,5218],{"class":5452},[5280,12575,6138],{"class":5293},[5280,12577,6141],{"class":5452},[5280,12579,6144],{"class":5293},[5280,12581,5835],{"class":5452},[5280,12583,12584],{"class":5297},"\"Access-Control-Expose-Headers\"",[5280,12586,6368],{"class":5452},[5280,12588,12589,12592,12594,12596,12598,12600,12602,12604,12606,12608],{"class":5282,"line":6721},[5280,12590,12591],{"class":5424},"                        strings",[5280,12593,5218],{"class":5452},[5280,12595,12482],{"class":5293},[5280,12597,5835],{"class":5452},[5280,12599,12145],{"class":5424},[5280,12601,5218],{"class":5452},[5280,12603,12560],{"class":5424},[5280,12605,5887],{"class":5452},[5280,12607,12496],{"class":5297},[5280,12609,5970],{"class":5452},[5280,12611,12612],{"class":5282,"line":6727},[5280,12613,12350],{"class":5452},[5280,12615,12616],{"class":5282,"line":6751},[5280,12617,10674],{"class":5452},[5280,12619,12620,12622,12624,12626,12629],{"class":5282,"line":6784},[5280,12621,12311],{"class":5436},[5280,12623,12299],{"class":5424},[5280,12625,5218],{"class":5452},[5280,12627,12628],{"class":5424},"AllowCredentials",[5280,12630,6111],{"class":5452},[5280,12632,12633,12635,12637,12639,12641,12643,12645,12648,12650,12653],{"class":5282,"line":6799},[5280,12634,12394],{"class":5424},[5280,12636,5218],{"class":5452},[5280,12638,6138],{"class":5293},[5280,12640,6141],{"class":5452},[5280,12642,6144],{"class":5293},[5280,12644,5835],{"class":5452},[5280,12646,12647],{"class":5297},"\"Access-Control-Allow-Credentials\"",[5280,12649,5887],{"class":5452},[5280,12651,12652],{"class":5297},"\"true\"",[5280,12654,5598],{"class":5452},[5280,12656,12657],{"class":5282,"line":6831},[5280,12658,12350],{"class":5452},[5280,12660,12661],{"class":5282,"line":6837},[5280,12662,10674],{"class":5452},[5280,12664,12665,12667,12669,12671,12674,12677,12679],{"class":5282,"line":6842},[5280,12666,12311],{"class":5436},[5280,12668,12299],{"class":5424},[5280,12670,5218],{"class":5452},[5280,12672,12673],{"class":5424},"MaxAge",[5280,12675,12676],{"class":5452}," > ",[5280,12678,11413],{"class":5439},[5280,12680,6111],{"class":5452},[5280,12682,12683,12685,12687,12689,12691,12693,12695,12698],{"class":5282,"line":6901},[5280,12684,12394],{"class":5424},[5280,12686,5218],{"class":5452},[5280,12688,6138],{"class":5293},[5280,12690,6141],{"class":5452},[5280,12692,6144],{"class":5293},[5280,12694,5835],{"class":5452},[5280,12696,12697],{"class":5297},"\"Access-Control-Max-Age\"",[5280,12699,6368],{"class":5452},[5280,12701,12703,12706,12708,12711,12713,12715,12717,12719,12721,12723,12725,12727],{"class":5282,"line":12702},70,[5280,12704,12705],{"class":5424},"                        fmt",[5280,12707,5218],{"class":5452},[5280,12709,12710],{"class":5293},"Sprintf",[5280,12712,5835],{"class":5452},[5280,12714,6584],{"class":5297},[5280,12716,9946],{"class":5424},[5280,12718,6584],{"class":5297},[5280,12720,5887],{"class":5452},[5280,12722,12145],{"class":5424},[5280,12724,5218],{"class":5452},[5280,12726,12673],{"class":5424},[5280,12728,5970],{"class":5452},[5280,12730,12732],{"class":5282,"line":12731},71,[5280,12733,12350],{"class":5452},[5280,12735,12737],{"class":5282,"line":12736},72,[5280,12738,10717],{"class":5452},[5280,12740,12742],{"class":5282,"line":12741},73,[5280,12743,12263],{"class":5452},[5280,12745,12747],{"class":5282,"line":12746},74,[5280,12748,12749],{"class":5286},"            \u002F\u002F Preflight-запит (OPTIONS) — відповідаємо одразу\n",[5280,12751,12753,12755,12757,12759,12761,12763,12765,12767,12770],{"class":5282,"line":12752},75,[5280,12754,10605],{"class":5436},[5280,12756,6104],{"class":5424},[5280,12758,5218],{"class":5452},[5280,12760,6074],{"class":5424},[5280,12762,7029],{"class":5452},[5280,12764,5952],{"class":5424},[5280,12766,5218],{"class":5452},[5280,12768,12769],{"class":5424},"MethodOptions",[5280,12771,6111],{"class":5452},[5280,12773,12775,12777,12779,12781,12783,12785,12787,12790],{"class":5282,"line":12774},76,[5280,12776,12457],{"class":5424},[5280,12778,5218],{"class":5452},[5280,12780,6457],{"class":5293},[5280,12782,5835],{"class":5452},[5280,12784,5952],{"class":5424},[5280,12786,5218],{"class":5452},[5280,12788,12789],{"class":5424},"StatusNoContent",[5280,12791,5598],{"class":5452},[5280,12793,12795],{"class":5282,"line":12794},77,[5280,12796,12797],{"class":5436},"                return\n",[5280,12799,12801],{"class":5282,"line":12800},78,[5280,12802,10717],{"class":5452},[5280,12804,12806],{"class":5282,"line":12805},79,[5280,12807,12263],{"class":5452},[5280,12809,12811],{"class":5282,"line":12810},80,[5280,12812,12813],{"class":5286},"            \u002F\u002F Інші запити — передаємо далі\n",[5280,12815,12817,12820,12822,12824,12826,12828,12830,12832],{"class":5282,"line":12816},81,[5280,12818,12819],{"class":5424},"            next",[5280,12821,5218],{"class":5452},[5280,12823,8033],{"class":5293},[5280,12825,5835],{"class":5452},[5280,12827,5994],{"class":5424},[5280,12829,5887],{"class":5452},[5280,12831,6007],{"class":5424},[5280,12833,5598],{"class":5452},[5280,12835,12837],{"class":5282,"line":12836},82,[5280,12838,12839],{"class":5452},"        })\n",[5280,12841,12843],{"class":5282,"line":12842},83,[5280,12844,6553],{"class":5452},[5280,12846,12848],{"class":5282,"line":12847},84,[5280,12849,5976],{"class":5452},[5266,12851,12853],{"id":12852},"використання-cors","Використання CORS",[5271,12855,12857],{"className":5411,"code":12856,"language":5325,"meta":5276,"style":5276},"\u002F\u002F cmd\u002Fcors-server\u002Fmain.go\npackage main\n\nimport (\n    \"encoding\u002Fjson\"\n    \"fmt\"\n    \"log\"\n    \"net\u002Fhttp\"\n    \"myapp\u002Fmiddleware\"\n)\n\nfunc apiHandler(w http.ResponseWriter, r *http.Request) {\n    w.Header().Set(\"Content-Type\", \"application\u002Fjson\")\n    json.NewEncoder(w).Encode(map[string]string{\n        \"message\": \"API доступне з будь-якого домену\",\n    })\n}\n\nfunc main() {\n    corsConfig := middleware.DefaultCORSConfig()\n    \n    handler := middleware.Chain(\n        http.HandlerFunc(apiHandler),\n        middleware.Logger,\n        middleware.Recovery,\n        middleware.CORS(corsConfig),\n    )\n    \n    http.Handle(\"\u002Fapi\u002Fdata\", handler)\n    \n    fmt.Println(\"CORS-enabled server on :8080...\")\n    log.Fatal(http.ListenAndServe(\":8080\", nil))\n}\n",[5215,12858,12859,12864,12870,12874,12880,12884,12888,12892,12896,12900,12904,12908,12939,12961,12991,13003,13007,13011,13015,13023,13039,13043,13058,13073,13083,13093,13108,13113,13117,13136,13140,13155,13181],{"__ignoreMap":5276},[5280,12860,12861],{"class":5282,"line":5283},[5280,12862,12863],{"class":5286},"\u002F\u002F cmd\u002Fcors-server\u002Fmain.go\n",[5280,12865,12866,12868],{"class":5282,"line":5290},[5280,12867,5755],{"class":5619},[5280,12869,5759],{"class":5758},[5280,12871,12872],{"class":5282,"line":5301},[5280,12873,5313],{"emptyLinePlaceholder":5312},[5280,12875,12876,12878],{"class":5282,"line":5309},[5280,12877,5768],{"class":5436},[5280,12879,5453],{"class":5452},[5280,12881,12882],{"class":5282,"line":5316},[5280,12883,5775],{"class":5297},[5280,12885,12886],{"class":5282,"line":5322},[5280,12887,5780],{"class":5297},[5280,12889,12890],{"class":5282,"line":5336},[5280,12891,5785],{"class":5297},[5280,12893,12894],{"class":5282,"line":5341},[5280,12895,5790],{"class":5297},[5280,12897,12898],{"class":5282,"line":5347},[5280,12899,10075],{"class":5297},[5280,12901,12902],{"class":5282,"line":5358},[5280,12903,5598],{"class":5452},[5280,12905,12906],{"class":5282,"line":5368},[5280,12907,5313],{"emptyLinePlaceholder":5312},[5280,12909,12910,12912,12915,12917,12919,12921,12923,12925,12927,12929,12931,12933,12935,12937],{"class":5282,"line":5378},[5280,12911,5862],{"class":5619},[5280,12913,12914],{"class":5293}," apiHandler",[5280,12916,5835],{"class":5452},[5280,12918,5994],{"class":5424},[5280,12920,5997],{"class":5758},[5280,12922,5218],{"class":5452},[5280,12924,6002],{"class":5758},[5280,12926,5887],{"class":5452},[5280,12928,6007],{"class":5424},[5280,12930,6010],{"class":5452},[5280,12932,5952],{"class":5758},[5280,12934,5218],{"class":5452},[5280,12936,6017],{"class":5758},[5280,12938,6020],{"class":5452},[5280,12940,12941,12943,12945,12947,12949,12951,12953,12955,12957,12959],{"class":5282,"line":5391},[5280,12942,6730],{"class":5424},[5280,12944,5218],{"class":5452},[5280,12946,6138],{"class":5293},[5280,12948,6141],{"class":5452},[5280,12950,6144],{"class":5293},[5280,12952,5835],{"class":5452},[5280,12954,6149],{"class":5297},[5280,12956,5887],{"class":5452},[5280,12958,6154],{"class":5297},[5280,12960,5598],{"class":5452},[5280,12962,12963,12965,12967,12969,12971,12973,12975,12977,12979,12981,12983,12985,12987,12989],{"class":5282,"line":5705},[5280,12964,6754],{"class":5424},[5280,12966,5218],{"class":5452},[5280,12968,6167],{"class":5293},[5280,12970,5835],{"class":5452},[5280,12972,5994],{"class":5424},[5280,12974,6174],{"class":5452},[5280,12976,6177],{"class":5293},[5280,12978,5835],{"class":5452},[5280,12980,5838],{"class":5619},[5280,12982,5841],{"class":5452},[5280,12984,5844],{"class":5758},[5280,12986,5847],{"class":5452},[5280,12988,5844],{"class":5758},[5280,12990,6781],{"class":5452},[5280,12992,12993,12996,12998,13001],{"class":5282,"line":5820},[5280,12994,12995],{"class":5297},"        \"message\"",[5280,12997,6790],{"class":5452},[5280,12999,13000],{"class":5297},"\"API доступне з будь-якого домену\"",[5280,13002,6796],{"class":5452},[5280,13004,13005],{"class":5282,"line":5854},[5280,13006,6834],{"class":5452},[5280,13008,13009],{"class":5282,"line":5859},[5280,13010,5976],{"class":5452},[5280,13012,13013],{"class":5282,"line":5871},[5280,13014,5313],{"emptyLinePlaceholder":5312},[5280,13016,13017,13019,13021],{"class":5282,"line":5895},[5280,13018,5862],{"class":5619},[5280,13020,5865],{"class":5293},[5280,13022,5868],{"class":5452},[5280,13024,13025,13028,13030,13032,13034,13037],{"class":5282,"line":5916},[5280,13026,13027],{"class":5424},"    corsConfig",[5280,13029,6029],{"class":5452},[5280,13031,8768],{"class":5424},[5280,13033,5218],{"class":5452},[5280,13035,13036],{"class":5293},"DefaultCORSConfig",[5280,13038,6039],{"class":5452},[5280,13040,13041],{"class":5282,"line":5921},[5280,13042,6724],{"class":5452},[5280,13044,13045,13048,13050,13052,13054,13056],{"class":5282,"line":5939},[5280,13046,13047],{"class":5424},"    handler",[5280,13049,6029],{"class":5452},[5280,13051,8768],{"class":5424},[5280,13053,5218],{"class":5452},[5280,13055,11590],{"class":5293},[5280,13057,9105],{"class":5452},[5280,13059,13060,13062,13064,13066,13068,13071],{"class":5282,"line":5973},[5280,13061,6523],{"class":5424},[5280,13063,5218],{"class":5452},[5280,13065,7312],{"class":5293},[5280,13067,5835],{"class":5452},[5280,13069,13070],{"class":5424},"apiHandler",[5280,13072,6828],{"class":5452},[5280,13074,13075,13077,13079,13081],{"class":5282,"line":5979},[5280,13076,10992],{"class":5424},[5280,13078,5218],{"class":5452},[5280,13080,10291],{"class":5424},[5280,13082,6796],{"class":5452},[5280,13084,13085,13087,13089,13091],{"class":5282,"line":5984},[5280,13086,10992],{"class":5424},[5280,13088,5218],{"class":5452},[5280,13090,11008],{"class":5424},[5280,13092,6796],{"class":5452},[5280,13094,13095,13097,13099,13101,13103,13106],{"class":5282,"line":6023},[5280,13096,10992],{"class":5424},[5280,13098,5218],{"class":5452},[5280,13100,11259],{"class":5293},[5280,13102,5835],{"class":5452},[5280,13104,13105],{"class":5424},"corsConfig",[5280,13107,6828],{"class":5452},[5280,13109,13110],{"class":5282,"line":6042},[5280,13111,13112],{"class":5452},"    )\n",[5280,13114,13115],{"class":5282,"line":6093},[5280,13116,6724],{"class":5452},[5280,13118,13119,13121,13123,13125,13127,13130,13132,13134],{"class":5282,"line":6098},[5280,13120,5874],{"class":5424},[5280,13122,5218],{"class":5452},[5280,13124,8145],{"class":5293},[5280,13126,5835],{"class":5452},[5280,13128,13129],{"class":5297},"\"\u002Fapi\u002Fdata\"",[5280,13131,5887],{"class":5452},[5280,13133,5233],{"class":5424},[5280,13135,5598],{"class":5452},[5280,13137,13138],{"class":5282,"line":6114},[5280,13139,6724],{"class":5452},[5280,13141,13142,13144,13146,13148,13150,13153],{"class":5282,"line":6130},[5280,13143,5924],{"class":5424},[5280,13145,5218],{"class":5452},[5280,13147,5929],{"class":5293},[5280,13149,5835],{"class":5452},[5280,13151,13152],{"class":5297},"\"CORS-enabled server on :8080...\"",[5280,13154,5598],{"class":5452},[5280,13156,13157,13159,13161,13163,13165,13167,13169,13171,13173,13175,13177,13179],{"class":5282,"line":6159},[5280,13158,5942],{"class":5424},[5280,13160,5218],{"class":5452},[5280,13162,5947],{"class":5293},[5280,13164,5835],{"class":5452},[5280,13166,5952],{"class":5424},[5280,13168,5218],{"class":5452},[5280,13170,5957],{"class":5293},[5280,13172,5835],{"class":5452},[5280,13174,5962],{"class":5297},[5280,13176,5887],{"class":5452},[5280,13178,5967],{"class":5619},[5280,13180,5970],{"class":5452},[5280,13182,13183],{"class":5282,"line":6187},[5280,13184,5976],{"class":5452},[5262,13186,13187],{},[5227,13188,13189],{},"Тестування з браузера:",[5271,13191,13195],{"className":13192,"code":13193,"language":13194,"meta":5276,"style":5276},"language-html shiki shiki-themes light-plus dark-plus dark-plus","\u003C!-- test.html -->\n\u003C!DOCTYPE html>\n\u003Chtml>\n\u003Chead>\n    \u003Ctitle>CORS Test\u003C\u002Ftitle>\n\u003C\u002Fhead>\n\u003Cbody>\n    \u003Cbutton onclick=\"fetchData()\">Fetch Data\u003C\u002Fbutton>\n    \u003Cpre id=\"result\">\u003C\u002Fpre>\n    \n    \u003Cscript>\n        async function fetchData() {\n            try {\n                const response = await fetch('http:\u002F\u002Flocalhost:8080\u002Fapi\u002Fdata');\n                const data = await response.json();\n                document.getElementById('result').textContent = \n                    JSON.stringify(data, null, 2);\n            } catch (error) {\n                document.getElementById('result').textContent = \n                    'Error: ' + error.message;\n            }\n        }\n    \u003C\u002Fscript>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>\n","html",[5215,13196,13197,13202,13218,13227,13236,13257,13265,13274,13307,13328,13332,13341,13355,13362,13388,13408,13433,13460,13474,13494,13513,13517,13521,13530,13538],{"__ignoreMap":5276},[5280,13198,13199],{"class":5282,"line":5283},[5280,13200,13201],{"class":5286},"\u003C!-- test.html -->\n",[5280,13203,13204,13208,13211,13215],{"class":5282,"line":5290},[5280,13205,13207],{"class":13206},"s0P7L","\u003C!",[5280,13209,13210],{"class":11729},"DOCTYPE",[5280,13212,13214],{"class":13213},"sa4r_"," html",[5280,13216,13217],{"class":13206},">\n",[5280,13219,13220,13223,13225],{"class":5282,"line":5301},[5280,13221,13222],{"class":13206},"\u003C",[5280,13224,13194],{"class":11729},[5280,13226,13217],{"class":13206},[5280,13228,13229,13231,13234],{"class":5282,"line":5309},[5280,13230,13222],{"class":13206},[5280,13232,13233],{"class":11729},"head",[5280,13235,13217],{"class":13206},[5280,13237,13238,13241,13244,13247,13250,13253,13255],{"class":5282,"line":5316},[5280,13239,13240],{"class":13206},"    \u003C",[5280,13242,13243],{"class":11729},"title",[5280,13245,13246],{"class":13206},">",[5280,13248,13249],{"class":5452},"CORS Test",[5280,13251,13252],{"class":13206},"\u003C\u002F",[5280,13254,13243],{"class":11729},[5280,13256,13217],{"class":13206},[5280,13258,13259,13261,13263],{"class":5282,"line":5322},[5280,13260,13252],{"class":13206},[5280,13262,13233],{"class":11729},[5280,13264,13217],{"class":13206},[5280,13266,13267,13269,13272],{"class":5282,"line":5336},[5280,13268,13222],{"class":13206},[5280,13270,13271],{"class":11729},"body",[5280,13273,13217],{"class":13206},[5280,13275,13276,13278,13281,13284,13287,13290,13293,13296,13298,13301,13303,13305],{"class":5282,"line":5341},[5280,13277,13240],{"class":13206},[5280,13279,13280],{"class":11729},"button",[5280,13282,13283],{"class":13213}," onclick",[5280,13285,13286],{"class":5452},"=",[5280,13288,6584],{"class":13289},"su9tN",[5280,13291,13292],{"class":5293},"fetchData",[5280,13294,13295],{"class":13289},"()\"",[5280,13297,13246],{"class":13206},[5280,13299,13300],{"class":5452},"Fetch Data",[5280,13302,13252],{"class":13206},[5280,13304,13280],{"class":11729},[5280,13306,13217],{"class":13206},[5280,13308,13309,13311,13313,13316,13318,13321,13324,13326],{"class":5282,"line":5347},[5280,13310,13240],{"class":13206},[5280,13312,5271],{"class":11729},[5280,13314,13315],{"class":13213}," id",[5280,13317,13286],{"class":5452},[5280,13319,13320],{"class":13289},"\"result\"",[5280,13322,13323],{"class":13206},">\u003C\u002F",[5280,13325,5271],{"class":11729},[5280,13327,13217],{"class":13206},[5280,13329,13330],{"class":5282,"line":5358},[5280,13331,6724],{"class":5452},[5280,13333,13334,13336,13339],{"class":5282,"line":5368},[5280,13335,13240],{"class":13206},[5280,13337,13338],{"class":11729},"script",[5280,13340,13217],{"class":13206},[5280,13342,13343,13346,13349,13352],{"class":5282,"line":5378},[5280,13344,13345],{"class":5619},"        async",[5280,13347,13348],{"class":5619}," function",[5280,13350,13351],{"class":5293}," fetchData",[5280,13353,5868],{"class":13354},"sD7JJ",[5280,13356,13357,13360],{"class":5282,"line":5391},[5280,13358,13359],{"class":5436},"            try",[5280,13361,6111],{"class":13354},[5280,13363,13364,13367,13371,13374,13377,13380,13382,13385],{"class":5282,"line":5705},[5280,13365,13366],{"class":5619},"                const",[5280,13368,13370],{"class":13369},"s-QsJ"," response",[5280,13372,13373],{"class":5452}," =",[5280,13375,13376],{"class":5436}," await",[5280,13378,13379],{"class":5293}," fetch",[5280,13381,5835],{"class":13354},[5280,13383,13384],{"class":5297},"'http:\u002F\u002Flocalhost:8080\u002Fapi\u002Fdata'",[5280,13386,13387],{"class":13354},");\n",[5280,13389,13390,13392,13395,13397,13399,13401,13403,13405],{"class":5282,"line":5820},[5280,13391,13366],{"class":5619},[5280,13393,13394],{"class":13369}," data",[5280,13396,13373],{"class":5452},[5280,13398,13376],{"class":5436},[5280,13400,13370],{"class":5424},[5280,13402,5218],{"class":13354},[5280,13404,6267],{"class":5293},[5280,13406,13407],{"class":13354},"();\n",[5280,13409,13410,13413,13415,13418,13420,13423,13425,13428,13430],{"class":5282,"line":5854},[5280,13411,13412],{"class":5424},"                document",[5280,13414,5218],{"class":13354},[5280,13416,13417],{"class":5293},"getElementById",[5280,13419,5835],{"class":13354},[5280,13421,13422],{"class":5297},"'result'",[5280,13424,6174],{"class":13354},[5280,13426,13427],{"class":5424},"textContent",[5280,13429,13373],{"class":5452},[5280,13431,13432],{"class":13354}," \n",[5280,13434,13435,13438,13440,13443,13445,13448,13450,13453,13455,13458],{"class":5282,"line":5859},[5280,13436,13437],{"class":13369},"                    JSON",[5280,13439,5218],{"class":13354},[5280,13441,13442],{"class":5293},"stringify",[5280,13444,5835],{"class":13354},[5280,13446,13447],{"class":5424},"data",[5280,13449,5887],{"class":13354},[5280,13451,13452],{"class":5619},"null",[5280,13454,5887],{"class":13354},[5280,13456,13457],{"class":5439},"2",[5280,13459,13387],{"class":13354},[5280,13461,13462,13465,13468,13470,13472],{"class":5282,"line":5871},[5280,13463,13464],{"class":13354},"            } ",[5280,13466,13467],{"class":5436},"catch",[5280,13469,5720],{"class":13354},[5280,13471,9567],{"class":5424},[5280,13473,6020],{"class":13354},[5280,13475,13476,13478,13480,13482,13484,13486,13488,13490,13492],{"class":5282,"line":5895},[5280,13477,13412],{"class":5424},[5280,13479,5218],{"class":13354},[5280,13481,13417],{"class":5293},[5280,13483,5835],{"class":13354},[5280,13485,13422],{"class":5297},[5280,13487,6174],{"class":13354},[5280,13489,13427],{"class":5424},[5280,13491,13373],{"class":5452},[5280,13493,13432],{"class":13354},[5280,13495,13496,13499,13502,13505,13507,13510],{"class":5282,"line":5916},[5280,13497,13498],{"class":5297},"                    'Error: '",[5280,13500,13501],{"class":5452}," +",[5280,13503,13504],{"class":5424}," error",[5280,13506,5218],{"class":13354},[5280,13508,13509],{"class":5424},"message",[5280,13511,13512],{"class":13354},";\n",[5280,13514,13515],{"class":5282,"line":5921},[5280,13516,10717],{"class":13354},[5280,13518,13519],{"class":5282,"line":5939},[5280,13520,6248],{"class":13354},[5280,13522,13523,13526,13528],{"class":5282,"line":5973},[5280,13524,13525],{"class":13206},"    \u003C\u002F",[5280,13527,13338],{"class":11729},[5280,13529,13217],{"class":13206},[5280,13531,13532,13534,13536],{"class":5282,"line":5979},[5280,13533,13252],{"class":13206},[5280,13535,13271],{"class":11729},[5280,13537,13217],{"class":13206},[5280,13539,13540,13542,13544],{"class":5282,"line":5984},[5280,13541,13252],{"class":13206},[5280,13543,13194],{"class":11729},[5280,13545,13217],{"class":13206},[5262,13547,13548],{},"Без CORS Middleware браузер заблокував би запит. З CORS — запит пройде успішно.",[5254,13550],{},[5257,13552,13554],{"id":13553},"rate-limiting-middleware","Rate Limiting Middleware",[5266,13556,13558],{"id":13557},"навіщо-потрібен-rate-limiting","Навіщо потрібен Rate Limiting?",[5262,13560,13561,5720,13564,13567],{},[5227,13562,13563],{},"Rate Limiting",[5207,13565,13566],{},"обмеження частоти запитів",") захищає API від:",[5194,13569,13570,13573,13576],{},[5197,13571,13572],{},"DDoS-атак",[5197,13574,13575],{},"Ненавмисного перевантаження (помилки у клієнтському коді)",[5197,13577,13578],{},"Зловживань безплатними API",[5262,13580,13581],{},"Типові стратегії:",[5194,13583,13584,13590,13596],{},[5197,13585,13586,13589],{},[5227,13587,13588],{},"Fixed Window:"," X запитів за хвилину",[5197,13591,13592,13595],{},[5227,13593,13594],{},"Sliding Window:"," X запитів за останню хвилину",[5197,13597,13598,13601],{},[5227,13599,13600],{},"Token Bucket:"," поповнюваний запас токенів",[5266,13603,13605],{"id":13604},"реалізація-через-token-bucket","Реалізація через Token Bucket",[5262,13607,13608,13609,5408],{},"Використаємо пакет ",[5215,13610,13611],{},"golang.org\u002Fx\u002Ftime\u002Frate",[5271,13613,13615],{"className":5273,"code":13614,"language":5275,"meta":5276,"style":5276},"go get golang.org\u002Fx\u002Ftime\u002Frate\n",[5215,13616,13617],{"__ignoreMap":5276},[5280,13618,13619,13621,13623],{"class":5282,"line":5283},[5280,13620,5325],{"class":5293},[5280,13622,5352],{"class":5297},[5280,13624,5365],{"class":5297},[5271,13626,13628],{"className":5411,"code":13627,"language":5325,"meta":5276,"style":5276},"\u002F\u002F middleware\u002Fratelimit.go\npackage middleware\n\nimport (\n    \"net\u002Fhttp\"\n    \"sync\"\n    \"time\"\n\n    \"golang.org\u002Fx\u002Ftime\u002Frate\"\n)\n\n\u002F\u002F RateLimiter зберігає окремий лімітер для кожного клієнта\ntype RateLimiter struct {\n    mu       sync.Mutex\n    limiters map[string]*rate.Limiter\n    rate     rate.Limit\n    burst    int\n}\n\n\u002F\u002F NewRateLimiter створює новий rate limiter\n\u002F\u002F rps - запитів на секунду, burst - максимальний сплеск\nfunc NewRateLimiter(rps int, burst int) *RateLimiter {\n    return &RateLimiter{\n        limiters: make(map[string]*rate.Limiter),\n        rate:     rate.Limit(rps),\n        burst:    burst,\n    }\n}\n\n\u002F\u002F getLimiter отримує або створює лімітер для IP\nfunc (rl *RateLimiter) getLimiter(ip string) *rate.Limiter {\n    rl.mu.Lock()\n    defer rl.mu.Unlock()\n    \n    limiter, exists := rl.limiters[ip]\n    if !exists {\n        limiter = rate.NewLimiter(rl.rate, rl.burst)\n        rl.limiters[ip] = limiter\n    }\n    \n    return limiter\n}\n\n\u002F\u002F Middleware повертає HTTP Middleware\nfunc (rl *RateLimiter) Middleware(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        \u002F\u002F Отримуємо IP клієнта\n        ip := r.RemoteAddr\n        \n        \u002F\u002F Перевіряємо ліміт\n        limiter := rl.getLimiter(ip)\n        \n        if !limiter.Allow() {\n            \u002F\u002F Ліміт перевищено\n            w.Header().Set(\"X-RateLimit-Retry-After\", \n                time.Second.String())\n            http.Error(w, \n                \"Too Many Requests - Rate limit exceeded\", \n                http.StatusTooManyRequests)\n            return\n        }\n        \n        \u002F\u002F Ліміт не перевищено — передаємо далі\n        next.ServeHTTP(w, r)\n    })\n}\n",[5215,13629,13630,13635,13641,13645,13651,13655,13660,13664,13668,13673,13677,13681,13686,13697,13710,13733,13746,13754,13758,13762,13767,13772,13801,13812,13840,13860,13872,13876,13880,13884,13889,13924,13941,13959,13963,13990,14000,14032,14050,14054,14058,14065,14069,14073,14078,14114,14152,14157,14171,14175,14180,14198,14202,14218,14223,14243,14260,14274,14281,14292,14296,14300,14304,14309,14327,14331],{"__ignoreMap":5276},[5280,13631,13632],{"class":5282,"line":5283},[5280,13633,13634],{"class":5286},"\u002F\u002F middleware\u002Fratelimit.go\n",[5280,13636,13637,13639],{"class":5282,"line":5290},[5280,13638,5755],{"class":5619},[5280,13640,5641],{"class":5758},[5280,13642,13643],{"class":5282,"line":5301},[5280,13644,5313],{"emptyLinePlaceholder":5312},[5280,13646,13647,13649],{"class":5282,"line":5309},[5280,13648,5768],{"class":5436},[5280,13650,5453],{"class":5452},[5280,13652,13653],{"class":5282,"line":5316},[5280,13654,5790],{"class":5297},[5280,13656,13657],{"class":5282,"line":5322},[5280,13658,13659],{"class":5297},"    \"sync\"\n",[5280,13661,13662],{"class":5282,"line":5336},[5280,13663,5795],{"class":5297},[5280,13665,13666],{"class":5282,"line":5341},[5280,13667,5313],{"emptyLinePlaceholder":5312},[5280,13669,13670],{"class":5282,"line":5347},[5280,13671,13672],{"class":5297},"    \"golang.org\u002Fx\u002Ftime\u002Frate\"\n",[5280,13674,13675],{"class":5282,"line":5358},[5280,13676,5598],{"class":5452},[5280,13678,13679],{"class":5282,"line":5368},[5280,13680,5313],{"emptyLinePlaceholder":5312},[5280,13682,13683],{"class":5282,"line":5378},[5280,13684,13685],{"class":5286},"\u002F\u002F RateLimiter зберігає окремий лімітер для кожного клієнта\n",[5280,13687,13688,13690,13693,13695],{"class":5282,"line":5391},[5280,13689,7921],{"class":5619},[5280,13691,13692],{"class":5758}," RateLimiter",[5280,13694,6216],{"class":5619},[5280,13696,6111],{"class":5452},[5280,13698,13699,13702,13705,13707],{"class":5282,"line":5705},[5280,13700,13701],{"class":5424},"    mu",[5280,13703,13704],{"class":5758},"       sync",[5280,13706,5218],{"class":5452},[5280,13708,13709],{"class":5758},"Mutex\n",[5280,13711,13712,13715,13718,13720,13722,13725,13728,13730],{"class":5282,"line":5820},[5280,13713,13714],{"class":5424},"    limiters",[5280,13716,13717],{"class":5619}," map",[5280,13719,5841],{"class":5452},[5280,13721,5844],{"class":5758},[5280,13723,13724],{"class":5452},"]*",[5280,13726,13727],{"class":5758},"rate",[5280,13729,5218],{"class":5452},[5280,13731,13732],{"class":5758},"Limiter\n",[5280,13734,13735,13738,13741,13743],{"class":5282,"line":5854},[5280,13736,13737],{"class":5424},"    rate",[5280,13739,13740],{"class":5758},"     rate",[5280,13742,5218],{"class":5452},[5280,13744,13745],{"class":5758},"Limit\n",[5280,13747,13748,13751],{"class":5282,"line":5859},[5280,13749,13750],{"class":5424},"    burst",[5280,13752,13753],{"class":5758},"    int\n",[5280,13755,13756],{"class":5282,"line":5871},[5280,13757,5976],{"class":5452},[5280,13759,13760],{"class":5282,"line":5895},[5280,13761,5313],{"emptyLinePlaceholder":5312},[5280,13763,13764],{"class":5282,"line":5916},[5280,13765,13766],{"class":5286},"\u002F\u002F NewRateLimiter створює новий rate limiter\n",[5280,13768,13769],{"class":5282,"line":5921},[5280,13770,13771],{"class":5286},"\u002F\u002F rps - запитів на секунду, burst - максимальний сплеск\n",[5280,13773,13774,13776,13779,13781,13784,13786,13788,13791,13793,13796,13799],{"class":5282,"line":5939},[5280,13775,5862],{"class":5619},[5280,13777,13778],{"class":5293}," NewRateLimiter",[5280,13780,5835],{"class":5452},[5280,13782,13783],{"class":5424},"rps",[5280,13785,9446],{"class":5758},[5280,13787,5887],{"class":5452},[5280,13789,13790],{"class":5424},"burst",[5280,13792,9446],{"class":5758},[5280,13794,13795],{"class":5452},") *",[5280,13797,13798],{"class":5758},"RateLimiter",[5280,13800,6111],{"class":5452},[5280,13802,13803,13805,13808,13810],{"class":5282,"line":5973},[5280,13804,7328],{"class":5436},[5280,13806,13807],{"class":5452}," &",[5280,13809,13798],{"class":5758},[5280,13811,6781],{"class":5452},[5280,13813,13814,13817,13819,13821,13823,13825,13827,13829,13831,13833,13835,13838],{"class":5282,"line":5979},[5280,13815,13816],{"class":5424},"        limiters",[5280,13818,6790],{"class":5452},[5280,13820,5832],{"class":5293},[5280,13822,5835],{"class":5452},[5280,13824,5838],{"class":5619},[5280,13826,5841],{"class":5452},[5280,13828,5844],{"class":5758},[5280,13830,13724],{"class":5452},[5280,13832,13727],{"class":5758},[5280,13834,5218],{"class":5452},[5280,13836,13837],{"class":5758},"Limiter",[5280,13839,6828],{"class":5452},[5280,13841,13842,13845,13847,13849,13851,13854,13856,13858],{"class":5282,"line":5984},[5280,13843,13844],{"class":5424},"        rate",[5280,13846,9768],{"class":5452},[5280,13848,13727],{"class":5424},[5280,13850,5218],{"class":5452},[5280,13852,13853],{"class":5293},"Limit",[5280,13855,5835],{"class":5452},[5280,13857,13783],{"class":5424},[5280,13859,6828],{"class":5452},[5280,13861,13862,13865,13868,13870],{"class":5282,"line":6023},[5280,13863,13864],{"class":5424},"        burst",[5280,13866,13867],{"class":5452},":    ",[5280,13869,13790],{"class":5424},[5280,13871,6796],{"class":5452},[5280,13873,13874],{"class":5282,"line":6042},[5280,13875,6553],{"class":5452},[5280,13877,13878],{"class":5282,"line":6093},[5280,13879,5976],{"class":5452},[5280,13881,13882],{"class":5282,"line":6098},[5280,13883,5313],{"emptyLinePlaceholder":5312},[5280,13885,13886],{"class":5282,"line":6114},[5280,13887,13888],{"class":5286},"\u002F\u002F getLimiter отримує або створює лімітер для IP\n",[5280,13890,13891,13893,13895,13898,13900,13902,13904,13907,13909,13912,13914,13916,13918,13920,13922],{"class":5282,"line":6130},[5280,13892,5862],{"class":5619},[5280,13894,5720],{"class":5452},[5280,13896,13897],{"class":5424},"rl ",[5280,13899,8072],{"class":5452},[5280,13901,13798],{"class":5758},[5280,13903,7315],{"class":5452},[5280,13905,13906],{"class":5293},"getLimiter",[5280,13908,5835],{"class":5452},[5280,13910,13911],{"class":5424},"ip",[5280,13913,6239],{"class":5758},[5280,13915,13795],{"class":5452},[5280,13917,13727],{"class":5758},[5280,13919,5218],{"class":5452},[5280,13921,13837],{"class":5758},[5280,13923,6111],{"class":5452},[5280,13925,13926,13929,13931,13934,13936,13939],{"class":5282,"line":6159},[5280,13927,13928],{"class":5424},"    rl",[5280,13930,5218],{"class":5452},[5280,13932,13933],{"class":5424},"mu",[5280,13935,5218],{"class":5452},[5280,13937,13938],{"class":5293},"Lock",[5280,13940,6039],{"class":5452},[5280,13942,13943,13945,13948,13950,13952,13954,13957],{"class":5282,"line":6187},[5280,13944,7217],{"class":5436},[5280,13946,13947],{"class":5424}," rl",[5280,13949,5218],{"class":5452},[5280,13951,13933],{"class":5424},[5280,13953,5218],{"class":5452},[5280,13955,13956],{"class":5293},"Unlock",[5280,13958,6039],{"class":5452},[5280,13960,13961],{"class":5282,"line":6193},[5280,13962,6724],{"class":5452},[5280,13964,13965,13968,13970,13973,13975,13978,13980,13983,13985,13987],{"class":5282,"line":6207},[5280,13966,13967],{"class":5424},"    limiter",[5280,13969,5887],{"class":5452},[5280,13971,13972],{"class":5424},"exists",[5280,13974,6029],{"class":5452},[5280,13976,13977],{"class":5424},"rl",[5280,13979,5218],{"class":5452},[5280,13981,13982],{"class":5424},"limiters",[5280,13984,5841],{"class":5452},[5280,13986,13911],{"class":5424},[5280,13988,13989],{"class":5452},"]\n",[5280,13991,13992,13994,13996,13998],{"class":5282,"line":6221},[5280,13993,7023],{"class":5436},[5280,13995,9455],{"class":5452},[5280,13997,13972],{"class":5424},[5280,13999,6111],{"class":5452},[5280,14001,14002,14005,14007,14009,14011,14014,14016,14018,14020,14022,14024,14026,14028,14030],{"class":5282,"line":6233},[5280,14003,14004],{"class":5424},"        limiter",[5280,14006,5829],{"class":5452},[5280,14008,13727],{"class":5424},[5280,14010,5218],{"class":5452},[5280,14012,14013],{"class":5293},"NewLimiter",[5280,14015,5835],{"class":5452},[5280,14017,13977],{"class":5424},[5280,14019,5218],{"class":5452},[5280,14021,13727],{"class":5424},[5280,14023,5887],{"class":5452},[5280,14025,13977],{"class":5424},[5280,14027,5218],{"class":5452},[5280,14029,13790],{"class":5424},[5280,14031,5598],{"class":5452},[5280,14033,14034,14037,14039,14041,14043,14045,14047],{"class":5282,"line":6245},[5280,14035,14036],{"class":5424},"        rl",[5280,14038,5218],{"class":5452},[5280,14040,13982],{"class":5424},[5280,14042,5841],{"class":5452},[5280,14044,13911],{"class":5424},[5280,14046,6440],{"class":5452},[5280,14048,14049],{"class":5424},"limiter\n",[5280,14051,14052],{"class":5282,"line":6251},[5280,14053,6553],{"class":5452},[5280,14055,14056],{"class":5282,"line":6256},[5280,14057,6724],{"class":5452},[5280,14059,14060,14062],{"class":5282,"line":6308},[5280,14061,7328],{"class":5436},[5280,14063,14064],{"class":5424}," limiter\n",[5280,14066,14067],{"class":5282,"line":6339},[5280,14068,5976],{"class":5452},[5280,14070,14071],{"class":5282,"line":6371},[5280,14072,5313],{"emptyLinePlaceholder":5312},[5280,14074,14075],{"class":5282,"line":6409},[5280,14076,14077],{"class":5286},"\u002F\u002F Middleware повертає HTTP Middleware\n",[5280,14079,14080,14082,14084,14086,14088,14090,14092,14094,14096,14098,14100,14102,14104,14106,14108,14110,14112],{"class":5282,"line":6415},[5280,14081,5862],{"class":5619},[5280,14083,5720],{"class":5452},[5280,14085,13897],{"class":5424},[5280,14087,8072],{"class":5452},[5280,14089,13798],{"class":5758},[5280,14091,7315],{"class":5452},[5280,14093,7633],{"class":5293},[5280,14095,5835],{"class":5452},[5280,14097,7305],{"class":5424},[5280,14099,5997],{"class":5758},[5280,14101,5218],{"class":5452},[5280,14103,7935],{"class":5758},[5280,14105,7315],{"class":5452},[5280,14107,5952],{"class":5758},[5280,14109,5218],{"class":5452},[5280,14111,7935],{"class":5758},[5280,14113,6111],{"class":5452},[5280,14115,14116,14118,14120,14122,14124,14126,14128,14130,14132,14134,14136,14138,14140,14142,14144,14146,14148,14150],{"class":5282,"line":6420},[5280,14117,7328],{"class":5436},[5280,14119,5997],{"class":5424},[5280,14121,5218],{"class":5452},[5280,14123,7312],{"class":5293},[5280,14125,5835],{"class":5452},[5280,14127,5862],{"class":5619},[5280,14129,5835],{"class":5452},[5280,14131,5994],{"class":5424},[5280,14133,5997],{"class":5758},[5280,14135,5218],{"class":5452},[5280,14137,6002],{"class":5758},[5280,14139,5887],{"class":5452},[5280,14141,6007],{"class":5424},[5280,14143,6010],{"class":5452},[5280,14145,5952],{"class":5758},[5280,14147,5218],{"class":5452},[5280,14149,6017],{"class":5758},[5280,14151,6020],{"class":5452},[5280,14153,14154],{"class":5282,"line":6425},[5280,14155,14156],{"class":5286},"        \u002F\u002F Отримуємо IP клієнта\n",[5280,14158,14159,14162,14164,14166,14168],{"class":5282,"line":6450},[5280,14160,14161],{"class":5424},"        ip",[5280,14163,6029],{"class":5452},[5280,14165,6007],{"class":5424},[5280,14167,5218],{"class":5452},[5280,14169,14170],{"class":5424},"RemoteAddr\n",[5280,14172,14173],{"class":5282,"line":6471},[5280,14174,6190],{"class":5452},[5280,14176,14177],{"class":5282,"line":6507},[5280,14178,14179],{"class":5286},"        \u002F\u002F Перевіряємо ліміт\n",[5280,14181,14182,14184,14186,14188,14190,14192,14194,14196],{"class":5282,"line":6512},[5280,14183,14004],{"class":5424},[5280,14185,6029],{"class":5452},[5280,14187,13977],{"class":5424},[5280,14189,5218],{"class":5452},[5280,14191,13906],{"class":5293},[5280,14193,5835],{"class":5452},[5280,14195,13911],{"class":5424},[5280,14197,5598],{"class":5452},[5280,14199,14200],{"class":5282,"line":6520},[5280,14201,6190],{"class":5452},[5280,14203,14204,14206,14208,14211,14213,14216],{"class":5282,"line":6550},[5280,14205,6259],{"class":5436},[5280,14207,9455],{"class":5452},[5280,14209,14210],{"class":5424},"limiter",[5280,14212,5218],{"class":5452},[5280,14214,14215],{"class":5293},"Allow",[5280,14217,5868],{"class":5452},[5280,14219,14220],{"class":5282,"line":6556},[5280,14221,14222],{"class":5286},"            \u002F\u002F Ліміт перевищено\n",[5280,14224,14225,14228,14230,14232,14234,14236,14238,14241],{"class":5282,"line":6561},[5280,14226,14227],{"class":5424},"            w",[5280,14229,5218],{"class":5452},[5280,14231,6138],{"class":5293},[5280,14233,6141],{"class":5452},[5280,14235,6144],{"class":5293},[5280,14237,5835],{"class":5452},[5280,14239,14240],{"class":5297},"\"X-RateLimit-Retry-After\"",[5280,14242,6368],{"class":5452},[5280,14244,14245,14248,14250,14253,14255,14258],{"class":5282,"line":6621},[5280,14246,14247],{"class":5424},"                time",[5280,14249,5218],{"class":5452},[5280,14251,14252],{"class":5424},"Second",[5280,14254,5218],{"class":5452},[5280,14256,14257],{"class":5293},"String",[5280,14259,10669],{"class":5452},[5280,14261,14262,14264,14266,14268,14270,14272],{"class":5282,"line":6626},[5280,14263,6311],{"class":5424},[5280,14265,5218],{"class":5452},[5280,14267,6316],{"class":5293},[5280,14269,5835],{"class":5452},[5280,14271,5994],{"class":5424},[5280,14273,6368],{"class":5452},[5280,14275,14276,14279],{"class":5282,"line":6631},[5280,14277,14278],{"class":5297},"                \"Too Many Requests - Rate limit exceeded\"",[5280,14280,6368],{"class":5452},[5280,14282,14283,14285,14287,14290],{"class":5282,"line":6663},[5280,14284,10684],{"class":5424},[5280,14286,5218],{"class":5452},[5280,14288,14289],{"class":5424},"StatusTooManyRequests",[5280,14291,5598],{"class":5452},[5280,14293,14294],{"class":5282,"line":6678},[5280,14295,6412],{"class":5436},[5280,14297,14298],{"class":5282,"line":6721},[5280,14299,6248],{"class":5452},[5280,14301,14302],{"class":5282,"line":6727},[5280,14303,6190],{"class":5452},[5280,14305,14306],{"class":5282,"line":6751},[5280,14307,14308],{"class":5286},"        \u002F\u002F Ліміт не перевищено — передаємо далі\n",[5280,14310,14311,14313,14315,14317,14319,14321,14323,14325],{"class":5282,"line":6784},[5280,14312,7426],{"class":5424},[5280,14314,5218],{"class":5452},[5280,14316,8033],{"class":5293},[5280,14318,5835],{"class":5452},[5280,14320,5994],{"class":5424},[5280,14322,5887],{"class":5452},[5280,14324,6007],{"class":5424},[5280,14326,5598],{"class":5452},[5280,14328,14329],{"class":5282,"line":6799},[5280,14330,6834],{"class":5452},[5280,14332,14333],{"class":5282,"line":6831},[5280,14334,5976],{"class":5452},[5266,14336,14338],{"id":14337},"використання-rate-limiter","Використання Rate Limiter",[5271,14340,14342],{"className":5411,"code":14341,"language":5325,"meta":5276,"style":5276},"\u002F\u002F cmd\u002Fratelimit-server\u002Fmain.go\npackage main\n\nimport (\n    \"encoding\u002Fjson\"\n    \"fmt\"\n    \"log\"\n    \"net\u002Fhttp\"\n    \"myapp\u002Fmiddleware\"\n)\n\nfunc dataHandler(w http.ResponseWriter, r *http.Request) {\n    w.Header().Set(\"Content-Type\", \"application\u002Fjson\")\n    json.NewEncoder(w).Encode(map[string]string{\n        \"data\": \"This endpoint is rate-limited to 2 req\u002Fsec\",\n    })\n}\n\nfunc main() {\n    \u002F\u002F Дозволяємо 2 запити на секунду, сплеск до 5\n    rateLimiter := middleware.NewRateLimiter(2, 5)\n    \n    handler := middleware.Chain(\n        http.HandlerFunc(dataHandler),\n        middleware.Logger,\n        middleware.Recovery,\n        rateLimiter.Middleware,\n    )\n    \n    http.Handle(\"\u002Fapi\u002Fdata\", handler)\n    \n    fmt.Println(\"Rate-limited server (2 req\u002Fs) on :8080...\")\n    log.Fatal(http.ListenAndServe(\":8080\", nil))\n}\n",[5215,14343,14344,14349,14355,14359,14365,14369,14373,14377,14381,14385,14389,14393,14424,14446,14476,14488,14492,14496,14500,14508,14513,14538,14542,14556,14571,14581,14591,14602,14606,14610,14628,14632,14647,14673],{"__ignoreMap":5276},[5280,14345,14346],{"class":5282,"line":5283},[5280,14347,14348],{"class":5286},"\u002F\u002F cmd\u002Fratelimit-server\u002Fmain.go\n",[5280,14350,14351,14353],{"class":5282,"line":5290},[5280,14352,5755],{"class":5619},[5280,14354,5759],{"class":5758},[5280,14356,14357],{"class":5282,"line":5301},[5280,14358,5313],{"emptyLinePlaceholder":5312},[5280,14360,14361,14363],{"class":5282,"line":5309},[5280,14362,5768],{"class":5436},[5280,14364,5453],{"class":5452},[5280,14366,14367],{"class":5282,"line":5316},[5280,14368,5775],{"class":5297},[5280,14370,14371],{"class":5282,"line":5322},[5280,14372,5780],{"class":5297},[5280,14374,14375],{"class":5282,"line":5336},[5280,14376,5785],{"class":5297},[5280,14378,14379],{"class":5282,"line":5341},[5280,14380,5790],{"class":5297},[5280,14382,14383],{"class":5282,"line":5347},[5280,14384,10075],{"class":5297},[5280,14386,14387],{"class":5282,"line":5358},[5280,14388,5598],{"class":5452},[5280,14390,14391],{"class":5282,"line":5368},[5280,14392,5313],{"emptyLinePlaceholder":5312},[5280,14394,14395,14397,14400,14402,14404,14406,14408,14410,14412,14414,14416,14418,14420,14422],{"class":5282,"line":5378},[5280,14396,5862],{"class":5619},[5280,14398,14399],{"class":5293}," dataHandler",[5280,14401,5835],{"class":5452},[5280,14403,5994],{"class":5424},[5280,14405,5997],{"class":5758},[5280,14407,5218],{"class":5452},[5280,14409,6002],{"class":5758},[5280,14411,5887],{"class":5452},[5280,14413,6007],{"class":5424},[5280,14415,6010],{"class":5452},[5280,14417,5952],{"class":5758},[5280,14419,5218],{"class":5452},[5280,14421,6017],{"class":5758},[5280,14423,6020],{"class":5452},[5280,14425,14426,14428,14430,14432,14434,14436,14438,14440,14442,14444],{"class":5282,"line":5391},[5280,14427,6730],{"class":5424},[5280,14429,5218],{"class":5452},[5280,14431,6138],{"class":5293},[5280,14433,6141],{"class":5452},[5280,14435,6144],{"class":5293},[5280,14437,5835],{"class":5452},[5280,14439,6149],{"class":5297},[5280,14441,5887],{"class":5452},[5280,14443,6154],{"class":5297},[5280,14445,5598],{"class":5452},[5280,14447,14448,14450,14452,14454,14456,14458,14460,14462,14464,14466,14468,14470,14472,14474],{"class":5282,"line":5705},[5280,14449,6754],{"class":5424},[5280,14451,5218],{"class":5452},[5280,14453,6167],{"class":5293},[5280,14455,5835],{"class":5452},[5280,14457,5994],{"class":5424},[5280,14459,6174],{"class":5452},[5280,14461,6177],{"class":5293},[5280,14463,5835],{"class":5452},[5280,14465,5838],{"class":5619},[5280,14467,5841],{"class":5452},[5280,14469,5844],{"class":5758},[5280,14471,5847],{"class":5452},[5280,14473,5844],{"class":5758},[5280,14475,6781],{"class":5452},[5280,14477,14478,14481,14483,14486],{"class":5282,"line":5820},[5280,14479,14480],{"class":5297},"        \"data\"",[5280,14482,6790],{"class":5452},[5280,14484,14485],{"class":5297},"\"This endpoint is rate-limited to 2 req\u002Fsec\"",[5280,14487,6796],{"class":5452},[5280,14489,14490],{"class":5282,"line":5854},[5280,14491,6834],{"class":5452},[5280,14493,14494],{"class":5282,"line":5859},[5280,14495,5976],{"class":5452},[5280,14497,14498],{"class":5282,"line":5871},[5280,14499,5313],{"emptyLinePlaceholder":5312},[5280,14501,14502,14504,14506],{"class":5282,"line":5895},[5280,14503,5862],{"class":5619},[5280,14505,5865],{"class":5293},[5280,14507,5868],{"class":5452},[5280,14509,14510],{"class":5282,"line":5916},[5280,14511,14512],{"class":5286},"    \u002F\u002F Дозволяємо 2 запити на секунду, сплеск до 5\n",[5280,14514,14515,14518,14520,14522,14524,14527,14529,14531,14533,14536],{"class":5282,"line":5921},[5280,14516,14517],{"class":5424},"    rateLimiter",[5280,14519,6029],{"class":5452},[5280,14521,8768],{"class":5424},[5280,14523,5218],{"class":5452},[5280,14525,14526],{"class":5293},"NewRateLimiter",[5280,14528,5835],{"class":5452},[5280,14530,13457],{"class":5439},[5280,14532,5887],{"class":5452},[5280,14534,14535],{"class":5439},"5",[5280,14537,5598],{"class":5452},[5280,14539,14540],{"class":5282,"line":5939},[5280,14541,6724],{"class":5452},[5280,14543,14544,14546,14548,14550,14552,14554],{"class":5282,"line":5973},[5280,14545,13047],{"class":5424},[5280,14547,6029],{"class":5452},[5280,14549,8768],{"class":5424},[5280,14551,5218],{"class":5452},[5280,14553,11590],{"class":5293},[5280,14555,9105],{"class":5452},[5280,14557,14558,14560,14562,14564,14566,14569],{"class":5282,"line":5979},[5280,14559,6523],{"class":5424},[5280,14561,5218],{"class":5452},[5280,14563,7312],{"class":5293},[5280,14565,5835],{"class":5452},[5280,14567,14568],{"class":5424},"dataHandler",[5280,14570,6828],{"class":5452},[5280,14572,14573,14575,14577,14579],{"class":5282,"line":5984},[5280,14574,10992],{"class":5424},[5280,14576,5218],{"class":5452},[5280,14578,10291],{"class":5424},[5280,14580,6796],{"class":5452},[5280,14582,14583,14585,14587,14589],{"class":5282,"line":6023},[5280,14584,10992],{"class":5424},[5280,14586,5218],{"class":5452},[5280,14588,11008],{"class":5424},[5280,14590,6796],{"class":5452},[5280,14592,14593,14596,14598,14600],{"class":5282,"line":6042},[5280,14594,14595],{"class":5424},"        rateLimiter",[5280,14597,5218],{"class":5452},[5280,14599,7633],{"class":5424},[5280,14601,6796],{"class":5452},[5280,14603,14604],{"class":5282,"line":6093},[5280,14605,13112],{"class":5452},[5280,14607,14608],{"class":5282,"line":6098},[5280,14609,6724],{"class":5452},[5280,14611,14612,14614,14616,14618,14620,14622,14624,14626],{"class":5282,"line":6114},[5280,14613,5874],{"class":5424},[5280,14615,5218],{"class":5452},[5280,14617,8145],{"class":5293},[5280,14619,5835],{"class":5452},[5280,14621,13129],{"class":5297},[5280,14623,5887],{"class":5452},[5280,14625,5233],{"class":5424},[5280,14627,5598],{"class":5452},[5280,14629,14630],{"class":5282,"line":6130},[5280,14631,6724],{"class":5452},[5280,14633,14634,14636,14638,14640,14642,14645],{"class":5282,"line":6159},[5280,14635,5924],{"class":5424},[5280,14637,5218],{"class":5452},[5280,14639,5929],{"class":5293},[5280,14641,5835],{"class":5452},[5280,14643,14644],{"class":5297},"\"Rate-limited server (2 req\u002Fs) on :8080...\"",[5280,14646,5598],{"class":5452},[5280,14648,14649,14651,14653,14655,14657,14659,14661,14663,14665,14667,14669,14671],{"class":5282,"line":6187},[5280,14650,5942],{"class":5424},[5280,14652,5218],{"class":5452},[5280,14654,5947],{"class":5293},[5280,14656,5835],{"class":5452},[5280,14658,5952],{"class":5424},[5280,14660,5218],{"class":5452},[5280,14662,5957],{"class":5293},[5280,14664,5835],{"class":5452},[5280,14666,5962],{"class":5297},[5280,14668,5887],{"class":5452},[5280,14670,5967],{"class":5619},[5280,14672,5970],{"class":5452},[5280,14674,14675],{"class":5282,"line":6193},[5280,14676,5976],{"class":5452},[5262,14678,14679],{},[5227,14680,14681],{},"Тестування:",[8866,14683,14685,14695,14699,14702,14706,14709],{":cursor":8868,"title":14684},"Перевірка rate limit",[8871,14686,14688,7648,14691],{"className":14687},[5282],[5280,14689,8878],{"className":14690},[8877],[5227,14692,14694],{"className":14693},[8882],"for i in {1..10}; do curl -s http:\u002F\u002Flocalhost:8080\u002Fapi\u002Fdata; echo; sleep 0.3; done",[8871,14696,14698],{"className":14697},[5282],"{\"data\":\"This endpoint is rate-limited to 2 req\u002Fsec\"}",[8871,14700,14698],{"className":14701},[5282],[8871,14703,14705],{"className":14704},[5282],"Too Many Requests - Rate limit exceeded",[8871,14707,14705],{"className":14708},[5282],[8871,14710,14698],{"className":14711},[5282],[5712,14713,14714,14717,14718,14720,14721,14723],{},[5227,14715,14716],{},"Token Bucket Algorithm:"," уявіть відро з токенами. Кожен запит \"забирає\" токен. Відро поповнюється зі швидкістю ",[5215,14719,13727],{}," токенів на секунду, але не може вмістити більше ",[5215,14722,13790],{}," токенів. Це дозволяє короткочасні сплески запитів при збереженні середньої швидкості.",[5254,14725],{},[5257,14727,14729],{"id":14728},"authentication-middleware-передача-даних-через-контекст","Authentication Middleware: передача даних через контекст",[5266,14731,14733],{"id":14732},"проблема-передачі-даних-між-middleware","Проблема передачі даних між Middleware",[5262,14735,14736,14737,14740],{},"Припустимо, Middleware автентифікації перевіряє JWT токен та отримує ",[5215,14738,14739],{},"user_id",". Як передати цю інформацію до обробника?",[5262,14742,14743],{},[5227,14744,14745],{},"Погані варіанти:",[5194,14747,14748,14751,14754],{},[5197,14749,14750],{},"❌ Глобальні змінні (не потокобезпечні, плутанина при конкурентних запитах)",[5197,14752,14753],{},"❌ Заголовки відповіді (це для відповіді клієнту, не для внутрішньої передачі даних)",[5197,14755,14756],{},"❌ Модифікація URL (неестетично і порушує REST-принципи)",[5262,14758,14759,7648,14762,5218],{},[5227,14760,14761],{},"Правильне рішення:",[5215,14763,14764],{},"context.Context",[5266,14766,14768],{"id":14767},"що-таке-contextcontext","Що таке context.Context?",[5262,14770,14771,14773],{},[5215,14772,14764],{}," — це стандартний механізм Go для передачі:",[5194,14775,14776,14782,14788],{},[5197,14777,14778,14781],{},[5227,14779,14780],{},"Метаданих"," між функціями (user ID, request ID, trace ID)",[5197,14783,14784,14787],{},[5227,14785,14786],{},"Сигналів скасування"," операцій",[5197,14789,14790,14793],{},[5227,14791,14792],{},"Таймаутів"," виконання",[5262,14795,14796,14797,5218],{},"Кожен HTTP-запит має власний контекст: ",[5215,14798,14799],{},"r.Context()",[5712,14801,14802,14803,5720,14806,14809,14810,5887,14813,14816,14817,14820],{},"Контекст є ",[5227,14804,14805],{},"іммутабельним",[5207,14807,14808],{},"immutable","). Методи ",[5215,14811,14812],{},"context.WithValue()",[5215,14814,14815],{},"context.WithTimeout()"," тощо повертають ",[5227,14818,14819],{},"новий"," контекст, не змінюючи оригінал. Це забезпечує потокобезпечність.",[5266,14822,14824],{"id":14823},"створення-власних-ключів-контексту","Створення власних ключів контексту",[5262,14826,14827,14828,14831],{},"Ключі контексту мають бути ",[5227,14829,14830],{},"унікальними типами",", щоб уникнути конфліктів:",[5271,14833,14835],{"className":5411,"code":14834,"language":5325,"meta":5276,"style":5276},"\u002F\u002F middleware\u002Fauth.go\npackage middleware\n\nimport (\n    \"context\"\n    \"net\u002Fhttp\"\n    \"strings\"\n)\n\n\u002F\u002F contextKey — приватний тип для ключів контексту\ntype contextKey string\n\nconst (\n    \u002F\u002F UserIDKey — ключ для збереження user_id у контексті\n    UserIDKey contextKey = \"user_id\"\n)\n",[5215,14836,14837,14842,14848,14852,14858,14863,14867,14871,14875,14879,14884,14894,14898,14905,14910,14922],{"__ignoreMap":5276},[5280,14838,14839],{"class":5282,"line":5283},[5280,14840,14841],{"class":5286},"\u002F\u002F middleware\u002Fauth.go\n",[5280,14843,14844,14846],{"class":5282,"line":5290},[5280,14845,5755],{"class":5619},[5280,14847,5641],{"class":5758},[5280,14849,14850],{"class":5282,"line":5301},[5280,14851,5313],{"emptyLinePlaceholder":5312},[5280,14853,14854,14856],{"class":5282,"line":5309},[5280,14855,5768],{"class":5436},[5280,14857,5453],{"class":5452},[5280,14859,14860],{"class":5282,"line":5316},[5280,14861,14862],{"class":5297},"    \"context\"\n",[5280,14864,14865],{"class":5282,"line":5322},[5280,14866,5790],{"class":5297},[5280,14868,14869],{"class":5282,"line":5336},[5280,14870,11855],{"class":5297},[5280,14872,14873],{"class":5282,"line":5341},[5280,14874,5598],{"class":5452},[5280,14876,14877],{"class":5282,"line":5347},[5280,14878,5313],{"emptyLinePlaceholder":5312},[5280,14880,14881],{"class":5282,"line":5358},[5280,14882,14883],{"class":5286},"\u002F\u002F contextKey — приватний тип для ключів контексту\n",[5280,14885,14886,14888,14891],{"class":5282,"line":5368},[5280,14887,7921],{"class":5619},[5280,14889,14890],{"class":5758}," contextKey",[5280,14892,14893],{"class":5758}," string\n",[5280,14895,14896],{"class":5282,"line":5378},[5280,14897,5313],{"emptyLinePlaceholder":5312},[5280,14899,14900,14903],{"class":5282,"line":5391},[5280,14901,14902],{"class":5619},"const",[5280,14904,5453],{"class":5452},[5280,14906,14907],{"class":5282,"line":5705},[5280,14908,14909],{"class":5286},"    \u002F\u002F UserIDKey — ключ для збереження user_id у контексті\n",[5280,14911,14912,14915,14917,14919],{"class":5282,"line":5820},[5280,14913,14914],{"class":13369},"    UserIDKey",[5280,14916,14890],{"class":5758},[5280,14918,5829],{"class":5452},[5280,14920,14921],{"class":5297},"\"user_id\"\n",[5280,14923,14924],{"class":5282,"line":5854},[5280,14925,5598],{"class":5452},[8342,14927,14928,14929,14932,14933,14935],{},"Використання власного типу ",[5215,14930,14931],{},"contextKey"," замість звичайного ",[5215,14934,5844],{}," запобігає випадковим колізіям з ключами з інших пакетів. Це рекомендована практика у Go.",[5266,14937,14939],{"id":14938},"authentication-middleware","Authentication Middleware",[5262,14941,14942],{},"Створимо Middleware, що перевіряє JWT токен (спрощена версія):",[5271,14944,14946],{"className":5411,"code":14945,"language":5325,"meta":5276,"style":5276},"\u002F\u002F middleware\u002Fauth.go (продовження)\npackage middleware\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"net\u002Fhttp\"\n    \"strings\"\n)\n\n\u002F\u002F Auth — Middleware для перевірки Bearer токена\nfunc Auth(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        \u002F\u002F Читаємо заголовок Authorization\n        authHeader := r.Header.Get(\"Authorization\")\n        if authHeader == \"\" {\n            http.Error(w, \n                \"Missing Authorization header\", \n                http.StatusUnauthorized)\n            return\n        }\n        \n        \u002F\u002F Очікуємо формат: \"Bearer \u003Ctoken>\"\n        parts := strings.Split(authHeader, \" \")\n        if len(parts) != 2 || parts[0] != \"Bearer\" {\n            http.Error(w, \n                \"Invalid Authorization header format. Expected: Bearer \u003Ctoken>\", \n                http.StatusUnauthorized)\n            return\n        }\n        \n        token := parts[1]\n        \n        \u002F\u002F Валідація токена (тут спрощено)\n        userID, err := validateJWT(token)\n        if err != nil {\n            http.Error(w, \n                \"Invalid or expired token: \" + err.Error(), \n                http.StatusUnauthorized)\n            return\n        }\n        \n        \u002F\u002F Додаємо user_id у контекст запиту\n        ctx := context.WithValue(r.Context(), UserIDKey, userID)\n        \n        \u002F\u002F Передаємо оновлений запит з новим контекстом\n        next.ServeHTTP(w, r.WithContext(ctx))\n    })\n}\n\n\u002F\u002F validateJWT — спрощена валідація токена\n\u002F\u002F У реальному проєкті тут має бути перевірка підпису JWT\nfunc validateJWT(token string) (string, error) {\n    \u002F\u002F Для демонстрації: просто повертаємо токен як user_id\n    if token == \"\" {\n        return \"\", fmt.Errorf(\"empty token\")\n    }\n    \n    \u002F\u002F У реальності тут буде:\n    \u002F\u002F 1. Парсинг JWT\n    \u002F\u002F 2. Перевірка підпису\n    \u002F\u002F 3. Перевірка термінів дії (exp)\n    \u002F\u002F 4. Витягування claims (user_id, roles, etc.)\n    \n    return \"user-123\", nil \u002F\u002F Заглушка\n}\n",[5215,14947,14948,14953,14959,14963,14969,14973,14977,14981,14985,14989,14993,14998,15025,15063,15068,15091,15104,15118,15125,15136,15140,15144,15148,15153,15180,15212,15226,15233,15243,15247,15251,15255,15270,15274,15279,15300,15312,15326,15343,15353,15357,15361,15365,15370,15407,15411,15416,15444,15448,15452,15456,15461,15466,15489,15494,15507,15531,15535,15539,15544,15549,15554,15559,15564,15568,15582],{"__ignoreMap":5276},[5280,14949,14950],{"class":5282,"line":5283},[5280,14951,14952],{"class":5286},"\u002F\u002F middleware\u002Fauth.go (продовження)\n",[5280,14954,14955,14957],{"class":5282,"line":5290},[5280,14956,5755],{"class":5619},[5280,14958,5641],{"class":5758},[5280,14960,14961],{"class":5282,"line":5301},[5280,14962,5313],{"emptyLinePlaceholder":5312},[5280,14964,14965,14967],{"class":5282,"line":5309},[5280,14966,5768],{"class":5436},[5280,14968,5453],{"class":5452},[5280,14970,14971],{"class":5282,"line":5316},[5280,14972,14862],{"class":5297},[5280,14974,14975],{"class":5282,"line":5322},[5280,14976,5780],{"class":5297},[5280,14978,14979],{"class":5282,"line":5336},[5280,14980,5790],{"class":5297},[5280,14982,14983],{"class":5282,"line":5341},[5280,14984,11855],{"class":5297},[5280,14986,14987],{"class":5282,"line":5347},[5280,14988,5598],{"class":5452},[5280,14990,14991],{"class":5282,"line":5358},[5280,14992,5313],{"emptyLinePlaceholder":5312},[5280,14994,14995],{"class":5282,"line":5368},[5280,14996,14997],{"class":5286},"\u002F\u002F Auth — Middleware для перевірки Bearer токена\n",[5280,14999,15000,15002,15005,15007,15009,15011,15013,15015,15017,15019,15021,15023],{"class":5282,"line":5378},[5280,15001,5862],{"class":5619},[5280,15003,15004],{"class":5293}," Auth",[5280,15006,5835],{"class":5452},[5280,15008,7305],{"class":5424},[5280,15010,5997],{"class":5758},[5280,15012,5218],{"class":5452},[5280,15014,7935],{"class":5758},[5280,15016,7315],{"class":5452},[5280,15018,5952],{"class":5758},[5280,15020,5218],{"class":5452},[5280,15022,7935],{"class":5758},[5280,15024,6111],{"class":5452},[5280,15026,15027,15029,15031,15033,15035,15037,15039,15041,15043,15045,15047,15049,15051,15053,15055,15057,15059,15061],{"class":5282,"line":5391},[5280,15028,7328],{"class":5436},[5280,15030,5997],{"class":5424},[5280,15032,5218],{"class":5452},[5280,15034,7312],{"class":5293},[5280,15036,5835],{"class":5452},[5280,15038,5862],{"class":5619},[5280,15040,5835],{"class":5452},[5280,15042,5994],{"class":5424},[5280,15044,5997],{"class":5758},[5280,15046,5218],{"class":5452},[5280,15048,6002],{"class":5758},[5280,15050,5887],{"class":5452},[5280,15052,6007],{"class":5424},[5280,15054,6010],{"class":5452},[5280,15056,5952],{"class":5758},[5280,15058,5218],{"class":5452},[5280,15060,6017],{"class":5758},[5280,15062,6020],{"class":5452},[5280,15064,15065],{"class":5282,"line":5705},[5280,15066,15067],{"class":5286},"        \u002F\u002F Читаємо заголовок Authorization\n",[5280,15069,15070,15073,15075,15077,15079,15081,15083,15085,15087,15089],{"class":5282,"line":5820},[5280,15071,15072],{"class":5424},"        authHeader",[5280,15074,6029],{"class":5452},[5280,15076,6007],{"class":5424},[5280,15078,5218],{"class":5452},[5280,15080,6138],{"class":5424},[5280,15082,5218],{"class":5452},[5280,15084,12252],{"class":5293},[5280,15086,5835],{"class":5452},[5280,15088,12075],{"class":5297},[5280,15090,5598],{"class":5452},[5280,15092,15093,15095,15098,15100,15102],{"class":5282,"line":5854},[5280,15094,6259],{"class":5436},[5280,15096,15097],{"class":5424}," authHeader",[5280,15099,7029],{"class":5452},[5280,15101,12375],{"class":5297},[5280,15103,6111],{"class":5452},[5280,15105,15106,15108,15110,15112,15114,15116],{"class":5282,"line":5859},[5280,15107,6311],{"class":5424},[5280,15109,5218],{"class":5452},[5280,15111,6316],{"class":5293},[5280,15113,5835],{"class":5452},[5280,15115,5994],{"class":5424},[5280,15117,6368],{"class":5452},[5280,15119,15120,15123],{"class":5282,"line":5871},[5280,15121,15122],{"class":5297},"                \"Missing Authorization header\"",[5280,15124,6368],{"class":5452},[5280,15126,15127,15129,15131,15134],{"class":5282,"line":5895},[5280,15128,10684],{"class":5424},[5280,15130,5218],{"class":5452},[5280,15132,15133],{"class":5424},"StatusUnauthorized",[5280,15135,5598],{"class":5452},[5280,15137,15138],{"class":5282,"line":5916},[5280,15139,6412],{"class":5436},[5280,15141,15142],{"class":5282,"line":5921},[5280,15143,6248],{"class":5452},[5280,15145,15146],{"class":5282,"line":5939},[5280,15147,6190],{"class":5452},[5280,15149,15150],{"class":5282,"line":5973},[5280,15151,15152],{"class":5286},"        \u002F\u002F Очікуємо формат: \"Bearer \u003Ctoken>\"\n",[5280,15154,15155,15158,15160,15163,15165,15168,15170,15173,15175,15178],{"class":5282,"line":5979},[5280,15156,15157],{"class":5424},"        parts",[5280,15159,6029],{"class":5452},[5280,15161,15162],{"class":5424},"strings",[5280,15164,5218],{"class":5452},[5280,15166,15167],{"class":5293},"Split",[5280,15169,5835],{"class":5452},[5280,15171,15172],{"class":5424},"authHeader",[5280,15174,5887],{"class":5452},[5280,15176,15177],{"class":5297},"\" \"",[5280,15179,5598],{"class":5452},[5280,15181,15182,15184,15186,15188,15191,15194,15196,15198,15200,15202,15204,15207,15210],{"class":5282,"line":5984},[5280,15183,6259],{"class":5436},[5280,15185,12551],{"class":5293},[5280,15187,5835],{"class":5452},[5280,15189,15190],{"class":5424},"parts",[5280,15192,15193],{"class":5452},") != ",[5280,15195,13457],{"class":5439},[5280,15197,12321],{"class":5452},[5280,15199,15190],{"class":5424},[5280,15201,5841],{"class":5452},[5280,15203,11413],{"class":5439},[5280,15205,15206],{"class":5452},"] != ",[5280,15208,15209],{"class":5297},"\"Bearer\"",[5280,15211,6111],{"class":5452},[5280,15213,15214,15216,15218,15220,15222,15224],{"class":5282,"line":6023},[5280,15215,6311],{"class":5424},[5280,15217,5218],{"class":5452},[5280,15219,6316],{"class":5293},[5280,15221,5835],{"class":5452},[5280,15223,5994],{"class":5424},[5280,15225,6368],{"class":5452},[5280,15227,15228,15231],{"class":5282,"line":6042},[5280,15229,15230],{"class":5297},"                \"Invalid Authorization header format. Expected: Bearer \u003Ctoken>\"",[5280,15232,6368],{"class":5452},[5280,15234,15235,15237,15239,15241],{"class":5282,"line":6093},[5280,15236,10684],{"class":5424},[5280,15238,5218],{"class":5452},[5280,15240,15133],{"class":5424},[5280,15242,5598],{"class":5452},[5280,15244,15245],{"class":5282,"line":6098},[5280,15246,6412],{"class":5436},[5280,15248,15249],{"class":5282,"line":6114},[5280,15250,6248],{"class":5452},[5280,15252,15253],{"class":5282,"line":6130},[5280,15254,6190],{"class":5452},[5280,15256,15257,15260,15262,15264,15266,15268],{"class":5282,"line":6159},[5280,15258,15259],{"class":5424},"        token",[5280,15261,6029],{"class":5452},[5280,15263,15190],{"class":5424},[5280,15265,5841],{"class":5452},[5280,15267,11401],{"class":5439},[5280,15269,13989],{"class":5452},[5280,15271,15272],{"class":5282,"line":6187},[5280,15273,6190],{"class":5452},[5280,15275,15276],{"class":5282,"line":6193},[5280,15277,15278],{"class":5286},"        \u002F\u002F Валідація токена (тут спрощено)\n",[5280,15280,15281,15284,15286,15288,15290,15293,15295,15298],{"class":5282,"line":6207},[5280,15282,15283],{"class":5424},"        userID",[5280,15285,5887],{"class":5452},[5280,15287,6298],{"class":5424},[5280,15289,6029],{"class":5452},[5280,15291,15292],{"class":5293},"validateJWT",[5280,15294,5835],{"class":5452},[5280,15296,15297],{"class":5424},"token",[5280,15299,5598],{"class":5452},[5280,15301,15302,15304,15306,15308,15310],{"class":5282,"line":6221},[5280,15303,6259],{"class":5436},[5280,15305,6262],{"class":5424},[5280,15307,6301],{"class":5452},[5280,15309,5967],{"class":5619},[5280,15311,6111],{"class":5452},[5280,15313,15314,15316,15318,15320,15322,15324],{"class":5282,"line":6233},[5280,15315,6311],{"class":5424},[5280,15317,5218],{"class":5452},[5280,15319,6316],{"class":5293},[5280,15321,5835],{"class":5452},[5280,15323,5994],{"class":5424},[5280,15325,6368],{"class":5452},[5280,15327,15328,15331,15334,15336,15338,15340],{"class":5282,"line":6245},[5280,15329,15330],{"class":5297},"                \"Invalid or expired token: \"",[5280,15332,15333],{"class":5452}," + ",[5280,15335,6298],{"class":5424},[5280,15337,5218],{"class":5452},[5280,15339,6316],{"class":5293},[5280,15341,15342],{"class":5452},"(), \n",[5280,15344,15345,15347,15349,15351],{"class":5282,"line":6251},[5280,15346,10684],{"class":5424},[5280,15348,5218],{"class":5452},[5280,15350,15133],{"class":5424},[5280,15352,5598],{"class":5452},[5280,15354,15355],{"class":5282,"line":6256},[5280,15356,6412],{"class":5436},[5280,15358,15359],{"class":5282,"line":6308},[5280,15360,6248],{"class":5452},[5280,15362,15363],{"class":5282,"line":6339},[5280,15364,6190],{"class":5452},[5280,15366,15367],{"class":5282,"line":6371},[5280,15368,15369],{"class":5286},"        \u002F\u002F Додаємо user_id у контекст запиту\n",[5280,15371,15372,15375,15377,15380,15382,15385,15387,15389,15391,15394,15397,15400,15402,15405],{"class":5282,"line":6409},[5280,15373,15374],{"class":5424},"        ctx",[5280,15376,6029],{"class":5452},[5280,15378,15379],{"class":5424},"context",[5280,15381,5218],{"class":5452},[5280,15383,15384],{"class":5293},"WithValue",[5280,15386,5835],{"class":5452},[5280,15388,6007],{"class":5424},[5280,15390,5218],{"class":5452},[5280,15392,15393],{"class":5293},"Context",[5280,15395,15396],{"class":5452},"(), ",[5280,15398,15399],{"class":5424},"UserIDKey",[5280,15401,5887],{"class":5452},[5280,15403,15404],{"class":5424},"userID",[5280,15406,5598],{"class":5452},[5280,15408,15409],{"class":5282,"line":6415},[5280,15410,6190],{"class":5452},[5280,15412,15413],{"class":5282,"line":6420},[5280,15414,15415],{"class":5286},"        \u002F\u002F Передаємо оновлений запит з новим контекстом\n",[5280,15417,15418,15420,15422,15424,15426,15428,15430,15432,15434,15437,15439,15442],{"class":5282,"line":6425},[5280,15419,7426],{"class":5424},[5280,15421,5218],{"class":5452},[5280,15423,8033],{"class":5293},[5280,15425,5835],{"class":5452},[5280,15427,5994],{"class":5424},[5280,15429,5887],{"class":5452},[5280,15431,6007],{"class":5424},[5280,15433,5218],{"class":5452},[5280,15435,15436],{"class":5293},"WithContext",[5280,15438,5835],{"class":5452},[5280,15440,15441],{"class":5424},"ctx",[5280,15443,5970],{"class":5452},[5280,15445,15446],{"class":5282,"line":6450},[5280,15447,6834],{"class":5452},[5280,15449,15450],{"class":5282,"line":6471},[5280,15451,5976],{"class":5452},[5280,15453,15454],{"class":5282,"line":6507},[5280,15455,5313],{"emptyLinePlaceholder":5312},[5280,15457,15458],{"class":5282,"line":6512},[5280,15459,15460],{"class":5286},"\u002F\u002F validateJWT — спрощена валідація токена\n",[5280,15462,15463],{"class":5282,"line":6520},[5280,15464,15465],{"class":5286},"\u002F\u002F У реальному проєкті тут має бути перевірка підпису JWT\n",[5280,15467,15468,15470,15473,15475,15477,15479,15481,15483,15485,15487],{"class":5282,"line":6550},[5280,15469,5862],{"class":5619},[5280,15471,15472],{"class":5293}," validateJWT",[5280,15474,5835],{"class":5452},[5280,15476,15297],{"class":5424},[5280,15478,6239],{"class":5758},[5280,15480,9559],{"class":5452},[5280,15482,5844],{"class":5758},[5280,15484,5887],{"class":5452},[5280,15486,9567],{"class":5758},[5280,15488,6020],{"class":5452},[5280,15490,15491],{"class":5282,"line":6556},[5280,15492,15493],{"class":5286},"    \u002F\u002F Для демонстрації: просто повертаємо токен як user_id\n",[5280,15495,15496,15498,15501,15503,15505],{"class":5282,"line":6561},[5280,15497,7023],{"class":5436},[5280,15499,15500],{"class":5424}," token",[5280,15502,7029],{"class":5452},[5280,15504,12375],{"class":5297},[5280,15506,6111],{"class":5452},[5280,15508,15509,15511,15514,15516,15519,15521,15524,15526,15529],{"class":5282,"line":6621},[5280,15510,12200],{"class":5436},[5280,15512,15513],{"class":5297}," \"\"",[5280,15515,5887],{"class":5452},[5280,15517,15518],{"class":5424},"fmt",[5280,15520,5218],{"class":5452},[5280,15522,15523],{"class":5293},"Errorf",[5280,15525,5835],{"class":5452},[5280,15527,15528],{"class":5297},"\"empty token\"",[5280,15530,5598],{"class":5452},[5280,15532,15533],{"class":5282,"line":6626},[5280,15534,6553],{"class":5452},[5280,15536,15537],{"class":5282,"line":6631},[5280,15538,6724],{"class":5452},[5280,15540,15541],{"class":5282,"line":6663},[5280,15542,15543],{"class":5286},"    \u002F\u002F У реальності тут буде:\n",[5280,15545,15546],{"class":5282,"line":6678},[5280,15547,15548],{"class":5286},"    \u002F\u002F 1. Парсинг JWT\n",[5280,15550,15551],{"class":5282,"line":6721},[5280,15552,15553],{"class":5286},"    \u002F\u002F 2. Перевірка підпису\n",[5280,15555,15556],{"class":5282,"line":6727},[5280,15557,15558],{"class":5286},"    \u002F\u002F 3. Перевірка термінів дії (exp)\n",[5280,15560,15561],{"class":5282,"line":6751},[5280,15562,15563],{"class":5286},"    \u002F\u002F 4. Витягування claims (user_id, roles, etc.)\n",[5280,15565,15566],{"class":5282,"line":6784},[5280,15567,6724],{"class":5452},[5280,15569,15570,15572,15575,15577,15579],{"class":5282,"line":6799},[5280,15571,7328],{"class":5436},[5280,15573,15574],{"class":5297}," \"user-123\"",[5280,15576,5887],{"class":5452},[5280,15578,5967],{"class":5619},[5280,15580,15581],{"class":5286}," \u002F\u002F Заглушка\n",[5280,15583,15584],{"class":5282,"line":6831},[5280,15585,5976],{"class":5452},[5266,15587,15589],{"id":15588},"отримання-даних-з-контексту-в-обробнику","Отримання даних з контексту в обробнику",[5271,15591,15593],{"className":5411,"code":15592,"language":5325,"meta":5276,"style":5276},"\u002F\u002F cmd\u002Fauth-server\u002Fmain.go\npackage main\n\nimport (\n    \"encoding\u002Fjson\"\n    \"fmt\"\n    \"log\"\n    \"net\u002Fhttp\"\n    \"myapp\u002Fmiddleware\"\n)\n\n\u002F\u002F Публічний ендпоінт (без автентифікації)\nfunc publicHandler(w http.ResponseWriter, r *http.Request) {\n    w.Header().Set(\"Content-Type\", \"application\u002Fjson\")\n    json.NewEncoder(w).Encode(map[string]string{\n        \"message\": \"This is a public endpoint\",\n    })\n}\n\n\u002F\u002F Захищений ендпоінт (вимагає автентифікації)\nfunc protectedHandler(w http.ResponseWriter, r *http.Request) {\n    \u002F\u002F Витягуємо user_id з контексту\n    userID, ok := r.Context().Value(middleware.UserIDKey).(string)\n    if !ok {\n        http.Error(w, \"User not authenticated\", http.StatusUnauthorized)\n        return\n    }\n    \n    w.Header().Set(\"Content-Type\", \"application\u002Fjson\")\n    json.NewEncoder(w).Encode(map[string]string{\n        \"message\": \"This is a protected endpoint\",\n        \"user_id\": userID,\n    })\n}\n\nfunc main() {\n    \u002F\u002F Публічний маршрут (без Auth Middleware)\n    http.Handle(\"\u002Fapi\u002Fpublic\", \n        middleware.Chain(\n            http.HandlerFunc(publicHandler),\n            middleware.Logger,\n            middleware.Recovery,\n        ))\n    \n    \u002F\u002F Захищений маршрут (з Auth Middleware)\n    http.Handle(\"\u002Fapi\u002Fprotected\", \n        middleware.Chain(\n            http.HandlerFunc(protectedHandler),\n            middleware.Logger,\n            middleware.Recovery,\n            middleware.Auth, \u002F\u002F Додаємо автентифікацію\n        ))\n    \n    fmt.Println(\"Server with authentication on :8080...\")\n    log.Fatal(http.ListenAndServe(\":8080\", nil))\n}\n",[5215,15594,15595,15600,15606,15610,15616,15620,15624,15628,15632,15636,15640,15644,15649,15680,15702,15732,15743,15747,15751,15755,15760,15791,15796,15834,15844,15871,15876,15880,15884,15906,15936,15947,15958,15962,15966,15970,15978,15983,15998,16008,16023,16033,16043,16048,16052,16057,16072,16082,16097,16107,16117,16130,16134,16138,16153,16179],{"__ignoreMap":5276},[5280,15596,15597],{"class":5282,"line":5283},[5280,15598,15599],{"class":5286},"\u002F\u002F cmd\u002Fauth-server\u002Fmain.go\n",[5280,15601,15602,15604],{"class":5282,"line":5290},[5280,15603,5755],{"class":5619},[5280,15605,5759],{"class":5758},[5280,15607,15608],{"class":5282,"line":5301},[5280,15609,5313],{"emptyLinePlaceholder":5312},[5280,15611,15612,15614],{"class":5282,"line":5309},[5280,15613,5768],{"class":5436},[5280,15615,5453],{"class":5452},[5280,15617,15618],{"class":5282,"line":5316},[5280,15619,5775],{"class":5297},[5280,15621,15622],{"class":5282,"line":5322},[5280,15623,5780],{"class":5297},[5280,15625,15626],{"class":5282,"line":5336},[5280,15627,5785],{"class":5297},[5280,15629,15630],{"class":5282,"line":5341},[5280,15631,5790],{"class":5297},[5280,15633,15634],{"class":5282,"line":5347},[5280,15635,10075],{"class":5297},[5280,15637,15638],{"class":5282,"line":5358},[5280,15639,5598],{"class":5452},[5280,15641,15642],{"class":5282,"line":5368},[5280,15643,5313],{"emptyLinePlaceholder":5312},[5280,15645,15646],{"class":5282,"line":5378},[5280,15647,15648],{"class":5286},"\u002F\u002F Публічний ендпоінт (без автентифікації)\n",[5280,15650,15651,15653,15656,15658,15660,15662,15664,15666,15668,15670,15672,15674,15676,15678],{"class":5282,"line":5391},[5280,15652,5862],{"class":5619},[5280,15654,15655],{"class":5293}," publicHandler",[5280,15657,5835],{"class":5452},[5280,15659,5994],{"class":5424},[5280,15661,5997],{"class":5758},[5280,15663,5218],{"class":5452},[5280,15665,6002],{"class":5758},[5280,15667,5887],{"class":5452},[5280,15669,6007],{"class":5424},[5280,15671,6010],{"class":5452},[5280,15673,5952],{"class":5758},[5280,15675,5218],{"class":5452},[5280,15677,6017],{"class":5758},[5280,15679,6020],{"class":5452},[5280,15681,15682,15684,15686,15688,15690,15692,15694,15696,15698,15700],{"class":5282,"line":5705},[5280,15683,6730],{"class":5424},[5280,15685,5218],{"class":5452},[5280,15687,6138],{"class":5293},[5280,15689,6141],{"class":5452},[5280,15691,6144],{"class":5293},[5280,15693,5835],{"class":5452},[5280,15695,6149],{"class":5297},[5280,15697,5887],{"class":5452},[5280,15699,6154],{"class":5297},[5280,15701,5598],{"class":5452},[5280,15703,15704,15706,15708,15710,15712,15714,15716,15718,15720,15722,15724,15726,15728,15730],{"class":5282,"line":5820},[5280,15705,6754],{"class":5424},[5280,15707,5218],{"class":5452},[5280,15709,6167],{"class":5293},[5280,15711,5835],{"class":5452},[5280,15713,5994],{"class":5424},[5280,15715,6174],{"class":5452},[5280,15717,6177],{"class":5293},[5280,15719,5835],{"class":5452},[5280,15721,5838],{"class":5619},[5280,15723,5841],{"class":5452},[5280,15725,5844],{"class":5758},[5280,15727,5847],{"class":5452},[5280,15729,5844],{"class":5758},[5280,15731,6781],{"class":5452},[5280,15733,15734,15736,15738,15741],{"class":5282,"line":5854},[5280,15735,12995],{"class":5297},[5280,15737,6790],{"class":5452},[5280,15739,15740],{"class":5297},"\"This is a public endpoint\"",[5280,15742,6796],{"class":5452},[5280,15744,15745],{"class":5282,"line":5859},[5280,15746,6834],{"class":5452},[5280,15748,15749],{"class":5282,"line":5871},[5280,15750,5976],{"class":5452},[5280,15752,15753],{"class":5282,"line":5895},[5280,15754,5313],{"emptyLinePlaceholder":5312},[5280,15756,15757],{"class":5282,"line":5916},[5280,15758,15759],{"class":5286},"\u002F\u002F Захищений ендпоінт (вимагає автентифікації)\n",[5280,15761,15762,15764,15767,15769,15771,15773,15775,15777,15779,15781,15783,15785,15787,15789],{"class":5282,"line":5921},[5280,15763,5862],{"class":5619},[5280,15765,15766],{"class":5293}," protectedHandler",[5280,15768,5835],{"class":5452},[5280,15770,5994],{"class":5424},[5280,15772,5997],{"class":5758},[5280,15774,5218],{"class":5452},[5280,15776,6002],{"class":5758},[5280,15778,5887],{"class":5452},[5280,15780,6007],{"class":5424},[5280,15782,6010],{"class":5452},[5280,15784,5952],{"class":5758},[5280,15786,5218],{"class":5452},[5280,15788,6017],{"class":5758},[5280,15790,6020],{"class":5452},[5280,15792,15793],{"class":5282,"line":5939},[5280,15794,15795],{"class":5286},"    \u002F\u002F Витягуємо user_id з контексту\n",[5280,15797,15798,15801,15803,15806,15808,15810,15812,15814,15816,15819,15821,15823,15825,15827,15830,15832],{"class":5282,"line":5973},[5280,15799,15800],{"class":5424},"    userID",[5280,15802,5887],{"class":5452},[5280,15804,15805],{"class":5424},"ok",[5280,15807,6029],{"class":5452},[5280,15809,6007],{"class":5424},[5280,15811,5218],{"class":5452},[5280,15813,15393],{"class":5293},[5280,15815,6141],{"class":5452},[5280,15817,15818],{"class":5293},"Value",[5280,15820,5835],{"class":5452},[5280,15822,8768],{"class":5424},[5280,15824,5218],{"class":5452},[5280,15826,15399],{"class":5424},[5280,15828,15829],{"class":5452},").(",[5280,15831,5844],{"class":5758},[5280,15833,5598],{"class":5452},[5280,15835,15836,15838,15840,15842],{"class":5282,"line":5979},[5280,15837,7023],{"class":5436},[5280,15839,9455],{"class":5452},[5280,15841,15805],{"class":5424},[5280,15843,6111],{"class":5452},[5280,15845,15846,15848,15850,15852,15854,15856,15858,15861,15863,15865,15867,15869],{"class":5282,"line":5984},[5280,15847,6523],{"class":5424},[5280,15849,5218],{"class":5452},[5280,15851,6316],{"class":5293},[5280,15853,5835],{"class":5452},[5280,15855,5994],{"class":5424},[5280,15857,5887],{"class":5452},[5280,15859,15860],{"class":5297},"\"User not authenticated\"",[5280,15862,5887],{"class":5452},[5280,15864,5952],{"class":5424},[5280,15866,5218],{"class":5452},[5280,15868,15133],{"class":5424},[5280,15870,5598],{"class":5452},[5280,15872,15873],{"class":5282,"line":6023},[5280,15874,15875],{"class":5436},"        return\n",[5280,15877,15878],{"class":5282,"line":6042},[5280,15879,6553],{"class":5452},[5280,15881,15882],{"class":5282,"line":6093},[5280,15883,6724],{"class":5452},[5280,15885,15886,15888,15890,15892,15894,15896,15898,15900,15902,15904],{"class":5282,"line":6098},[5280,15887,6730],{"class":5424},[5280,15889,5218],{"class":5452},[5280,15891,6138],{"class":5293},[5280,15893,6141],{"class":5452},[5280,15895,6144],{"class":5293},[5280,15897,5835],{"class":5452},[5280,15899,6149],{"class":5297},[5280,15901,5887],{"class":5452},[5280,15903,6154],{"class":5297},[5280,15905,5598],{"class":5452},[5280,15907,15908,15910,15912,15914,15916,15918,15920,15922,15924,15926,15928,15930,15932,15934],{"class":5282,"line":6114},[5280,15909,6754],{"class":5424},[5280,15911,5218],{"class":5452},[5280,15913,6167],{"class":5293},[5280,15915,5835],{"class":5452},[5280,15917,5994],{"class":5424},[5280,15919,6174],{"class":5452},[5280,15921,6177],{"class":5293},[5280,15923,5835],{"class":5452},[5280,15925,5838],{"class":5619},[5280,15927,5841],{"class":5452},[5280,15929,5844],{"class":5758},[5280,15931,5847],{"class":5452},[5280,15933,5844],{"class":5758},[5280,15935,6781],{"class":5452},[5280,15937,15938,15940,15942,15945],{"class":5282,"line":6130},[5280,15939,12995],{"class":5297},[5280,15941,6790],{"class":5452},[5280,15943,15944],{"class":5297},"\"This is a protected endpoint\"",[5280,15946,6796],{"class":5452},[5280,15948,15949,15952,15954,15956],{"class":5282,"line":6159},[5280,15950,15951],{"class":5297},"        \"user_id\"",[5280,15953,6790],{"class":5452},[5280,15955,15404],{"class":5424},[5280,15957,6796],{"class":5452},[5280,15959,15960],{"class":5282,"line":6187},[5280,15961,6834],{"class":5452},[5280,15963,15964],{"class":5282,"line":6193},[5280,15965,5976],{"class":5452},[5280,15967,15968],{"class":5282,"line":6207},[5280,15969,5313],{"emptyLinePlaceholder":5312},[5280,15971,15972,15974,15976],{"class":5282,"line":6221},[5280,15973,5862],{"class":5619},[5280,15975,5865],{"class":5293},[5280,15977,5868],{"class":5452},[5280,15979,15980],{"class":5282,"line":6233},[5280,15981,15982],{"class":5286},"    \u002F\u002F Публічний маршрут (без Auth Middleware)\n",[5280,15984,15985,15987,15989,15991,15993,15996],{"class":5282,"line":6245},[5280,15986,5874],{"class":5424},[5280,15988,5218],{"class":5452},[5280,15990,8145],{"class":5293},[5280,15992,5835],{"class":5452},[5280,15994,15995],{"class":5297},"\"\u002Fapi\u002Fpublic\"",[5280,15997,6368],{"class":5452},[5280,15999,16000,16002,16004,16006],{"class":5282,"line":6251},[5280,16001,10992],{"class":5424},[5280,16003,5218],{"class":5452},[5280,16005,11590],{"class":5293},[5280,16007,9105],{"class":5452},[5280,16009,16010,16012,16014,16016,16018,16021],{"class":5282,"line":6256},[5280,16011,6311],{"class":5424},[5280,16013,5218],{"class":5452},[5280,16015,7312],{"class":5293},[5280,16017,5835],{"class":5452},[5280,16019,16020],{"class":5424},"publicHandler",[5280,16022,6828],{"class":5452},[5280,16024,16025,16027,16029,16031],{"class":5282,"line":6308},[5280,16026,11003],{"class":5424},[5280,16028,5218],{"class":5452},[5280,16030,10291],{"class":5424},[5280,16032,6796],{"class":5452},[5280,16034,16035,16037,16039,16041],{"class":5282,"line":6339},[5280,16036,11003],{"class":5424},[5280,16038,5218],{"class":5452},[5280,16040,11008],{"class":5424},[5280,16042,6796],{"class":5452},[5280,16044,16045],{"class":5282,"line":6371},[5280,16046,16047],{"class":5452},"        ))\n",[5280,16049,16050],{"class":5282,"line":6409},[5280,16051,6724],{"class":5452},[5280,16053,16054],{"class":5282,"line":6415},[5280,16055,16056],{"class":5286},"    \u002F\u002F Захищений маршрут (з Auth Middleware)\n",[5280,16058,16059,16061,16063,16065,16067,16070],{"class":5282,"line":6420},[5280,16060,5874],{"class":5424},[5280,16062,5218],{"class":5452},[5280,16064,8145],{"class":5293},[5280,16066,5835],{"class":5452},[5280,16068,16069],{"class":5297},"\"\u002Fapi\u002Fprotected\"",[5280,16071,6368],{"class":5452},[5280,16073,16074,16076,16078,16080],{"class":5282,"line":6425},[5280,16075,10992],{"class":5424},[5280,16077,5218],{"class":5452},[5280,16079,11590],{"class":5293},[5280,16081,9105],{"class":5452},[5280,16083,16084,16086,16088,16090,16092,16095],{"class":5282,"line":6450},[5280,16085,6311],{"class":5424},[5280,16087,5218],{"class":5452},[5280,16089,7312],{"class":5293},[5280,16091,5835],{"class":5452},[5280,16093,16094],{"class":5424},"protectedHandler",[5280,16096,6828],{"class":5452},[5280,16098,16099,16101,16103,16105],{"class":5282,"line":6471},[5280,16100,11003],{"class":5424},[5280,16102,5218],{"class":5452},[5280,16104,10291],{"class":5424},[5280,16106,6796],{"class":5452},[5280,16108,16109,16111,16113,16115],{"class":5282,"line":6507},[5280,16110,11003],{"class":5424},[5280,16112,5218],{"class":5452},[5280,16114,11008],{"class":5424},[5280,16116,6796],{"class":5452},[5280,16118,16119,16121,16123,16125,16127],{"class":5282,"line":6512},[5280,16120,11003],{"class":5424},[5280,16122,5218],{"class":5452},[5280,16124,792],{"class":5424},[5280,16126,5887],{"class":5452},[5280,16128,16129],{"class":5286},"\u002F\u002F Додаємо автентифікацію\n",[5280,16131,16132],{"class":5282,"line":6520},[5280,16133,16047],{"class":5452},[5280,16135,16136],{"class":5282,"line":6550},[5280,16137,6724],{"class":5452},[5280,16139,16140,16142,16144,16146,16148,16151],{"class":5282,"line":6556},[5280,16141,5924],{"class":5424},[5280,16143,5218],{"class":5452},[5280,16145,5929],{"class":5293},[5280,16147,5835],{"class":5452},[5280,16149,16150],{"class":5297},"\"Server with authentication on :8080...\"",[5280,16152,5598],{"class":5452},[5280,16154,16155,16157,16159,16161,16163,16165,16167,16169,16171,16173,16175,16177],{"class":5282,"line":6561},[5280,16156,5942],{"class":5424},[5280,16158,5218],{"class":5452},[5280,16160,5947],{"class":5293},[5280,16162,5835],{"class":5452},[5280,16164,5952],{"class":5424},[5280,16166,5218],{"class":5452},[5280,16168,5957],{"class":5293},[5280,16170,5835],{"class":5452},[5280,16172,5962],{"class":5297},[5280,16174,5887],{"class":5452},[5280,16176,5967],{"class":5619},[5280,16178,5970],{"class":5452},[5280,16180,16181],{"class":5282,"line":6621},[5280,16182,5976],{"class":5452},[5262,16184,16185],{},[5227,16186,14681],{},[8866,16188,16190,16200,16204,16207,16217,16221,16224,16234],{":cursor":8868,"title":16189},"Запити до публічного та захищеного ендпоінтів",[8871,16191,16193,7648,16196],{"className":16192},[5282],[5280,16194,8878],{"className":16195},[8877],[5227,16197,16199],{"className":16198},[8882],"curl http:\u002F\u002Flocalhost:8080\u002Fapi\u002Fpublic",[8871,16201,16203],{"className":16202},[5282],"{\"message\":\"This is a public endpoint\"}",[8871,16205],{"className":16206},[5282],[8871,16208,16210,7648,16213],{"className":16209},[5282],[5280,16211,8878],{"className":16212},[8877],[5227,16214,16216],{"className":16215},[8882],"curl http:\u002F\u002Flocalhost:8080\u002Fapi\u002Fprotected",[8871,16218,16220],{"className":16219},[5282],"Missing Authorization header",[8871,16222],{"className":16223},[5282],[8871,16225,16227,7648,16230],{"className":16226},[5282],[5280,16228,8878],{"className":16229},[8877],[5227,16231,16233],{"className":16232},[8882],"curl -H \"Authorization: Bearer my-token\" http:\u002F\u002Flocalhost:8080\u002Fapi\u002Fprotected",[8871,16235,16237],{"className":16236},[5282],"{\"message\":\"This is a protected endpoint\",\"user_id\":\"user-123\"}",[16239,16240,16241],"accordion",{},[16242,16243,16246,16261],"accordion-item",{"icon":16244,"label":16245},"i-lucide-help-circle","❓ Чому не можна використовувати звичайний string як ключ контексту?",[5262,16247,16248,16249,16252,16253,7648,16256,16258,16259,5218],{},"Якщо два різні пакети використовують однаковий string-ключ (наприклад, ",[5215,16250,16251],{},"\"user_id\"","), виникне конфлікт — значення будуть перезаписувати одне одного. Використання ",[5227,16254,16255],{},"приватного типу",[5215,16257,14931],{}," гарантує, що ключ є унікальним, навіть якщо інший пакет також має ключ зі значенням ",[5215,16260,16251],{},[5271,16262,16264],{"className":5411,"code":16263,"language":5325,"meta":5276,"style":5276},"\u002F\u002F Пакет A\ntype contextKey string\nconst UserIDKey contextKey = \"user_id\"\n\n\u002F\u002F Пакет B\ntype contextKey string\nconst UserIDKey contextKey = \"user_id\"\n\n\u002F\u002F Ці ключі РІЗНІ, бо мають різні типи!\n",[5215,16265,16266,16271,16279,16292,16296,16301,16309,16321,16325],{"__ignoreMap":5276},[5280,16267,16268],{"class":5282,"line":5283},[5280,16269,16270],{"class":5286},"\u002F\u002F Пакет A\n",[5280,16272,16273,16275,16277],{"class":5282,"line":5290},[5280,16274,7921],{"class":5619},[5280,16276,14890],{"class":5758},[5280,16278,14893],{"class":5758},[5280,16280,16281,16283,16286,16288,16290],{"class":5282,"line":5301},[5280,16282,14902],{"class":5619},[5280,16284,16285],{"class":13369}," UserIDKey",[5280,16287,14890],{"class":5758},[5280,16289,5829],{"class":5452},[5280,16291,14921],{"class":5297},[5280,16293,16294],{"class":5282,"line":5309},[5280,16295,5313],{"emptyLinePlaceholder":5312},[5280,16297,16298],{"class":5282,"line":5316},[5280,16299,16300],{"class":5286},"\u002F\u002F Пакет B\n",[5280,16302,16303,16305,16307],{"class":5282,"line":5322},[5280,16304,7921],{"class":5619},[5280,16306,14890],{"class":5758},[5280,16308,14893],{"class":5758},[5280,16310,16311,16313,16315,16317,16319],{"class":5282,"line":5336},[5280,16312,14902],{"class":5619},[5280,16314,16285],{"class":13369},[5280,16316,14890],{"class":5758},[5280,16318,5829],{"class":5452},[5280,16320,14921],{"class":5297},[5280,16322,16323],{"class":5282,"line":5341},[5280,16324,5313],{"emptyLinePlaceholder":5312},[5280,16326,16327],{"class":5282,"line":5347},[5280,16328,16329],{"class":5286},"\u002F\u002F Ці ключі РІЗНІ, бо мають різні типи!\n",[5254,16331],{},[5257,16333,16335],{"id":16334},"інтеграція-middleware-з-chi-router","Інтеграція Middleware з Chi Router",[5266,16337,16339],{"id":16338},"chi-router-маршрутизатор-з-вбудованою-підтримкою-middleware","Chi Router: маршрутизатор з вбудованою підтримкою Middleware",[5262,16341,16342,16344],{},[5227,16343,11646],{}," — популярний легковаговий маршрутизатор для Go з вбудованою підтримкою Middleware:",[5271,16346,16348],{"className":5273,"code":16347,"language":5275,"meta":5276,"style":5276},"go get -u github.com\u002Fgo-chi\u002Fchi\u002Fv5\n",[5215,16349,16350],{"__ignoreMap":5276},[5280,16351,16352,16354,16356,16359],{"class":5282,"line":5283},[5280,16353,5325],{"class":5293},[5280,16355,5352],{"class":5297},[5280,16357,16358],{"class":5619}," -u",[5280,16360,5355],{"class":5297},[5266,16362,16364],{"id":16363},"групування-маршрутів-з-різними-middleware","Групування маршрутів з різними Middleware",[5262,16366,16367],{},"Chi дозволяє застосовувати Middleware до груп маршрутів:",[5271,16369,16371],{"className":5411,"code":16370,"language":5325,"meta":5276,"style":5276},"\u002F\u002F cmd\u002Fchi-server\u002Fmain.go\npackage main\n\nimport (\n    \"encoding\u002Fjson\"\n    \"fmt\"\n    \"log\"\n    \"net\u002Fhttp\"\n    \"myapp\u002Fmiddleware\"\n\n    \"github.com\u002Fgo-chi\u002Fchi\u002Fv5\"\n)\n\nfunc main() {\n    r := chi.NewRouter()\n    \n    \u002F\u002F Глобальні Middleware (застосовуються до всіх маршрутів)\n    r.Use(middleware.Logger)\n    r.Use(middleware.Recovery)\n    \n    \u002F\u002F Публічні маршрути\n    r.Get(\"\u002Fapi\u002Fpublic\", func(w http.ResponseWriter, r *http.Request) {\n        json.NewEncoder(w).Encode(map[string]string{\n            \"message\": \"Public endpoint\",\n        })\n    })\n    \n    \u002F\u002F Група захищених маршрутів\n    r.Group(func(r chi.Router) {\n        r.Use(middleware.Auth) \u002F\u002F Auth тільки для цієї групи\n        \n        r.Get(\"\u002Fapi\u002Fprofile\", func(w http.ResponseWriter, r *http.Request) {\n            userID := r.Context().Value(middleware.UserIDKey).(string)\n            json.NewEncoder(w).Encode(map[string]string{\n                \"user_id\": userID,\n                \"message\": \"Your profile\",\n            })\n        })\n        \n        r.Get(\"\u002Fapi\u002Fsettings\", func(w http.ResponseWriter, r *http.Request) {\n            userID := r.Context().Value(middleware.UserIDKey).(string)\n            json.NewEncoder(w).Encode(map[string]string{\n                \"user_id\": userID,\n                \"message\": \"Your settings\",\n            })\n        })\n    })\n    \n    \u002F\u002F Адміністративна група з додатковим rate limiting\n    r.Group(func(r chi.Router) {\n        r.Use(middleware.Auth)\n        rateLimiter := middleware.NewRateLimiter(1, 3)\n        r.Use(rateLimiter.Middleware)\n        \n        r.Get(\"\u002Fapi\u002Fadmin\u002Fusers\", func(w http.ResponseWriter, r *http.Request) {\n            json.NewEncoder(w).Encode(map[string]string{\n                \"message\": \"Admin only - user list\",\n            })\n        })\n    })\n    \n    fmt.Println(\"Chi server with grouped middleware on :8080...\")\n    log.Fatal(http.ListenAndServe(\":8080\", r))\n}\n",[5215,16372,16373,16378,16384,16388,16394,16398,16402,16406,16410,16414,16418,16423,16427,16431,16439,16455,16459,16464,16483,16501,16505,16510,16550,16580,16592,16596,16600,16604,16609,16636,16658,16662,16703,16734,16765,16776,16788,16793,16797,16801,16842,16872,16902,16912,16923,16927,16931,16935,16939,16944,16968,16986,17009,17028,17032,17073,17103,17114,17118,17122,17126,17130,17145,17171],{"__ignoreMap":5276},[5280,16374,16375],{"class":5282,"line":5283},[5280,16376,16377],{"class":5286},"\u002F\u002F cmd\u002Fchi-server\u002Fmain.go\n",[5280,16379,16380,16382],{"class":5282,"line":5290},[5280,16381,5755],{"class":5619},[5280,16383,5759],{"class":5758},[5280,16385,16386],{"class":5282,"line":5301},[5280,16387,5313],{"emptyLinePlaceholder":5312},[5280,16389,16390,16392],{"class":5282,"line":5309},[5280,16391,5768],{"class":5436},[5280,16393,5453],{"class":5452},[5280,16395,16396],{"class":5282,"line":5316},[5280,16397,5775],{"class":5297},[5280,16399,16400],{"class":5282,"line":5322},[5280,16401,5780],{"class":5297},[5280,16403,16404],{"class":5282,"line":5336},[5280,16405,5785],{"class":5297},[5280,16407,16408],{"class":5282,"line":5341},[5280,16409,5790],{"class":5297},[5280,16411,16412],{"class":5282,"line":5347},[5280,16413,10075],{"class":5297},[5280,16415,16416],{"class":5282,"line":5358},[5280,16417,5313],{"emptyLinePlaceholder":5312},[5280,16419,16420],{"class":5282,"line":5368},[5280,16421,16422],{"class":5297},"    \"github.com\u002Fgo-chi\u002Fchi\u002Fv5\"\n",[5280,16424,16425],{"class":5282,"line":5378},[5280,16426,5598],{"class":5452},[5280,16428,16429],{"class":5282,"line":5391},[5280,16430,5313],{"emptyLinePlaceholder":5312},[5280,16432,16433,16435,16437],{"class":5282,"line":5705},[5280,16434,5862],{"class":5619},[5280,16436,5865],{"class":5293},[5280,16438,5868],{"class":5452},[5280,16440,16441,16444,16446,16448,16450,16453],{"class":5282,"line":5820},[5280,16442,16443],{"class":5424},"    r",[5280,16445,6029],{"class":5452},[5280,16447,5474],{"class":5424},[5280,16449,5218],{"class":5452},[5280,16451,16452],{"class":5293},"NewRouter",[5280,16454,6039],{"class":5452},[5280,16456,16457],{"class":5282,"line":5854},[5280,16458,6724],{"class":5452},[5280,16460,16461],{"class":5282,"line":5859},[5280,16462,16463],{"class":5286},"    \u002F\u002F Глобальні Middleware (застосовуються до всіх маршрутів)\n",[5280,16465,16466,16468,16470,16473,16475,16477,16479,16481],{"class":5282,"line":5871},[5280,16467,16443],{"class":5424},[5280,16469,5218],{"class":5452},[5280,16471,16472],{"class":5293},"Use",[5280,16474,5835],{"class":5452},[5280,16476,8768],{"class":5424},[5280,16478,5218],{"class":5452},[5280,16480,10291],{"class":5424},[5280,16482,5598],{"class":5452},[5280,16484,16485,16487,16489,16491,16493,16495,16497,16499],{"class":5282,"line":5895},[5280,16486,16443],{"class":5424},[5280,16488,5218],{"class":5452},[5280,16490,16472],{"class":5293},[5280,16492,5835],{"class":5452},[5280,16494,8768],{"class":5424},[5280,16496,5218],{"class":5452},[5280,16498,11008],{"class":5424},[5280,16500,5598],{"class":5452},[5280,16502,16503],{"class":5282,"line":5916},[5280,16504,6724],{"class":5452},[5280,16506,16507],{"class":5282,"line":5921},[5280,16508,16509],{"class":5286},"    \u002F\u002F Публічні маршрути\n",[5280,16511,16512,16514,16516,16518,16520,16522,16524,16526,16528,16530,16532,16534,16536,16538,16540,16542,16544,16546,16548],{"class":5282,"line":5939},[5280,16513,16443],{"class":5424},[5280,16515,5218],{"class":5452},[5280,16517,12252],{"class":5293},[5280,16519,5835],{"class":5452},[5280,16521,15995],{"class":5297},[5280,16523,5887],{"class":5452},[5280,16525,5862],{"class":5619},[5280,16527,5835],{"class":5452},[5280,16529,5994],{"class":5424},[5280,16531,5997],{"class":5758},[5280,16533,5218],{"class":5452},[5280,16535,6002],{"class":5758},[5280,16537,5887],{"class":5452},[5280,16539,6007],{"class":5424},[5280,16541,6010],{"class":5452},[5280,16543,5952],{"class":5758},[5280,16545,5218],{"class":5452},[5280,16547,6017],{"class":5758},[5280,16549,6020],{"class":5452},[5280,16551,16552,16554,16556,16558,16560,16562,16564,16566,16568,16570,16572,16574,16576,16578],{"class":5282,"line":5973},[5280,16553,6162],{"class":5424},[5280,16555,5218],{"class":5452},[5280,16557,6167],{"class":5293},[5280,16559,5835],{"class":5452},[5280,16561,5994],{"class":5424},[5280,16563,6174],{"class":5452},[5280,16565,6177],{"class":5293},[5280,16567,5835],{"class":5452},[5280,16569,5838],{"class":5619},[5280,16571,5841],{"class":5452},[5280,16573,5844],{"class":5758},[5280,16575,5847],{"class":5452},[5280,16577,5844],{"class":5758},[5280,16579,6781],{"class":5452},[5280,16581,16582,16585,16587,16590],{"class":5282,"line":5979},[5280,16583,16584],{"class":5297},"            \"message\"",[5280,16586,6790],{"class":5452},[5280,16588,16589],{"class":5297},"\"Public endpoint\"",[5280,16591,6796],{"class":5452},[5280,16593,16594],{"class":5282,"line":5984},[5280,16595,12839],{"class":5452},[5280,16597,16598],{"class":5282,"line":6023},[5280,16599,6834],{"class":5452},[5280,16601,16602],{"class":5282,"line":6042},[5280,16603,6724],{"class":5452},[5280,16605,16606],{"class":5282,"line":6093},[5280,16607,16608],{"class":5286},"    \u002F\u002F Група захищених маршрутів\n",[5280,16610,16611,16613,16615,16618,16620,16622,16624,16626,16629,16631,16634],{"class":5282,"line":6098},[5280,16612,16443],{"class":5424},[5280,16614,5218],{"class":5452},[5280,16616,16617],{"class":5293},"Group",[5280,16619,5835],{"class":5452},[5280,16621,5862],{"class":5619},[5280,16623,5835],{"class":5452},[5280,16625,6007],{"class":5424},[5280,16627,16628],{"class":5758}," chi",[5280,16630,5218],{"class":5452},[5280,16632,16633],{"class":5758},"Router",[5280,16635,6020],{"class":5452},[5280,16637,16638,16641,16643,16645,16647,16649,16651,16653,16655],{"class":5282,"line":6114},[5280,16639,16640],{"class":5424},"        r",[5280,16642,5218],{"class":5452},[5280,16644,16472],{"class":5293},[5280,16646,5835],{"class":5452},[5280,16648,8768],{"class":5424},[5280,16650,5218],{"class":5452},[5280,16652,792],{"class":5424},[5280,16654,7315],{"class":5452},[5280,16656,16657],{"class":5286},"\u002F\u002F Auth тільки для цієї групи\n",[5280,16659,16660],{"class":5282,"line":6130},[5280,16661,6190],{"class":5452},[5280,16663,16664,16666,16668,16670,16672,16675,16677,16679,16681,16683,16685,16687,16689,16691,16693,16695,16697,16699,16701],{"class":5282,"line":6159},[5280,16665,16640],{"class":5424},[5280,16667,5218],{"class":5452},[5280,16669,12252],{"class":5293},[5280,16671,5835],{"class":5452},[5280,16673,16674],{"class":5297},"\"\u002Fapi\u002Fprofile\"",[5280,16676,5887],{"class":5452},[5280,16678,5862],{"class":5619},[5280,16680,5835],{"class":5452},[5280,16682,5994],{"class":5424},[5280,16684,5997],{"class":5758},[5280,16686,5218],{"class":5452},[5280,16688,6002],{"class":5758},[5280,16690,5887],{"class":5452},[5280,16692,6007],{"class":5424},[5280,16694,6010],{"class":5452},[5280,16696,5952],{"class":5758},[5280,16698,5218],{"class":5452},[5280,16700,6017],{"class":5758},[5280,16702,6020],{"class":5452},[5280,16704,16705,16708,16710,16712,16714,16716,16718,16720,16722,16724,16726,16728,16730,16732],{"class":5282,"line":6187},[5280,16706,16707],{"class":5424},"            userID",[5280,16709,6029],{"class":5452},[5280,16711,6007],{"class":5424},[5280,16713,5218],{"class":5452},[5280,16715,15393],{"class":5293},[5280,16717,6141],{"class":5452},[5280,16719,15818],{"class":5293},[5280,16721,5835],{"class":5452},[5280,16723,8768],{"class":5424},[5280,16725,5218],{"class":5452},[5280,16727,15399],{"class":5424},[5280,16729,15829],{"class":5452},[5280,16731,5844],{"class":5758},[5280,16733,5598],{"class":5452},[5280,16735,16736,16739,16741,16743,16745,16747,16749,16751,16753,16755,16757,16759,16761,16763],{"class":5282,"line":6193},[5280,16737,16738],{"class":5424},"            json",[5280,16740,5218],{"class":5452},[5280,16742,6167],{"class":5293},[5280,16744,5835],{"class":5452},[5280,16746,5994],{"class":5424},[5280,16748,6174],{"class":5452},[5280,16750,6177],{"class":5293},[5280,16752,5835],{"class":5452},[5280,16754,5838],{"class":5619},[5280,16756,5841],{"class":5452},[5280,16758,5844],{"class":5758},[5280,16760,5847],{"class":5452},[5280,16762,5844],{"class":5758},[5280,16764,6781],{"class":5452},[5280,16766,16767,16770,16772,16774],{"class":5282,"line":6207},[5280,16768,16769],{"class":5297},"                \"user_id\"",[5280,16771,6790],{"class":5452},[5280,16773,15404],{"class":5424},[5280,16775,6796],{"class":5452},[5280,16777,16778,16781,16783,16786],{"class":5282,"line":6221},[5280,16779,16780],{"class":5297},"                \"message\"",[5280,16782,6790],{"class":5452},[5280,16784,16785],{"class":5297},"\"Your profile\"",[5280,16787,6796],{"class":5452},[5280,16789,16790],{"class":5282,"line":6233},[5280,16791,16792],{"class":5452},"            })\n",[5280,16794,16795],{"class":5282,"line":6245},[5280,16796,12839],{"class":5452},[5280,16798,16799],{"class":5282,"line":6251},[5280,16800,6190],{"class":5452},[5280,16802,16803,16805,16807,16809,16811,16814,16816,16818,16820,16822,16824,16826,16828,16830,16832,16834,16836,16838,16840],{"class":5282,"line":6256},[5280,16804,16640],{"class":5424},[5280,16806,5218],{"class":5452},[5280,16808,12252],{"class":5293},[5280,16810,5835],{"class":5452},[5280,16812,16813],{"class":5297},"\"\u002Fapi\u002Fsettings\"",[5280,16815,5887],{"class":5452},[5280,16817,5862],{"class":5619},[5280,16819,5835],{"class":5452},[5280,16821,5994],{"class":5424},[5280,16823,5997],{"class":5758},[5280,16825,5218],{"class":5452},[5280,16827,6002],{"class":5758},[5280,16829,5887],{"class":5452},[5280,16831,6007],{"class":5424},[5280,16833,6010],{"class":5452},[5280,16835,5952],{"class":5758},[5280,16837,5218],{"class":5452},[5280,16839,6017],{"class":5758},[5280,16841,6020],{"class":5452},[5280,16843,16844,16846,16848,16850,16852,16854,16856,16858,16860,16862,16864,16866,16868,16870],{"class":5282,"line":6308},[5280,16845,16707],{"class":5424},[5280,16847,6029],{"class":5452},[5280,16849,6007],{"class":5424},[5280,16851,5218],{"class":5452},[5280,16853,15393],{"class":5293},[5280,16855,6141],{"class":5452},[5280,16857,15818],{"class":5293},[5280,16859,5835],{"class":5452},[5280,16861,8768],{"class":5424},[5280,16863,5218],{"class":5452},[5280,16865,15399],{"class":5424},[5280,16867,15829],{"class":5452},[5280,16869,5844],{"class":5758},[5280,16871,5598],{"class":5452},[5280,16873,16874,16876,16878,16880,16882,16884,16886,16888,16890,16892,16894,16896,16898,16900],{"class":5282,"line":6339},[5280,16875,16738],{"class":5424},[5280,16877,5218],{"class":5452},[5280,16879,6167],{"class":5293},[5280,16881,5835],{"class":5452},[5280,16883,5994],{"class":5424},[5280,16885,6174],{"class":5452},[5280,16887,6177],{"class":5293},[5280,16889,5835],{"class":5452},[5280,16891,5838],{"class":5619},[5280,16893,5841],{"class":5452},[5280,16895,5844],{"class":5758},[5280,16897,5847],{"class":5452},[5280,16899,5844],{"class":5758},[5280,16901,6781],{"class":5452},[5280,16903,16904,16906,16908,16910],{"class":5282,"line":6371},[5280,16905,16769],{"class":5297},[5280,16907,6790],{"class":5452},[5280,16909,15404],{"class":5424},[5280,16911,6796],{"class":5452},[5280,16913,16914,16916,16918,16921],{"class":5282,"line":6409},[5280,16915,16780],{"class":5297},[5280,16917,6790],{"class":5452},[5280,16919,16920],{"class":5297},"\"Your settings\"",[5280,16922,6796],{"class":5452},[5280,16924,16925],{"class":5282,"line":6415},[5280,16926,16792],{"class":5452},[5280,16928,16929],{"class":5282,"line":6420},[5280,16930,12839],{"class":5452},[5280,16932,16933],{"class":5282,"line":6425},[5280,16934,6834],{"class":5452},[5280,16936,16937],{"class":5282,"line":6450},[5280,16938,6724],{"class":5452},[5280,16940,16941],{"class":5282,"line":6471},[5280,16942,16943],{"class":5286},"    \u002F\u002F Адміністративна група з додатковим rate limiting\n",[5280,16945,16946,16948,16950,16952,16954,16956,16958,16960,16962,16964,16966],{"class":5282,"line":6507},[5280,16947,16443],{"class":5424},[5280,16949,5218],{"class":5452},[5280,16951,16617],{"class":5293},[5280,16953,5835],{"class":5452},[5280,16955,5862],{"class":5619},[5280,16957,5835],{"class":5452},[5280,16959,6007],{"class":5424},[5280,16961,16628],{"class":5758},[5280,16963,5218],{"class":5452},[5280,16965,16633],{"class":5758},[5280,16967,6020],{"class":5452},[5280,16969,16970,16972,16974,16976,16978,16980,16982,16984],{"class":5282,"line":6512},[5280,16971,16640],{"class":5424},[5280,16973,5218],{"class":5452},[5280,16975,16472],{"class":5293},[5280,16977,5835],{"class":5452},[5280,16979,8768],{"class":5424},[5280,16981,5218],{"class":5452},[5280,16983,792],{"class":5424},[5280,16985,5598],{"class":5452},[5280,16987,16988,16990,16992,16994,16996,16998,17000,17002,17004,17007],{"class":5282,"line":6520},[5280,16989,14595],{"class":5424},[5280,16991,6029],{"class":5452},[5280,16993,8768],{"class":5424},[5280,16995,5218],{"class":5452},[5280,16997,14526],{"class":5293},[5280,16999,5835],{"class":5452},[5280,17001,11401],{"class":5439},[5280,17003,5887],{"class":5452},[5280,17005,17006],{"class":5439},"3",[5280,17008,5598],{"class":5452},[5280,17010,17011,17013,17015,17017,17019,17022,17024,17026],{"class":5282,"line":6550},[5280,17012,16640],{"class":5424},[5280,17014,5218],{"class":5452},[5280,17016,16472],{"class":5293},[5280,17018,5835],{"class":5452},[5280,17020,17021],{"class":5424},"rateLimiter",[5280,17023,5218],{"class":5452},[5280,17025,7633],{"class":5424},[5280,17027,5598],{"class":5452},[5280,17029,17030],{"class":5282,"line":6556},[5280,17031,6190],{"class":5452},[5280,17033,17034,17036,17038,17040,17042,17045,17047,17049,17051,17053,17055,17057,17059,17061,17063,17065,17067,17069,17071],{"class":5282,"line":6561},[5280,17035,16640],{"class":5424},[5280,17037,5218],{"class":5452},[5280,17039,12252],{"class":5293},[5280,17041,5835],{"class":5452},[5280,17043,17044],{"class":5297},"\"\u002Fapi\u002Fadmin\u002Fusers\"",[5280,17046,5887],{"class":5452},[5280,17048,5862],{"class":5619},[5280,17050,5835],{"class":5452},[5280,17052,5994],{"class":5424},[5280,17054,5997],{"class":5758},[5280,17056,5218],{"class":5452},[5280,17058,6002],{"class":5758},[5280,17060,5887],{"class":5452},[5280,17062,6007],{"class":5424},[5280,17064,6010],{"class":5452},[5280,17066,5952],{"class":5758},[5280,17068,5218],{"class":5452},[5280,17070,6017],{"class":5758},[5280,17072,6020],{"class":5452},[5280,17074,17075,17077,17079,17081,17083,17085,17087,17089,17091,17093,17095,17097,17099,17101],{"class":5282,"line":6621},[5280,17076,16738],{"class":5424},[5280,17078,5218],{"class":5452},[5280,17080,6167],{"class":5293},[5280,17082,5835],{"class":5452},[5280,17084,5994],{"class":5424},[5280,17086,6174],{"class":5452},[5280,17088,6177],{"class":5293},[5280,17090,5835],{"class":5452},[5280,17092,5838],{"class":5619},[5280,17094,5841],{"class":5452},[5280,17096,5844],{"class":5758},[5280,17098,5847],{"class":5452},[5280,17100,5844],{"class":5758},[5280,17102,6781],{"class":5452},[5280,17104,17105,17107,17109,17112],{"class":5282,"line":6626},[5280,17106,16780],{"class":5297},[5280,17108,6790],{"class":5452},[5280,17110,17111],{"class":5297},"\"Admin only - user list\"",[5280,17113,6796],{"class":5452},[5280,17115,17116],{"class":5282,"line":6631},[5280,17117,16792],{"class":5452},[5280,17119,17120],{"class":5282,"line":6663},[5280,17121,12839],{"class":5452},[5280,17123,17124],{"class":5282,"line":6678},[5280,17125,6834],{"class":5452},[5280,17127,17128],{"class":5282,"line":6721},[5280,17129,6724],{"class":5452},[5280,17131,17132,17134,17136,17138,17140,17143],{"class":5282,"line":6727},[5280,17133,5924],{"class":5424},[5280,17135,5218],{"class":5452},[5280,17137,5929],{"class":5293},[5280,17139,5835],{"class":5452},[5280,17141,17142],{"class":5297},"\"Chi server with grouped middleware on :8080...\"",[5280,17144,5598],{"class":5452},[5280,17146,17147,17149,17151,17153,17155,17157,17159,17161,17163,17165,17167,17169],{"class":5282,"line":6751},[5280,17148,5942],{"class":5424},[5280,17150,5218],{"class":5452},[5280,17152,5947],{"class":5293},[5280,17154,5835],{"class":5452},[5280,17156,5952],{"class":5424},[5280,17158,5218],{"class":5452},[5280,17160,5957],{"class":5293},[5280,17162,5835],{"class":5452},[5280,17164,5962],{"class":5297},[5280,17166,5887],{"class":5452},[5280,17168,6007],{"class":5424},[5280,17170,5970],{"class":5452},[5280,17172,17173],{"class":5282,"line":6784},[5280,17174,5976],{"class":5452},[7678,17176,17178],{"alt":17177},"Структура маршрутів з Middleware",[5271,17179,17181],{"className":7683,"code":17180,"language":7685,"meta":5276,"style":5276},"@startuml\nskinparam style plain\nskinparam backgroundColor #FFFFFF\n\npackage \"Chi Router\" {\n  rectangle \"Global Middleware\" as GM #FEF3C7 {\n    note right\n      Logger\n      Recovery\n    end note\n  }\n  \n  rectangle \"\u002Fapi\u002Fpublic\" as Public #E2E8F0\n  \n  rectangle \"Protected Group\" as PG #DBEAFE {\n    note right: + Auth\n    rectangle \"\u002Fapi\u002Fprofile\" as Profile #E2E8F0\n    rectangle \"\u002Fapi\u002Fsettings\" as Settings #E2E8F0\n  }\n  \n  rectangle \"Admin Group\" as AG #DCFCE7 {\n    note right\n      + Auth\n      + RateLimit\n    end note\n    rectangle \"\u002Fapi\u002Fadmin\u002Fusers\" as Admin #E2E8F0\n  }\n  \n  GM -down-> Public\n  GM -down-> PG\n  GM -down-> AG\n}\n\n@enduml\n",[5215,17182,17183,17187,17191,17195,17199,17204,17209,17214,17219,17224,17229,17233,17237,17242,17246,17251,17256,17261,17266,17270,17274,17279,17283,17288,17293,17297,17302,17306,17310,17315,17320,17325,17329,17333],{"__ignoreMap":5276},[5280,17184,17185],{"class":5282,"line":5283},[5280,17186,7692],{},[5280,17188,17189],{"class":5282,"line":5290},[5280,17190,7697],{},[5280,17192,17193],{"class":5282,"line":5301},[5280,17194,7702],{},[5280,17196,17197],{"class":5282,"line":5309},[5280,17198,5313],{"emptyLinePlaceholder":5312},[5280,17200,17201],{"class":5282,"line":5316},[5280,17202,17203],{},"package \"Chi Router\" {\n",[5280,17205,17206],{"class":5282,"line":5322},[5280,17207,17208],{},"  rectangle \"Global Middleware\" as GM #FEF3C7 {\n",[5280,17210,17211],{"class":5282,"line":5336},[5280,17212,17213],{},"    note right\n",[5280,17215,17216],{"class":5282,"line":5341},[5280,17217,17218],{},"      Logger\n",[5280,17220,17221],{"class":5282,"line":5347},[5280,17222,17223],{},"      Recovery\n",[5280,17225,17226],{"class":5282,"line":5358},[5280,17227,17228],{},"    end note\n",[5280,17230,17231],{"class":5282,"line":5368},[5280,17232,11514],{},[5280,17234,17235],{"class":5282,"line":5378},[5280,17236,11547],{},[5280,17238,17239],{"class":5282,"line":5391},[5280,17240,17241],{},"  rectangle \"\u002Fapi\u002Fpublic\" as Public #E2E8F0\n",[5280,17243,17244],{"class":5282,"line":5705},[5280,17245,11547],{},[5280,17247,17248],{"class":5282,"line":5820},[5280,17249,17250],{},"  rectangle \"Protected Group\" as PG #DBEAFE {\n",[5280,17252,17253],{"class":5282,"line":5854},[5280,17254,17255],{},"    note right: + Auth\n",[5280,17257,17258],{"class":5282,"line":5859},[5280,17259,17260],{},"    rectangle \"\u002Fapi\u002Fprofile\" as Profile #E2E8F0\n",[5280,17262,17263],{"class":5282,"line":5871},[5280,17264,17265],{},"    rectangle \"\u002Fapi\u002Fsettings\" as Settings #E2E8F0\n",[5280,17267,17268],{"class":5282,"line":5895},[5280,17269,11514],{},[5280,17271,17272],{"class":5282,"line":5916},[5280,17273,11547],{},[5280,17275,17276],{"class":5282,"line":5921},[5280,17277,17278],{},"  rectangle \"Admin Group\" as AG #DCFCE7 {\n",[5280,17280,17281],{"class":5282,"line":5939},[5280,17282,17213],{},[5280,17284,17285],{"class":5282,"line":5973},[5280,17286,17287],{},"      + Auth\n",[5280,17289,17290],{"class":5282,"line":5979},[5280,17291,17292],{},"      + RateLimit\n",[5280,17294,17295],{"class":5282,"line":5984},[5280,17296,17228],{},[5280,17298,17299],{"class":5282,"line":6023},[5280,17300,17301],{},"    rectangle \"\u002Fapi\u002Fadmin\u002Fusers\" as Admin #E2E8F0\n",[5280,17303,17304],{"class":5282,"line":6042},[5280,17305,11514],{},[5280,17307,17308],{"class":5282,"line":6093},[5280,17309,11547],{},[5280,17311,17312],{"class":5282,"line":6098},[5280,17313,17314],{},"  GM -down-> Public\n",[5280,17316,17317],{"class":5282,"line":6114},[5280,17318,17319],{},"  GM -down-> PG\n",[5280,17321,17322],{"class":5282,"line":6130},[5280,17323,17324],{},"  GM -down-> AG\n",[5280,17326,17327],{"class":5282,"line":6159},[5280,17328,5976],{},[5280,17330,17331],{"class":5282,"line":6187},[5280,17332,5313],{"emptyLinePlaceholder":5312},[5280,17334,17335],{"class":5282,"line":6193},[5280,17336,7904],{},[8342,17338,17339,17340,17343],{},"Chi дозволяє створювати ",[5227,17341,17342],{},"вкладені групи"," з наростаючою кількістю Middleware. Це зручно для побудови складних ієрархій доступу (публічні → автентифіковані → адміністратори → супер-адміністратори).",[5254,17345],{},[5257,17347,17349],{"id":17348},"повний-приклад-реальний-сервер-з-усіма-middleware","Повний приклад: реальний сервер з усіма Middleware",[5262,17351,17352],{},"Об'єднаємо все, що ми вивчили, в один повноцінний сервер:",[5271,17354,17356],{"className":5411,"code":17355,"language":5325,"meta":5276,"style":5276},"\u002F\u002F cmd\u002Ffull-server\u002Fmain.go\npackage main\n\nimport (\n    \"encoding\u002Fjson\"\n    \"fmt\"\n    \"log\"\n    \"net\u002Fhttp\"\n    \"time\"\n    \"myapp\u002Fmiddleware\"\n\n    \"github.com\u002Fgo-chi\u002Fchi\u002Fv5\"\n)\n\n\u002F\u002F Структури даних\ntype User struct {\n    ID        string    `json:\"id\"`\n    Name      string    `json:\"name\"`\n    Email     string    `json:\"email\"`\n    CreatedAt time.Time `json:\"created_at\"`\n}\n\n\u002F\u002F In-memory зберігання (у реальності — БД)\nvar users = map[string]*User{\n    \"user-1\": {\n        ID:        \"user-1\",\n        Name:      \"John Doe\",\n        Email:     \"john@example.com\",\n        CreatedAt: time.Now().Add(-24 * time.Hour),\n    },\n}\n\nfunc main() {\n    r := chi.NewRouter()\n    \n    \u002F\u002F Глобальні Middleware\n    r.Use(middleware.Logger)\n    r.Use(middleware.Recovery)\n    corsConfig := middleware.DefaultCORSConfig()\n    r.Use(middleware.CORS(corsConfig))\n    \n    \u002F\u002F Health check\n    r.Get(\"\u002Fhealth\", func(w http.ResponseWriter, r *http.Request) {\n        json.NewEncoder(w).Encode(map[string]interface{}{\n            \"status\":    \"ok\",\n            \"timestamp\": time.Now().Unix(),\n        })\n    })\n    \n    \u002F\u002F Публічні маршрути\n    r.Post(\"\u002Fapi\u002Fregister\", registerHandler)\n    r.Post(\"\u002Fapi\u002Flogin\", loginHandler)\n    \n    \u002F\u002F Захищені маршрути\n    r.Group(func(r chi.Router) {\n        r.Use(middleware.Auth)\n        \n        r.Get(\"\u002Fapi\u002Fusers\u002Fme\", getCurrentUserHandler)\n        r.Put(\"\u002Fapi\u002Fusers\u002Fme\", updateCurrentUserHandler)\n    })\n    \n    \u002F\u002F Адмінські маршрути з rate limiting\n    r.Group(func(r chi.Router) {\n        r.Use(middleware.Auth)\n        rateLimiter := middleware.NewRateLimiter(5, 10)\n        r.Use(rateLimiter.Middleware)\n        \n        r.Get(\"\u002Fapi\u002Fadmin\u002Fusers\", listUsersHandler)\n        r.Get(\"\u002Fapi\u002Fadmin\u002Fstats\", getStatsHandler)\n    })\n    \n    fmt.Println(\"╔════════════════════════════════════════╗\")\n    fmt.Println(\"║  Full-featured server on :8080 🚀      ║\")\n    fmt.Println(\"╠════════════════════════════════════════╣\")\n    fmt.Println(\"║  Endpoints:                            ║\")\n    fmt.Println(\"║   GET  \u002Fhealth                         ║\")\n    fmt.Println(\"║   POST \u002Fapi\u002Fregister                   ║\")\n    fmt.Println(\"║   POST \u002Fapi\u002Flogin                      ║\")\n    fmt.Println(\"║   GET  \u002Fapi\u002Fusers\u002Fme  (auth)           ║\")\n    fmt.Println(\"║   PUT  \u002Fapi\u002Fusers\u002Fme  (auth)           ║\")\n    fmt.Println(\"║   GET  \u002Fapi\u002Fadmin\u002Fusers (auth+limit)   ║\")\n    fmt.Println(\"║   GET  \u002Fapi\u002Fadmin\u002Fstats (auth+limit)   ║\")\n    fmt.Println(\"╚════════════════════════════════════════╝\")\n    \n    log.Fatal(http.ListenAndServe(\":8080\", r))\n}\n\nfunc registerHandler(w http.ResponseWriter, r *http.Request) {\n    var req struct {\n        Name     string `json:\"name\"`\n        Email    string `json:\"email\"`\n        Password string `json:\"password\"`\n    }\n    \n    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {\n        http.Error(w, \"Invalid JSON\", http.StatusBadRequest)\n        return\n    }\n    \n    \u002F\u002F Тут має бути хешування пароля і збереження в БД\n    newUser := &User{\n        ID:        fmt.Sprintf(\"user-%d\", len(users)+1),\n        Name:      req.Name,\n        Email:     req.Email,\n        CreatedAt: time.Now(),\n    }\n    users[newUser.ID] = newUser\n    \n    w.WriteHeader(http.StatusCreated)\n    json.NewEncoder(w).Encode(newUser)\n}\n\nfunc loginHandler(w http.ResponseWriter, r *http.Request) {\n    \u002F\u002F Спрощена реалізація (у реальності — перевірка паролю, генерація JWT)\n    json.NewEncoder(w).Encode(map[string]string{\n        \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\", \u002F\u002F Заглушка\n    })\n}\n\nfunc getCurrentUserHandler(w http.ResponseWriter, r *http.Request) {\n    userID := r.Context().Value(middleware.UserIDKey).(string)\n    \n    user, exists := users[userID]\n    if !exists {\n        http.Error(w, \"User not found\", http.StatusNotFound)\n        return\n    }\n    \n    json.NewEncoder(w).Encode(user)\n}\n\nfunc updateCurrentUserHandler(w http.ResponseWriter, r *http.Request) {\n    userID := r.Context().Value(middleware.UserIDKey).(string)\n    \n    var updates struct {\n        Name string `json:\"name\"`\n    }\n    if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {\n        http.Error(w, \"Invalid JSON\", http.StatusBadRequest)\n        return\n    }\n    \n    user, exists := users[userID]\n    if !exists {\n        http.Error(w, \"User not found\", http.StatusNotFound)\n        return\n    }\n    \n    user.Name = updates.Name\n    json.NewEncoder(w).Encode(user)\n}\n\nfunc listUsersHandler(w http.ResponseWriter, r *http.Request) {\n    allUsers := make([]*User, 0, len(users))\n    for _, u := range users {\n        allUsers = append(allUsers, u)\n    }\n    json.NewEncoder(w).Encode(allUsers)\n}\n\nfunc getStatsHandler(w http.ResponseWriter, r *http.Request) {\n    json.NewEncoder(w).Encode(map[string]interface{}{\n        \"total_users\":     len(users),\n        \"server_uptime\":   \"24h\", \u002F\u002F Заглушка\n        \"requests_today\":  12543,  \u002F\u002F Заглушка\n    })\n}\n",[5215,17357,17358,17363,17369,17373,17379,17383,17387,17391,17395,17399,17403,17407,17411,17415,17419,17424,17435,17446,17457,17468,17482,17486,17490,17495,17516,17524,17536,17549,17561,17597,17602,17606,17610,17618,17632,17636,17641,17659,17677,17691,17713,17717,17722,17762,17794,17805,17826,17830,17834,17838,17842,17863,17883,17887,17892,17916,17934,17938,17958,17978,17982,17986,17991,18015,18033,18056,18074,18078,18097,18117,18121,18125,18140,18155,18170,18185,18200,18215,18230,18245,18260,18275,18290,18305,18309,18336,18341,18346,18378,18391,18400,18410,18421,18426,18431,18473,18500,18505,18510,18515,18521,18533,18570,18585,18600,18615,18620,18641,18646,18665,18688,18693,18698,18730,18736,18767,18783,18788,18793,18798,18830,18861,18866,18886,18897,18926,18931,18936,18941,18964,18969,18974,19006,19037,19042,19054,19063,19068,19110,19137,19142,19147,19152,19171,19182,19209,19214,19219,19224,19241,19264,19269,19274,19306,19335,19355,19377,19382,19405,19410,19415,19447,19478,19494,19509,19526,19531],{"__ignoreMap":5276},[5280,17359,17360],{"class":5282,"line":5283},[5280,17361,17362],{"class":5286},"\u002F\u002F cmd\u002Ffull-server\u002Fmain.go\n",[5280,17364,17365,17367],{"class":5282,"line":5290},[5280,17366,5755],{"class":5619},[5280,17368,5759],{"class":5758},[5280,17370,17371],{"class":5282,"line":5301},[5280,17372,5313],{"emptyLinePlaceholder":5312},[5280,17374,17375,17377],{"class":5282,"line":5309},[5280,17376,5768],{"class":5436},[5280,17378,5453],{"class":5452},[5280,17380,17381],{"class":5282,"line":5316},[5280,17382,5775],{"class":5297},[5280,17384,17385],{"class":5282,"line":5322},[5280,17386,5780],{"class":5297},[5280,17388,17389],{"class":5282,"line":5336},[5280,17390,5785],{"class":5297},[5280,17392,17393],{"class":5282,"line":5341},[5280,17394,5790],{"class":5297},[5280,17396,17397],{"class":5282,"line":5347},[5280,17398,5795],{"class":5297},[5280,17400,17401],{"class":5282,"line":5358},[5280,17402,10075],{"class":5297},[5280,17404,17405],{"class":5282,"line":5368},[5280,17406,5313],{"emptyLinePlaceholder":5312},[5280,17408,17409],{"class":5282,"line":5378},[5280,17410,16422],{"class":5297},[5280,17412,17413],{"class":5282,"line":5391},[5280,17414,5598],{"class":5452},[5280,17416,17417],{"class":5282,"line":5705},[5280,17418,5313],{"emptyLinePlaceholder":5312},[5280,17420,17421],{"class":5282,"line":5820},[5280,17422,17423],{"class":5286},"\u002F\u002F Структури даних\n",[5280,17425,17426,17428,17431,17433],{"class":5282,"line":5854},[5280,17427,7921],{"class":5619},[5280,17429,17430],{"class":5758}," User",[5280,17432,6216],{"class":5619},[5280,17434,6111],{"class":5452},[5280,17436,17437,17440,17443],{"class":5282,"line":5859},[5280,17438,17439],{"class":5424},"    ID",[5280,17441,17442],{"class":5758},"        string",[5280,17444,17445],{"class":5297},"    `json:\"id\"`\n",[5280,17447,17448,17451,17454],{"class":5282,"line":5871},[5280,17449,17450],{"class":5424},"    Name",[5280,17452,17453],{"class":5758},"      string",[5280,17455,17456],{"class":5297},"    `json:\"name\"`\n",[5280,17458,17459,17462,17465],{"class":5282,"line":5895},[5280,17460,17461],{"class":5424},"    Email",[5280,17463,17464],{"class":5758},"     string",[5280,17466,17467],{"class":5297},"    `json:\"email\"`\n",[5280,17469,17470,17473,17475,17477,17479],{"class":5282,"line":5916},[5280,17471,17472],{"class":5424},"    CreatedAt",[5280,17474,7004],{"class":5758},[5280,17476,5218],{"class":5452},[5280,17478,7009],{"class":5758},[5280,17480,17481],{"class":5297}," `json:\"created_at\"`\n",[5280,17483,17484],{"class":5282,"line":5921},[5280,17485,5976],{"class":5452},[5280,17487,17488],{"class":5282,"line":5939},[5280,17489,5313],{"emptyLinePlaceholder":5312},[5280,17491,17492],{"class":5282,"line":5973},[5280,17493,17494],{"class":5286},"\u002F\u002F In-memory зберігання (у реальності — БД)\n",[5280,17496,17497,17499,17501,17503,17505,17507,17509,17511,17514],{"class":5282,"line":5979},[5280,17498,5823],{"class":5619},[5280,17500,5826],{"class":5424},[5280,17502,5829],{"class":5452},[5280,17504,5838],{"class":5619},[5280,17506,5841],{"class":5452},[5280,17508,5844],{"class":5758},[5280,17510,13724],{"class":5452},[5280,17512,17513],{"class":5758},"User",[5280,17515,6781],{"class":5452},[5280,17517,17518,17521],{"class":5282,"line":5984},[5280,17519,17520],{"class":5297},"    \"user-1\"",[5280,17522,17523],{"class":5452},": {\n",[5280,17525,17526,17529,17531,17534],{"class":5282,"line":6023},[5280,17527,17528],{"class":5424},"        ID",[5280,17530,9788],{"class":5452},[5280,17532,17533],{"class":5297},"\"user-1\"",[5280,17535,6796],{"class":5452},[5280,17537,17538,17541,17544,17547],{"class":5282,"line":6042},[5280,17539,17540],{"class":5424},"        Name",[5280,17542,17543],{"class":5452},":      ",[5280,17545,17546],{"class":5297},"\"John Doe\"",[5280,17548,6796],{"class":5452},[5280,17550,17551,17554,17556,17559],{"class":5282,"line":6093},[5280,17552,17553],{"class":5424},"        Email",[5280,17555,9768],{"class":5452},[5280,17557,17558],{"class":5297},"\"john@example.com\"",[5280,17560,6796],{"class":5452},[5280,17562,17563,17566,17568,17570,17572,17574,17576,17579,17582,17585,17588,17590,17592,17595],{"class":5282,"line":6098},[5280,17564,17565],{"class":5424},"        CreatedAt",[5280,17567,6790],{"class":5452},[5280,17569,5587],{"class":5424},[5280,17571,5218],{"class":5452},[5280,17573,6036],{"class":5293},[5280,17575,6141],{"class":5452},[5280,17577,17578],{"class":5293},"Add",[5280,17580,17581],{"class":5452},"(-",[5280,17583,17584],{"class":5439},"24",[5280,17586,17587],{"class":5452}," * ",[5280,17589,5587],{"class":5424},[5280,17591,5218],{"class":5452},[5280,17593,17594],{"class":5424},"Hour",[5280,17596,6828],{"class":5452},[5280,17598,17599],{"class":5282,"line":6114},[5280,17600,17601],{"class":5452},"    },\n",[5280,17603,17604],{"class":5282,"line":6130},[5280,17605,5976],{"class":5452},[5280,17607,17608],{"class":5282,"line":6159},[5280,17609,5313],{"emptyLinePlaceholder":5312},[5280,17611,17612,17614,17616],{"class":5282,"line":6187},[5280,17613,5862],{"class":5619},[5280,17615,5865],{"class":5293},[5280,17617,5868],{"class":5452},[5280,17619,17620,17622,17624,17626,17628,17630],{"class":5282,"line":6193},[5280,17621,16443],{"class":5424},[5280,17623,6029],{"class":5452},[5280,17625,5474],{"class":5424},[5280,17627,5218],{"class":5452},[5280,17629,16452],{"class":5293},[5280,17631,6039],{"class":5452},[5280,17633,17634],{"class":5282,"line":6207},[5280,17635,6724],{"class":5452},[5280,17637,17638],{"class":5282,"line":6221},[5280,17639,17640],{"class":5286},"    \u002F\u002F Глобальні Middleware\n",[5280,17642,17643,17645,17647,17649,17651,17653,17655,17657],{"class":5282,"line":6233},[5280,17644,16443],{"class":5424},[5280,17646,5218],{"class":5452},[5280,17648,16472],{"class":5293},[5280,17650,5835],{"class":5452},[5280,17652,8768],{"class":5424},[5280,17654,5218],{"class":5452},[5280,17656,10291],{"class":5424},[5280,17658,5598],{"class":5452},[5280,17660,17661,17663,17665,17667,17669,17671,17673,17675],{"class":5282,"line":6245},[5280,17662,16443],{"class":5424},[5280,17664,5218],{"class":5452},[5280,17666,16472],{"class":5293},[5280,17668,5835],{"class":5452},[5280,17670,8768],{"class":5424},[5280,17672,5218],{"class":5452},[5280,17674,11008],{"class":5424},[5280,17676,5598],{"class":5452},[5280,17678,17679,17681,17683,17685,17687,17689],{"class":5282,"line":6251},[5280,17680,13027],{"class":5424},[5280,17682,6029],{"class":5452},[5280,17684,8768],{"class":5424},[5280,17686,5218],{"class":5452},[5280,17688,13036],{"class":5293},[5280,17690,6039],{"class":5452},[5280,17692,17693,17695,17697,17699,17701,17703,17705,17707,17709,17711],{"class":5282,"line":6256},[5280,17694,16443],{"class":5424},[5280,17696,5218],{"class":5452},[5280,17698,16472],{"class":5293},[5280,17700,5835],{"class":5452},[5280,17702,8768],{"class":5424},[5280,17704,5218],{"class":5452},[5280,17706,11259],{"class":5293},[5280,17708,5835],{"class":5452},[5280,17710,13105],{"class":5424},[5280,17712,5970],{"class":5452},[5280,17714,17715],{"class":5282,"line":6308},[5280,17716,6724],{"class":5452},[5280,17718,17719],{"class":5282,"line":6339},[5280,17720,17721],{"class":5286},"    \u002F\u002F Health check\n",[5280,17723,17724,17726,17728,17730,17732,17734,17736,17738,17740,17742,17744,17746,17748,17750,17752,17754,17756,17758,17760],{"class":5282,"line":6371},[5280,17725,16443],{"class":5424},[5280,17727,5218],{"class":5452},[5280,17729,12252],{"class":5293},[5280,17731,5835],{"class":5452},[5280,17733,10282],{"class":5297},[5280,17735,5887],{"class":5452},[5280,17737,5862],{"class":5619},[5280,17739,5835],{"class":5452},[5280,17741,5994],{"class":5424},[5280,17743,5997],{"class":5758},[5280,17745,5218],{"class":5452},[5280,17747,6002],{"class":5758},[5280,17749,5887],{"class":5452},[5280,17751,6007],{"class":5424},[5280,17753,6010],{"class":5452},[5280,17755,5952],{"class":5758},[5280,17757,5218],{"class":5452},[5280,17759,6017],{"class":5758},[5280,17761,6020],{"class":5452},[5280,17763,17764,17766,17768,17770,17772,17774,17776,17778,17780,17782,17784,17786,17788,17791],{"class":5282,"line":6409},[5280,17765,6162],{"class":5424},[5280,17767,5218],{"class":5452},[5280,17769,6167],{"class":5293},[5280,17771,5835],{"class":5452},[5280,17773,5994],{"class":5424},[5280,17775,6174],{"class":5452},[5280,17777,6177],{"class":5293},[5280,17779,5835],{"class":5452},[5280,17781,5838],{"class":5619},[5280,17783,5841],{"class":5452},[5280,17785,5844],{"class":5758},[5280,17787,5847],{"class":5452},[5280,17789,17790],{"class":5619},"interface",[5280,17792,17793],{"class":5452},"{}{\n",[5280,17795,17796,17799,17801,17803],{"class":5282,"line":6415},[5280,17797,17798],{"class":5297},"            \"status\"",[5280,17800,13867],{"class":5452},[5280,17802,6793],{"class":5297},[5280,17804,6796],{"class":5452},[5280,17806,17807,17810,17812,17814,17816,17818,17820,17823],{"class":5282,"line":6420},[5280,17808,17809],{"class":5297},"            \"timestamp\"",[5280,17811,6790],{"class":5452},[5280,17813,5587],{"class":5424},[5280,17815,5218],{"class":5452},[5280,17817,6036],{"class":5293},[5280,17819,6141],{"class":5452},[5280,17821,17822],{"class":5293},"Unix",[5280,17824,17825],{"class":5452},"(),\n",[5280,17827,17828],{"class":5282,"line":6425},[5280,17829,12839],{"class":5452},[5280,17831,17832],{"class":5282,"line":6450},[5280,17833,6834],{"class":5452},[5280,17835,17836],{"class":5282,"line":6471},[5280,17837,6724],{"class":5452},[5280,17839,17840],{"class":5282,"line":6507},[5280,17841,16509],{"class":5286},[5280,17843,17844,17846,17848,17851,17853,17856,17858,17861],{"class":5282,"line":6512},[5280,17845,16443],{"class":5424},[5280,17847,5218],{"class":5452},[5280,17849,17850],{"class":5293},"Post",[5280,17852,5835],{"class":5452},[5280,17854,17855],{"class":5297},"\"\u002Fapi\u002Fregister\"",[5280,17857,5887],{"class":5452},[5280,17859,17860],{"class":5424},"registerHandler",[5280,17862,5598],{"class":5452},[5280,17864,17865,17867,17869,17871,17873,17876,17878,17881],{"class":5282,"line":6520},[5280,17866,16443],{"class":5424},[5280,17868,5218],{"class":5452},[5280,17870,17850],{"class":5293},[5280,17872,5835],{"class":5452},[5280,17874,17875],{"class":5297},"\"\u002Fapi\u002Flogin\"",[5280,17877,5887],{"class":5452},[5280,17879,17880],{"class":5424},"loginHandler",[5280,17882,5598],{"class":5452},[5280,17884,17885],{"class":5282,"line":6550},[5280,17886,6724],{"class":5452},[5280,17888,17889],{"class":5282,"line":6556},[5280,17890,17891],{"class":5286},"    \u002F\u002F Захищені маршрути\n",[5280,17893,17894,17896,17898,17900,17902,17904,17906,17908,17910,17912,17914],{"class":5282,"line":6561},[5280,17895,16443],{"class":5424},[5280,17897,5218],{"class":5452},[5280,17899,16617],{"class":5293},[5280,17901,5835],{"class":5452},[5280,17903,5862],{"class":5619},[5280,17905,5835],{"class":5452},[5280,17907,6007],{"class":5424},[5280,17909,16628],{"class":5758},[5280,17911,5218],{"class":5452},[5280,17913,16633],{"class":5758},[5280,17915,6020],{"class":5452},[5280,17917,17918,17920,17922,17924,17926,17928,17930,17932],{"class":5282,"line":6621},[5280,17919,16640],{"class":5424},[5280,17921,5218],{"class":5452},[5280,17923,16472],{"class":5293},[5280,17925,5835],{"class":5452},[5280,17927,8768],{"class":5424},[5280,17929,5218],{"class":5452},[5280,17931,792],{"class":5424},[5280,17933,5598],{"class":5452},[5280,17935,17936],{"class":5282,"line":6626},[5280,17937,6190],{"class":5452},[5280,17939,17940,17942,17944,17946,17948,17951,17953,17956],{"class":5282,"line":6631},[5280,17941,16640],{"class":5424},[5280,17943,5218],{"class":5452},[5280,17945,12252],{"class":5293},[5280,17947,5835],{"class":5452},[5280,17949,17950],{"class":5297},"\"\u002Fapi\u002Fusers\u002Fme\"",[5280,17952,5887],{"class":5452},[5280,17954,17955],{"class":5424},"getCurrentUserHandler",[5280,17957,5598],{"class":5452},[5280,17959,17960,17962,17964,17967,17969,17971,17973,17976],{"class":5282,"line":6663},[5280,17961,16640],{"class":5424},[5280,17963,5218],{"class":5452},[5280,17965,17966],{"class":5293},"Put",[5280,17968,5835],{"class":5452},[5280,17970,17950],{"class":5297},[5280,17972,5887],{"class":5452},[5280,17974,17975],{"class":5424},"updateCurrentUserHandler",[5280,17977,5598],{"class":5452},[5280,17979,17980],{"class":5282,"line":6678},[5280,17981,6834],{"class":5452},[5280,17983,17984],{"class":5282,"line":6721},[5280,17985,6724],{"class":5452},[5280,17987,17988],{"class":5282,"line":6727},[5280,17989,17990],{"class":5286},"    \u002F\u002F Адмінські маршрути з rate limiting\n",[5280,17992,17993,17995,17997,17999,18001,18003,18005,18007,18009,18011,18013],{"class":5282,"line":6751},[5280,17994,16443],{"class":5424},[5280,17996,5218],{"class":5452},[5280,17998,16617],{"class":5293},[5280,18000,5835],{"class":5452},[5280,18002,5862],{"class":5619},[5280,18004,5835],{"class":5452},[5280,18006,6007],{"class":5424},[5280,18008,16628],{"class":5758},[5280,18010,5218],{"class":5452},[5280,18012,16633],{"class":5758},[5280,18014,6020],{"class":5452},[5280,18016,18017,18019,18021,18023,18025,18027,18029,18031],{"class":5282,"line":6784},[5280,18018,16640],{"class":5424},[5280,18020,5218],{"class":5452},[5280,18022,16472],{"class":5293},[5280,18024,5835],{"class":5452},[5280,18026,8768],{"class":5424},[5280,18028,5218],{"class":5452},[5280,18030,792],{"class":5424},[5280,18032,5598],{"class":5452},[5280,18034,18035,18037,18039,18041,18043,18045,18047,18049,18051,18054],{"class":5282,"line":6799},[5280,18036,14595],{"class":5424},[5280,18038,6029],{"class":5452},[5280,18040,8768],{"class":5424},[5280,18042,5218],{"class":5452},[5280,18044,14526],{"class":5293},[5280,18046,5835],{"class":5452},[5280,18048,14535],{"class":5439},[5280,18050,5887],{"class":5452},[5280,18052,18053],{"class":5439},"10",[5280,18055,5598],{"class":5452},[5280,18057,18058,18060,18062,18064,18066,18068,18070,18072],{"class":5282,"line":6831},[5280,18059,16640],{"class":5424},[5280,18061,5218],{"class":5452},[5280,18063,16472],{"class":5293},[5280,18065,5835],{"class":5452},[5280,18067,17021],{"class":5424},[5280,18069,5218],{"class":5452},[5280,18071,7633],{"class":5424},[5280,18073,5598],{"class":5452},[5280,18075,18076],{"class":5282,"line":6837},[5280,18077,6190],{"class":5452},[5280,18079,18080,18082,18084,18086,18088,18090,18092,18095],{"class":5282,"line":6842},[5280,18081,16640],{"class":5424},[5280,18083,5218],{"class":5452},[5280,18085,12252],{"class":5293},[5280,18087,5835],{"class":5452},[5280,18089,17044],{"class":5297},[5280,18091,5887],{"class":5452},[5280,18093,18094],{"class":5424},"listUsersHandler",[5280,18096,5598],{"class":5452},[5280,18098,18099,18101,18103,18105,18107,18110,18112,18115],{"class":5282,"line":6901},[5280,18100,16640],{"class":5424},[5280,18102,5218],{"class":5452},[5280,18104,12252],{"class":5293},[5280,18106,5835],{"class":5452},[5280,18108,18109],{"class":5297},"\"\u002Fapi\u002Fadmin\u002Fstats\"",[5280,18111,5887],{"class":5452},[5280,18113,18114],{"class":5424},"getStatsHandler",[5280,18116,5598],{"class":5452},[5280,18118,18119],{"class":5282,"line":12702},[5280,18120,6834],{"class":5452},[5280,18122,18123],{"class":5282,"line":12731},[5280,18124,6724],{"class":5452},[5280,18126,18127,18129,18131,18133,18135,18138],{"class":5282,"line":12736},[5280,18128,5924],{"class":5424},[5280,18130,5218],{"class":5452},[5280,18132,5929],{"class":5293},[5280,18134,5835],{"class":5452},[5280,18136,18137],{"class":5297},"\"╔════════════════════════════════════════╗\"",[5280,18139,5598],{"class":5452},[5280,18141,18142,18144,18146,18148,18150,18153],{"class":5282,"line":12741},[5280,18143,5924],{"class":5424},[5280,18145,5218],{"class":5452},[5280,18147,5929],{"class":5293},[5280,18149,5835],{"class":5452},[5280,18151,18152],{"class":5297},"\"║  Full-featured server on :8080 🚀      ║\"",[5280,18154,5598],{"class":5452},[5280,18156,18157,18159,18161,18163,18165,18168],{"class":5282,"line":12746},[5280,18158,5924],{"class":5424},[5280,18160,5218],{"class":5452},[5280,18162,5929],{"class":5293},[5280,18164,5835],{"class":5452},[5280,18166,18167],{"class":5297},"\"╠════════════════════════════════════════╣\"",[5280,18169,5598],{"class":5452},[5280,18171,18172,18174,18176,18178,18180,18183],{"class":5282,"line":12752},[5280,18173,5924],{"class":5424},[5280,18175,5218],{"class":5452},[5280,18177,5929],{"class":5293},[5280,18179,5835],{"class":5452},[5280,18181,18182],{"class":5297},"\"║  Endpoints:                            ║\"",[5280,18184,5598],{"class":5452},[5280,18186,18187,18189,18191,18193,18195,18198],{"class":5282,"line":12774},[5280,18188,5924],{"class":5424},[5280,18190,5218],{"class":5452},[5280,18192,5929],{"class":5293},[5280,18194,5835],{"class":5452},[5280,18196,18197],{"class":5297},"\"║   GET  \u002Fhealth                         ║\"",[5280,18199,5598],{"class":5452},[5280,18201,18202,18204,18206,18208,18210,18213],{"class":5282,"line":12794},[5280,18203,5924],{"class":5424},[5280,18205,5218],{"class":5452},[5280,18207,5929],{"class":5293},[5280,18209,5835],{"class":5452},[5280,18211,18212],{"class":5297},"\"║   POST \u002Fapi\u002Fregister                   ║\"",[5280,18214,5598],{"class":5452},[5280,18216,18217,18219,18221,18223,18225,18228],{"class":5282,"line":12800},[5280,18218,5924],{"class":5424},[5280,18220,5218],{"class":5452},[5280,18222,5929],{"class":5293},[5280,18224,5835],{"class":5452},[5280,18226,18227],{"class":5297},"\"║   POST \u002Fapi\u002Flogin                      ║\"",[5280,18229,5598],{"class":5452},[5280,18231,18232,18234,18236,18238,18240,18243],{"class":5282,"line":12805},[5280,18233,5924],{"class":5424},[5280,18235,5218],{"class":5452},[5280,18237,5929],{"class":5293},[5280,18239,5835],{"class":5452},[5280,18241,18242],{"class":5297},"\"║   GET  \u002Fapi\u002Fusers\u002Fme  (auth)           ║\"",[5280,18244,5598],{"class":5452},[5280,18246,18247,18249,18251,18253,18255,18258],{"class":5282,"line":12810},[5280,18248,5924],{"class":5424},[5280,18250,5218],{"class":5452},[5280,18252,5929],{"class":5293},[5280,18254,5835],{"class":5452},[5280,18256,18257],{"class":5297},"\"║   PUT  \u002Fapi\u002Fusers\u002Fme  (auth)           ║\"",[5280,18259,5598],{"class":5452},[5280,18261,18262,18264,18266,18268,18270,18273],{"class":5282,"line":12816},[5280,18263,5924],{"class":5424},[5280,18265,5218],{"class":5452},[5280,18267,5929],{"class":5293},[5280,18269,5835],{"class":5452},[5280,18271,18272],{"class":5297},"\"║   GET  \u002Fapi\u002Fadmin\u002Fusers (auth+limit)   ║\"",[5280,18274,5598],{"class":5452},[5280,18276,18277,18279,18281,18283,18285,18288],{"class":5282,"line":12836},[5280,18278,5924],{"class":5424},[5280,18280,5218],{"class":5452},[5280,18282,5929],{"class":5293},[5280,18284,5835],{"class":5452},[5280,18286,18287],{"class":5297},"\"║   GET  \u002Fapi\u002Fadmin\u002Fstats (auth+limit)   ║\"",[5280,18289,5598],{"class":5452},[5280,18291,18292,18294,18296,18298,18300,18303],{"class":5282,"line":12842},[5280,18293,5924],{"class":5424},[5280,18295,5218],{"class":5452},[5280,18297,5929],{"class":5293},[5280,18299,5835],{"class":5452},[5280,18301,18302],{"class":5297},"\"╚════════════════════════════════════════╝\"",[5280,18304,5598],{"class":5452},[5280,18306,18307],{"class":5282,"line":12847},[5280,18308,6724],{"class":5452},[5280,18310,18312,18314,18316,18318,18320,18322,18324,18326,18328,18330,18332,18334],{"class":5282,"line":18311},85,[5280,18313,5942],{"class":5424},[5280,18315,5218],{"class":5452},[5280,18317,5947],{"class":5293},[5280,18319,5835],{"class":5452},[5280,18321,5952],{"class":5424},[5280,18323,5218],{"class":5452},[5280,18325,5957],{"class":5293},[5280,18327,5835],{"class":5452},[5280,18329,5962],{"class":5297},[5280,18331,5887],{"class":5452},[5280,18333,6007],{"class":5424},[5280,18335,5970],{"class":5452},[5280,18337,18339],{"class":5282,"line":18338},86,[5280,18340,5976],{"class":5452},[5280,18342,18344],{"class":5282,"line":18343},87,[5280,18345,5313],{"emptyLinePlaceholder":5312},[5280,18347,18349,18351,18354,18356,18358,18360,18362,18364,18366,18368,18370,18372,18374,18376],{"class":5282,"line":18348},88,[5280,18350,5862],{"class":5619},[5280,18352,18353],{"class":5293}," registerHandler",[5280,18355,5835],{"class":5452},[5280,18357,5994],{"class":5424},[5280,18359,5997],{"class":5758},[5280,18361,5218],{"class":5452},[5280,18363,6002],{"class":5758},[5280,18365,5887],{"class":5452},[5280,18367,6007],{"class":5424},[5280,18369,6010],{"class":5452},[5280,18371,5952],{"class":5758},[5280,18373,5218],{"class":5452},[5280,18375,6017],{"class":5758},[5280,18377,6020],{"class":5452},[5280,18379,18381,18384,18387,18389],{"class":5282,"line":18380},89,[5280,18382,18383],{"class":5619},"    var",[5280,18385,18386],{"class":5424}," req",[5280,18388,6216],{"class":5619},[5280,18390,6111],{"class":5452},[5280,18392,18394,18396,18398],{"class":5282,"line":18393},90,[5280,18395,17540],{"class":5424},[5280,18397,17464],{"class":5758},[5280,18399,6230],{"class":5297},[5280,18401,18403,18405,18408],{"class":5282,"line":18402},91,[5280,18404,17553],{"class":5424},[5280,18406,18407],{"class":5758},"    string",[5280,18409,6242],{"class":5297},[5280,18411,18413,18416,18418],{"class":5282,"line":18412},92,[5280,18414,18415],{"class":5424},"        Password",[5280,18417,6239],{"class":5758},[5280,18419,18420],{"class":5297}," `json:\"password\"`\n",[5280,18422,18424],{"class":5282,"line":18423},93,[5280,18425,6553],{"class":5452},[5280,18427,18429],{"class":5282,"line":18428},94,[5280,18430,6724],{"class":5452},[5280,18432,18434,18436,18438,18440,18442,18444,18446,18448,18450,18452,18454,18456,18458,18460,18463,18465,18467,18469,18471],{"class":5282,"line":18433},95,[5280,18435,7023],{"class":5436},[5280,18437,6262],{"class":5424},[5280,18439,6029],{"class":5452},[5280,18441,6267],{"class":5424},[5280,18443,5218],{"class":5452},[5280,18445,6272],{"class":5293},[5280,18447,5835],{"class":5452},[5280,18449,6007],{"class":5424},[5280,18451,5218],{"class":5452},[5280,18453,6281],{"class":5424},[5280,18455,6174],{"class":5452},[5280,18457,6286],{"class":5293},[5280,18459,6289],{"class":5452},[5280,18461,18462],{"class":5424},"req",[5280,18464,6295],{"class":5452},[5280,18466,6298],{"class":5424},[5280,18468,6301],{"class":5452},[5280,18470,5967],{"class":5619},[5280,18472,6111],{"class":5452},[5280,18474,18476,18478,18480,18482,18484,18486,18488,18490,18492,18494,18496,18498],{"class":5282,"line":18475},96,[5280,18477,6523],{"class":5424},[5280,18479,5218],{"class":5452},[5280,18481,6316],{"class":5293},[5280,18483,5835],{"class":5452},[5280,18485,5994],{"class":5424},[5280,18487,5887],{"class":5452},[5280,18489,6325],{"class":5297},[5280,18491,5887],{"class":5452},[5280,18493,5952],{"class":5424},[5280,18495,5218],{"class":5452},[5280,18497,6334],{"class":5424},[5280,18499,5598],{"class":5452},[5280,18501,18503],{"class":5282,"line":18502},97,[5280,18504,15875],{"class":5436},[5280,18506,18508],{"class":5282,"line":18507},98,[5280,18509,6553],{"class":5452},[5280,18511,18513],{"class":5282,"line":18512},99,[5280,18514,6724],{"class":5452},[5280,18516,18518],{"class":5282,"line":18517},100,[5280,18519,18520],{"class":5286},"    \u002F\u002F Тут має бути хешування пароля і збереження в БД\n",[5280,18522,18524,18527,18529,18531],{"class":5282,"line":18523},101,[5280,18525,18526],{"class":5424},"    newUser",[5280,18528,9745],{"class":5452},[5280,18530,17513],{"class":5758},[5280,18532,6781],{"class":5452},[5280,18534,18536,18538,18540,18542,18544,18546,18548,18551,18553,18555,18557,18559,18561,18563,18566,18568],{"class":5282,"line":18535},102,[5280,18537,17528],{"class":5424},[5280,18539,9788],{"class":5452},[5280,18541,15518],{"class":5424},[5280,18543,5218],{"class":5452},[5280,18545,12710],{"class":5293},[5280,18547,5835],{"class":5452},[5280,18549,18550],{"class":5297},"\"user-",[5280,18552,9946],{"class":5424},[5280,18554,6584],{"class":5297},[5280,18556,5887],{"class":5452},[5280,18558,11391],{"class":5293},[5280,18560,5835],{"class":5452},[5280,18562,6182],{"class":5424},[5280,18564,18565],{"class":5452},")+",[5280,18567,11401],{"class":5439},[5280,18569,6828],{"class":5452},[5280,18571,18573,18575,18577,18579,18581,18583],{"class":5282,"line":18572},103,[5280,18574,17540],{"class":5424},[5280,18576,17543],{"class":5452},[5280,18578,18462],{"class":5424},[5280,18580,5218],{"class":5452},[5280,18582,6502],{"class":5424},[5280,18584,6796],{"class":5452},[5280,18586,18588,18590,18592,18594,18596,18598],{"class":5282,"line":18587},104,[5280,18589,17553],{"class":5424},[5280,18591,9768],{"class":5452},[5280,18593,18462],{"class":5424},[5280,18595,5218],{"class":5452},[5280,18597,6437],{"class":5424},[5280,18599,6796],{"class":5452},[5280,18601,18603,18605,18607,18609,18611,18613],{"class":5282,"line":18602},105,[5280,18604,17565],{"class":5424},[5280,18606,6790],{"class":5452},[5280,18608,5587],{"class":5424},[5280,18610,5218],{"class":5452},[5280,18612,6036],{"class":5293},[5280,18614,17825],{"class":5452},[5280,18616,18618],{"class":5282,"line":18617},106,[5280,18619,6553],{"class":5452},[5280,18621,18623,18626,18628,18631,18633,18636,18638],{"class":5282,"line":18622},107,[5280,18624,18625],{"class":5424},"    users",[5280,18627,5841],{"class":5452},[5280,18629,18630],{"class":5424},"newUser",[5280,18632,5218],{"class":5452},[5280,18634,18635],{"class":5424},"ID",[5280,18637,6440],{"class":5452},[5280,18639,18640],{"class":5424},"newUser\n",[5280,18642,18644],{"class":5282,"line":18643},108,[5280,18645,6724],{"class":5452},[5280,18647,18649,18651,18653,18655,18657,18659,18661,18663],{"class":5282,"line":18648},109,[5280,18650,6730],{"class":5424},[5280,18652,5218],{"class":5452},[5280,18654,6457],{"class":5293},[5280,18656,5835],{"class":5452},[5280,18658,5952],{"class":5424},[5280,18660,5218],{"class":5452},[5280,18662,6466],{"class":5424},[5280,18664,5598],{"class":5452},[5280,18666,18668,18670,18672,18674,18676,18678,18680,18682,18684,18686],{"class":5282,"line":18667},110,[5280,18669,6754],{"class":5424},[5280,18671,5218],{"class":5452},[5280,18673,6167],{"class":5293},[5280,18675,5835],{"class":5452},[5280,18677,5994],{"class":5424},[5280,18679,6174],{"class":5452},[5280,18681,6177],{"class":5293},[5280,18683,5835],{"class":5452},[5280,18685,18630],{"class":5424},[5280,18687,5598],{"class":5452},[5280,18689,18691],{"class":5282,"line":18690},111,[5280,18692,5976],{"class":5452},[5280,18694,18696],{"class":5282,"line":18695},112,[5280,18697,5313],{"emptyLinePlaceholder":5312},[5280,18699,18701,18703,18706,18708,18710,18712,18714,18716,18718,18720,18722,18724,18726,18728],{"class":5282,"line":18700},113,[5280,18702,5862],{"class":5619},[5280,18704,18705],{"class":5293}," loginHandler",[5280,18707,5835],{"class":5452},[5280,18709,5994],{"class":5424},[5280,18711,5997],{"class":5758},[5280,18713,5218],{"class":5452},[5280,18715,6002],{"class":5758},[5280,18717,5887],{"class":5452},[5280,18719,6007],{"class":5424},[5280,18721,6010],{"class":5452},[5280,18723,5952],{"class":5758},[5280,18725,5218],{"class":5452},[5280,18727,6017],{"class":5758},[5280,18729,6020],{"class":5452},[5280,18731,18733],{"class":5282,"line":18732},114,[5280,18734,18735],{"class":5286},"    \u002F\u002F Спрощена реалізація (у реальності — перевірка паролю, генерація JWT)\n",[5280,18737,18739,18741,18743,18745,18747,18749,18751,18753,18755,18757,18759,18761,18763,18765],{"class":5282,"line":18738},115,[5280,18740,6754],{"class":5424},[5280,18742,5218],{"class":5452},[5280,18744,6167],{"class":5293},[5280,18746,5835],{"class":5452},[5280,18748,5994],{"class":5424},[5280,18750,6174],{"class":5452},[5280,18752,6177],{"class":5293},[5280,18754,5835],{"class":5452},[5280,18756,5838],{"class":5619},[5280,18758,5841],{"class":5452},[5280,18760,5844],{"class":5758},[5280,18762,5847],{"class":5452},[5280,18764,5844],{"class":5758},[5280,18766,6781],{"class":5452},[5280,18768,18770,18773,18775,18778,18780],{"class":5282,"line":18769},116,[5280,18771,18772],{"class":5297},"        \"token\"",[5280,18774,6790],{"class":5452},[5280,18776,18777],{"class":5297},"\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\"",[5280,18779,5887],{"class":5452},[5280,18781,18782],{"class":5286},"\u002F\u002F Заглушка\n",[5280,18784,18786],{"class":5282,"line":18785},117,[5280,18787,6834],{"class":5452},[5280,18789,18791],{"class":5282,"line":18790},118,[5280,18792,5976],{"class":5452},[5280,18794,18796],{"class":5282,"line":18795},119,[5280,18797,5313],{"emptyLinePlaceholder":5312},[5280,18799,18801,18803,18806,18808,18810,18812,18814,18816,18818,18820,18822,18824,18826,18828],{"class":5282,"line":18800},120,[5280,18802,5862],{"class":5619},[5280,18804,18805],{"class":5293}," getCurrentUserHandler",[5280,18807,5835],{"class":5452},[5280,18809,5994],{"class":5424},[5280,18811,5997],{"class":5758},[5280,18813,5218],{"class":5452},[5280,18815,6002],{"class":5758},[5280,18817,5887],{"class":5452},[5280,18819,6007],{"class":5424},[5280,18821,6010],{"class":5452},[5280,18823,5952],{"class":5758},[5280,18825,5218],{"class":5452},[5280,18827,6017],{"class":5758},[5280,18829,6020],{"class":5452},[5280,18831,18833,18835,18837,18839,18841,18843,18845,18847,18849,18851,18853,18855,18857,18859],{"class":5282,"line":18832},121,[5280,18834,15800],{"class":5424},[5280,18836,6029],{"class":5452},[5280,18838,6007],{"class":5424},[5280,18840,5218],{"class":5452},[5280,18842,15393],{"class":5293},[5280,18844,6141],{"class":5452},[5280,18846,15818],{"class":5293},[5280,18848,5835],{"class":5452},[5280,18850,8768],{"class":5424},[5280,18852,5218],{"class":5452},[5280,18854,15399],{"class":5424},[5280,18856,15829],{"class":5452},[5280,18858,5844],{"class":5758},[5280,18860,5598],{"class":5452},[5280,18862,18864],{"class":5282,"line":18863},122,[5280,18865,6724],{"class":5452},[5280,18867,18869,18872,18874,18876,18878,18880,18882,18884],{"class":5282,"line":18868},123,[5280,18870,18871],{"class":5424},"    user",[5280,18873,5887],{"class":5452},[5280,18875,13972],{"class":5424},[5280,18877,6029],{"class":5452},[5280,18879,6182],{"class":5424},[5280,18881,5841],{"class":5452},[5280,18883,15404],{"class":5424},[5280,18885,13989],{"class":5452},[5280,18887,18889,18891,18893,18895],{"class":5282,"line":18888},124,[5280,18890,7023],{"class":5436},[5280,18892,9455],{"class":5452},[5280,18894,13972],{"class":5424},[5280,18896,6111],{"class":5452},[5280,18898,18900,18902,18904,18906,18908,18910,18912,18915,18917,18919,18921,18924],{"class":5282,"line":18899},125,[5280,18901,6523],{"class":5424},[5280,18903,5218],{"class":5452},[5280,18905,6316],{"class":5293},[5280,18907,5835],{"class":5452},[5280,18909,5994],{"class":5424},[5280,18911,5887],{"class":5452},[5280,18913,18914],{"class":5297},"\"User not found\"",[5280,18916,5887],{"class":5452},[5280,18918,5952],{"class":5424},[5280,18920,5218],{"class":5452},[5280,18922,18923],{"class":5424},"StatusNotFound",[5280,18925,5598],{"class":5452},[5280,18927,18929],{"class":5282,"line":18928},126,[5280,18930,15875],{"class":5436},[5280,18932,18934],{"class":5282,"line":18933},127,[5280,18935,6553],{"class":5452},[5280,18937,18939],{"class":5282,"line":18938},128,[5280,18940,6724],{"class":5452},[5280,18942,18944,18946,18948,18950,18952,18954,18956,18958,18960,18962],{"class":5282,"line":18943},129,[5280,18945,6754],{"class":5424},[5280,18947,5218],{"class":5452},[5280,18949,6167],{"class":5293},[5280,18951,5835],{"class":5452},[5280,18953,5994],{"class":5424},[5280,18955,6174],{"class":5452},[5280,18957,6177],{"class":5293},[5280,18959,5835],{"class":5452},[5280,18961,6292],{"class":5424},[5280,18963,5598],{"class":5452},[5280,18965,18967],{"class":5282,"line":18966},130,[5280,18968,5976],{"class":5452},[5280,18970,18972],{"class":5282,"line":18971},131,[5280,18973,5313],{"emptyLinePlaceholder":5312},[5280,18975,18977,18979,18982,18984,18986,18988,18990,18992,18994,18996,18998,19000,19002,19004],{"class":5282,"line":18976},132,[5280,18978,5862],{"class":5619},[5280,18980,18981],{"class":5293}," updateCurrentUserHandler",[5280,18983,5835],{"class":5452},[5280,18985,5994],{"class":5424},[5280,18987,5997],{"class":5758},[5280,18989,5218],{"class":5452},[5280,18991,6002],{"class":5758},[5280,18993,5887],{"class":5452},[5280,18995,6007],{"class":5424},[5280,18997,6010],{"class":5452},[5280,18999,5952],{"class":5758},[5280,19001,5218],{"class":5452},[5280,19003,6017],{"class":5758},[5280,19005,6020],{"class":5452},[5280,19007,19009,19011,19013,19015,19017,19019,19021,19023,19025,19027,19029,19031,19033,19035],{"class":5282,"line":19008},133,[5280,19010,15800],{"class":5424},[5280,19012,6029],{"class":5452},[5280,19014,6007],{"class":5424},[5280,19016,5218],{"class":5452},[5280,19018,15393],{"class":5293},[5280,19020,6141],{"class":5452},[5280,19022,15818],{"class":5293},[5280,19024,5835],{"class":5452},[5280,19026,8768],{"class":5424},[5280,19028,5218],{"class":5452},[5280,19030,15399],{"class":5424},[5280,19032,15829],{"class":5452},[5280,19034,5844],{"class":5758},[5280,19036,5598],{"class":5452},[5280,19038,19040],{"class":5282,"line":19039},134,[5280,19041,6724],{"class":5452},[5280,19043,19045,19047,19050,19052],{"class":5282,"line":19044},135,[5280,19046,18383],{"class":5619},[5280,19048,19049],{"class":5424}," updates",[5280,19051,6216],{"class":5619},[5280,19053,6111],{"class":5452},[5280,19055,19057,19059,19061],{"class":5282,"line":19056},136,[5280,19058,17540],{"class":5424},[5280,19060,6239],{"class":5758},[5280,19062,6230],{"class":5297},[5280,19064,19066],{"class":5282,"line":19065},137,[5280,19067,6553],{"class":5452},[5280,19069,19071,19073,19075,19077,19079,19081,19083,19085,19087,19089,19091,19093,19095,19097,19100,19102,19104,19106,19108],{"class":5282,"line":19070},138,[5280,19072,7023],{"class":5436},[5280,19074,6262],{"class":5424},[5280,19076,6029],{"class":5452},[5280,19078,6267],{"class":5424},[5280,19080,5218],{"class":5452},[5280,19082,6272],{"class":5293},[5280,19084,5835],{"class":5452},[5280,19086,6007],{"class":5424},[5280,19088,5218],{"class":5452},[5280,19090,6281],{"class":5424},[5280,19092,6174],{"class":5452},[5280,19094,6286],{"class":5293},[5280,19096,6289],{"class":5452},[5280,19098,19099],{"class":5424},"updates",[5280,19101,6295],{"class":5452},[5280,19103,6298],{"class":5424},[5280,19105,6301],{"class":5452},[5280,19107,5967],{"class":5619},[5280,19109,6111],{"class":5452},[5280,19111,19113,19115,19117,19119,19121,19123,19125,19127,19129,19131,19133,19135],{"class":5282,"line":19112},139,[5280,19114,6523],{"class":5424},[5280,19116,5218],{"class":5452},[5280,19118,6316],{"class":5293},[5280,19120,5835],{"class":5452},[5280,19122,5994],{"class":5424},[5280,19124,5887],{"class":5452},[5280,19126,6325],{"class":5297},[5280,19128,5887],{"class":5452},[5280,19130,5952],{"class":5424},[5280,19132,5218],{"class":5452},[5280,19134,6334],{"class":5424},[5280,19136,5598],{"class":5452},[5280,19138,19140],{"class":5282,"line":19139},140,[5280,19141,15875],{"class":5436},[5280,19143,19145],{"class":5282,"line":19144},141,[5280,19146,6553],{"class":5452},[5280,19148,19150],{"class":5282,"line":19149},142,[5280,19151,6724],{"class":5452},[5280,19153,19155,19157,19159,19161,19163,19165,19167,19169],{"class":5282,"line":19154},143,[5280,19156,18871],{"class":5424},[5280,19158,5887],{"class":5452},[5280,19160,13972],{"class":5424},[5280,19162,6029],{"class":5452},[5280,19164,6182],{"class":5424},[5280,19166,5841],{"class":5452},[5280,19168,15404],{"class":5424},[5280,19170,13989],{"class":5452},[5280,19172,19174,19176,19178,19180],{"class":5282,"line":19173},144,[5280,19175,7023],{"class":5436},[5280,19177,9455],{"class":5452},[5280,19179,13972],{"class":5424},[5280,19181,6111],{"class":5452},[5280,19183,19185,19187,19189,19191,19193,19195,19197,19199,19201,19203,19205,19207],{"class":5282,"line":19184},145,[5280,19186,6523],{"class":5424},[5280,19188,5218],{"class":5452},[5280,19190,6316],{"class":5293},[5280,19192,5835],{"class":5452},[5280,19194,5994],{"class":5424},[5280,19196,5887],{"class":5452},[5280,19198,18914],{"class":5297},[5280,19200,5887],{"class":5452},[5280,19202,5952],{"class":5424},[5280,19204,5218],{"class":5452},[5280,19206,18923],{"class":5424},[5280,19208,5598],{"class":5452},[5280,19210,19212],{"class":5282,"line":19211},146,[5280,19213,15875],{"class":5436},[5280,19215,19217],{"class":5282,"line":19216},147,[5280,19218,6553],{"class":5452},[5280,19220,19222],{"class":5282,"line":19221},148,[5280,19223,6724],{"class":5452},[5280,19225,19227,19229,19231,19233,19235,19237,19239],{"class":5282,"line":19226},149,[5280,19228,18871],{"class":5424},[5280,19230,5218],{"class":5452},[5280,19232,6502],{"class":5424},[5280,19234,5829],{"class":5452},[5280,19236,19099],{"class":5424},[5280,19238,5218],{"class":5452},[5280,19240,6447],{"class":5424},[5280,19242,19244,19246,19248,19250,19252,19254,19256,19258,19260,19262],{"class":5282,"line":19243},150,[5280,19245,6754],{"class":5424},[5280,19247,5218],{"class":5452},[5280,19249,6167],{"class":5293},[5280,19251,5835],{"class":5452},[5280,19253,5994],{"class":5424},[5280,19255,6174],{"class":5452},[5280,19257,6177],{"class":5293},[5280,19259,5835],{"class":5452},[5280,19261,6292],{"class":5424},[5280,19263,5598],{"class":5452},[5280,19265,19267],{"class":5282,"line":19266},151,[5280,19268,5976],{"class":5452},[5280,19270,19272],{"class":5282,"line":19271},152,[5280,19273,5313],{"emptyLinePlaceholder":5312},[5280,19275,19277,19279,19282,19284,19286,19288,19290,19292,19294,19296,19298,19300,19302,19304],{"class":5282,"line":19276},153,[5280,19278,5862],{"class":5619},[5280,19280,19281],{"class":5293}," listUsersHandler",[5280,19283,5835],{"class":5452},[5280,19285,5994],{"class":5424},[5280,19287,5997],{"class":5758},[5280,19289,5218],{"class":5452},[5280,19291,6002],{"class":5758},[5280,19293,5887],{"class":5452},[5280,19295,6007],{"class":5424},[5280,19297,6010],{"class":5452},[5280,19299,5952],{"class":5758},[5280,19301,5218],{"class":5452},[5280,19303,6017],{"class":5758},[5280,19305,6020],{"class":5452},[5280,19307,19309,19312,19314,19316,19319,19321,19323,19325,19327,19329,19331,19333],{"class":5282,"line":19308},154,[5280,19310,19311],{"class":5424},"    allUsers",[5280,19313,6029],{"class":5452},[5280,19315,5832],{"class":5293},[5280,19317,19318],{"class":5452},"([]*",[5280,19320,17513],{"class":5758},[5280,19322,5887],{"class":5452},[5280,19324,11413],{"class":5439},[5280,19326,5887],{"class":5452},[5280,19328,11391],{"class":5293},[5280,19330,5835],{"class":5452},[5280,19332,6182],{"class":5424},[5280,19334,5970],{"class":5452},[5280,19336,19338,19340,19342,19344,19347,19349,19351,19353],{"class":5282,"line":19337},155,[5280,19339,11383],{"class":5436},[5280,19341,12286],{"class":5424},[5280,19343,5887],{"class":5452},[5280,19345,19346],{"class":5424},"u",[5280,19348,6029],{"class":5452},[5280,19350,12296],{"class":5436},[5280,19352,5826],{"class":5424},[5280,19354,6111],{"class":5452},[5280,19356,19358,19361,19363,19366,19368,19371,19373,19375],{"class":5282,"line":19357},156,[5280,19359,19360],{"class":5424},"        allUsers",[5280,19362,5829],{"class":5452},[5280,19364,19365],{"class":5293},"append",[5280,19367,5835],{"class":5452},[5280,19369,19370],{"class":5424},"allUsers",[5280,19372,5887],{"class":5452},[5280,19374,19346],{"class":5424},[5280,19376,5598],{"class":5452},[5280,19378,19380],{"class":5282,"line":19379},157,[5280,19381,6553],{"class":5452},[5280,19383,19385,19387,19389,19391,19393,19395,19397,19399,19401,19403],{"class":5282,"line":19384},158,[5280,19386,6754],{"class":5424},[5280,19388,5218],{"class":5452},[5280,19390,6167],{"class":5293},[5280,19392,5835],{"class":5452},[5280,19394,5994],{"class":5424},[5280,19396,6174],{"class":5452},[5280,19398,6177],{"class":5293},[5280,19400,5835],{"class":5452},[5280,19402,19370],{"class":5424},[5280,19404,5598],{"class":5452},[5280,19406,19408],{"class":5282,"line":19407},159,[5280,19409,5976],{"class":5452},[5280,19411,19413],{"class":5282,"line":19412},160,[5280,19414,5313],{"emptyLinePlaceholder":5312},[5280,19416,19418,19420,19423,19425,19427,19429,19431,19433,19435,19437,19439,19441,19443,19445],{"class":5282,"line":19417},161,[5280,19419,5862],{"class":5619},[5280,19421,19422],{"class":5293}," getStatsHandler",[5280,19424,5835],{"class":5452},[5280,19426,5994],{"class":5424},[5280,19428,5997],{"class":5758},[5280,19430,5218],{"class":5452},[5280,19432,6002],{"class":5758},[5280,19434,5887],{"class":5452},[5280,19436,6007],{"class":5424},[5280,19438,6010],{"class":5452},[5280,19440,5952],{"class":5758},[5280,19442,5218],{"class":5452},[5280,19444,6017],{"class":5758},[5280,19446,6020],{"class":5452},[5280,19448,19450,19452,19454,19456,19458,19460,19462,19464,19466,19468,19470,19472,19474,19476],{"class":5282,"line":19449},162,[5280,19451,6754],{"class":5424},[5280,19453,5218],{"class":5452},[5280,19455,6167],{"class":5293},[5280,19457,5835],{"class":5452},[5280,19459,5994],{"class":5424},[5280,19461,6174],{"class":5452},[5280,19463,6177],{"class":5293},[5280,19465,5835],{"class":5452},[5280,19467,5838],{"class":5619},[5280,19469,5841],{"class":5452},[5280,19471,5844],{"class":5758},[5280,19473,5847],{"class":5452},[5280,19475,17790],{"class":5619},[5280,19477,17793],{"class":5452},[5280,19479,19481,19484,19486,19488,19490,19492],{"class":5282,"line":19480},163,[5280,19482,19483],{"class":5297},"        \"total_users\"",[5280,19485,9768],{"class":5452},[5280,19487,11391],{"class":5293},[5280,19489,5835],{"class":5452},[5280,19491,6182],{"class":5424},[5280,19493,6828],{"class":5452},[5280,19495,19497,19500,19502,19505,19507],{"class":5282,"line":19496},164,[5280,19498,19499],{"class":5297},"        \"server_uptime\"",[5280,19501,6805],{"class":5452},[5280,19503,19504],{"class":5297},"\"24h\"",[5280,19506,5887],{"class":5452},[5280,19508,18782],{"class":5286},[5280,19510,19512,19515,19518,19521,19524],{"class":5282,"line":19511},165,[5280,19513,19514],{"class":5297},"        \"requests_today\"",[5280,19516,19517],{"class":5452},":  ",[5280,19519,19520],{"class":5439},"12543",[5280,19522,19523],{"class":5452},",  ",[5280,19525,18782],{"class":5286},[5280,19527,19529],{"class":5282,"line":19528},166,[5280,19530,6834],{"class":5452},[5280,19532,19534],{"class":5282,"line":19533},167,[5280,19535,5976],{"class":5452},[5262,19537,19538],{},"Цей сервер демонструє всі аспекти Middleware, які ми вивчили: логування, recovery, CORS, автентифікацію, rate limiting та групування маршрутів.",[5254,19540],{},[5257,19542,19544],{"id":19543},"від-монолітного-коду-до-clean-architecture","Від монолітного коду до Clean Architecture",[5266,19546,19548],{"id":19547},"проблема-бізнес-логіка-у-http-обробниках","Проблема: бізнес-логіка у HTTP-обробниках",[5262,19550,19551],{},"Повернемось до нашого прикладу сервера. Зараз вся логіка знаходиться безпосередньо в обробниках:",[5271,19553,19555],{"className":5411,"code":19554,"language":5325,"meta":5276,"style":5276},"func registerHandler(w http.ResponseWriter, r *http.Request) {\n    \u002F\u002F 1. Парсинг JSON\n    var req struct {\n        Name     string `json:\"name\"`\n        Email    string `json:\"email\"`\n        Password string `json:\"password\"`\n    }\n    json.NewDecoder(r.Body).Decode(&req)\n    \n    \u002F\u002F 2. Валідація\n    if req.Email == \"\" || req.Password == \"\" {\n        http.Error(w, \"Invalid input\", http.StatusBadRequest)\n        return\n    }\n    \n    \u002F\u002F 3. Перевірка існування користувача\n    for _, u := range users {\n        if u.Email == req.Email {\n            http.Error(w, \"Email already exists\", http.StatusConflict)\n            return\n        }\n    }\n    \n    \u002F\u002F 4. Хешування пароля\n    hashedPassword := hashPassword(req.Password)\n    \n    \u002F\u002F 5. Створення користувача\n    newUser := &User{\n        ID:       generateID(),\n        Name:     req.Name,\n        Email:    req.Email,\n        Password: hashedPassword,\n    }\n    users[newUser.ID] = newUser\n    \n    \u002F\u002F 6. Формування відповіді\n    json.NewEncoder(w).Encode(newUser)\n}\n",[5215,19556,19557,19587,19592,19602,19610,19618,19626,19630,19656,19660,19665,19694,19721,19725,19729,19733,19738,19756,19777,19805,19809,19813,19817,19821,19826,19846,19850,19855,19865,19877,19891,19905,19916,19920,19936,19940,19945,19967],{"__ignoreMap":5276},[5280,19558,19559,19561,19563,19565,19567,19569,19571,19573,19575,19577,19579,19581,19583,19585],{"class":5282,"line":5283},[5280,19560,5862],{"class":5619},[5280,19562,18353],{"class":5293},[5280,19564,5835],{"class":5452},[5280,19566,5994],{"class":5424},[5280,19568,5997],{"class":5758},[5280,19570,5218],{"class":5452},[5280,19572,6002],{"class":5758},[5280,19574,5887],{"class":5452},[5280,19576,6007],{"class":5424},[5280,19578,6010],{"class":5452},[5280,19580,5952],{"class":5758},[5280,19582,5218],{"class":5452},[5280,19584,6017],{"class":5758},[5280,19586,6020],{"class":5452},[5280,19588,19589],{"class":5282,"line":5290},[5280,19590,19591],{"class":5286},"    \u002F\u002F 1. Парсинг JSON\n",[5280,19593,19594,19596,19598,19600],{"class":5282,"line":5301},[5280,19595,18383],{"class":5619},[5280,19597,18386],{"class":5424},[5280,19599,6216],{"class":5619},[5280,19601,6111],{"class":5452},[5280,19603,19604,19606,19608],{"class":5282,"line":5309},[5280,19605,17540],{"class":5424},[5280,19607,17464],{"class":5758},[5280,19609,6230],{"class":5297},[5280,19611,19612,19614,19616],{"class":5282,"line":5316},[5280,19613,17553],{"class":5424},[5280,19615,18407],{"class":5758},[5280,19617,6242],{"class":5297},[5280,19619,19620,19622,19624],{"class":5282,"line":5322},[5280,19621,18415],{"class":5424},[5280,19623,6239],{"class":5758},[5280,19625,18420],{"class":5297},[5280,19627,19628],{"class":5282,"line":5336},[5280,19629,6553],{"class":5452},[5280,19631,19632,19634,19636,19638,19640,19642,19644,19646,19648,19650,19652,19654],{"class":5282,"line":5341},[5280,19633,6754],{"class":5424},[5280,19635,5218],{"class":5452},[5280,19637,6272],{"class":5293},[5280,19639,5835],{"class":5452},[5280,19641,6007],{"class":5424},[5280,19643,5218],{"class":5452},[5280,19645,6281],{"class":5424},[5280,19647,6174],{"class":5452},[5280,19649,6286],{"class":5293},[5280,19651,6289],{"class":5452},[5280,19653,18462],{"class":5424},[5280,19655,5598],{"class":5452},[5280,19657,19658],{"class":5282,"line":5347},[5280,19659,6724],{"class":5452},[5280,19661,19662],{"class":5282,"line":5358},[5280,19663,19664],{"class":5286},"    \u002F\u002F 2. Валідація\n",[5280,19666,19667,19669,19671,19673,19675,19677,19679,19681,19683,19685,19688,19690,19692],{"class":5282,"line":5368},[5280,19668,7023],{"class":5436},[5280,19670,18386],{"class":5424},[5280,19672,5218],{"class":5452},[5280,19674,6437],{"class":5424},[5280,19676,7029],{"class":5452},[5280,19678,12375],{"class":5297},[5280,19680,12321],{"class":5452},[5280,19682,18462],{"class":5424},[5280,19684,5218],{"class":5452},[5280,19686,19687],{"class":5424},"Password",[5280,19689,7029],{"class":5452},[5280,19691,12375],{"class":5297},[5280,19693,6111],{"class":5452},[5280,19695,19696,19698,19700,19702,19704,19706,19708,19711,19713,19715,19717,19719],{"class":5282,"line":5378},[5280,19697,6523],{"class":5424},[5280,19699,5218],{"class":5452},[5280,19701,6316],{"class":5293},[5280,19703,5835],{"class":5452},[5280,19705,5994],{"class":5424},[5280,19707,5887],{"class":5452},[5280,19709,19710],{"class":5297},"\"Invalid input\"",[5280,19712,5887],{"class":5452},[5280,19714,5952],{"class":5424},[5280,19716,5218],{"class":5452},[5280,19718,6334],{"class":5424},[5280,19720,5598],{"class":5452},[5280,19722,19723],{"class":5282,"line":5391},[5280,19724,15875],{"class":5436},[5280,19726,19727],{"class":5282,"line":5705},[5280,19728,6553],{"class":5452},[5280,19730,19731],{"class":5282,"line":5820},[5280,19732,6724],{"class":5452},[5280,19734,19735],{"class":5282,"line":5854},[5280,19736,19737],{"class":5286},"    \u002F\u002F 3. Перевірка існування користувача\n",[5280,19739,19740,19742,19744,19746,19748,19750,19752,19754],{"class":5282,"line":5859},[5280,19741,11383],{"class":5436},[5280,19743,12286],{"class":5424},[5280,19745,5887],{"class":5452},[5280,19747,19346],{"class":5424},[5280,19749,6029],{"class":5452},[5280,19751,12296],{"class":5436},[5280,19753,5826],{"class":5424},[5280,19755,6111],{"class":5452},[5280,19757,19758,19760,19763,19765,19767,19769,19771,19773,19775],{"class":5282,"line":5871},[5280,19759,6259],{"class":5436},[5280,19761,19762],{"class":5424}," u",[5280,19764,5218],{"class":5452},[5280,19766,6437],{"class":5424},[5280,19768,7029],{"class":5452},[5280,19770,18462],{"class":5424},[5280,19772,5218],{"class":5452},[5280,19774,6437],{"class":5424},[5280,19776,6111],{"class":5452},[5280,19778,19779,19781,19783,19785,19787,19789,19791,19794,19796,19798,19800,19803],{"class":5282,"line":5895},[5280,19780,6311],{"class":5424},[5280,19782,5218],{"class":5452},[5280,19784,6316],{"class":5293},[5280,19786,5835],{"class":5452},[5280,19788,5994],{"class":5424},[5280,19790,5887],{"class":5452},[5280,19792,19793],{"class":5297},"\"Email already exists\"",[5280,19795,5887],{"class":5452},[5280,19797,5952],{"class":5424},[5280,19799,5218],{"class":5452},[5280,19801,19802],{"class":5424},"StatusConflict",[5280,19804,5598],{"class":5452},[5280,19806,19807],{"class":5282,"line":5916},[5280,19808,6412],{"class":5436},[5280,19810,19811],{"class":5282,"line":5921},[5280,19812,6248],{"class":5452},[5280,19814,19815],{"class":5282,"line":5939},[5280,19816,6553],{"class":5452},[5280,19818,19819],{"class":5282,"line":5973},[5280,19820,6724],{"class":5452},[5280,19822,19823],{"class":5282,"line":5979},[5280,19824,19825],{"class":5286},"    \u002F\u002F 4. Хешування пароля\n",[5280,19827,19828,19831,19833,19836,19838,19840,19842,19844],{"class":5282,"line":5984},[5280,19829,19830],{"class":5424},"    hashedPassword",[5280,19832,6029],{"class":5452},[5280,19834,19835],{"class":5293},"hashPassword",[5280,19837,5835],{"class":5452},[5280,19839,18462],{"class":5424},[5280,19841,5218],{"class":5452},[5280,19843,19687],{"class":5424},[5280,19845,5598],{"class":5452},[5280,19847,19848],{"class":5282,"line":6023},[5280,19849,6724],{"class":5452},[5280,19851,19852],{"class":5282,"line":6042},[5280,19853,19854],{"class":5286},"    \u002F\u002F 5. Створення користувача\n",[5280,19856,19857,19859,19861,19863],{"class":5282,"line":6093},[5280,19858,18526],{"class":5424},[5280,19860,9745],{"class":5452},[5280,19862,17513],{"class":5758},[5280,19864,6781],{"class":5452},[5280,19866,19867,19869,19872,19875],{"class":5282,"line":6098},[5280,19868,17528],{"class":5424},[5280,19870,19871],{"class":5452},":       ",[5280,19873,19874],{"class":5293},"generateID",[5280,19876,17825],{"class":5452},[5280,19878,19879,19881,19883,19885,19887,19889],{"class":5282,"line":6114},[5280,19880,17540],{"class":5424},[5280,19882,9768],{"class":5452},[5280,19884,18462],{"class":5424},[5280,19886,5218],{"class":5452},[5280,19888,6502],{"class":5424},[5280,19890,6796],{"class":5452},[5280,19892,19893,19895,19897,19899,19901,19903],{"class":5282,"line":6130},[5280,19894,17553],{"class":5424},[5280,19896,13867],{"class":5452},[5280,19898,18462],{"class":5424},[5280,19900,5218],{"class":5452},[5280,19902,6437],{"class":5424},[5280,19904,6796],{"class":5452},[5280,19906,19907,19909,19911,19914],{"class":5282,"line":6159},[5280,19908,18415],{"class":5424},[5280,19910,6790],{"class":5452},[5280,19912,19913],{"class":5424},"hashedPassword",[5280,19915,6796],{"class":5452},[5280,19917,19918],{"class":5282,"line":6187},[5280,19919,6553],{"class":5452},[5280,19921,19922,19924,19926,19928,19930,19932,19934],{"class":5282,"line":6193},[5280,19923,18625],{"class":5424},[5280,19925,5841],{"class":5452},[5280,19927,18630],{"class":5424},[5280,19929,5218],{"class":5452},[5280,19931,18635],{"class":5424},[5280,19933,6440],{"class":5452},[5280,19935,18640],{"class":5424},[5280,19937,19938],{"class":5282,"line":6207},[5280,19939,6724],{"class":5452},[5280,19941,19942],{"class":5282,"line":6221},[5280,19943,19944],{"class":5286},"    \u002F\u002F 6. Формування відповіді\n",[5280,19946,19947,19949,19951,19953,19955,19957,19959,19961,19963,19965],{"class":5282,"line":6233},[5280,19948,6754],{"class":5424},[5280,19950,5218],{"class":5452},[5280,19952,6167],{"class":5293},[5280,19954,5835],{"class":5452},[5280,19956,5994],{"class":5424},[5280,19958,6174],{"class":5452},[5280,19960,6177],{"class":5293},[5280,19962,5835],{"class":5452},[5280,19964,18630],{"class":5424},[5280,19966,5598],{"class":5452},[5280,19968,19969],{"class":5282,"line":6245},[5280,19970,5976],{"class":5452},[5262,19972,19973],{},[5227,19974,19975],{},"Що не так з цим кодом?",[6916,19977,19978,19984,19990,19996,20002],{},[5197,19979,19980,19983],{},[5227,19981,19982],{},"Неможливо протестувати бізнес-логіку"," без запуску HTTP-сервера",[5197,19985,19986,19989],{},[5227,19987,19988],{},"Порушення Single Responsibility Principle",": обробник відповідає і за HTTP, і за валідацію, і за роботу з даними",[5197,19991,19992,19995],{},[5227,19993,19994],{},"Жорстка прив'язка"," до способу зберігання даних (in-memory map)",[5197,19997,19998,20001],{},[5227,19999,20000],{},"Неможливо повторно використати"," логіку реєстрації в інших місцях (наприклад, в CLI-утиліті або gRPC-сервісі)",[5197,20003,20004,20007],{},[5227,20005,20006],{},"Складність розширення",": додавання функціональності (відправка email, логування аудиту) робить код ще більш заплутаним",[5266,20009,20011],{"id":20010},"крок-1-виділяємо-бізнес-логіку-в-окрему-функцію","Крок 1: Виділяємо бізнес-логіку в окрему функцію",[5262,20013,20014],{},"Спробуємо винести логіку реєстрації за межі HTTP-обробника:",[5271,20016,20018],{"className":5411,"code":20017,"language":5325,"meta":5276,"style":5276},"\u002F\u002F business\u002Fuser.go\npackage business\n\nimport (\n    \"errors\"\n    \"fmt\"\n)\n\ntype User struct {\n    ID       string\n    Name     string\n    Email    string\n    Password string\n}\n\n\u002F\u002F RegisterUser — бізнес-логіка реєстрації\nfunc RegisterUser(name, email, password string, existingUsers map[string]*User) (*User, error) {\n    \u002F\u002F Валідація\n    if email == \"\" || password == \"\" {\n        return nil, errors.New(\"email and password are required\")\n    }\n    \n    if len(password) \u003C 8 {\n        return nil, errors.New(\"password must be at least 8 characters\")\n    }\n    \n    \u002F\u002F Перевірка дублікатів\n    for _, u := range existingUsers {\n        if u.Email == email {\n            return nil, errors.New(\"email already exists\")\n        }\n    }\n    \n    \u002F\u002F Хешування пароля\n    hashedPassword := hashPassword(password)\n    \n    \u002F\u002F Створення користувача\n    user := &User{\n        ID:       fmt.Sprintf(\"user-%d\", len(existingUsers)+1),\n        Name:     name,\n        Email:    email,\n        Password: hashedPassword,\n    }\n    \n    return user, nil\n}\n\nfunc hashPassword(password string) string {\n    \u002F\u002F Спрощена реалізація\n    return \"hashed_\" + password\n}\n",[5215,20019,20020,20025,20032,20036,20042,20047,20051,20055,20059,20069,20076,20083,20090,20097,20101,20105,20110,20160,20165,20186,20210,20214,20218,20236,20257,20261,20265,20270,20289,20305,20327,20331,20335,20339,20344,20358,20362,20367,20377,20411,20421,20431,20441,20445,20449,20460,20464,20468,20487,20492,20504],{"__ignoreMap":5276},[5280,20021,20022],{"class":5282,"line":5283},[5280,20023,20024],{"class":5286},"\u002F\u002F business\u002Fuser.go\n",[5280,20026,20027,20029],{"class":5282,"line":5290},[5280,20028,5755],{"class":5619},[5280,20030,20031],{"class":5758}," business\n",[5280,20033,20034],{"class":5282,"line":5301},[5280,20035,5313],{"emptyLinePlaceholder":5312},[5280,20037,20038,20040],{"class":5282,"line":5309},[5280,20039,5768],{"class":5436},[5280,20041,5453],{"class":5452},[5280,20043,20044],{"class":5282,"line":5316},[5280,20045,20046],{"class":5297},"    \"errors\"\n",[5280,20048,20049],{"class":5282,"line":5322},[5280,20050,5780],{"class":5297},[5280,20052,20053],{"class":5282,"line":5336},[5280,20054,5598],{"class":5452},[5280,20056,20057],{"class":5282,"line":5341},[5280,20058,5313],{"emptyLinePlaceholder":5312},[5280,20060,20061,20063,20065,20067],{"class":5282,"line":5347},[5280,20062,7921],{"class":5619},[5280,20064,17430],{"class":5758},[5280,20066,6216],{"class":5619},[5280,20068,6111],{"class":5452},[5280,20070,20071,20073],{"class":5282,"line":5358},[5280,20072,17439],{"class":5424},[5280,20074,20075],{"class":5758},"       string\n",[5280,20077,20078,20080],{"class":5282,"line":5368},[5280,20079,17450],{"class":5424},[5280,20081,20082],{"class":5758},"     string\n",[5280,20084,20085,20087],{"class":5282,"line":5378},[5280,20086,17461],{"class":5424},[5280,20088,20089],{"class":5758},"    string\n",[5280,20091,20092,20095],{"class":5282,"line":5391},[5280,20093,20094],{"class":5424},"    Password",[5280,20096,14893],{"class":5758},[5280,20098,20099],{"class":5282,"line":5705},[5280,20100,5976],{"class":5452},[5280,20102,20103],{"class":5282,"line":5820},[5280,20104,5313],{"emptyLinePlaceholder":5312},[5280,20106,20107],{"class":5282,"line":5854},[5280,20108,20109],{"class":5286},"\u002F\u002F RegisterUser — бізнес-логіка реєстрації\n",[5280,20111,20112,20114,20117,20119,20122,20124,20127,20129,20132,20134,20136,20139,20141,20143,20145,20147,20149,20152,20154,20156,20158],{"class":5282,"line":5859},[5280,20113,5862],{"class":5619},[5280,20115,20116],{"class":5293}," RegisterUser",[5280,20118,5835],{"class":5452},[5280,20120,20121],{"class":5424},"name",[5280,20123,5887],{"class":5452},[5280,20125,20126],{"class":5424},"email",[5280,20128,5887],{"class":5452},[5280,20130,20131],{"class":5424},"password",[5280,20133,6239],{"class":5758},[5280,20135,5887],{"class":5452},[5280,20137,20138],{"class":5424},"existingUsers",[5280,20140,13717],{"class":5619},[5280,20142,5841],{"class":5452},[5280,20144,5844],{"class":5758},[5280,20146,13724],{"class":5452},[5280,20148,17513],{"class":5758},[5280,20150,20151],{"class":5452},") (*",[5280,20153,17513],{"class":5758},[5280,20155,5887],{"class":5452},[5280,20157,9567],{"class":5758},[5280,20159,6020],{"class":5452},[5280,20161,20162],{"class":5282,"line":5871},[5280,20163,20164],{"class":5286},"    \u002F\u002F Валідація\n",[5280,20166,20167,20169,20172,20174,20176,20178,20180,20182,20184],{"class":5282,"line":5895},[5280,20168,7023],{"class":5436},[5280,20170,20171],{"class":5424}," email",[5280,20173,7029],{"class":5452},[5280,20175,12375],{"class":5297},[5280,20177,12321],{"class":5452},[5280,20179,20131],{"class":5424},[5280,20181,7029],{"class":5452},[5280,20183,12375],{"class":5297},[5280,20185,6111],{"class":5452},[5280,20187,20188,20190,20193,20195,20198,20200,20203,20205,20208],{"class":5282,"line":5916},[5280,20189,12200],{"class":5436},[5280,20191,20192],{"class":5619}," nil",[5280,20194,5887],{"class":5452},[5280,20196,20197],{"class":5424},"errors",[5280,20199,5218],{"class":5452},[5280,20201,20202],{"class":5293},"New",[5280,20204,5835],{"class":5452},[5280,20206,20207],{"class":5297},"\"email and password are required\"",[5280,20209,5598],{"class":5452},[5280,20211,20212],{"class":5282,"line":5921},[5280,20213,6553],{"class":5452},[5280,20215,20216],{"class":5282,"line":5939},[5280,20217,6724],{"class":5452},[5280,20219,20220,20222,20224,20226,20228,20231,20234],{"class":5282,"line":5973},[5280,20221,7023],{"class":5436},[5280,20223,12551],{"class":5293},[5280,20225,5835],{"class":5452},[5280,20227,20131],{"class":5424},[5280,20229,20230],{"class":5452},") \u003C ",[5280,20232,20233],{"class":5439},"8",[5280,20235,6111],{"class":5452},[5280,20237,20238,20240,20242,20244,20246,20248,20250,20252,20255],{"class":5282,"line":5979},[5280,20239,12200],{"class":5436},[5280,20241,20192],{"class":5619},[5280,20243,5887],{"class":5452},[5280,20245,20197],{"class":5424},[5280,20247,5218],{"class":5452},[5280,20249,20202],{"class":5293},[5280,20251,5835],{"class":5452},[5280,20253,20254],{"class":5297},"\"password must be at least 8 characters\"",[5280,20256,5598],{"class":5452},[5280,20258,20259],{"class":5282,"line":5984},[5280,20260,6553],{"class":5452},[5280,20262,20263],{"class":5282,"line":6023},[5280,20264,6724],{"class":5452},[5280,20266,20267],{"class":5282,"line":6042},[5280,20268,20269],{"class":5286},"    \u002F\u002F Перевірка дублікатів\n",[5280,20271,20272,20274,20276,20278,20280,20282,20284,20287],{"class":5282,"line":6093},[5280,20273,11383],{"class":5436},[5280,20275,12286],{"class":5424},[5280,20277,5887],{"class":5452},[5280,20279,19346],{"class":5424},[5280,20281,6029],{"class":5452},[5280,20283,12296],{"class":5436},[5280,20285,20286],{"class":5424}," existingUsers",[5280,20288,6111],{"class":5452},[5280,20290,20291,20293,20295,20297,20299,20301,20303],{"class":5282,"line":6098},[5280,20292,6259],{"class":5436},[5280,20294,19762],{"class":5424},[5280,20296,5218],{"class":5452},[5280,20298,6437],{"class":5424},[5280,20300,7029],{"class":5452},[5280,20302,20126],{"class":5424},[5280,20304,6111],{"class":5452},[5280,20306,20307,20310,20312,20314,20316,20318,20320,20322,20325],{"class":5282,"line":6114},[5280,20308,20309],{"class":5436},"            return",[5280,20311,20192],{"class":5619},[5280,20313,5887],{"class":5452},[5280,20315,20197],{"class":5424},[5280,20317,5218],{"class":5452},[5280,20319,20202],{"class":5293},[5280,20321,5835],{"class":5452},[5280,20323,20324],{"class":5297},"\"email already exists\"",[5280,20326,5598],{"class":5452},[5280,20328,20329],{"class":5282,"line":6130},[5280,20330,6248],{"class":5452},[5280,20332,20333],{"class":5282,"line":6159},[5280,20334,6553],{"class":5452},[5280,20336,20337],{"class":5282,"line":6187},[5280,20338,6724],{"class":5452},[5280,20340,20341],{"class":5282,"line":6193},[5280,20342,20343],{"class":5286},"    \u002F\u002F Хешування пароля\n",[5280,20345,20346,20348,20350,20352,20354,20356],{"class":5282,"line":6207},[5280,20347,19830],{"class":5424},[5280,20349,6029],{"class":5452},[5280,20351,19835],{"class":5293},[5280,20353,5835],{"class":5452},[5280,20355,20131],{"class":5424},[5280,20357,5598],{"class":5452},[5280,20359,20360],{"class":5282,"line":6221},[5280,20361,6724],{"class":5452},[5280,20363,20364],{"class":5282,"line":6233},[5280,20365,20366],{"class":5286},"    \u002F\u002F Створення користувача\n",[5280,20368,20369,20371,20373,20375],{"class":5282,"line":6245},[5280,20370,18871],{"class":5424},[5280,20372,9745],{"class":5452},[5280,20374,17513],{"class":5758},[5280,20376,6781],{"class":5452},[5280,20378,20379,20381,20383,20385,20387,20389,20391,20393,20395,20397,20399,20401,20403,20405,20407,20409],{"class":5282,"line":6251},[5280,20380,17528],{"class":5424},[5280,20382,19871],{"class":5452},[5280,20384,15518],{"class":5424},[5280,20386,5218],{"class":5452},[5280,20388,12710],{"class":5293},[5280,20390,5835],{"class":5452},[5280,20392,18550],{"class":5297},[5280,20394,9946],{"class":5424},[5280,20396,6584],{"class":5297},[5280,20398,5887],{"class":5452},[5280,20400,11391],{"class":5293},[5280,20402,5835],{"class":5452},[5280,20404,20138],{"class":5424},[5280,20406,18565],{"class":5452},[5280,20408,11401],{"class":5439},[5280,20410,6828],{"class":5452},[5280,20412,20413,20415,20417,20419],{"class":5282,"line":6256},[5280,20414,17540],{"class":5424},[5280,20416,9768],{"class":5452},[5280,20418,20121],{"class":5424},[5280,20420,6796],{"class":5452},[5280,20422,20423,20425,20427,20429],{"class":5282,"line":6308},[5280,20424,17553],{"class":5424},[5280,20426,13867],{"class":5452},[5280,20428,20126],{"class":5424},[5280,20430,6796],{"class":5452},[5280,20432,20433,20435,20437,20439],{"class":5282,"line":6339},[5280,20434,18415],{"class":5424},[5280,20436,6790],{"class":5452},[5280,20438,19913],{"class":5424},[5280,20440,6796],{"class":5452},[5280,20442,20443],{"class":5282,"line":6371},[5280,20444,6553],{"class":5452},[5280,20446,20447],{"class":5282,"line":6409},[5280,20448,6724],{"class":5452},[5280,20450,20451,20453,20455,20457],{"class":5282,"line":6415},[5280,20452,7328],{"class":5436},[5280,20454,6213],{"class":5424},[5280,20456,5887],{"class":5452},[5280,20458,20459],{"class":5619},"nil\n",[5280,20461,20462],{"class":5282,"line":6420},[5280,20463,5976],{"class":5452},[5280,20465,20466],{"class":5282,"line":6425},[5280,20467,5313],{"emptyLinePlaceholder":5312},[5280,20469,20470,20472,20475,20477,20479,20481,20483,20485],{"class":5282,"line":6450},[5280,20471,5862],{"class":5619},[5280,20473,20474],{"class":5293}," hashPassword",[5280,20476,5835],{"class":5452},[5280,20478,20131],{"class":5424},[5280,20480,6239],{"class":5758},[5280,20482,7315],{"class":5452},[5280,20484,5844],{"class":5758},[5280,20486,6111],{"class":5452},[5280,20488,20489],{"class":5282,"line":6471},[5280,20490,20491],{"class":5286},"    \u002F\u002F Спрощена реалізація\n",[5280,20493,20494,20496,20499,20501],{"class":5282,"line":6507},[5280,20495,7328],{"class":5436},[5280,20497,20498],{"class":5297}," \"hashed_\"",[5280,20500,15333],{"class":5452},[5280,20502,20503],{"class":5424},"password\n",[5280,20505,20506],{"class":5282,"line":6512},[5280,20507,5976],{"class":5452},[5262,20509,20510],{},"Тепер HTTP-обробник стає простішим:",[5271,20512,20514],{"className":5411,"code":20513,"language":5325,"meta":5276,"style":5276},"func registerHandler(w http.ResponseWriter, r *http.Request) {\n    var req struct {\n        Name     string `json:\"name\"`\n        Email    string `json:\"email\"`\n        Password string `json:\"password\"`\n    }\n    \n    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {\n        http.Error(w, \"Invalid JSON\", http.StatusBadRequest)\n        return\n    }\n    \n    \u002F\u002F Викликаємо бізнес-логіку\n    user, err := business.RegisterUser(req.Name, req.Email, req.Password, users)\n    if err != nil {\n        http.Error(w, err.Error(), http.StatusBadRequest)\n        return\n    }\n    \n    \u002F\u002F Зберігаємо користувача\n    users[user.ID] = user\n    \n    w.WriteHeader(http.StatusCreated)\n    json.NewEncoder(w).Encode(user)\n}\n",[5215,20515,20516,20546,20556,20564,20572,20580,20584,20588,20628,20654,20658,20662,20666,20671,20719,20731,20761,20765,20769,20773,20778,20795,20799,20817,20839],{"__ignoreMap":5276},[5280,20517,20518,20520,20522,20524,20526,20528,20530,20532,20534,20536,20538,20540,20542,20544],{"class":5282,"line":5283},[5280,20519,5862],{"class":5619},[5280,20521,18353],{"class":5293},[5280,20523,5835],{"class":5452},[5280,20525,5994],{"class":5424},[5280,20527,5997],{"class":5758},[5280,20529,5218],{"class":5452},[5280,20531,6002],{"class":5758},[5280,20533,5887],{"class":5452},[5280,20535,6007],{"class":5424},[5280,20537,6010],{"class":5452},[5280,20539,5952],{"class":5758},[5280,20541,5218],{"class":5452},[5280,20543,6017],{"class":5758},[5280,20545,6020],{"class":5452},[5280,20547,20548,20550,20552,20554],{"class":5282,"line":5290},[5280,20549,18383],{"class":5619},[5280,20551,18386],{"class":5424},[5280,20553,6216],{"class":5619},[5280,20555,6111],{"class":5452},[5280,20557,20558,20560,20562],{"class":5282,"line":5301},[5280,20559,17540],{"class":5424},[5280,20561,17464],{"class":5758},[5280,20563,6230],{"class":5297},[5280,20565,20566,20568,20570],{"class":5282,"line":5309},[5280,20567,17553],{"class":5424},[5280,20569,18407],{"class":5758},[5280,20571,6242],{"class":5297},[5280,20573,20574,20576,20578],{"class":5282,"line":5316},[5280,20575,18415],{"class":5424},[5280,20577,6239],{"class":5758},[5280,20579,18420],{"class":5297},[5280,20581,20582],{"class":5282,"line":5322},[5280,20583,6553],{"class":5452},[5280,20585,20586],{"class":5282,"line":5336},[5280,20587,6724],{"class":5452},[5280,20589,20590,20592,20594,20596,20598,20600,20602,20604,20606,20608,20610,20612,20614,20616,20618,20620,20622,20624,20626],{"class":5282,"line":5341},[5280,20591,7023],{"class":5436},[5280,20593,6262],{"class":5424},[5280,20595,6029],{"class":5452},[5280,20597,6267],{"class":5424},[5280,20599,5218],{"class":5452},[5280,20601,6272],{"class":5293},[5280,20603,5835],{"class":5452},[5280,20605,6007],{"class":5424},[5280,20607,5218],{"class":5452},[5280,20609,6281],{"class":5424},[5280,20611,6174],{"class":5452},[5280,20613,6286],{"class":5293},[5280,20615,6289],{"class":5452},[5280,20617,18462],{"class":5424},[5280,20619,6295],{"class":5452},[5280,20621,6298],{"class":5424},[5280,20623,6301],{"class":5452},[5280,20625,5967],{"class":5619},[5280,20627,6111],{"class":5452},[5280,20629,20630,20632,20634,20636,20638,20640,20642,20644,20646,20648,20650,20652],{"class":5282,"line":5347},[5280,20631,6523],{"class":5424},[5280,20633,5218],{"class":5452},[5280,20635,6316],{"class":5293},[5280,20637,5835],{"class":5452},[5280,20639,5994],{"class":5424},[5280,20641,5887],{"class":5452},[5280,20643,6325],{"class":5297},[5280,20645,5887],{"class":5452},[5280,20647,5952],{"class":5424},[5280,20649,5218],{"class":5452},[5280,20651,6334],{"class":5424},[5280,20653,5598],{"class":5452},[5280,20655,20656],{"class":5282,"line":5358},[5280,20657,15875],{"class":5436},[5280,20659,20660],{"class":5282,"line":5368},[5280,20661,6553],{"class":5452},[5280,20663,20664],{"class":5282,"line":5378},[5280,20665,6724],{"class":5452},[5280,20667,20668],{"class":5282,"line":5391},[5280,20669,20670],{"class":5286},"    \u002F\u002F Викликаємо бізнес-логіку\n",[5280,20672,20673,20675,20677,20679,20681,20684,20686,20689,20691,20693,20695,20697,20699,20701,20703,20705,20707,20709,20711,20713,20715,20717],{"class":5282,"line":5705},[5280,20674,18871],{"class":5424},[5280,20676,5887],{"class":5452},[5280,20678,6298],{"class":5424},[5280,20680,6029],{"class":5452},[5280,20682,20683],{"class":5424},"business",[5280,20685,5218],{"class":5452},[5280,20687,20688],{"class":5293},"RegisterUser",[5280,20690,5835],{"class":5452},[5280,20692,18462],{"class":5424},[5280,20694,5218],{"class":5452},[5280,20696,6502],{"class":5424},[5280,20698,5887],{"class":5452},[5280,20700,18462],{"class":5424},[5280,20702,5218],{"class":5452},[5280,20704,6437],{"class":5424},[5280,20706,5887],{"class":5452},[5280,20708,18462],{"class":5424},[5280,20710,5218],{"class":5452},[5280,20712,19687],{"class":5424},[5280,20714,5887],{"class":5452},[5280,20716,6182],{"class":5424},[5280,20718,5598],{"class":5452},[5280,20720,20721,20723,20725,20727,20729],{"class":5282,"line":5820},[5280,20722,7023],{"class":5436},[5280,20724,6262],{"class":5424},[5280,20726,6301],{"class":5452},[5280,20728,5967],{"class":5619},[5280,20730,6111],{"class":5452},[5280,20732,20733,20735,20737,20739,20741,20743,20745,20747,20749,20751,20753,20755,20757,20759],{"class":5282,"line":5854},[5280,20734,6523],{"class":5424},[5280,20736,5218],{"class":5452},[5280,20738,6316],{"class":5293},[5280,20740,5835],{"class":5452},[5280,20742,5994],{"class":5424},[5280,20744,5887],{"class":5452},[5280,20746,6298],{"class":5424},[5280,20748,5218],{"class":5452},[5280,20750,6316],{"class":5293},[5280,20752,15396],{"class":5452},[5280,20754,5952],{"class":5424},[5280,20756,5218],{"class":5452},[5280,20758,6334],{"class":5424},[5280,20760,5598],{"class":5452},[5280,20762,20763],{"class":5282,"line":5859},[5280,20764,15875],{"class":5436},[5280,20766,20767],{"class":5282,"line":5871},[5280,20768,6553],{"class":5452},[5280,20770,20771],{"class":5282,"line":5895},[5280,20772,6724],{"class":5452},[5280,20774,20775],{"class":5282,"line":5916},[5280,20776,20777],{"class":5286},"    \u002F\u002F Зберігаємо користувача\n",[5280,20779,20780,20782,20784,20786,20788,20790,20792],{"class":5282,"line":5921},[5280,20781,18625],{"class":5424},[5280,20783,5841],{"class":5452},[5280,20785,6292],{"class":5424},[5280,20787,5218],{"class":5452},[5280,20789,18635],{"class":5424},[5280,20791,6440],{"class":5452},[5280,20793,20794],{"class":5424},"user\n",[5280,20796,20797],{"class":5282,"line":5939},[5280,20798,6724],{"class":5452},[5280,20800,20801,20803,20805,20807,20809,20811,20813,20815],{"class":5282,"line":5973},[5280,20802,6730],{"class":5424},[5280,20804,5218],{"class":5452},[5280,20806,6457],{"class":5293},[5280,20808,5835],{"class":5452},[5280,20810,5952],{"class":5424},[5280,20812,5218],{"class":5452},[5280,20814,6466],{"class":5424},[5280,20816,5598],{"class":5452},[5280,20818,20819,20821,20823,20825,20827,20829,20831,20833,20835,20837],{"class":5282,"line":5979},[5280,20820,6754],{"class":5424},[5280,20822,5218],{"class":5452},[5280,20824,6167],{"class":5293},[5280,20826,5835],{"class":5452},[5280,20828,5994],{"class":5424},[5280,20830,6174],{"class":5452},[5280,20832,6177],{"class":5293},[5280,20834,5835],{"class":5452},[5280,20836,6292],{"class":5424},[5280,20838,5598],{"class":5452},[5280,20840,20841],{"class":5282,"line":5984},[5280,20842,5976],{"class":5452},[5262,20844,20845,20848],{},[5227,20846,20847],{},"Це вже краще",", але:",[5194,20850,20851,20858,20861],{},[5197,20852,20853,20854,20857],{},"Бізнес-логіка все ще залежить від ",[5215,20855,20856],{},"map[string]*User"," (конкретного способу зберігання)",[5197,20859,20860],{},"Складно замінити map на базу даних",[5197,20862,20863],{},"Немає чіткого розділення відповідальності",[5254,20865],{},[5257,20867,20869],{"id":20868},"clean-architecture-філософія-та-принципи","Clean Architecture: філософія та принципи",[5266,20871,20873],{"id":20872},"історична-довідка","Історична довідка",[5262,20875,20876,5720,20879,20882,20883,20886],{},[5227,20877,20878],{},"Clean Architecture",[5207,20880,20881],{},"чиста архітектура",") — це архітектурний підхід, запропонований Робертом Мартіном (",[5207,20884,20885],{},"Uncle Bob",") у 2012 році. Він узагальнює ідеї з кількох інших архітектурних патернів:",[5194,20888,20889,20895,20901],{},[5197,20890,20891,20894],{},[5227,20892,20893],{},"Hexagonal Architecture"," (Alistair Cockburn, 2005)",[5197,20896,20897,20900],{},[5227,20898,20899],{},"Onion Architecture"," (Jeffrey Palermo, 2008)",[5197,20902,20903,20906],{},[5227,20904,20905],{},"DCI (Data, Context, Interactions)"," (Trygve Reenskaug, 2000-і)",[5262,20908,20909,20910,5218],{},"Всі ці підходи об'єднує спільна ідея: ",[5227,20911,20912],{},"бізнес-логіка не повинна залежати від технічних деталей",[5266,20914,20916],{"id":20915},"ключовий-принцип-dependency-rule","Ключовий принцип: Dependency Rule",[5262,20918,20919,20926,20927,20930],{},[5227,20920,20921,20922,20925],{},"Правило залежностей (",[5207,20923,20924],{},"Dependency Rule","):"," Залежності вихідного коду можуть вказувати тільки ",[5227,20928,20929],{},"всередину"," (до внутрішніх шарів). Внутрішні шари не повинні знати про існування зовнішніх.",[7678,20932,20934],{"alt":20933},"Концентричні кола Clean Architecture",[5271,20935,20937],{"className":7683,"code":20936,"language":7685,"meta":5276,"style":5276},"@startuml\nskinparam style plain\nskinparam backgroundColor #FFFFFF\n\ncircle \"Entities\\n(Domain)\" as E #FEF3C7\ncircle \"Use Cases\\n(Business Logic)\" as UC #DBEAFE\ncircle \"Interface Adapters\\n(Controllers, Gateways)\" as IA #E2E8F0\ncircle \"Frameworks & Drivers\\n(HTTP, DB, UI)\" as FD #F1F5F9\n\nE -[hidden]-> UC\nUC -[hidden]-> IA\nIA -[hidden]-> FD\n\nnote top of E\n  \u003Cb>Центр\u003C\u002Fb>\n  Доменні сутності\n  Бізнес-правила\n  \n  Залежить: ні від кого\nend note\n\nnote right of UC\n  Сценарії використання\n  Application logic\n  \n  Залежить: Domain\nend note\n\nnote bottom of IA\n  HTTP handlers, Repositories\n  Presenters, Gateways\n  \n  Залежить: Use Cases\nend note\n\nnote left of FD\n  Web framework, СУБД\n  External APIs\n  \n  Залежить: Interface Adapters\nend note\n\nFD --> IA : залежність\nIA --> UC : залежність\nUC --> E : залежність\n\n@enduml\n",[5215,20938,20939,20943,20947,20951,20955,20960,20965,20970,20975,20979,20984,20989,20994,20998,21003,21008,21013,21018,21022,21027,21031,21035,21040,21045,21050,21054,21059,21063,21067,21072,21077,21082,21086,21091,21095,21099,21104,21109,21114,21118,21123,21127,21131,21136,21141,21146,21150],{"__ignoreMap":5276},[5280,20940,20941],{"class":5282,"line":5283},[5280,20942,7692],{},[5280,20944,20945],{"class":5282,"line":5290},[5280,20946,7697],{},[5280,20948,20949],{"class":5282,"line":5301},[5280,20950,7702],{},[5280,20952,20953],{"class":5282,"line":5309},[5280,20954,5313],{"emptyLinePlaceholder":5312},[5280,20956,20957],{"class":5282,"line":5316},[5280,20958,20959],{},"circle \"Entities\\n(Domain)\" as E #FEF3C7\n",[5280,20961,20962],{"class":5282,"line":5322},[5280,20963,20964],{},"circle \"Use Cases\\n(Business Logic)\" as UC #DBEAFE\n",[5280,20966,20967],{"class":5282,"line":5336},[5280,20968,20969],{},"circle \"Interface Adapters\\n(Controllers, Gateways)\" as IA #E2E8F0\n",[5280,20971,20972],{"class":5282,"line":5341},[5280,20973,20974],{},"circle \"Frameworks & Drivers\\n(HTTP, DB, UI)\" as FD #F1F5F9\n",[5280,20976,20977],{"class":5282,"line":5347},[5280,20978,5313],{"emptyLinePlaceholder":5312},[5280,20980,20981],{"class":5282,"line":5358},[5280,20982,20983],{},"E -[hidden]-> UC\n",[5280,20985,20986],{"class":5282,"line":5368},[5280,20987,20988],{},"UC -[hidden]-> IA\n",[5280,20990,20991],{"class":5282,"line":5378},[5280,20992,20993],{},"IA -[hidden]-> FD\n",[5280,20995,20996],{"class":5282,"line":5391},[5280,20997,5313],{"emptyLinePlaceholder":5312},[5280,20999,21000],{"class":5282,"line":5705},[5280,21001,21002],{},"note top of E\n",[5280,21004,21005],{"class":5282,"line":5820},[5280,21006,21007],{},"  \u003Cb>Центр\u003C\u002Fb>\n",[5280,21009,21010],{"class":5282,"line":5854},[5280,21011,21012],{},"  Доменні сутності\n",[5280,21014,21015],{"class":5282,"line":5859},[5280,21016,21017],{},"  Бізнес-правила\n",[5280,21019,21020],{"class":5282,"line":5871},[5280,21021,11547],{},[5280,21023,21024],{"class":5282,"line":5895},[5280,21025,21026],{},"  Залежить: ні від кого\n",[5280,21028,21029],{"class":5282,"line":5916},[5280,21030,7765],{},[5280,21032,21033],{"class":5282,"line":5921},[5280,21034,5313],{"emptyLinePlaceholder":5312},[5280,21036,21037],{"class":5282,"line":5939},[5280,21038,21039],{},"note right of UC\n",[5280,21041,21042],{"class":5282,"line":5973},[5280,21043,21044],{},"  Сценарії використання\n",[5280,21046,21047],{"class":5282,"line":5979},[5280,21048,21049],{},"  Application logic\n",[5280,21051,21052],{"class":5282,"line":5984},[5280,21053,11547],{},[5280,21055,21056],{"class":5282,"line":6023},[5280,21057,21058],{},"  Залежить: Domain\n",[5280,21060,21061],{"class":5282,"line":6042},[5280,21062,7765],{},[5280,21064,21065],{"class":5282,"line":6093},[5280,21066,5313],{"emptyLinePlaceholder":5312},[5280,21068,21069],{"class":5282,"line":6098},[5280,21070,21071],{},"note bottom of IA\n",[5280,21073,21074],{"class":5282,"line":6114},[5280,21075,21076],{},"  HTTP handlers, Repositories\n",[5280,21078,21079],{"class":5282,"line":6130},[5280,21080,21081],{},"  Presenters, Gateways\n",[5280,21083,21084],{"class":5282,"line":6159},[5280,21085,11547],{},[5280,21087,21088],{"class":5282,"line":6187},[5280,21089,21090],{},"  Залежить: Use Cases\n",[5280,21092,21093],{"class":5282,"line":6193},[5280,21094,7765],{},[5280,21096,21097],{"class":5282,"line":6207},[5280,21098,5313],{"emptyLinePlaceholder":5312},[5280,21100,21101],{"class":5282,"line":6221},[5280,21102,21103],{},"note left of FD\n",[5280,21105,21106],{"class":5282,"line":6233},[5280,21107,21108],{},"  Web framework, СУБД\n",[5280,21110,21111],{"class":5282,"line":6245},[5280,21112,21113],{},"  External APIs\n",[5280,21115,21116],{"class":5282,"line":6251},[5280,21117,11547],{},[5280,21119,21120],{"class":5282,"line":6256},[5280,21121,21122],{},"  Залежить: Interface Adapters\n",[5280,21124,21125],{"class":5282,"line":6308},[5280,21126,7765],{},[5280,21128,21129],{"class":5282,"line":6339},[5280,21130,5313],{"emptyLinePlaceholder":5312},[5280,21132,21133],{"class":5282,"line":6371},[5280,21134,21135],{},"FD --> IA : залежність\n",[5280,21137,21138],{"class":5282,"line":6409},[5280,21139,21140],{},"IA --> UC : залежність\n",[5280,21142,21143],{"class":5282,"line":6415},[5280,21144,21145],{},"UC --> E : залежність\n",[5280,21147,21148],{"class":5282,"line":6420},[5280,21149,5313],{"emptyLinePlaceholder":5312},[5280,21151,21152],{"class":5282,"line":6425},[5280,21153,7904],{},[5266,21155,21157],{"id":21156},"чотири-основні-шари","Чотири основні шари",[21159,21160,21162,21163,21166],"h4",{"id":21161},"_1-entities-domain-layer-шар-доменних-сутностей","1. ",[5227,21164,21165],{},"Entities (Domain Layer)"," — шар доменних сутностей",[5262,21168,21169],{},"Найвнутрішній шар. Містить:",[5194,21171,21172,21178],{},[5197,21173,21174,21177],{},[5227,21175,21176],{},"Доменні об'єкти"," — структури даних, що моделюють предметну область",[5197,21179,21180,21183],{},[5227,21181,21182],{},"Бізнес-правила рівня сутності"," — валідація, обчислення, інваріанти",[5271,21185,21187],{"className":5411,"code":21186,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fdomain\u002Fuser.go\npackage domain\n\nimport (\n    \"errors\"\n    \"regexp\"\n    \"time\"\n)\n\n\u002F\u002F User — доменна сутність користувача\ntype User struct {\n    ID        string\n    Name      string\n    Email     string\n    Password  string \u002F\u002F Хеш пароля\n    CreatedAt time.Time\n    UpdatedAt time.Time\n}\n\nvar emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$`)\n\n\u002F\u002F Validate перевіряє коректність даних користувача\nfunc (u *User) Validate() error {\n    if u.Name == \"\" {\n        return errors.New(\"name is required\")\n    }\n    \n    if u.Email == \"\" {\n        return errors.New(\"email is required\")\n    }\n    \n    if !emailRegex.MatchString(u.Email) {\n        return errors.New(\"invalid email format\")\n    }\n    \n    if len(u.Password) \u003C 8 {\n        return errors.New(\"password must be at least 8 characters\")\n    }\n    \n    return nil\n}\n\n\u002F\u002F IsPasswordValid перевіряє відповідність пароля хешу\nfunc (u *User) IsPasswordValid(plainPassword string) bool {\n    \u002F\u002F У реальності тут bcrypt.CompareHashAndPassword\n    return \"hashed_\"+plainPassword == u.Password\n}\n",[5215,21188,21189,21194,21201,21205,21211,21215,21220,21224,21228,21232,21237,21247,21254,21261,21267,21276,21287,21298,21302,21306,21330,21334,21339,21363,21379,21397,21401,21405,21421,21438,21442,21446,21470,21487,21491,21495,21515,21531,21535,21539,21546,21550,21554,21559,21590,21595,21615],{"__ignoreMap":5276},[5280,21190,21191],{"class":5282,"line":5283},[5280,21192,21193],{"class":5286},"\u002F\u002F internal\u002Fdomain\u002Fuser.go\n",[5280,21195,21196,21198],{"class":5282,"line":5290},[5280,21197,5755],{"class":5619},[5280,21199,21200],{"class":5758}," domain\n",[5280,21202,21203],{"class":5282,"line":5301},[5280,21204,5313],{"emptyLinePlaceholder":5312},[5280,21206,21207,21209],{"class":5282,"line":5309},[5280,21208,5768],{"class":5436},[5280,21210,5453],{"class":5452},[5280,21212,21213],{"class":5282,"line":5316},[5280,21214,20046],{"class":5297},[5280,21216,21217],{"class":5282,"line":5322},[5280,21218,21219],{"class":5297},"    \"regexp\"\n",[5280,21221,21222],{"class":5282,"line":5336},[5280,21223,5795],{"class":5297},[5280,21225,21226],{"class":5282,"line":5341},[5280,21227,5598],{"class":5452},[5280,21229,21230],{"class":5282,"line":5347},[5280,21231,5313],{"emptyLinePlaceholder":5312},[5280,21233,21234],{"class":5282,"line":5358},[5280,21235,21236],{"class":5286},"\u002F\u002F User — доменна сутність користувача\n",[5280,21238,21239,21241,21243,21245],{"class":5282,"line":5368},[5280,21240,7921],{"class":5619},[5280,21242,17430],{"class":5758},[5280,21244,6216],{"class":5619},[5280,21246,6111],{"class":5452},[5280,21248,21249,21251],{"class":5282,"line":5378},[5280,21250,17439],{"class":5424},[5280,21252,21253],{"class":5758},"        string\n",[5280,21255,21256,21258],{"class":5282,"line":5391},[5280,21257,17450],{"class":5424},[5280,21259,21260],{"class":5758},"      string\n",[5280,21262,21263,21265],{"class":5282,"line":5705},[5280,21264,17461],{"class":5424},[5280,21266,20082],{"class":5758},[5280,21268,21269,21271,21273],{"class":5282,"line":5820},[5280,21270,20094],{"class":5424},[5280,21272,6227],{"class":5758},[5280,21274,21275],{"class":5286}," \u002F\u002F Хеш пароля\n",[5280,21277,21278,21280,21282,21284],{"class":5282,"line":5854},[5280,21279,17472],{"class":5424},[5280,21281,7004],{"class":5758},[5280,21283,5218],{"class":5452},[5280,21285,21286],{"class":5758},"Time\n",[5280,21288,21289,21292,21294,21296],{"class":5282,"line":5859},[5280,21290,21291],{"class":5424},"    UpdatedAt",[5280,21293,7004],{"class":5758},[5280,21295,5218],{"class":5452},[5280,21297,21286],{"class":5758},[5280,21299,21300],{"class":5282,"line":5871},[5280,21301,5976],{"class":5452},[5280,21303,21304],{"class":5282,"line":5895},[5280,21305,5313],{"emptyLinePlaceholder":5312},[5280,21307,21308,21310,21313,21315,21318,21320,21323,21325,21328],{"class":5282,"line":5916},[5280,21309,5823],{"class":5619},[5280,21311,21312],{"class":5424}," emailRegex",[5280,21314,5829],{"class":5452},[5280,21316,21317],{"class":5424},"regexp",[5280,21319,5218],{"class":5452},[5280,21321,21322],{"class":5293},"MustCompile",[5280,21324,5835],{"class":5452},[5280,21326,21327],{"class":5297},"`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$`",[5280,21329,5598],{"class":5452},[5280,21331,21332],{"class":5282,"line":5921},[5280,21333,5313],{"emptyLinePlaceholder":5312},[5280,21335,21336],{"class":5282,"line":5939},[5280,21337,21338],{"class":5286},"\u002F\u002F Validate перевіряє коректність даних користувача\n",[5280,21340,21341,21343,21345,21348,21350,21352,21354,21357,21359,21361],{"class":5282,"line":5973},[5280,21342,5862],{"class":5619},[5280,21344,5720],{"class":5452},[5280,21346,21347],{"class":5424},"u ",[5280,21349,8072],{"class":5452},[5280,21351,17513],{"class":5758},[5280,21353,7315],{"class":5452},[5280,21355,21356],{"class":5293},"Validate",[5280,21358,11974],{"class":5452},[5280,21360,9567],{"class":5758},[5280,21362,6111],{"class":5452},[5280,21364,21365,21367,21369,21371,21373,21375,21377],{"class":5282,"line":5979},[5280,21366,7023],{"class":5436},[5280,21368,19762],{"class":5424},[5280,21370,5218],{"class":5452},[5280,21372,6502],{"class":5424},[5280,21374,7029],{"class":5452},[5280,21376,12375],{"class":5297},[5280,21378,6111],{"class":5452},[5280,21380,21381,21383,21386,21388,21390,21392,21395],{"class":5282,"line":5984},[5280,21382,12200],{"class":5436},[5280,21384,21385],{"class":5424}," errors",[5280,21387,5218],{"class":5452},[5280,21389,20202],{"class":5293},[5280,21391,5835],{"class":5452},[5280,21393,21394],{"class":5297},"\"name is required\"",[5280,21396,5598],{"class":5452},[5280,21398,21399],{"class":5282,"line":6023},[5280,21400,6553],{"class":5452},[5280,21402,21403],{"class":5282,"line":6042},[5280,21404,6724],{"class":5452},[5280,21406,21407,21409,21411,21413,21415,21417,21419],{"class":5282,"line":6093},[5280,21408,7023],{"class":5436},[5280,21410,19762],{"class":5424},[5280,21412,5218],{"class":5452},[5280,21414,6437],{"class":5424},[5280,21416,7029],{"class":5452},[5280,21418,12375],{"class":5297},[5280,21420,6111],{"class":5452},[5280,21422,21423,21425,21427,21429,21431,21433,21436],{"class":5282,"line":6098},[5280,21424,12200],{"class":5436},[5280,21426,21385],{"class":5424},[5280,21428,5218],{"class":5452},[5280,21430,20202],{"class":5293},[5280,21432,5835],{"class":5452},[5280,21434,21435],{"class":5297},"\"email is required\"",[5280,21437,5598],{"class":5452},[5280,21439,21440],{"class":5282,"line":6114},[5280,21441,6553],{"class":5452},[5280,21443,21444],{"class":5282,"line":6130},[5280,21445,6724],{"class":5452},[5280,21447,21448,21450,21452,21455,21457,21460,21462,21464,21466,21468],{"class":5282,"line":6159},[5280,21449,7023],{"class":5436},[5280,21451,9455],{"class":5452},[5280,21453,21454],{"class":5424},"emailRegex",[5280,21456,5218],{"class":5452},[5280,21458,21459],{"class":5293},"MatchString",[5280,21461,5835],{"class":5452},[5280,21463,19346],{"class":5424},[5280,21465,5218],{"class":5452},[5280,21467,6437],{"class":5424},[5280,21469,6020],{"class":5452},[5280,21471,21472,21474,21476,21478,21480,21482,21485],{"class":5282,"line":6187},[5280,21473,12200],{"class":5436},[5280,21475,21385],{"class":5424},[5280,21477,5218],{"class":5452},[5280,21479,20202],{"class":5293},[5280,21481,5835],{"class":5452},[5280,21483,21484],{"class":5297},"\"invalid email format\"",[5280,21486,5598],{"class":5452},[5280,21488,21489],{"class":5282,"line":6193},[5280,21490,6553],{"class":5452},[5280,21492,21493],{"class":5282,"line":6207},[5280,21494,6724],{"class":5452},[5280,21496,21497,21499,21501,21503,21505,21507,21509,21511,21513],{"class":5282,"line":6221},[5280,21498,7023],{"class":5436},[5280,21500,12551],{"class":5293},[5280,21502,5835],{"class":5452},[5280,21504,19346],{"class":5424},[5280,21506,5218],{"class":5452},[5280,21508,19687],{"class":5424},[5280,21510,20230],{"class":5452},[5280,21512,20233],{"class":5439},[5280,21514,6111],{"class":5452},[5280,21516,21517,21519,21521,21523,21525,21527,21529],{"class":5282,"line":6233},[5280,21518,12200],{"class":5436},[5280,21520,21385],{"class":5424},[5280,21522,5218],{"class":5452},[5280,21524,20202],{"class":5293},[5280,21526,5835],{"class":5452},[5280,21528,20254],{"class":5297},[5280,21530,5598],{"class":5452},[5280,21532,21533],{"class":5282,"line":6245},[5280,21534,6553],{"class":5452},[5280,21536,21537],{"class":5282,"line":6251},[5280,21538,6724],{"class":5452},[5280,21540,21541,21543],{"class":5282,"line":6256},[5280,21542,7328],{"class":5436},[5280,21544,21545],{"class":5619}," nil\n",[5280,21547,21548],{"class":5282,"line":6308},[5280,21549,5976],{"class":5452},[5280,21551,21552],{"class":5282,"line":6339},[5280,21553,5313],{"emptyLinePlaceholder":5312},[5280,21555,21556],{"class":5282,"line":6371},[5280,21557,21558],{"class":5286},"\u002F\u002F IsPasswordValid перевіряє відповідність пароля хешу\n",[5280,21560,21561,21563,21565,21567,21569,21571,21573,21576,21578,21581,21583,21585,21588],{"class":5282,"line":6409},[5280,21562,5862],{"class":5619},[5280,21564,5720],{"class":5452},[5280,21566,21347],{"class":5424},[5280,21568,8072],{"class":5452},[5280,21570,17513],{"class":5758},[5280,21572,7315],{"class":5452},[5280,21574,21575],{"class":5293},"IsPasswordValid",[5280,21577,5835],{"class":5452},[5280,21579,21580],{"class":5424},"plainPassword",[5280,21582,6239],{"class":5758},[5280,21584,7315],{"class":5452},[5280,21586,21587],{"class":5758},"bool",[5280,21589,6111],{"class":5452},[5280,21591,21592],{"class":5282,"line":6415},[5280,21593,21594],{"class":5286},"    \u002F\u002F У реальності тут bcrypt.CompareHashAndPassword\n",[5280,21596,21597,21599,21601,21604,21606,21608,21610,21612],{"class":5282,"line":6420},[5280,21598,7328],{"class":5436},[5280,21600,20498],{"class":5297},[5280,21602,21603],{"class":5452},"+",[5280,21605,21580],{"class":5424},[5280,21607,7029],{"class":5452},[5280,21609,19346],{"class":5424},[5280,21611,5218],{"class":5452},[5280,21613,21614],{"class":5424},"Password\n",[5280,21616,21617],{"class":5282,"line":6425},[5280,21618,5976],{"class":5452},[5712,21620,21621,21622,21625,21626,21629],{},"Шар ",[5227,21623,21624],{},"Domain"," не імпортує жодні зовнішні залежності (крім стандартної бібліотеки Go). Це робить його ",[5227,21627,21628],{},"максимально переносимим"," — доменну логіку можна використовувати в будь-якому проєкті.",[21159,21631,21633,21634,21637],{"id":21632},"_2-use-cases-applicationbusiness-logic-layer-шар-сценаріїв","2. ",[5227,21635,21636],{},"Use Cases (Application\u002FBusiness Logic Layer)"," — шар сценаріїв",[5262,21639,21640,21641,5408],{},"Містить ",[5227,21642,21643],{},"конкретні бізнес-сценарії",[5194,21645,21646,21649,21652,21655],{},[5197,21647,21648],{},"Реєстрація користувача",[5197,21650,21651],{},"Вхід в систему",[5197,21653,21654],{},"Оформлення замовлення",[5197,21656,21657],{},"Відправка повідомлення",[5262,21659,21660,21661,21664],{},"Use Case ",[5227,21662,21663],{},"залежить від інтерфейсів",", а не від реалізацій:",[5271,21666,21668],{"className":5411,"code":21667,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fdomain\u002Fuser_repository.go\npackage domain\n\nimport \"context\"\n\n\u002F\u002F UserRepository — інтерфейс для доступу до даних користувачів\ntype UserRepository interface {\n    Create(ctx context.Context, user *User) error\n    FindByID(ctx context.Context, id string) (*User, error)\n    FindByEmail(ctx context.Context, email string) (*User, error)\n    Update(ctx context.Context, user *User) error\n    Delete(ctx context.Context, id string) error\n}\n\n\u002F\u002F Стандартні помилки домену\nvar (\n    ErrUserNotFound      = errors.New(\"user not found\")\n    ErrUserAlreadyExists = errors.New(\"user already exists\")\n    ErrInvalidCredentials = errors.New(\"invalid credentials\")\n)\n",[5215,21669,21670,21675,21681,21685,21692,21696,21701,21712,21741,21773,21804,21831,21856,21860,21864,21869,21875,21896,21916,21936],{"__ignoreMap":5276},[5280,21671,21672],{"class":5282,"line":5283},[5280,21673,21674],{"class":5286},"\u002F\u002F internal\u002Fdomain\u002Fuser_repository.go\n",[5280,21676,21677,21679],{"class":5282,"line":5290},[5280,21678,5755],{"class":5619},[5280,21680,21200],{"class":5758},[5280,21682,21683],{"class":5282,"line":5301},[5280,21684,5313],{"emptyLinePlaceholder":5312},[5280,21686,21687,21689],{"class":5282,"line":5309},[5280,21688,5768],{"class":5436},[5280,21690,21691],{"class":5297}," \"context\"\n",[5280,21693,21694],{"class":5282,"line":5316},[5280,21695,5313],{"emptyLinePlaceholder":5312},[5280,21697,21698],{"class":5282,"line":5322},[5280,21699,21700],{"class":5286},"\u002F\u002F UserRepository — інтерфейс для доступу до даних користувачів\n",[5280,21702,21703,21705,21708,21710],{"class":5282,"line":5336},[5280,21704,7921],{"class":5619},[5280,21706,21707],{"class":5758}," UserRepository",[5280,21709,8005],{"class":5619},[5280,21711,6111],{"class":5452},[5280,21713,21714,21717,21719,21721,21724,21726,21728,21730,21732,21734,21736,21738],{"class":5282,"line":5341},[5280,21715,21716],{"class":5293},"    Create",[5280,21718,5835],{"class":5452},[5280,21720,15441],{"class":5424},[5280,21722,21723],{"class":5758}," context",[5280,21725,5218],{"class":5452},[5280,21727,15393],{"class":5758},[5280,21729,5887],{"class":5452},[5280,21731,6292],{"class":5424},[5280,21733,6010],{"class":5452},[5280,21735,17513],{"class":5758},[5280,21737,7315],{"class":5452},[5280,21739,21740],{"class":5758},"error\n",[5280,21742,21743,21746,21748,21750,21752,21754,21756,21758,21761,21763,21765,21767,21769,21771],{"class":5282,"line":5347},[5280,21744,21745],{"class":5293},"    FindByID",[5280,21747,5835],{"class":5452},[5280,21749,15441],{"class":5424},[5280,21751,21723],{"class":5758},[5280,21753,5218],{"class":5452},[5280,21755,15393],{"class":5758},[5280,21757,5887],{"class":5452},[5280,21759,21760],{"class":5424},"id",[5280,21762,6239],{"class":5758},[5280,21764,20151],{"class":5452},[5280,21766,17513],{"class":5758},[5280,21768,5887],{"class":5452},[5280,21770,9567],{"class":5758},[5280,21772,5598],{"class":5452},[5280,21774,21775,21778,21780,21782,21784,21786,21788,21790,21792,21794,21796,21798,21800,21802],{"class":5282,"line":5358},[5280,21776,21777],{"class":5293},"    FindByEmail",[5280,21779,5835],{"class":5452},[5280,21781,15441],{"class":5424},[5280,21783,21723],{"class":5758},[5280,21785,5218],{"class":5452},[5280,21787,15393],{"class":5758},[5280,21789,5887],{"class":5452},[5280,21791,20126],{"class":5424},[5280,21793,6239],{"class":5758},[5280,21795,20151],{"class":5452},[5280,21797,17513],{"class":5758},[5280,21799,5887],{"class":5452},[5280,21801,9567],{"class":5758},[5280,21803,5598],{"class":5452},[5280,21805,21806,21809,21811,21813,21815,21817,21819,21821,21823,21825,21827,21829],{"class":5282,"line":5368},[5280,21807,21808],{"class":5293},"    Update",[5280,21810,5835],{"class":5452},[5280,21812,15441],{"class":5424},[5280,21814,21723],{"class":5758},[5280,21816,5218],{"class":5452},[5280,21818,15393],{"class":5758},[5280,21820,5887],{"class":5452},[5280,21822,6292],{"class":5424},[5280,21824,6010],{"class":5452},[5280,21826,17513],{"class":5758},[5280,21828,7315],{"class":5452},[5280,21830,21740],{"class":5758},[5280,21832,21833,21836,21838,21840,21842,21844,21846,21848,21850,21852,21854],{"class":5282,"line":5378},[5280,21834,21835],{"class":5293},"    Delete",[5280,21837,5835],{"class":5452},[5280,21839,15441],{"class":5424},[5280,21841,21723],{"class":5758},[5280,21843,5218],{"class":5452},[5280,21845,15393],{"class":5758},[5280,21847,5887],{"class":5452},[5280,21849,21760],{"class":5424},[5280,21851,6239],{"class":5758},[5280,21853,7315],{"class":5452},[5280,21855,21740],{"class":5758},[5280,21857,21858],{"class":5282,"line":5391},[5280,21859,5976],{"class":5452},[5280,21861,21862],{"class":5282,"line":5705},[5280,21863,5313],{"emptyLinePlaceholder":5312},[5280,21865,21866],{"class":5282,"line":5820},[5280,21867,21868],{"class":5286},"\u002F\u002F Стандартні помилки домену\n",[5280,21870,21871,21873],{"class":5282,"line":5854},[5280,21872,5823],{"class":5619},[5280,21874,5453],{"class":5452},[5280,21876,21877,21880,21883,21885,21887,21889,21891,21894],{"class":5282,"line":5859},[5280,21878,21879],{"class":5424},"    ErrUserNotFound",[5280,21881,21882],{"class":5452},"      = ",[5280,21884,20197],{"class":5424},[5280,21886,5218],{"class":5452},[5280,21888,20202],{"class":5293},[5280,21890,5835],{"class":5452},[5280,21892,21893],{"class":5297},"\"user not found\"",[5280,21895,5598],{"class":5452},[5280,21897,21898,21901,21903,21905,21907,21909,21911,21914],{"class":5282,"line":5871},[5280,21899,21900],{"class":5424},"    ErrUserAlreadyExists",[5280,21902,5829],{"class":5452},[5280,21904,20197],{"class":5424},[5280,21906,5218],{"class":5452},[5280,21908,20202],{"class":5293},[5280,21910,5835],{"class":5452},[5280,21912,21913],{"class":5297},"\"user already exists\"",[5280,21915,5598],{"class":5452},[5280,21917,21918,21921,21923,21925,21927,21929,21931,21934],{"class":5282,"line":5895},[5280,21919,21920],{"class":5424},"    ErrInvalidCredentials",[5280,21922,5829],{"class":5452},[5280,21924,20197],{"class":5424},[5280,21926,5218],{"class":5452},[5280,21928,20202],{"class":5293},[5280,21930,5835],{"class":5452},[5280,21932,21933],{"class":5297},"\"invalid credentials\"",[5280,21935,5598],{"class":5452},[5280,21937,21938],{"class":5282,"line":5916},[5280,21939,5598],{"class":5452},[5262,21941,21942],{},"Тепер створюємо Use Case:",[5271,21944,21946],{"className":5411,"code":21945,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fusecase\u002Fuser_usecase.go\npackage usecase\n\nimport (\n    \"context\"\n    \"myapp\u002Finternal\u002Fdomain\"\n    \"time\"\n\n    \"golang.org\u002Fx\u002Fcrypto\u002Fbcrypt\"\n)\n\n\u002F\u002F UserUseCase — бізнес-логіка роботи з користувачами\ntype UserUseCase struct {\n    repo domain.UserRepository\n}\n\n\u002F\u002F NewUserUseCase створює новий UseCase\nfunc NewUserUseCase(repo domain.UserRepository) *UserUseCase {\n    return &UserUseCase{repo: repo}\n}\n\n\u002F\u002F RegisterUser реєструє нового користувача\nfunc (uc *UserUseCase) RegisterUser(ctx context.Context, name, email, plainPassword string) (*domain.User, error) {\n    \u002F\u002F 1. Перевіряємо, чи не існує користувач з таким email\n    existing, _ := uc.repo.FindByEmail(ctx, email)\n    if existing != nil {\n        return nil, domain.ErrUserAlreadyExists\n    }\n    \n    \u002F\u002F 2. Хешуємо пароль\n    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(plainPassword), bcrypt.DefaultCost)\n    if err != nil {\n        return nil, err\n    }\n    \n    \u002F\u002F 3. Створюємо доменну сутність\n    user := &domain.User{\n        ID:        generateUserID(),\n        Name:      name,\n        Email:     email,\n        Password:  string(hashedPassword),\n        CreatedAt: time.Now(),\n        UpdatedAt: time.Now(),\n    }\n    \n    \u002F\u002F 4. Валідуємо доменні правила\n    if err := user.Validate(); err != nil {\n        return nil, err\n    }\n    \n    \u002F\u002F 5. Зберігаємо через репозиторій\n    if err := uc.repo.Create(ctx, user); err != nil {\n        return nil, err\n    }\n    \n    return user, nil\n}\n\n\u002F\u002F Login виконує вхід користувача\nfunc (uc *UserUseCase) Login(ctx context.Context, email, plainPassword string) (*domain.User, error) {\n    \u002F\u002F 1. Знаходимо користувача\n    user, err := uc.repo.FindByEmail(ctx, email)\n    if err != nil {\n        return nil, domain.ErrInvalidCredentials\n    }\n    \n    \u002F\u002F 2. Перевіряємо пароль\n    if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(plainPassword)); err != nil {\n        return nil, domain.ErrInvalidCredentials\n    }\n    \n    return user, nil\n}\n\nfunc generateUserID() string {\n    \u002F\u002F У реальності: UUID або ULID\n    return fmt.Sprintf(\"user-%d\", time.Now().UnixNano())\n}\n",[5215,21947,21948,21953,21960,21964,21970,21974,21979,21983,21987,21992,21996,22000,22005,22016,22029,22033,22037,22042,22068,22086,22090,22094,22099,22155,22160,22194,22207,22222,22226,22230,22235,22274,22286,22297,22301,22305,22310,22324,22335,22345,22355,22369,22383,22398,22402,22406,22411,22435,22445,22449,22453,22458,22495,22505,22509,22513,22523,22527,22531,22536,22587,22592,22622,22634,22649,22653,22657,22662,22709,22723,22727,22731,22741,22745,22749,22762,22767,22801],{"__ignoreMap":5276},[5280,21949,21950],{"class":5282,"line":5283},[5280,21951,21952],{"class":5286},"\u002F\u002F internal\u002Fusecase\u002Fuser_usecase.go\n",[5280,21954,21955,21957],{"class":5282,"line":5290},[5280,21956,5755],{"class":5619},[5280,21958,21959],{"class":5758}," usecase\n",[5280,21961,21962],{"class":5282,"line":5301},[5280,21963,5313],{"emptyLinePlaceholder":5312},[5280,21965,21966,21968],{"class":5282,"line":5309},[5280,21967,5768],{"class":5436},[5280,21969,5453],{"class":5452},[5280,21971,21972],{"class":5282,"line":5316},[5280,21973,14862],{"class":5297},[5280,21975,21976],{"class":5282,"line":5322},[5280,21977,21978],{"class":5297},"    \"myapp\u002Finternal\u002Fdomain\"\n",[5280,21980,21981],{"class":5282,"line":5336},[5280,21982,5795],{"class":5297},[5280,21984,21985],{"class":5282,"line":5341},[5280,21986,5313],{"emptyLinePlaceholder":5312},[5280,21988,21989],{"class":5282,"line":5347},[5280,21990,21991],{"class":5297},"    \"golang.org\u002Fx\u002Fcrypto\u002Fbcrypt\"\n",[5280,21993,21994],{"class":5282,"line":5358},[5280,21995,5598],{"class":5452},[5280,21997,21998],{"class":5282,"line":5368},[5280,21999,5313],{"emptyLinePlaceholder":5312},[5280,22001,22002],{"class":5282,"line":5378},[5280,22003,22004],{"class":5286},"\u002F\u002F UserUseCase — бізнес-логіка роботи з користувачами\n",[5280,22006,22007,22009,22012,22014],{"class":5282,"line":5391},[5280,22008,7921],{"class":5619},[5280,22010,22011],{"class":5758}," UserUseCase",[5280,22013,6216],{"class":5619},[5280,22015,6111],{"class":5452},[5280,22017,22018,22021,22024,22026],{"class":5282,"line":5705},[5280,22019,22020],{"class":5424},"    repo",[5280,22022,22023],{"class":5758}," domain",[5280,22025,5218],{"class":5452},[5280,22027,22028],{"class":5758},"UserRepository\n",[5280,22030,22031],{"class":5282,"line":5820},[5280,22032,5976],{"class":5452},[5280,22034,22035],{"class":5282,"line":5854},[5280,22036,5313],{"emptyLinePlaceholder":5312},[5280,22038,22039],{"class":5282,"line":5859},[5280,22040,22041],{"class":5286},"\u002F\u002F NewUserUseCase створює новий UseCase\n",[5280,22043,22044,22046,22049,22051,22054,22056,22058,22061,22063,22066],{"class":5282,"line":5871},[5280,22045,5862],{"class":5619},[5280,22047,22048],{"class":5293}," NewUserUseCase",[5280,22050,5835],{"class":5452},[5280,22052,22053],{"class":5424},"repo",[5280,22055,22023],{"class":5758},[5280,22057,5218],{"class":5452},[5280,22059,22060],{"class":5758},"UserRepository",[5280,22062,13795],{"class":5452},[5280,22064,22065],{"class":5758},"UserUseCase",[5280,22067,6111],{"class":5452},[5280,22069,22070,22072,22074,22076,22078,22080,22082,22084],{"class":5282,"line":5895},[5280,22071,7328],{"class":5436},[5280,22073,13807],{"class":5452},[5280,22075,22065],{"class":5758},[5280,22077,12000],{"class":5452},[5280,22079,22053],{"class":5424},[5280,22081,6790],{"class":5452},[5280,22083,22053],{"class":5424},[5280,22085,5976],{"class":5452},[5280,22087,22088],{"class":5282,"line":5916},[5280,22089,5976],{"class":5452},[5280,22091,22092],{"class":5282,"line":5921},[5280,22093,5313],{"emptyLinePlaceholder":5312},[5280,22095,22096],{"class":5282,"line":5939},[5280,22097,22098],{"class":5286},"\u002F\u002F RegisterUser реєструє нового користувача\n",[5280,22100,22101,22103,22105,22108,22110,22112,22114,22116,22118,22120,22122,22124,22126,22128,22130,22132,22134,22136,22138,22140,22142,22145,22147,22149,22151,22153],{"class":5282,"line":5973},[5280,22102,5862],{"class":5619},[5280,22104,5720],{"class":5452},[5280,22106,22107],{"class":5424},"uc ",[5280,22109,8072],{"class":5452},[5280,22111,22065],{"class":5758},[5280,22113,7315],{"class":5452},[5280,22115,20688],{"class":5293},[5280,22117,5835],{"class":5452},[5280,22119,15441],{"class":5424},[5280,22121,21723],{"class":5758},[5280,22123,5218],{"class":5452},[5280,22125,15393],{"class":5758},[5280,22127,5887],{"class":5452},[5280,22129,20121],{"class":5424},[5280,22131,5887],{"class":5452},[5280,22133,20126],{"class":5424},[5280,22135,5887],{"class":5452},[5280,22137,21580],{"class":5424},[5280,22139,6239],{"class":5758},[5280,22141,20151],{"class":5452},[5280,22143,22144],{"class":5758},"domain",[5280,22146,5218],{"class":5452},[5280,22148,17513],{"class":5758},[5280,22150,5887],{"class":5452},[5280,22152,9567],{"class":5758},[5280,22154,6020],{"class":5452},[5280,22156,22157],{"class":5282,"line":5979},[5280,22158,22159],{"class":5286},"    \u002F\u002F 1. Перевіряємо, чи не існує користувач з таким email\n",[5280,22161,22162,22165,22167,22170,22172,22175,22177,22179,22181,22184,22186,22188,22190,22192],{"class":5282,"line":5984},[5280,22163,22164],{"class":5424},"    existing",[5280,22166,5887],{"class":5452},[5280,22168,22169],{"class":5424},"_",[5280,22171,6029],{"class":5452},[5280,22173,22174],{"class":5424},"uc",[5280,22176,5218],{"class":5452},[5280,22178,22053],{"class":5424},[5280,22180,5218],{"class":5452},[5280,22182,22183],{"class":5293},"FindByEmail",[5280,22185,5835],{"class":5452},[5280,22187,15441],{"class":5424},[5280,22189,5887],{"class":5452},[5280,22191,20126],{"class":5424},[5280,22193,5598],{"class":5452},[5280,22195,22196,22198,22201,22203,22205],{"class":5282,"line":6023},[5280,22197,7023],{"class":5436},[5280,22199,22200],{"class":5424}," existing",[5280,22202,6301],{"class":5452},[5280,22204,5967],{"class":5619},[5280,22206,6111],{"class":5452},[5280,22208,22209,22211,22213,22215,22217,22219],{"class":5282,"line":6042},[5280,22210,12200],{"class":5436},[5280,22212,20192],{"class":5619},[5280,22214,5887],{"class":5452},[5280,22216,22144],{"class":5424},[5280,22218,5218],{"class":5452},[5280,22220,22221],{"class":5424},"ErrUserAlreadyExists\n",[5280,22223,22224],{"class":5282,"line":6093},[5280,22225,6553],{"class":5452},[5280,22227,22228],{"class":5282,"line":6098},[5280,22229,6724],{"class":5452},[5280,22231,22232],{"class":5282,"line":6114},[5280,22233,22234],{"class":5286},"    \u002F\u002F 2. Хешуємо пароль\n",[5280,22236,22237,22239,22241,22243,22245,22248,22250,22253,22256,22258,22260,22262,22265,22267,22269,22272],{"class":5282,"line":6130},[5280,22238,19830],{"class":5424},[5280,22240,5887],{"class":5452},[5280,22242,6298],{"class":5424},[5280,22244,6029],{"class":5452},[5280,22246,22247],{"class":5424},"bcrypt",[5280,22249,5218],{"class":5452},[5280,22251,22252],{"class":5293},"GenerateFromPassword",[5280,22254,22255],{"class":5452},"([]",[5280,22257,9556],{"class":5758},[5280,22259,5835],{"class":5452},[5280,22261,21580],{"class":5424},[5280,22263,22264],{"class":5452},"), ",[5280,22266,22247],{"class":5424},[5280,22268,5218],{"class":5452},[5280,22270,22271],{"class":5424},"DefaultCost",[5280,22273,5598],{"class":5452},[5280,22275,22276,22278,22280,22282,22284],{"class":5282,"line":6159},[5280,22277,7023],{"class":5436},[5280,22279,6262],{"class":5424},[5280,22281,6301],{"class":5452},[5280,22283,5967],{"class":5619},[5280,22285,6111],{"class":5452},[5280,22287,22288,22290,22292,22294],{"class":5282,"line":6187},[5280,22289,12200],{"class":5436},[5280,22291,20192],{"class":5619},[5280,22293,5887],{"class":5452},[5280,22295,22296],{"class":5424},"err\n",[5280,22298,22299],{"class":5282,"line":6193},[5280,22300,6553],{"class":5452},[5280,22302,22303],{"class":5282,"line":6207},[5280,22304,6724],{"class":5452},[5280,22306,22307],{"class":5282,"line":6221},[5280,22308,22309],{"class":5286},"    \u002F\u002F 3. Створюємо доменну сутність\n",[5280,22311,22312,22314,22316,22318,22320,22322],{"class":5282,"line":6233},[5280,22313,18871],{"class":5424},[5280,22315,9745],{"class":5452},[5280,22317,22144],{"class":5758},[5280,22319,5218],{"class":5452},[5280,22321,17513],{"class":5758},[5280,22323,6781],{"class":5452},[5280,22325,22326,22328,22330,22333],{"class":5282,"line":6245},[5280,22327,17528],{"class":5424},[5280,22329,9788],{"class":5452},[5280,22331,22332],{"class":5293},"generateUserID",[5280,22334,17825],{"class":5452},[5280,22336,22337,22339,22341,22343],{"class":5282,"line":6251},[5280,22338,17540],{"class":5424},[5280,22340,17543],{"class":5452},[5280,22342,20121],{"class":5424},[5280,22344,6796],{"class":5452},[5280,22346,22347,22349,22351,22353],{"class":5282,"line":6256},[5280,22348,17553],{"class":5424},[5280,22350,9768],{"class":5452},[5280,22352,20126],{"class":5424},[5280,22354,6796],{"class":5452},[5280,22356,22357,22359,22361,22363,22365,22367],{"class":5282,"line":6308},[5280,22358,18415],{"class":5424},[5280,22360,19517],{"class":5452},[5280,22362,5844],{"class":5758},[5280,22364,5835],{"class":5452},[5280,22366,19913],{"class":5424},[5280,22368,6828],{"class":5452},[5280,22370,22371,22373,22375,22377,22379,22381],{"class":5282,"line":6339},[5280,22372,17565],{"class":5424},[5280,22374,6790],{"class":5452},[5280,22376,5587],{"class":5424},[5280,22378,5218],{"class":5452},[5280,22380,6036],{"class":5293},[5280,22382,17825],{"class":5452},[5280,22384,22385,22388,22390,22392,22394,22396],{"class":5282,"line":6371},[5280,22386,22387],{"class":5424},"        UpdatedAt",[5280,22389,6790],{"class":5452},[5280,22391,5587],{"class":5424},[5280,22393,5218],{"class":5452},[5280,22395,6036],{"class":5293},[5280,22397,17825],{"class":5452},[5280,22399,22400],{"class":5282,"line":6409},[5280,22401,6553],{"class":5452},[5280,22403,22404],{"class":5282,"line":6415},[5280,22405,6724],{"class":5452},[5280,22407,22408],{"class":5282,"line":6420},[5280,22409,22410],{"class":5286},"    \u002F\u002F 4. Валідуємо доменні правила\n",[5280,22412,22413,22415,22417,22419,22421,22423,22425,22427,22429,22431,22433],{"class":5282,"line":6425},[5280,22414,7023],{"class":5436},[5280,22416,6262],{"class":5424},[5280,22418,6029],{"class":5452},[5280,22420,6292],{"class":5424},[5280,22422,5218],{"class":5452},[5280,22424,21356],{"class":5293},[5280,22426,10615],{"class":5452},[5280,22428,6298],{"class":5424},[5280,22430,6301],{"class":5452},[5280,22432,5967],{"class":5619},[5280,22434,6111],{"class":5452},[5280,22436,22437,22439,22441,22443],{"class":5282,"line":6450},[5280,22438,12200],{"class":5436},[5280,22440,20192],{"class":5619},[5280,22442,5887],{"class":5452},[5280,22444,22296],{"class":5424},[5280,22446,22447],{"class":5282,"line":6471},[5280,22448,6553],{"class":5452},[5280,22450,22451],{"class":5282,"line":6507},[5280,22452,6724],{"class":5452},[5280,22454,22455],{"class":5282,"line":6512},[5280,22456,22457],{"class":5286},"    \u002F\u002F 5. Зберігаємо через репозиторій\n",[5280,22459,22460,22462,22464,22466,22468,22470,22472,22474,22477,22479,22481,22483,22485,22487,22489,22491,22493],{"class":5282,"line":6520},[5280,22461,7023],{"class":5436},[5280,22463,6262],{"class":5424},[5280,22465,6029],{"class":5452},[5280,22467,22174],{"class":5424},[5280,22469,5218],{"class":5452},[5280,22471,22053],{"class":5424},[5280,22473,5218],{"class":5452},[5280,22475,22476],{"class":5293},"Create",[5280,22478,5835],{"class":5452},[5280,22480,15441],{"class":5424},[5280,22482,5887],{"class":5452},[5280,22484,6292],{"class":5424},[5280,22486,6295],{"class":5452},[5280,22488,6298],{"class":5424},[5280,22490,6301],{"class":5452},[5280,22492,5967],{"class":5619},[5280,22494,6111],{"class":5452},[5280,22496,22497,22499,22501,22503],{"class":5282,"line":6550},[5280,22498,12200],{"class":5436},[5280,22500,20192],{"class":5619},[5280,22502,5887],{"class":5452},[5280,22504,22296],{"class":5424},[5280,22506,22507],{"class":5282,"line":6556},[5280,22508,6553],{"class":5452},[5280,22510,22511],{"class":5282,"line":6561},[5280,22512,6724],{"class":5452},[5280,22514,22515,22517,22519,22521],{"class":5282,"line":6621},[5280,22516,7328],{"class":5436},[5280,22518,6213],{"class":5424},[5280,22520,5887],{"class":5452},[5280,22522,20459],{"class":5619},[5280,22524,22525],{"class":5282,"line":6626},[5280,22526,5976],{"class":5452},[5280,22528,22529],{"class":5282,"line":6631},[5280,22530,5313],{"emptyLinePlaceholder":5312},[5280,22532,22533],{"class":5282,"line":6663},[5280,22534,22535],{"class":5286},"\u002F\u002F Login виконує вхід користувача\n",[5280,22537,22538,22540,22542,22544,22546,22548,22550,22553,22555,22557,22559,22561,22563,22565,22567,22569,22571,22573,22575,22577,22579,22581,22583,22585],{"class":5282,"line":6678},[5280,22539,5862],{"class":5619},[5280,22541,5720],{"class":5452},[5280,22543,22107],{"class":5424},[5280,22545,8072],{"class":5452},[5280,22547,22065],{"class":5758},[5280,22549,7315],{"class":5452},[5280,22551,22552],{"class":5293},"Login",[5280,22554,5835],{"class":5452},[5280,22556,15441],{"class":5424},[5280,22558,21723],{"class":5758},[5280,22560,5218],{"class":5452},[5280,22562,15393],{"class":5758},[5280,22564,5887],{"class":5452},[5280,22566,20126],{"class":5424},[5280,22568,5887],{"class":5452},[5280,22570,21580],{"class":5424},[5280,22572,6239],{"class":5758},[5280,22574,20151],{"class":5452},[5280,22576,22144],{"class":5758},[5280,22578,5218],{"class":5452},[5280,22580,17513],{"class":5758},[5280,22582,5887],{"class":5452},[5280,22584,9567],{"class":5758},[5280,22586,6020],{"class":5452},[5280,22588,22589],{"class":5282,"line":6721},[5280,22590,22591],{"class":5286},"    \u002F\u002F 1. Знаходимо користувача\n",[5280,22593,22594,22596,22598,22600,22602,22604,22606,22608,22610,22612,22614,22616,22618,22620],{"class":5282,"line":6727},[5280,22595,18871],{"class":5424},[5280,22597,5887],{"class":5452},[5280,22599,6298],{"class":5424},[5280,22601,6029],{"class":5452},[5280,22603,22174],{"class":5424},[5280,22605,5218],{"class":5452},[5280,22607,22053],{"class":5424},[5280,22609,5218],{"class":5452},[5280,22611,22183],{"class":5293},[5280,22613,5835],{"class":5452},[5280,22615,15441],{"class":5424},[5280,22617,5887],{"class":5452},[5280,22619,20126],{"class":5424},[5280,22621,5598],{"class":5452},[5280,22623,22624,22626,22628,22630,22632],{"class":5282,"line":6751},[5280,22625,7023],{"class":5436},[5280,22627,6262],{"class":5424},[5280,22629,6301],{"class":5452},[5280,22631,5967],{"class":5619},[5280,22633,6111],{"class":5452},[5280,22635,22636,22638,22640,22642,22644,22646],{"class":5282,"line":6784},[5280,22637,12200],{"class":5436},[5280,22639,20192],{"class":5619},[5280,22641,5887],{"class":5452},[5280,22643,22144],{"class":5424},[5280,22645,5218],{"class":5452},[5280,22647,22648],{"class":5424},"ErrInvalidCredentials\n",[5280,22650,22651],{"class":5282,"line":6799},[5280,22652,6553],{"class":5452},[5280,22654,22655],{"class":5282,"line":6831},[5280,22656,6724],{"class":5452},[5280,22658,22659],{"class":5282,"line":6837},[5280,22660,22661],{"class":5286},"    \u002F\u002F 2. Перевіряємо пароль\n",[5280,22663,22664,22666,22668,22670,22672,22674,22677,22679,22681,22683,22685,22687,22689,22692,22694,22696,22698,22701,22703,22705,22707],{"class":5282,"line":6842},[5280,22665,7023],{"class":5436},[5280,22667,6262],{"class":5424},[5280,22669,6029],{"class":5452},[5280,22671,22247],{"class":5424},[5280,22673,5218],{"class":5452},[5280,22675,22676],{"class":5293},"CompareHashAndPassword",[5280,22678,22255],{"class":5452},[5280,22680,9556],{"class":5758},[5280,22682,5835],{"class":5452},[5280,22684,6292],{"class":5424},[5280,22686,5218],{"class":5452},[5280,22688,19687],{"class":5424},[5280,22690,22691],{"class":5452},"), []",[5280,22693,9556],{"class":5758},[5280,22695,5835],{"class":5452},[5280,22697,21580],{"class":5424},[5280,22699,22700],{"class":5452},")); ",[5280,22702,6298],{"class":5424},[5280,22704,6301],{"class":5452},[5280,22706,5967],{"class":5619},[5280,22708,6111],{"class":5452},[5280,22710,22711,22713,22715,22717,22719,22721],{"class":5282,"line":6901},[5280,22712,12200],{"class":5436},[5280,22714,20192],{"class":5619},[5280,22716,5887],{"class":5452},[5280,22718,22144],{"class":5424},[5280,22720,5218],{"class":5452},[5280,22722,22648],{"class":5424},[5280,22724,22725],{"class":5282,"line":12702},[5280,22726,6553],{"class":5452},[5280,22728,22729],{"class":5282,"line":12731},[5280,22730,6724],{"class":5452},[5280,22732,22733,22735,22737,22739],{"class":5282,"line":12736},[5280,22734,7328],{"class":5436},[5280,22736,6213],{"class":5424},[5280,22738,5887],{"class":5452},[5280,22740,20459],{"class":5619},[5280,22742,22743],{"class":5282,"line":12741},[5280,22744,5976],{"class":5452},[5280,22746,22747],{"class":5282,"line":12746},[5280,22748,5313],{"emptyLinePlaceholder":5312},[5280,22750,22751,22753,22756,22758,22760],{"class":5282,"line":12752},[5280,22752,5862],{"class":5619},[5280,22754,22755],{"class":5293}," generateUserID",[5280,22757,11974],{"class":5452},[5280,22759,5844],{"class":5758},[5280,22761,6111],{"class":5452},[5280,22763,22764],{"class":5282,"line":12774},[5280,22765,22766],{"class":5286},"    \u002F\u002F У реальності: UUID або ULID\n",[5280,22768,22769,22771,22774,22776,22778,22780,22782,22784,22786,22788,22790,22792,22794,22796,22799],{"class":5282,"line":12794},[5280,22770,7328],{"class":5436},[5280,22772,22773],{"class":5424}," fmt",[5280,22775,5218],{"class":5452},[5280,22777,12710],{"class":5293},[5280,22779,5835],{"class":5452},[5280,22781,18550],{"class":5297},[5280,22783,9946],{"class":5424},[5280,22785,6584],{"class":5297},[5280,22787,5887],{"class":5452},[5280,22789,5587],{"class":5424},[5280,22791,5218],{"class":5452},[5280,22793,6036],{"class":5293},[5280,22795,6141],{"class":5452},[5280,22797,22798],{"class":5293},"UnixNano",[5280,22800,10669],{"class":5452},[5280,22802,22803],{"class":5282,"line":12800},[5280,22804,5976],{"class":5452},[5262,22806,22807],{},[5227,22808,22809],{},"Ключові моменти:",[5194,22811,22812,22824,22827],{},[5197,22813,22814,22816,22817,7648,22820,22823],{},[5215,22815,22065],{}," залежить від ",[5227,22818,22819],{},"інтерфейсу",[5215,22821,22822],{},"domain.UserRepository",", а не від конкретної реалізації",[5197,22825,22826],{},"Бізнес-логіка не знає, де зберігаються дані (PostgreSQL? MongoDB? in-memory?)",[5197,22828,22829],{},"Немає залежності від HTTP, JSON, або будь-яких інших технічних деталей",[21159,22831,22833,22834,22837],{"id":22832},"_3-repository-pattern-абстракція-доступу-до-даних","3. ",[5227,22835,22836],{},"Repository Pattern"," — абстракція доступу до даних",[5262,22839,22840,22841,22844],{},"Repository — це ",[5227,22842,22843],{},"міст між бізнес-логікою та базою даних",". Інтерфейс визначається в Domain, реалізація — у зовнішньому шарі.",[5262,22846,22847],{},[5227,22848,22849],{},"In-memory реалізація (для тестування та прототипування):",[5271,22851,22853],{"className":5411,"code":22852,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Frepository\u002Fmemory\u002Fuser_repository.go\npackage memory\n\nimport (\n    \"context\"\n    \"myapp\u002Finternal\u002Fdomain\"\n    \"sync\"\n)\n\n\u002F\u002F UserRepository — in-memory реалізація\ntype UserRepository struct {\n    mu    sync.RWMutex\n    users map[string]*domain.User\n}\n\n\u002F\u002F NewUserRepository створює новий in-memory репозиторій\nfunc NewUserRepository() *UserRepository {\n    return &UserRepository{\n        users: make(map[string]*domain.User),\n    }\n}\n\nfunc (r *UserRepository) Create(ctx context.Context, user *domain.User) error {\n    r.mu.Lock()\n    defer r.mu.Unlock()\n    \n    r.users[user.ID] = user\n    return nil\n}\n\nfunc (r *UserRepository) FindByID(ctx context.Context, id string) (*domain.User, error) {\n    r.mu.RLock()\n    defer r.mu.RUnlock()\n    \n    user, exists := r.users[id]\n    if !exists {\n        return nil, domain.ErrUserNotFound\n    }\n    return user, nil\n}\n\nfunc (r *UserRepository) FindByEmail(ctx context.Context, email string) (*domain.User, error) {\n    r.mu.RLock()\n    defer r.mu.RUnlock()\n    \n    for _, user := range r.users {\n        if user.Email == email {\n            return user, nil\n        }\n    }\n    return nil, domain.ErrUserNotFound\n}\n\nfunc (r *UserRepository) Update(ctx context.Context, user *domain.User) error {\n    r.mu.Lock()\n    defer r.mu.Unlock()\n    \n    if _, exists := r.users[user.ID]; !exists {\n        return domain.ErrUserNotFound\n    }\n    \n    r.users[user.ID] = user\n    return nil\n}\n\nfunc (r *UserRepository) Delete(ctx context.Context, id string) error {\n    r.mu.Lock()\n    defer r.mu.Unlock()\n    \n    delete(r.users, id)\n    return nil\n}\n",[5215,22854,22855,22860,22867,22871,22877,22881,22885,22889,22893,22897,22902,22912,22924,22943,22947,22951,22956,22970,22980,23006,23010,23014,23018,23063,23077,23093,23097,23117,23123,23127,23131,23178,23193,23210,23214,23236,23246,23261,23265,23275,23279,23283,23329,23343,23359,23363,23385,23401,23411,23415,23419,23433,23437,23441,23486,23500,23516,23520,23553,23563,23567,23571,23591,23597,23601,23605,23644,23658,23674,23678,23697,23703],{"__ignoreMap":5276},[5280,22856,22857],{"class":5282,"line":5283},[5280,22858,22859],{"class":5286},"\u002F\u002F internal\u002Frepository\u002Fmemory\u002Fuser_repository.go\n",[5280,22861,22862,22864],{"class":5282,"line":5290},[5280,22863,5755],{"class":5619},[5280,22865,22866],{"class":5758}," memory\n",[5280,22868,22869],{"class":5282,"line":5301},[5280,22870,5313],{"emptyLinePlaceholder":5312},[5280,22872,22873,22875],{"class":5282,"line":5309},[5280,22874,5768],{"class":5436},[5280,22876,5453],{"class":5452},[5280,22878,22879],{"class":5282,"line":5316},[5280,22880,14862],{"class":5297},[5280,22882,22883],{"class":5282,"line":5322},[5280,22884,21978],{"class":5297},[5280,22886,22887],{"class":5282,"line":5336},[5280,22888,13659],{"class":5297},[5280,22890,22891],{"class":5282,"line":5341},[5280,22892,5598],{"class":5452},[5280,22894,22895],{"class":5282,"line":5347},[5280,22896,5313],{"emptyLinePlaceholder":5312},[5280,22898,22899],{"class":5282,"line":5358},[5280,22900,22901],{"class":5286},"\u002F\u002F UserRepository — in-memory реалізація\n",[5280,22903,22904,22906,22908,22910],{"class":5282,"line":5368},[5280,22905,7921],{"class":5619},[5280,22907,21707],{"class":5758},[5280,22909,6216],{"class":5619},[5280,22911,6111],{"class":5452},[5280,22913,22914,22916,22919,22921],{"class":5282,"line":5378},[5280,22915,13701],{"class":5424},[5280,22917,22918],{"class":5758},"    sync",[5280,22920,5218],{"class":5452},[5280,22922,22923],{"class":5758},"RWMutex\n",[5280,22925,22926,22928,22930,22932,22934,22936,22938,22940],{"class":5282,"line":5391},[5280,22927,18625],{"class":5424},[5280,22929,13717],{"class":5619},[5280,22931,5841],{"class":5452},[5280,22933,5844],{"class":5758},[5280,22935,13724],{"class":5452},[5280,22937,22144],{"class":5758},[5280,22939,5218],{"class":5452},[5280,22941,22942],{"class":5758},"User\n",[5280,22944,22945],{"class":5282,"line":5705},[5280,22946,5976],{"class":5452},[5280,22948,22949],{"class":5282,"line":5820},[5280,22950,5313],{"emptyLinePlaceholder":5312},[5280,22952,22953],{"class":5282,"line":5854},[5280,22954,22955],{"class":5286},"\u002F\u002F NewUserRepository створює новий in-memory репозиторій\n",[5280,22957,22958,22960,22963,22966,22968],{"class":5282,"line":5859},[5280,22959,5862],{"class":5619},[5280,22961,22962],{"class":5293}," NewUserRepository",[5280,22964,22965],{"class":5452},"() *",[5280,22967,22060],{"class":5758},[5280,22969,6111],{"class":5452},[5280,22971,22972,22974,22976,22978],{"class":5282,"line":5871},[5280,22973,7328],{"class":5436},[5280,22975,13807],{"class":5452},[5280,22977,22060],{"class":5758},[5280,22979,6781],{"class":5452},[5280,22981,22982,22984,22986,22988,22990,22992,22994,22996,22998,23000,23002,23004],{"class":5282,"line":5895},[5280,22983,6428],{"class":5424},[5280,22985,6790],{"class":5452},[5280,22987,5832],{"class":5293},[5280,22989,5835],{"class":5452},[5280,22991,5838],{"class":5619},[5280,22993,5841],{"class":5452},[5280,22995,5844],{"class":5758},[5280,22997,13724],{"class":5452},[5280,22999,22144],{"class":5758},[5280,23001,5218],{"class":5452},[5280,23003,17513],{"class":5758},[5280,23005,6828],{"class":5452},[5280,23007,23008],{"class":5282,"line":5916},[5280,23009,6553],{"class":5452},[5280,23011,23012],{"class":5282,"line":5921},[5280,23013,5976],{"class":5452},[5280,23015,23016],{"class":5282,"line":5939},[5280,23017,5313],{"emptyLinePlaceholder":5312},[5280,23019,23020,23022,23024,23027,23029,23031,23033,23035,23037,23039,23041,23043,23045,23047,23049,23051,23053,23055,23057,23059,23061],{"class":5282,"line":5973},[5280,23021,5862],{"class":5619},[5280,23023,5720],{"class":5452},[5280,23025,23026],{"class":5424},"r ",[5280,23028,8072],{"class":5452},[5280,23030,22060],{"class":5758},[5280,23032,7315],{"class":5452},[5280,23034,22476],{"class":5293},[5280,23036,5835],{"class":5452},[5280,23038,15441],{"class":5424},[5280,23040,21723],{"class":5758},[5280,23042,5218],{"class":5452},[5280,23044,15393],{"class":5758},[5280,23046,5887],{"class":5452},[5280,23048,6292],{"class":5424},[5280,23050,6010],{"class":5452},[5280,23052,22144],{"class":5758},[5280,23054,5218],{"class":5452},[5280,23056,17513],{"class":5758},[5280,23058,7315],{"class":5452},[5280,23060,9567],{"class":5758},[5280,23062,6111],{"class":5452},[5280,23064,23065,23067,23069,23071,23073,23075],{"class":5282,"line":5979},[5280,23066,16443],{"class":5424},[5280,23068,5218],{"class":5452},[5280,23070,13933],{"class":5424},[5280,23072,5218],{"class":5452},[5280,23074,13938],{"class":5293},[5280,23076,6039],{"class":5452},[5280,23078,23079,23081,23083,23085,23087,23089,23091],{"class":5282,"line":5984},[5280,23080,7217],{"class":5436},[5280,23082,6104],{"class":5424},[5280,23084,5218],{"class":5452},[5280,23086,13933],{"class":5424},[5280,23088,5218],{"class":5452},[5280,23090,13956],{"class":5293},[5280,23092,6039],{"class":5452},[5280,23094,23095],{"class":5282,"line":6023},[5280,23096,6724],{"class":5452},[5280,23098,23099,23101,23103,23105,23107,23109,23111,23113,23115],{"class":5282,"line":6042},[5280,23100,16443],{"class":5424},[5280,23102,5218],{"class":5452},[5280,23104,6182],{"class":5424},[5280,23106,5841],{"class":5452},[5280,23108,6292],{"class":5424},[5280,23110,5218],{"class":5452},[5280,23112,18635],{"class":5424},[5280,23114,6440],{"class":5452},[5280,23116,20794],{"class":5424},[5280,23118,23119,23121],{"class":5282,"line":6093},[5280,23120,7328],{"class":5436},[5280,23122,21545],{"class":5619},[5280,23124,23125],{"class":5282,"line":6098},[5280,23126,5976],{"class":5452},[5280,23128,23129],{"class":5282,"line":6114},[5280,23130,5313],{"emptyLinePlaceholder":5312},[5280,23132,23133,23135,23137,23139,23141,23143,23145,23148,23150,23152,23154,23156,23158,23160,23162,23164,23166,23168,23170,23172,23174,23176],{"class":5282,"line":6130},[5280,23134,5862],{"class":5619},[5280,23136,5720],{"class":5452},[5280,23138,23026],{"class":5424},[5280,23140,8072],{"class":5452},[5280,23142,22060],{"class":5758},[5280,23144,7315],{"class":5452},[5280,23146,23147],{"class":5293},"FindByID",[5280,23149,5835],{"class":5452},[5280,23151,15441],{"class":5424},[5280,23153,21723],{"class":5758},[5280,23155,5218],{"class":5452},[5280,23157,15393],{"class":5758},[5280,23159,5887],{"class":5452},[5280,23161,21760],{"class":5424},[5280,23163,6239],{"class":5758},[5280,23165,20151],{"class":5452},[5280,23167,22144],{"class":5758},[5280,23169,5218],{"class":5452},[5280,23171,17513],{"class":5758},[5280,23173,5887],{"class":5452},[5280,23175,9567],{"class":5758},[5280,23177,6020],{"class":5452},[5280,23179,23180,23182,23184,23186,23188,23191],{"class":5282,"line":6159},[5280,23181,16443],{"class":5424},[5280,23183,5218],{"class":5452},[5280,23185,13933],{"class":5424},[5280,23187,5218],{"class":5452},[5280,23189,23190],{"class":5293},"RLock",[5280,23192,6039],{"class":5452},[5280,23194,23195,23197,23199,23201,23203,23205,23208],{"class":5282,"line":6187},[5280,23196,7217],{"class":5436},[5280,23198,6104],{"class":5424},[5280,23200,5218],{"class":5452},[5280,23202,13933],{"class":5424},[5280,23204,5218],{"class":5452},[5280,23206,23207],{"class":5293},"RUnlock",[5280,23209,6039],{"class":5452},[5280,23211,23212],{"class":5282,"line":6193},[5280,23213,6724],{"class":5452},[5280,23215,23216,23218,23220,23222,23224,23226,23228,23230,23232,23234],{"class":5282,"line":6207},[5280,23217,18871],{"class":5424},[5280,23219,5887],{"class":5452},[5280,23221,13972],{"class":5424},[5280,23223,6029],{"class":5452},[5280,23225,6007],{"class":5424},[5280,23227,5218],{"class":5452},[5280,23229,6182],{"class":5424},[5280,23231,5841],{"class":5452},[5280,23233,21760],{"class":5424},[5280,23235,13989],{"class":5452},[5280,23237,23238,23240,23242,23244],{"class":5282,"line":6221},[5280,23239,7023],{"class":5436},[5280,23241,9455],{"class":5452},[5280,23243,13972],{"class":5424},[5280,23245,6111],{"class":5452},[5280,23247,23248,23250,23252,23254,23256,23258],{"class":5282,"line":6233},[5280,23249,12200],{"class":5436},[5280,23251,20192],{"class":5619},[5280,23253,5887],{"class":5452},[5280,23255,22144],{"class":5424},[5280,23257,5218],{"class":5452},[5280,23259,23260],{"class":5424},"ErrUserNotFound\n",[5280,23262,23263],{"class":5282,"line":6245},[5280,23264,6553],{"class":5452},[5280,23266,23267,23269,23271,23273],{"class":5282,"line":6251},[5280,23268,7328],{"class":5436},[5280,23270,6213],{"class":5424},[5280,23272,5887],{"class":5452},[5280,23274,20459],{"class":5619},[5280,23276,23277],{"class":5282,"line":6256},[5280,23278,5976],{"class":5452},[5280,23280,23281],{"class":5282,"line":6308},[5280,23282,5313],{"emptyLinePlaceholder":5312},[5280,23284,23285,23287,23289,23291,23293,23295,23297,23299,23301,23303,23305,23307,23309,23311,23313,23315,23317,23319,23321,23323,23325,23327],{"class":5282,"line":6339},[5280,23286,5862],{"class":5619},[5280,23288,5720],{"class":5452},[5280,23290,23026],{"class":5424},[5280,23292,8072],{"class":5452},[5280,23294,22060],{"class":5758},[5280,23296,7315],{"class":5452},[5280,23298,22183],{"class":5293},[5280,23300,5835],{"class":5452},[5280,23302,15441],{"class":5424},[5280,23304,21723],{"class":5758},[5280,23306,5218],{"class":5452},[5280,23308,15393],{"class":5758},[5280,23310,5887],{"class":5452},[5280,23312,20126],{"class":5424},[5280,23314,6239],{"class":5758},[5280,23316,20151],{"class":5452},[5280,23318,22144],{"class":5758},[5280,23320,5218],{"class":5452},[5280,23322,17513],{"class":5758},[5280,23324,5887],{"class":5452},[5280,23326,9567],{"class":5758},[5280,23328,6020],{"class":5452},[5280,23330,23331,23333,23335,23337,23339,23341],{"class":5282,"line":6371},[5280,23332,16443],{"class":5424},[5280,23334,5218],{"class":5452},[5280,23336,13933],{"class":5424},[5280,23338,5218],{"class":5452},[5280,23340,23190],{"class":5293},[5280,23342,6039],{"class":5452},[5280,23344,23345,23347,23349,23351,23353,23355,23357],{"class":5282,"line":6409},[5280,23346,7217],{"class":5436},[5280,23348,6104],{"class":5424},[5280,23350,5218],{"class":5452},[5280,23352,13933],{"class":5424},[5280,23354,5218],{"class":5452},[5280,23356,23207],{"class":5293},[5280,23358,6039],{"class":5452},[5280,23360,23361],{"class":5282,"line":6415},[5280,23362,6724],{"class":5452},[5280,23364,23365,23367,23369,23371,23373,23375,23377,23379,23381,23383],{"class":5282,"line":6420},[5280,23366,11383],{"class":5436},[5280,23368,12286],{"class":5424},[5280,23370,5887],{"class":5452},[5280,23372,6292],{"class":5424},[5280,23374,6029],{"class":5452},[5280,23376,12296],{"class":5436},[5280,23378,6104],{"class":5424},[5280,23380,5218],{"class":5452},[5280,23382,6182],{"class":5424},[5280,23384,6111],{"class":5452},[5280,23386,23387,23389,23391,23393,23395,23397,23399],{"class":5282,"line":6425},[5280,23388,6259],{"class":5436},[5280,23390,6213],{"class":5424},[5280,23392,5218],{"class":5452},[5280,23394,6437],{"class":5424},[5280,23396,7029],{"class":5452},[5280,23398,20126],{"class":5424},[5280,23400,6111],{"class":5452},[5280,23402,23403,23405,23407,23409],{"class":5282,"line":6450},[5280,23404,20309],{"class":5436},[5280,23406,6213],{"class":5424},[5280,23408,5887],{"class":5452},[5280,23410,20459],{"class":5619},[5280,23412,23413],{"class":5282,"line":6471},[5280,23414,6248],{"class":5452},[5280,23416,23417],{"class":5282,"line":6507},[5280,23418,6553],{"class":5452},[5280,23420,23421,23423,23425,23427,23429,23431],{"class":5282,"line":6512},[5280,23422,7328],{"class":5436},[5280,23424,20192],{"class":5619},[5280,23426,5887],{"class":5452},[5280,23428,22144],{"class":5424},[5280,23430,5218],{"class":5452},[5280,23432,23260],{"class":5424},[5280,23434,23435],{"class":5282,"line":6520},[5280,23436,5976],{"class":5452},[5280,23438,23439],{"class":5282,"line":6550},[5280,23440,5313],{"emptyLinePlaceholder":5312},[5280,23442,23443,23445,23447,23449,23451,23453,23455,23458,23460,23462,23464,23466,23468,23470,23472,23474,23476,23478,23480,23482,23484],{"class":5282,"line":6556},[5280,23444,5862],{"class":5619},[5280,23446,5720],{"class":5452},[5280,23448,23026],{"class":5424},[5280,23450,8072],{"class":5452},[5280,23452,22060],{"class":5758},[5280,23454,7315],{"class":5452},[5280,23456,23457],{"class":5293},"Update",[5280,23459,5835],{"class":5452},[5280,23461,15441],{"class":5424},[5280,23463,21723],{"class":5758},[5280,23465,5218],{"class":5452},[5280,23467,15393],{"class":5758},[5280,23469,5887],{"class":5452},[5280,23471,6292],{"class":5424},[5280,23473,6010],{"class":5452},[5280,23475,22144],{"class":5758},[5280,23477,5218],{"class":5452},[5280,23479,17513],{"class":5758},[5280,23481,7315],{"class":5452},[5280,23483,9567],{"class":5758},[5280,23485,6111],{"class":5452},[5280,23487,23488,23490,23492,23494,23496,23498],{"class":5282,"line":6561},[5280,23489,16443],{"class":5424},[5280,23491,5218],{"class":5452},[5280,23493,13933],{"class":5424},[5280,23495,5218],{"class":5452},[5280,23497,13938],{"class":5293},[5280,23499,6039],{"class":5452},[5280,23501,23502,23504,23506,23508,23510,23512,23514],{"class":5282,"line":6621},[5280,23503,7217],{"class":5436},[5280,23505,6104],{"class":5424},[5280,23507,5218],{"class":5452},[5280,23509,13933],{"class":5424},[5280,23511,5218],{"class":5452},[5280,23513,13956],{"class":5293},[5280,23515,6039],{"class":5452},[5280,23517,23518],{"class":5282,"line":6626},[5280,23519,6724],{"class":5452},[5280,23521,23522,23524,23526,23528,23530,23532,23534,23536,23538,23540,23542,23544,23546,23549,23551],{"class":5282,"line":6631},[5280,23523,7023],{"class":5436},[5280,23525,12286],{"class":5424},[5280,23527,5887],{"class":5452},[5280,23529,13972],{"class":5424},[5280,23531,6029],{"class":5452},[5280,23533,6007],{"class":5424},[5280,23535,5218],{"class":5452},[5280,23537,6182],{"class":5424},[5280,23539,5841],{"class":5452},[5280,23541,6292],{"class":5424},[5280,23543,5218],{"class":5452},[5280,23545,18635],{"class":5424},[5280,23547,23548],{"class":5452},"]; !",[5280,23550,13972],{"class":5424},[5280,23552,6111],{"class":5452},[5280,23554,23555,23557,23559,23561],{"class":5282,"line":6663},[5280,23556,12200],{"class":5436},[5280,23558,22023],{"class":5424},[5280,23560,5218],{"class":5452},[5280,23562,23260],{"class":5424},[5280,23564,23565],{"class":5282,"line":6678},[5280,23566,6553],{"class":5452},[5280,23568,23569],{"class":5282,"line":6721},[5280,23570,6724],{"class":5452},[5280,23572,23573,23575,23577,23579,23581,23583,23585,23587,23589],{"class":5282,"line":6727},[5280,23574,16443],{"class":5424},[5280,23576,5218],{"class":5452},[5280,23578,6182],{"class":5424},[5280,23580,5841],{"class":5452},[5280,23582,6292],{"class":5424},[5280,23584,5218],{"class":5452},[5280,23586,18635],{"class":5424},[5280,23588,6440],{"class":5452},[5280,23590,20794],{"class":5424},[5280,23592,23593,23595],{"class":5282,"line":6751},[5280,23594,7328],{"class":5436},[5280,23596,21545],{"class":5619},[5280,23598,23599],{"class":5282,"line":6784},[5280,23600,5976],{"class":5452},[5280,23602,23603],{"class":5282,"line":6799},[5280,23604,5313],{"emptyLinePlaceholder":5312},[5280,23606,23607,23609,23611,23613,23615,23617,23619,23622,23624,23626,23628,23630,23632,23634,23636,23638,23640,23642],{"class":5282,"line":6831},[5280,23608,5862],{"class":5619},[5280,23610,5720],{"class":5452},[5280,23612,23026],{"class":5424},[5280,23614,8072],{"class":5452},[5280,23616,22060],{"class":5758},[5280,23618,7315],{"class":5452},[5280,23620,23621],{"class":5293},"Delete",[5280,23623,5835],{"class":5452},[5280,23625,15441],{"class":5424},[5280,23627,21723],{"class":5758},[5280,23629,5218],{"class":5452},[5280,23631,15393],{"class":5758},[5280,23633,5887],{"class":5452},[5280,23635,21760],{"class":5424},[5280,23637,6239],{"class":5758},[5280,23639,7315],{"class":5452},[5280,23641,9567],{"class":5758},[5280,23643,6111],{"class":5452},[5280,23645,23646,23648,23650,23652,23654,23656],{"class":5282,"line":6837},[5280,23647,16443],{"class":5424},[5280,23649,5218],{"class":5452},[5280,23651,13933],{"class":5424},[5280,23653,5218],{"class":5452},[5280,23655,13938],{"class":5293},[5280,23657,6039],{"class":5452},[5280,23659,23660,23662,23664,23666,23668,23670,23672],{"class":5282,"line":6842},[5280,23661,7217],{"class":5436},[5280,23663,6104],{"class":5424},[5280,23665,5218],{"class":5452},[5280,23667,13933],{"class":5424},[5280,23669,5218],{"class":5452},[5280,23671,13956],{"class":5293},[5280,23673,6039],{"class":5452},[5280,23675,23676],{"class":5282,"line":6901},[5280,23677,6724],{"class":5452},[5280,23679,23680,23683,23685,23687,23689,23691,23693,23695],{"class":5282,"line":12702},[5280,23681,23682],{"class":5293},"    delete",[5280,23684,5835],{"class":5452},[5280,23686,6007],{"class":5424},[5280,23688,5218],{"class":5452},[5280,23690,6182],{"class":5424},[5280,23692,5887],{"class":5452},[5280,23694,21760],{"class":5424},[5280,23696,5598],{"class":5452},[5280,23698,23699,23701],{"class":5282,"line":12731},[5280,23700,7328],{"class":5436},[5280,23702,21545],{"class":5619},[5280,23704,23705],{"class":5282,"line":12736},[5280,23706,5976],{"class":5452},[5262,23708,23709],{},[5227,23710,23711],{},"PostgreSQL реалізація (для продакшну):",[5271,23713,23715],{"className":5411,"code":23714,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Frepository\u002Fpostgres\u002Fuser_repository.go\npackage postgres\n\nimport (\n    \"context\"\n    \"database\u002Fsql\"\n    \"myapp\u002Finternal\u002Fdomain\"\n)\n\n\u002F\u002F UserRepository — PostgreSQL реалізація\ntype UserRepository struct {\n    db *sql.DB\n}\n\n\u002F\u002F NewUserRepository створює PostgreSQL репозиторій\nfunc NewUserRepository(db *sql.DB) *UserRepository {\n    return &UserRepository{db: db}\n}\n\nfunc (r *UserRepository) Create(ctx context.Context, user *domain.User) error {\n    query := `\n        INSERT INTO users (id, name, email, password, created_at, updated_at)\n        VALUES ($1, $2, $3, $4, $5, $6)\n    `\n    \n    _, err := r.db.ExecContext(ctx, query,\n        user.ID,\n        user.Name,\n        user.Email,\n        user.Password,\n        user.CreatedAt,\n        user.UpdatedAt,\n    )\n    \n    return err\n}\n\nfunc (r *UserRepository) FindByID(ctx context.Context, id string) (*domain.User, error) {\n    query := `\n        SELECT id, name, email, password, created_at, updated_at\n        FROM users\n        WHERE id = $1\n    `\n    \n    user := &domain.User{}\n    err := r.db.QueryRowContext(ctx, query, id).Scan(\n        &user.ID,\n        &user.Name,\n        &user.Email,\n        &user.Password,\n        &user.CreatedAt,\n        &user.UpdatedAt,\n    )\n    \n    if err == sql.ErrNoRows {\n        return nil, domain.ErrUserNotFound\n    }\n    \n    return user, err\n}\n\nfunc (r *UserRepository) FindByEmail(ctx context.Context, email string) (*domain.User, error) {\n    query := `\n        SELECT id, name, email, password, created_at, updated_at\n        FROM users\n        WHERE email = $1\n    `\n    \n    user := &domain.User{}\n    err := r.db.QueryRowContext(ctx, query, email).Scan(\n        &user.ID,\n        &user.Name,\n        &user.Email,\n        &user.Password,\n        &user.CreatedAt,\n        &user.UpdatedAt,\n    )\n    \n    if err == sql.ErrNoRows {\n        return nil, domain.ErrUserNotFound\n    }\n    \n    return user, err\n}\n\nfunc (r *UserRepository) Update(ctx context.Context, user *domain.User) error {\n    query := `\n        UPDATE users\n        SET name = $2, email = $3, password = $4, updated_at = $5\n        WHERE id = $1\n    `\n    \n    result, err := r.db.ExecContext(ctx, query,\n        user.ID,\n        user.Name,\n        user.Email,\n        user.Password,\n        user.UpdatedAt,\n    )\n    \n    if err != nil {\n        return err\n    }\n    \n    rows, _ := result.RowsAffected()\n    if rows == 0 {\n        return domain.ErrUserNotFound\n    }\n    \n    return nil\n}\n\nfunc (r *UserRepository) Delete(ctx context.Context, id string) error {\n    query := `DELETE FROM users WHERE id = $1`\n    _, err := r.db.ExecContext(ctx, query, id)\n    return err\n}\n",[5215,23716,23717,23722,23729,23733,23739,23743,23748,23752,23756,23760,23765,23775,23790,23794,23798,23803,23829,23847,23851,23855,23899,23909,23914,23919,23924,23928,23961,23972,23982,23992,24002,24013,24024,24028,24032,24039,24043,24047,24093,24101,24106,24111,24116,24120,24124,24138,24175,24188,24200,24212,24224,24236,24248,24252,24256,24273,24287,24291,24295,24305,24309,24313,24359,24367,24371,24375,24380,24384,24388,24402,24436,24448,24460,24472,24484,24496,24508,24512,24516,24532,24546,24550,24554,24564,24568,24572,24616,24624,24629,24634,24638,24642,24646,24677,24687,24697,24707,24717,24727,24731,24735,24747,24753,24757,24761,24782,24795,24805,24809,24813,24819,24823,24827,24865,24874,24908,24914],{"__ignoreMap":5276},[5280,23718,23719],{"class":5282,"line":5283},[5280,23720,23721],{"class":5286},"\u002F\u002F internal\u002Frepository\u002Fpostgres\u002Fuser_repository.go\n",[5280,23723,23724,23726],{"class":5282,"line":5290},[5280,23725,5755],{"class":5619},[5280,23727,23728],{"class":5758}," postgres\n",[5280,23730,23731],{"class":5282,"line":5301},[5280,23732,5313],{"emptyLinePlaceholder":5312},[5280,23734,23735,23737],{"class":5282,"line":5309},[5280,23736,5768],{"class":5436},[5280,23738,5453],{"class":5452},[5280,23740,23741],{"class":5282,"line":5316},[5280,23742,14862],{"class":5297},[5280,23744,23745],{"class":5282,"line":5322},[5280,23746,23747],{"class":5297},"    \"database\u002Fsql\"\n",[5280,23749,23750],{"class":5282,"line":5336},[5280,23751,21978],{"class":5297},[5280,23753,23754],{"class":5282,"line":5341},[5280,23755,5598],{"class":5452},[5280,23757,23758],{"class":5282,"line":5347},[5280,23759,5313],{"emptyLinePlaceholder":5312},[5280,23761,23762],{"class":5282,"line":5358},[5280,23763,23764],{"class":5286},"\u002F\u002F UserRepository — PostgreSQL реалізація\n",[5280,23766,23767,23769,23771,23773],{"class":5282,"line":5368},[5280,23768,7921],{"class":5619},[5280,23770,21707],{"class":5758},[5280,23772,6216],{"class":5619},[5280,23774,6111],{"class":5452},[5280,23776,23777,23780,23782,23785,23787],{"class":5282,"line":5378},[5280,23778,23779],{"class":5424},"    db",[5280,23781,6010],{"class":5452},[5280,23783,23784],{"class":5758},"sql",[5280,23786,5218],{"class":5452},[5280,23788,23789],{"class":5758},"DB\n",[5280,23791,23792],{"class":5282,"line":5391},[5280,23793,5976],{"class":5452},[5280,23795,23796],{"class":5282,"line":5705},[5280,23797,5313],{"emptyLinePlaceholder":5312},[5280,23799,23800],{"class":5282,"line":5820},[5280,23801,23802],{"class":5286},"\u002F\u002F NewUserRepository створює PostgreSQL репозиторій\n",[5280,23804,23805,23807,23809,23811,23814,23816,23818,23820,23823,23825,23827],{"class":5282,"line":5854},[5280,23806,5862],{"class":5619},[5280,23808,22962],{"class":5293},[5280,23810,5835],{"class":5452},[5280,23812,23813],{"class":5424},"db",[5280,23815,6010],{"class":5452},[5280,23817,23784],{"class":5758},[5280,23819,5218],{"class":5452},[5280,23821,23822],{"class":5758},"DB",[5280,23824,13795],{"class":5452},[5280,23826,22060],{"class":5758},[5280,23828,6111],{"class":5452},[5280,23830,23831,23833,23835,23837,23839,23841,23843,23845],{"class":5282,"line":5859},[5280,23832,7328],{"class":5436},[5280,23834,13807],{"class":5452},[5280,23836,22060],{"class":5758},[5280,23838,12000],{"class":5452},[5280,23840,23813],{"class":5424},[5280,23842,6790],{"class":5452},[5280,23844,23813],{"class":5424},[5280,23846,5976],{"class":5452},[5280,23848,23849],{"class":5282,"line":5871},[5280,23850,5976],{"class":5452},[5280,23852,23853],{"class":5282,"line":5895},[5280,23854,5313],{"emptyLinePlaceholder":5312},[5280,23856,23857,23859,23861,23863,23865,23867,23869,23871,23873,23875,23877,23879,23881,23883,23885,23887,23889,23891,23893,23895,23897],{"class":5282,"line":5916},[5280,23858,5862],{"class":5619},[5280,23860,5720],{"class":5452},[5280,23862,23026],{"class":5424},[5280,23864,8072],{"class":5452},[5280,23866,22060],{"class":5758},[5280,23868,7315],{"class":5452},[5280,23870,22476],{"class":5293},[5280,23872,5835],{"class":5452},[5280,23874,15441],{"class":5424},[5280,23876,21723],{"class":5758},[5280,23878,5218],{"class":5452},[5280,23880,15393],{"class":5758},[5280,23882,5887],{"class":5452},[5280,23884,6292],{"class":5424},[5280,23886,6010],{"class":5452},[5280,23888,22144],{"class":5758},[5280,23890,5218],{"class":5452},[5280,23892,17513],{"class":5758},[5280,23894,7315],{"class":5452},[5280,23896,9567],{"class":5758},[5280,23898,6111],{"class":5452},[5280,23900,23901,23904,23906],{"class":5282,"line":5921},[5280,23902,23903],{"class":5424},"    query",[5280,23905,6029],{"class":5452},[5280,23907,23908],{"class":5297},"`\n",[5280,23910,23911],{"class":5282,"line":5939},[5280,23912,23913],{"class":5297},"        INSERT INTO users (id, name, email, password, created_at, updated_at)\n",[5280,23915,23916],{"class":5282,"line":5973},[5280,23917,23918],{"class":5297},"        VALUES ($1, $2, $3, $4, $5, $6)\n",[5280,23920,23921],{"class":5282,"line":5979},[5280,23922,23923],{"class":5297},"    `\n",[5280,23925,23926],{"class":5282,"line":5984},[5280,23927,6724],{"class":5452},[5280,23929,23930,23933,23935,23937,23939,23941,23943,23945,23947,23950,23952,23954,23956,23959],{"class":5282,"line":6023},[5280,23931,23932],{"class":5424},"    _",[5280,23934,5887],{"class":5452},[5280,23936,6298],{"class":5424},[5280,23938,6029],{"class":5452},[5280,23940,6007],{"class":5424},[5280,23942,5218],{"class":5452},[5280,23944,23813],{"class":5424},[5280,23946,5218],{"class":5452},[5280,23948,23949],{"class":5293},"ExecContext",[5280,23951,5835],{"class":5452},[5280,23953,15441],{"class":5424},[5280,23955,5887],{"class":5452},[5280,23957,23958],{"class":5424},"query",[5280,23960,6796],{"class":5452},[5280,23962,23963,23966,23968,23970],{"class":5282,"line":6042},[5280,23964,23965],{"class":5424},"        user",[5280,23967,5218],{"class":5452},[5280,23969,18635],{"class":5424},[5280,23971,6796],{"class":5452},[5280,23973,23974,23976,23978,23980],{"class":5282,"line":6093},[5280,23975,23965],{"class":5424},[5280,23977,5218],{"class":5452},[5280,23979,6502],{"class":5424},[5280,23981,6796],{"class":5452},[5280,23983,23984,23986,23988,23990],{"class":5282,"line":6098},[5280,23985,23965],{"class":5424},[5280,23987,5218],{"class":5452},[5280,23989,6437],{"class":5424},[5280,23991,6796],{"class":5452},[5280,23993,23994,23996,23998,24000],{"class":5282,"line":6114},[5280,23995,23965],{"class":5424},[5280,23997,5218],{"class":5452},[5280,23999,19687],{"class":5424},[5280,24001,6796],{"class":5452},[5280,24003,24004,24006,24008,24011],{"class":5282,"line":6130},[5280,24005,23965],{"class":5424},[5280,24007,5218],{"class":5452},[5280,24009,24010],{"class":5424},"CreatedAt",[5280,24012,6796],{"class":5452},[5280,24014,24015,24017,24019,24022],{"class":5282,"line":6159},[5280,24016,23965],{"class":5424},[5280,24018,5218],{"class":5452},[5280,24020,24021],{"class":5424},"UpdatedAt",[5280,24023,6796],{"class":5452},[5280,24025,24026],{"class":5282,"line":6187},[5280,24027,13112],{"class":5452},[5280,24029,24030],{"class":5282,"line":6193},[5280,24031,6724],{"class":5452},[5280,24033,24034,24036],{"class":5282,"line":6207},[5280,24035,7328],{"class":5436},[5280,24037,24038],{"class":5424}," err\n",[5280,24040,24041],{"class":5282,"line":6221},[5280,24042,5976],{"class":5452},[5280,24044,24045],{"class":5282,"line":6233},[5280,24046,5313],{"emptyLinePlaceholder":5312},[5280,24048,24049,24051,24053,24055,24057,24059,24061,24063,24065,24067,24069,24071,24073,24075,24077,24079,24081,24083,24085,24087,24089,24091],{"class":5282,"line":6245},[5280,24050,5862],{"class":5619},[5280,24052,5720],{"class":5452},[5280,24054,23026],{"class":5424},[5280,24056,8072],{"class":5452},[5280,24058,22060],{"class":5758},[5280,24060,7315],{"class":5452},[5280,24062,23147],{"class":5293},[5280,24064,5835],{"class":5452},[5280,24066,15441],{"class":5424},[5280,24068,21723],{"class":5758},[5280,24070,5218],{"class":5452},[5280,24072,15393],{"class":5758},[5280,24074,5887],{"class":5452},[5280,24076,21760],{"class":5424},[5280,24078,6239],{"class":5758},[5280,24080,20151],{"class":5452},[5280,24082,22144],{"class":5758},[5280,24084,5218],{"class":5452},[5280,24086,17513],{"class":5758},[5280,24088,5887],{"class":5452},[5280,24090,9567],{"class":5758},[5280,24092,6020],{"class":5452},[5280,24094,24095,24097,24099],{"class":5282,"line":6251},[5280,24096,23903],{"class":5424},[5280,24098,6029],{"class":5452},[5280,24100,23908],{"class":5297},[5280,24102,24103],{"class":5282,"line":6256},[5280,24104,24105],{"class":5297},"        SELECT id, name, email, password, created_at, updated_at\n",[5280,24107,24108],{"class":5282,"line":6308},[5280,24109,24110],{"class":5297},"        FROM users\n",[5280,24112,24113],{"class":5282,"line":6339},[5280,24114,24115],{"class":5297},"        WHERE id = $1\n",[5280,24117,24118],{"class":5282,"line":6371},[5280,24119,23923],{"class":5297},[5280,24121,24122],{"class":5282,"line":6409},[5280,24123,6724],{"class":5452},[5280,24125,24126,24128,24130,24132,24134,24136],{"class":5282,"line":6415},[5280,24127,18871],{"class":5424},[5280,24129,9745],{"class":5452},[5280,24131,22144],{"class":5758},[5280,24133,5218],{"class":5452},[5280,24135,17513],{"class":5758},[5280,24137,8056],{"class":5452},[5280,24139,24140,24143,24145,24147,24149,24151,24153,24156,24158,24160,24162,24164,24166,24168,24170,24173],{"class":5282,"line":6420},[5280,24141,24142],{"class":5424},"    err",[5280,24144,6029],{"class":5452},[5280,24146,6007],{"class":5424},[5280,24148,5218],{"class":5452},[5280,24150,23813],{"class":5424},[5280,24152,5218],{"class":5452},[5280,24154,24155],{"class":5293},"QueryRowContext",[5280,24157,5835],{"class":5452},[5280,24159,15441],{"class":5424},[5280,24161,5887],{"class":5452},[5280,24163,23958],{"class":5424},[5280,24165,5887],{"class":5452},[5280,24167,21760],{"class":5424},[5280,24169,6174],{"class":5452},[5280,24171,24172],{"class":5293},"Scan",[5280,24174,9105],{"class":5452},[5280,24176,24177,24180,24182,24184,24186],{"class":5282,"line":6425},[5280,24178,24179],{"class":5452},"        &",[5280,24181,6292],{"class":5424},[5280,24183,5218],{"class":5452},[5280,24185,18635],{"class":5424},[5280,24187,6796],{"class":5452},[5280,24189,24190,24192,24194,24196,24198],{"class":5282,"line":6450},[5280,24191,24179],{"class":5452},[5280,24193,6292],{"class":5424},[5280,24195,5218],{"class":5452},[5280,24197,6502],{"class":5424},[5280,24199,6796],{"class":5452},[5280,24201,24202,24204,24206,24208,24210],{"class":5282,"line":6471},[5280,24203,24179],{"class":5452},[5280,24205,6292],{"class":5424},[5280,24207,5218],{"class":5452},[5280,24209,6437],{"class":5424},[5280,24211,6796],{"class":5452},[5280,24213,24214,24216,24218,24220,24222],{"class":5282,"line":6507},[5280,24215,24179],{"class":5452},[5280,24217,6292],{"class":5424},[5280,24219,5218],{"class":5452},[5280,24221,19687],{"class":5424},[5280,24223,6796],{"class":5452},[5280,24225,24226,24228,24230,24232,24234],{"class":5282,"line":6512},[5280,24227,24179],{"class":5452},[5280,24229,6292],{"class":5424},[5280,24231,5218],{"class":5452},[5280,24233,24010],{"class":5424},[5280,24235,6796],{"class":5452},[5280,24237,24238,24240,24242,24244,24246],{"class":5282,"line":6520},[5280,24239,24179],{"class":5452},[5280,24241,6292],{"class":5424},[5280,24243,5218],{"class":5452},[5280,24245,24021],{"class":5424},[5280,24247,6796],{"class":5452},[5280,24249,24250],{"class":5282,"line":6550},[5280,24251,13112],{"class":5452},[5280,24253,24254],{"class":5282,"line":6556},[5280,24255,6724],{"class":5452},[5280,24257,24258,24260,24262,24264,24266,24268,24271],{"class":5282,"line":6561},[5280,24259,7023],{"class":5436},[5280,24261,6262],{"class":5424},[5280,24263,7029],{"class":5452},[5280,24265,23784],{"class":5424},[5280,24267,5218],{"class":5452},[5280,24269,24270],{"class":5424},"ErrNoRows",[5280,24272,6111],{"class":5452},[5280,24274,24275,24277,24279,24281,24283,24285],{"class":5282,"line":6621},[5280,24276,12200],{"class":5436},[5280,24278,20192],{"class":5619},[5280,24280,5887],{"class":5452},[5280,24282,22144],{"class":5424},[5280,24284,5218],{"class":5452},[5280,24286,23260],{"class":5424},[5280,24288,24289],{"class":5282,"line":6626},[5280,24290,6553],{"class":5452},[5280,24292,24293],{"class":5282,"line":6631},[5280,24294,6724],{"class":5452},[5280,24296,24297,24299,24301,24303],{"class":5282,"line":6663},[5280,24298,7328],{"class":5436},[5280,24300,6213],{"class":5424},[5280,24302,5887],{"class":5452},[5280,24304,22296],{"class":5424},[5280,24306,24307],{"class":5282,"line":6678},[5280,24308,5976],{"class":5452},[5280,24310,24311],{"class":5282,"line":6721},[5280,24312,5313],{"emptyLinePlaceholder":5312},[5280,24314,24315,24317,24319,24321,24323,24325,24327,24329,24331,24333,24335,24337,24339,24341,24343,24345,24347,24349,24351,24353,24355,24357],{"class":5282,"line":6727},[5280,24316,5862],{"class":5619},[5280,24318,5720],{"class":5452},[5280,24320,23026],{"class":5424},[5280,24322,8072],{"class":5452},[5280,24324,22060],{"class":5758},[5280,24326,7315],{"class":5452},[5280,24328,22183],{"class":5293},[5280,24330,5835],{"class":5452},[5280,24332,15441],{"class":5424},[5280,24334,21723],{"class":5758},[5280,24336,5218],{"class":5452},[5280,24338,15393],{"class":5758},[5280,24340,5887],{"class":5452},[5280,24342,20126],{"class":5424},[5280,24344,6239],{"class":5758},[5280,24346,20151],{"class":5452},[5280,24348,22144],{"class":5758},[5280,24350,5218],{"class":5452},[5280,24352,17513],{"class":5758},[5280,24354,5887],{"class":5452},[5280,24356,9567],{"class":5758},[5280,24358,6020],{"class":5452},[5280,24360,24361,24363,24365],{"class":5282,"line":6751},[5280,24362,23903],{"class":5424},[5280,24364,6029],{"class":5452},[5280,24366,23908],{"class":5297},[5280,24368,24369],{"class":5282,"line":6784},[5280,24370,24105],{"class":5297},[5280,24372,24373],{"class":5282,"line":6799},[5280,24374,24110],{"class":5297},[5280,24376,24377],{"class":5282,"line":6831},[5280,24378,24379],{"class":5297},"        WHERE email = $1\n",[5280,24381,24382],{"class":5282,"line":6837},[5280,24383,23923],{"class":5297},[5280,24385,24386],{"class":5282,"line":6842},[5280,24387,6724],{"class":5452},[5280,24389,24390,24392,24394,24396,24398,24400],{"class":5282,"line":6901},[5280,24391,18871],{"class":5424},[5280,24393,9745],{"class":5452},[5280,24395,22144],{"class":5758},[5280,24397,5218],{"class":5452},[5280,24399,17513],{"class":5758},[5280,24401,8056],{"class":5452},[5280,24403,24404,24406,24408,24410,24412,24414,24416,24418,24420,24422,24424,24426,24428,24430,24432,24434],{"class":5282,"line":12702},[5280,24405,24142],{"class":5424},[5280,24407,6029],{"class":5452},[5280,24409,6007],{"class":5424},[5280,24411,5218],{"class":5452},[5280,24413,23813],{"class":5424},[5280,24415,5218],{"class":5452},[5280,24417,24155],{"class":5293},[5280,24419,5835],{"class":5452},[5280,24421,15441],{"class":5424},[5280,24423,5887],{"class":5452},[5280,24425,23958],{"class":5424},[5280,24427,5887],{"class":5452},[5280,24429,20126],{"class":5424},[5280,24431,6174],{"class":5452},[5280,24433,24172],{"class":5293},[5280,24435,9105],{"class":5452},[5280,24437,24438,24440,24442,24444,24446],{"class":5282,"line":12731},[5280,24439,24179],{"class":5452},[5280,24441,6292],{"class":5424},[5280,24443,5218],{"class":5452},[5280,24445,18635],{"class":5424},[5280,24447,6796],{"class":5452},[5280,24449,24450,24452,24454,24456,24458],{"class":5282,"line":12736},[5280,24451,24179],{"class":5452},[5280,24453,6292],{"class":5424},[5280,24455,5218],{"class":5452},[5280,24457,6502],{"class":5424},[5280,24459,6796],{"class":5452},[5280,24461,24462,24464,24466,24468,24470],{"class":5282,"line":12741},[5280,24463,24179],{"class":5452},[5280,24465,6292],{"class":5424},[5280,24467,5218],{"class":5452},[5280,24469,6437],{"class":5424},[5280,24471,6796],{"class":5452},[5280,24473,24474,24476,24478,24480,24482],{"class":5282,"line":12746},[5280,24475,24179],{"class":5452},[5280,24477,6292],{"class":5424},[5280,24479,5218],{"class":5452},[5280,24481,19687],{"class":5424},[5280,24483,6796],{"class":5452},[5280,24485,24486,24488,24490,24492,24494],{"class":5282,"line":12752},[5280,24487,24179],{"class":5452},[5280,24489,6292],{"class":5424},[5280,24491,5218],{"class":5452},[5280,24493,24010],{"class":5424},[5280,24495,6796],{"class":5452},[5280,24497,24498,24500,24502,24504,24506],{"class":5282,"line":12774},[5280,24499,24179],{"class":5452},[5280,24501,6292],{"class":5424},[5280,24503,5218],{"class":5452},[5280,24505,24021],{"class":5424},[5280,24507,6796],{"class":5452},[5280,24509,24510],{"class":5282,"line":12794},[5280,24511,13112],{"class":5452},[5280,24513,24514],{"class":5282,"line":12800},[5280,24515,6724],{"class":5452},[5280,24517,24518,24520,24522,24524,24526,24528,24530],{"class":5282,"line":12805},[5280,24519,7023],{"class":5436},[5280,24521,6262],{"class":5424},[5280,24523,7029],{"class":5452},[5280,24525,23784],{"class":5424},[5280,24527,5218],{"class":5452},[5280,24529,24270],{"class":5424},[5280,24531,6111],{"class":5452},[5280,24533,24534,24536,24538,24540,24542,24544],{"class":5282,"line":12810},[5280,24535,12200],{"class":5436},[5280,24537,20192],{"class":5619},[5280,24539,5887],{"class":5452},[5280,24541,22144],{"class":5424},[5280,24543,5218],{"class":5452},[5280,24545,23260],{"class":5424},[5280,24547,24548],{"class":5282,"line":12816},[5280,24549,6553],{"class":5452},[5280,24551,24552],{"class":5282,"line":12836},[5280,24553,6724],{"class":5452},[5280,24555,24556,24558,24560,24562],{"class":5282,"line":12842},[5280,24557,7328],{"class":5436},[5280,24559,6213],{"class":5424},[5280,24561,5887],{"class":5452},[5280,24563,22296],{"class":5424},[5280,24565,24566],{"class":5282,"line":12847},[5280,24567,5976],{"class":5452},[5280,24569,24570],{"class":5282,"line":18311},[5280,24571,5313],{"emptyLinePlaceholder":5312},[5280,24573,24574,24576,24578,24580,24582,24584,24586,24588,24590,24592,24594,24596,24598,24600,24602,24604,24606,24608,24610,24612,24614],{"class":5282,"line":18338},[5280,24575,5862],{"class":5619},[5280,24577,5720],{"class":5452},[5280,24579,23026],{"class":5424},[5280,24581,8072],{"class":5452},[5280,24583,22060],{"class":5758},[5280,24585,7315],{"class":5452},[5280,24587,23457],{"class":5293},[5280,24589,5835],{"class":5452},[5280,24591,15441],{"class":5424},[5280,24593,21723],{"class":5758},[5280,24595,5218],{"class":5452},[5280,24597,15393],{"class":5758},[5280,24599,5887],{"class":5452},[5280,24601,6292],{"class":5424},[5280,24603,6010],{"class":5452},[5280,24605,22144],{"class":5758},[5280,24607,5218],{"class":5452},[5280,24609,17513],{"class":5758},[5280,24611,7315],{"class":5452},[5280,24613,9567],{"class":5758},[5280,24615,6111],{"class":5452},[5280,24617,24618,24620,24622],{"class":5282,"line":18343},[5280,24619,23903],{"class":5424},[5280,24621,6029],{"class":5452},[5280,24623,23908],{"class":5297},[5280,24625,24626],{"class":5282,"line":18348},[5280,24627,24628],{"class":5297},"        UPDATE users\n",[5280,24630,24631],{"class":5282,"line":18380},[5280,24632,24633],{"class":5297},"        SET name = $2, email = $3, password = $4, updated_at = $5\n",[5280,24635,24636],{"class":5282,"line":18393},[5280,24637,24115],{"class":5297},[5280,24639,24640],{"class":5282,"line":18402},[5280,24641,23923],{"class":5297},[5280,24643,24644],{"class":5282,"line":18412},[5280,24645,6724],{"class":5452},[5280,24647,24648,24651,24653,24655,24657,24659,24661,24663,24665,24667,24669,24671,24673,24675],{"class":5282,"line":18423},[5280,24649,24650],{"class":5424},"    result",[5280,24652,5887],{"class":5452},[5280,24654,6298],{"class":5424},[5280,24656,6029],{"class":5452},[5280,24658,6007],{"class":5424},[5280,24660,5218],{"class":5452},[5280,24662,23813],{"class":5424},[5280,24664,5218],{"class":5452},[5280,24666,23949],{"class":5293},[5280,24668,5835],{"class":5452},[5280,24670,15441],{"class":5424},[5280,24672,5887],{"class":5452},[5280,24674,23958],{"class":5424},[5280,24676,6796],{"class":5452},[5280,24678,24679,24681,24683,24685],{"class":5282,"line":18428},[5280,24680,23965],{"class":5424},[5280,24682,5218],{"class":5452},[5280,24684,18635],{"class":5424},[5280,24686,6796],{"class":5452},[5280,24688,24689,24691,24693,24695],{"class":5282,"line":18433},[5280,24690,23965],{"class":5424},[5280,24692,5218],{"class":5452},[5280,24694,6502],{"class":5424},[5280,24696,6796],{"class":5452},[5280,24698,24699,24701,24703,24705],{"class":5282,"line":18475},[5280,24700,23965],{"class":5424},[5280,24702,5218],{"class":5452},[5280,24704,6437],{"class":5424},[5280,24706,6796],{"class":5452},[5280,24708,24709,24711,24713,24715],{"class":5282,"line":18502},[5280,24710,23965],{"class":5424},[5280,24712,5218],{"class":5452},[5280,24714,19687],{"class":5424},[5280,24716,6796],{"class":5452},[5280,24718,24719,24721,24723,24725],{"class":5282,"line":18507},[5280,24720,23965],{"class":5424},[5280,24722,5218],{"class":5452},[5280,24724,24021],{"class":5424},[5280,24726,6796],{"class":5452},[5280,24728,24729],{"class":5282,"line":18512},[5280,24730,13112],{"class":5452},[5280,24732,24733],{"class":5282,"line":18517},[5280,24734,6724],{"class":5452},[5280,24736,24737,24739,24741,24743,24745],{"class":5282,"line":18523},[5280,24738,7023],{"class":5436},[5280,24740,6262],{"class":5424},[5280,24742,6301],{"class":5452},[5280,24744,5967],{"class":5619},[5280,24746,6111],{"class":5452},[5280,24748,24749,24751],{"class":5282,"line":18535},[5280,24750,12200],{"class":5436},[5280,24752,24038],{"class":5424},[5280,24754,24755],{"class":5282,"line":18572},[5280,24756,6553],{"class":5452},[5280,24758,24759],{"class":5282,"line":18587},[5280,24760,6724],{"class":5452},[5280,24762,24763,24766,24768,24770,24772,24775,24777,24780],{"class":5282,"line":18602},[5280,24764,24765],{"class":5424},"    rows",[5280,24767,5887],{"class":5452},[5280,24769,22169],{"class":5424},[5280,24771,6029],{"class":5452},[5280,24773,24774],{"class":5424},"result",[5280,24776,5218],{"class":5452},[5280,24778,24779],{"class":5293},"RowsAffected",[5280,24781,6039],{"class":5452},[5280,24783,24784,24786,24789,24791,24793],{"class":5282,"line":18617},[5280,24785,7023],{"class":5436},[5280,24787,24788],{"class":5424}," rows",[5280,24790,7029],{"class":5452},[5280,24792,11413],{"class":5439},[5280,24794,6111],{"class":5452},[5280,24796,24797,24799,24801,24803],{"class":5282,"line":18622},[5280,24798,12200],{"class":5436},[5280,24800,22023],{"class":5424},[5280,24802,5218],{"class":5452},[5280,24804,23260],{"class":5424},[5280,24806,24807],{"class":5282,"line":18643},[5280,24808,6553],{"class":5452},[5280,24810,24811],{"class":5282,"line":18648},[5280,24812,6724],{"class":5452},[5280,24814,24815,24817],{"class":5282,"line":18667},[5280,24816,7328],{"class":5436},[5280,24818,21545],{"class":5619},[5280,24820,24821],{"class":5282,"line":18690},[5280,24822,5976],{"class":5452},[5280,24824,24825],{"class":5282,"line":18695},[5280,24826,5313],{"emptyLinePlaceholder":5312},[5280,24828,24829,24831,24833,24835,24837,24839,24841,24843,24845,24847,24849,24851,24853,24855,24857,24859,24861,24863],{"class":5282,"line":18700},[5280,24830,5862],{"class":5619},[5280,24832,5720],{"class":5452},[5280,24834,23026],{"class":5424},[5280,24836,8072],{"class":5452},[5280,24838,22060],{"class":5758},[5280,24840,7315],{"class":5452},[5280,24842,23621],{"class":5293},[5280,24844,5835],{"class":5452},[5280,24846,15441],{"class":5424},[5280,24848,21723],{"class":5758},[5280,24850,5218],{"class":5452},[5280,24852,15393],{"class":5758},[5280,24854,5887],{"class":5452},[5280,24856,21760],{"class":5424},[5280,24858,6239],{"class":5758},[5280,24860,7315],{"class":5452},[5280,24862,9567],{"class":5758},[5280,24864,6111],{"class":5452},[5280,24866,24867,24869,24871],{"class":5282,"line":18732},[5280,24868,23903],{"class":5424},[5280,24870,6029],{"class":5452},[5280,24872,24873],{"class":5297},"`DELETE FROM users WHERE id = $1`\n",[5280,24875,24876,24878,24880,24882,24884,24886,24888,24890,24892,24894,24896,24898,24900,24902,24904,24906],{"class":5282,"line":18738},[5280,24877,23932],{"class":5424},[5280,24879,5887],{"class":5452},[5280,24881,6298],{"class":5424},[5280,24883,6029],{"class":5452},[5280,24885,6007],{"class":5424},[5280,24887,5218],{"class":5452},[5280,24889,23813],{"class":5424},[5280,24891,5218],{"class":5452},[5280,24893,23949],{"class":5293},[5280,24895,5835],{"class":5452},[5280,24897,15441],{"class":5424},[5280,24899,5887],{"class":5452},[5280,24901,23958],{"class":5424},[5280,24903,5887],{"class":5452},[5280,24905,21760],{"class":5424},[5280,24907,5598],{"class":5452},[5280,24909,24910,24912],{"class":5282,"line":18769},[5280,24911,7328],{"class":5436},[5280,24913,24038],{"class":5424},[5280,24915,24916],{"class":5282,"line":18785},[5280,24917,5976],{"class":5452},[8342,24919,24920,24923],{},[5227,24921,24922],{},"Ключова перевага Repository Pattern:"," UseCase не знає, з якою СУБД він працює. Можна легко замінити PostgreSQL на MySQL, MongoDB, або навіть cloud storage (AWS DynamoDB), не змінюючи жодного рядка у UseCase!",[21159,24925,24927,24928,24931],{"id":24926},"_4-delivery-layer-http-handlers-шар-доставки","4. ",[5227,24929,24930],{},"Delivery Layer (HTTP Handlers)"," — шар доставки",[5262,24933,24934],{},"Найзовнішній шар. Відповідає за:",[5194,24936,24937,24940,24943],{},[5197,24938,24939],{},"Парсинг HTTP-запитів (JSON, form data, query parameters)",[5197,24941,24942],{},"Виклик відповідного UseCase",[5197,24944,24945],{},"Формування HTTP-відповіді",[5271,24947,24949],{"className":5411,"code":24948,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fdelivery\u002Fhttp\u002Fuser_handler.go\npackage http\n\nimport (\n    \"encoding\u002Fjson\"\n    \"myapp\u002Finternal\u002Fdomain\"\n    \"myapp\u002Finternal\u002Fusecase\"\n    \"net\u002Fhttp\"\n)\n\n\u002F\u002F UserHandler — HTTP-обробники для користувачів\ntype UserHandler struct {\n    useCase *usecase.UserUseCase\n}\n\n\u002F\u002F NewUserHandler створює новий handler\nfunc NewUserHandler(useCase *usecase.UserUseCase) *UserHandler {\n    return &UserHandler{useCase: useCase}\n}\n\n\u002F\u002F RegisterRequest — DTO для реєстрації\ntype RegisterRequest struct {\n    Name     string `json:\"name\"`\n    Email    string `json:\"email\"`\n    Password string `json:\"password\"`\n}\n\n\u002F\u002F UserResponse — DTO для відповіді (без пароля!)\ntype UserResponse struct {\n    ID        string `json:\"id\"`\n    Name      string `json:\"name\"`\n    Email     string `json:\"email\"`\n    CreatedAt string `json:\"created_at\"`\n}\n\n\u002F\u002F Register — POST \u002Fapi\u002Fusers\u002Fregister\nfunc (h *UserHandler) Register(w http.ResponseWriter, r *http.Request) {\n    var req RegisterRequest\n    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {\n        http.Error(w, \"Invalid JSON\", http.StatusBadRequest)\n        return\n    }\n    \n    \u002F\u002F Викликаємо UseCase\n    user, err := h.useCase.RegisterUser(r.Context(), req.Name, req.Email, req.Password)\n    if err != nil {\n        \u002F\u002F Мапування доменних помилок на HTTP-статуси\n        if err == domain.ErrUserAlreadyExists {\n            http.Error(w, err.Error(), http.StatusConflict)\n            return\n        }\n        http.Error(w, err.Error(), http.StatusBadRequest)\n        return\n    }\n    \n    \u002F\u002F Формуємо відповідь (без пароля!)\n    response := UserResponse{\n        ID:        user.ID,\n        Name:      user.Name,\n        Email:     user.Email,\n        CreatedAt: user.CreatedAt.Format(\"2006-01-02T15:04:05Z\"),\n    }\n    \n    w.Header().Set(\"Content-Type\", \"application\u002Fjson\")\n    w.WriteHeader(http.StatusCreated)\n    json.NewEncoder(w).Encode(response)\n}\n\n\u002F\u002F LoginRequest — DTO для входу\ntype LoginRequest struct {\n    Email    string `json:\"email\"`\n    Password string `json:\"password\"`\n}\n\n\u002F\u002F LoginResponse — DTO з токеном\ntype LoginResponse struct {\n    Token string       `json:\"token\"`\n    User  UserResponse `json:\"user\"`\n}\n\n\u002F\u002F Login — POST \u002Fapi\u002Fusers\u002Flogin\nfunc (h *UserHandler) Login(w http.ResponseWriter, r *http.Request) {\n    var req LoginRequest\n    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {\n        http.Error(w, \"Invalid JSON\", http.StatusBadRequest)\n        return\n    }\n    \n    \u002F\u002F Викликаємо UseCase\n    user, err := h.useCase.Login(r.Context(), req.Email, req.Password)\n    if err != nil {\n        if err == domain.ErrInvalidCredentials {\n            http.Error(w, \"Invalid email or password\", http.StatusUnauthorized)\n            return\n        }\n        http.Error(w, err.Error(), http.StatusInternalServerError)\n        return\n    }\n    \n    \u002F\u002F Генеруємо JWT токен (тут спрощено)\n    token := generateJWT(user.ID)\n    \n    response := LoginResponse{\n        Token: token,\n        User: UserResponse{\n            ID:        user.ID,\n            Name:      user.Name,\n            Email:     user.Email,\n            CreatedAt: user.CreatedAt.Format(\"2006-01-02T15:04:05Z\"),\n        },\n    }\n    \n    w.Header().Set(\"Content-Type\", \"application\u002Fjson\")\n    json.NewEncoder(w).Encode(response)\n}\n\nfunc generateJWT(userID string) string {\n    \u002F\u002F Спрощена реалізація\n    \u002F\u002F У реальності: jwt-go або golang-jwt\n    return \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\" + userID\n}\n",[5215,24950,24951,24956,24963,24967,24973,24977,24981,24986,24990,24994,24998,25003,25014,25029,25033,25037,25042,25069,25087,25091,25095,25100,25111,25119,25127,25135,25139,25143,25148,25159,25168,25176,25184,25192,25196,25200,25205,25246,25255,25295,25321,25325,25329,25333,25338,25392,25404,25409,25426,25456,25460,25464,25494,25498,25502,25506,25511,25523,25537,25551,25565,25588,25592,25596,25618,25636,25659,25663,25667,25672,25683,25691,25699,25703,25707,25712,25723,25733,25744,25748,25752,25757,25797,25806,25846,25872,25876,25880,25884,25888,25934,25946,25963,25990,25994,25998,26028,26032,26036,26040,26045,26065,26069,26080,26091,26102,26117,26131,26145,26168,26173,26177,26181,26203,26225,26229,26233,26252,26256,26261,26273],{"__ignoreMap":5276},[5280,24952,24953],{"class":5282,"line":5283},[5280,24954,24955],{"class":5286},"\u002F\u002F internal\u002Fdelivery\u002Fhttp\u002Fuser_handler.go\n",[5280,24957,24958,24960],{"class":5282,"line":5290},[5280,24959,5755],{"class":5619},[5280,24961,24962],{"class":5758}," http\n",[5280,24964,24965],{"class":5282,"line":5301},[5280,24966,5313],{"emptyLinePlaceholder":5312},[5280,24968,24969,24971],{"class":5282,"line":5309},[5280,24970,5768],{"class":5436},[5280,24972,5453],{"class":5452},[5280,24974,24975],{"class":5282,"line":5316},[5280,24976,5775],{"class":5297},[5280,24978,24979],{"class":5282,"line":5322},[5280,24980,21978],{"class":5297},[5280,24982,24983],{"class":5282,"line":5336},[5280,24984,24985],{"class":5297},"    \"myapp\u002Finternal\u002Fusecase\"\n",[5280,24987,24988],{"class":5282,"line":5341},[5280,24989,5790],{"class":5297},[5280,24991,24992],{"class":5282,"line":5347},[5280,24993,5598],{"class":5452},[5280,24995,24996],{"class":5282,"line":5358},[5280,24997,5313],{"emptyLinePlaceholder":5312},[5280,24999,25000],{"class":5282,"line":5368},[5280,25001,25002],{"class":5286},"\u002F\u002F UserHandler — HTTP-обробники для користувачів\n",[5280,25004,25005,25007,25010,25012],{"class":5282,"line":5378},[5280,25006,7921],{"class":5619},[5280,25008,25009],{"class":5758}," UserHandler",[5280,25011,6216],{"class":5619},[5280,25013,6111],{"class":5452},[5280,25015,25016,25019,25021,25024,25026],{"class":5282,"line":5391},[5280,25017,25018],{"class":5424},"    useCase",[5280,25020,6010],{"class":5452},[5280,25022,25023],{"class":5758},"usecase",[5280,25025,5218],{"class":5452},[5280,25027,25028],{"class":5758},"UserUseCase\n",[5280,25030,25031],{"class":5282,"line":5705},[5280,25032,5976],{"class":5452},[5280,25034,25035],{"class":5282,"line":5820},[5280,25036,5313],{"emptyLinePlaceholder":5312},[5280,25038,25039],{"class":5282,"line":5854},[5280,25040,25041],{"class":5286},"\u002F\u002F NewUserHandler створює новий handler\n",[5280,25043,25044,25046,25049,25051,25054,25056,25058,25060,25062,25064,25067],{"class":5282,"line":5859},[5280,25045,5862],{"class":5619},[5280,25047,25048],{"class":5293}," NewUserHandler",[5280,25050,5835],{"class":5452},[5280,25052,25053],{"class":5424},"useCase",[5280,25055,6010],{"class":5452},[5280,25057,25023],{"class":5758},[5280,25059,5218],{"class":5452},[5280,25061,22065],{"class":5758},[5280,25063,13795],{"class":5452},[5280,25065,25066],{"class":5758},"UserHandler",[5280,25068,6111],{"class":5452},[5280,25070,25071,25073,25075,25077,25079,25081,25083,25085],{"class":5282,"line":5871},[5280,25072,7328],{"class":5436},[5280,25074,13807],{"class":5452},[5280,25076,25066],{"class":5758},[5280,25078,12000],{"class":5452},[5280,25080,25053],{"class":5424},[5280,25082,6790],{"class":5452},[5280,25084,25053],{"class":5424},[5280,25086,5976],{"class":5452},[5280,25088,25089],{"class":5282,"line":5895},[5280,25090,5976],{"class":5452},[5280,25092,25093],{"class":5282,"line":5916},[5280,25094,5313],{"emptyLinePlaceholder":5312},[5280,25096,25097],{"class":5282,"line":5921},[5280,25098,25099],{"class":5286},"\u002F\u002F RegisterRequest — DTO для реєстрації\n",[5280,25101,25102,25104,25107,25109],{"class":5282,"line":5939},[5280,25103,7921],{"class":5619},[5280,25105,25106],{"class":5758}," RegisterRequest",[5280,25108,6216],{"class":5619},[5280,25110,6111],{"class":5452},[5280,25112,25113,25115,25117],{"class":5282,"line":5973},[5280,25114,17450],{"class":5424},[5280,25116,17464],{"class":5758},[5280,25118,6230],{"class":5297},[5280,25120,25121,25123,25125],{"class":5282,"line":5979},[5280,25122,17461],{"class":5424},[5280,25124,18407],{"class":5758},[5280,25126,6242],{"class":5297},[5280,25128,25129,25131,25133],{"class":5282,"line":5984},[5280,25130,20094],{"class":5424},[5280,25132,6239],{"class":5758},[5280,25134,18420],{"class":5297},[5280,25136,25137],{"class":5282,"line":6023},[5280,25138,5976],{"class":5452},[5280,25140,25141],{"class":5282,"line":6042},[5280,25142,5313],{"emptyLinePlaceholder":5312},[5280,25144,25145],{"class":5282,"line":6093},[5280,25146,25147],{"class":5286},"\u002F\u002F UserResponse — DTO для відповіді (без пароля!)\n",[5280,25149,25150,25152,25155,25157],{"class":5282,"line":6098},[5280,25151,7921],{"class":5619},[5280,25153,25154],{"class":5758}," UserResponse",[5280,25156,6216],{"class":5619},[5280,25158,6111],{"class":5452},[5280,25160,25161,25163,25165],{"class":5282,"line":6114},[5280,25162,17439],{"class":5424},[5280,25164,17442],{"class":5758},[5280,25166,25167],{"class":5297}," `json:\"id\"`\n",[5280,25169,25170,25172,25174],{"class":5282,"line":6130},[5280,25171,17450],{"class":5424},[5280,25173,17453],{"class":5758},[5280,25175,6230],{"class":5297},[5280,25177,25178,25180,25182],{"class":5282,"line":6159},[5280,25179,17461],{"class":5424},[5280,25181,17464],{"class":5758},[5280,25183,6242],{"class":5297},[5280,25185,25186,25188,25190],{"class":5282,"line":6187},[5280,25187,17472],{"class":5424},[5280,25189,6239],{"class":5758},[5280,25191,17481],{"class":5297},[5280,25193,25194],{"class":5282,"line":6193},[5280,25195,5976],{"class":5452},[5280,25197,25198],{"class":5282,"line":6207},[5280,25199,5313],{"emptyLinePlaceholder":5312},[5280,25201,25202],{"class":5282,"line":6221},[5280,25203,25204],{"class":5286},"\u002F\u002F Register — POST \u002Fapi\u002Fusers\u002Fregister\n",[5280,25206,25207,25209,25211,25213,25215,25217,25219,25222,25224,25226,25228,25230,25232,25234,25236,25238,25240,25242,25244],{"class":5282,"line":6233},[5280,25208,5862],{"class":5619},[5280,25210,5720],{"class":5452},[5280,25212,8069],{"class":5424},[5280,25214,8072],{"class":5452},[5280,25216,25066],{"class":5758},[5280,25218,7315],{"class":5452},[5280,25220,25221],{"class":5293},"Register",[5280,25223,5835],{"class":5452},[5280,25225,5994],{"class":5424},[5280,25227,5997],{"class":5758},[5280,25229,5218],{"class":5452},[5280,25231,6002],{"class":5758},[5280,25233,5887],{"class":5452},[5280,25235,6007],{"class":5424},[5280,25237,6010],{"class":5452},[5280,25239,5952],{"class":5758},[5280,25241,5218],{"class":5452},[5280,25243,6017],{"class":5758},[5280,25245,6020],{"class":5452},[5280,25247,25248,25250,25252],{"class":5282,"line":6245},[5280,25249,18383],{"class":5619},[5280,25251,18386],{"class":5424},[5280,25253,25254],{"class":5758}," RegisterRequest\n",[5280,25256,25257,25259,25261,25263,25265,25267,25269,25271,25273,25275,25277,25279,25281,25283,25285,25287,25289,25291,25293],{"class":5282,"line":6251},[5280,25258,7023],{"class":5436},[5280,25260,6262],{"class":5424},[5280,25262,6029],{"class":5452},[5280,25264,6267],{"class":5424},[5280,25266,5218],{"class":5452},[5280,25268,6272],{"class":5293},[5280,25270,5835],{"class":5452},[5280,25272,6007],{"class":5424},[5280,25274,5218],{"class":5452},[5280,25276,6281],{"class":5424},[5280,25278,6174],{"class":5452},[5280,25280,6286],{"class":5293},[5280,25282,6289],{"class":5452},[5280,25284,18462],{"class":5424},[5280,25286,6295],{"class":5452},[5280,25288,6298],{"class":5424},[5280,25290,6301],{"class":5452},[5280,25292,5967],{"class":5619},[5280,25294,6111],{"class":5452},[5280,25296,25297,25299,25301,25303,25305,25307,25309,25311,25313,25315,25317,25319],{"class":5282,"line":6256},[5280,25298,6523],{"class":5424},[5280,25300,5218],{"class":5452},[5280,25302,6316],{"class":5293},[5280,25304,5835],{"class":5452},[5280,25306,5994],{"class":5424},[5280,25308,5887],{"class":5452},[5280,25310,6325],{"class":5297},[5280,25312,5887],{"class":5452},[5280,25314,5952],{"class":5424},[5280,25316,5218],{"class":5452},[5280,25318,6334],{"class":5424},[5280,25320,5598],{"class":5452},[5280,25322,25323],{"class":5282,"line":6308},[5280,25324,15875],{"class":5436},[5280,25326,25327],{"class":5282,"line":6339},[5280,25328,6553],{"class":5452},[5280,25330,25331],{"class":5282,"line":6371},[5280,25332,6724],{"class":5452},[5280,25334,25335],{"class":5282,"line":6409},[5280,25336,25337],{"class":5286},"    \u002F\u002F Викликаємо UseCase\n",[5280,25339,25340,25342,25344,25346,25348,25350,25352,25354,25356,25358,25360,25362,25364,25366,25368,25370,25372,25374,25376,25378,25380,25382,25384,25386,25388,25390],{"class":5282,"line":6415},[5280,25341,18871],{"class":5424},[5280,25343,5887],{"class":5452},[5280,25345,6298],{"class":5424},[5280,25347,6029],{"class":5452},[5280,25349,11326],{"class":5424},[5280,25351,5218],{"class":5452},[5280,25353,25053],{"class":5424},[5280,25355,5218],{"class":5452},[5280,25357,20688],{"class":5293},[5280,25359,5835],{"class":5452},[5280,25361,6007],{"class":5424},[5280,25363,5218],{"class":5452},[5280,25365,15393],{"class":5293},[5280,25367,15396],{"class":5452},[5280,25369,18462],{"class":5424},[5280,25371,5218],{"class":5452},[5280,25373,6502],{"class":5424},[5280,25375,5887],{"class":5452},[5280,25377,18462],{"class":5424},[5280,25379,5218],{"class":5452},[5280,25381,6437],{"class":5424},[5280,25383,5887],{"class":5452},[5280,25385,18462],{"class":5424},[5280,25387,5218],{"class":5452},[5280,25389,19687],{"class":5424},[5280,25391,5598],{"class":5452},[5280,25393,25394,25396,25398,25400,25402],{"class":5282,"line":6420},[5280,25395,7023],{"class":5436},[5280,25397,6262],{"class":5424},[5280,25399,6301],{"class":5452},[5280,25401,5967],{"class":5619},[5280,25403,6111],{"class":5452},[5280,25405,25406],{"class":5282,"line":6425},[5280,25407,25408],{"class":5286},"        \u002F\u002F Мапування доменних помилок на HTTP-статуси\n",[5280,25410,25411,25413,25415,25417,25419,25421,25424],{"class":5282,"line":6450},[5280,25412,6259],{"class":5436},[5280,25414,6262],{"class":5424},[5280,25416,7029],{"class":5452},[5280,25418,22144],{"class":5424},[5280,25420,5218],{"class":5452},[5280,25422,25423],{"class":5424},"ErrUserAlreadyExists",[5280,25425,6111],{"class":5452},[5280,25427,25428,25430,25432,25434,25436,25438,25440,25442,25444,25446,25448,25450,25452,25454],{"class":5282,"line":6471},[5280,25429,6311],{"class":5424},[5280,25431,5218],{"class":5452},[5280,25433,6316],{"class":5293},[5280,25435,5835],{"class":5452},[5280,25437,5994],{"class":5424},[5280,25439,5887],{"class":5452},[5280,25441,6298],{"class":5424},[5280,25443,5218],{"class":5452},[5280,25445,6316],{"class":5293},[5280,25447,15396],{"class":5452},[5280,25449,5952],{"class":5424},[5280,25451,5218],{"class":5452},[5280,25453,19802],{"class":5424},[5280,25455,5598],{"class":5452},[5280,25457,25458],{"class":5282,"line":6507},[5280,25459,6412],{"class":5436},[5280,25461,25462],{"class":5282,"line":6512},[5280,25463,6248],{"class":5452},[5280,25465,25466,25468,25470,25472,25474,25476,25478,25480,25482,25484,25486,25488,25490,25492],{"class":5282,"line":6520},[5280,25467,6523],{"class":5424},[5280,25469,5218],{"class":5452},[5280,25471,6316],{"class":5293},[5280,25473,5835],{"class":5452},[5280,25475,5994],{"class":5424},[5280,25477,5887],{"class":5452},[5280,25479,6298],{"class":5424},[5280,25481,5218],{"class":5452},[5280,25483,6316],{"class":5293},[5280,25485,15396],{"class":5452},[5280,25487,5952],{"class":5424},[5280,25489,5218],{"class":5452},[5280,25491,6334],{"class":5424},[5280,25493,5598],{"class":5452},[5280,25495,25496],{"class":5282,"line":6550},[5280,25497,15875],{"class":5436},[5280,25499,25500],{"class":5282,"line":6556},[5280,25501,6553],{"class":5452},[5280,25503,25504],{"class":5282,"line":6561},[5280,25505,6724],{"class":5452},[5280,25507,25508],{"class":5282,"line":6621},[5280,25509,25510],{"class":5286},"    \u002F\u002F Формуємо відповідь (без пароля!)\n",[5280,25512,25513,25516,25518,25521],{"class":5282,"line":6626},[5280,25514,25515],{"class":5424},"    response",[5280,25517,6029],{"class":5452},[5280,25519,25520],{"class":5758},"UserResponse",[5280,25522,6781],{"class":5452},[5280,25524,25525,25527,25529,25531,25533,25535],{"class":5282,"line":6631},[5280,25526,17528],{"class":5424},[5280,25528,9788],{"class":5452},[5280,25530,6292],{"class":5424},[5280,25532,5218],{"class":5452},[5280,25534,18635],{"class":5424},[5280,25536,6796],{"class":5452},[5280,25538,25539,25541,25543,25545,25547,25549],{"class":5282,"line":6663},[5280,25540,17540],{"class":5424},[5280,25542,17543],{"class":5452},[5280,25544,6292],{"class":5424},[5280,25546,5218],{"class":5452},[5280,25548,6502],{"class":5424},[5280,25550,6796],{"class":5452},[5280,25552,25553,25555,25557,25559,25561,25563],{"class":5282,"line":6678},[5280,25554,17553],{"class":5424},[5280,25556,9768],{"class":5452},[5280,25558,6292],{"class":5424},[5280,25560,5218],{"class":5452},[5280,25562,6437],{"class":5424},[5280,25564,6796],{"class":5452},[5280,25566,25567,25569,25571,25573,25575,25577,25579,25581,25583,25586],{"class":5282,"line":6721},[5280,25568,17565],{"class":5424},[5280,25570,6790],{"class":5452},[5280,25572,6292],{"class":5424},[5280,25574,5218],{"class":5452},[5280,25576,24010],{"class":5424},[5280,25578,5218],{"class":5452},[5280,25580,6816],{"class":5293},[5280,25582,5835],{"class":5452},[5280,25584,25585],{"class":5297},"\"2006-01-02T15:04:05Z\"",[5280,25587,6828],{"class":5452},[5280,25589,25590],{"class":5282,"line":6727},[5280,25591,6553],{"class":5452},[5280,25593,25594],{"class":5282,"line":6751},[5280,25595,6724],{"class":5452},[5280,25597,25598,25600,25602,25604,25606,25608,25610,25612,25614,25616],{"class":5282,"line":6784},[5280,25599,6730],{"class":5424},[5280,25601,5218],{"class":5452},[5280,25603,6138],{"class":5293},[5280,25605,6141],{"class":5452},[5280,25607,6144],{"class":5293},[5280,25609,5835],{"class":5452},[5280,25611,6149],{"class":5297},[5280,25613,5887],{"class":5452},[5280,25615,6154],{"class":5297},[5280,25617,5598],{"class":5452},[5280,25619,25620,25622,25624,25626,25628,25630,25632,25634],{"class":5282,"line":6799},[5280,25621,6730],{"class":5424},[5280,25623,5218],{"class":5452},[5280,25625,6457],{"class":5293},[5280,25627,5835],{"class":5452},[5280,25629,5952],{"class":5424},[5280,25631,5218],{"class":5452},[5280,25633,6466],{"class":5424},[5280,25635,5598],{"class":5452},[5280,25637,25638,25640,25642,25644,25646,25648,25650,25652,25654,25657],{"class":5282,"line":6831},[5280,25639,6754],{"class":5424},[5280,25641,5218],{"class":5452},[5280,25643,6167],{"class":5293},[5280,25645,5835],{"class":5452},[5280,25647,5994],{"class":5424},[5280,25649,6174],{"class":5452},[5280,25651,6177],{"class":5293},[5280,25653,5835],{"class":5452},[5280,25655,25656],{"class":5424},"response",[5280,25658,5598],{"class":5452},[5280,25660,25661],{"class":5282,"line":6837},[5280,25662,5976],{"class":5452},[5280,25664,25665],{"class":5282,"line":6842},[5280,25666,5313],{"emptyLinePlaceholder":5312},[5280,25668,25669],{"class":5282,"line":6901},[5280,25670,25671],{"class":5286},"\u002F\u002F LoginRequest — DTO для входу\n",[5280,25673,25674,25676,25679,25681],{"class":5282,"line":12702},[5280,25675,7921],{"class":5619},[5280,25677,25678],{"class":5758}," LoginRequest",[5280,25680,6216],{"class":5619},[5280,25682,6111],{"class":5452},[5280,25684,25685,25687,25689],{"class":5282,"line":12731},[5280,25686,17461],{"class":5424},[5280,25688,18407],{"class":5758},[5280,25690,6242],{"class":5297},[5280,25692,25693,25695,25697],{"class":5282,"line":12736},[5280,25694,20094],{"class":5424},[5280,25696,6239],{"class":5758},[5280,25698,18420],{"class":5297},[5280,25700,25701],{"class":5282,"line":12741},[5280,25702,5976],{"class":5452},[5280,25704,25705],{"class":5282,"line":12746},[5280,25706,5313],{"emptyLinePlaceholder":5312},[5280,25708,25709],{"class":5282,"line":12752},[5280,25710,25711],{"class":5286},"\u002F\u002F LoginResponse — DTO з токеном\n",[5280,25713,25714,25716,25719,25721],{"class":5282,"line":12774},[5280,25715,7921],{"class":5619},[5280,25717,25718],{"class":5758}," LoginResponse",[5280,25720,6216],{"class":5619},[5280,25722,6111],{"class":5452},[5280,25724,25725,25728,25730],{"class":5282,"line":12794},[5280,25726,25727],{"class":5424},"    Token",[5280,25729,6239],{"class":5758},[5280,25731,25732],{"class":5297},"       `json:\"token\"`\n",[5280,25734,25735,25738,25741],{"class":5282,"line":12800},[5280,25736,25737],{"class":5424},"    User",[5280,25739,25740],{"class":5758},"  UserResponse",[5280,25742,25743],{"class":5297}," `json:\"user\"`\n",[5280,25745,25746],{"class":5282,"line":12805},[5280,25747,5976],{"class":5452},[5280,25749,25750],{"class":5282,"line":12810},[5280,25751,5313],{"emptyLinePlaceholder":5312},[5280,25753,25754],{"class":5282,"line":12816},[5280,25755,25756],{"class":5286},"\u002F\u002F Login — POST \u002Fapi\u002Fusers\u002Flogin\n",[5280,25758,25759,25761,25763,25765,25767,25769,25771,25773,25775,25777,25779,25781,25783,25785,25787,25789,25791,25793,25795],{"class":5282,"line":12836},[5280,25760,5862],{"class":5619},[5280,25762,5720],{"class":5452},[5280,25764,8069],{"class":5424},[5280,25766,8072],{"class":5452},[5280,25768,25066],{"class":5758},[5280,25770,7315],{"class":5452},[5280,25772,22552],{"class":5293},[5280,25774,5835],{"class":5452},[5280,25776,5994],{"class":5424},[5280,25778,5997],{"class":5758},[5280,25780,5218],{"class":5452},[5280,25782,6002],{"class":5758},[5280,25784,5887],{"class":5452},[5280,25786,6007],{"class":5424},[5280,25788,6010],{"class":5452},[5280,25790,5952],{"class":5758},[5280,25792,5218],{"class":5452},[5280,25794,6017],{"class":5758},[5280,25796,6020],{"class":5452},[5280,25798,25799,25801,25803],{"class":5282,"line":12842},[5280,25800,18383],{"class":5619},[5280,25802,18386],{"class":5424},[5280,25804,25805],{"class":5758}," LoginRequest\n",[5280,25807,25808,25810,25812,25814,25816,25818,25820,25822,25824,25826,25828,25830,25832,25834,25836,25838,25840,25842,25844],{"class":5282,"line":12847},[5280,25809,7023],{"class":5436},[5280,25811,6262],{"class":5424},[5280,25813,6029],{"class":5452},[5280,25815,6267],{"class":5424},[5280,25817,5218],{"class":5452},[5280,25819,6272],{"class":5293},[5280,25821,5835],{"class":5452},[5280,25823,6007],{"class":5424},[5280,25825,5218],{"class":5452},[5280,25827,6281],{"class":5424},[5280,25829,6174],{"class":5452},[5280,25831,6286],{"class":5293},[5280,25833,6289],{"class":5452},[5280,25835,18462],{"class":5424},[5280,25837,6295],{"class":5452},[5280,25839,6298],{"class":5424},[5280,25841,6301],{"class":5452},[5280,25843,5967],{"class":5619},[5280,25845,6111],{"class":5452},[5280,25847,25848,25850,25852,25854,25856,25858,25860,25862,25864,25866,25868,25870],{"class":5282,"line":18311},[5280,25849,6523],{"class":5424},[5280,25851,5218],{"class":5452},[5280,25853,6316],{"class":5293},[5280,25855,5835],{"class":5452},[5280,25857,5994],{"class":5424},[5280,25859,5887],{"class":5452},[5280,25861,6325],{"class":5297},[5280,25863,5887],{"class":5452},[5280,25865,5952],{"class":5424},[5280,25867,5218],{"class":5452},[5280,25869,6334],{"class":5424},[5280,25871,5598],{"class":5452},[5280,25873,25874],{"class":5282,"line":18338},[5280,25875,15875],{"class":5436},[5280,25877,25878],{"class":5282,"line":18343},[5280,25879,6553],{"class":5452},[5280,25881,25882],{"class":5282,"line":18348},[5280,25883,6724],{"class":5452},[5280,25885,25886],{"class":5282,"line":18380},[5280,25887,25337],{"class":5286},[5280,25889,25890,25892,25894,25896,25898,25900,25902,25904,25906,25908,25910,25912,25914,25916,25918,25920,25922,25924,25926,25928,25930,25932],{"class":5282,"line":18393},[5280,25891,18871],{"class":5424},[5280,25893,5887],{"class":5452},[5280,25895,6298],{"class":5424},[5280,25897,6029],{"class":5452},[5280,25899,11326],{"class":5424},[5280,25901,5218],{"class":5452},[5280,25903,25053],{"class":5424},[5280,25905,5218],{"class":5452},[5280,25907,22552],{"class":5293},[5280,25909,5835],{"class":5452},[5280,25911,6007],{"class":5424},[5280,25913,5218],{"class":5452},[5280,25915,15393],{"class":5293},[5280,25917,15396],{"class":5452},[5280,25919,18462],{"class":5424},[5280,25921,5218],{"class":5452},[5280,25923,6437],{"class":5424},[5280,25925,5887],{"class":5452},[5280,25927,18462],{"class":5424},[5280,25929,5218],{"class":5452},[5280,25931,19687],{"class":5424},[5280,25933,5598],{"class":5452},[5280,25935,25936,25938,25940,25942,25944],{"class":5282,"line":18402},[5280,25937,7023],{"class":5436},[5280,25939,6262],{"class":5424},[5280,25941,6301],{"class":5452},[5280,25943,5967],{"class":5619},[5280,25945,6111],{"class":5452},[5280,25947,25948,25950,25952,25954,25956,25958,25961],{"class":5282,"line":18412},[5280,25949,6259],{"class":5436},[5280,25951,6262],{"class":5424},[5280,25953,7029],{"class":5452},[5280,25955,22144],{"class":5424},[5280,25957,5218],{"class":5452},[5280,25959,25960],{"class":5424},"ErrInvalidCredentials",[5280,25962,6111],{"class":5452},[5280,25964,25965,25967,25969,25971,25973,25975,25977,25980,25982,25984,25986,25988],{"class":5282,"line":18423},[5280,25966,6311],{"class":5424},[5280,25968,5218],{"class":5452},[5280,25970,6316],{"class":5293},[5280,25972,5835],{"class":5452},[5280,25974,5994],{"class":5424},[5280,25976,5887],{"class":5452},[5280,25978,25979],{"class":5297},"\"Invalid email or password\"",[5280,25981,5887],{"class":5452},[5280,25983,5952],{"class":5424},[5280,25985,5218],{"class":5452},[5280,25987,15133],{"class":5424},[5280,25989,5598],{"class":5452},[5280,25991,25992],{"class":5282,"line":18428},[5280,25993,6412],{"class":5436},[5280,25995,25996],{"class":5282,"line":18433},[5280,25997,6248],{"class":5452},[5280,25999,26000,26002,26004,26006,26008,26010,26012,26014,26016,26018,26020,26022,26024,26026],{"class":5282,"line":18475},[5280,26001,6523],{"class":5424},[5280,26003,5218],{"class":5452},[5280,26005,6316],{"class":5293},[5280,26007,5835],{"class":5452},[5280,26009,5994],{"class":5424},[5280,26011,5887],{"class":5452},[5280,26013,6298],{"class":5424},[5280,26015,5218],{"class":5452},[5280,26017,6316],{"class":5293},[5280,26019,15396],{"class":5452},[5280,26021,5952],{"class":5424},[5280,26023,5218],{"class":5452},[5280,26025,10246],{"class":5424},[5280,26027,5598],{"class":5452},[5280,26029,26030],{"class":5282,"line":18502},[5280,26031,15875],{"class":5436},[5280,26033,26034],{"class":5282,"line":18507},[5280,26035,6553],{"class":5452},[5280,26037,26038],{"class":5282,"line":18512},[5280,26039,6724],{"class":5452},[5280,26041,26042],{"class":5282,"line":18517},[5280,26043,26044],{"class":5286},"    \u002F\u002F Генеруємо JWT токен (тут спрощено)\n",[5280,26046,26047,26050,26052,26055,26057,26059,26061,26063],{"class":5282,"line":18523},[5280,26048,26049],{"class":5424},"    token",[5280,26051,6029],{"class":5452},[5280,26053,26054],{"class":5293},"generateJWT",[5280,26056,5835],{"class":5452},[5280,26058,6292],{"class":5424},[5280,26060,5218],{"class":5452},[5280,26062,18635],{"class":5424},[5280,26064,5598],{"class":5452},[5280,26066,26067],{"class":5282,"line":18535},[5280,26068,6724],{"class":5452},[5280,26070,26071,26073,26075,26078],{"class":5282,"line":18572},[5280,26072,25515],{"class":5424},[5280,26074,6029],{"class":5452},[5280,26076,26077],{"class":5758},"LoginResponse",[5280,26079,6781],{"class":5452},[5280,26081,26082,26085,26087,26089],{"class":5282,"line":18587},[5280,26083,26084],{"class":5424},"        Token",[5280,26086,6790],{"class":5452},[5280,26088,15297],{"class":5424},[5280,26090,6796],{"class":5452},[5280,26092,26093,26096,26098,26100],{"class":5282,"line":18602},[5280,26094,26095],{"class":5424},"        User",[5280,26097,6790],{"class":5452},[5280,26099,25520],{"class":5758},[5280,26101,6781],{"class":5452},[5280,26103,26104,26107,26109,26111,26113,26115],{"class":5282,"line":18617},[5280,26105,26106],{"class":5424},"            ID",[5280,26108,9788],{"class":5452},[5280,26110,6292],{"class":5424},[5280,26112,5218],{"class":5452},[5280,26114,18635],{"class":5424},[5280,26116,6796],{"class":5452},[5280,26118,26119,26121,26123,26125,26127,26129],{"class":5282,"line":18622},[5280,26120,6224],{"class":5424},[5280,26122,17543],{"class":5452},[5280,26124,6292],{"class":5424},[5280,26126,5218],{"class":5452},[5280,26128,6502],{"class":5424},[5280,26130,6796],{"class":5452},[5280,26132,26133,26135,26137,26139,26141,26143],{"class":5282,"line":18643},[5280,26134,6236],{"class":5424},[5280,26136,9768],{"class":5452},[5280,26138,6292],{"class":5424},[5280,26140,5218],{"class":5452},[5280,26142,6437],{"class":5424},[5280,26144,6796],{"class":5452},[5280,26146,26147,26150,26152,26154,26156,26158,26160,26162,26164,26166],{"class":5282,"line":18648},[5280,26148,26149],{"class":5424},"            CreatedAt",[5280,26151,6790],{"class":5452},[5280,26153,6292],{"class":5424},[5280,26155,5218],{"class":5452},[5280,26157,24010],{"class":5424},[5280,26159,5218],{"class":5452},[5280,26161,6816],{"class":5293},[5280,26163,5835],{"class":5452},[5280,26165,25585],{"class":5297},[5280,26167,6828],{"class":5452},[5280,26169,26170],{"class":5282,"line":18667},[5280,26171,26172],{"class":5452},"        },\n",[5280,26174,26175],{"class":5282,"line":18690},[5280,26176,6553],{"class":5452},[5280,26178,26179],{"class":5282,"line":18695},[5280,26180,6724],{"class":5452},[5280,26182,26183,26185,26187,26189,26191,26193,26195,26197,26199,26201],{"class":5282,"line":18700},[5280,26184,6730],{"class":5424},[5280,26186,5218],{"class":5452},[5280,26188,6138],{"class":5293},[5280,26190,6141],{"class":5452},[5280,26192,6144],{"class":5293},[5280,26194,5835],{"class":5452},[5280,26196,6149],{"class":5297},[5280,26198,5887],{"class":5452},[5280,26200,6154],{"class":5297},[5280,26202,5598],{"class":5452},[5280,26204,26205,26207,26209,26211,26213,26215,26217,26219,26221,26223],{"class":5282,"line":18732},[5280,26206,6754],{"class":5424},[5280,26208,5218],{"class":5452},[5280,26210,6167],{"class":5293},[5280,26212,5835],{"class":5452},[5280,26214,5994],{"class":5424},[5280,26216,6174],{"class":5452},[5280,26218,6177],{"class":5293},[5280,26220,5835],{"class":5452},[5280,26222,25656],{"class":5424},[5280,26224,5598],{"class":5452},[5280,26226,26227],{"class":5282,"line":18738},[5280,26228,5976],{"class":5452},[5280,26230,26231],{"class":5282,"line":18769},[5280,26232,5313],{"emptyLinePlaceholder":5312},[5280,26234,26235,26237,26240,26242,26244,26246,26248,26250],{"class":5282,"line":18785},[5280,26236,5862],{"class":5619},[5280,26238,26239],{"class":5293}," generateJWT",[5280,26241,5835],{"class":5452},[5280,26243,15404],{"class":5424},[5280,26245,6239],{"class":5758},[5280,26247,7315],{"class":5452},[5280,26249,5844],{"class":5758},[5280,26251,6111],{"class":5452},[5280,26253,26254],{"class":5282,"line":18790},[5280,26255,20491],{"class":5286},[5280,26257,26258],{"class":5282,"line":18795},[5280,26259,26260],{"class":5286},"    \u002F\u002F У реальності: jwt-go або golang-jwt\n",[5280,26262,26263,26265,26268,26270],{"class":5282,"line":18800},[5280,26264,7328],{"class":5436},[5280,26266,26267],{"class":5297}," \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\"",[5280,26269,15333],{"class":5452},[5280,26271,26272],{"class":5424},"userID\n",[5280,26274,26275],{"class":5282,"line":18832},[5280,26276,5976],{"class":5452},[5712,26278,26279,26289],{},[5262,26280,26281,26284,26285,26288],{},[5227,26282,26283],{},"DTO (Data Transfer Object)"," — це структури для передачі даних через мережу. Вони ",[5227,26286,26287],{},"відрізняються"," від доменних сутностей:",[5194,26290,26291,26294,26297],{},[5197,26292,26293],{},"DTO містить тільки поля, необхідні для клієнта (без пароля!)",[5197,26295,26296],{},"DTO може мати іншу структуру (згруповані поля, змінені імена)",[5197,26298,26299],{},"DTO спрощує версіонування API (зміна DTO не впливає на Domain)",[5254,26301],{},[5257,26303,26305],{"id":26304},"dependency-injection-збираємо-все-разом","Dependency Injection: збираємо все разом",[5262,26307,26308,26309,26312,26313,8350,26316,5720,26319,20925],{},"Тепер потрібно ",[5227,26310,26311],{},"з'єднати"," всі шари. Це робиться у функції ",[5215,26314,26315],{},"main()",[5227,26317,26318],{},"ручне впровадження залежностей",[5207,26320,26321],{},"manual dependency injection",[5271,26323,26325],{"className":5411,"code":26324,"language":5325,"meta":5276,"style":5276},"\u002F\u002F cmd\u002Fserver\u002Fmain.go\npackage main\n\nimport (\n    \"database\u002Fsql\"\n    \"fmt\"\n    \"log\"\n    \"net\u002Fhttp\"\n    \n    \"myapp\u002Finternal\u002Fdelivery\u002Fhttp\"\n    httphandler \"myapp\u002Finternal\u002Fdelivery\u002Fhttp\"\n    \"myapp\u002Finternal\u002Frepository\u002Fmemory\"\n    \"myapp\u002Finternal\u002Frepository\u002Fpostgres\"\n    \"myapp\u002Finternal\u002Fusecase\"\n    \"myapp\u002Fmiddleware\"\n    \n    \"github.com\u002Fgo-chi\u002Fchi\u002Fv5\"\n    _ \"github.com\u002Flib\u002Fpq\"\n)\n\nfunc main() {\n    \u002F\u002F Вибираємо тип репозиторію через змінну оточення\n    usePostgres := os.Getenv(\"USE_POSTGRES\") == \"true\"\n    \n    var userRepo domain.UserRepository\n    \n    if usePostgres {\n        \u002F\u002F PostgreSQL реалізація\n        db, err := sql.Open(\"postgres\", \n            \"host=localhost port=5432 user=myuser password=mypass dbname=mydb sslmode=disable\")\n        if err != nil {\n            log.Fatal(\"Failed to connect to database:\", err)\n        }\n        defer db.Close()\n        \n        if err := db.Ping(); err != nil {\n            log.Fatal(\"Database is unreachable:\", err)\n        }\n        \n        userRepo = postgres.NewUserRepository(db)\n        log.Println(\"Using PostgreSQL repository\")\n    } else {\n        \u002F\u002F In-memory реалізація\n        userRepo = memory.NewUserRepository()\n        log.Println(\"Using in-memory repository\")\n    }\n    \n    \u002F\u002F Створюємо UseCase (впроваджуємо репозиторій)\n    userUseCase := usecase.NewUserUseCase(userRepo)\n    \n    \u002F\u002F Створюємо HTTP handler (впроваджуємо UseCase)\n    userHandler := httphandler.NewUserHandler(userUseCase)\n    \n    \u002F\u002F Налаштовуємо роутер\n    r := chi.NewRouter()\n    \n    \u002F\u002F Глобальні Middleware\n    r.Use(middleware.Logger)\n    r.Use(middleware.Recovery)\n    \n    \u002F\u002F Маршрути\n    r.Post(\"\u002Fapi\u002Fusers\u002Fregister\", userHandler.Register)\n    r.Post(\"\u002Fapi\u002Fusers\u002Flogin\", userHandler.Login)\n    \n    \u002F\u002F Захищені маршрути\n    r.Group(func(r chi.Router) {\n        r.Use(middleware.Auth)\n        \u002F\u002F тут можуть бути інші маршрути\n    })\n    \n    fmt.Println(\"🚀 Server with Clean Architecture on :8080\")\n    log.Fatal(http.ListenAndServe(\":8080\", r))\n}\n",[5215,26326,26327,26332,26338,26342,26348,26352,26356,26360,26364,26368,26373,26381,26386,26391,26395,26399,26403,26407,26414,26418,26422,26430,26435,26461,26465,26478,26482,26491,26496,26521,26528,26540,26559,26563,26577,26581,26606,26625,26629,26633,26654,26669,26677,26682,26697,26712,26716,26720,26725,26746,26750,26755,26777,26781,26786,26800,26804,26808,26826,26844,26848,26853,26877,26900,26904,26908,26932,26950,26955,26959,26963,26978,27004],{"__ignoreMap":5276},[5280,26328,26329],{"class":5282,"line":5283},[5280,26330,26331],{"class":5286},"\u002F\u002F cmd\u002Fserver\u002Fmain.go\n",[5280,26333,26334,26336],{"class":5282,"line":5290},[5280,26335,5755],{"class":5619},[5280,26337,5759],{"class":5758},[5280,26339,26340],{"class":5282,"line":5301},[5280,26341,5313],{"emptyLinePlaceholder":5312},[5280,26343,26344,26346],{"class":5282,"line":5309},[5280,26345,5768],{"class":5436},[5280,26347,5453],{"class":5452},[5280,26349,26350],{"class":5282,"line":5316},[5280,26351,23747],{"class":5297},[5280,26353,26354],{"class":5282,"line":5322},[5280,26355,5780],{"class":5297},[5280,26357,26358],{"class":5282,"line":5336},[5280,26359,5785],{"class":5297},[5280,26361,26362],{"class":5282,"line":5341},[5280,26363,5790],{"class":5297},[5280,26365,26366],{"class":5282,"line":5347},[5280,26367,6724],{"class":5452},[5280,26369,26370],{"class":5282,"line":5358},[5280,26371,26372],{"class":5297},"    \"myapp\u002Finternal\u002Fdelivery\u002Fhttp\"\n",[5280,26374,26375,26378],{"class":5282,"line":5368},[5280,26376,26377],{"class":5424},"    httphandler",[5280,26379,26380],{"class":5297}," \"myapp\u002Finternal\u002Fdelivery\u002Fhttp\"\n",[5280,26382,26383],{"class":5282,"line":5378},[5280,26384,26385],{"class":5297},"    \"myapp\u002Finternal\u002Frepository\u002Fmemory\"\n",[5280,26387,26388],{"class":5282,"line":5391},[5280,26389,26390],{"class":5297},"    \"myapp\u002Finternal\u002Frepository\u002Fpostgres\"\n",[5280,26392,26393],{"class":5282,"line":5705},[5280,26394,24985],{"class":5297},[5280,26396,26397],{"class":5282,"line":5820},[5280,26398,10075],{"class":5297},[5280,26400,26401],{"class":5282,"line":5854},[5280,26402,6724],{"class":5452},[5280,26404,26405],{"class":5282,"line":5859},[5280,26406,16422],{"class":5297},[5280,26408,26409,26411],{"class":5282,"line":5871},[5280,26410,23932],{"class":5424},[5280,26412,26413],{"class":5297}," \"github.com\u002Flib\u002Fpq\"\n",[5280,26415,26416],{"class":5282,"line":5895},[5280,26417,5598],{"class":5452},[5280,26419,26420],{"class":5282,"line":5916},[5280,26421,5313],{"emptyLinePlaceholder":5312},[5280,26423,26424,26426,26428],{"class":5282,"line":5921},[5280,26425,5862],{"class":5619},[5280,26427,5865],{"class":5293},[5280,26429,5868],{"class":5452},[5280,26431,26432],{"class":5282,"line":5939},[5280,26433,26434],{"class":5286},"    \u002F\u002F Вибираємо тип репозиторію через змінну оточення\n",[5280,26436,26437,26440,26442,26445,26447,26450,26452,26455,26458],{"class":5282,"line":5973},[5280,26438,26439],{"class":5424},"    usePostgres",[5280,26441,6029],{"class":5452},[5280,26443,26444],{"class":5424},"os",[5280,26446,5218],{"class":5452},[5280,26448,26449],{"class":5293},"Getenv",[5280,26451,5835],{"class":5452},[5280,26453,26454],{"class":5297},"\"USE_POSTGRES\"",[5280,26456,26457],{"class":5452},") == ",[5280,26459,26460],{"class":5297},"\"true\"\n",[5280,26462,26463],{"class":5282,"line":5979},[5280,26464,6724],{"class":5452},[5280,26466,26467,26469,26472,26474,26476],{"class":5282,"line":5984},[5280,26468,18383],{"class":5619},[5280,26470,26471],{"class":5424}," userRepo",[5280,26473,22023],{"class":5758},[5280,26475,5218],{"class":5452},[5280,26477,22028],{"class":5758},[5280,26479,26480],{"class":5282,"line":6023},[5280,26481,6724],{"class":5452},[5280,26483,26484,26486,26489],{"class":5282,"line":6042},[5280,26485,7023],{"class":5436},[5280,26487,26488],{"class":5424}," usePostgres",[5280,26490,6111],{"class":5452},[5280,26492,26493],{"class":5282,"line":6093},[5280,26494,26495],{"class":5286},"        \u002F\u002F PostgreSQL реалізація\n",[5280,26497,26498,26501,26503,26505,26507,26509,26511,26514,26516,26519],{"class":5282,"line":6098},[5280,26499,26500],{"class":5424},"        db",[5280,26502,5887],{"class":5452},[5280,26504,6298],{"class":5424},[5280,26506,6029],{"class":5452},[5280,26508,23784],{"class":5424},[5280,26510,5218],{"class":5452},[5280,26512,26513],{"class":5293},"Open",[5280,26515,5835],{"class":5452},[5280,26517,26518],{"class":5297},"\"postgres\"",[5280,26520,6368],{"class":5452},[5280,26522,26523,26526],{"class":5282,"line":6114},[5280,26524,26525],{"class":5297},"            \"host=localhost port=5432 user=myuser password=mypass dbname=mydb sslmode=disable\"",[5280,26527,5598],{"class":5452},[5280,26529,26530,26532,26534,26536,26538],{"class":5282,"line":6130},[5280,26531,6259],{"class":5436},[5280,26533,6262],{"class":5424},[5280,26535,6301],{"class":5452},[5280,26537,5967],{"class":5619},[5280,26539,6111],{"class":5452},[5280,26541,26542,26544,26546,26548,26550,26553,26555,26557],{"class":5282,"line":6159},[5280,26543,6342],{"class":5424},[5280,26545,5218],{"class":5452},[5280,26547,5947],{"class":5293},[5280,26549,5835],{"class":5452},[5280,26551,26552],{"class":5297},"\"Failed to connect to database:\"",[5280,26554,5887],{"class":5452},[5280,26556,6298],{"class":5424},[5280,26558,5598],{"class":5452},[5280,26560,26561],{"class":5282,"line":6187},[5280,26562,6248],{"class":5452},[5280,26564,26565,26567,26570,26572,26575],{"class":5282,"line":6193},[5280,26566,10596],{"class":5436},[5280,26568,26569],{"class":5424}," db",[5280,26571,5218],{"class":5452},[5280,26573,26574],{"class":5293},"Close",[5280,26576,6039],{"class":5452},[5280,26578,26579],{"class":5282,"line":6207},[5280,26580,6190],{"class":5452},[5280,26582,26583,26585,26587,26589,26591,26593,26596,26598,26600,26602,26604],{"class":5282,"line":6221},[5280,26584,6259],{"class":5436},[5280,26586,6262],{"class":5424},[5280,26588,6029],{"class":5452},[5280,26590,23813],{"class":5424},[5280,26592,5218],{"class":5452},[5280,26594,26595],{"class":5293},"Ping",[5280,26597,10615],{"class":5452},[5280,26599,6298],{"class":5424},[5280,26601,6301],{"class":5452},[5280,26603,5967],{"class":5619},[5280,26605,6111],{"class":5452},[5280,26607,26608,26610,26612,26614,26616,26619,26621,26623],{"class":5282,"line":6233},[5280,26609,6342],{"class":5424},[5280,26611,5218],{"class":5452},[5280,26613,5947],{"class":5293},[5280,26615,5835],{"class":5452},[5280,26617,26618],{"class":5297},"\"Database is unreachable:\"",[5280,26620,5887],{"class":5452},[5280,26622,6298],{"class":5424},[5280,26624,5598],{"class":5452},[5280,26626,26627],{"class":5282,"line":6245},[5280,26628,6248],{"class":5452},[5280,26630,26631],{"class":5282,"line":6251},[5280,26632,6190],{"class":5452},[5280,26634,26635,26638,26640,26643,26645,26648,26650,26652],{"class":5282,"line":6256},[5280,26636,26637],{"class":5424},"        userRepo",[5280,26639,5829],{"class":5452},[5280,26641,26642],{"class":5424},"postgres",[5280,26644,5218],{"class":5452},[5280,26646,26647],{"class":5293},"NewUserRepository",[5280,26649,5835],{"class":5452},[5280,26651,23813],{"class":5424},[5280,26653,5598],{"class":5452},[5280,26655,26656,26658,26660,26662,26664,26667],{"class":5282,"line":6308},[5280,26657,7039],{"class":5424},[5280,26659,5218],{"class":5452},[5280,26661,5929],{"class":5293},[5280,26663,5835],{"class":5452},[5280,26665,26666],{"class":5297},"\"Using PostgreSQL repository\"",[5280,26668,5598],{"class":5452},[5280,26670,26671,26673,26675],{"class":5282,"line":6339},[5280,26672,7070],{"class":5452},[5280,26674,7073],{"class":5436},[5280,26676,6111],{"class":5452},[5280,26678,26679],{"class":5282,"line":6371},[5280,26680,26681],{"class":5286},"        \u002F\u002F In-memory реалізація\n",[5280,26683,26684,26686,26688,26691,26693,26695],{"class":5282,"line":6409},[5280,26685,26637],{"class":5424},[5280,26687,5829],{"class":5452},[5280,26689,26690],{"class":5424},"memory",[5280,26692,5218],{"class":5452},[5280,26694,26647],{"class":5293},[5280,26696,6039],{"class":5452},[5280,26698,26699,26701,26703,26705,26707,26710],{"class":5282,"line":6415},[5280,26700,7039],{"class":5424},[5280,26702,5218],{"class":5452},[5280,26704,5929],{"class":5293},[5280,26706,5835],{"class":5452},[5280,26708,26709],{"class":5297},"\"Using in-memory repository\"",[5280,26711,5598],{"class":5452},[5280,26713,26714],{"class":5282,"line":6420},[5280,26715,6553],{"class":5452},[5280,26717,26718],{"class":5282,"line":6425},[5280,26719,6724],{"class":5452},[5280,26721,26722],{"class":5282,"line":6450},[5280,26723,26724],{"class":5286},"    \u002F\u002F Створюємо UseCase (впроваджуємо репозиторій)\n",[5280,26726,26727,26730,26732,26734,26736,26739,26741,26744],{"class":5282,"line":6471},[5280,26728,26729],{"class":5424},"    userUseCase",[5280,26731,6029],{"class":5452},[5280,26733,25023],{"class":5424},[5280,26735,5218],{"class":5452},[5280,26737,26738],{"class":5293},"NewUserUseCase",[5280,26740,5835],{"class":5452},[5280,26742,26743],{"class":5424},"userRepo",[5280,26745,5598],{"class":5452},[5280,26747,26748],{"class":5282,"line":6507},[5280,26749,6724],{"class":5452},[5280,26751,26752],{"class":5282,"line":6512},[5280,26753,26754],{"class":5286},"    \u002F\u002F Створюємо HTTP handler (впроваджуємо UseCase)\n",[5280,26756,26757,26760,26762,26765,26767,26770,26772,26775],{"class":5282,"line":6520},[5280,26758,26759],{"class":5424},"    userHandler",[5280,26761,6029],{"class":5452},[5280,26763,26764],{"class":5424},"httphandler",[5280,26766,5218],{"class":5452},[5280,26768,26769],{"class":5293},"NewUserHandler",[5280,26771,5835],{"class":5452},[5280,26773,26774],{"class":5424},"userUseCase",[5280,26776,5598],{"class":5452},[5280,26778,26779],{"class":5282,"line":6550},[5280,26780,6724],{"class":5452},[5280,26782,26783],{"class":5282,"line":6556},[5280,26784,26785],{"class":5286},"    \u002F\u002F Налаштовуємо роутер\n",[5280,26787,26788,26790,26792,26794,26796,26798],{"class":5282,"line":6561},[5280,26789,16443],{"class":5424},[5280,26791,6029],{"class":5452},[5280,26793,5474],{"class":5424},[5280,26795,5218],{"class":5452},[5280,26797,16452],{"class":5293},[5280,26799,6039],{"class":5452},[5280,26801,26802],{"class":5282,"line":6621},[5280,26803,6724],{"class":5452},[5280,26805,26806],{"class":5282,"line":6626},[5280,26807,17640],{"class":5286},[5280,26809,26810,26812,26814,26816,26818,26820,26822,26824],{"class":5282,"line":6631},[5280,26811,16443],{"class":5424},[5280,26813,5218],{"class":5452},[5280,26815,16472],{"class":5293},[5280,26817,5835],{"class":5452},[5280,26819,8768],{"class":5424},[5280,26821,5218],{"class":5452},[5280,26823,10291],{"class":5424},[5280,26825,5598],{"class":5452},[5280,26827,26828,26830,26832,26834,26836,26838,26840,26842],{"class":5282,"line":6663},[5280,26829,16443],{"class":5424},[5280,26831,5218],{"class":5452},[5280,26833,16472],{"class":5293},[5280,26835,5835],{"class":5452},[5280,26837,8768],{"class":5424},[5280,26839,5218],{"class":5452},[5280,26841,11008],{"class":5424},[5280,26843,5598],{"class":5452},[5280,26845,26846],{"class":5282,"line":6678},[5280,26847,6724],{"class":5452},[5280,26849,26850],{"class":5282,"line":6721},[5280,26851,26852],{"class":5286},"    \u002F\u002F Маршрути\n",[5280,26854,26855,26857,26859,26861,26863,26866,26868,26871,26873,26875],{"class":5282,"line":6727},[5280,26856,16443],{"class":5424},[5280,26858,5218],{"class":5452},[5280,26860,17850],{"class":5293},[5280,26862,5835],{"class":5452},[5280,26864,26865],{"class":5297},"\"\u002Fapi\u002Fusers\u002Fregister\"",[5280,26867,5887],{"class":5452},[5280,26869,26870],{"class":5424},"userHandler",[5280,26872,5218],{"class":5452},[5280,26874,25221],{"class":5424},[5280,26876,5598],{"class":5452},[5280,26878,26879,26881,26883,26885,26887,26890,26892,26894,26896,26898],{"class":5282,"line":6751},[5280,26880,16443],{"class":5424},[5280,26882,5218],{"class":5452},[5280,26884,17850],{"class":5293},[5280,26886,5835],{"class":5452},[5280,26888,26889],{"class":5297},"\"\u002Fapi\u002Fusers\u002Flogin\"",[5280,26891,5887],{"class":5452},[5280,26893,26870],{"class":5424},[5280,26895,5218],{"class":5452},[5280,26897,22552],{"class":5424},[5280,26899,5598],{"class":5452},[5280,26901,26902],{"class":5282,"line":6784},[5280,26903,6724],{"class":5452},[5280,26905,26906],{"class":5282,"line":6799},[5280,26907,17891],{"class":5286},[5280,26909,26910,26912,26914,26916,26918,26920,26922,26924,26926,26928,26930],{"class":5282,"line":6831},[5280,26911,16443],{"class":5424},[5280,26913,5218],{"class":5452},[5280,26915,16617],{"class":5293},[5280,26917,5835],{"class":5452},[5280,26919,5862],{"class":5619},[5280,26921,5835],{"class":5452},[5280,26923,6007],{"class":5424},[5280,26925,16628],{"class":5758},[5280,26927,5218],{"class":5452},[5280,26929,16633],{"class":5758},[5280,26931,6020],{"class":5452},[5280,26933,26934,26936,26938,26940,26942,26944,26946,26948],{"class":5282,"line":6837},[5280,26935,16640],{"class":5424},[5280,26937,5218],{"class":5452},[5280,26939,16472],{"class":5293},[5280,26941,5835],{"class":5452},[5280,26943,8768],{"class":5424},[5280,26945,5218],{"class":5452},[5280,26947,792],{"class":5424},[5280,26949,5598],{"class":5452},[5280,26951,26952],{"class":5282,"line":6842},[5280,26953,26954],{"class":5286},"        \u002F\u002F тут можуть бути інші маршрути\n",[5280,26956,26957],{"class":5282,"line":6901},[5280,26958,6834],{"class":5452},[5280,26960,26961],{"class":5282,"line":12702},[5280,26962,6724],{"class":5452},[5280,26964,26965,26967,26969,26971,26973,26976],{"class":5282,"line":12731},[5280,26966,5924],{"class":5424},[5280,26968,5218],{"class":5452},[5280,26970,5929],{"class":5293},[5280,26972,5835],{"class":5452},[5280,26974,26975],{"class":5297},"\"🚀 Server with Clean Architecture on :8080\"",[5280,26977,5598],{"class":5452},[5280,26979,26980,26982,26984,26986,26988,26990,26992,26994,26996,26998,27000,27002],{"class":5282,"line":12736},[5280,26981,5942],{"class":5424},[5280,26983,5218],{"class":5452},[5280,26985,5947],{"class":5293},[5280,26987,5835],{"class":5452},[5280,26989,5952],{"class":5424},[5280,26991,5218],{"class":5452},[5280,26993,5957],{"class":5293},[5280,26995,5835],{"class":5452},[5280,26997,5962],{"class":5297},[5280,26999,5887],{"class":5452},[5280,27001,6007],{"class":5424},[5280,27003,5970],{"class":5452},[5280,27005,27006],{"class":5282,"line":12741},[5280,27007,5976],{"class":5452},[7678,27009,27011],{"alt":27010},"Потік залежностей у Clean Architecture",[5271,27012,27014],{"className":7683,"code":27013,"language":7685,"meta":5276,"style":5276},"@startuml\nskinparam style plain\nskinparam backgroundColor #FFFFFF\n\ncomponent \"main()\" as Main #FEF3C7\ncomponent \"UserHandler\\n(Delivery)\" as Handler #F1F5F9\ncomponent \"UserUseCase\\n(Business Logic)\" as UseCase #DBEAFE\ncomponent \"UserRepository\\nInterface\" as RepoInterface #E2E8F0\ncomponent \"PostgresUserRepository\\n(Implementation)\" as PostgresRepo #DCFCE7\ncomponent \"MemoryUserRepository\\n(Implementation)\" as MemoryRepo #DCFCE7\n\nMain -down-> PostgresRepo : створює\nMain -down-> UseCase : створює\\nта впроваджує repo\nMain -down-> Handler : створює\\nта впроваджує useCase\n\nHandler -right-> UseCase : викликає методи\nUseCase -right-> RepoInterface : викликає методи\nPostgresRepo .up.|> RepoInterface : реалізує\nMemoryRepo .up.|> RepoInterface : реалізує\n\nnote right of UseCase\n  UseCase залежить від\n  \u003Cb>інтерфейсу\u003C\u002Fb>, а не від\n  конкретної реалізації\nend note\n\nnote bottom of Main\n  main() вирішує, яку\n  реалізацію використовувати:\n  PostgreSQL або In-Memory\nend note\n\n@enduml\n",[5215,27015,27016,27020,27024,27028,27032,27037,27042,27047,27052,27057,27062,27066,27071,27076,27081,27085,27090,27095,27100,27105,27109,27114,27119,27124,27129,27133,27137,27142,27147,27152,27157,27161,27165],{"__ignoreMap":5276},[5280,27017,27018],{"class":5282,"line":5283},[5280,27019,7692],{},[5280,27021,27022],{"class":5282,"line":5290},[5280,27023,7697],{},[5280,27025,27026],{"class":5282,"line":5301},[5280,27027,7702],{},[5280,27029,27030],{"class":5282,"line":5309},[5280,27031,5313],{"emptyLinePlaceholder":5312},[5280,27033,27034],{"class":5282,"line":5316},[5280,27035,27036],{},"component \"main()\" as Main #FEF3C7\n",[5280,27038,27039],{"class":5282,"line":5322},[5280,27040,27041],{},"component \"UserHandler\\n(Delivery)\" as Handler #F1F5F9\n",[5280,27043,27044],{"class":5282,"line":5336},[5280,27045,27046],{},"component \"UserUseCase\\n(Business Logic)\" as UseCase #DBEAFE\n",[5280,27048,27049],{"class":5282,"line":5341},[5280,27050,27051],{},"component \"UserRepository\\nInterface\" as RepoInterface #E2E8F0\n",[5280,27053,27054],{"class":5282,"line":5347},[5280,27055,27056],{},"component \"PostgresUserRepository\\n(Implementation)\" as PostgresRepo #DCFCE7\n",[5280,27058,27059],{"class":5282,"line":5358},[5280,27060,27061],{},"component \"MemoryUserRepository\\n(Implementation)\" as MemoryRepo #DCFCE7\n",[5280,27063,27064],{"class":5282,"line":5368},[5280,27065,5313],{"emptyLinePlaceholder":5312},[5280,27067,27068],{"class":5282,"line":5378},[5280,27069,27070],{},"Main -down-> PostgresRepo : створює\n",[5280,27072,27073],{"class":5282,"line":5391},[5280,27074,27075],{},"Main -down-> UseCase : створює\\nта впроваджує repo\n",[5280,27077,27078],{"class":5282,"line":5705},[5280,27079,27080],{},"Main -down-> Handler : створює\\nта впроваджує useCase\n",[5280,27082,27083],{"class":5282,"line":5820},[5280,27084,5313],{"emptyLinePlaceholder":5312},[5280,27086,27087],{"class":5282,"line":5854},[5280,27088,27089],{},"Handler -right-> UseCase : викликає методи\n",[5280,27091,27092],{"class":5282,"line":5859},[5280,27093,27094],{},"UseCase -right-> RepoInterface : викликає методи\n",[5280,27096,27097],{"class":5282,"line":5871},[5280,27098,27099],{},"PostgresRepo .up.|> RepoInterface : реалізує\n",[5280,27101,27102],{"class":5282,"line":5895},[5280,27103,27104],{},"MemoryRepo .up.|> RepoInterface : реалізує\n",[5280,27106,27107],{"class":5282,"line":5916},[5280,27108,5313],{"emptyLinePlaceholder":5312},[5280,27110,27111],{"class":5282,"line":5921},[5280,27112,27113],{},"note right of UseCase\n",[5280,27115,27116],{"class":5282,"line":5939},[5280,27117,27118],{},"  UseCase залежить від\n",[5280,27120,27121],{"class":5282,"line":5973},[5280,27122,27123],{},"  \u003Cb>інтерфейсу\u003C\u002Fb>, а не від\n",[5280,27125,27126],{"class":5282,"line":5979},[5280,27127,27128],{},"  конкретної реалізації\n",[5280,27130,27131],{"class":5282,"line":5984},[5280,27132,7765],{},[5280,27134,27135],{"class":5282,"line":6023},[5280,27136,5313],{"emptyLinePlaceholder":5312},[5280,27138,27139],{"class":5282,"line":6042},[5280,27140,27141],{},"note bottom of Main\n",[5280,27143,27144],{"class":5282,"line":6093},[5280,27145,27146],{},"  main() вирішує, яку\n",[5280,27148,27149],{"class":5282,"line":6098},[5280,27150,27151],{},"  реалізацію використовувати:\n",[5280,27153,27154],{"class":5282,"line":6114},[5280,27155,27156],{},"  PostgreSQL або In-Memory\n",[5280,27158,27159],{"class":5282,"line":6130},[5280,27160,7765],{},[5280,27162,27163],{"class":5282,"line":6159},[5280,27164,5313],{"emptyLinePlaceholder":5312},[5280,27166,27167],{"class":5282,"line":6187},[5280,27168,7904],{},[5262,27170,27171],{},[5227,27172,27173],{},"Що ми отримали?",[6916,27175,27176,27182,27188,27194],{},[5197,27177,27178,27181],{},[5227,27179,27180],{},"Тестованість:"," Кожен шар можна тестувати незалежно з моками",[5197,27183,27184,27187],{},[5227,27185,27186],{},"Гнучкість:"," Легко змінити СУБД, HTTP-фреймворк, формат відповіді",[5197,27189,27190,27193],{},[5227,27191,27192],{},"Розширюваність:"," Додати gRPC-сервіс паралельно з HTTP без зміни бізнес-логіки",[5197,27195,27196,27199],{},[5227,27197,27198],{},"Підтримуваність:"," Чітке розділення відповідальності",[8342,27201,27202,27205],{},[5227,27203,27204],{},"Ручна Dependency Injection у Go"," є стандартною практикою. На відміну від Java (Spring) чи C# (.NET DI), Go-спільнота віддає перевагу явності та простоті. Фреймворки типу Wire або Dig існують, але використовуються рідко.",[5254,27207],{},[5257,27209,27211],{"id":27210},"модульне-тестування-з-clean-architecture","Модульне тестування з Clean Architecture",[5266,27213,27215],{"id":27214},"перевага-архітектури-ізольоване-тестування-кожного-шару","Перевага архітектури: ізольоване тестування кожного шару",[5262,27217,27218],{},"Завдяки чіткому розділенню шарів, ми можемо тестувати:",[5194,27220,27221,27227,27233,27239],{},[5197,27222,27223,27226],{},[5227,27224,27225],{},"Domain:"," валідацію та бізнес-правила",[5197,27228,27229,27232],{},[5227,27230,27231],{},"UseCase:"," бізнес-логіку з мок-репозиторіями",[5197,27234,27235,27238],{},[5227,27236,27237],{},"Repository:"," запити до БД (інтеграційні тести)",[5197,27240,27241,27244],{},[5227,27242,27243],{},"Handlers:"," HTTP-шар з мок-UseCases",[5266,27246,27248,27249],{"id":27247},"пакет-nethttphttptest","Пакет ",[5215,27250,5217],{},[5262,27252,27253],{},"Go надає вбудований пакет для тестування HTTP без запуску сервера:",[5262,27255,27256],{},[5227,27257,27258],{},"Основні компоненти:",[5194,27260,27261,27267],{},[5197,27262,27263,27266],{},[5215,27264,27265],{},"httptest.ResponseRecorder"," — записує відповідь без надсилання по мережі",[5197,27268,27269,27272],{},[5215,27270,27271],{},"httptest.NewRequest"," — створює фіктивний HTTP-запит",[5266,27274,27276],{"id":27275},"крок-1-тестування-domain","Крок 1: Тестування Domain",[5271,27278,27280],{"className":5411,"code":27279,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fdomain\u002Fuser_test.go\npackage domain\n\nimport (\n    \"testing\"\n)\n\nfunc TestUser_Validate(t *testing.T) {\n    tests := []struct {\n        name    string\n        user    User\n        wantErr bool\n        errMsg  string\n    }{\n        {\n            name: \"Valid user\",\n            user: User{\n                Name:     \"John Doe\",\n                Email:    \"john@example.com\",\n                Password: \"password123\",\n            },\n            wantErr: false,\n        },\n        {\n            name: \"Missing name\",\n            user: User{\n                Name:     \"\",\n                Email:    \"john@example.com\",\n                Password: \"password123\",\n            },\n            wantErr: true,\n            errMsg:  \"name is required\",\n        },\n        {\n            name: \"Invalid email format\",\n            user: User{\n                Name:     \"John\",\n                Email:    \"invalid-email\",\n                Password: \"password123\",\n            },\n            wantErr: true,\n            errMsg:  \"invalid email format\",\n        },\n        {\n            name: \"Short password\",\n            user: User{\n                Name:     \"John\",\n                Email:    \"john@example.com\",\n                Password: \"short\",\n            },\n            wantErr: true,\n            errMsg:  \"password must be at least 8 characters\",\n        },\n    }\n    \n    for _, tt := range tests {\n        t.Run(tt.name, func(t *testing.T) {\n            err := tt.user.Validate()\n            \n            if tt.wantErr {\n                if err == nil {\n                    t.Errorf(\"Expected error, got nil\")\n                } else if err.Error() != tt.errMsg {\n                    t.Errorf(\"Expected error %q, got %q\", tt.errMsg, err.Error())\n                }\n            } else {\n                if err != nil {\n                    t.Errorf(\"Expected no error, got %v\", err)\n                }\n            }\n        })\n    }\n}\n",[5215,27281,27282,27287,27293,27297,27303,27308,27312,27316,27340,27353,27360,27367,27375,27383,27388,27393,27405,27416,27427,27438,27450,27455,27466,27470,27474,27485,27495,27505,27515,27525,27529,27539,27550,27554,27558,27569,27579,27590,27601,27611,27615,27625,27635,27639,27643,27654,27664,27674,27684,27695,27699,27709,27719,27723,27727,27731,27751,27787,27806,27810,27824,27836,27852,27879,27920,27924,27932,27944,27967,27971,27975,27979,27983],{"__ignoreMap":5276},[5280,27283,27284],{"class":5282,"line":5283},[5280,27285,27286],{"class":5286},"\u002F\u002F internal\u002Fdomain\u002Fuser_test.go\n",[5280,27288,27289,27291],{"class":5282,"line":5290},[5280,27290,5755],{"class":5619},[5280,27292,21200],{"class":5758},[5280,27294,27295],{"class":5282,"line":5301},[5280,27296,5313],{"emptyLinePlaceholder":5312},[5280,27298,27299,27301],{"class":5282,"line":5309},[5280,27300,5768],{"class":5436},[5280,27302,5453],{"class":5452},[5280,27304,27305],{"class":5282,"line":5316},[5280,27306,27307],{"class":5297},"    \"testing\"\n",[5280,27309,27310],{"class":5282,"line":5322},[5280,27311,5598],{"class":5452},[5280,27313,27314],{"class":5282,"line":5336},[5280,27315,5313],{"emptyLinePlaceholder":5312},[5280,27317,27318,27320,27323,27325,27328,27330,27333,27335,27338],{"class":5282,"line":5341},[5280,27319,5862],{"class":5619},[5280,27321,27322],{"class":5293}," TestUser_Validate",[5280,27324,5835],{"class":5452},[5280,27326,27327],{"class":5424},"t",[5280,27329,6010],{"class":5452},[5280,27331,27332],{"class":5758},"testing",[5280,27334,5218],{"class":5452},[5280,27336,27337],{"class":5758},"T",[5280,27339,6020],{"class":5452},[5280,27341,27342,27345,27348,27351],{"class":5282,"line":5347},[5280,27343,27344],{"class":5424},"    tests",[5280,27346,27347],{"class":5452}," := []",[5280,27349,27350],{"class":5619},"struct",[5280,27352,6111],{"class":5452},[5280,27354,27355,27358],{"class":5282,"line":5358},[5280,27356,27357],{"class":5424},"        name",[5280,27359,20089],{"class":5758},[5280,27361,27362,27364],{"class":5282,"line":5368},[5280,27363,23965],{"class":5424},[5280,27365,27366],{"class":5758},"    User\n",[5280,27368,27369,27372],{"class":5282,"line":5378},[5280,27370,27371],{"class":5424},"        wantErr",[5280,27373,27374],{"class":5758}," bool\n",[5280,27376,27377,27380],{"class":5282,"line":5391},[5280,27378,27379],{"class":5424},"        errMsg",[5280,27381,27382],{"class":5758},"  string\n",[5280,27384,27385],{"class":5282,"line":5705},[5280,27386,27387],{"class":5452},"    }{\n",[5280,27389,27390],{"class":5282,"line":5820},[5280,27391,27392],{"class":5452},"        {\n",[5280,27394,27395,27398,27400,27403],{"class":5282,"line":5854},[5280,27396,27397],{"class":5424},"            name",[5280,27399,6790],{"class":5452},[5280,27401,27402],{"class":5297},"\"Valid user\"",[5280,27404,6796],{"class":5452},[5280,27406,27407,27410,27412,27414],{"class":5282,"line":5859},[5280,27408,27409],{"class":5424},"            user",[5280,27411,6790],{"class":5452},[5280,27413,17513],{"class":5758},[5280,27415,6781],{"class":5452},[5280,27417,27418,27421,27423,27425],{"class":5282,"line":5871},[5280,27419,27420],{"class":5424},"                Name",[5280,27422,9768],{"class":5452},[5280,27424,17546],{"class":5297},[5280,27426,6796],{"class":5452},[5280,27428,27429,27432,27434,27436],{"class":5282,"line":5895},[5280,27430,27431],{"class":5424},"                Email",[5280,27433,13867],{"class":5452},[5280,27435,17558],{"class":5297},[5280,27437,6796],{"class":5452},[5280,27439,27440,27443,27445,27448],{"class":5282,"line":5916},[5280,27441,27442],{"class":5424},"                Password",[5280,27444,6790],{"class":5452},[5280,27446,27447],{"class":5297},"\"password123\"",[5280,27449,6796],{"class":5452},[5280,27451,27452],{"class":5282,"line":5921},[5280,27453,27454],{"class":5452},"            },\n",[5280,27456,27457,27460,27462,27464],{"class":5282,"line":5939},[5280,27458,27459],{"class":5424},"            wantErr",[5280,27461,6790],{"class":5452},[5280,27463,8915],{"class":5619},[5280,27465,6796],{"class":5452},[5280,27467,27468],{"class":5282,"line":5973},[5280,27469,26172],{"class":5452},[5280,27471,27472],{"class":5282,"line":5979},[5280,27473,27392],{"class":5452},[5280,27475,27476,27478,27480,27483],{"class":5282,"line":5984},[5280,27477,27397],{"class":5424},[5280,27479,6790],{"class":5452},[5280,27481,27482],{"class":5297},"\"Missing name\"",[5280,27484,6796],{"class":5452},[5280,27486,27487,27489,27491,27493],{"class":5282,"line":6023},[5280,27488,27409],{"class":5424},[5280,27490,6790],{"class":5452},[5280,27492,17513],{"class":5758},[5280,27494,6781],{"class":5452},[5280,27496,27497,27499,27501,27503],{"class":5282,"line":6042},[5280,27498,27420],{"class":5424},[5280,27500,9768],{"class":5452},[5280,27502,12375],{"class":5297},[5280,27504,6796],{"class":5452},[5280,27506,27507,27509,27511,27513],{"class":5282,"line":6093},[5280,27508,27431],{"class":5424},[5280,27510,13867],{"class":5452},[5280,27512,17558],{"class":5297},[5280,27514,6796],{"class":5452},[5280,27516,27517,27519,27521,27523],{"class":5282,"line":6098},[5280,27518,27442],{"class":5424},[5280,27520,6790],{"class":5452},[5280,27522,27447],{"class":5297},[5280,27524,6796],{"class":5452},[5280,27526,27527],{"class":5282,"line":6114},[5280,27528,27454],{"class":5452},[5280,27530,27531,27533,27535,27537],{"class":5282,"line":6130},[5280,27532,27459],{"class":5424},[5280,27534,6790],{"class":5452},[5280,27536,8868],{"class":5619},[5280,27538,6796],{"class":5452},[5280,27540,27541,27544,27546,27548],{"class":5282,"line":6159},[5280,27542,27543],{"class":5424},"            errMsg",[5280,27545,19517],{"class":5452},[5280,27547,21394],{"class":5297},[5280,27549,6796],{"class":5452},[5280,27551,27552],{"class":5282,"line":6187},[5280,27553,26172],{"class":5452},[5280,27555,27556],{"class":5282,"line":6193},[5280,27557,27392],{"class":5452},[5280,27559,27560,27562,27564,27567],{"class":5282,"line":6207},[5280,27561,27397],{"class":5424},[5280,27563,6790],{"class":5452},[5280,27565,27566],{"class":5297},"\"Invalid email format\"",[5280,27568,6796],{"class":5452},[5280,27570,27571,27573,27575,27577],{"class":5282,"line":6221},[5280,27572,27409],{"class":5424},[5280,27574,6790],{"class":5452},[5280,27576,17513],{"class":5758},[5280,27578,6781],{"class":5452},[5280,27580,27581,27583,27585,27588],{"class":5282,"line":6233},[5280,27582,27420],{"class":5424},[5280,27584,9768],{"class":5452},[5280,27586,27587],{"class":5297},"\"John\"",[5280,27589,6796],{"class":5452},[5280,27591,27592,27594,27596,27599],{"class":5282,"line":6245},[5280,27593,27431],{"class":5424},[5280,27595,13867],{"class":5452},[5280,27597,27598],{"class":5297},"\"invalid-email\"",[5280,27600,6796],{"class":5452},[5280,27602,27603,27605,27607,27609],{"class":5282,"line":6251},[5280,27604,27442],{"class":5424},[5280,27606,6790],{"class":5452},[5280,27608,27447],{"class":5297},[5280,27610,6796],{"class":5452},[5280,27612,27613],{"class":5282,"line":6256},[5280,27614,27454],{"class":5452},[5280,27616,27617,27619,27621,27623],{"class":5282,"line":6308},[5280,27618,27459],{"class":5424},[5280,27620,6790],{"class":5452},[5280,27622,8868],{"class":5619},[5280,27624,6796],{"class":5452},[5280,27626,27627,27629,27631,27633],{"class":5282,"line":6339},[5280,27628,27543],{"class":5424},[5280,27630,19517],{"class":5452},[5280,27632,21484],{"class":5297},[5280,27634,6796],{"class":5452},[5280,27636,27637],{"class":5282,"line":6371},[5280,27638,26172],{"class":5452},[5280,27640,27641],{"class":5282,"line":6409},[5280,27642,27392],{"class":5452},[5280,27644,27645,27647,27649,27652],{"class":5282,"line":6415},[5280,27646,27397],{"class":5424},[5280,27648,6790],{"class":5452},[5280,27650,27651],{"class":5297},"\"Short password\"",[5280,27653,6796],{"class":5452},[5280,27655,27656,27658,27660,27662],{"class":5282,"line":6420},[5280,27657,27409],{"class":5424},[5280,27659,6790],{"class":5452},[5280,27661,17513],{"class":5758},[5280,27663,6781],{"class":5452},[5280,27665,27666,27668,27670,27672],{"class":5282,"line":6425},[5280,27667,27420],{"class":5424},[5280,27669,9768],{"class":5452},[5280,27671,27587],{"class":5297},[5280,27673,6796],{"class":5452},[5280,27675,27676,27678,27680,27682],{"class":5282,"line":6450},[5280,27677,27431],{"class":5424},[5280,27679,13867],{"class":5452},[5280,27681,17558],{"class":5297},[5280,27683,6796],{"class":5452},[5280,27685,27686,27688,27690,27693],{"class":5282,"line":6471},[5280,27687,27442],{"class":5424},[5280,27689,6790],{"class":5452},[5280,27691,27692],{"class":5297},"\"short\"",[5280,27694,6796],{"class":5452},[5280,27696,27697],{"class":5282,"line":6507},[5280,27698,27454],{"class":5452},[5280,27700,27701,27703,27705,27707],{"class":5282,"line":6512},[5280,27702,27459],{"class":5424},[5280,27704,6790],{"class":5452},[5280,27706,8868],{"class":5619},[5280,27708,6796],{"class":5452},[5280,27710,27711,27713,27715,27717],{"class":5282,"line":6520},[5280,27712,27543],{"class":5424},[5280,27714,19517],{"class":5452},[5280,27716,20254],{"class":5297},[5280,27718,6796],{"class":5452},[5280,27720,27721],{"class":5282,"line":6550},[5280,27722,26172],{"class":5452},[5280,27724,27725],{"class":5282,"line":6556},[5280,27726,6553],{"class":5452},[5280,27728,27729],{"class":5282,"line":6561},[5280,27730,6724],{"class":5452},[5280,27732,27733,27735,27737,27739,27742,27744,27746,27749],{"class":5282,"line":6621},[5280,27734,11383],{"class":5436},[5280,27736,12286],{"class":5424},[5280,27738,5887],{"class":5452},[5280,27740,27741],{"class":5424},"tt",[5280,27743,6029],{"class":5452},[5280,27745,12296],{"class":5436},[5280,27747,27748],{"class":5424}," tests",[5280,27750,6111],{"class":5452},[5280,27752,27753,27756,27758,27761,27763,27765,27767,27769,27771,27773,27775,27777,27779,27781,27783,27785],{"class":5282,"line":6626},[5280,27754,27755],{"class":5424},"        t",[5280,27757,5218],{"class":5452},[5280,27759,27760],{"class":5293},"Run",[5280,27762,5835],{"class":5452},[5280,27764,27741],{"class":5424},[5280,27766,5218],{"class":5452},[5280,27768,20121],{"class":5424},[5280,27770,5887],{"class":5452},[5280,27772,5862],{"class":5619},[5280,27774,5835],{"class":5452},[5280,27776,27327],{"class":5424},[5280,27778,6010],{"class":5452},[5280,27780,27332],{"class":5758},[5280,27782,5218],{"class":5452},[5280,27784,27337],{"class":5758},[5280,27786,6020],{"class":5452},[5280,27788,27789,27792,27794,27796,27798,27800,27802,27804],{"class":5282,"line":6631},[5280,27790,27791],{"class":5424},"            err",[5280,27793,6029],{"class":5452},[5280,27795,27741],{"class":5424},[5280,27797,5218],{"class":5452},[5280,27799,6292],{"class":5424},[5280,27801,5218],{"class":5452},[5280,27803,21356],{"class":5293},[5280,27805,6039],{"class":5452},[5280,27807,27808],{"class":5282,"line":6663},[5280,27809,12263],{"class":5452},[5280,27811,27812,27814,27817,27819,27822],{"class":5282,"line":6678},[5280,27813,10605],{"class":5436},[5280,27815,27816],{"class":5424}," tt",[5280,27818,5218],{"class":5452},[5280,27820,27821],{"class":5424},"wantErr",[5280,27823,6111],{"class":5452},[5280,27825,27826,27828,27830,27832,27834],{"class":5282,"line":6721},[5280,27827,12311],{"class":5436},[5280,27829,6262],{"class":5424},[5280,27831,7029],{"class":5452},[5280,27833,5967],{"class":5619},[5280,27835,6111],{"class":5452},[5280,27837,27838,27841,27843,27845,27847,27850],{"class":5282,"line":6727},[5280,27839,27840],{"class":5424},"                    t",[5280,27842,5218],{"class":5452},[5280,27844,15523],{"class":5293},[5280,27846,5835],{"class":5452},[5280,27848,27849],{"class":5297},"\"Expected error, got nil\"",[5280,27851,5598],{"class":5452},[5280,27853,27854,27856,27858,27861,27863,27865,27867,27870,27872,27874,27877],{"class":5282,"line":6751},[5280,27855,12418],{"class":5452},[5280,27857,7073],{"class":5436},[5280,27859,27860],{"class":5436}," if",[5280,27862,6262],{"class":5424},[5280,27864,5218],{"class":5452},[5280,27866,6316],{"class":5293},[5280,27868,27869],{"class":5452},"() != ",[5280,27871,27741],{"class":5424},[5280,27873,5218],{"class":5452},[5280,27875,27876],{"class":5424},"errMsg",[5280,27878,6111],{"class":5452},[5280,27880,27881,27883,27885,27887,27889,27892,27895,27898,27900,27902,27904,27906,27908,27910,27912,27914,27916,27918],{"class":5282,"line":6784},[5280,27882,27840],{"class":5424},[5280,27884,5218],{"class":5452},[5280,27886,15523],{"class":5293},[5280,27888,5835],{"class":5452},[5280,27890,27891],{"class":5297},"\"Expected error ",[5280,27893,27894],{"class":5424},"%q",[5280,27896,27897],{"class":5297},", got ",[5280,27899,27894],{"class":5424},[5280,27901,6584],{"class":5297},[5280,27903,5887],{"class":5452},[5280,27905,27741],{"class":5424},[5280,27907,5218],{"class":5452},[5280,27909,27876],{"class":5424},[5280,27911,5887],{"class":5452},[5280,27913,6298],{"class":5424},[5280,27915,5218],{"class":5452},[5280,27917,6316],{"class":5293},[5280,27919,10669],{"class":5452},[5280,27921,27922],{"class":5282,"line":6799},[5280,27923,12350],{"class":5452},[5280,27925,27926,27928,27930],{"class":5282,"line":6831},[5280,27927,13464],{"class":5452},[5280,27929,7073],{"class":5436},[5280,27931,6111],{"class":5452},[5280,27933,27934,27936,27938,27940,27942],{"class":5282,"line":6837},[5280,27935,12311],{"class":5436},[5280,27937,6262],{"class":5424},[5280,27939,6301],{"class":5452},[5280,27941,5967],{"class":5619},[5280,27943,6111],{"class":5452},[5280,27945,27946,27948,27950,27952,27954,27957,27959,27961,27963,27965],{"class":5282,"line":6842},[5280,27947,27840],{"class":5424},[5280,27949,5218],{"class":5452},[5280,27951,15523],{"class":5293},[5280,27953,5835],{"class":5452},[5280,27955,27956],{"class":5297},"\"Expected no error, got ",[5280,27958,6362],{"class":5424},[5280,27960,6584],{"class":5297},[5280,27962,5887],{"class":5452},[5280,27964,6298],{"class":5424},[5280,27966,5598],{"class":5452},[5280,27968,27969],{"class":5282,"line":6901},[5280,27970,12350],{"class":5452},[5280,27972,27973],{"class":5282,"line":12702},[5280,27974,10717],{"class":5452},[5280,27976,27977],{"class":5282,"line":12731},[5280,27978,12839],{"class":5452},[5280,27980,27981],{"class":5282,"line":12736},[5280,27982,6553],{"class":5452},[5280,27984,27985],{"class":5282,"line":12741},[5280,27986,5976],{"class":5452},[5262,27988,27989],{},[5227,27990,27991],{},"Запуск тестів:",[8866,27993,27995,28005,28009,28013,28017,28021,28025,28029,28033,28037,28041,28045,28053],{":cursor":8868,"title":27994},"go test .\u002Finternal\u002Fdomain",[8871,27996,27998,7648,28001],{"className":27997},[5282],[5280,27999,8878],{"className":28000},[8877],[5227,28002,28004],{"className":28003},[8882],"go test .\u002Finternal\u002Fdomain -v",[8871,28006,28008],{"className":28007},[5282],"=== RUN   TestUser_Validate",[8871,28010,28012],{"className":28011},[5282],"=== RUN   TestUser_Validate\u002FValid_user",[8871,28014,28016],{"className":28015},[5282],"=== RUN   TestUser_Validate\u002FMissing_name",[8871,28018,28020],{"className":28019},[5282],"=== RUN   TestUser_Validate\u002FInvalid_email_format",[8871,28022,28024],{"className":28023},[5282],"=== RUN   TestUser_Validate\u002FShort_password",[8871,28026,28028],{"className":28027},[5282],"--- PASS: TestUser_Validate (0.00s)",[8871,28030,28032],{"className":28031},[5282],"    --- PASS: TestUser_Validate\u002FValid_user (0.00s)",[8871,28034,28036],{"className":28035},[5282],"    --- PASS: TestUser_Validate\u002FMissing_name (0.00s)",[8871,28038,28040],{"className":28039},[5282],"    --- PASS: TestUser_Validate\u002FInvalid_email_format (0.00s)",[8871,28042,28044],{"className":28043},[5282],"    --- PASS: TestUser_Validate\u002FShort_password (0.00s)",[8871,28046,28048],{"className":28047},[5282],[5280,28049,28052],{"className":28050},[28051,8882],"text-green-400","PASS",[8871,28054,28056],{"className":28055},[5282],"ok      myapp\u002Finternal\u002Fdomain   0.003s",[5266,28058,28060],{"id":28059},"крок-2-тестування-usecase-з-мок-репозиторієм","Крок 2: Тестування UseCase з мок-репозиторієм",[5262,28062,28063,28064,5408],{},"Створимо мок-реалізацію ",[5215,28065,22060],{},[5271,28067,28069],{"className":5411,"code":28068,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fusecase\u002Fuser_usecase_test.go\npackage usecase\n\nimport (\n    \"context\"\n    \"myapp\u002Finternal\u002Fdomain\"\n    \"testing\"\n)\n\n\u002F\u002F MockUserRepository — мок для тестування\ntype MockUserRepository struct {\n    users         map[string]*domain.User\n    createErr     error\n    findByEmailFn func(ctx context.Context, email string) (*domain.User, error)\n}\n\nfunc NewMockUserRepository() *MockUserRepository {\n    return &MockUserRepository{\n        users: make(map[string]*domain.User),\n    }\n}\n\nfunc (m *MockUserRepository) Create(ctx context.Context, user *domain.User) error {\n    if m.createErr != nil {\n        return m.createErr\n    }\n    m.users[user.ID] = user\n    return nil\n}\n\nfunc (m *MockUserRepository) FindByEmail(ctx context.Context, email string) (*domain.User, error) {\n    if m.findByEmailFn != nil {\n        return m.findByEmailFn(ctx, email)\n    }\n    \n    for _, u := range m.users {\n        if u.Email == email {\n            return u, nil\n        }\n    }\n    return nil, domain.ErrUserNotFound\n}\n\nfunc (m *MockUserRepository) FindByID(ctx context.Context, id string) (*domain.User, error) {\n    user, exists := m.users[id]\n    if !exists {\n        return nil, domain.ErrUserNotFound\n    }\n    return user, nil\n}\n\nfunc (m *MockUserRepository) Update(ctx context.Context, user *domain.User) error {\n    m.users[user.ID] = user\n    return nil\n}\n\nfunc (m *MockUserRepository) Delete(ctx context.Context, id string) error {\n    delete(m.users, id)\n    return nil\n}\n",[5215,28070,28071,28076,28082,28086,28092,28096,28100,28104,28108,28112,28117,28128,28147,28155,28192,28196,28200,28214,28224,28250,28254,28258,28262,28307,28325,28336,28340,28361,28367,28371,28375,28421,28438,28458,28462,28466,28488,28504,28514,28518,28522,28536,28540,28544,28590,28613,28623,28637,28641,28651,28655,28659,28703,28723,28729,28733,28737,28775,28793,28799],{"__ignoreMap":5276},[5280,28072,28073],{"class":5282,"line":5283},[5280,28074,28075],{"class":5286},"\u002F\u002F internal\u002Fusecase\u002Fuser_usecase_test.go\n",[5280,28077,28078,28080],{"class":5282,"line":5290},[5280,28079,5755],{"class":5619},[5280,28081,21959],{"class":5758},[5280,28083,28084],{"class":5282,"line":5301},[5280,28085,5313],{"emptyLinePlaceholder":5312},[5280,28087,28088,28090],{"class":5282,"line":5309},[5280,28089,5768],{"class":5436},[5280,28091,5453],{"class":5452},[5280,28093,28094],{"class":5282,"line":5316},[5280,28095,14862],{"class":5297},[5280,28097,28098],{"class":5282,"line":5322},[5280,28099,21978],{"class":5297},[5280,28101,28102],{"class":5282,"line":5336},[5280,28103,27307],{"class":5297},[5280,28105,28106],{"class":5282,"line":5341},[5280,28107,5598],{"class":5452},[5280,28109,28110],{"class":5282,"line":5347},[5280,28111,5313],{"emptyLinePlaceholder":5312},[5280,28113,28114],{"class":5282,"line":5358},[5280,28115,28116],{"class":5286},"\u002F\u002F MockUserRepository — мок для тестування\n",[5280,28118,28119,28121,28124,28126],{"class":5282,"line":5368},[5280,28120,7921],{"class":5619},[5280,28122,28123],{"class":5758}," MockUserRepository",[5280,28125,6216],{"class":5619},[5280,28127,6111],{"class":5452},[5280,28129,28130,28132,28135,28137,28139,28141,28143,28145],{"class":5282,"line":5378},[5280,28131,18625],{"class":5424},[5280,28133,28134],{"class":5619},"         map",[5280,28136,5841],{"class":5452},[5280,28138,5844],{"class":5758},[5280,28140,13724],{"class":5452},[5280,28142,22144],{"class":5758},[5280,28144,5218],{"class":5452},[5280,28146,22942],{"class":5758},[5280,28148,28149,28152],{"class":5282,"line":5391},[5280,28150,28151],{"class":5424},"    createErr",[5280,28153,28154],{"class":5758},"     error\n",[5280,28156,28157,28160,28162,28164,28166,28168,28170,28172,28174,28176,28178,28180,28182,28184,28186,28188,28190],{"class":5282,"line":5705},[5280,28158,28159],{"class":5424},"    findByEmailFn",[5280,28161,7331],{"class":5619},[5280,28163,5835],{"class":5452},[5280,28165,15441],{"class":5424},[5280,28167,21723],{"class":5758},[5280,28169,5218],{"class":5452},[5280,28171,15393],{"class":5758},[5280,28173,5887],{"class":5452},[5280,28175,20126],{"class":5424},[5280,28177,6239],{"class":5758},[5280,28179,20151],{"class":5452},[5280,28181,22144],{"class":5758},[5280,28183,5218],{"class":5452},[5280,28185,17513],{"class":5758},[5280,28187,5887],{"class":5452},[5280,28189,9567],{"class":5758},[5280,28191,5598],{"class":5452},[5280,28193,28194],{"class":5282,"line":5820},[5280,28195,5976],{"class":5452},[5280,28197,28198],{"class":5282,"line":5854},[5280,28199,5313],{"emptyLinePlaceholder":5312},[5280,28201,28202,28204,28207,28209,28212],{"class":5282,"line":5859},[5280,28203,5862],{"class":5619},[5280,28205,28206],{"class":5293}," NewMockUserRepository",[5280,28208,22965],{"class":5452},[5280,28210,28211],{"class":5758},"MockUserRepository",[5280,28213,6111],{"class":5452},[5280,28215,28216,28218,28220,28222],{"class":5282,"line":5871},[5280,28217,7328],{"class":5436},[5280,28219,13807],{"class":5452},[5280,28221,28211],{"class":5758},[5280,28223,6781],{"class":5452},[5280,28225,28226,28228,28230,28232,28234,28236,28238,28240,28242,28244,28246,28248],{"class":5282,"line":5895},[5280,28227,6428],{"class":5424},[5280,28229,6790],{"class":5452},[5280,28231,5832],{"class":5293},[5280,28233,5835],{"class":5452},[5280,28235,5838],{"class":5619},[5280,28237,5841],{"class":5452},[5280,28239,5844],{"class":5758},[5280,28241,13724],{"class":5452},[5280,28243,22144],{"class":5758},[5280,28245,5218],{"class":5452},[5280,28247,17513],{"class":5758},[5280,28249,6828],{"class":5452},[5280,28251,28252],{"class":5282,"line":5916},[5280,28253,6553],{"class":5452},[5280,28255,28256],{"class":5282,"line":5921},[5280,28257,5976],{"class":5452},[5280,28259,28260],{"class":5282,"line":5939},[5280,28261,5313],{"emptyLinePlaceholder":5312},[5280,28263,28264,28266,28268,28271,28273,28275,28277,28279,28281,28283,28285,28287,28289,28291,28293,28295,28297,28299,28301,28303,28305],{"class":5282,"line":5973},[5280,28265,5862],{"class":5619},[5280,28267,5720],{"class":5452},[5280,28269,28270],{"class":5424},"m ",[5280,28272,8072],{"class":5452},[5280,28274,28211],{"class":5758},[5280,28276,7315],{"class":5452},[5280,28278,22476],{"class":5293},[5280,28280,5835],{"class":5452},[5280,28282,15441],{"class":5424},[5280,28284,21723],{"class":5758},[5280,28286,5218],{"class":5452},[5280,28288,15393],{"class":5758},[5280,28290,5887],{"class":5452},[5280,28292,6292],{"class":5424},[5280,28294,6010],{"class":5452},[5280,28296,22144],{"class":5758},[5280,28298,5218],{"class":5452},[5280,28300,17513],{"class":5758},[5280,28302,7315],{"class":5452},[5280,28304,9567],{"class":5758},[5280,28306,6111],{"class":5452},[5280,28308,28309,28311,28314,28316,28319,28321,28323],{"class":5282,"line":5979},[5280,28310,7023],{"class":5436},[5280,28312,28313],{"class":5424}," m",[5280,28315,5218],{"class":5452},[5280,28317,28318],{"class":5424},"createErr",[5280,28320,6301],{"class":5452},[5280,28322,5967],{"class":5619},[5280,28324,6111],{"class":5452},[5280,28326,28327,28329,28331,28333],{"class":5282,"line":5984},[5280,28328,12200],{"class":5436},[5280,28330,28313],{"class":5424},[5280,28332,5218],{"class":5452},[5280,28334,28335],{"class":5424},"createErr\n",[5280,28337,28338],{"class":5282,"line":6023},[5280,28339,6553],{"class":5452},[5280,28341,28342,28345,28347,28349,28351,28353,28355,28357,28359],{"class":5282,"line":6042},[5280,28343,28344],{"class":5424},"    m",[5280,28346,5218],{"class":5452},[5280,28348,6182],{"class":5424},[5280,28350,5841],{"class":5452},[5280,28352,6292],{"class":5424},[5280,28354,5218],{"class":5452},[5280,28356,18635],{"class":5424},[5280,28358,6440],{"class":5452},[5280,28360,20794],{"class":5424},[5280,28362,28363,28365],{"class":5282,"line":6093},[5280,28364,7328],{"class":5436},[5280,28366,21545],{"class":5619},[5280,28368,28369],{"class":5282,"line":6098},[5280,28370,5976],{"class":5452},[5280,28372,28373],{"class":5282,"line":6114},[5280,28374,5313],{"emptyLinePlaceholder":5312},[5280,28376,28377,28379,28381,28383,28385,28387,28389,28391,28393,28395,28397,28399,28401,28403,28405,28407,28409,28411,28413,28415,28417,28419],{"class":5282,"line":6130},[5280,28378,5862],{"class":5619},[5280,28380,5720],{"class":5452},[5280,28382,28270],{"class":5424},[5280,28384,8072],{"class":5452},[5280,28386,28211],{"class":5758},[5280,28388,7315],{"class":5452},[5280,28390,22183],{"class":5293},[5280,28392,5835],{"class":5452},[5280,28394,15441],{"class":5424},[5280,28396,21723],{"class":5758},[5280,28398,5218],{"class":5452},[5280,28400,15393],{"class":5758},[5280,28402,5887],{"class":5452},[5280,28404,20126],{"class":5424},[5280,28406,6239],{"class":5758},[5280,28408,20151],{"class":5452},[5280,28410,22144],{"class":5758},[5280,28412,5218],{"class":5452},[5280,28414,17513],{"class":5758},[5280,28416,5887],{"class":5452},[5280,28418,9567],{"class":5758},[5280,28420,6020],{"class":5452},[5280,28422,28423,28425,28427,28429,28432,28434,28436],{"class":5282,"line":6159},[5280,28424,7023],{"class":5436},[5280,28426,28313],{"class":5424},[5280,28428,5218],{"class":5452},[5280,28430,28431],{"class":5424},"findByEmailFn",[5280,28433,6301],{"class":5452},[5280,28435,5967],{"class":5619},[5280,28437,6111],{"class":5452},[5280,28439,28440,28442,28444,28446,28448,28450,28452,28454,28456],{"class":5282,"line":6187},[5280,28441,12200],{"class":5436},[5280,28443,28313],{"class":5424},[5280,28445,5218],{"class":5452},[5280,28447,28431],{"class":5293},[5280,28449,5835],{"class":5452},[5280,28451,15441],{"class":5424},[5280,28453,5887],{"class":5452},[5280,28455,20126],{"class":5424},[5280,28457,5598],{"class":5452},[5280,28459,28460],{"class":5282,"line":6193},[5280,28461,6553],{"class":5452},[5280,28463,28464],{"class":5282,"line":6207},[5280,28465,6724],{"class":5452},[5280,28467,28468,28470,28472,28474,28476,28478,28480,28482,28484,28486],{"class":5282,"line":6221},[5280,28469,11383],{"class":5436},[5280,28471,12286],{"class":5424},[5280,28473,5887],{"class":5452},[5280,28475,19346],{"class":5424},[5280,28477,6029],{"class":5452},[5280,28479,12296],{"class":5436},[5280,28481,28313],{"class":5424},[5280,28483,5218],{"class":5452},[5280,28485,6182],{"class":5424},[5280,28487,6111],{"class":5452},[5280,28489,28490,28492,28494,28496,28498,28500,28502],{"class":5282,"line":6233},[5280,28491,6259],{"class":5436},[5280,28493,19762],{"class":5424},[5280,28495,5218],{"class":5452},[5280,28497,6437],{"class":5424},[5280,28499,7029],{"class":5452},[5280,28501,20126],{"class":5424},[5280,28503,6111],{"class":5452},[5280,28505,28506,28508,28510,28512],{"class":5282,"line":6245},[5280,28507,20309],{"class":5436},[5280,28509,19762],{"class":5424},[5280,28511,5887],{"class":5452},[5280,28513,20459],{"class":5619},[5280,28515,28516],{"class":5282,"line":6251},[5280,28517,6248],{"class":5452},[5280,28519,28520],{"class":5282,"line":6256},[5280,28521,6553],{"class":5452},[5280,28523,28524,28526,28528,28530,28532,28534],{"class":5282,"line":6308},[5280,28525,7328],{"class":5436},[5280,28527,20192],{"class":5619},[5280,28529,5887],{"class":5452},[5280,28531,22144],{"class":5424},[5280,28533,5218],{"class":5452},[5280,28535,23260],{"class":5424},[5280,28537,28538],{"class":5282,"line":6339},[5280,28539,5976],{"class":5452},[5280,28541,28542],{"class":5282,"line":6371},[5280,28543,5313],{"emptyLinePlaceholder":5312},[5280,28545,28546,28548,28550,28552,28554,28556,28558,28560,28562,28564,28566,28568,28570,28572,28574,28576,28578,28580,28582,28584,28586,28588],{"class":5282,"line":6409},[5280,28547,5862],{"class":5619},[5280,28549,5720],{"class":5452},[5280,28551,28270],{"class":5424},[5280,28553,8072],{"class":5452},[5280,28555,28211],{"class":5758},[5280,28557,7315],{"class":5452},[5280,28559,23147],{"class":5293},[5280,28561,5835],{"class":5452},[5280,28563,15441],{"class":5424},[5280,28565,21723],{"class":5758},[5280,28567,5218],{"class":5452},[5280,28569,15393],{"class":5758},[5280,28571,5887],{"class":5452},[5280,28573,21760],{"class":5424},[5280,28575,6239],{"class":5758},[5280,28577,20151],{"class":5452},[5280,28579,22144],{"class":5758},[5280,28581,5218],{"class":5452},[5280,28583,17513],{"class":5758},[5280,28585,5887],{"class":5452},[5280,28587,9567],{"class":5758},[5280,28589,6020],{"class":5452},[5280,28591,28592,28594,28596,28598,28600,28603,28605,28607,28609,28611],{"class":5282,"line":6415},[5280,28593,18871],{"class":5424},[5280,28595,5887],{"class":5452},[5280,28597,13972],{"class":5424},[5280,28599,6029],{"class":5452},[5280,28601,28602],{"class":5424},"m",[5280,28604,5218],{"class":5452},[5280,28606,6182],{"class":5424},[5280,28608,5841],{"class":5452},[5280,28610,21760],{"class":5424},[5280,28612,13989],{"class":5452},[5280,28614,28615,28617,28619,28621],{"class":5282,"line":6420},[5280,28616,7023],{"class":5436},[5280,28618,9455],{"class":5452},[5280,28620,13972],{"class":5424},[5280,28622,6111],{"class":5452},[5280,28624,28625,28627,28629,28631,28633,28635],{"class":5282,"line":6425},[5280,28626,12200],{"class":5436},[5280,28628,20192],{"class":5619},[5280,28630,5887],{"class":5452},[5280,28632,22144],{"class":5424},[5280,28634,5218],{"class":5452},[5280,28636,23260],{"class":5424},[5280,28638,28639],{"class":5282,"line":6450},[5280,28640,6553],{"class":5452},[5280,28642,28643,28645,28647,28649],{"class":5282,"line":6471},[5280,28644,7328],{"class":5436},[5280,28646,6213],{"class":5424},[5280,28648,5887],{"class":5452},[5280,28650,20459],{"class":5619},[5280,28652,28653],{"class":5282,"line":6507},[5280,28654,5976],{"class":5452},[5280,28656,28657],{"class":5282,"line":6512},[5280,28658,5313],{"emptyLinePlaceholder":5312},[5280,28660,28661,28663,28665,28667,28669,28671,28673,28675,28677,28679,28681,28683,28685,28687,28689,28691,28693,28695,28697,28699,28701],{"class":5282,"line":6520},[5280,28662,5862],{"class":5619},[5280,28664,5720],{"class":5452},[5280,28666,28270],{"class":5424},[5280,28668,8072],{"class":5452},[5280,28670,28211],{"class":5758},[5280,28672,7315],{"class":5452},[5280,28674,23457],{"class":5293},[5280,28676,5835],{"class":5452},[5280,28678,15441],{"class":5424},[5280,28680,21723],{"class":5758},[5280,28682,5218],{"class":5452},[5280,28684,15393],{"class":5758},[5280,28686,5887],{"class":5452},[5280,28688,6292],{"class":5424},[5280,28690,6010],{"class":5452},[5280,28692,22144],{"class":5758},[5280,28694,5218],{"class":5452},[5280,28696,17513],{"class":5758},[5280,28698,7315],{"class":5452},[5280,28700,9567],{"class":5758},[5280,28702,6111],{"class":5452},[5280,28704,28705,28707,28709,28711,28713,28715,28717,28719,28721],{"class":5282,"line":6550},[5280,28706,28344],{"class":5424},[5280,28708,5218],{"class":5452},[5280,28710,6182],{"class":5424},[5280,28712,5841],{"class":5452},[5280,28714,6292],{"class":5424},[5280,28716,5218],{"class":5452},[5280,28718,18635],{"class":5424},[5280,28720,6440],{"class":5452},[5280,28722,20794],{"class":5424},[5280,28724,28725,28727],{"class":5282,"line":6556},[5280,28726,7328],{"class":5436},[5280,28728,21545],{"class":5619},[5280,28730,28731],{"class":5282,"line":6561},[5280,28732,5976],{"class":5452},[5280,28734,28735],{"class":5282,"line":6621},[5280,28736,5313],{"emptyLinePlaceholder":5312},[5280,28738,28739,28741,28743,28745,28747,28749,28751,28753,28755,28757,28759,28761,28763,28765,28767,28769,28771,28773],{"class":5282,"line":6626},[5280,28740,5862],{"class":5619},[5280,28742,5720],{"class":5452},[5280,28744,28270],{"class":5424},[5280,28746,8072],{"class":5452},[5280,28748,28211],{"class":5758},[5280,28750,7315],{"class":5452},[5280,28752,23621],{"class":5293},[5280,28754,5835],{"class":5452},[5280,28756,15441],{"class":5424},[5280,28758,21723],{"class":5758},[5280,28760,5218],{"class":5452},[5280,28762,15393],{"class":5758},[5280,28764,5887],{"class":5452},[5280,28766,21760],{"class":5424},[5280,28768,6239],{"class":5758},[5280,28770,7315],{"class":5452},[5280,28772,9567],{"class":5758},[5280,28774,6111],{"class":5452},[5280,28776,28777,28779,28781,28783,28785,28787,28789,28791],{"class":5282,"line":6631},[5280,28778,23682],{"class":5293},[5280,28780,5835],{"class":5452},[5280,28782,28602],{"class":5424},[5280,28784,5218],{"class":5452},[5280,28786,6182],{"class":5424},[5280,28788,5887],{"class":5452},[5280,28790,21760],{"class":5424},[5280,28792,5598],{"class":5452},[5280,28794,28795,28797],{"class":5282,"line":6663},[5280,28796,7328],{"class":5436},[5280,28798,21545],{"class":5619},[5280,28800,28801],{"class":5282,"line":6678},[5280,28802,5976],{"class":5452},[5262,28804,28805],{},"Тепер тестуємо UseCase:",[5271,28807,28809],{"className":5411,"code":28808,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fusecase\u002Fuser_usecase_test.go (продовження)\n\nfunc TestUserUseCase_RegisterUser_Success(t *testing.T) {\n    \u002F\u002F Arrange: створюємо мок-репозиторій\n    mockRepo := NewMockUserRepository()\n    uc := NewUserUseCase(mockRepo)\n    \n    \u002F\u002F Act: викликаємо метод UseCase\n    user, err := uc.RegisterUser(\n        context.Background(),\n        \"John Doe\",\n        \"john@example.com\",\n        \"password123\",\n    )\n    \n    \u002F\u002F Assert: перевіряємо результат\n    if err != nil {\n        t.Fatalf(\"Expected no error, got %v\", err)\n    }\n    \n    if user == nil {\n        t.Fatal(\"Expected user, got nil\")\n    }\n    \n    if user.Name != \"John Doe\" {\n        t.Errorf(\"Expected name 'John Doe', got '%s'\", user.Name)\n    }\n    \n    if user.Email != \"john@example.com\" {\n        t.Errorf(\"Expected email 'john@example.com', got '%s'\", user.Email)\n    }\n    \n    \u002F\u002F Перевіряємо, що користувач збережений у репозиторії\n    savedUser, err := mockRepo.FindByEmail(context.Background(), \"john@example.com\")\n    if err != nil {\n        t.Fatalf(\"User not saved in repository: %v\", err)\n    }\n    \n    if savedUser.ID != user.ID {\n        t.Errorf(\"Saved user ID mismatch\")\n    }\n}\n\nfunc TestUserUseCase_RegisterUser_DuplicateEmail(t *testing.T) {\n    \u002F\u002F Arrange: створюємо репозиторій з існуючим користувачем\n    mockRepo := NewMockUserRepository()\n    mockRepo.findByEmailFn = func(ctx context.Context, email string) (*domain.User, error) {\n        \u002F\u002F Імітуємо, що користувач вже існує\n        return &domain.User{\n            ID:    \"existing-user\",\n            Email: email,\n        }, nil\n    }\n    \n    uc := NewUserUseCase(mockRepo)\n    \n    \u002F\u002F Act\n    user, err := uc.RegisterUser(\n        context.Background(),\n        \"Jane Doe\",\n        \"jane@example.com\",\n        \"password123\",\n    )\n    \n    \u002F\u002F Assert: очікуємо помилку\n    if err != domain.ErrUserAlreadyExists {\n        t.Errorf(\"Expected ErrUserAlreadyExists, got %v\", err)\n    }\n    \n    if user != nil {\n        t.Errorf(\"Expected nil user, got %v\", user)\n    }\n}\n\nfunc TestUserUseCase_Login_Success(t *testing.T) {\n    \u002F\u002F Arrange: створюємо користувача з хешованим паролем\n    mockRepo := NewMockUserRepository()\n    \n    \u002F\u002F Хешуємо пароль (bcrypt)\n    hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(\"password123\"), bcrypt.DefaultCost)\n    \n    existingUser := &domain.User{\n        ID:       \"user-1\",\n        Name:     \"John\",\n        Email:    \"john@example.com\",\n        Password: string(hashedPassword),\n    }\n    mockRepo.users[existingUser.ID] = existingUser\n    \n    uc := NewUserUseCase(mockRepo)\n    \n    \u002F\u002F Act\n    user, err := uc.Login(context.Background(), \"john@example.com\", \"password123\")\n    \n    \u002F\u002F Assert\n    if err != nil {\n        t.Fatalf(\"Expected successful login, got error: %v\", err)\n    }\n    \n    if user.ID != \"user-1\" {\n        t.Errorf(\"Expected user ID 'user-1', got '%s'\", user.ID)\n    }\n}\n\nfunc TestUserUseCase_Login_InvalidPassword(t *testing.T) {\n    \u002F\u002F Arrange\n    mockRepo := NewMockUserRepository()\n    hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(\"correct-password\"), bcrypt.DefaultCost)\n    \n    mockRepo.users[\"user-1\"] = &domain.User{\n        ID:       \"user-1\",\n        Email:    \"john@example.com\",\n        Password: string(hashedPassword),\n    }\n    \n    uc := NewUserUseCase(mockRepo)\n    \n    \u002F\u002F Act: неправильний пароль\n    user, err := uc.Login(context.Background(), \"john@example.com\", \"wrong-password\")\n    \n    \u002F\u002F Assert\n    if err != domain.ErrInvalidCredentials {\n        t.Errorf(\"Expected ErrInvalidCredentials, got %v\", err)\n    }\n    \n    if user != nil {\n        t.Errorf(\"Expected nil user, got %v\", user)\n    }\n}\n",[5215,28810,28811,28816,28820,28841,28846,28858,28874,28878,28883,28901,28913,28920,28927,28934,28938,28942,28947,28959,28982,28986,28990,29002,29017,29021,29025,29041,29069,29073,29077,29093,29120,29124,29128,29133,29164,29176,29199,29203,29207,29228,29243,29247,29251,29255,29276,29281,29291,29333,29338,29352,29363,29373,29380,29384,29388,29402,29406,29411,29429,29439,29446,29453,29459,29463,29467,29472,29488,29511,29515,29519,29531,29554,29558,29562,29566,29587,29592,29602,29606,29611,29645,29649,29664,29674,29684,29694,29708,29712,29734,29738,29752,29756,29760,29794,29798,29803,29815,29838,29842,29846,29862,29889,29893,29897,29901,29922,29927,29937,29972,29976,29999,30009,30019,30033,30037,30041,30055,30059,30064,30099,30103,30107,30123,30146,30150,30154,30166,30188,30192],{"__ignoreMap":5276},[5280,28812,28813],{"class":5282,"line":5283},[5280,28814,28815],{"class":5286},"\u002F\u002F internal\u002Fusecase\u002Fuser_usecase_test.go (продовження)\n",[5280,28817,28818],{"class":5282,"line":5290},[5280,28819,5313],{"emptyLinePlaceholder":5312},[5280,28821,28822,28824,28827,28829,28831,28833,28835,28837,28839],{"class":5282,"line":5301},[5280,28823,5862],{"class":5619},[5280,28825,28826],{"class":5293}," TestUserUseCase_RegisterUser_Success",[5280,28828,5835],{"class":5452},[5280,28830,27327],{"class":5424},[5280,28832,6010],{"class":5452},[5280,28834,27332],{"class":5758},[5280,28836,5218],{"class":5452},[5280,28838,27337],{"class":5758},[5280,28840,6020],{"class":5452},[5280,28842,28843],{"class":5282,"line":5309},[5280,28844,28845],{"class":5286},"    \u002F\u002F Arrange: створюємо мок-репозиторій\n",[5280,28847,28848,28851,28853,28856],{"class":5282,"line":5316},[5280,28849,28850],{"class":5424},"    mockRepo",[5280,28852,6029],{"class":5452},[5280,28854,28855],{"class":5293},"NewMockUserRepository",[5280,28857,6039],{"class":5452},[5280,28859,28860,28863,28865,28867,28869,28872],{"class":5282,"line":5322},[5280,28861,28862],{"class":5424},"    uc",[5280,28864,6029],{"class":5452},[5280,28866,26738],{"class":5293},[5280,28868,5835],{"class":5452},[5280,28870,28871],{"class":5424},"mockRepo",[5280,28873,5598],{"class":5452},[5280,28875,28876],{"class":5282,"line":5336},[5280,28877,6724],{"class":5452},[5280,28879,28880],{"class":5282,"line":5341},[5280,28881,28882],{"class":5286},"    \u002F\u002F Act: викликаємо метод UseCase\n",[5280,28884,28885,28887,28889,28891,28893,28895,28897,28899],{"class":5282,"line":5347},[5280,28886,18871],{"class":5424},[5280,28888,5887],{"class":5452},[5280,28890,6298],{"class":5424},[5280,28892,6029],{"class":5452},[5280,28894,22174],{"class":5424},[5280,28896,5218],{"class":5452},[5280,28898,20688],{"class":5293},[5280,28900,9105],{"class":5452},[5280,28902,28903,28906,28908,28911],{"class":5282,"line":5358},[5280,28904,28905],{"class":5424},"        context",[5280,28907,5218],{"class":5452},[5280,28909,28910],{"class":5293},"Background",[5280,28912,17825],{"class":5452},[5280,28914,28915,28918],{"class":5282,"line":5368},[5280,28916,28917],{"class":5297},"        \"John Doe\"",[5280,28919,6796],{"class":5452},[5280,28921,28922,28925],{"class":5282,"line":5378},[5280,28923,28924],{"class":5297},"        \"john@example.com\"",[5280,28926,6796],{"class":5452},[5280,28928,28929,28932],{"class":5282,"line":5391},[5280,28930,28931],{"class":5297},"        \"password123\"",[5280,28933,6796],{"class":5452},[5280,28935,28936],{"class":5282,"line":5705},[5280,28937,13112],{"class":5452},[5280,28939,28940],{"class":5282,"line":5820},[5280,28941,6724],{"class":5452},[5280,28943,28944],{"class":5282,"line":5854},[5280,28945,28946],{"class":5286},"    \u002F\u002F Assert: перевіряємо результат\n",[5280,28948,28949,28951,28953,28955,28957],{"class":5282,"line":5859},[5280,28950,7023],{"class":5436},[5280,28952,6262],{"class":5424},[5280,28954,6301],{"class":5452},[5280,28956,5967],{"class":5619},[5280,28958,6111],{"class":5452},[5280,28960,28961,28963,28965,28968,28970,28972,28974,28976,28978,28980],{"class":5282,"line":5871},[5280,28962,27755],{"class":5424},[5280,28964,5218],{"class":5452},[5280,28966,28967],{"class":5293},"Fatalf",[5280,28969,5835],{"class":5452},[5280,28971,27956],{"class":5297},[5280,28973,6362],{"class":5424},[5280,28975,6584],{"class":5297},[5280,28977,5887],{"class":5452},[5280,28979,6298],{"class":5424},[5280,28981,5598],{"class":5452},[5280,28983,28984],{"class":5282,"line":5895},[5280,28985,6553],{"class":5452},[5280,28987,28988],{"class":5282,"line":5916},[5280,28989,6724],{"class":5452},[5280,28991,28992,28994,28996,28998,29000],{"class":5282,"line":5921},[5280,28993,7023],{"class":5436},[5280,28995,6213],{"class":5424},[5280,28997,7029],{"class":5452},[5280,28999,5967],{"class":5619},[5280,29001,6111],{"class":5452},[5280,29003,29004,29006,29008,29010,29012,29015],{"class":5282,"line":5939},[5280,29005,27755],{"class":5424},[5280,29007,5218],{"class":5452},[5280,29009,5947],{"class":5293},[5280,29011,5835],{"class":5452},[5280,29013,29014],{"class":5297},"\"Expected user, got nil\"",[5280,29016,5598],{"class":5452},[5280,29018,29019],{"class":5282,"line":5973},[5280,29020,6553],{"class":5452},[5280,29022,29023],{"class":5282,"line":5979},[5280,29024,6724],{"class":5452},[5280,29026,29027,29029,29031,29033,29035,29037,29039],{"class":5282,"line":5984},[5280,29028,7023],{"class":5436},[5280,29030,6213],{"class":5424},[5280,29032,5218],{"class":5452},[5280,29034,6502],{"class":5424},[5280,29036,6301],{"class":5452},[5280,29038,17546],{"class":5297},[5280,29040,6111],{"class":5452},[5280,29042,29043,29045,29047,29049,29051,29054,29056,29059,29061,29063,29065,29067],{"class":5282,"line":6023},[5280,29044,27755],{"class":5424},[5280,29046,5218],{"class":5452},[5280,29048,15523],{"class":5293},[5280,29050,5835],{"class":5452},[5280,29052,29053],{"class":5297},"\"Expected name 'John Doe', got '",[5280,29055,6057],{"class":5424},[5280,29057,29058],{"class":5297},"'\"",[5280,29060,5887],{"class":5452},[5280,29062,6292],{"class":5424},[5280,29064,5218],{"class":5452},[5280,29066,6502],{"class":5424},[5280,29068,5598],{"class":5452},[5280,29070,29071],{"class":5282,"line":6042},[5280,29072,6553],{"class":5452},[5280,29074,29075],{"class":5282,"line":6093},[5280,29076,6724],{"class":5452},[5280,29078,29079,29081,29083,29085,29087,29089,29091],{"class":5282,"line":6098},[5280,29080,7023],{"class":5436},[5280,29082,6213],{"class":5424},[5280,29084,5218],{"class":5452},[5280,29086,6437],{"class":5424},[5280,29088,6301],{"class":5452},[5280,29090,17558],{"class":5297},[5280,29092,6111],{"class":5452},[5280,29094,29095,29097,29099,29101,29103,29106,29108,29110,29112,29114,29116,29118],{"class":5282,"line":6114},[5280,29096,27755],{"class":5424},[5280,29098,5218],{"class":5452},[5280,29100,15523],{"class":5293},[5280,29102,5835],{"class":5452},[5280,29104,29105],{"class":5297},"\"Expected email 'john@example.com', got '",[5280,29107,6057],{"class":5424},[5280,29109,29058],{"class":5297},[5280,29111,5887],{"class":5452},[5280,29113,6292],{"class":5424},[5280,29115,5218],{"class":5452},[5280,29117,6437],{"class":5424},[5280,29119,5598],{"class":5452},[5280,29121,29122],{"class":5282,"line":6130},[5280,29123,6553],{"class":5452},[5280,29125,29126],{"class":5282,"line":6159},[5280,29127,6724],{"class":5452},[5280,29129,29130],{"class":5282,"line":6187},[5280,29131,29132],{"class":5286},"    \u002F\u002F Перевіряємо, що користувач збережений у репозиторії\n",[5280,29134,29135,29138,29140,29142,29144,29146,29148,29150,29152,29154,29156,29158,29160,29162],{"class":5282,"line":6193},[5280,29136,29137],{"class":5424},"    savedUser",[5280,29139,5887],{"class":5452},[5280,29141,6298],{"class":5424},[5280,29143,6029],{"class":5452},[5280,29145,28871],{"class":5424},[5280,29147,5218],{"class":5452},[5280,29149,22183],{"class":5293},[5280,29151,5835],{"class":5452},[5280,29153,15379],{"class":5424},[5280,29155,5218],{"class":5452},[5280,29157,28910],{"class":5293},[5280,29159,15396],{"class":5452},[5280,29161,17558],{"class":5297},[5280,29163,5598],{"class":5452},[5280,29165,29166,29168,29170,29172,29174],{"class":5282,"line":6207},[5280,29167,7023],{"class":5436},[5280,29169,6262],{"class":5424},[5280,29171,6301],{"class":5452},[5280,29173,5967],{"class":5619},[5280,29175,6111],{"class":5452},[5280,29177,29178,29180,29182,29184,29186,29189,29191,29193,29195,29197],{"class":5282,"line":6221},[5280,29179,27755],{"class":5424},[5280,29181,5218],{"class":5452},[5280,29183,28967],{"class":5293},[5280,29185,5835],{"class":5452},[5280,29187,29188],{"class":5297},"\"User not saved in repository: ",[5280,29190,6362],{"class":5424},[5280,29192,6584],{"class":5297},[5280,29194,5887],{"class":5452},[5280,29196,6298],{"class":5424},[5280,29198,5598],{"class":5452},[5280,29200,29201],{"class":5282,"line":6233},[5280,29202,6553],{"class":5452},[5280,29204,29205],{"class":5282,"line":6245},[5280,29206,6724],{"class":5452},[5280,29208,29209,29211,29214,29216,29218,29220,29222,29224,29226],{"class":5282,"line":6251},[5280,29210,7023],{"class":5436},[5280,29212,29213],{"class":5424}," savedUser",[5280,29215,5218],{"class":5452},[5280,29217,18635],{"class":5424},[5280,29219,6301],{"class":5452},[5280,29221,6292],{"class":5424},[5280,29223,5218],{"class":5452},[5280,29225,18635],{"class":5424},[5280,29227,6111],{"class":5452},[5280,29229,29230,29232,29234,29236,29238,29241],{"class":5282,"line":6256},[5280,29231,27755],{"class":5424},[5280,29233,5218],{"class":5452},[5280,29235,15523],{"class":5293},[5280,29237,5835],{"class":5452},[5280,29239,29240],{"class":5297},"\"Saved user ID mismatch\"",[5280,29242,5598],{"class":5452},[5280,29244,29245],{"class":5282,"line":6308},[5280,29246,6553],{"class":5452},[5280,29248,29249],{"class":5282,"line":6339},[5280,29250,5976],{"class":5452},[5280,29252,29253],{"class":5282,"line":6371},[5280,29254,5313],{"emptyLinePlaceholder":5312},[5280,29256,29257,29259,29262,29264,29266,29268,29270,29272,29274],{"class":5282,"line":6409},[5280,29258,5862],{"class":5619},[5280,29260,29261],{"class":5293}," TestUserUseCase_RegisterUser_DuplicateEmail",[5280,29263,5835],{"class":5452},[5280,29265,27327],{"class":5424},[5280,29267,6010],{"class":5452},[5280,29269,27332],{"class":5758},[5280,29271,5218],{"class":5452},[5280,29273,27337],{"class":5758},[5280,29275,6020],{"class":5452},[5280,29277,29278],{"class":5282,"line":6415},[5280,29279,29280],{"class":5286},"    \u002F\u002F Arrange: створюємо репозиторій з існуючим користувачем\n",[5280,29282,29283,29285,29287,29289],{"class":5282,"line":6420},[5280,29284,28850],{"class":5424},[5280,29286,6029],{"class":5452},[5280,29288,28855],{"class":5293},[5280,29290,6039],{"class":5452},[5280,29292,29293,29295,29297,29299,29301,29303,29305,29307,29309,29311,29313,29315,29317,29319,29321,29323,29325,29327,29329,29331],{"class":5282,"line":6425},[5280,29294,28850],{"class":5424},[5280,29296,5218],{"class":5452},[5280,29298,28431],{"class":5424},[5280,29300,5829],{"class":5452},[5280,29302,5862],{"class":5619},[5280,29304,5835],{"class":5452},[5280,29306,15441],{"class":5424},[5280,29308,21723],{"class":5758},[5280,29310,5218],{"class":5452},[5280,29312,15393],{"class":5758},[5280,29314,5887],{"class":5452},[5280,29316,20126],{"class":5424},[5280,29318,6239],{"class":5758},[5280,29320,20151],{"class":5452},[5280,29322,22144],{"class":5758},[5280,29324,5218],{"class":5452},[5280,29326,17513],{"class":5758},[5280,29328,5887],{"class":5452},[5280,29330,9567],{"class":5758},[5280,29332,6020],{"class":5452},[5280,29334,29335],{"class":5282,"line":6450},[5280,29336,29337],{"class":5286},"        \u002F\u002F Імітуємо, що користувач вже існує\n",[5280,29339,29340,29342,29344,29346,29348,29350],{"class":5282,"line":6471},[5280,29341,12200],{"class":5436},[5280,29343,13807],{"class":5452},[5280,29345,22144],{"class":5758},[5280,29347,5218],{"class":5452},[5280,29349,17513],{"class":5758},[5280,29351,6781],{"class":5452},[5280,29353,29354,29356,29358,29361],{"class":5282,"line":6507},[5280,29355,26106],{"class":5424},[5280,29357,13867],{"class":5452},[5280,29359,29360],{"class":5297},"\"existing-user\"",[5280,29362,6796],{"class":5452},[5280,29364,29365,29367,29369,29371],{"class":5282,"line":6512},[5280,29366,6236],{"class":5424},[5280,29368,6790],{"class":5452},[5280,29370,20126],{"class":5424},[5280,29372,6796],{"class":5452},[5280,29374,29375,29378],{"class":5282,"line":6520},[5280,29376,29377],{"class":5452},"        }, ",[5280,29379,20459],{"class":5619},[5280,29381,29382],{"class":5282,"line":6550},[5280,29383,6553],{"class":5452},[5280,29385,29386],{"class":5282,"line":6556},[5280,29387,6724],{"class":5452},[5280,29389,29390,29392,29394,29396,29398,29400],{"class":5282,"line":6561},[5280,29391,28862],{"class":5424},[5280,29393,6029],{"class":5452},[5280,29395,26738],{"class":5293},[5280,29397,5835],{"class":5452},[5280,29399,28871],{"class":5424},[5280,29401,5598],{"class":5452},[5280,29403,29404],{"class":5282,"line":6621},[5280,29405,6724],{"class":5452},[5280,29407,29408],{"class":5282,"line":6626},[5280,29409,29410],{"class":5286},"    \u002F\u002F Act\n",[5280,29412,29413,29415,29417,29419,29421,29423,29425,29427],{"class":5282,"line":6631},[5280,29414,18871],{"class":5424},[5280,29416,5887],{"class":5452},[5280,29418,6298],{"class":5424},[5280,29420,6029],{"class":5452},[5280,29422,22174],{"class":5424},[5280,29424,5218],{"class":5452},[5280,29426,20688],{"class":5293},[5280,29428,9105],{"class":5452},[5280,29430,29431,29433,29435,29437],{"class":5282,"line":6663},[5280,29432,28905],{"class":5424},[5280,29434,5218],{"class":5452},[5280,29436,28910],{"class":5293},[5280,29438,17825],{"class":5452},[5280,29440,29441,29444],{"class":5282,"line":6678},[5280,29442,29443],{"class":5297},"        \"Jane Doe\"",[5280,29445,6796],{"class":5452},[5280,29447,29448,29451],{"class":5282,"line":6721},[5280,29449,29450],{"class":5297},"        \"jane@example.com\"",[5280,29452,6796],{"class":5452},[5280,29454,29455,29457],{"class":5282,"line":6727},[5280,29456,28931],{"class":5297},[5280,29458,6796],{"class":5452},[5280,29460,29461],{"class":5282,"line":6751},[5280,29462,13112],{"class":5452},[5280,29464,29465],{"class":5282,"line":6784},[5280,29466,6724],{"class":5452},[5280,29468,29469],{"class":5282,"line":6799},[5280,29470,29471],{"class":5286},"    \u002F\u002F Assert: очікуємо помилку\n",[5280,29473,29474,29476,29478,29480,29482,29484,29486],{"class":5282,"line":6831},[5280,29475,7023],{"class":5436},[5280,29477,6262],{"class":5424},[5280,29479,6301],{"class":5452},[5280,29481,22144],{"class":5424},[5280,29483,5218],{"class":5452},[5280,29485,25423],{"class":5424},[5280,29487,6111],{"class":5452},[5280,29489,29490,29492,29494,29496,29498,29501,29503,29505,29507,29509],{"class":5282,"line":6837},[5280,29491,27755],{"class":5424},[5280,29493,5218],{"class":5452},[5280,29495,15523],{"class":5293},[5280,29497,5835],{"class":5452},[5280,29499,29500],{"class":5297},"\"Expected ErrUserAlreadyExists, got ",[5280,29502,6362],{"class":5424},[5280,29504,6584],{"class":5297},[5280,29506,5887],{"class":5452},[5280,29508,6298],{"class":5424},[5280,29510,5598],{"class":5452},[5280,29512,29513],{"class":5282,"line":6842},[5280,29514,6553],{"class":5452},[5280,29516,29517],{"class":5282,"line":6901},[5280,29518,6724],{"class":5452},[5280,29520,29521,29523,29525,29527,29529],{"class":5282,"line":12702},[5280,29522,7023],{"class":5436},[5280,29524,6213],{"class":5424},[5280,29526,6301],{"class":5452},[5280,29528,5967],{"class":5619},[5280,29530,6111],{"class":5452},[5280,29532,29533,29535,29537,29539,29541,29544,29546,29548,29550,29552],{"class":5282,"line":12731},[5280,29534,27755],{"class":5424},[5280,29536,5218],{"class":5452},[5280,29538,15523],{"class":5293},[5280,29540,5835],{"class":5452},[5280,29542,29543],{"class":5297},"\"Expected nil user, got ",[5280,29545,6362],{"class":5424},[5280,29547,6584],{"class":5297},[5280,29549,5887],{"class":5452},[5280,29551,6292],{"class":5424},[5280,29553,5598],{"class":5452},[5280,29555,29556],{"class":5282,"line":12736},[5280,29557,6553],{"class":5452},[5280,29559,29560],{"class":5282,"line":12741},[5280,29561,5976],{"class":5452},[5280,29563,29564],{"class":5282,"line":12746},[5280,29565,5313],{"emptyLinePlaceholder":5312},[5280,29567,29568,29570,29573,29575,29577,29579,29581,29583,29585],{"class":5282,"line":12752},[5280,29569,5862],{"class":5619},[5280,29571,29572],{"class":5293}," TestUserUseCase_Login_Success",[5280,29574,5835],{"class":5452},[5280,29576,27327],{"class":5424},[5280,29578,6010],{"class":5452},[5280,29580,27332],{"class":5758},[5280,29582,5218],{"class":5452},[5280,29584,27337],{"class":5758},[5280,29586,6020],{"class":5452},[5280,29588,29589],{"class":5282,"line":12774},[5280,29590,29591],{"class":5286},"    \u002F\u002F Arrange: створюємо користувача з хешованим паролем\n",[5280,29593,29594,29596,29598,29600],{"class":5282,"line":12794},[5280,29595,28850],{"class":5424},[5280,29597,6029],{"class":5452},[5280,29599,28855],{"class":5293},[5280,29601,6039],{"class":5452},[5280,29603,29604],{"class":5282,"line":12800},[5280,29605,6724],{"class":5452},[5280,29607,29608],{"class":5282,"line":12805},[5280,29609,29610],{"class":5286},"    \u002F\u002F Хешуємо пароль (bcrypt)\n",[5280,29612,29613,29615,29617,29619,29621,29623,29625,29627,29629,29631,29633,29635,29637,29639,29641,29643],{"class":5282,"line":12810},[5280,29614,19830],{"class":5424},[5280,29616,5887],{"class":5452},[5280,29618,22169],{"class":5424},[5280,29620,6029],{"class":5452},[5280,29622,22247],{"class":5424},[5280,29624,5218],{"class":5452},[5280,29626,22252],{"class":5293},[5280,29628,22255],{"class":5452},[5280,29630,9556],{"class":5758},[5280,29632,5835],{"class":5452},[5280,29634,27447],{"class":5297},[5280,29636,22264],{"class":5452},[5280,29638,22247],{"class":5424},[5280,29640,5218],{"class":5452},[5280,29642,22271],{"class":5424},[5280,29644,5598],{"class":5452},[5280,29646,29647],{"class":5282,"line":12816},[5280,29648,6724],{"class":5452},[5280,29650,29651,29654,29656,29658,29660,29662],{"class":5282,"line":12836},[5280,29652,29653],{"class":5424},"    existingUser",[5280,29655,9745],{"class":5452},[5280,29657,22144],{"class":5758},[5280,29659,5218],{"class":5452},[5280,29661,17513],{"class":5758},[5280,29663,6781],{"class":5452},[5280,29665,29666,29668,29670,29672],{"class":5282,"line":12842},[5280,29667,17528],{"class":5424},[5280,29669,19871],{"class":5452},[5280,29671,17533],{"class":5297},[5280,29673,6796],{"class":5452},[5280,29675,29676,29678,29680,29682],{"class":5282,"line":12847},[5280,29677,17540],{"class":5424},[5280,29679,9768],{"class":5452},[5280,29681,27587],{"class":5297},[5280,29683,6796],{"class":5452},[5280,29685,29686,29688,29690,29692],{"class":5282,"line":18311},[5280,29687,17553],{"class":5424},[5280,29689,13867],{"class":5452},[5280,29691,17558],{"class":5297},[5280,29693,6796],{"class":5452},[5280,29695,29696,29698,29700,29702,29704,29706],{"class":5282,"line":18338},[5280,29697,18415],{"class":5424},[5280,29699,6790],{"class":5452},[5280,29701,5844],{"class":5758},[5280,29703,5835],{"class":5452},[5280,29705,19913],{"class":5424},[5280,29707,6828],{"class":5452},[5280,29709,29710],{"class":5282,"line":18343},[5280,29711,6553],{"class":5452},[5280,29713,29714,29716,29718,29720,29722,29725,29727,29729,29731],{"class":5282,"line":18348},[5280,29715,28850],{"class":5424},[5280,29717,5218],{"class":5452},[5280,29719,6182],{"class":5424},[5280,29721,5841],{"class":5452},[5280,29723,29724],{"class":5424},"existingUser",[5280,29726,5218],{"class":5452},[5280,29728,18635],{"class":5424},[5280,29730,6440],{"class":5452},[5280,29732,29733],{"class":5424},"existingUser\n",[5280,29735,29736],{"class":5282,"line":18380},[5280,29737,6724],{"class":5452},[5280,29739,29740,29742,29744,29746,29748,29750],{"class":5282,"line":18393},[5280,29741,28862],{"class":5424},[5280,29743,6029],{"class":5452},[5280,29745,26738],{"class":5293},[5280,29747,5835],{"class":5452},[5280,29749,28871],{"class":5424},[5280,29751,5598],{"class":5452},[5280,29753,29754],{"class":5282,"line":18402},[5280,29755,6724],{"class":5452},[5280,29757,29758],{"class":5282,"line":18412},[5280,29759,29410],{"class":5286},[5280,29761,29762,29764,29766,29768,29770,29772,29774,29776,29778,29780,29782,29784,29786,29788,29790,29792],{"class":5282,"line":18423},[5280,29763,18871],{"class":5424},[5280,29765,5887],{"class":5452},[5280,29767,6298],{"class":5424},[5280,29769,6029],{"class":5452},[5280,29771,22174],{"class":5424},[5280,29773,5218],{"class":5452},[5280,29775,22552],{"class":5293},[5280,29777,5835],{"class":5452},[5280,29779,15379],{"class":5424},[5280,29781,5218],{"class":5452},[5280,29783,28910],{"class":5293},[5280,29785,15396],{"class":5452},[5280,29787,17558],{"class":5297},[5280,29789,5887],{"class":5452},[5280,29791,27447],{"class":5297},[5280,29793,5598],{"class":5452},[5280,29795,29796],{"class":5282,"line":18428},[5280,29797,6724],{"class":5452},[5280,29799,29800],{"class":5282,"line":18433},[5280,29801,29802],{"class":5286},"    \u002F\u002F Assert\n",[5280,29804,29805,29807,29809,29811,29813],{"class":5282,"line":18475},[5280,29806,7023],{"class":5436},[5280,29808,6262],{"class":5424},[5280,29810,6301],{"class":5452},[5280,29812,5967],{"class":5619},[5280,29814,6111],{"class":5452},[5280,29816,29817,29819,29821,29823,29825,29828,29830,29832,29834,29836],{"class":5282,"line":18502},[5280,29818,27755],{"class":5424},[5280,29820,5218],{"class":5452},[5280,29822,28967],{"class":5293},[5280,29824,5835],{"class":5452},[5280,29826,29827],{"class":5297},"\"Expected successful login, got error: ",[5280,29829,6362],{"class":5424},[5280,29831,6584],{"class":5297},[5280,29833,5887],{"class":5452},[5280,29835,6298],{"class":5424},[5280,29837,5598],{"class":5452},[5280,29839,29840],{"class":5282,"line":18507},[5280,29841,6553],{"class":5452},[5280,29843,29844],{"class":5282,"line":18512},[5280,29845,6724],{"class":5452},[5280,29847,29848,29850,29852,29854,29856,29858,29860],{"class":5282,"line":18517},[5280,29849,7023],{"class":5436},[5280,29851,6213],{"class":5424},[5280,29853,5218],{"class":5452},[5280,29855,18635],{"class":5424},[5280,29857,6301],{"class":5452},[5280,29859,17533],{"class":5297},[5280,29861,6111],{"class":5452},[5280,29863,29864,29866,29868,29870,29872,29875,29877,29879,29881,29883,29885,29887],{"class":5282,"line":18523},[5280,29865,27755],{"class":5424},[5280,29867,5218],{"class":5452},[5280,29869,15523],{"class":5293},[5280,29871,5835],{"class":5452},[5280,29873,29874],{"class":5297},"\"Expected user ID 'user-1', got '",[5280,29876,6057],{"class":5424},[5280,29878,29058],{"class":5297},[5280,29880,5887],{"class":5452},[5280,29882,6292],{"class":5424},[5280,29884,5218],{"class":5452},[5280,29886,18635],{"class":5424},[5280,29888,5598],{"class":5452},[5280,29890,29891],{"class":5282,"line":18535},[5280,29892,6553],{"class":5452},[5280,29894,29895],{"class":5282,"line":18572},[5280,29896,5976],{"class":5452},[5280,29898,29899],{"class":5282,"line":18587},[5280,29900,5313],{"emptyLinePlaceholder":5312},[5280,29902,29903,29905,29908,29910,29912,29914,29916,29918,29920],{"class":5282,"line":18602},[5280,29904,5862],{"class":5619},[5280,29906,29907],{"class":5293}," TestUserUseCase_Login_InvalidPassword",[5280,29909,5835],{"class":5452},[5280,29911,27327],{"class":5424},[5280,29913,6010],{"class":5452},[5280,29915,27332],{"class":5758},[5280,29917,5218],{"class":5452},[5280,29919,27337],{"class":5758},[5280,29921,6020],{"class":5452},[5280,29923,29924],{"class":5282,"line":18617},[5280,29925,29926],{"class":5286},"    \u002F\u002F Arrange\n",[5280,29928,29929,29931,29933,29935],{"class":5282,"line":18622},[5280,29930,28850],{"class":5424},[5280,29932,6029],{"class":5452},[5280,29934,28855],{"class":5293},[5280,29936,6039],{"class":5452},[5280,29938,29939,29941,29943,29945,29947,29949,29951,29953,29955,29957,29959,29962,29964,29966,29968,29970],{"class":5282,"line":18643},[5280,29940,19830],{"class":5424},[5280,29942,5887],{"class":5452},[5280,29944,22169],{"class":5424},[5280,29946,6029],{"class":5452},[5280,29948,22247],{"class":5424},[5280,29950,5218],{"class":5452},[5280,29952,22252],{"class":5293},[5280,29954,22255],{"class":5452},[5280,29956,9556],{"class":5758},[5280,29958,5835],{"class":5452},[5280,29960,29961],{"class":5297},"\"correct-password\"",[5280,29963,22264],{"class":5452},[5280,29965,22247],{"class":5424},[5280,29967,5218],{"class":5452},[5280,29969,22271],{"class":5424},[5280,29971,5598],{"class":5452},[5280,29973,29974],{"class":5282,"line":18648},[5280,29975,6724],{"class":5452},[5280,29977,29978,29980,29982,29984,29986,29988,29991,29993,29995,29997],{"class":5282,"line":18667},[5280,29979,28850],{"class":5424},[5280,29981,5218],{"class":5452},[5280,29983,6182],{"class":5424},[5280,29985,5841],{"class":5452},[5280,29987,17533],{"class":5297},[5280,29989,29990],{"class":5452},"] = &",[5280,29992,22144],{"class":5758},[5280,29994,5218],{"class":5452},[5280,29996,17513],{"class":5758},[5280,29998,6781],{"class":5452},[5280,30000,30001,30003,30005,30007],{"class":5282,"line":18690},[5280,30002,17528],{"class":5424},[5280,30004,19871],{"class":5452},[5280,30006,17533],{"class":5297},[5280,30008,6796],{"class":5452},[5280,30010,30011,30013,30015,30017],{"class":5282,"line":18695},[5280,30012,17553],{"class":5424},[5280,30014,13867],{"class":5452},[5280,30016,17558],{"class":5297},[5280,30018,6796],{"class":5452},[5280,30020,30021,30023,30025,30027,30029,30031],{"class":5282,"line":18700},[5280,30022,18415],{"class":5424},[5280,30024,6790],{"class":5452},[5280,30026,5844],{"class":5758},[5280,30028,5835],{"class":5452},[5280,30030,19913],{"class":5424},[5280,30032,6828],{"class":5452},[5280,30034,30035],{"class":5282,"line":18732},[5280,30036,6553],{"class":5452},[5280,30038,30039],{"class":5282,"line":18738},[5280,30040,6724],{"class":5452},[5280,30042,30043,30045,30047,30049,30051,30053],{"class":5282,"line":18769},[5280,30044,28862],{"class":5424},[5280,30046,6029],{"class":5452},[5280,30048,26738],{"class":5293},[5280,30050,5835],{"class":5452},[5280,30052,28871],{"class":5424},[5280,30054,5598],{"class":5452},[5280,30056,30057],{"class":5282,"line":18785},[5280,30058,6724],{"class":5452},[5280,30060,30061],{"class":5282,"line":18790},[5280,30062,30063],{"class":5286},"    \u002F\u002F Act: неправильний пароль\n",[5280,30065,30066,30068,30070,30072,30074,30076,30078,30080,30082,30084,30086,30088,30090,30092,30094,30097],{"class":5282,"line":18795},[5280,30067,18871],{"class":5424},[5280,30069,5887],{"class":5452},[5280,30071,6298],{"class":5424},[5280,30073,6029],{"class":5452},[5280,30075,22174],{"class":5424},[5280,30077,5218],{"class":5452},[5280,30079,22552],{"class":5293},[5280,30081,5835],{"class":5452},[5280,30083,15379],{"class":5424},[5280,30085,5218],{"class":5452},[5280,30087,28910],{"class":5293},[5280,30089,15396],{"class":5452},[5280,30091,17558],{"class":5297},[5280,30093,5887],{"class":5452},[5280,30095,30096],{"class":5297},"\"wrong-password\"",[5280,30098,5598],{"class":5452},[5280,30100,30101],{"class":5282,"line":18800},[5280,30102,6724],{"class":5452},[5280,30104,30105],{"class":5282,"line":18832},[5280,30106,29802],{"class":5286},[5280,30108,30109,30111,30113,30115,30117,30119,30121],{"class":5282,"line":18863},[5280,30110,7023],{"class":5436},[5280,30112,6262],{"class":5424},[5280,30114,6301],{"class":5452},[5280,30116,22144],{"class":5424},[5280,30118,5218],{"class":5452},[5280,30120,25960],{"class":5424},[5280,30122,6111],{"class":5452},[5280,30124,30125,30127,30129,30131,30133,30136,30138,30140,30142,30144],{"class":5282,"line":18868},[5280,30126,27755],{"class":5424},[5280,30128,5218],{"class":5452},[5280,30130,15523],{"class":5293},[5280,30132,5835],{"class":5452},[5280,30134,30135],{"class":5297},"\"Expected ErrInvalidCredentials, got ",[5280,30137,6362],{"class":5424},[5280,30139,6584],{"class":5297},[5280,30141,5887],{"class":5452},[5280,30143,6298],{"class":5424},[5280,30145,5598],{"class":5452},[5280,30147,30148],{"class":5282,"line":18888},[5280,30149,6553],{"class":5452},[5280,30151,30152],{"class":5282,"line":18899},[5280,30153,6724],{"class":5452},[5280,30155,30156,30158,30160,30162,30164],{"class":5282,"line":18928},[5280,30157,7023],{"class":5436},[5280,30159,6213],{"class":5424},[5280,30161,6301],{"class":5452},[5280,30163,5967],{"class":5619},[5280,30165,6111],{"class":5452},[5280,30167,30168,30170,30172,30174,30176,30178,30180,30182,30184,30186],{"class":5282,"line":18933},[5280,30169,27755],{"class":5424},[5280,30171,5218],{"class":5452},[5280,30173,15523],{"class":5293},[5280,30175,5835],{"class":5452},[5280,30177,29543],{"class":5297},[5280,30179,6362],{"class":5424},[5280,30181,6584],{"class":5297},[5280,30183,5887],{"class":5452},[5280,30185,6292],{"class":5424},[5280,30187,5598],{"class":5452},[5280,30189,30190],{"class":5282,"line":18938},[5280,30191,6553],{"class":5452},[5280,30193,30194],{"class":5282,"line":18943},[5280,30195,5976],{"class":5452},[8866,30197,30199,30208,30212,30216,30220,30224,30228,30232,30236,30240,30246],{":cursor":8868,"title":30198},"go test .\u002Finternal\u002Fusecase -v",[8871,30200,30202,7648,30205],{"className":30201},[5282],[5280,30203,8878],{"className":30204},[8877],[5227,30206,30198],{"className":30207},[8882],[8871,30209,30211],{"className":30210},[5282],"=== RUN   TestUserUseCase_RegisterUser_Success",[8871,30213,30215],{"className":30214},[5282],"--- PASS: TestUserUseCase_RegisterUser_Success (0.12s)",[8871,30217,30219],{"className":30218},[5282],"=== RUN   TestUserUseCase_RegisterUser_DuplicateEmail",[8871,30221,30223],{"className":30222},[5282],"--- PASS: TestUserUseCase_RegisterUser_DuplicateEmail (0.00s)",[8871,30225,30227],{"className":30226},[5282],"=== RUN   TestUserUseCase_Login_Success",[8871,30229,30231],{"className":30230},[5282],"--- PASS: TestUserUseCase_Login_Success (0.11s)",[8871,30233,30235],{"className":30234},[5282],"=== RUN   TestUserUseCase_Login_InvalidPassword",[8871,30237,30239],{"className":30238},[5282],"--- PASS: TestUserUseCase_Login_InvalidPassword (0.10s)",[8871,30241,30243],{"className":30242},[5282],[5280,30244,28052],{"className":30245},[28051,8882],[8871,30247,30249],{"className":30248},[5282],"ok      myapp\u002Finternal\u002Fusecase  0.334s",[5266,30251,30253],{"id":30252},"крок-3-тестування-http-обробників","Крок 3: Тестування HTTP-обробників",[5262,30255,30256],{},"Тестуємо HTTP-шар з мок-UseCase:",[5271,30258,30260],{"className":5411,"code":30259,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fdelivery\u002Fhttp\u002Fuser_handler_test.go\npackage http\n\nimport (\n    \"bytes\"\n    \"context\"\n    \"encoding\u002Fjson\"\n    \"myapp\u002Finternal\u002Fdomain\"\n    \"net\u002Fhttp\"\n    \"net\u002Fhttp\u002Fhttptest\"\n    \"testing\"\n    \"time\"\n)\n\n\u002F\u002F MockUserUseCase — мок UseCase для тестування handlers\ntype MockUserUseCase struct {\n    registerFn func(ctx context.Context, name, email, password string) (*domain.User, error)\n    loginFn    func(ctx context.Context, email, password string) (*domain.User, error)\n}\n\nfunc (m *MockUserUseCase) RegisterUser(ctx context.Context, name, email, password string) (*domain.User, error) {\n    if m.registerFn != nil {\n        return m.registerFn(ctx, name, email, password)\n    }\n    return nil, nil\n}\n\nfunc (m *MockUserUseCase) Login(ctx context.Context, email, password string) (*domain.User, error) {\n    if m.loginFn != nil {\n        return m.loginFn(ctx, email, password)\n    }\n    return nil, nil\n}\n\nfunc TestUserHandler_Register_Success(t *testing.T) {\n    \u002F\u002F Arrange: створюємо мок UseCase\n    mockUC := &MockUserUseCase{\n        registerFn: func(ctx context.Context, name, email, password string) (*domain.User, error) {\n            return &domain.User{\n                ID:        \"user-123\",\n                Name:      name,\n                Email:     email,\n                CreatedAt: time.Now(),\n            }, nil\n        },\n    }\n    \n    handler := NewUserHandler(mockUC)\n    \n    \u002F\u002F Створюємо HTTP-запит\n    reqBody := RegisterRequest{\n        Name:     \"John Doe\",\n        Email:    \"john@example.com\",\n        Password: \"password123\",\n    }\n    body, _ := json.Marshal(reqBody)\n    \n    req := httptest.NewRequest(http.MethodPost, \"\u002Fapi\u002Fusers\u002Fregister\", bytes.NewReader(body))\n    req.Header.Set(\"Content-Type\", \"application\u002Fjson\")\n    \n    \u002F\u002F Створюємо ResponseRecorder\n    rr := httptest.NewRecorder()\n    \n    \u002F\u002F Act: викликаємо handler\n    handler.Register(rr, req)\n    \n    \u002F\u002F Assert: перевіряємо статус-код\n    if status := rr.Code; status != http.StatusCreated {\n        t.Errorf(\"Expected status %d, got %d\", http.StatusCreated, status)\n    }\n    \n    \u002F\u002F Перевіряємо Content-Type\n    expectedCT := \"application\u002Fjson\"\n    if ct := rr.Header().Get(\"Content-Type\"); ct != expectedCT {\n        t.Errorf(\"Expected Content-Type %s, got %s\", expectedCT, ct)\n    }\n    \n    \u002F\u002F Перевіряємо тіло відповіді\n    var response UserResponse\n    if err := json.NewDecoder(rr.Body).Decode(&response); err != nil {\n        t.Fatalf(\"Failed to decode response: %v\", err)\n    }\n    \n    if response.Email != \"john@example.com\" {\n        t.Errorf(\"Expected email 'john@example.com', got '%s'\", response.Email)\n    }\n    \n    if response.Name != \"John Doe\" {\n        t.Errorf(\"Expected name 'John Doe', got '%s'\", response.Name)\n    }\n}\n\nfunc TestUserHandler_Register_InvalidJSON(t *testing.T) {\n    mockUC := &MockUserUseCase{}\n    handler := NewUserHandler(mockUC)\n    \n    \u002F\u002F Невалідний JSON\n    req := httptest.NewRequest(http.MethodPost, \"\u002Fapi\u002Fusers\u002Fregister\", \n        bytes.NewReader([]byte(\"invalid json\")))\n    req.Header.Set(\"Content-Type\", \"application\u002Fjson\")\n    \n    rr := httptest.NewRecorder()\n    handler.Register(rr, req)\n    \n    if status := rr.Code; status != http.StatusBadRequest {\n        t.Errorf(\"Expected status %d, got %d\", http.StatusBadRequest, status)\n    }\n}\n\nfunc TestUserHandler_Register_DuplicateEmail(t *testing.T) {\n    mockUC := &MockUserUseCase{\n        registerFn: func(ctx context.Context, name, email, password string) (*domain.User, error) {\n            return nil, domain.ErrUserAlreadyExists\n        },\n    }\n    \n    handler := NewUserHandler(mockUC)\n    \n    reqBody := RegisterRequest{\n        Name:     \"John\",\n        Email:    \"existing@example.com\",\n        Password: \"password123\",\n    }\n    body, _ := json.Marshal(reqBody)\n    \n    req := httptest.NewRequest(http.MethodPost, \"\u002Fapi\u002Fusers\u002Fregister\", bytes.NewReader(body))\n    req.Header.Set(\"Content-Type\", \"application\u002Fjson\")\n    \n    rr := httptest.NewRecorder()\n    handler.Register(rr, req)\n    \n    if status := rr.Code; status != http.StatusConflict {\n        t.Errorf(\"Expected status %d (Conflict), got %d\", http.StatusConflict, status)\n    }\n}\n",[5215,30261,30262,30267,30273,30277,30283,30288,30292,30296,30300,30304,30309,30313,30317,30321,30325,30330,30341,30386,30428,30432,30436,30491,30508,30536,30540,30550,30554,30558,30608,30625,30649,30653,30663,30667,30671,30692,30697,30708,30755,30769,30781,30791,30801,30816,30823,30827,30831,30835,30850,30854,30859,30871,30881,30891,30901,30905,30930,30934,30977,30999,31003,31008,31024,31028,31033,31052,31056,31061,31092,31127,31131,31135,31140,31150,31185,31216,31220,31224,31229,31238,31278,31301,31305,31309,31325,31351,31355,31359,31375,31401,31405,31409,31413,31434,31444,31458,31462,31467,31493,31513,31535,31539,31553,31571,31575,31603,31637,31641,31645,31649,31670,31680,31726,31740,31744,31748,31752,31766,31770,31780,31790,31801,31811,31815,31837,31841,31879,31901,31905,31919,31937,31941,31969,32004,32008],{"__ignoreMap":5276},[5280,30263,30264],{"class":5282,"line":5283},[5280,30265,30266],{"class":5286},"\u002F\u002F internal\u002Fdelivery\u002Fhttp\u002Fuser_handler_test.go\n",[5280,30268,30269,30271],{"class":5282,"line":5290},[5280,30270,5755],{"class":5619},[5280,30272,24962],{"class":5758},[5280,30274,30275],{"class":5282,"line":5301},[5280,30276,5313],{"emptyLinePlaceholder":5312},[5280,30278,30279,30281],{"class":5282,"line":5309},[5280,30280,5768],{"class":5436},[5280,30282,5453],{"class":5452},[5280,30284,30285],{"class":5282,"line":5316},[5280,30286,30287],{"class":5297},"    \"bytes\"\n",[5280,30289,30290],{"class":5282,"line":5322},[5280,30291,14862],{"class":5297},[5280,30293,30294],{"class":5282,"line":5336},[5280,30295,5775],{"class":5297},[5280,30297,30298],{"class":5282,"line":5341},[5280,30299,21978],{"class":5297},[5280,30301,30302],{"class":5282,"line":5347},[5280,30303,5790],{"class":5297},[5280,30305,30306],{"class":5282,"line":5358},[5280,30307,30308],{"class":5297},"    \"net\u002Fhttp\u002Fhttptest\"\n",[5280,30310,30311],{"class":5282,"line":5368},[5280,30312,27307],{"class":5297},[5280,30314,30315],{"class":5282,"line":5378},[5280,30316,5795],{"class":5297},[5280,30318,30319],{"class":5282,"line":5391},[5280,30320,5598],{"class":5452},[5280,30322,30323],{"class":5282,"line":5705},[5280,30324,5313],{"emptyLinePlaceholder":5312},[5280,30326,30327],{"class":5282,"line":5820},[5280,30328,30329],{"class":5286},"\u002F\u002F MockUserUseCase — мок UseCase для тестування handlers\n",[5280,30331,30332,30334,30337,30339],{"class":5282,"line":5854},[5280,30333,7921],{"class":5619},[5280,30335,30336],{"class":5758}," MockUserUseCase",[5280,30338,6216],{"class":5619},[5280,30340,6111],{"class":5452},[5280,30342,30343,30346,30348,30350,30352,30354,30356,30358,30360,30362,30364,30366,30368,30370,30372,30374,30376,30378,30380,30382,30384],{"class":5282,"line":5859},[5280,30344,30345],{"class":5424},"    registerFn",[5280,30347,7331],{"class":5619},[5280,30349,5835],{"class":5452},[5280,30351,15441],{"class":5424},[5280,30353,21723],{"class":5758},[5280,30355,5218],{"class":5452},[5280,30357,15393],{"class":5758},[5280,30359,5887],{"class":5452},[5280,30361,20121],{"class":5424},[5280,30363,5887],{"class":5452},[5280,30365,20126],{"class":5424},[5280,30367,5887],{"class":5452},[5280,30369,20131],{"class":5424},[5280,30371,6239],{"class":5758},[5280,30373,20151],{"class":5452},[5280,30375,22144],{"class":5758},[5280,30377,5218],{"class":5452},[5280,30379,17513],{"class":5758},[5280,30381,5887],{"class":5452},[5280,30383,9567],{"class":5758},[5280,30385,5598],{"class":5452},[5280,30387,30388,30391,30394,30396,30398,30400,30402,30404,30406,30408,30410,30412,30414,30416,30418,30420,30422,30424,30426],{"class":5282,"line":5871},[5280,30389,30390],{"class":5424},"    loginFn",[5280,30392,30393],{"class":5619},"    func",[5280,30395,5835],{"class":5452},[5280,30397,15441],{"class":5424},[5280,30399,21723],{"class":5758},[5280,30401,5218],{"class":5452},[5280,30403,15393],{"class":5758},[5280,30405,5887],{"class":5452},[5280,30407,20126],{"class":5424},[5280,30409,5887],{"class":5452},[5280,30411,20131],{"class":5424},[5280,30413,6239],{"class":5758},[5280,30415,20151],{"class":5452},[5280,30417,22144],{"class":5758},[5280,30419,5218],{"class":5452},[5280,30421,17513],{"class":5758},[5280,30423,5887],{"class":5452},[5280,30425,9567],{"class":5758},[5280,30427,5598],{"class":5452},[5280,30429,30430],{"class":5282,"line":5895},[5280,30431,5976],{"class":5452},[5280,30433,30434],{"class":5282,"line":5916},[5280,30435,5313],{"emptyLinePlaceholder":5312},[5280,30437,30438,30440,30442,30444,30446,30449,30451,30453,30455,30457,30459,30461,30463,30465,30467,30469,30471,30473,30475,30477,30479,30481,30483,30485,30487,30489],{"class":5282,"line":5921},[5280,30439,5862],{"class":5619},[5280,30441,5720],{"class":5452},[5280,30443,28270],{"class":5424},[5280,30445,8072],{"class":5452},[5280,30447,30448],{"class":5758},"MockUserUseCase",[5280,30450,7315],{"class":5452},[5280,30452,20688],{"class":5293},[5280,30454,5835],{"class":5452},[5280,30456,15441],{"class":5424},[5280,30458,21723],{"class":5758},[5280,30460,5218],{"class":5452},[5280,30462,15393],{"class":5758},[5280,30464,5887],{"class":5452},[5280,30466,20121],{"class":5424},[5280,30468,5887],{"class":5452},[5280,30470,20126],{"class":5424},[5280,30472,5887],{"class":5452},[5280,30474,20131],{"class":5424},[5280,30476,6239],{"class":5758},[5280,30478,20151],{"class":5452},[5280,30480,22144],{"class":5758},[5280,30482,5218],{"class":5452},[5280,30484,17513],{"class":5758},[5280,30486,5887],{"class":5452},[5280,30488,9567],{"class":5758},[5280,30490,6020],{"class":5452},[5280,30492,30493,30495,30497,30499,30502,30504,30506],{"class":5282,"line":5939},[5280,30494,7023],{"class":5436},[5280,30496,28313],{"class":5424},[5280,30498,5218],{"class":5452},[5280,30500,30501],{"class":5424},"registerFn",[5280,30503,6301],{"class":5452},[5280,30505,5967],{"class":5619},[5280,30507,6111],{"class":5452},[5280,30509,30510,30512,30514,30516,30518,30520,30522,30524,30526,30528,30530,30532,30534],{"class":5282,"line":5973},[5280,30511,12200],{"class":5436},[5280,30513,28313],{"class":5424},[5280,30515,5218],{"class":5452},[5280,30517,30501],{"class":5293},[5280,30519,5835],{"class":5452},[5280,30521,15441],{"class":5424},[5280,30523,5887],{"class":5452},[5280,30525,20121],{"class":5424},[5280,30527,5887],{"class":5452},[5280,30529,20126],{"class":5424},[5280,30531,5887],{"class":5452},[5280,30533,20131],{"class":5424},[5280,30535,5598],{"class":5452},[5280,30537,30538],{"class":5282,"line":5979},[5280,30539,6553],{"class":5452},[5280,30541,30542,30544,30546,30548],{"class":5282,"line":5984},[5280,30543,7328],{"class":5436},[5280,30545,20192],{"class":5619},[5280,30547,5887],{"class":5452},[5280,30549,20459],{"class":5619},[5280,30551,30552],{"class":5282,"line":6023},[5280,30553,5976],{"class":5452},[5280,30555,30556],{"class":5282,"line":6042},[5280,30557,5313],{"emptyLinePlaceholder":5312},[5280,30559,30560,30562,30564,30566,30568,30570,30572,30574,30576,30578,30580,30582,30584,30586,30588,30590,30592,30594,30596,30598,30600,30602,30604,30606],{"class":5282,"line":6093},[5280,30561,5862],{"class":5619},[5280,30563,5720],{"class":5452},[5280,30565,28270],{"class":5424},[5280,30567,8072],{"class":5452},[5280,30569,30448],{"class":5758},[5280,30571,7315],{"class":5452},[5280,30573,22552],{"class":5293},[5280,30575,5835],{"class":5452},[5280,30577,15441],{"class":5424},[5280,30579,21723],{"class":5758},[5280,30581,5218],{"class":5452},[5280,30583,15393],{"class":5758},[5280,30585,5887],{"class":5452},[5280,30587,20126],{"class":5424},[5280,30589,5887],{"class":5452},[5280,30591,20131],{"class":5424},[5280,30593,6239],{"class":5758},[5280,30595,20151],{"class":5452},[5280,30597,22144],{"class":5758},[5280,30599,5218],{"class":5452},[5280,30601,17513],{"class":5758},[5280,30603,5887],{"class":5452},[5280,30605,9567],{"class":5758},[5280,30607,6020],{"class":5452},[5280,30609,30610,30612,30614,30616,30619,30621,30623],{"class":5282,"line":6098},[5280,30611,7023],{"class":5436},[5280,30613,28313],{"class":5424},[5280,30615,5218],{"class":5452},[5280,30617,30618],{"class":5424},"loginFn",[5280,30620,6301],{"class":5452},[5280,30622,5967],{"class":5619},[5280,30624,6111],{"class":5452},[5280,30626,30627,30629,30631,30633,30635,30637,30639,30641,30643,30645,30647],{"class":5282,"line":6114},[5280,30628,12200],{"class":5436},[5280,30630,28313],{"class":5424},[5280,30632,5218],{"class":5452},[5280,30634,30618],{"class":5293},[5280,30636,5835],{"class":5452},[5280,30638,15441],{"class":5424},[5280,30640,5887],{"class":5452},[5280,30642,20126],{"class":5424},[5280,30644,5887],{"class":5452},[5280,30646,20131],{"class":5424},[5280,30648,5598],{"class":5452},[5280,30650,30651],{"class":5282,"line":6130},[5280,30652,6553],{"class":5452},[5280,30654,30655,30657,30659,30661],{"class":5282,"line":6159},[5280,30656,7328],{"class":5436},[5280,30658,20192],{"class":5619},[5280,30660,5887],{"class":5452},[5280,30662,20459],{"class":5619},[5280,30664,30665],{"class":5282,"line":6187},[5280,30666,5976],{"class":5452},[5280,30668,30669],{"class":5282,"line":6193},[5280,30670,5313],{"emptyLinePlaceholder":5312},[5280,30672,30673,30675,30678,30680,30682,30684,30686,30688,30690],{"class":5282,"line":6207},[5280,30674,5862],{"class":5619},[5280,30676,30677],{"class":5293}," TestUserHandler_Register_Success",[5280,30679,5835],{"class":5452},[5280,30681,27327],{"class":5424},[5280,30683,6010],{"class":5452},[5280,30685,27332],{"class":5758},[5280,30687,5218],{"class":5452},[5280,30689,27337],{"class":5758},[5280,30691,6020],{"class":5452},[5280,30693,30694],{"class":5282,"line":6221},[5280,30695,30696],{"class":5286},"    \u002F\u002F Arrange: створюємо мок UseCase\n",[5280,30698,30699,30702,30704,30706],{"class":5282,"line":6233},[5280,30700,30701],{"class":5424},"    mockUC",[5280,30703,9745],{"class":5452},[5280,30705,30448],{"class":5758},[5280,30707,6781],{"class":5452},[5280,30709,30710,30713,30715,30717,30719,30721,30723,30725,30727,30729,30731,30733,30735,30737,30739,30741,30743,30745,30747,30749,30751,30753],{"class":5282,"line":6245},[5280,30711,30712],{"class":5424},"        registerFn",[5280,30714,6790],{"class":5452},[5280,30716,5862],{"class":5619},[5280,30718,5835],{"class":5452},[5280,30720,15441],{"class":5424},[5280,30722,21723],{"class":5758},[5280,30724,5218],{"class":5452},[5280,30726,15393],{"class":5758},[5280,30728,5887],{"class":5452},[5280,30730,20121],{"class":5424},[5280,30732,5887],{"class":5452},[5280,30734,20126],{"class":5424},[5280,30736,5887],{"class":5452},[5280,30738,20131],{"class":5424},[5280,30740,6239],{"class":5758},[5280,30742,20151],{"class":5452},[5280,30744,22144],{"class":5758},[5280,30746,5218],{"class":5452},[5280,30748,17513],{"class":5758},[5280,30750,5887],{"class":5452},[5280,30752,9567],{"class":5758},[5280,30754,6020],{"class":5452},[5280,30756,30757,30759,30761,30763,30765,30767],{"class":5282,"line":6251},[5280,30758,20309],{"class":5436},[5280,30760,13807],{"class":5452},[5280,30762,22144],{"class":5758},[5280,30764,5218],{"class":5452},[5280,30766,17513],{"class":5758},[5280,30768,6781],{"class":5452},[5280,30770,30771,30774,30776,30779],{"class":5282,"line":6256},[5280,30772,30773],{"class":5424},"                ID",[5280,30775,9788],{"class":5452},[5280,30777,30778],{"class":5297},"\"user-123\"",[5280,30780,6796],{"class":5452},[5280,30782,30783,30785,30787,30789],{"class":5282,"line":6308},[5280,30784,27420],{"class":5424},[5280,30786,17543],{"class":5452},[5280,30788,20121],{"class":5424},[5280,30790,6796],{"class":5452},[5280,30792,30793,30795,30797,30799],{"class":5282,"line":6339},[5280,30794,27431],{"class":5424},[5280,30796,9768],{"class":5452},[5280,30798,20126],{"class":5424},[5280,30800,6796],{"class":5452},[5280,30802,30803,30806,30808,30810,30812,30814],{"class":5282,"line":6371},[5280,30804,30805],{"class":5424},"                CreatedAt",[5280,30807,6790],{"class":5452},[5280,30809,5587],{"class":5424},[5280,30811,5218],{"class":5452},[5280,30813,6036],{"class":5293},[5280,30815,17825],{"class":5452},[5280,30817,30818,30821],{"class":5282,"line":6409},[5280,30819,30820],{"class":5452},"            }, ",[5280,30822,20459],{"class":5619},[5280,30824,30825],{"class":5282,"line":6415},[5280,30826,26172],{"class":5452},[5280,30828,30829],{"class":5282,"line":6420},[5280,30830,6553],{"class":5452},[5280,30832,30833],{"class":5282,"line":6425},[5280,30834,6724],{"class":5452},[5280,30836,30837,30839,30841,30843,30845,30848],{"class":5282,"line":6450},[5280,30838,13047],{"class":5424},[5280,30840,6029],{"class":5452},[5280,30842,26769],{"class":5293},[5280,30844,5835],{"class":5452},[5280,30846,30847],{"class":5424},"mockUC",[5280,30849,5598],{"class":5452},[5280,30851,30852],{"class":5282,"line":6471},[5280,30853,6724],{"class":5452},[5280,30855,30856],{"class":5282,"line":6507},[5280,30857,30858],{"class":5286},"    \u002F\u002F Створюємо HTTP-запит\n",[5280,30860,30861,30864,30866,30869],{"class":5282,"line":6512},[5280,30862,30863],{"class":5424},"    reqBody",[5280,30865,6029],{"class":5452},[5280,30867,30868],{"class":5758},"RegisterRequest",[5280,30870,6781],{"class":5452},[5280,30872,30873,30875,30877,30879],{"class":5282,"line":6520},[5280,30874,17540],{"class":5424},[5280,30876,9768],{"class":5452},[5280,30878,17546],{"class":5297},[5280,30880,6796],{"class":5452},[5280,30882,30883,30885,30887,30889],{"class":5282,"line":6550},[5280,30884,17553],{"class":5424},[5280,30886,13867],{"class":5452},[5280,30888,17558],{"class":5297},[5280,30890,6796],{"class":5452},[5280,30892,30893,30895,30897,30899],{"class":5282,"line":6556},[5280,30894,18415],{"class":5424},[5280,30896,6790],{"class":5452},[5280,30898,27447],{"class":5297},[5280,30900,6796],{"class":5452},[5280,30902,30903],{"class":5282,"line":6561},[5280,30904,6553],{"class":5452},[5280,30906,30907,30910,30912,30914,30916,30918,30920,30923,30925,30928],{"class":5282,"line":6621},[5280,30908,30909],{"class":5424},"    body",[5280,30911,5887],{"class":5452},[5280,30913,22169],{"class":5424},[5280,30915,6029],{"class":5452},[5280,30917,6267],{"class":5424},[5280,30919,5218],{"class":5452},[5280,30921,30922],{"class":5293},"Marshal",[5280,30924,5835],{"class":5452},[5280,30926,30927],{"class":5424},"reqBody",[5280,30929,5598],{"class":5452},[5280,30931,30932],{"class":5282,"line":6626},[5280,30933,6724],{"class":5452},[5280,30935,30936,30939,30941,30944,30946,30949,30951,30953,30955,30957,30959,30961,30963,30966,30968,30971,30973,30975],{"class":5282,"line":6631},[5280,30937,30938],{"class":5424},"    req",[5280,30940,6029],{"class":5452},[5280,30942,30943],{"class":5424},"httptest",[5280,30945,5218],{"class":5452},[5280,30947,30948],{"class":5293},"NewRequest",[5280,30950,5835],{"class":5452},[5280,30952,5952],{"class":5424},[5280,30954,5218],{"class":5452},[5280,30956,6202],{"class":5424},[5280,30958,5887],{"class":5452},[5280,30960,26865],{"class":5297},[5280,30962,5887],{"class":5452},[5280,30964,30965],{"class":5424},"bytes",[5280,30967,5218],{"class":5452},[5280,30969,30970],{"class":5293},"NewReader",[5280,30972,5835],{"class":5452},[5280,30974,13271],{"class":5424},[5280,30976,5970],{"class":5452},[5280,30978,30979,30981,30983,30985,30987,30989,30991,30993,30995,30997],{"class":5282,"line":6663},[5280,30980,30938],{"class":5424},[5280,30982,5218],{"class":5452},[5280,30984,6138],{"class":5424},[5280,30986,5218],{"class":5452},[5280,30988,6144],{"class":5293},[5280,30990,5835],{"class":5452},[5280,30992,6149],{"class":5297},[5280,30994,5887],{"class":5452},[5280,30996,6154],{"class":5297},[5280,30998,5598],{"class":5452},[5280,31000,31001],{"class":5282,"line":6678},[5280,31002,6724],{"class":5452},[5280,31004,31005],{"class":5282,"line":6721},[5280,31006,31007],{"class":5286},"    \u002F\u002F Створюємо ResponseRecorder\n",[5280,31009,31010,31013,31015,31017,31019,31022],{"class":5282,"line":6727},[5280,31011,31012],{"class":5424},"    rr",[5280,31014,6029],{"class":5452},[5280,31016,30943],{"class":5424},[5280,31018,5218],{"class":5452},[5280,31020,31021],{"class":5293},"NewRecorder",[5280,31023,6039],{"class":5452},[5280,31025,31026],{"class":5282,"line":6751},[5280,31027,6724],{"class":5452},[5280,31029,31030],{"class":5282,"line":6784},[5280,31031,31032],{"class":5286},"    \u002F\u002F Act: викликаємо handler\n",[5280,31034,31035,31037,31039,31041,31043,31046,31048,31050],{"class":5282,"line":6799},[5280,31036,13047],{"class":5424},[5280,31038,5218],{"class":5452},[5280,31040,25221],{"class":5293},[5280,31042,5835],{"class":5452},[5280,31044,31045],{"class":5424},"rr",[5280,31047,5887],{"class":5452},[5280,31049,18462],{"class":5424},[5280,31051,5598],{"class":5452},[5280,31053,31054],{"class":5282,"line":6831},[5280,31055,6724],{"class":5452},[5280,31057,31058],{"class":5282,"line":6837},[5280,31059,31060],{"class":5286},"    \u002F\u002F Assert: перевіряємо статус-код\n",[5280,31062,31063,31065,31068,31070,31072,31074,31077,31079,31082,31084,31086,31088,31090],{"class":5282,"line":6842},[5280,31064,7023],{"class":5436},[5280,31066,31067],{"class":5424}," status",[5280,31069,6029],{"class":5452},[5280,31071,31045],{"class":5424},[5280,31073,5218],{"class":5452},[5280,31075,31076],{"class":5424},"Code",[5280,31078,11404],{"class":5452},[5280,31080,31081],{"class":5424},"status",[5280,31083,6301],{"class":5452},[5280,31085,5952],{"class":5424},[5280,31087,5218],{"class":5452},[5280,31089,6466],{"class":5424},[5280,31091,6111],{"class":5452},[5280,31093,31094,31096,31098,31100,31102,31105,31107,31109,31111,31113,31115,31117,31119,31121,31123,31125],{"class":5282,"line":6901},[5280,31095,27755],{"class":5424},[5280,31097,5218],{"class":5452},[5280,31099,15523],{"class":5293},[5280,31101,5835],{"class":5452},[5280,31103,31104],{"class":5297},"\"Expected status ",[5280,31106,9946],{"class":5424},[5280,31108,27897],{"class":5297},[5280,31110,9946],{"class":5424},[5280,31112,6584],{"class":5297},[5280,31114,5887],{"class":5452},[5280,31116,5952],{"class":5424},[5280,31118,5218],{"class":5452},[5280,31120,6466],{"class":5424},[5280,31122,5887],{"class":5452},[5280,31124,31081],{"class":5424},[5280,31126,5598],{"class":5452},[5280,31128,31129],{"class":5282,"line":12702},[5280,31130,6553],{"class":5452},[5280,31132,31133],{"class":5282,"line":12731},[5280,31134,6724],{"class":5452},[5280,31136,31137],{"class":5282,"line":12736},[5280,31138,31139],{"class":5286},"    \u002F\u002F Перевіряємо Content-Type\n",[5280,31141,31142,31145,31147],{"class":5282,"line":12741},[5280,31143,31144],{"class":5424},"    expectedCT",[5280,31146,6029],{"class":5452},[5280,31148,31149],{"class":5297},"\"application\u002Fjson\"\n",[5280,31151,31152,31154,31157,31159,31161,31163,31165,31167,31169,31171,31173,31175,31178,31180,31183],{"class":5282,"line":12746},[5280,31153,7023],{"class":5436},[5280,31155,31156],{"class":5424}," ct",[5280,31158,6029],{"class":5452},[5280,31160,31045],{"class":5424},[5280,31162,5218],{"class":5452},[5280,31164,6138],{"class":5293},[5280,31166,6141],{"class":5452},[5280,31168,12252],{"class":5293},[5280,31170,5835],{"class":5452},[5280,31172,6149],{"class":5297},[5280,31174,6295],{"class":5452},[5280,31176,31177],{"class":5424},"ct",[5280,31179,6301],{"class":5452},[5280,31181,31182],{"class":5424},"expectedCT",[5280,31184,6111],{"class":5452},[5280,31186,31187,31189,31191,31193,31195,31198,31200,31202,31204,31206,31208,31210,31212,31214],{"class":5282,"line":12752},[5280,31188,27755],{"class":5424},[5280,31190,5218],{"class":5452},[5280,31192,15523],{"class":5293},[5280,31194,5835],{"class":5452},[5280,31196,31197],{"class":5297},"\"Expected Content-Type ",[5280,31199,6057],{"class":5424},[5280,31201,27897],{"class":5297},[5280,31203,6057],{"class":5424},[5280,31205,6584],{"class":5297},[5280,31207,5887],{"class":5452},[5280,31209,31182],{"class":5424},[5280,31211,5887],{"class":5452},[5280,31213,31177],{"class":5424},[5280,31215,5598],{"class":5452},[5280,31217,31218],{"class":5282,"line":12774},[5280,31219,6553],{"class":5452},[5280,31221,31222],{"class":5282,"line":12794},[5280,31223,6724],{"class":5452},[5280,31225,31226],{"class":5282,"line":12800},[5280,31227,31228],{"class":5286},"    \u002F\u002F Перевіряємо тіло відповіді\n",[5280,31230,31231,31233,31235],{"class":5282,"line":12805},[5280,31232,18383],{"class":5619},[5280,31234,13370],{"class":5424},[5280,31236,31237],{"class":5758}," UserResponse\n",[5280,31239,31240,31242,31244,31246,31248,31250,31252,31254,31256,31258,31260,31262,31264,31266,31268,31270,31272,31274,31276],{"class":5282,"line":12810},[5280,31241,7023],{"class":5436},[5280,31243,6262],{"class":5424},[5280,31245,6029],{"class":5452},[5280,31247,6267],{"class":5424},[5280,31249,5218],{"class":5452},[5280,31251,6272],{"class":5293},[5280,31253,5835],{"class":5452},[5280,31255,31045],{"class":5424},[5280,31257,5218],{"class":5452},[5280,31259,6281],{"class":5424},[5280,31261,6174],{"class":5452},[5280,31263,6286],{"class":5293},[5280,31265,6289],{"class":5452},[5280,31267,25656],{"class":5424},[5280,31269,6295],{"class":5452},[5280,31271,6298],{"class":5424},[5280,31273,6301],{"class":5452},[5280,31275,5967],{"class":5619},[5280,31277,6111],{"class":5452},[5280,31279,31280,31282,31284,31286,31288,31291,31293,31295,31297,31299],{"class":5282,"line":12816},[5280,31281,27755],{"class":5424},[5280,31283,5218],{"class":5452},[5280,31285,28967],{"class":5293},[5280,31287,5835],{"class":5452},[5280,31289,31290],{"class":5297},"\"Failed to decode response: ",[5280,31292,6362],{"class":5424},[5280,31294,6584],{"class":5297},[5280,31296,5887],{"class":5452},[5280,31298,6298],{"class":5424},[5280,31300,5598],{"class":5452},[5280,31302,31303],{"class":5282,"line":12836},[5280,31304,6553],{"class":5452},[5280,31306,31307],{"class":5282,"line":12842},[5280,31308,6724],{"class":5452},[5280,31310,31311,31313,31315,31317,31319,31321,31323],{"class":5282,"line":12847},[5280,31312,7023],{"class":5436},[5280,31314,13370],{"class":5424},[5280,31316,5218],{"class":5452},[5280,31318,6437],{"class":5424},[5280,31320,6301],{"class":5452},[5280,31322,17558],{"class":5297},[5280,31324,6111],{"class":5452},[5280,31326,31327,31329,31331,31333,31335,31337,31339,31341,31343,31345,31347,31349],{"class":5282,"line":18311},[5280,31328,27755],{"class":5424},[5280,31330,5218],{"class":5452},[5280,31332,15523],{"class":5293},[5280,31334,5835],{"class":5452},[5280,31336,29105],{"class":5297},[5280,31338,6057],{"class":5424},[5280,31340,29058],{"class":5297},[5280,31342,5887],{"class":5452},[5280,31344,25656],{"class":5424},[5280,31346,5218],{"class":5452},[5280,31348,6437],{"class":5424},[5280,31350,5598],{"class":5452},[5280,31352,31353],{"class":5282,"line":18338},[5280,31354,6553],{"class":5452},[5280,31356,31357],{"class":5282,"line":18343},[5280,31358,6724],{"class":5452},[5280,31360,31361,31363,31365,31367,31369,31371,31373],{"class":5282,"line":18348},[5280,31362,7023],{"class":5436},[5280,31364,13370],{"class":5424},[5280,31366,5218],{"class":5452},[5280,31368,6502],{"class":5424},[5280,31370,6301],{"class":5452},[5280,31372,17546],{"class":5297},[5280,31374,6111],{"class":5452},[5280,31376,31377,31379,31381,31383,31385,31387,31389,31391,31393,31395,31397,31399],{"class":5282,"line":18380},[5280,31378,27755],{"class":5424},[5280,31380,5218],{"class":5452},[5280,31382,15523],{"class":5293},[5280,31384,5835],{"class":5452},[5280,31386,29053],{"class":5297},[5280,31388,6057],{"class":5424},[5280,31390,29058],{"class":5297},[5280,31392,5887],{"class":5452},[5280,31394,25656],{"class":5424},[5280,31396,5218],{"class":5452},[5280,31398,6502],{"class":5424},[5280,31400,5598],{"class":5452},[5280,31402,31403],{"class":5282,"line":18393},[5280,31404,6553],{"class":5452},[5280,31406,31407],{"class":5282,"line":18402},[5280,31408,5976],{"class":5452},[5280,31410,31411],{"class":5282,"line":18412},[5280,31412,5313],{"emptyLinePlaceholder":5312},[5280,31414,31415,31417,31420,31422,31424,31426,31428,31430,31432],{"class":5282,"line":18423},[5280,31416,5862],{"class":5619},[5280,31418,31419],{"class":5293}," TestUserHandler_Register_InvalidJSON",[5280,31421,5835],{"class":5452},[5280,31423,27327],{"class":5424},[5280,31425,6010],{"class":5452},[5280,31427,27332],{"class":5758},[5280,31429,5218],{"class":5452},[5280,31431,27337],{"class":5758},[5280,31433,6020],{"class":5452},[5280,31435,31436,31438,31440,31442],{"class":5282,"line":18428},[5280,31437,30701],{"class":5424},[5280,31439,9745],{"class":5452},[5280,31441,30448],{"class":5758},[5280,31443,8056],{"class":5452},[5280,31445,31446,31448,31450,31452,31454,31456],{"class":5282,"line":18433},[5280,31447,13047],{"class":5424},[5280,31449,6029],{"class":5452},[5280,31451,26769],{"class":5293},[5280,31453,5835],{"class":5452},[5280,31455,30847],{"class":5424},[5280,31457,5598],{"class":5452},[5280,31459,31460],{"class":5282,"line":18475},[5280,31461,6724],{"class":5452},[5280,31463,31464],{"class":5282,"line":18502},[5280,31465,31466],{"class":5286},"    \u002F\u002F Невалідний JSON\n",[5280,31468,31469,31471,31473,31475,31477,31479,31481,31483,31485,31487,31489,31491],{"class":5282,"line":18507},[5280,31470,30938],{"class":5424},[5280,31472,6029],{"class":5452},[5280,31474,30943],{"class":5424},[5280,31476,5218],{"class":5452},[5280,31478,30948],{"class":5293},[5280,31480,5835],{"class":5452},[5280,31482,5952],{"class":5424},[5280,31484,5218],{"class":5452},[5280,31486,6202],{"class":5424},[5280,31488,5887],{"class":5452},[5280,31490,26865],{"class":5297},[5280,31492,6368],{"class":5452},[5280,31494,31495,31498,31500,31502,31504,31506,31508,31511],{"class":5282,"line":18512},[5280,31496,31497],{"class":5424},"        bytes",[5280,31499,5218],{"class":5452},[5280,31501,30970],{"class":5293},[5280,31503,22255],{"class":5452},[5280,31505,9556],{"class":5758},[5280,31507,5835],{"class":5452},[5280,31509,31510],{"class":5297},"\"invalid json\"",[5280,31512,10307],{"class":5452},[5280,31514,31515,31517,31519,31521,31523,31525,31527,31529,31531,31533],{"class":5282,"line":18517},[5280,31516,30938],{"class":5424},[5280,31518,5218],{"class":5452},[5280,31520,6138],{"class":5424},[5280,31522,5218],{"class":5452},[5280,31524,6144],{"class":5293},[5280,31526,5835],{"class":5452},[5280,31528,6149],{"class":5297},[5280,31530,5887],{"class":5452},[5280,31532,6154],{"class":5297},[5280,31534,5598],{"class":5452},[5280,31536,31537],{"class":5282,"line":18523},[5280,31538,6724],{"class":5452},[5280,31540,31541,31543,31545,31547,31549,31551],{"class":5282,"line":18535},[5280,31542,31012],{"class":5424},[5280,31544,6029],{"class":5452},[5280,31546,30943],{"class":5424},[5280,31548,5218],{"class":5452},[5280,31550,31021],{"class":5293},[5280,31552,6039],{"class":5452},[5280,31554,31555,31557,31559,31561,31563,31565,31567,31569],{"class":5282,"line":18572},[5280,31556,13047],{"class":5424},[5280,31558,5218],{"class":5452},[5280,31560,25221],{"class":5293},[5280,31562,5835],{"class":5452},[5280,31564,31045],{"class":5424},[5280,31566,5887],{"class":5452},[5280,31568,18462],{"class":5424},[5280,31570,5598],{"class":5452},[5280,31572,31573],{"class":5282,"line":18587},[5280,31574,6724],{"class":5452},[5280,31576,31577,31579,31581,31583,31585,31587,31589,31591,31593,31595,31597,31599,31601],{"class":5282,"line":18602},[5280,31578,7023],{"class":5436},[5280,31580,31067],{"class":5424},[5280,31582,6029],{"class":5452},[5280,31584,31045],{"class":5424},[5280,31586,5218],{"class":5452},[5280,31588,31076],{"class":5424},[5280,31590,11404],{"class":5452},[5280,31592,31081],{"class":5424},[5280,31594,6301],{"class":5452},[5280,31596,5952],{"class":5424},[5280,31598,5218],{"class":5452},[5280,31600,6334],{"class":5424},[5280,31602,6111],{"class":5452},[5280,31604,31605,31607,31609,31611,31613,31615,31617,31619,31621,31623,31625,31627,31629,31631,31633,31635],{"class":5282,"line":18617},[5280,31606,27755],{"class":5424},[5280,31608,5218],{"class":5452},[5280,31610,15523],{"class":5293},[5280,31612,5835],{"class":5452},[5280,31614,31104],{"class":5297},[5280,31616,9946],{"class":5424},[5280,31618,27897],{"class":5297},[5280,31620,9946],{"class":5424},[5280,31622,6584],{"class":5297},[5280,31624,5887],{"class":5452},[5280,31626,5952],{"class":5424},[5280,31628,5218],{"class":5452},[5280,31630,6334],{"class":5424},[5280,31632,5887],{"class":5452},[5280,31634,31081],{"class":5424},[5280,31636,5598],{"class":5452},[5280,31638,31639],{"class":5282,"line":18622},[5280,31640,6553],{"class":5452},[5280,31642,31643],{"class":5282,"line":18643},[5280,31644,5976],{"class":5452},[5280,31646,31647],{"class":5282,"line":18648},[5280,31648,5313],{"emptyLinePlaceholder":5312},[5280,31650,31651,31653,31656,31658,31660,31662,31664,31666,31668],{"class":5282,"line":18667},[5280,31652,5862],{"class":5619},[5280,31654,31655],{"class":5293}," TestUserHandler_Register_DuplicateEmail",[5280,31657,5835],{"class":5452},[5280,31659,27327],{"class":5424},[5280,31661,6010],{"class":5452},[5280,31663,27332],{"class":5758},[5280,31665,5218],{"class":5452},[5280,31667,27337],{"class":5758},[5280,31669,6020],{"class":5452},[5280,31671,31672,31674,31676,31678],{"class":5282,"line":18690},[5280,31673,30701],{"class":5424},[5280,31675,9745],{"class":5452},[5280,31677,30448],{"class":5758},[5280,31679,6781],{"class":5452},[5280,31681,31682,31684,31686,31688,31690,31692,31694,31696,31698,31700,31702,31704,31706,31708,31710,31712,31714,31716,31718,31720,31722,31724],{"class":5282,"line":18695},[5280,31683,30712],{"class":5424},[5280,31685,6790],{"class":5452},[5280,31687,5862],{"class":5619},[5280,31689,5835],{"class":5452},[5280,31691,15441],{"class":5424},[5280,31693,21723],{"class":5758},[5280,31695,5218],{"class":5452},[5280,31697,15393],{"class":5758},[5280,31699,5887],{"class":5452},[5280,31701,20121],{"class":5424},[5280,31703,5887],{"class":5452},[5280,31705,20126],{"class":5424},[5280,31707,5887],{"class":5452},[5280,31709,20131],{"class":5424},[5280,31711,6239],{"class":5758},[5280,31713,20151],{"class":5452},[5280,31715,22144],{"class":5758},[5280,31717,5218],{"class":5452},[5280,31719,17513],{"class":5758},[5280,31721,5887],{"class":5452},[5280,31723,9567],{"class":5758},[5280,31725,6020],{"class":5452},[5280,31727,31728,31730,31732,31734,31736,31738],{"class":5282,"line":18700},[5280,31729,20309],{"class":5436},[5280,31731,20192],{"class":5619},[5280,31733,5887],{"class":5452},[5280,31735,22144],{"class":5424},[5280,31737,5218],{"class":5452},[5280,31739,22221],{"class":5424},[5280,31741,31742],{"class":5282,"line":18732},[5280,31743,26172],{"class":5452},[5280,31745,31746],{"class":5282,"line":18738},[5280,31747,6553],{"class":5452},[5280,31749,31750],{"class":5282,"line":18769},[5280,31751,6724],{"class":5452},[5280,31753,31754,31756,31758,31760,31762,31764],{"class":5282,"line":18785},[5280,31755,13047],{"class":5424},[5280,31757,6029],{"class":5452},[5280,31759,26769],{"class":5293},[5280,31761,5835],{"class":5452},[5280,31763,30847],{"class":5424},[5280,31765,5598],{"class":5452},[5280,31767,31768],{"class":5282,"line":18790},[5280,31769,6724],{"class":5452},[5280,31771,31772,31774,31776,31778],{"class":5282,"line":18795},[5280,31773,30863],{"class":5424},[5280,31775,6029],{"class":5452},[5280,31777,30868],{"class":5758},[5280,31779,6781],{"class":5452},[5280,31781,31782,31784,31786,31788],{"class":5282,"line":18800},[5280,31783,17540],{"class":5424},[5280,31785,9768],{"class":5452},[5280,31787,27587],{"class":5297},[5280,31789,6796],{"class":5452},[5280,31791,31792,31794,31796,31799],{"class":5282,"line":18832},[5280,31793,17553],{"class":5424},[5280,31795,13867],{"class":5452},[5280,31797,31798],{"class":5297},"\"existing@example.com\"",[5280,31800,6796],{"class":5452},[5280,31802,31803,31805,31807,31809],{"class":5282,"line":18863},[5280,31804,18415],{"class":5424},[5280,31806,6790],{"class":5452},[5280,31808,27447],{"class":5297},[5280,31810,6796],{"class":5452},[5280,31812,31813],{"class":5282,"line":18868},[5280,31814,6553],{"class":5452},[5280,31816,31817,31819,31821,31823,31825,31827,31829,31831,31833,31835],{"class":5282,"line":18888},[5280,31818,30909],{"class":5424},[5280,31820,5887],{"class":5452},[5280,31822,22169],{"class":5424},[5280,31824,6029],{"class":5452},[5280,31826,6267],{"class":5424},[5280,31828,5218],{"class":5452},[5280,31830,30922],{"class":5293},[5280,31832,5835],{"class":5452},[5280,31834,30927],{"class":5424},[5280,31836,5598],{"class":5452},[5280,31838,31839],{"class":5282,"line":18899},[5280,31840,6724],{"class":5452},[5280,31842,31843,31845,31847,31849,31851,31853,31855,31857,31859,31861,31863,31865,31867,31869,31871,31873,31875,31877],{"class":5282,"line":18928},[5280,31844,30938],{"class":5424},[5280,31846,6029],{"class":5452},[5280,31848,30943],{"class":5424},[5280,31850,5218],{"class":5452},[5280,31852,30948],{"class":5293},[5280,31854,5835],{"class":5452},[5280,31856,5952],{"class":5424},[5280,31858,5218],{"class":5452},[5280,31860,6202],{"class":5424},[5280,31862,5887],{"class":5452},[5280,31864,26865],{"class":5297},[5280,31866,5887],{"class":5452},[5280,31868,30965],{"class":5424},[5280,31870,5218],{"class":5452},[5280,31872,30970],{"class":5293},[5280,31874,5835],{"class":5452},[5280,31876,13271],{"class":5424},[5280,31878,5970],{"class":5452},[5280,31880,31881,31883,31885,31887,31889,31891,31893,31895,31897,31899],{"class":5282,"line":18933},[5280,31882,30938],{"class":5424},[5280,31884,5218],{"class":5452},[5280,31886,6138],{"class":5424},[5280,31888,5218],{"class":5452},[5280,31890,6144],{"class":5293},[5280,31892,5835],{"class":5452},[5280,31894,6149],{"class":5297},[5280,31896,5887],{"class":5452},[5280,31898,6154],{"class":5297},[5280,31900,5598],{"class":5452},[5280,31902,31903],{"class":5282,"line":18938},[5280,31904,6724],{"class":5452},[5280,31906,31907,31909,31911,31913,31915,31917],{"class":5282,"line":18943},[5280,31908,31012],{"class":5424},[5280,31910,6029],{"class":5452},[5280,31912,30943],{"class":5424},[5280,31914,5218],{"class":5452},[5280,31916,31021],{"class":5293},[5280,31918,6039],{"class":5452},[5280,31920,31921,31923,31925,31927,31929,31931,31933,31935],{"class":5282,"line":18966},[5280,31922,13047],{"class":5424},[5280,31924,5218],{"class":5452},[5280,31926,25221],{"class":5293},[5280,31928,5835],{"class":5452},[5280,31930,31045],{"class":5424},[5280,31932,5887],{"class":5452},[5280,31934,18462],{"class":5424},[5280,31936,5598],{"class":5452},[5280,31938,31939],{"class":5282,"line":18971},[5280,31940,6724],{"class":5452},[5280,31942,31943,31945,31947,31949,31951,31953,31955,31957,31959,31961,31963,31965,31967],{"class":5282,"line":18976},[5280,31944,7023],{"class":5436},[5280,31946,31067],{"class":5424},[5280,31948,6029],{"class":5452},[5280,31950,31045],{"class":5424},[5280,31952,5218],{"class":5452},[5280,31954,31076],{"class":5424},[5280,31956,11404],{"class":5452},[5280,31958,31081],{"class":5424},[5280,31960,6301],{"class":5452},[5280,31962,5952],{"class":5424},[5280,31964,5218],{"class":5452},[5280,31966,19802],{"class":5424},[5280,31968,6111],{"class":5452},[5280,31970,31971,31973,31975,31977,31979,31981,31983,31986,31988,31990,31992,31994,31996,31998,32000,32002],{"class":5282,"line":19008},[5280,31972,27755],{"class":5424},[5280,31974,5218],{"class":5452},[5280,31976,15523],{"class":5293},[5280,31978,5835],{"class":5452},[5280,31980,31104],{"class":5297},[5280,31982,9946],{"class":5424},[5280,31984,31985],{"class":5297}," (Conflict), got ",[5280,31987,9946],{"class":5424},[5280,31989,6584],{"class":5297},[5280,31991,5887],{"class":5452},[5280,31993,5952],{"class":5424},[5280,31995,5218],{"class":5452},[5280,31997,19802],{"class":5424},[5280,31999,5887],{"class":5452},[5280,32001,31081],{"class":5424},[5280,32003,5598],{"class":5452},[5280,32005,32006],{"class":5282,"line":19039},[5280,32007,6553],{"class":5452},[5280,32009,32010],{"class":5282,"line":19044},[5280,32011,5976],{"class":5452},[8866,32013,32015,32024,32028,32032,32036,32040,32044,32048,32054],{":cursor":8868,"title":32014},"go test .\u002Finternal\u002Fdelivery\u002Fhttp -v",[8871,32016,32018,7648,32021],{"className":32017},[5282],[5280,32019,8878],{"className":32020},[8877],[5227,32022,32014],{"className":32023},[8882],[8871,32025,32027],{"className":32026},[5282],"=== RUN   TestUserHandler_Register_Success",[8871,32029,32031],{"className":32030},[5282],"--- PASS: TestUserHandler_Register_Success (0.00s)",[8871,32033,32035],{"className":32034},[5282],"=== RUN   TestUserHandler_Register_InvalidJSON",[8871,32037,32039],{"className":32038},[5282],"--- PASS: TestUserHandler_Register_InvalidJSON (0.00s)",[8871,32041,32043],{"className":32042},[5282],"=== RUN   TestUserHandler_Register_DuplicateEmail",[8871,32045,32047],{"className":32046},[5282],"--- PASS: TestUserHandler_Register_DuplicateEmail (0.00s)",[8871,32049,32051],{"className":32050},[5282],[5280,32052,28052],{"className":32053},[28051,8882],[8871,32055,32057],{"className":32056},[5282],"ok      myapp\u002Finternal\u002Fdelivery\u002Fhttp    0.002s",[8342,32059,32060,32063,32064,32067,32068,32071,32072,32075],{},[5227,32061,32062],{},"Переваги тестування з моками:"," тести виконуються ",[5227,32065,32066],{},"миттєво"," (немає реальних запитів до БД чи HTTP-сервера), ",[5227,32069,32070],{},"детерміновано"," (результат завжди однаковий) та ",[5227,32073,32074],{},"ізольовано"," (помилка в одному шарі не впливає на тести іншого).",[5254,32077],{},[5257,32079,32081],{"id":32080},"структура-проєкту-з-clean-architecture","Структура проєкту з Clean Architecture",[5266,32083,32085],{"id":32084},"рекомендована-організація-каталогів","Рекомендована організація каталогів",[32087,32088,32089],"code-tree",{},[5271,32090,32095],{"className":32091,"code":32092,"filename":32093,"language":32094,"meta":5276,"style":5276},"language-plaintext shiki shiki-themes light-plus dark-plus dark-plus","myapp\u002F\n├── cmd\u002F\n│   └── server\u002F\n│       └── main.go                    # Точка входу, DI\n│\n├── internal\u002F                          # Приватний код застосунку\n│   ├── domain\u002F                        # 🟡 Domain Layer\n│   │   ├── user.go                    # Доменна сутність\n│   │   ├── user_repository.go         # Інтерфейс репозиторію\n│   │   └── errors.go                  # Доменні помилки\n│   │\n│   ├── usecase\u002F                       # 🔵 Use Case Layer\n│   │   ├── user_usecase.go            # Бізнес-логіка\n│   │   └── user_usecase_test.go       # Тести UseCase\n│   │\n│   ├── repository\u002F                    # 🟢 Repository Implementations\n│   │   ├── memory\u002F\n│   │   │   └── user_repository.go     # In-memory реалізація\n│   │   └── postgres\u002F\n│   │       └── user_repository.go     # PostgreSQL реалізація\n│   │\n│   └── delivery\u002F                      # 🔴 Delivery Layer\n│       └── http\u002F\n│           ├── user_handler.go        # HTTP-обробники\n│           ├── user_handler_test.go   # Тести handlers\n│           └── middleware\u002F\n│               ├── logger.go\n│               ├── recovery.go\n│               ├── auth.go\n│               ├── cors.go\n│               └── ratelimit.go\n│\n├── migrations\u002F                        # SQL-міграції\n│   ├── 001_create_users_table.up.sql\n│   └── 001_create_users_table.down.sql\n│\n├── pkg\u002F                               # Публічні бібліотеки (опціонально)\n│   └── jwt\u002F\n│       └── jwt.go\n│\n├── go.mod\n├── go.sum\n└── README.md\n","Повна структура проєкту","plaintext",[5215,32096,32097,32102,32107,32112,32117,32122,32127,32132,32137,32142,32147,32152,32157,32162,32167,32171,32176,32181,32186,32191,32196,32200,32205,32210,32215,32220,32225,32230,32235,32240,32245,32250,32254,32259,32264,32269,32273,32278,32283,32288,32292,32297,32302],{"__ignoreMap":5276},[5280,32098,32099],{"class":5282,"line":5283},[5280,32100,32101],{},"myapp\u002F\n",[5280,32103,32104],{"class":5282,"line":5290},[5280,32105,32106],{},"├── cmd\u002F\n",[5280,32108,32109],{"class":5282,"line":5301},[5280,32110,32111],{},"│   └── server\u002F\n",[5280,32113,32114],{"class":5282,"line":5309},[5280,32115,32116],{},"│       └── main.go                    # Точка входу, DI\n",[5280,32118,32119],{"class":5282,"line":5316},[5280,32120,32121],{},"│\n",[5280,32123,32124],{"class":5282,"line":5322},[5280,32125,32126],{},"├── internal\u002F                          # Приватний код застосунку\n",[5280,32128,32129],{"class":5282,"line":5336},[5280,32130,32131],{},"│   ├── domain\u002F                        # 🟡 Domain Layer\n",[5280,32133,32134],{"class":5282,"line":5341},[5280,32135,32136],{},"│   │   ├── user.go                    # Доменна сутність\n",[5280,32138,32139],{"class":5282,"line":5347},[5280,32140,32141],{},"│   │   ├── user_repository.go         # Інтерфейс репозиторію\n",[5280,32143,32144],{"class":5282,"line":5358},[5280,32145,32146],{},"│   │   └── errors.go                  # Доменні помилки\n",[5280,32148,32149],{"class":5282,"line":5368},[5280,32150,32151],{},"│   │\n",[5280,32153,32154],{"class":5282,"line":5378},[5280,32155,32156],{},"│   ├── usecase\u002F                       # 🔵 Use Case Layer\n",[5280,32158,32159],{"class":5282,"line":5391},[5280,32160,32161],{},"│   │   ├── user_usecase.go            # Бізнес-логіка\n",[5280,32163,32164],{"class":5282,"line":5705},[5280,32165,32166],{},"│   │   └── user_usecase_test.go       # Тести UseCase\n",[5280,32168,32169],{"class":5282,"line":5820},[5280,32170,32151],{},[5280,32172,32173],{"class":5282,"line":5854},[5280,32174,32175],{},"│   ├── repository\u002F                    # 🟢 Repository Implementations\n",[5280,32177,32178],{"class":5282,"line":5859},[5280,32179,32180],{},"│   │   ├── memory\u002F\n",[5280,32182,32183],{"class":5282,"line":5871},[5280,32184,32185],{},"│   │   │   └── user_repository.go     # In-memory реалізація\n",[5280,32187,32188],{"class":5282,"line":5895},[5280,32189,32190],{},"│   │   └── postgres\u002F\n",[5280,32192,32193],{"class":5282,"line":5916},[5280,32194,32195],{},"│   │       └── user_repository.go     # PostgreSQL реалізація\n",[5280,32197,32198],{"class":5282,"line":5921},[5280,32199,32151],{},[5280,32201,32202],{"class":5282,"line":5939},[5280,32203,32204],{},"│   └── delivery\u002F                      # 🔴 Delivery Layer\n",[5280,32206,32207],{"class":5282,"line":5973},[5280,32208,32209],{},"│       └── http\u002F\n",[5280,32211,32212],{"class":5282,"line":5979},[5280,32213,32214],{},"│           ├── user_handler.go        # HTTP-обробники\n",[5280,32216,32217],{"class":5282,"line":5984},[5280,32218,32219],{},"│           ├── user_handler_test.go   # Тести handlers\n",[5280,32221,32222],{"class":5282,"line":6023},[5280,32223,32224],{},"│           └── middleware\u002F\n",[5280,32226,32227],{"class":5282,"line":6042},[5280,32228,32229],{},"│               ├── logger.go\n",[5280,32231,32232],{"class":5282,"line":6093},[5280,32233,32234],{},"│               ├── recovery.go\n",[5280,32236,32237],{"class":5282,"line":6098},[5280,32238,32239],{},"│               ├── auth.go\n",[5280,32241,32242],{"class":5282,"line":6114},[5280,32243,32244],{},"│               ├── cors.go\n",[5280,32246,32247],{"class":5282,"line":6130},[5280,32248,32249],{},"│               └── ratelimit.go\n",[5280,32251,32252],{"class":5282,"line":6159},[5280,32253,32121],{},[5280,32255,32256],{"class":5282,"line":6187},[5280,32257,32258],{},"├── migrations\u002F                        # SQL-міграції\n",[5280,32260,32261],{"class":5282,"line":6193},[5280,32262,32263],{},"│   ├── 001_create_users_table.up.sql\n",[5280,32265,32266],{"class":5282,"line":6207},[5280,32267,32268],{},"│   └── 001_create_users_table.down.sql\n",[5280,32270,32271],{"class":5282,"line":6221},[5280,32272,32121],{},[5280,32274,32275],{"class":5282,"line":6233},[5280,32276,32277],{},"├── pkg\u002F                               # Публічні бібліотеки (опціонально)\n",[5280,32279,32280],{"class":5282,"line":6245},[5280,32281,32282],{},"│   └── jwt\u002F\n",[5280,32284,32285],{"class":5282,"line":6251},[5280,32286,32287],{},"│       └── jwt.go\n",[5280,32289,32290],{"class":5282,"line":6256},[5280,32291,32121],{},[5280,32293,32294],{"class":5282,"line":6308},[5280,32295,32296],{},"├── go.mod\n",[5280,32298,32299],{"class":5282,"line":6339},[5280,32300,32301],{},"├── go.sum\n",[5280,32303,32304],{"class":5282,"line":6371},[5280,32305,32306],{},"└── README.md\n",[5262,32308,32309],{},[5227,32310,32311],{},"Пояснення структури:",[5194,32313,32314,32322,32330,32338,32346,32354,32362,32370],{},[5197,32315,32316,32321],{},[5227,32317,32318],{},[5215,32319,32320],{},"cmd\u002Fserver\u002Fmain.go"," — точка входу. Тут відбувається створення та з'єднання всіх компонентів (DI).",[5197,32323,32324,32329],{},[5227,32325,32326],{},[5215,32327,32328],{},"internal\u002F"," — приватний код, що не може імпортуватися іншими проєктами (механізм Go).",[5197,32331,32332,32337],{},[5227,32333,32334],{},[5215,32335,32336],{},"internal\u002Fdomain\u002F"," — найвнутрішній шар, без зовнішніх залежностей.",[5197,32339,32340,32345],{},[5227,32341,32342],{},[5215,32343,32344],{},"internal\u002Fusecase\u002F"," — бізнес-логіка, залежить тільки від Domain.",[5197,32347,32348,32353],{},[5227,32349,32350],{},[5215,32351,32352],{},"internal\u002Frepository\u002F"," — реалізації доступу до даних.",[5197,32355,32356,32361],{},[5227,32357,32358],{},[5215,32359,32360],{},"internal\u002Fdelivery\u002Fhttp\u002F"," — HTTP-шар, залежить від UseCase.",[5197,32363,32364,32369],{},[5227,32365,32366],{},[5215,32367,32368],{},"migrations\u002F"," — SQL-файли для версіонування схеми БД.",[5197,32371,32372,32377],{},[5227,32373,32374],{},[5215,32375,32376],{},"pkg\u002F"," — код, що може бути використаний іншими проєктами (опціонально).",[5712,32379,32380,32385,32386,32389,32390,32392],{},[5227,32381,32382,32383],{},"Конвенція ",[5215,32384,32328],{}," є вбудованою в Go. Компілятор ",[5227,32387,32388],{},"забороняє"," імпортування пакетів з ",[5215,32391,32328],{}," іншими модулями. Це гарантує інкапсуляцію та запобігає випадковому використанню приватного коду.",[5254,32394],{},[5257,32396,32398],{"id":32397},"порівняння-підходів-монолітний-vs-clean-architecture","Порівняння підходів: монолітний vs Clean Architecture",[32400,32401,32402,32423],"table",{},[32403,32404,32405],"thead",{},[32406,32407,32408,32414,32419],"tr",{},[32409,32410,32411],"th",{},[5227,32412,32413],{},"Критерій",[32409,32415,32416],{},[5227,32417,32418],{},"Монолітний обробник",[32409,32420,32421],{},[5227,32422,20878],{},[32424,32425,32426,32440,32453,32466,32479,32492,32505],"tbody",{},[32406,32427,32428,32434,32437],{},[32429,32430,32431],"td",{},[5227,32432,32433],{},"Складність входу",[32429,32435,32436],{},"⭐ Низька (почати легко)",[32429,32438,32439],{},"⭐⭐⭐ Висока (багато файлів і шарів)",[32406,32441,32442,32447,32450],{},[32429,32443,32444],{},[5227,32445,32446],{},"Тестованість",[32429,32448,32449],{},"❌ Складно: потрібен HTTP-сервер та БД",[32429,32451,32452],{},"✅ Легко: кожен шар тестується окремо",[32406,32454,32455,32460,32463],{},[32429,32456,32457],{},[5227,32458,32459],{},"Повторне використання",[32429,32461,32462],{},"❌ Бізнес-логіка прив'язана до HTTP",[32429,32464,32465],{},"✅ UseCase можна викликати з HTTP, gRPC, CLI",[32406,32467,32468,32473,32476],{},[32429,32469,32470],{},[5227,32471,32472],{},"Зміна технологій",[32429,32474,32475],{},"❌ Вимагає переписування всього коду",[32429,32477,32478],{},"✅ Заміна лише реалізації Repository або Delivery",[32406,32480,32481,32486,32489],{},[32429,32482,32483],{},[5227,32484,32485],{},"Підтримуваність",[32429,32487,32488],{},"❌ Код швидко стає заплутаним",[32429,32490,32491],{},"✅ Чітке розділення відповідальності",[32406,32493,32494,32499,32502],{},[32429,32495,32496],{},[5227,32497,32498],{},"Швидкість розробки",[32429,32500,32501],{},"⚡ Швидше на старті (прототипи, MVP)",[32429,32503,32504],{},"🐢 Повільніше на старті, але швидше при масштабуванні",[32406,32506,32507,32512,32515],{},[32429,32508,32509],{},[5227,32510,32511],{},"Розмір проєкту",[32429,32513,32514],{},"Підходить для 1-5 ендпоінтів",[32429,32516,32517],{},"Виправданий для 10+ ендпоінтів",[5266,32519,32521],{"id":32520},"коли-використовувати-clean-architecture","Коли використовувати Clean Architecture?",[5262,32523,32524],{},[5227,32525,32526],{},"✅ Варто використовувати, якщо:",[5194,32528,32529,32532,32535,32538,32541],{},[5197,32530,32531],{},"Проєкт планується розвивати довгостроково",[5197,32533,32534],{},"Команда > 2-3 розробників",[5197,32536,32537],{},"Очікується часта зміна вимог",[5197,32539,32540],{},"Потрібна підтримка різних способів доставки (HTTP + gRPC + CLI)",[5197,32542,32543],{},"Критична надійність та покриття тестами",[5262,32545,32546],{},[5227,32547,32548],{},"❌ Можна обійтись без неї, якщо:",[5194,32550,32551,32554,32557,32560],{},[5197,32552,32553],{},"Простий прототип або MVP",[5197,32555,32556],{},"Проєкт на 1-3 ендпоінти",[5197,32558,32559],{},"Одноразова утиліта",[5197,32561,32562],{},"Дедлайн через кілька днів",[8342,32564,32565,32568],{},[5227,32566,32567],{},"Прагматичний підхід:"," Почніть з простих обробників, а коли побачите дублювання логіки або складність тестування — поступово рефакторте код, виділяючи шари. Не варто вводити Clean Architecture \"на всяк випадок\" у невеликих проєктах.",[5254,32570],{},[5257,32572,32574],{"id":32573},"додаткові-поради-та-best-practices","Додаткові поради та best practices",[5266,32576,32578,32579,32581],{"id":32577},"_1-завжди-передавайте-contextcontext-першим-аргументом","1. Завжди передавайте ",[5215,32580,14764],{}," першим аргументом",[5271,32583,32585],{"className":5411,"code":32584,"language":5325,"meta":5276,"style":5276},"\u002F\u002F ✅ Правильно\nfunc (uc *UserUseCase) RegisterUser(ctx context.Context, name, email, password string) (*domain.User, error)\n\n\u002F\u002F ❌ Неправильно\nfunc (uc *UserUseCase) RegisterUser(name, email, password string) (*domain.User, error)\n",[5215,32586,32587,32592,32646,32650,32655],{"__ignoreMap":5276},[5280,32588,32589],{"class":5282,"line":5283},[5280,32590,32591],{"class":5286},"\u002F\u002F ✅ Правильно\n",[5280,32593,32594,32596,32598,32600,32602,32604,32606,32608,32610,32612,32614,32616,32618,32620,32622,32624,32626,32628,32630,32632,32634,32636,32638,32640,32642,32644],{"class":5282,"line":5290},[5280,32595,5862],{"class":5619},[5280,32597,5720],{"class":5452},[5280,32599,22107],{"class":5424},[5280,32601,8072],{"class":5452},[5280,32603,22065],{"class":5758},[5280,32605,7315],{"class":5452},[5280,32607,20688],{"class":5293},[5280,32609,5835],{"class":5452},[5280,32611,15441],{"class":5424},[5280,32613,21723],{"class":5758},[5280,32615,5218],{"class":5452},[5280,32617,15393],{"class":5758},[5280,32619,5887],{"class":5452},[5280,32621,20121],{"class":5424},[5280,32623,5887],{"class":5452},[5280,32625,20126],{"class":5424},[5280,32627,5887],{"class":5452},[5280,32629,20131],{"class":5424},[5280,32631,6239],{"class":5758},[5280,32633,20151],{"class":5452},[5280,32635,22144],{"class":5758},[5280,32637,5218],{"class":5452},[5280,32639,17513],{"class":5758},[5280,32641,5887],{"class":5452},[5280,32643,9567],{"class":5758},[5280,32645,5598],{"class":5452},[5280,32647,32648],{"class":5282,"line":5301},[5280,32649,5313],{"emptyLinePlaceholder":5312},[5280,32651,32652],{"class":5282,"line":5309},[5280,32653,32654],{"class":5286},"\u002F\u002F ❌ Неправильно\n",[5280,32656,32657,32659,32661,32663,32665,32667,32669,32671,32673,32675,32677,32679,32681,32683,32685,32687,32689,32691,32693,32695,32697],{"class":5282,"line":5316},[5280,32658,5862],{"class":5619},[5280,32660,5720],{"class":5452},[5280,32662,22107],{"class":5424},[5280,32664,8072],{"class":5452},[5280,32666,22065],{"class":5758},[5280,32668,7315],{"class":5452},[5280,32670,20688],{"class":5293},[5280,32672,5835],{"class":5452},[5280,32674,20121],{"class":5424},[5280,32676,5887],{"class":5452},[5280,32678,20126],{"class":5424},[5280,32680,5887],{"class":5452},[5280,32682,20131],{"class":5424},[5280,32684,6239],{"class":5758},[5280,32686,20151],{"class":5452},[5280,32688,22144],{"class":5758},[5280,32690,5218],{"class":5452},[5280,32692,17513],{"class":5758},[5280,32694,5887],{"class":5452},[5280,32696,9567],{"class":5758},[5280,32698,5598],{"class":5452},[5262,32700,32701],{},"Контекст дозволяє скасовувати операції, встановлювати таймаути та передавати метадані.",[5266,32703,32705],{"id":32704},"_2-використовуйте-dto-замість-доменних-сутностей-у-відповідях","2. Використовуйте DTO замість доменних сутностей у відповідях",[5271,32707,32709],{"className":5411,"code":32708,"language":5325,"meta":5276,"style":5276},"\u002F\u002F ✅ Правильно: окремий DTO без пароля\ntype UserResponse struct {\n    ID    string `json:\"id\"`\n    Name  string `json:\"name\"`\n    Email string `json:\"email\"`\n}\n\n\u002F\u002F ❌ Неправильно: віддаємо доменну сутність з паролем\njson.NewEncoder(w).Encode(user) \u002F\u002F user містить Password!\n",[5215,32710,32711,32716,32726,32734,32742,32750,32754,32758,32763],{"__ignoreMap":5276},[5280,32712,32713],{"class":5282,"line":5283},[5280,32714,32715],{"class":5286},"\u002F\u002F ✅ Правильно: окремий DTO без пароля\n",[5280,32717,32718,32720,32722,32724],{"class":5282,"line":5290},[5280,32719,7921],{"class":5619},[5280,32721,25154],{"class":5758},[5280,32723,6216],{"class":5619},[5280,32725,6111],{"class":5452},[5280,32727,32728,32730,32732],{"class":5282,"line":5301},[5280,32729,17439],{"class":5424},[5280,32731,18407],{"class":5758},[5280,32733,25167],{"class":5297},[5280,32735,32736,32738,32740],{"class":5282,"line":5309},[5280,32737,17450],{"class":5424},[5280,32739,6227],{"class":5758},[5280,32741,6230],{"class":5297},[5280,32743,32744,32746,32748],{"class":5282,"line":5316},[5280,32745,17461],{"class":5424},[5280,32747,6239],{"class":5758},[5280,32749,6242],{"class":5297},[5280,32751,32752],{"class":5282,"line":5322},[5280,32753,5976],{"class":5452},[5280,32755,32756],{"class":5282,"line":5336},[5280,32757,5313],{"emptyLinePlaceholder":5312},[5280,32759,32760],{"class":5282,"line":5341},[5280,32761,32762],{"class":5286},"\u002F\u002F ❌ Неправильно: віддаємо доменну сутність з паролем\n",[5280,32764,32765,32767,32769,32771,32773,32775,32777,32779,32781,32783,32785],{"class":5282,"line":5347},[5280,32766,6267],{"class":5424},[5280,32768,5218],{"class":5452},[5280,32770,6167],{"class":5293},[5280,32772,5835],{"class":5452},[5280,32774,5994],{"class":5424},[5280,32776,6174],{"class":5452},[5280,32778,6177],{"class":5293},[5280,32780,5835],{"class":5452},[5280,32782,6292],{"class":5424},[5280,32784,7315],{"class":5452},[5280,32786,32787],{"class":5286},"\u002F\u002F user містить Password!\n",[5266,32789,32791],{"id":32790},"_3-доменні-помилки-vs-http-статуси","3. Доменні помилки vs HTTP-статуси",[5262,32793,32794,32795,5408],{},"Мапування доменних помилок на HTTP-статуси має відбуватися ",[5227,32796,32797],{},"тільки в Delivery Layer",[5271,32799,32801],{"className":5411,"code":32800,"language":5325,"meta":5276,"style":5276},"func (h *UserHandler) Register(w http.ResponseWriter, r *http.Request) {\n    user, err := h.useCase.RegisterUser(...)\n    \n    if err != nil {\n        \u002F\u002F Мапування доменних помилок на HTTP-статуси\n        switch err {\n        case domain.ErrUserAlreadyExists:\n            http.Error(w, err.Error(), http.StatusConflict)\n        case domain.ErrInvalidEmail:\n            http.Error(w, err.Error(), http.StatusBadRequest)\n        default:\n            http.Error(w, \"Internal Server Error\", http.StatusInternalServerError)\n        }\n        return\n    }\n    \n    \u002F\u002F ...\n}\n",[5215,32802,32803,32843,32866,32870,32882,32886,32895,32908,32938,32951,32981,32988,33015,33019,33023,33027,33031,33036],{"__ignoreMap":5276},[5280,32804,32805,32807,32809,32811,32813,32815,32817,32819,32821,32823,32825,32827,32829,32831,32833,32835,32837,32839,32841],{"class":5282,"line":5283},[5280,32806,5862],{"class":5619},[5280,32808,5720],{"class":5452},[5280,32810,8069],{"class":5424},[5280,32812,8072],{"class":5452},[5280,32814,25066],{"class":5758},[5280,32816,7315],{"class":5452},[5280,32818,25221],{"class":5293},[5280,32820,5835],{"class":5452},[5280,32822,5994],{"class":5424},[5280,32824,5997],{"class":5758},[5280,32826,5218],{"class":5452},[5280,32828,6002],{"class":5758},[5280,32830,5887],{"class":5452},[5280,32832,6007],{"class":5424},[5280,32834,6010],{"class":5452},[5280,32836,5952],{"class":5758},[5280,32838,5218],{"class":5452},[5280,32840,6017],{"class":5758},[5280,32842,6020],{"class":5452},[5280,32844,32845,32847,32849,32851,32853,32855,32857,32859,32861,32863],{"class":5282,"line":5290},[5280,32846,18871],{"class":5424},[5280,32848,5887],{"class":5452},[5280,32850,6298],{"class":5424},[5280,32852,6029],{"class":5452},[5280,32854,11326],{"class":5424},[5280,32856,5218],{"class":5452},[5280,32858,25053],{"class":5424},[5280,32860,5218],{"class":5452},[5280,32862,20688],{"class":5293},[5280,32864,32865],{"class":5452},"(...)\n",[5280,32867,32868],{"class":5282,"line":5301},[5280,32869,6724],{"class":5452},[5280,32871,32872,32874,32876,32878,32880],{"class":5282,"line":5309},[5280,32873,7023],{"class":5436},[5280,32875,6262],{"class":5424},[5280,32877,6301],{"class":5452},[5280,32879,5967],{"class":5619},[5280,32881,6111],{"class":5452},[5280,32883,32884],{"class":5282,"line":5316},[5280,32885,25408],{"class":5286},[5280,32887,32888,32891,32893],{"class":5282,"line":5322},[5280,32889,32890],{"class":5436},"        switch",[5280,32892,6262],{"class":5424},[5280,32894,6111],{"class":5452},[5280,32896,32897,32900,32902,32904,32906],{"class":5282,"line":5336},[5280,32898,32899],{"class":5436},"        case",[5280,32901,22023],{"class":5424},[5280,32903,5218],{"class":5452},[5280,32905,25423],{"class":5424},[5280,32907,6127],{"class":5452},[5280,32909,32910,32912,32914,32916,32918,32920,32922,32924,32926,32928,32930,32932,32934,32936],{"class":5282,"line":5341},[5280,32911,6311],{"class":5424},[5280,32913,5218],{"class":5452},[5280,32915,6316],{"class":5293},[5280,32917,5835],{"class":5452},[5280,32919,5994],{"class":5424},[5280,32921,5887],{"class":5452},[5280,32923,6298],{"class":5424},[5280,32925,5218],{"class":5452},[5280,32927,6316],{"class":5293},[5280,32929,15396],{"class":5452},[5280,32931,5952],{"class":5424},[5280,32933,5218],{"class":5452},[5280,32935,19802],{"class":5424},[5280,32937,5598],{"class":5452},[5280,32939,32940,32942,32944,32946,32949],{"class":5282,"line":5347},[5280,32941,32899],{"class":5436},[5280,32943,22023],{"class":5424},[5280,32945,5218],{"class":5452},[5280,32947,32948],{"class":5424},"ErrInvalidEmail",[5280,32950,6127],{"class":5452},[5280,32952,32953,32955,32957,32959,32961,32963,32965,32967,32969,32971,32973,32975,32977,32979],{"class":5282,"line":5358},[5280,32954,6311],{"class":5424},[5280,32956,5218],{"class":5452},[5280,32958,6316],{"class":5293},[5280,32960,5835],{"class":5452},[5280,32962,5994],{"class":5424},[5280,32964,5887],{"class":5452},[5280,32966,6298],{"class":5424},[5280,32968,5218],{"class":5452},[5280,32970,6316],{"class":5293},[5280,32972,15396],{"class":5452},[5280,32974,5952],{"class":5424},[5280,32976,5218],{"class":5452},[5280,32978,6334],{"class":5424},[5280,32980,5598],{"class":5452},[5280,32982,32983,32986],{"class":5282,"line":5368},[5280,32984,32985],{"class":5436},"        default",[5280,32987,6127],{"class":5452},[5280,32989,32990,32992,32994,32996,32998,33000,33002,33005,33007,33009,33011,33013],{"class":5282,"line":5378},[5280,32991,6311],{"class":5424},[5280,32993,5218],{"class":5452},[5280,32995,6316],{"class":5293},[5280,32997,5835],{"class":5452},[5280,32999,5994],{"class":5424},[5280,33001,5887],{"class":5452},[5280,33003,33004],{"class":5297},"\"Internal Server Error\"",[5280,33006,5887],{"class":5452},[5280,33008,5952],{"class":5424},[5280,33010,5218],{"class":5452},[5280,33012,10246],{"class":5424},[5280,33014,5598],{"class":5452},[5280,33016,33017],{"class":5282,"line":5391},[5280,33018,6248],{"class":5452},[5280,33020,33021],{"class":5282,"line":5705},[5280,33022,15875],{"class":5436},[5280,33024,33025],{"class":5282,"line":5820},[5280,33026,6553],{"class":5452},[5280,33028,33029],{"class":5282,"line":5854},[5280,33030,6724],{"class":5452},[5280,33032,33033],{"class":5282,"line":5859},[5280,33034,33035],{"class":5286},"    \u002F\u002F ...\n",[5280,33037,33038],{"class":5282,"line":5871},[5280,33039,5976],{"class":5452},[5266,33041,33043],{"id":33042},"_4-не-витікайте-абстракції","4. Не витікайте абстракції",[5262,33045,33046],{},"UseCase не повинен знати про HTTP, JSON, або статус-коди:",[5271,33048,33050],{"className":5411,"code":33049,"language":5325,"meta":5276,"style":5276},"\u002F\u002F ❌ Погано: UseCase знає про HTTP\nfunc (uc *UserUseCase) RegisterUser(...) (int, error) {\n    if exists {\n        return http.StatusConflict, errors.New(\"user exists\")\n    }\n    \u002F\u002F ...\n    return http.StatusCreated, nil\n}\n\n\u002F\u002F ✅ Добре: UseCase повертає доменну помилку\nfunc (uc *UserUseCase) RegisterUser(...) (*domain.User, error) {\n    if exists {\n        return nil, domain.ErrUserAlreadyExists\n    }\n    \u002F\u002F ...\n    return user, nil\n}\n",[5215,33051,33052,33057,33084,33093,33118,33122,33126,33140,33144,33148,33153,33184,33192,33206,33210,33214,33224],{"__ignoreMap":5276},[5280,33053,33054],{"class":5282,"line":5283},[5280,33055,33056],{"class":5286},"\u002F\u002F ❌ Погано: UseCase знає про HTTP\n",[5280,33058,33059,33061,33063,33065,33067,33069,33071,33073,33076,33078,33080,33082],{"class":5282,"line":5290},[5280,33060,5862],{"class":5619},[5280,33062,5720],{"class":5452},[5280,33064,22107],{"class":5424},[5280,33066,8072],{"class":5452},[5280,33068,22065],{"class":5758},[5280,33070,7315],{"class":5452},[5280,33072,20688],{"class":5293},[5280,33074,33075],{"class":5452},"(...) (",[5280,33077,9562],{"class":5758},[5280,33079,5887],{"class":5452},[5280,33081,9567],{"class":5758},[5280,33083,6020],{"class":5452},[5280,33085,33086,33088,33091],{"class":5282,"line":5301},[5280,33087,7023],{"class":5436},[5280,33089,33090],{"class":5424}," exists",[5280,33092,6111],{"class":5452},[5280,33094,33095,33097,33099,33101,33103,33105,33107,33109,33111,33113,33116],{"class":5282,"line":5309},[5280,33096,12200],{"class":5436},[5280,33098,5997],{"class":5424},[5280,33100,5218],{"class":5452},[5280,33102,19802],{"class":5424},[5280,33104,5887],{"class":5452},[5280,33106,20197],{"class":5424},[5280,33108,5218],{"class":5452},[5280,33110,20202],{"class":5293},[5280,33112,5835],{"class":5452},[5280,33114,33115],{"class":5297},"\"user exists\"",[5280,33117,5598],{"class":5452},[5280,33119,33120],{"class":5282,"line":5316},[5280,33121,6553],{"class":5452},[5280,33123,33124],{"class":5282,"line":5322},[5280,33125,33035],{"class":5286},[5280,33127,33128,33130,33132,33134,33136,33138],{"class":5282,"line":5336},[5280,33129,7328],{"class":5436},[5280,33131,5997],{"class":5424},[5280,33133,5218],{"class":5452},[5280,33135,6466],{"class":5424},[5280,33137,5887],{"class":5452},[5280,33139,20459],{"class":5619},[5280,33141,33142],{"class":5282,"line":5341},[5280,33143,5976],{"class":5452},[5280,33145,33146],{"class":5282,"line":5347},[5280,33147,5313],{"emptyLinePlaceholder":5312},[5280,33149,33150],{"class":5282,"line":5358},[5280,33151,33152],{"class":5286},"\u002F\u002F ✅ Добре: UseCase повертає доменну помилку\n",[5280,33154,33155,33157,33159,33161,33163,33165,33167,33169,33172,33174,33176,33178,33180,33182],{"class":5282,"line":5368},[5280,33156,5862],{"class":5619},[5280,33158,5720],{"class":5452},[5280,33160,22107],{"class":5424},[5280,33162,8072],{"class":5452},[5280,33164,22065],{"class":5758},[5280,33166,7315],{"class":5452},[5280,33168,20688],{"class":5293},[5280,33170,33171],{"class":5452},"(...) (*",[5280,33173,22144],{"class":5758},[5280,33175,5218],{"class":5452},[5280,33177,17513],{"class":5758},[5280,33179,5887],{"class":5452},[5280,33181,9567],{"class":5758},[5280,33183,6020],{"class":5452},[5280,33185,33186,33188,33190],{"class":5282,"line":5378},[5280,33187,7023],{"class":5436},[5280,33189,33090],{"class":5424},[5280,33191,6111],{"class":5452},[5280,33193,33194,33196,33198,33200,33202,33204],{"class":5282,"line":5391},[5280,33195,12200],{"class":5436},[5280,33197,20192],{"class":5619},[5280,33199,5887],{"class":5452},[5280,33201,22144],{"class":5424},[5280,33203,5218],{"class":5452},[5280,33205,22221],{"class":5424},[5280,33207,33208],{"class":5282,"line":5705},[5280,33209,6553],{"class":5452},[5280,33211,33212],{"class":5282,"line":5820},[5280,33213,33035],{"class":5286},[5280,33215,33216,33218,33220,33222],{"class":5282,"line":5854},[5280,33217,7328],{"class":5436},[5280,33219,6213],{"class":5424},[5280,33221,5887],{"class":5452},[5280,33223,20459],{"class":5619},[5280,33225,33226],{"class":5282,"line":5859},[5280,33227,5976],{"class":5452},[5266,33229,33231],{"id":33230},"_5-інтеграційні-тести-vs-модульні-тести","5. Інтеграційні тести vs модульні тести",[5262,33233,33234],{},[5227,33235,33236],{},"Модульні тести (unit tests):",[5194,33238,33239,33242,33245,33248],{},[5197,33240,33241],{},"Тестують окремий компонент з моками",[5197,33243,33244],{},"Швидкі (мілісекунди)",[5197,33246,33247],{},"Легко налагоджувати",[5197,33249,33250],{},"Використовуйте для Domain та UseCase",[5262,33252,33253],{},[5227,33254,33255],{},"Інтеграційні тести (integration tests):",[5194,33257,33258,33261,33264,33267],{},[5197,33259,33260],{},"Тестують взаємодію з реальною БД\u002FAPI",[5197,33262,33263],{},"Повільніші (секунди)",[5197,33265,33266],{},"Виявляють проблеми інтеграції",[5197,33268,33269],{},"Використовуйте для Repository та E2E-тестів",[5254,33271],{},[5257,33273,33275],{"id":33274},"запитання-для-самоконтролю","Запитання для самоконтролю",[16239,33277,33278,33315,33367,33406,33449],{},[16242,33279,33281,33288,33312],{"icon":16244,"label":33280},"❓ Чому Middleware повертає http.Handler, а не просто виконує логіку?",[5262,33282,33283,33284,33287],{},"Middleware створює ",[5227,33285,33286],{},"новий обробник",", що обгортає попередній. Це дозволяє:",[6916,33289,33290,33299,33305],{},[5197,33291,33292,33293,8595,33296,33298],{},"Виконати код ",[5227,33294,33295],{},"до",[5215,33297,8598],{}," (препроцесинг)",[5197,33300,33301,33302,33304],{},"Вирішити, чи викликати ",[5215,33303,8598],{}," взагалі (наприклад, припинити обробку при невалідній автентифікації)",[5197,33306,33292,33307,8595,33309,33311],{},[5227,33308,10772],{},[5215,33310,8598],{}," (постпроцесинг, логування результату)",[5262,33313,33314],{},"Без цього підходу неможливо було б контролювати потік виконання.",[16242,33316,33318,33323,33341,33346,33364],{"icon":16244,"label":33317},"❓ У чому різниця між Domain та UseCase шарами?",[5262,33319,33320],{},[5227,33321,33322],{},"Domain (Entities):",[5194,33324,33325,33332,33335],{},[5197,33326,33327,33328,33331],{},"Доменні сутності та бізнес-правила рівня ",[5227,33329,33330],{},"окремої"," сутності",[5197,33333,33334],{},"Наприклад: валідація email, перевірка мінімальної довжини пароля",[5197,33336,33337,33340],{},[5227,33338,33339],{},"Не залежить ні від чого"," (навіть від інших доменних сутностей)",[5262,33342,33343],{},[5227,33344,33345],{},"UseCase (Business Logic):",[5194,33347,33348,33355,33358],{},[5197,33349,33350,33351,33354],{},"Сценарії використання, що ",[5227,33352,33353],{},"оркеструють"," роботу кількох сутностей",[5197,33356,33357],{},"Наприклад: \"зареєструвати користувача\" = перевірити існування + хешувати пароль + зберегти",[5197,33359,33360,33363],{},[5227,33361,33362],{},"Залежить від Domain"," (використовує доменні сутності та репозиторії)",[5262,33365,33366],{},"Аналогія: Domain — це \"словник мови\", UseCase — \"речення та абзаци\".",[16242,33368,33370,33375],{"icon":16244,"label":33369},"❓ Навіщо використовувати інтерфейси для репозиторіїв?",[5262,33371,7968,33372,33374],{},[5215,33373,22060],{}," дозволяє:",[6916,33376,33377,33394,33400],{},[5197,33378,33379,33382,33383],{},[5227,33380,33381],{},"Підставити різні реалізації"," без зміни UseCase:",[5194,33384,33385,33388,33391],{},[5197,33386,33387],{},"PostgreSQL для продакшну",[5197,33389,33390],{},"In-memory для локальної розробки",[5197,33392,33393],{},"Мок для тестування",[5197,33395,33396,33399],{},[5227,33397,33398],{},"Дотриматися Dependency Rule:"," UseCase (внутрішній шар) залежить від інтерфейсу Domain, а не від зовнішньої реалізації Repository.",[5197,33401,33402,33405],{},[5227,33403,33404],{},"Полегшити тестування:"," можна створити мок-реалізацію за 10 рядків коду замість налаштування реальної БД.",[16242,33407,33409,33417,33420,33425,33439],{"icon":16244,"label":33408},"❓ Чи можна використовувати Clean Architecture у невеликих проєктах?",[5262,33410,33411,5720,33414,6174],{},[5227,33412,33413],{},"Так, але це може бути надлишковим",[5207,33415,33416],{},"over-engineering",[5262,33418,33419],{},"Для проєкту на 3-5 ендпоінтів розділення на 4 шари додає більше складності, ніж переваг.",[5262,33421,33422],{},[5227,33423,33424],{},"Компромісні підходи:",[6916,33426,33427,33430,33433,33436],{},[5197,33428,33429],{},"Почніть з простих обробників без шарування",[5197,33431,33432],{},"Коли з'являється дублювання бізнес-логіки, винесіть її в окремі функції",[5197,33434,33435],{},"Коли змінюється спосіб зберігання (in-memory → PostgreSQL), введіть Repository Pattern",[5197,33437,33438],{},"Повна Clean Architecture — коли проєкт переростає 10-15 ендпоінтів або працює команда > 3 осіб",[5262,33440,33441,33444,33445,33448],{},[5227,33442,33443],{},"Правило:"," архітектура має ",[5227,33446,33447],{},"полегшувати"," розробку, а не ускладнювати.",[16242,33450,33452,33458,33754],{"icon":16244,"label":33451},"❓ Як тестувати Middleware?",[5262,33453,33454,33455,33457],{},"Middleware тестується через ",[5215,33456,30943],{}," аналогічно до звичайних обробників:",[5271,33459,33461],{"className":5411,"code":33460,"language":5325,"meta":5276,"style":5276},"func TestLogger_Middleware(t *testing.T) {\n    nextCalled := false\n    nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        nextCalled = true\n        w.WriteHeader(http.StatusOK)\n    })\n    \n    loggedHandler := middleware.Logger(nextHandler)\n    \n    req := httptest.NewRequest(http.MethodGet, \"\u002Ftest\", nil)\n    rr := httptest.NewRecorder()\n    \n    loggedHandler.ServeHTTP(rr, req)\n    \n    if !nextCalled {\n        t.Error(\"Next handler was not called\")\n    }\n    \n    if rr.Code != http.StatusOK {\n        t.Errorf(\"Expected status 200, got %d\", rr.Code)\n    }\n}\n",[5215,33462,33463,33484,33494,33535,33544,33562,33566,33570,33590,33594,33624,33638,33642,33660,33664,33675,33690,33694,33698,33719,33746,33750],{"__ignoreMap":5276},[5280,33464,33465,33467,33470,33472,33474,33476,33478,33480,33482],{"class":5282,"line":5283},[5280,33466,5862],{"class":5619},[5280,33468,33469],{"class":5293}," TestLogger_Middleware",[5280,33471,5835],{"class":5452},[5280,33473,27327],{"class":5424},[5280,33475,6010],{"class":5452},[5280,33477,27332],{"class":5758},[5280,33479,5218],{"class":5452},[5280,33481,27337],{"class":5758},[5280,33483,6020],{"class":5452},[5280,33485,33486,33489,33491],{"class":5282,"line":5290},[5280,33487,33488],{"class":5424},"    nextCalled",[5280,33490,6029],{"class":5452},[5280,33492,33493],{"class":5619},"false\n",[5280,33495,33496,33499,33501,33503,33505,33507,33509,33511,33513,33515,33517,33519,33521,33523,33525,33527,33529,33531,33533],{"class":5282,"line":5301},[5280,33497,33498],{"class":5424},"    nextHandler",[5280,33500,6029],{"class":5452},[5280,33502,5952],{"class":5424},[5280,33504,5218],{"class":5452},[5280,33506,7312],{"class":5293},[5280,33508,5835],{"class":5452},[5280,33510,5862],{"class":5619},[5280,33512,5835],{"class":5452},[5280,33514,5994],{"class":5424},[5280,33516,5997],{"class":5758},[5280,33518,5218],{"class":5452},[5280,33520,6002],{"class":5758},[5280,33522,5887],{"class":5452},[5280,33524,6007],{"class":5424},[5280,33526,6010],{"class":5452},[5280,33528,5952],{"class":5758},[5280,33530,5218],{"class":5452},[5280,33532,6017],{"class":5758},[5280,33534,6020],{"class":5452},[5280,33536,33537,33540,33542],{"class":5282,"line":5309},[5280,33538,33539],{"class":5424},"        nextCalled",[5280,33541,5829],{"class":5452},[5280,33543,9493],{"class":5619},[5280,33545,33546,33548,33550,33552,33554,33556,33558,33560],{"class":5282,"line":5316},[5280,33547,6133],{"class":5424},[5280,33549,5218],{"class":5452},[5280,33551,6457],{"class":5293},[5280,33553,5835],{"class":5452},[5280,33555,5952],{"class":5424},[5280,33557,5218],{"class":5452},[5280,33559,9775],{"class":5424},[5280,33561,5598],{"class":5452},[5280,33563,33564],{"class":5282,"line":5322},[5280,33565,6834],{"class":5452},[5280,33567,33568],{"class":5282,"line":5336},[5280,33569,6724],{"class":5452},[5280,33571,33572,33575,33577,33579,33581,33583,33585,33588],{"class":5282,"line":5341},[5280,33573,33574],{"class":5424},"    loggedHandler",[5280,33576,6029],{"class":5452},[5280,33578,8768],{"class":5424},[5280,33580,5218],{"class":5452},[5280,33582,10291],{"class":5293},[5280,33584,5835],{"class":5452},[5280,33586,33587],{"class":5424},"nextHandler",[5280,33589,5598],{"class":5452},[5280,33591,33592],{"class":5282,"line":5347},[5280,33593,6724],{"class":5452},[5280,33595,33596,33598,33600,33602,33604,33606,33608,33610,33612,33614,33616,33618,33620,33622],{"class":5282,"line":5358},[5280,33597,30938],{"class":5424},[5280,33599,6029],{"class":5452},[5280,33601,30943],{"class":5424},[5280,33603,5218],{"class":5452},[5280,33605,30948],{"class":5293},[5280,33607,5835],{"class":5452},[5280,33609,5952],{"class":5424},[5280,33611,5218],{"class":5452},[5280,33613,6124],{"class":5424},[5280,33615,5887],{"class":5452},[5280,33617,8150],{"class":5297},[5280,33619,5887],{"class":5452},[5280,33621,5967],{"class":5619},[5280,33623,5598],{"class":5452},[5280,33625,33626,33628,33630,33632,33634,33636],{"class":5282,"line":5368},[5280,33627,31012],{"class":5424},[5280,33629,6029],{"class":5452},[5280,33631,30943],{"class":5424},[5280,33633,5218],{"class":5452},[5280,33635,31021],{"class":5293},[5280,33637,6039],{"class":5452},[5280,33639,33640],{"class":5282,"line":5378},[5280,33641,6724],{"class":5452},[5280,33643,33644,33646,33648,33650,33652,33654,33656,33658],{"class":5282,"line":5391},[5280,33645,33574],{"class":5424},[5280,33647,5218],{"class":5452},[5280,33649,8033],{"class":5293},[5280,33651,5835],{"class":5452},[5280,33653,31045],{"class":5424},[5280,33655,5887],{"class":5452},[5280,33657,18462],{"class":5424},[5280,33659,5598],{"class":5452},[5280,33661,33662],{"class":5282,"line":5705},[5280,33663,6724],{"class":5452},[5280,33665,33666,33668,33670,33673],{"class":5282,"line":5820},[5280,33667,7023],{"class":5436},[5280,33669,9455],{"class":5452},[5280,33671,33672],{"class":5424},"nextCalled",[5280,33674,6111],{"class":5452},[5280,33676,33677,33679,33681,33683,33685,33688],{"class":5282,"line":5854},[5280,33678,27755],{"class":5424},[5280,33680,5218],{"class":5452},[5280,33682,6316],{"class":5293},[5280,33684,5835],{"class":5452},[5280,33686,33687],{"class":5297},"\"Next handler was not called\"",[5280,33689,5598],{"class":5452},[5280,33691,33692],{"class":5282,"line":5859},[5280,33693,6553],{"class":5452},[5280,33695,33696],{"class":5282,"line":5871},[5280,33697,6724],{"class":5452},[5280,33699,33700,33702,33705,33707,33709,33711,33713,33715,33717],{"class":5282,"line":5895},[5280,33701,7023],{"class":5436},[5280,33703,33704],{"class":5424}," rr",[5280,33706,5218],{"class":5452},[5280,33708,31076],{"class":5424},[5280,33710,6301],{"class":5452},[5280,33712,5952],{"class":5424},[5280,33714,5218],{"class":5452},[5280,33716,9775],{"class":5424},[5280,33718,6111],{"class":5452},[5280,33720,33721,33723,33725,33727,33729,33732,33734,33736,33738,33740,33742,33744],{"class":5282,"line":5916},[5280,33722,27755],{"class":5424},[5280,33724,5218],{"class":5452},[5280,33726,15523],{"class":5293},[5280,33728,5835],{"class":5452},[5280,33730,33731],{"class":5297},"\"Expected status 200, got ",[5280,33733,9946],{"class":5424},[5280,33735,6584],{"class":5297},[5280,33737,5887],{"class":5452},[5280,33739,31045],{"class":5424},[5280,33741,5218],{"class":5452},[5280,33743,31076],{"class":5424},[5280,33745,5598],{"class":5452},[5280,33747,33748],{"class":5282,"line":5921},[5280,33749,6553],{"class":5452},[5280,33751,33752],{"class":5282,"line":5939},[5280,33753,5976],{"class":5452},[5262,33755,33756,33757,33760],{},"Для Middleware, що модифікує контекст, перевірте ",[5215,33758,33759],{},"r.Context().Value(...)"," у наступному обробнику.",[5254,33762],{},[5257,33764,33766],{"id":33765},"sql-міграції-для-postgresql","SQL-міграції для PostgreSQL",[5266,33768,33770],{"id":33769},"створення-таблиці-користувачів","Створення таблиці користувачів",[5262,33772,33773,33774,5408],{},"Міграції дозволяють версіонувати схему бази даних. Створимо міграції для таблиці ",[5215,33775,6182],{},[5271,33777,33780],{"className":33778,"code":33779,"language":23784,"meta":5276,"style":5276},"language-sql shiki shiki-themes light-plus dark-plus dark-plus","-- migrations\u002F001_create_users_table.up.sql\nCREATE TABLE IF NOT EXISTS users (\n    id VARCHAR(255) PRIMARY KEY,\n    name VARCHAR(255) NOT NULL,\n    email VARCHAR(255) UNIQUE NOT NULL,\n    password VARCHAR(255) NOT NULL,\n    created_at TIMESTAMP NOT NULL DEFAULT NOW(),\n    updated_at TIMESTAMP NOT NULL DEFAULT NOW()\n);\n\nCREATE INDEX idx_users_email ON users(email);\n\n-- Тригер для автоматичного оновлення updated_at\nCREATE OR REPLACE FUNCTION update_updated_at_column()\nRETURNS TRIGGER AS $$\nBEGIN\n    NEW.updated_at = NOW();\n    RETURN NEW;\nEND;\n$$ language 'plpgsql';\n\nCREATE TRIGGER update_users_updated_at \n    BEFORE UPDATE ON users \n    FOR EACH ROW \n    EXECUTE FUNCTION update_updated_at_column();\n",[5215,33781,33782,33787,33807,33827,33846,33867,33884,33902,33917,33921,33925,33941,33945,33950,33963,33977,33982,33992,34000,34007,34020,34024,34036,34049,34062],{"__ignoreMap":5276},[5280,33783,33784],{"class":5282,"line":5283},[5280,33785,33786],{"class":5286},"-- migrations\u002F001_create_users_table.up.sql\n",[5280,33788,33789,33792,33795,33798,33801,33804],{"class":5282,"line":5290},[5280,33790,33791],{"class":5619},"CREATE",[5280,33793,33794],{"class":5619}," TABLE",[5280,33796,33797],{"class":5293}," IF",[5280,33799,33800],{"class":5619}," NOT",[5280,33802,33803],{"class":5619}," EXISTS",[5280,33805,33806],{"class":5452}," users (\n",[5280,33808,33809,33812,33815,33817,33820,33822,33825],{"class":5282,"line":5301},[5280,33810,33811],{"class":5452},"    id ",[5280,33813,33814],{"class":5619},"VARCHAR",[5280,33816,5835],{"class":5452},[5280,33818,33819],{"class":5439},"255",[5280,33821,7315],{"class":5452},[5280,33823,33824],{"class":5619},"PRIMARY KEY",[5280,33826,6796],{"class":5452},[5280,33828,33829,33832,33835,33837,33839,33841,33844],{"class":5282,"line":5309},[5280,33830,33831],{"class":5619},"    name",[5280,33833,33834],{"class":5619}," VARCHAR",[5280,33836,5835],{"class":5452},[5280,33838,33819],{"class":5439},[5280,33840,7315],{"class":5452},[5280,33842,33843],{"class":5619},"NOT NULL",[5280,33845,6796],{"class":5452},[5280,33847,33848,33851,33853,33855,33857,33859,33862,33865],{"class":5282,"line":5316},[5280,33849,33850],{"class":5452},"    email ",[5280,33852,33814],{"class":5619},[5280,33854,5835],{"class":5452},[5280,33856,33819],{"class":5439},[5280,33858,7315],{"class":5452},[5280,33860,33861],{"class":5619},"UNIQUE",[5280,33863,33864],{"class":5619}," NOT NULL",[5280,33866,6796],{"class":5452},[5280,33868,33869,33872,33874,33876,33878,33880,33882],{"class":5282,"line":5322},[5280,33870,33871],{"class":5619},"    password",[5280,33873,33834],{"class":5619},[5280,33875,5835],{"class":5452},[5280,33877,33819],{"class":5439},[5280,33879,7315],{"class":5452},[5280,33881,33843],{"class":5619},[5280,33883,6796],{"class":5452},[5280,33885,33886,33889,33892,33894,33897,33900],{"class":5282,"line":5336},[5280,33887,33888],{"class":5452},"    created_at ",[5280,33890,33891],{"class":5619},"TIMESTAMP",[5280,33893,33864],{"class":5619},[5280,33895,33896],{"class":5619}," DEFAULT",[5280,33898,33899],{"class":5619}," NOW",[5280,33901,17825],{"class":5452},[5280,33903,33904,33907,33909,33911,33913,33915],{"class":5282,"line":5341},[5280,33905,33906],{"class":5452},"    updated_at ",[5280,33908,33891],{"class":5619},[5280,33910,33864],{"class":5619},[5280,33912,33896],{"class":5619},[5280,33914,33899],{"class":5619},[5280,33916,6039],{"class":5452},[5280,33918,33919],{"class":5282,"line":5347},[5280,33920,13387],{"class":5452},[5280,33922,33923],{"class":5282,"line":5358},[5280,33924,5313],{"emptyLinePlaceholder":5312},[5280,33926,33927,33929,33932,33935,33938],{"class":5282,"line":5368},[5280,33928,33791],{"class":5619},[5280,33930,33931],{"class":5619}," INDEX",[5280,33933,33934],{"class":5293}," idx_users_email",[5280,33936,33937],{"class":5619}," ON",[5280,33939,33940],{"class":5452}," users(email);\n",[5280,33942,33943],{"class":5282,"line":5378},[5280,33944,5313],{"emptyLinePlaceholder":5312},[5280,33946,33947],{"class":5282,"line":5391},[5280,33948,33949],{"class":5286},"-- Тригер для автоматичного оновлення updated_at\n",[5280,33951,33952,33955,33958,33961],{"class":5282,"line":5705},[5280,33953,33954],{"class":5619},"CREATE OR REPLACE",[5280,33956,33957],{"class":5619}," FUNCTION",[5280,33959,33960],{"class":5293}," update_updated_at_column",[5280,33962,6039],{"class":5452},[5280,33964,33965,33968,33971,33974],{"class":5282,"line":5820},[5280,33966,33967],{"class":5619},"RETURNS",[5280,33969,33970],{"class":5452}," TRIGGER ",[5280,33972,33973],{"class":5619},"AS",[5280,33975,33976],{"class":5452}," $$\n",[5280,33978,33979],{"class":5282,"line":5854},[5280,33980,33981],{"class":5619},"BEGIN\n",[5280,33983,33984,33987,33990],{"class":5282,"line":5859},[5280,33985,33986],{"class":5452},"    NEW.updated_at = ",[5280,33988,33989],{"class":5619},"NOW",[5280,33991,13407],{"class":5452},[5280,33993,33994,33997],{"class":5282,"line":5871},[5280,33995,33996],{"class":5619},"    RETURN",[5280,33998,33999],{"class":5452}," NEW;\n",[5280,34001,34002,34005],{"class":5282,"line":5895},[5280,34003,34004],{"class":5619},"END",[5280,34006,13512],{"class":5452},[5280,34008,34009,34012,34015,34018],{"class":5282,"line":5916},[5280,34010,34011],{"class":5452},"$$ ",[5280,34013,34014],{"class":5619},"language",[5280,34016,34017],{"class":5297}," 'plpgsql'",[5280,34019,13512],{"class":5452},[5280,34021,34022],{"class":5282,"line":5921},[5280,34023,5313],{"emptyLinePlaceholder":5312},[5280,34025,34026,34028,34031,34034],{"class":5282,"line":5939},[5280,34027,33791],{"class":5619},[5280,34029,34030],{"class":5619}," TRIGGER",[5280,34032,34033],{"class":5293}," update_users_updated_at",[5280,34035,13432],{"class":5452},[5280,34037,34038,34041,34044,34046],{"class":5282,"line":5973},[5280,34039,34040],{"class":5619},"    BEFORE",[5280,34042,34043],{"class":5619}," UPDATE",[5280,34045,33937],{"class":5619},[5280,34047,34048],{"class":5452}," users \n",[5280,34050,34051,34054,34057,34060],{"class":5282,"line":5979},[5280,34052,34053],{"class":5619},"    FOR",[5280,34055,34056],{"class":5452}," EACH ",[5280,34058,34059],{"class":5619},"ROW",[5280,34061,13432],{"class":5452},[5280,34063,34064,34067,34069],{"class":5282,"line":5984},[5280,34065,34066],{"class":5619},"    EXECUTE",[5280,34068,33957],{"class":5619},[5280,34070,34071],{"class":5452}," update_updated_at_column();\n",[5271,34073,34075],{"className":33778,"code":34074,"language":23784,"meta":5276,"style":5276},"-- migrations\u002F001_create_users_table.down.sql\nDROP TRIGGER IF EXISTS update_users_updated_at ON users;\nDROP FUNCTION IF EXISTS update_updated_at_column();\nDROP INDEX IF EXISTS idx_users_email;\nDROP TABLE IF EXISTS users;\n",[5215,34076,34077,34082,34102,34114,34127],{"__ignoreMap":5276},[5280,34078,34079],{"class":5282,"line":5283},[5280,34080,34081],{"class":5286},"-- migrations\u002F001_create_users_table.down.sql\n",[5280,34083,34084,34087,34089,34091,34093,34096,34099],{"class":5282,"line":5290},[5280,34085,34086],{"class":5619},"DROP",[5280,34088,34030],{"class":5619},[5280,34090,33797],{"class":5619},[5280,34092,33803],{"class":5619},[5280,34094,34095],{"class":5452}," update_users_updated_at ",[5280,34097,34098],{"class":5619},"ON",[5280,34100,34101],{"class":5452}," users;\n",[5280,34103,34104,34106,34108,34110,34112],{"class":5282,"line":5301},[5280,34105,34086],{"class":5619},[5280,34107,33957],{"class":5619},[5280,34109,33797],{"class":5619},[5280,34111,33803],{"class":5619},[5280,34113,34071],{"class":5452},[5280,34115,34116,34118,34120,34122,34124],{"class":5282,"line":5309},[5280,34117,34086],{"class":5619},[5280,34119,33931],{"class":5619},[5280,34121,33797],{"class":5619},[5280,34123,33803],{"class":5619},[5280,34125,34126],{"class":5452}," idx_users_email;\n",[5280,34128,34129,34131,34133,34135,34137],{"class":5282,"line":5316},[5280,34130,34086],{"class":5619},[5280,34132,33794],{"class":5619},[5280,34134,33797],{"class":5619},[5280,34136,33803],{"class":5619},[5280,34138,34101],{"class":5452},[5266,34140,34142],{"id":34141},"застосування-міграцій","Застосування міграцій",[5262,34144,34145],{},"Встановимо інструмент для міграцій:",[5271,34147,34149],{"className":5273,"code":34148,"language":5275,"meta":5276,"style":5276},"go install -tags 'postgres' github.com\u002Fgolang-migrate\u002Fmigrate\u002Fv4\u002Fcmd\u002Fmigrate@latest\n",[5215,34150,34151],{"__ignoreMap":5276},[5280,34152,34153,34155,34158,34161,34164],{"class":5282,"line":5283},[5280,34154,5325],{"class":5293},[5280,34156,34157],{"class":5297}," install",[5280,34159,34160],{"class":5619}," -tags",[5280,34162,34163],{"class":5297}," 'postgres'",[5280,34165,34166],{"class":5297}," github.com\u002Fgolang-migrate\u002Fmigrate\u002Fv4\u002Fcmd\u002Fmigrate@latest\n",[5262,34168,34169],{},"Запуск міграцій:",[34171,34172,34173,34202,34225],"tabs",{},[34174,34175,34177],"tabs-item",{"label":34176},"Застосувати міграції",[5271,34178,34180],{"className":5273,"code":34179,"language":5275,"meta":5276,"style":5276},"migrate -path migrations -database \"postgres:\u002F\u002Fuser:password@localhost:5432\u002Fmydb?sslmode=disable\" up\n",[5215,34181,34182],{"__ignoreMap":5276},[5280,34183,34184,34187,34190,34193,34196,34199],{"class":5282,"line":5283},[5280,34185,34186],{"class":5293},"migrate",[5280,34188,34189],{"class":5619}," -path",[5280,34191,34192],{"class":5297}," migrations",[5280,34194,34195],{"class":5619}," -database",[5280,34197,34198],{"class":5297}," \"postgres:\u002F\u002Fuser:password@localhost:5432\u002Fmydb?sslmode=disable\"",[5280,34200,34201],{"class":5297}," up\n",[34174,34203,34205],{"label":34204},"Відкотити міграції",[5271,34206,34208],{"className":5273,"code":34207,"language":5275,"meta":5276,"style":5276},"migrate -path migrations -database \"postgres:\u002F\u002Fuser:password@localhost:5432\u002Fmydb?sslmode=disable\" down\n",[5215,34209,34210],{"__ignoreMap":5276},[5280,34211,34212,34214,34216,34218,34220,34222],{"class":5282,"line":5283},[5280,34213,34186],{"class":5293},[5280,34215,34189],{"class":5619},[5280,34217,34192],{"class":5297},[5280,34219,34195],{"class":5619},[5280,34221,34198],{"class":5297},[5280,34223,34224],{"class":5297}," down\n",[34174,34226,34228,34362,34365],{"label":34227},"Docker Compose для PostgreSQL",[5271,34229,34233],{"className":34230,"code":34231,"language":34232,"meta":5276,"style":5276},"language-yaml shiki shiki-themes light-plus dark-plus dark-plus","# docker-compose.yml\nversion: '3.8'\n\nservices:\n  postgres:\n    image: postgres:16-alpine\n    environment:\n      POSTGRES_USER: myuser\n      POSTGRES_PASSWORD: mypass\n      POSTGRES_DB: mydb\n    ports:\n      - \"5432:5432\"\n    volumes:\n      - postgres_data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n\nvolumes:\n  postgres_data:\n","yaml",[5215,34234,34235,34240,34250,34254,34261,34268,34278,34285,34295,34305,34315,34322,34330,34337,34344,34348,34355],{"__ignoreMap":5276},[5280,34236,34237],{"class":5282,"line":5283},[5280,34238,34239],{"class":5286},"# docker-compose.yml\n",[5280,34241,34242,34245,34247],{"class":5282,"line":5290},[5280,34243,34244],{"class":11729},"version",[5280,34246,6790],{"class":5452},[5280,34248,34249],{"class":13289},"'3.8'\n",[5280,34251,34252],{"class":5282,"line":5301},[5280,34253,5313],{"emptyLinePlaceholder":5312},[5280,34255,34256,34259],{"class":5282,"line":5309},[5280,34257,34258],{"class":11729},"services",[5280,34260,6127],{"class":5452},[5280,34262,34263,34266],{"class":5282,"line":5316},[5280,34264,34265],{"class":11729},"  postgres",[5280,34267,6127],{"class":5452},[5280,34269,34270,34273,34275],{"class":5282,"line":5322},[5280,34271,34272],{"class":11729},"    image",[5280,34274,6790],{"class":5452},[5280,34276,34277],{"class":13289},"postgres:16-alpine\n",[5280,34279,34280,34283],{"class":5282,"line":5336},[5280,34281,34282],{"class":11729},"    environment",[5280,34284,6127],{"class":5452},[5280,34286,34287,34290,34292],{"class":5282,"line":5341},[5280,34288,34289],{"class":11729},"      POSTGRES_USER",[5280,34291,6790],{"class":5452},[5280,34293,34294],{"class":13289},"myuser\n",[5280,34296,34297,34300,34302],{"class":5282,"line":5347},[5280,34298,34299],{"class":11729},"      POSTGRES_PASSWORD",[5280,34301,6790],{"class":5452},[5280,34303,34304],{"class":13289},"mypass\n",[5280,34306,34307,34310,34312],{"class":5282,"line":5358},[5280,34308,34309],{"class":11729},"      POSTGRES_DB",[5280,34311,6790],{"class":5452},[5280,34313,34314],{"class":13289},"mydb\n",[5280,34316,34317,34320],{"class":5282,"line":5368},[5280,34318,34319],{"class":11729},"    ports",[5280,34321,6127],{"class":5452},[5280,34323,34324,34327],{"class":5282,"line":5378},[5280,34325,34326],{"class":5452},"      - ",[5280,34328,34329],{"class":5297},"\"5432:5432\"\n",[5280,34331,34332,34335],{"class":5282,"line":5391},[5280,34333,34334],{"class":11729},"    volumes",[5280,34336,6127],{"class":5452},[5280,34338,34339,34341],{"class":5282,"line":5705},[5280,34340,34326],{"class":5452},[5280,34342,34343],{"class":13289},"postgres_data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n",[5280,34345,34346],{"class":5282,"line":5820},[5280,34347,5313],{"emptyLinePlaceholder":5312},[5280,34349,34350,34353],{"class":5282,"line":5854},[5280,34351,34352],{"class":11729},"volumes",[5280,34354,6127],{"class":5452},[5280,34356,34357,34360],{"class":5282,"line":5859},[5280,34358,34359],{"class":11729},"  postgres_data",[5280,34361,6127],{"class":5452},[5262,34363,34364],{},"Запуск:",[5271,34366,34368],{"className":5273,"code":34367,"language":5275,"meta":5276,"style":5276},"docker compose up -d\n",[5215,34369,34370],{"__ignoreMap":5276},[5280,34371,34372,34375,34378,34381],{"class":5282,"line":5283},[5280,34373,34374],{"class":5293},"docker",[5280,34376,34377],{"class":5297}," compose",[5280,34379,34380],{"class":5297}," up",[5280,34382,34383],{"class":5619}," -d\n",[5254,34385],{},[5257,34387,34389],{"id":34388},"end-to-end-повний-приклад-від-створення-до-запуску","End-to-End: повний приклад від створення до запуску",[5262,34391,34392],{},"Тепер об'єднаємо все, що ми вивчили, в один повний робочий проєкт.",[5266,34394,34396],{"id":34395},"крок-1-структура-проєкту","Крок 1: Структура проєкту",[5262,34398,34399],{},"Фінальна структура має виглядати так:",[32087,34401,34402],{},[5271,34403,34406],{"className":32091,"code":34404,"filename":34405,"language":32094,"meta":5276,"style":5276},"myapp\u002F\n├── cmd\u002F\n│   └── server\u002F\n│       └── main.go\n├── internal\u002F\n│   ├── domain\u002F\n│   │   ├── user.go\n│   │   ├── user_repository.go\n│   │   └── errors.go\n│   ├── usecase\u002F\n│   │   ├── user_usecase.go\n│   │   └── user_usecase_test.go\n│   ├── repository\u002F\n│   │   ├── memory\u002F\n│   │   │   └── user_repository.go\n│   │   └── postgres\u002F\n│   │       └── user_repository.go\n│   └── delivery\u002F\n│       └── http\u002F\n│           ├── user_handler.go\n│           └── user_handler_test.go\n├── middleware\u002F\n│   ├── logger.go\n│   ├── recovery.go\n│   ├── cors.go\n│   ├── ratelimit.go\n│   └── auth.go\n├── migrations\u002F\n│   ├── 001_create_users_table.up.sql\n│   └── 001_create_users_table.down.sql\n├── docker-compose.yml\n├── Makefile\n├── go.mod\n└── go.sum\n","Структура myapp\u002F",[5215,34407,34408,34412,34416,34420,34425,34430,34435,34440,34445,34450,34455,34460,34465,34470,34474,34479,34483,34488,34493,34497,34502,34507,34512,34517,34522,34527,34532,34537,34542,34546,34550,34555,34560,34564],{"__ignoreMap":5276},[5280,34409,34410],{"class":5282,"line":5283},[5280,34411,32101],{},[5280,34413,34414],{"class":5282,"line":5290},[5280,34415,32106],{},[5280,34417,34418],{"class":5282,"line":5301},[5280,34419,32111],{},[5280,34421,34422],{"class":5282,"line":5309},[5280,34423,34424],{},"│       └── main.go\n",[5280,34426,34427],{"class":5282,"line":5316},[5280,34428,34429],{},"├── internal\u002F\n",[5280,34431,34432],{"class":5282,"line":5322},[5280,34433,34434],{},"│   ├── domain\u002F\n",[5280,34436,34437],{"class":5282,"line":5336},[5280,34438,34439],{},"│   │   ├── user.go\n",[5280,34441,34442],{"class":5282,"line":5341},[5280,34443,34444],{},"│   │   ├── user_repository.go\n",[5280,34446,34447],{"class":5282,"line":5347},[5280,34448,34449],{},"│   │   └── errors.go\n",[5280,34451,34452],{"class":5282,"line":5358},[5280,34453,34454],{},"│   ├── usecase\u002F\n",[5280,34456,34457],{"class":5282,"line":5368},[5280,34458,34459],{},"│   │   ├── user_usecase.go\n",[5280,34461,34462],{"class":5282,"line":5378},[5280,34463,34464],{},"│   │   └── user_usecase_test.go\n",[5280,34466,34467],{"class":5282,"line":5391},[5280,34468,34469],{},"│   ├── repository\u002F\n",[5280,34471,34472],{"class":5282,"line":5705},[5280,34473,32180],{},[5280,34475,34476],{"class":5282,"line":5820},[5280,34477,34478],{},"│   │   │   └── user_repository.go\n",[5280,34480,34481],{"class":5282,"line":5854},[5280,34482,32190],{},[5280,34484,34485],{"class":5282,"line":5859},[5280,34486,34487],{},"│   │       └── user_repository.go\n",[5280,34489,34490],{"class":5282,"line":5871},[5280,34491,34492],{},"│   └── delivery\u002F\n",[5280,34494,34495],{"class":5282,"line":5895},[5280,34496,32209],{},[5280,34498,34499],{"class":5282,"line":5916},[5280,34500,34501],{},"│           ├── user_handler.go\n",[5280,34503,34504],{"class":5282,"line":5921},[5280,34505,34506],{},"│           └── user_handler_test.go\n",[5280,34508,34509],{"class":5282,"line":5939},[5280,34510,34511],{},"├── middleware\u002F\n",[5280,34513,34514],{"class":5282,"line":5973},[5280,34515,34516],{},"│   ├── logger.go\n",[5280,34518,34519],{"class":5282,"line":5979},[5280,34520,34521],{},"│   ├── recovery.go\n",[5280,34523,34524],{"class":5282,"line":5984},[5280,34525,34526],{},"│   ├── cors.go\n",[5280,34528,34529],{"class":5282,"line":6023},[5280,34530,34531],{},"│   ├── ratelimit.go\n",[5280,34533,34534],{"class":5282,"line":6042},[5280,34535,34536],{},"│   └── auth.go\n",[5280,34538,34539],{"class":5282,"line":6093},[5280,34540,34541],{},"├── migrations\u002F\n",[5280,34543,34544],{"class":5282,"line":6098},[5280,34545,32263],{},[5280,34547,34548],{"class":5282,"line":6114},[5280,34549,32268],{},[5280,34551,34552],{"class":5282,"line":6130},[5280,34553,34554],{},"├── docker-compose.yml\n",[5280,34556,34557],{"class":5282,"line":6159},[5280,34558,34559],{},"├── Makefile\n",[5280,34561,34562],{"class":5282,"line":6187},[5280,34563,32296],{},[5280,34565,34566],{"class":5282,"line":6193},[5280,34567,34568],{},"└── go.sum\n",[5266,34570,34572],{"id":34571},"крок-2-створення-makefile","Крок 2: Створення Makefile",[5262,34574,34575],{},"Створимо Makefile для автоматизації:",[5271,34577,34581],{"className":34578,"code":34579,"language":34580,"meta":5276,"style":5276},"language-makefile shiki shiki-themes light-plus dark-plus dark-plus","# Makefile\n.PHONY: help run test migrate-up migrate-down docker-up docker-down clean\n\nhelp: ## Показати довідку\n    @echo \"Доступні команди:\"\n    @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = \":.*?## \"}; {printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2}'\n\nrun: ## Запустити сервер (in-memory)\n    @echo \"🚀 Запуск сервера...\"\n    go run cmd\u002Fserver\u002Fmain.go\n\nrun-postgres: ## Запустити сервер (PostgreSQL)\n    @echo \"🚀 Запуск сервера з PostgreSQL...\"\n    USE_POSTGRES=true go run cmd\u002Fserver\u002Fmain.go\n\ntest: ## Запустити всі тести\n    @echo \"🧪 Запуск тестів...\"\n    go test -v -cover .\u002F...\n\ntest-coverage: ## Запустити тести з покриттям\n    @echo \"📊 Звіт покриття тестами...\"\n    go test -coverprofile=coverage.out .\u002F...\n    go tool cover -html=coverage.out\n\ndocker-up: ## Запустити PostgreSQL через Docker\n    @echo \"🐳 Запуск PostgreSQL...\"\n    docker compose up -d\n    @sleep 2\n    @echo \"✅ PostgreSQL готовий на localhost:5432\"\n\ndocker-down: ## Зупинити Docker контейнери\n    @echo \"🛑 Зупинка контейнерів...\"\n    docker compose down\n\nmigrate-up: ## Застосувати міграції\n    @echo \"⬆️  Застосування міграцій...\"\n    migrate -path migrations -database \"postgres:\u002F\u002Fmyuser:mypass@localhost:5432\u002Fmydb?sslmode=disable\" up\n\nmigrate-down: ## Відкотити міграції\n    @echo \"⬇️  Відкат міграцій...\"\n    migrate -path migrations -database \"postgres:\u002F\u002Fmyuser:mypass@localhost:5432\u002Fmydb?sslmode=disable\" down\n\nclean: ## Очистити згенеровані файли\n    @echo \"🧹 Очищення...\"\n    rm -f coverage.out\n    go clean -testcache\n\ndeps: ## Встановити залежності\n    @echo \"📦 Встановлення залежностей...\"\n    go mod download\n    go mod tidy\n\nbuild: ## Зібрати бінарний файл\n    @echo \"🔨 Збірка...\"\n    go build -o bin\u002Fserver cmd\u002Fserver\u002Fmain.go\n    @echo \"✅ Бінарний файл: bin\u002Fserver\"\n","makefile",[5215,34582,34583,34588,34593,34597,34602,34607,34612,34616,34621,34626,34631,34635,34640,34645,34650,34654,34659,34664,34669,34673,34678,34683,34688,34693,34697,34702,34707,34712,34717,34722,34726,34731,34736,34741,34745,34750,34755,34760,34764,34769,34774,34779,34783,34788,34793,34798,34803,34807,34812,34817,34822,34827,34831,34836,34841,34846],{"__ignoreMap":5276},[5280,34584,34585],{"class":5282,"line":5283},[5280,34586,34587],{},"# Makefile\n",[5280,34589,34590],{"class":5282,"line":5290},[5280,34591,34592],{},".PHONY: help run test migrate-up migrate-down docker-up docker-down clean\n",[5280,34594,34595],{"class":5282,"line":5301},[5280,34596,5313],{"emptyLinePlaceholder":5312},[5280,34598,34599],{"class":5282,"line":5309},[5280,34600,34601],{},"help: ## Показати довідку\n",[5280,34603,34604],{"class":5282,"line":5316},[5280,34605,34606],{},"    @echo \"Доступні команди:\"\n",[5280,34608,34609],{"class":5282,"line":5322},[5280,34610,34611],{},"    @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = \":.*?## \"}; {printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2}'\n",[5280,34613,34614],{"class":5282,"line":5336},[5280,34615,5313],{"emptyLinePlaceholder":5312},[5280,34617,34618],{"class":5282,"line":5341},[5280,34619,34620],{},"run: ## Запустити сервер (in-memory)\n",[5280,34622,34623],{"class":5282,"line":5347},[5280,34624,34625],{},"    @echo \"🚀 Запуск сервера...\"\n",[5280,34627,34628],{"class":5282,"line":5358},[5280,34629,34630],{},"    go run cmd\u002Fserver\u002Fmain.go\n",[5280,34632,34633],{"class":5282,"line":5368},[5280,34634,5313],{"emptyLinePlaceholder":5312},[5280,34636,34637],{"class":5282,"line":5378},[5280,34638,34639],{},"run-postgres: ## Запустити сервер (PostgreSQL)\n",[5280,34641,34642],{"class":5282,"line":5391},[5280,34643,34644],{},"    @echo \"🚀 Запуск сервера з PostgreSQL...\"\n",[5280,34646,34647],{"class":5282,"line":5705},[5280,34648,34649],{},"    USE_POSTGRES=true go run cmd\u002Fserver\u002Fmain.go\n",[5280,34651,34652],{"class":5282,"line":5820},[5280,34653,5313],{"emptyLinePlaceholder":5312},[5280,34655,34656],{"class":5282,"line":5854},[5280,34657,34658],{},"test: ## Запустити всі тести\n",[5280,34660,34661],{"class":5282,"line":5859},[5280,34662,34663],{},"    @echo \"🧪 Запуск тестів...\"\n",[5280,34665,34666],{"class":5282,"line":5871},[5280,34667,34668],{},"    go test -v -cover .\u002F...\n",[5280,34670,34671],{"class":5282,"line":5895},[5280,34672,5313],{"emptyLinePlaceholder":5312},[5280,34674,34675],{"class":5282,"line":5916},[5280,34676,34677],{},"test-coverage: ## Запустити тести з покриттям\n",[5280,34679,34680],{"class":5282,"line":5921},[5280,34681,34682],{},"    @echo \"📊 Звіт покриття тестами...\"\n",[5280,34684,34685],{"class":5282,"line":5939},[5280,34686,34687],{},"    go test -coverprofile=coverage.out .\u002F...\n",[5280,34689,34690],{"class":5282,"line":5973},[5280,34691,34692],{},"    go tool cover -html=coverage.out\n",[5280,34694,34695],{"class":5282,"line":5979},[5280,34696,5313],{"emptyLinePlaceholder":5312},[5280,34698,34699],{"class":5282,"line":5984},[5280,34700,34701],{},"docker-up: ## Запустити PostgreSQL через Docker\n",[5280,34703,34704],{"class":5282,"line":6023},[5280,34705,34706],{},"    @echo \"🐳 Запуск PostgreSQL...\"\n",[5280,34708,34709],{"class":5282,"line":6042},[5280,34710,34711],{},"    docker compose up -d\n",[5280,34713,34714],{"class":5282,"line":6093},[5280,34715,34716],{},"    @sleep 2\n",[5280,34718,34719],{"class":5282,"line":6098},[5280,34720,34721],{},"    @echo \"✅ PostgreSQL готовий на localhost:5432\"\n",[5280,34723,34724],{"class":5282,"line":6114},[5280,34725,5313],{"emptyLinePlaceholder":5312},[5280,34727,34728],{"class":5282,"line":6130},[5280,34729,34730],{},"docker-down: ## Зупинити Docker контейнери\n",[5280,34732,34733],{"class":5282,"line":6159},[5280,34734,34735],{},"    @echo \"🛑 Зупинка контейнерів...\"\n",[5280,34737,34738],{"class":5282,"line":6187},[5280,34739,34740],{},"    docker compose down\n",[5280,34742,34743],{"class":5282,"line":6193},[5280,34744,5313],{"emptyLinePlaceholder":5312},[5280,34746,34747],{"class":5282,"line":6207},[5280,34748,34749],{},"migrate-up: ## Застосувати міграції\n",[5280,34751,34752],{"class":5282,"line":6221},[5280,34753,34754],{},"    @echo \"⬆️  Застосування міграцій...\"\n",[5280,34756,34757],{"class":5282,"line":6233},[5280,34758,34759],{},"    migrate -path migrations -database \"postgres:\u002F\u002Fmyuser:mypass@localhost:5432\u002Fmydb?sslmode=disable\" up\n",[5280,34761,34762],{"class":5282,"line":6245},[5280,34763,5313],{"emptyLinePlaceholder":5312},[5280,34765,34766],{"class":5282,"line":6251},[5280,34767,34768],{},"migrate-down: ## Відкотити міграції\n",[5280,34770,34771],{"class":5282,"line":6256},[5280,34772,34773],{},"    @echo \"⬇️  Відкат міграцій...\"\n",[5280,34775,34776],{"class":5282,"line":6308},[5280,34777,34778],{},"    migrate -path migrations -database \"postgres:\u002F\u002Fmyuser:mypass@localhost:5432\u002Fmydb?sslmode=disable\" down\n",[5280,34780,34781],{"class":5282,"line":6339},[5280,34782,5313],{"emptyLinePlaceholder":5312},[5280,34784,34785],{"class":5282,"line":6371},[5280,34786,34787],{},"clean: ## Очистити згенеровані файли\n",[5280,34789,34790],{"class":5282,"line":6409},[5280,34791,34792],{},"    @echo \"🧹 Очищення...\"\n",[5280,34794,34795],{"class":5282,"line":6415},[5280,34796,34797],{},"    rm -f coverage.out\n",[5280,34799,34800],{"class":5282,"line":6420},[5280,34801,34802],{},"    go clean -testcache\n",[5280,34804,34805],{"class":5282,"line":6425},[5280,34806,5313],{"emptyLinePlaceholder":5312},[5280,34808,34809],{"class":5282,"line":6450},[5280,34810,34811],{},"deps: ## Встановити залежності\n",[5280,34813,34814],{"class":5282,"line":6471},[5280,34815,34816],{},"    @echo \"📦 Встановлення залежностей...\"\n",[5280,34818,34819],{"class":5282,"line":6507},[5280,34820,34821],{},"    go mod download\n",[5280,34823,34824],{"class":5282,"line":6512},[5280,34825,34826],{},"    go mod tidy\n",[5280,34828,34829],{"class":5282,"line":6520},[5280,34830,5313],{"emptyLinePlaceholder":5312},[5280,34832,34833],{"class":5282,"line":6550},[5280,34834,34835],{},"build: ## Зібрати бінарний файл\n",[5280,34837,34838],{"class":5282,"line":6556},[5280,34839,34840],{},"    @echo \"🔨 Збірка...\"\n",[5280,34842,34843],{"class":5282,"line":6561},[5280,34844,34845],{},"    go build -o bin\u002Fserver cmd\u002Fserver\u002Fmain.go\n",[5280,34847,34848],{"class":5282,"line":6621},[5280,34849,34850],{},"    @echo \"✅ Бінарний файл: bin\u002Fserver\"\n",[5266,34852,34854],{"id":34853},"крок-3-запуск-всієї-системи","Крок 3: Запуск всієї системи",[5262,34856,34857],{},[5227,34858,34859],{},"Варіант 1: In-memory (без бази даних)",[5271,34861,34863],{"className":5273,"code":34862,"language":5275,"meta":5276,"style":5276},"# Встановити залежності\nmake deps\n\n# Запустити сервер\nmake run\n",[5215,34864,34865,34870,34877,34881,34886],{"__ignoreMap":5276},[5280,34866,34867],{"class":5282,"line":5283},[5280,34868,34869],{"class":5286},"# Встановити залежності\n",[5280,34871,34872,34874],{"class":5282,"line":5290},[5280,34873,5832],{"class":5293},[5280,34875,34876],{"class":5297}," deps\n",[5280,34878,34879],{"class":5282,"line":5301},[5280,34880,5313],{"emptyLinePlaceholder":5312},[5280,34882,34883],{"class":5282,"line":5309},[5280,34884,34885],{"class":5286},"# Запустити сервер\n",[5280,34887,34888,34890],{"class":5282,"line":5316},[5280,34889,5832],{"class":5293},[5280,34891,34892],{"class":5297}," run\n",[5262,34894,34895],{},[5227,34896,34897],{},"Варіант 2: З PostgreSQL",[5271,34899,34901],{"className":5273,"code":34900,"language":5275,"meta":5276,"style":5276},"# 1. Запустити PostgreSQL\nmake docker-up\n\n# 2. Застосувати міграції\nmake migrate-up\n\n# 3. Запустити сервер\nmake run-postgres\n",[5215,34902,34903,34908,34915,34919,34924,34931,34935,34940],{"__ignoreMap":5276},[5280,34904,34905],{"class":5282,"line":5283},[5280,34906,34907],{"class":5286},"# 1. Запустити PostgreSQL\n",[5280,34909,34910,34912],{"class":5282,"line":5290},[5280,34911,5832],{"class":5293},[5280,34913,34914],{"class":5297}," docker-up\n",[5280,34916,34917],{"class":5282,"line":5301},[5280,34918,5313],{"emptyLinePlaceholder":5312},[5280,34920,34921],{"class":5282,"line":5309},[5280,34922,34923],{"class":5286},"# 2. Застосувати міграції\n",[5280,34925,34926,34928],{"class":5282,"line":5316},[5280,34927,5832],{"class":5293},[5280,34929,34930],{"class":5297}," migrate-up\n",[5280,34932,34933],{"class":5282,"line":5322},[5280,34934,5313],{"emptyLinePlaceholder":5312},[5280,34936,34937],{"class":5282,"line":5336},[5280,34938,34939],{"class":5286},"# 3. Запустити сервер\n",[5280,34941,34942,34944],{"class":5282,"line":5341},[5280,34943,5832],{"class":5293},[5280,34945,34946],{"class":5297}," run-postgres\n",[5266,34948,34950],{"id":34949},"крок-4-тестування-api","Крок 4: Тестування API",[34171,34952,34953,35093],{},[34174,34954,34956],{"label":34955},"curl",[5271,34957,34959],{"className":5273,"code":34958,"language":5275,"meta":5276,"style":5276},"# Реєстрація користувача\ncurl -X POST http:\u002F\u002Flocalhost:8080\u002Fapi\u002Fusers\u002Fregister \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"name\": \"John Doe\",\n    \"email\": \"john@example.com\",\n    \"password\": \"password123\"\n  }'\n\n# Вхід\ncurl -X POST http:\u002F\u002Flocalhost:8080\u002Fapi\u002Fusers\u002Flogin \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"email\": \"john@example.com\",\n    \"password\": \"password123\"\n  }'\n\n# Отримати профіль (потрібен токен з попереднього запиту)\ncurl http:\u002F\u002Flocalhost:8080\u002Fapi\u002Fusers\u002Fme \\\n  -H \"Authorization: Bearer YOUR_TOKEN_HERE\"\n",[5215,34960,34961,34966,34982,34992,35000,35005,35010,35015,35020,35024,35029,35042,35050,35056,35060,35064,35068,35072,35077,35086],{"__ignoreMap":5276},[5280,34962,34963],{"class":5282,"line":5283},[5280,34964,34965],{"class":5286},"# Реєстрація користувача\n",[5280,34967,34968,34970,34973,34976,34979],{"class":5282,"line":5290},[5280,34969,34955],{"class":5293},[5280,34971,34972],{"class":5619}," -X",[5280,34974,34975],{"class":5297}," POST",[5280,34977,34978],{"class":5297}," http:\u002F\u002Flocalhost:8080\u002Fapi\u002Fusers\u002Fregister",[5280,34980,34981],{"class":10647}," \\\n",[5280,34983,34984,34987,34990],{"class":5282,"line":5301},[5280,34985,34986],{"class":5619},"  -H",[5280,34988,34989],{"class":5297}," \"Content-Type: application\u002Fjson\"",[5280,34991,34981],{"class":10647},[5280,34993,34994,34997],{"class":5282,"line":5309},[5280,34995,34996],{"class":5619},"  -d",[5280,34998,34999],{"class":5297}," '{\n",[5280,35001,35002],{"class":5282,"line":5316},[5280,35003,35004],{"class":5297},"    \"name\": \"John Doe\",\n",[5280,35006,35007],{"class":5282,"line":5322},[5280,35008,35009],{"class":5297},"    \"email\": \"john@example.com\",\n",[5280,35011,35012],{"class":5282,"line":5336},[5280,35013,35014],{"class":5297},"    \"password\": \"password123\"\n",[5280,35016,35017],{"class":5282,"line":5341},[5280,35018,35019],{"class":5297},"  }'\n",[5280,35021,35022],{"class":5282,"line":5347},[5280,35023,5313],{"emptyLinePlaceholder":5312},[5280,35025,35026],{"class":5282,"line":5358},[5280,35027,35028],{"class":5286},"# Вхід\n",[5280,35030,35031,35033,35035,35037,35040],{"class":5282,"line":5368},[5280,35032,34955],{"class":5293},[5280,35034,34972],{"class":5619},[5280,35036,34975],{"class":5297},[5280,35038,35039],{"class":5297}," http:\u002F\u002Flocalhost:8080\u002Fapi\u002Fusers\u002Flogin",[5280,35041,34981],{"class":10647},[5280,35043,35044,35046,35048],{"class":5282,"line":5378},[5280,35045,34986],{"class":5619},[5280,35047,34989],{"class":5297},[5280,35049,34981],{"class":10647},[5280,35051,35052,35054],{"class":5282,"line":5391},[5280,35053,34996],{"class":5619},[5280,35055,34999],{"class":5297},[5280,35057,35058],{"class":5282,"line":5705},[5280,35059,35009],{"class":5297},[5280,35061,35062],{"class":5282,"line":5820},[5280,35063,35014],{"class":5297},[5280,35065,35066],{"class":5282,"line":5854},[5280,35067,35019],{"class":5297},[5280,35069,35070],{"class":5282,"line":5859},[5280,35071,5313],{"emptyLinePlaceholder":5312},[5280,35073,35074],{"class":5282,"line":5871},[5280,35075,35076],{"class":5286},"# Отримати профіль (потрібен токен з попереднього запиту)\n",[5280,35078,35079,35081,35084],{"class":5282,"line":5895},[5280,35080,34955],{"class":5293},[5280,35082,35083],{"class":5297}," http:\u002F\u002Flocalhost:8080\u002Fapi\u002Fusers\u002Fme",[5280,35085,34981],{"class":10647},[5280,35087,35088,35090],{"class":5282,"line":5916},[5280,35089,34986],{"class":5619},[5280,35091,35092],{"class":5297}," \"Authorization: Bearer YOUR_TOKEN_HERE\"\n",[34174,35094,35096],{"label":35095},"httpie",[5271,35097,35099],{"className":5273,"code":35098,"language":5275,"meta":5276,"style":5276},"# Реєстрація\nhttp POST :8080\u002Fapi\u002Fusers\u002Fregister \\\n  name=\"John Doe\" \\\n  email=\"john@example.com\" \\\n  password=\"password123\"\n\n# Вхід\nhttp POST :8080\u002Fapi\u002Fusers\u002Flogin \\\n  email=\"john@example.com\" \\\n  password=\"password123\"\n\n# Профіль\nhttp :8080\u002Fapi\u002Fusers\u002Fme \\\n  \"Authorization: Bearer YOUR_TOKEN\"\n",[5215,35100,35101,35106,35117,35124,35131,35136,35140,35144,35155,35161,35165,35169,35174,35183],{"__ignoreMap":5276},[5280,35102,35103],{"class":5282,"line":5283},[5280,35104,35105],{"class":5286},"# Реєстрація\n",[5280,35107,35108,35110,35112,35115],{"class":5282,"line":5290},[5280,35109,5952],{"class":5293},[5280,35111,34975],{"class":5297},[5280,35113,35114],{"class":5297}," :8080\u002Fapi\u002Fusers\u002Fregister",[5280,35116,34981],{"class":10647},[5280,35118,35119,35122],{"class":5282,"line":5301},[5280,35120,35121],{"class":5297},"  name=\"John Doe\"",[5280,35123,34981],{"class":10647},[5280,35125,35126,35129],{"class":5282,"line":5309},[5280,35127,35128],{"class":5297},"  email=\"john@example.com\"",[5280,35130,34981],{"class":10647},[5280,35132,35133],{"class":5282,"line":5316},[5280,35134,35135],{"class":5297},"  password=\"password123\"\n",[5280,35137,35138],{"class":5282,"line":5322},[5280,35139,5313],{"emptyLinePlaceholder":5312},[5280,35141,35142],{"class":5282,"line":5336},[5280,35143,35028],{"class":5286},[5280,35145,35146,35148,35150,35153],{"class":5282,"line":5341},[5280,35147,5952],{"class":5293},[5280,35149,34975],{"class":5297},[5280,35151,35152],{"class":5297}," :8080\u002Fapi\u002Fusers\u002Flogin",[5280,35154,34981],{"class":10647},[5280,35156,35157,35159],{"class":5282,"line":5347},[5280,35158,35128],{"class":5297},[5280,35160,34981],{"class":10647},[5280,35162,35163],{"class":5282,"line":5358},[5280,35164,35135],{"class":5297},[5280,35166,35167],{"class":5282,"line":5368},[5280,35168,5313],{"emptyLinePlaceholder":5312},[5280,35170,35171],{"class":5282,"line":5378},[5280,35172,35173],{"class":5286},"# Профіль\n",[5280,35175,35176,35178,35181],{"class":5282,"line":5391},[5280,35177,5952],{"class":5293},[5280,35179,35180],{"class":5297}," :8080\u002Fapi\u002Fusers\u002Fme",[5280,35182,34981],{"class":10647},[5280,35184,35185],{"class":5282,"line":5705},[5280,35186,35187],{"class":5297},"  \"Authorization: Bearer YOUR_TOKEN\"\n",[5266,35189,35191],{"id":35190},"крок-5-запуск-тестів","Крок 5: Запуск тестів",[5271,35193,35195],{"className":5273,"code":35194,"language":5275,"meta":5276,"style":5276},"# Всі тести\nmake test\n\n# Тести з покриттям\nmake test-coverage\n",[5215,35196,35197,35202,35209,35213,35218],{"__ignoreMap":5276},[5280,35198,35199],{"class":5282,"line":5283},[5280,35200,35201],{"class":5286},"# Всі тести\n",[5280,35203,35204,35206],{"class":5282,"line":5290},[5280,35205,5832],{"class":5293},[5280,35207,35208],{"class":5297}," test\n",[5280,35210,35211],{"class":5282,"line":5301},[5280,35212,5313],{"emptyLinePlaceholder":5312},[5280,35214,35215],{"class":5282,"line":5309},[5280,35216,35217],{"class":5286},"# Тести з покриттям\n",[5280,35219,35220,35222],{"class":5282,"line":5316},[5280,35221,5832],{"class":5293},[5280,35223,35224],{"class":5297}," test-coverage\n",[8866,35226,35228,35237,35241,35245,35249,35253,35257],{":cursor":8868,"title":35227},"make test",[8871,35229,35231,7648,35234],{"className":35230},[5282],[5280,35232,8878],{"className":35233},[8877],[5227,35235,35227],{"className":35236},[8882],[8871,35238,35240],{"className":35239},[5282],"🧪 Запуск тестів...",[8871,35242,35244],{"className":35243},[5282],"?       myapp\u002Fcmd\u002Fserver        [no test files]",[8871,35246,35248],{"className":35247},[5282],"ok      myapp\u002Finternal\u002Fdomain   0.003s  coverage: 85.7% of statements",[8871,35250,35252],{"className":35251},[5282],"ok      myapp\u002Finternal\u002Fusecase  0.334s  coverage: 92.3% of statements",[8871,35254,35256],{"className":35255},[5282],"ok      myapp\u002Finternal\u002Fdelivery\u002Fhttp    0.002s  coverage: 88.9% of statements",[8871,35258,35260],{"className":35259},[5282],[5280,35261,35263],{"className":35262},[28051,8882],"✅ Всі тести пройшли успішно!",[5254,35265],{},[5257,35267,35269],{"id":35268},"обробка-помилок-через-шари-архітектури","Обробка помилок через шари архітектури",[5266,35271,35273],{"id":35272},"філософія-обробки-помилок-у-go","Філософія обробки помилок у Go",[5262,35275,35276,35277,35280,35281,35283,35284,35287],{},"У Go немає винятків (",[5207,35278,35279],{},"exceptions","). Замість цього помилки повертаються як значення типу ",[5215,35282,9567],{},". Це ",[5227,35285,35286],{},"явний"," підхід, що змушує розробника обробляти помилки на кожному кроці.",[5266,35289,35291],{"id":35290},"доменні-помилки","Доменні помилки",[5262,35293,35294],{},"Створимо власні типи помилок для домену:",[5271,35296,35298],{"className":5411,"code":35297,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fdomain\u002Ferrors.go\npackage domain\n\nimport \"errors\"\n\n\u002F\u002F Базові помилки домену\nvar (\n    \u002F\u002F Помилки користувача\n    ErrUserNotFound       = errors.New(\"user not found\")\n    ErrUserAlreadyExists  = errors.New(\"user already exists\")\n    ErrInvalidCredentials = errors.New(\"invalid credentials\")\n    ErrInvalidEmail       = errors.New(\"invalid email format\")\n    ErrPasswordTooShort   = errors.New(\"password too short\")\n    \n    \u002F\u002F Помилки автентифікації\n    ErrUnauthorized      = errors.New(\"unauthorized\")\n    ErrInvalidToken      = errors.New(\"invalid token\")\n    ErrTokenExpired      = errors.New(\"token expired\")\n    \n    \u002F\u002F Системні помилки\n    ErrInternal          = errors.New(\"internal server error\")\n    ErrDatabaseConnection = errors.New(\"database connection failed\")\n)\n",[5215,35299,35300,35305,35311,35315,35322,35326,35331,35337,35342,35361,35380,35398,35417,35438,35442,35447,35467,35487,35507,35511,35516,35537,35557],{"__ignoreMap":5276},[5280,35301,35302],{"class":5282,"line":5283},[5280,35303,35304],{"class":5286},"\u002F\u002F internal\u002Fdomain\u002Ferrors.go\n",[5280,35306,35307,35309],{"class":5282,"line":5290},[5280,35308,5755],{"class":5619},[5280,35310,21200],{"class":5758},[5280,35312,35313],{"class":5282,"line":5301},[5280,35314,5313],{"emptyLinePlaceholder":5312},[5280,35316,35317,35319],{"class":5282,"line":5309},[5280,35318,5768],{"class":5436},[5280,35320,35321],{"class":5297}," \"errors\"\n",[5280,35323,35324],{"class":5282,"line":5316},[5280,35325,5313],{"emptyLinePlaceholder":5312},[5280,35327,35328],{"class":5282,"line":5322},[5280,35329,35330],{"class":5286},"\u002F\u002F Базові помилки домену\n",[5280,35332,35333,35335],{"class":5282,"line":5336},[5280,35334,5823],{"class":5619},[5280,35336,5453],{"class":5452},[5280,35338,35339],{"class":5282,"line":5341},[5280,35340,35341],{"class":5286},"    \u002F\u002F Помилки користувача\n",[5280,35343,35344,35346,35349,35351,35353,35355,35357,35359],{"class":5282,"line":5347},[5280,35345,21879],{"class":5424},[5280,35347,35348],{"class":5452},"       = ",[5280,35350,20197],{"class":5424},[5280,35352,5218],{"class":5452},[5280,35354,20202],{"class":5293},[5280,35356,5835],{"class":5452},[5280,35358,21893],{"class":5297},[5280,35360,5598],{"class":5452},[5280,35362,35363,35365,35368,35370,35372,35374,35376,35378],{"class":5282,"line":5358},[5280,35364,21900],{"class":5424},[5280,35366,35367],{"class":5452},"  = ",[5280,35369,20197],{"class":5424},[5280,35371,5218],{"class":5452},[5280,35373,20202],{"class":5293},[5280,35375,5835],{"class":5452},[5280,35377,21913],{"class":5297},[5280,35379,5598],{"class":5452},[5280,35381,35382,35384,35386,35388,35390,35392,35394,35396],{"class":5282,"line":5368},[5280,35383,21920],{"class":5424},[5280,35385,5829],{"class":5452},[5280,35387,20197],{"class":5424},[5280,35389,5218],{"class":5452},[5280,35391,20202],{"class":5293},[5280,35393,5835],{"class":5452},[5280,35395,21933],{"class":5297},[5280,35397,5598],{"class":5452},[5280,35399,35400,35403,35405,35407,35409,35411,35413,35415],{"class":5282,"line":5378},[5280,35401,35402],{"class":5424},"    ErrInvalidEmail",[5280,35404,35348],{"class":5452},[5280,35406,20197],{"class":5424},[5280,35408,5218],{"class":5452},[5280,35410,20202],{"class":5293},[5280,35412,5835],{"class":5452},[5280,35414,21484],{"class":5297},[5280,35416,5598],{"class":5452},[5280,35418,35419,35422,35425,35427,35429,35431,35433,35436],{"class":5282,"line":5391},[5280,35420,35421],{"class":5424},"    ErrPasswordTooShort",[5280,35423,35424],{"class":5452},"   = ",[5280,35426,20197],{"class":5424},[5280,35428,5218],{"class":5452},[5280,35430,20202],{"class":5293},[5280,35432,5835],{"class":5452},[5280,35434,35435],{"class":5297},"\"password too short\"",[5280,35437,5598],{"class":5452},[5280,35439,35440],{"class":5282,"line":5705},[5280,35441,6724],{"class":5452},[5280,35443,35444],{"class":5282,"line":5820},[5280,35445,35446],{"class":5286},"    \u002F\u002F Помилки автентифікації\n",[5280,35448,35449,35452,35454,35456,35458,35460,35462,35465],{"class":5282,"line":5854},[5280,35450,35451],{"class":5424},"    ErrUnauthorized",[5280,35453,21882],{"class":5452},[5280,35455,20197],{"class":5424},[5280,35457,5218],{"class":5452},[5280,35459,20202],{"class":5293},[5280,35461,5835],{"class":5452},[5280,35463,35464],{"class":5297},"\"unauthorized\"",[5280,35466,5598],{"class":5452},[5280,35468,35469,35472,35474,35476,35478,35480,35482,35485],{"class":5282,"line":5859},[5280,35470,35471],{"class":5424},"    ErrInvalidToken",[5280,35473,21882],{"class":5452},[5280,35475,20197],{"class":5424},[5280,35477,5218],{"class":5452},[5280,35479,20202],{"class":5293},[5280,35481,5835],{"class":5452},[5280,35483,35484],{"class":5297},"\"invalid token\"",[5280,35486,5598],{"class":5452},[5280,35488,35489,35492,35494,35496,35498,35500,35502,35505],{"class":5282,"line":5871},[5280,35490,35491],{"class":5424},"    ErrTokenExpired",[5280,35493,21882],{"class":5452},[5280,35495,20197],{"class":5424},[5280,35497,5218],{"class":5452},[5280,35499,20202],{"class":5293},[5280,35501,5835],{"class":5452},[5280,35503,35504],{"class":5297},"\"token expired\"",[5280,35506,5598],{"class":5452},[5280,35508,35509],{"class":5282,"line":5895},[5280,35510,6724],{"class":5452},[5280,35512,35513],{"class":5282,"line":5916},[5280,35514,35515],{"class":5286},"    \u002F\u002F Системні помилки\n",[5280,35517,35518,35521,35524,35526,35528,35530,35532,35535],{"class":5282,"line":5921},[5280,35519,35520],{"class":5424},"    ErrInternal",[5280,35522,35523],{"class":5452},"          = ",[5280,35525,20197],{"class":5424},[5280,35527,5218],{"class":5452},[5280,35529,20202],{"class":5293},[5280,35531,5835],{"class":5452},[5280,35533,35534],{"class":5297},"\"internal server error\"",[5280,35536,5598],{"class":5452},[5280,35538,35539,35542,35544,35546,35548,35550,35552,35555],{"class":5282,"line":5939},[5280,35540,35541],{"class":5424},"    ErrDatabaseConnection",[5280,35543,5829],{"class":5452},[5280,35545,20197],{"class":5424},[5280,35547,5218],{"class":5452},[5280,35549,20202],{"class":5293},[5280,35551,5835],{"class":5452},[5280,35553,35554],{"class":5297},"\"database connection failed\"",[5280,35556,5598],{"class":5452},[5280,35558,35559],{"class":5282,"line":5973},[5280,35560,5598],{"class":5452},[5266,35562,35564],{"id":35563},"власні-типи-помилок-з-контекстом","Власні типи помилок з контекстом",[5262,35566,35567],{},"Для складніших випадків створюємо структури помилок:",[5271,35569,35571],{"className":5411,"code":35570,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fdomain\u002Ferrors.go (продовження)\npackage domain\n\nimport \"fmt\"\n\n\u002F\u002F ValidationError — помилка валідації з деталями\ntype ValidationError struct {\n    Field   string\n    Message string\n}\n\nfunc (e *ValidationError) Error() string {\n    return fmt.Sprintf(\"validation error: field '%s' - %s\", e.Field, e.Message)\n}\n\n\u002F\u002F NewValidationError створює нову помилку валідації\nfunc NewValidationError(field, message string) *ValidationError {\n    return &ValidationError{\n        Field:   field,\n        Message: message,\n    }\n}\n\n\u002F\u002F NotFoundError — помилка \"не знайдено\" з деталями\ntype NotFoundError struct {\n    Resource string\n    ID       string\n}\n\nfunc (e *NotFoundError) Error() string {\n    return fmt.Sprintf(\"%s with id '%s' not found\", e.Resource, e.ID)\n}\n",[5215,35572,35573,35578,35584,35588,35595,35599,35604,35615,35623,35630,35634,35638,35662,35707,35711,35715,35720,35744,35754,35765,35776,35780,35784,35788,35793,35804,35811,35817,35821,35825,35848,35891],{"__ignoreMap":5276},[5280,35574,35575],{"class":5282,"line":5283},[5280,35576,35577],{"class":5286},"\u002F\u002F internal\u002Fdomain\u002Ferrors.go (продовження)\n",[5280,35579,35580,35582],{"class":5282,"line":5290},[5280,35581,5755],{"class":5619},[5280,35583,21200],{"class":5758},[5280,35585,35586],{"class":5282,"line":5301},[5280,35587,5313],{"emptyLinePlaceholder":5312},[5280,35589,35590,35592],{"class":5282,"line":5309},[5280,35591,5768],{"class":5436},[5280,35593,35594],{"class":5297}," \"fmt\"\n",[5280,35596,35597],{"class":5282,"line":5316},[5280,35598,5313],{"emptyLinePlaceholder":5312},[5280,35600,35601],{"class":5282,"line":5322},[5280,35602,35603],{"class":5286},"\u002F\u002F ValidationError — помилка валідації з деталями\n",[5280,35605,35606,35608,35611,35613],{"class":5282,"line":5336},[5280,35607,7921],{"class":5619},[5280,35609,35610],{"class":5758}," ValidationError",[5280,35612,6216],{"class":5619},[5280,35614,6111],{"class":5452},[5280,35616,35617,35620],{"class":5282,"line":5341},[5280,35618,35619],{"class":5424},"    Field",[5280,35621,35622],{"class":5758},"   string\n",[5280,35624,35625,35628],{"class":5282,"line":5347},[5280,35626,35627],{"class":5424},"    Message",[5280,35629,14893],{"class":5758},[5280,35631,35632],{"class":5282,"line":5358},[5280,35633,5976],{"class":5452},[5280,35635,35636],{"class":5282,"line":5368},[5280,35637,5313],{"emptyLinePlaceholder":5312},[5280,35639,35640,35642,35644,35647,35649,35652,35654,35656,35658,35660],{"class":5282,"line":5378},[5280,35641,5862],{"class":5619},[5280,35643,5720],{"class":5452},[5280,35645,35646],{"class":5424},"e ",[5280,35648,8072],{"class":5452},[5280,35650,35651],{"class":5758},"ValidationError",[5280,35653,7315],{"class":5452},[5280,35655,6316],{"class":5293},[5280,35657,11974],{"class":5452},[5280,35659,5844],{"class":5758},[5280,35661,6111],{"class":5452},[5280,35663,35664,35666,35668,35670,35672,35674,35677,35679,35682,35684,35686,35688,35691,35693,35696,35698,35700,35702,35705],{"class":5282,"line":5391},[5280,35665,7328],{"class":5436},[5280,35667,22773],{"class":5424},[5280,35669,5218],{"class":5452},[5280,35671,12710],{"class":5293},[5280,35673,5835],{"class":5452},[5280,35675,35676],{"class":5297},"\"validation error: field '",[5280,35678,6057],{"class":5424},[5280,35680,35681],{"class":5297},"' - ",[5280,35683,6057],{"class":5424},[5280,35685,6584],{"class":5297},[5280,35687,5887],{"class":5452},[5280,35689,35690],{"class":5424},"e",[5280,35692,5218],{"class":5452},[5280,35694,35695],{"class":5424},"Field",[5280,35697,5887],{"class":5452},[5280,35699,35690],{"class":5424},[5280,35701,5218],{"class":5452},[5280,35703,35704],{"class":5424},"Message",[5280,35706,5598],{"class":5452},[5280,35708,35709],{"class":5282,"line":5705},[5280,35710,5976],{"class":5452},[5280,35712,35713],{"class":5282,"line":5820},[5280,35714,5313],{"emptyLinePlaceholder":5312},[5280,35716,35717],{"class":5282,"line":5854},[5280,35718,35719],{"class":5286},"\u002F\u002F NewValidationError створює нову помилку валідації\n",[5280,35721,35722,35724,35727,35729,35732,35734,35736,35738,35740,35742],{"class":5282,"line":5859},[5280,35723,5862],{"class":5619},[5280,35725,35726],{"class":5293}," NewValidationError",[5280,35728,5835],{"class":5452},[5280,35730,35731],{"class":5424},"field",[5280,35733,5887],{"class":5452},[5280,35735,13509],{"class":5424},[5280,35737,6239],{"class":5758},[5280,35739,13795],{"class":5452},[5280,35741,35651],{"class":5758},[5280,35743,6111],{"class":5452},[5280,35745,35746,35748,35750,35752],{"class":5282,"line":5871},[5280,35747,7328],{"class":5436},[5280,35749,13807],{"class":5452},[5280,35751,35651],{"class":5758},[5280,35753,6781],{"class":5452},[5280,35755,35756,35759,35761,35763],{"class":5282,"line":5895},[5280,35757,35758],{"class":5424},"        Field",[5280,35760,6805],{"class":5452},[5280,35762,35731],{"class":5424},[5280,35764,6796],{"class":5452},[5280,35766,35767,35770,35772,35774],{"class":5282,"line":5916},[5280,35768,35769],{"class":5424},"        Message",[5280,35771,6790],{"class":5452},[5280,35773,13509],{"class":5424},[5280,35775,6796],{"class":5452},[5280,35777,35778],{"class":5282,"line":5921},[5280,35779,6553],{"class":5452},[5280,35781,35782],{"class":5282,"line":5939},[5280,35783,5976],{"class":5452},[5280,35785,35786],{"class":5282,"line":5973},[5280,35787,5313],{"emptyLinePlaceholder":5312},[5280,35789,35790],{"class":5282,"line":5979},[5280,35791,35792],{"class":5286},"\u002F\u002F NotFoundError — помилка \"не знайдено\" з деталями\n",[5280,35794,35795,35797,35800,35802],{"class":5282,"line":5984},[5280,35796,7921],{"class":5619},[5280,35798,35799],{"class":5758}," NotFoundError",[5280,35801,6216],{"class":5619},[5280,35803,6111],{"class":5452},[5280,35805,35806,35809],{"class":5282,"line":6023},[5280,35807,35808],{"class":5424},"    Resource",[5280,35810,14893],{"class":5758},[5280,35812,35813,35815],{"class":5282,"line":6042},[5280,35814,17439],{"class":5424},[5280,35816,20075],{"class":5758},[5280,35818,35819],{"class":5282,"line":6093},[5280,35820,5976],{"class":5452},[5280,35822,35823],{"class":5282,"line":6098},[5280,35824,5313],{"emptyLinePlaceholder":5312},[5280,35826,35827,35829,35831,35833,35835,35838,35840,35842,35844,35846],{"class":5282,"line":6114},[5280,35828,5862],{"class":5619},[5280,35830,5720],{"class":5452},[5280,35832,35646],{"class":5424},[5280,35834,8072],{"class":5452},[5280,35836,35837],{"class":5758},"NotFoundError",[5280,35839,7315],{"class":5452},[5280,35841,6316],{"class":5293},[5280,35843,11974],{"class":5452},[5280,35845,5844],{"class":5758},[5280,35847,6111],{"class":5452},[5280,35849,35850,35852,35854,35856,35858,35860,35862,35864,35867,35869,35872,35874,35876,35878,35881,35883,35885,35887,35889],{"class":5282,"line":6130},[5280,35851,7328],{"class":5436},[5280,35853,22773],{"class":5424},[5280,35855,5218],{"class":5452},[5280,35857,12710],{"class":5293},[5280,35859,5835],{"class":5452},[5280,35861,6584],{"class":5297},[5280,35863,6057],{"class":5424},[5280,35865,35866],{"class":5297}," with id '",[5280,35868,6057],{"class":5424},[5280,35870,35871],{"class":5297},"' not found\"",[5280,35873,5887],{"class":5452},[5280,35875,35690],{"class":5424},[5280,35877,5218],{"class":5452},[5280,35879,35880],{"class":5424},"Resource",[5280,35882,5887],{"class":5452},[5280,35884,35690],{"class":5424},[5280,35886,5218],{"class":5452},[5280,35888,18635],{"class":5424},[5280,35890,5598],{"class":5452},[5280,35892,35893],{"class":5282,"line":6159},[5280,35894,5976],{"class":5452},[5266,35896,35898],{"id":35897},"обгортання-помилок-для-трейсингу","Обгортання помилок для трейсингу",[5262,35900,35901,35902,5408],{},"Go 1.13+ дозволяє обгортати помилки через ",[5215,35903,35904],{},"%w",[5271,35906,35908],{"className":5411,"code":35907,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Frepository\u002Fpostgres\u002Fuser_repository.go\nfunc (r *UserRepository) FindByID(ctx context.Context, id string) (*domain.User, error) {\n    user := &domain.User{}\n    err := r.db.QueryRowContext(ctx, query, id).Scan(...)\n    \n    if err == sql.ErrNoRows {\n        \u002F\u002F Повертаємо доменну помилку\n        return nil, domain.ErrUserNotFound\n    }\n    \n    if err != nil {\n        \u002F\u002F Обгортаємо помилку для збереження контексту\n        return nil, fmt.Errorf(\"failed to find user by id %s: %w\", id, err)\n    }\n    \n    return user, nil\n}\n",[5215,35909,35910,35914,35960,35974,36008,36012,36028,36033,36047,36051,36055,36067,36072,36109,36113,36117,36127],{"__ignoreMap":5276},[5280,35911,35912],{"class":5282,"line":5283},[5280,35913,23721],{"class":5286},[5280,35915,35916,35918,35920,35922,35924,35926,35928,35930,35932,35934,35936,35938,35940,35942,35944,35946,35948,35950,35952,35954,35956,35958],{"class":5282,"line":5290},[5280,35917,5862],{"class":5619},[5280,35919,5720],{"class":5452},[5280,35921,23026],{"class":5424},[5280,35923,8072],{"class":5452},[5280,35925,22060],{"class":5758},[5280,35927,7315],{"class":5452},[5280,35929,23147],{"class":5293},[5280,35931,5835],{"class":5452},[5280,35933,15441],{"class":5424},[5280,35935,21723],{"class":5758},[5280,35937,5218],{"class":5452},[5280,35939,15393],{"class":5758},[5280,35941,5887],{"class":5452},[5280,35943,21760],{"class":5424},[5280,35945,6239],{"class":5758},[5280,35947,20151],{"class":5452},[5280,35949,22144],{"class":5758},[5280,35951,5218],{"class":5452},[5280,35953,17513],{"class":5758},[5280,35955,5887],{"class":5452},[5280,35957,9567],{"class":5758},[5280,35959,6020],{"class":5452},[5280,35961,35962,35964,35966,35968,35970,35972],{"class":5282,"line":5301},[5280,35963,18871],{"class":5424},[5280,35965,9745],{"class":5452},[5280,35967,22144],{"class":5758},[5280,35969,5218],{"class":5452},[5280,35971,17513],{"class":5758},[5280,35973,8056],{"class":5452},[5280,35975,35976,35978,35980,35982,35984,35986,35988,35990,35992,35994,35996,35998,36000,36002,36004,36006],{"class":5282,"line":5309},[5280,35977,24142],{"class":5424},[5280,35979,6029],{"class":5452},[5280,35981,6007],{"class":5424},[5280,35983,5218],{"class":5452},[5280,35985,23813],{"class":5424},[5280,35987,5218],{"class":5452},[5280,35989,24155],{"class":5293},[5280,35991,5835],{"class":5452},[5280,35993,15441],{"class":5424},[5280,35995,5887],{"class":5452},[5280,35997,23958],{"class":5424},[5280,35999,5887],{"class":5452},[5280,36001,21760],{"class":5424},[5280,36003,6174],{"class":5452},[5280,36005,24172],{"class":5293},[5280,36007,32865],{"class":5452},[5280,36009,36010],{"class":5282,"line":5316},[5280,36011,6724],{"class":5452},[5280,36013,36014,36016,36018,36020,36022,36024,36026],{"class":5282,"line":5322},[5280,36015,7023],{"class":5436},[5280,36017,6262],{"class":5424},[5280,36019,7029],{"class":5452},[5280,36021,23784],{"class":5424},[5280,36023,5218],{"class":5452},[5280,36025,24270],{"class":5424},[5280,36027,6111],{"class":5452},[5280,36029,36030],{"class":5282,"line":5336},[5280,36031,36032],{"class":5286},"        \u002F\u002F Повертаємо доменну помилку\n",[5280,36034,36035,36037,36039,36041,36043,36045],{"class":5282,"line":5341},[5280,36036,12200],{"class":5436},[5280,36038,20192],{"class":5619},[5280,36040,5887],{"class":5452},[5280,36042,22144],{"class":5424},[5280,36044,5218],{"class":5452},[5280,36046,23260],{"class":5424},[5280,36048,36049],{"class":5282,"line":5347},[5280,36050,6553],{"class":5452},[5280,36052,36053],{"class":5282,"line":5358},[5280,36054,6724],{"class":5452},[5280,36056,36057,36059,36061,36063,36065],{"class":5282,"line":5368},[5280,36058,7023],{"class":5436},[5280,36060,6262],{"class":5424},[5280,36062,6301],{"class":5452},[5280,36064,5967],{"class":5619},[5280,36066,6111],{"class":5452},[5280,36068,36069],{"class":5282,"line":5378},[5280,36070,36071],{"class":5286},"        \u002F\u002F Обгортаємо помилку для збереження контексту\n",[5280,36073,36074,36076,36078,36080,36082,36084,36086,36088,36091,36093,36095,36097,36099,36101,36103,36105,36107],{"class":5282,"line":5391},[5280,36075,12200],{"class":5436},[5280,36077,20192],{"class":5619},[5280,36079,5887],{"class":5452},[5280,36081,15518],{"class":5424},[5280,36083,5218],{"class":5452},[5280,36085,15523],{"class":5293},[5280,36087,5835],{"class":5452},[5280,36089,36090],{"class":5297},"\"failed to find user by id ",[5280,36092,6057],{"class":5424},[5280,36094,6790],{"class":5297},[5280,36096,35904],{"class":5424},[5280,36098,6584],{"class":5297},[5280,36100,5887],{"class":5452},[5280,36102,21760],{"class":5424},[5280,36104,5887],{"class":5452},[5280,36106,6298],{"class":5424},[5280,36108,5598],{"class":5452},[5280,36110,36111],{"class":5282,"line":5705},[5280,36112,6553],{"class":5452},[5280,36114,36115],{"class":5282,"line":5820},[5280,36116,6724],{"class":5452},[5280,36118,36119,36121,36123,36125],{"class":5282,"line":5854},[5280,36120,7328],{"class":5436},[5280,36122,6213],{"class":5424},[5280,36124,5887],{"class":5452},[5280,36126,20459],{"class":5619},[5280,36128,36129],{"class":5282,"line":5859},[5280,36130,5976],{"class":5452},[5266,36132,36134],{"id":36133},"usecase-обробка-та-трансформація-помилок","UseCase: обробка та трансформація помилок",[5271,36136,36138],{"className":5411,"code":36137,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fusecase\u002Fuser_usecase.go\nfunc (uc *UserUseCase) RegisterUser(ctx context.Context, name, email, password string) (*domain.User, error) {\n    \u002F\u002F 1. Валідація вхідних даних\n    if email == \"\" {\n        return nil, domain.NewValidationError(\"email\", \"is required\")\n    }\n    \n    if len(password) \u003C 8 {\n        return nil, domain.NewValidationError(\"password\", \"must be at least 8 characters\")\n    }\n    \n    \u002F\u002F 2. Перевірка існування\n    existing, err := uc.repo.FindByEmail(ctx, email)\n    if err != nil && err != domain.ErrUserNotFound {\n        \u002F\u002F Несподівана помилка БД\n        return nil, fmt.Errorf(\"failed to check user existence: %w\", err)\n    }\n    \n    if existing != nil {\n        return nil, domain.ErrUserAlreadyExists\n    }\n    \n    \u002F\u002F 3. Хешування пароля\n    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to hash password: %w\", err)\n    }\n    \n    \u002F\u002F 4. Створення користувача\n    user := &domain.User{\n        ID:        generateUserID(),\n        Name:      name,\n        Email:     email,\n        Password:  string(hashedPassword),\n        CreatedAt: time.Now(),\n        UpdatedAt: time.Now(),\n    }\n    \n    \u002F\u002F 5. Валідація доменних правил\n    if err := user.Validate(); err != nil {\n        return nil, err\n    }\n    \n    \u002F\u002F 6. Збереження\n    if err := uc.repo.Create(ctx, user); err != nil {\n        return nil, fmt.Errorf(\"failed to create user: %w\", err)\n    }\n    \n    return user, nil\n}\n",[5215,36139,36140,36144,36198,36203,36215,36242,36246,36250,36266,36292,36296,36300,36305,36335,36361,36366,36395,36399,36403,36415,36429,36433,36437,36442,36476,36488,36517,36521,36525,36530,36544,36554,36564,36574,36588,36602,36616,36620,36624,36629,36653,36663,36667,36671,36676,36712,36741,36745,36749,36759],{"__ignoreMap":5276},[5280,36141,36142],{"class":5282,"line":5283},[5280,36143,21952],{"class":5286},[5280,36145,36146,36148,36150,36152,36154,36156,36158,36160,36162,36164,36166,36168,36170,36172,36174,36176,36178,36180,36182,36184,36186,36188,36190,36192,36194,36196],{"class":5282,"line":5290},[5280,36147,5862],{"class":5619},[5280,36149,5720],{"class":5452},[5280,36151,22107],{"class":5424},[5280,36153,8072],{"class":5452},[5280,36155,22065],{"class":5758},[5280,36157,7315],{"class":5452},[5280,36159,20688],{"class":5293},[5280,36161,5835],{"class":5452},[5280,36163,15441],{"class":5424},[5280,36165,21723],{"class":5758},[5280,36167,5218],{"class":5452},[5280,36169,15393],{"class":5758},[5280,36171,5887],{"class":5452},[5280,36173,20121],{"class":5424},[5280,36175,5887],{"class":5452},[5280,36177,20126],{"class":5424},[5280,36179,5887],{"class":5452},[5280,36181,20131],{"class":5424},[5280,36183,6239],{"class":5758},[5280,36185,20151],{"class":5452},[5280,36187,22144],{"class":5758},[5280,36189,5218],{"class":5452},[5280,36191,17513],{"class":5758},[5280,36193,5887],{"class":5452},[5280,36195,9567],{"class":5758},[5280,36197,6020],{"class":5452},[5280,36199,36200],{"class":5282,"line":5301},[5280,36201,36202],{"class":5286},"    \u002F\u002F 1. Валідація вхідних даних\n",[5280,36204,36205,36207,36209,36211,36213],{"class":5282,"line":5309},[5280,36206,7023],{"class":5436},[5280,36208,20171],{"class":5424},[5280,36210,7029],{"class":5452},[5280,36212,12375],{"class":5297},[5280,36214,6111],{"class":5452},[5280,36216,36217,36219,36221,36223,36225,36227,36230,36232,36235,36237,36240],{"class":5282,"line":5316},[5280,36218,12200],{"class":5436},[5280,36220,20192],{"class":5619},[5280,36222,5887],{"class":5452},[5280,36224,22144],{"class":5424},[5280,36226,5218],{"class":5452},[5280,36228,36229],{"class":5293},"NewValidationError",[5280,36231,5835],{"class":5452},[5280,36233,36234],{"class":5297},"\"email\"",[5280,36236,5887],{"class":5452},[5280,36238,36239],{"class":5297},"\"is required\"",[5280,36241,5598],{"class":5452},[5280,36243,36244],{"class":5282,"line":5322},[5280,36245,6553],{"class":5452},[5280,36247,36248],{"class":5282,"line":5336},[5280,36249,6724],{"class":5452},[5280,36251,36252,36254,36256,36258,36260,36262,36264],{"class":5282,"line":5341},[5280,36253,7023],{"class":5436},[5280,36255,12551],{"class":5293},[5280,36257,5835],{"class":5452},[5280,36259,20131],{"class":5424},[5280,36261,20230],{"class":5452},[5280,36263,20233],{"class":5439},[5280,36265,6111],{"class":5452},[5280,36267,36268,36270,36272,36274,36276,36278,36280,36282,36285,36287,36290],{"class":5282,"line":5347},[5280,36269,12200],{"class":5436},[5280,36271,20192],{"class":5619},[5280,36273,5887],{"class":5452},[5280,36275,22144],{"class":5424},[5280,36277,5218],{"class":5452},[5280,36279,36229],{"class":5293},[5280,36281,5835],{"class":5452},[5280,36283,36284],{"class":5297},"\"password\"",[5280,36286,5887],{"class":5452},[5280,36288,36289],{"class":5297},"\"must be at least 8 characters\"",[5280,36291,5598],{"class":5452},[5280,36293,36294],{"class":5282,"line":5358},[5280,36295,6553],{"class":5452},[5280,36297,36298],{"class":5282,"line":5368},[5280,36299,6724],{"class":5452},[5280,36301,36302],{"class":5282,"line":5378},[5280,36303,36304],{"class":5286},"    \u002F\u002F 2. Перевірка існування\n",[5280,36306,36307,36309,36311,36313,36315,36317,36319,36321,36323,36325,36327,36329,36331,36333],{"class":5282,"line":5391},[5280,36308,22164],{"class":5424},[5280,36310,5887],{"class":5452},[5280,36312,6298],{"class":5424},[5280,36314,6029],{"class":5452},[5280,36316,22174],{"class":5424},[5280,36318,5218],{"class":5452},[5280,36320,22053],{"class":5424},[5280,36322,5218],{"class":5452},[5280,36324,22183],{"class":5293},[5280,36326,5835],{"class":5452},[5280,36328,15441],{"class":5424},[5280,36330,5887],{"class":5452},[5280,36332,20126],{"class":5424},[5280,36334,5598],{"class":5452},[5280,36336,36337,36339,36341,36343,36345,36348,36350,36352,36354,36356,36359],{"class":5282,"line":5705},[5280,36338,7023],{"class":5436},[5280,36340,6262],{"class":5424},[5280,36342,6301],{"class":5452},[5280,36344,5967],{"class":5619},[5280,36346,36347],{"class":5452}," && ",[5280,36349,6298],{"class":5424},[5280,36351,6301],{"class":5452},[5280,36353,22144],{"class":5424},[5280,36355,5218],{"class":5452},[5280,36357,36358],{"class":5424},"ErrUserNotFound",[5280,36360,6111],{"class":5452},[5280,36362,36363],{"class":5282,"line":5820},[5280,36364,36365],{"class":5286},"        \u002F\u002F Несподівана помилка БД\n",[5280,36367,36368,36370,36372,36374,36376,36378,36380,36382,36385,36387,36389,36391,36393],{"class":5282,"line":5854},[5280,36369,12200],{"class":5436},[5280,36371,20192],{"class":5619},[5280,36373,5887],{"class":5452},[5280,36375,15518],{"class":5424},[5280,36377,5218],{"class":5452},[5280,36379,15523],{"class":5293},[5280,36381,5835],{"class":5452},[5280,36383,36384],{"class":5297},"\"failed to check user existence: ",[5280,36386,35904],{"class":5424},[5280,36388,6584],{"class":5297},[5280,36390,5887],{"class":5452},[5280,36392,6298],{"class":5424},[5280,36394,5598],{"class":5452},[5280,36396,36397],{"class":5282,"line":5859},[5280,36398,6553],{"class":5452},[5280,36400,36401],{"class":5282,"line":5871},[5280,36402,6724],{"class":5452},[5280,36404,36405,36407,36409,36411,36413],{"class":5282,"line":5895},[5280,36406,7023],{"class":5436},[5280,36408,22200],{"class":5424},[5280,36410,6301],{"class":5452},[5280,36412,5967],{"class":5619},[5280,36414,6111],{"class":5452},[5280,36416,36417,36419,36421,36423,36425,36427],{"class":5282,"line":5916},[5280,36418,12200],{"class":5436},[5280,36420,20192],{"class":5619},[5280,36422,5887],{"class":5452},[5280,36424,22144],{"class":5424},[5280,36426,5218],{"class":5452},[5280,36428,22221],{"class":5424},[5280,36430,36431],{"class":5282,"line":5921},[5280,36432,6553],{"class":5452},[5280,36434,36435],{"class":5282,"line":5939},[5280,36436,6724],{"class":5452},[5280,36438,36439],{"class":5282,"line":5973},[5280,36440,36441],{"class":5286},"    \u002F\u002F 3. Хешування пароля\n",[5280,36443,36444,36446,36448,36450,36452,36454,36456,36458,36460,36462,36464,36466,36468,36470,36472,36474],{"class":5282,"line":5979},[5280,36445,19830],{"class":5424},[5280,36447,5887],{"class":5452},[5280,36449,6298],{"class":5424},[5280,36451,6029],{"class":5452},[5280,36453,22247],{"class":5424},[5280,36455,5218],{"class":5452},[5280,36457,22252],{"class":5293},[5280,36459,22255],{"class":5452},[5280,36461,9556],{"class":5758},[5280,36463,5835],{"class":5452},[5280,36465,20131],{"class":5424},[5280,36467,22264],{"class":5452},[5280,36469,22247],{"class":5424},[5280,36471,5218],{"class":5452},[5280,36473,22271],{"class":5424},[5280,36475,5598],{"class":5452},[5280,36477,36478,36480,36482,36484,36486],{"class":5282,"line":5984},[5280,36479,7023],{"class":5436},[5280,36481,6262],{"class":5424},[5280,36483,6301],{"class":5452},[5280,36485,5967],{"class":5619},[5280,36487,6111],{"class":5452},[5280,36489,36490,36492,36494,36496,36498,36500,36502,36504,36507,36509,36511,36513,36515],{"class":5282,"line":6023},[5280,36491,12200],{"class":5436},[5280,36493,20192],{"class":5619},[5280,36495,5887],{"class":5452},[5280,36497,15518],{"class":5424},[5280,36499,5218],{"class":5452},[5280,36501,15523],{"class":5293},[5280,36503,5835],{"class":5452},[5280,36505,36506],{"class":5297},"\"failed to hash password: ",[5280,36508,35904],{"class":5424},[5280,36510,6584],{"class":5297},[5280,36512,5887],{"class":5452},[5280,36514,6298],{"class":5424},[5280,36516,5598],{"class":5452},[5280,36518,36519],{"class":5282,"line":6042},[5280,36520,6553],{"class":5452},[5280,36522,36523],{"class":5282,"line":6093},[5280,36524,6724],{"class":5452},[5280,36526,36527],{"class":5282,"line":6098},[5280,36528,36529],{"class":5286},"    \u002F\u002F 4. Створення користувача\n",[5280,36531,36532,36534,36536,36538,36540,36542],{"class":5282,"line":6114},[5280,36533,18871],{"class":5424},[5280,36535,9745],{"class":5452},[5280,36537,22144],{"class":5758},[5280,36539,5218],{"class":5452},[5280,36541,17513],{"class":5758},[5280,36543,6781],{"class":5452},[5280,36545,36546,36548,36550,36552],{"class":5282,"line":6130},[5280,36547,17528],{"class":5424},[5280,36549,9788],{"class":5452},[5280,36551,22332],{"class":5293},[5280,36553,17825],{"class":5452},[5280,36555,36556,36558,36560,36562],{"class":5282,"line":6159},[5280,36557,17540],{"class":5424},[5280,36559,17543],{"class":5452},[5280,36561,20121],{"class":5424},[5280,36563,6796],{"class":5452},[5280,36565,36566,36568,36570,36572],{"class":5282,"line":6187},[5280,36567,17553],{"class":5424},[5280,36569,9768],{"class":5452},[5280,36571,20126],{"class":5424},[5280,36573,6796],{"class":5452},[5280,36575,36576,36578,36580,36582,36584,36586],{"class":5282,"line":6193},[5280,36577,18415],{"class":5424},[5280,36579,19517],{"class":5452},[5280,36581,5844],{"class":5758},[5280,36583,5835],{"class":5452},[5280,36585,19913],{"class":5424},[5280,36587,6828],{"class":5452},[5280,36589,36590,36592,36594,36596,36598,36600],{"class":5282,"line":6207},[5280,36591,17565],{"class":5424},[5280,36593,6790],{"class":5452},[5280,36595,5587],{"class":5424},[5280,36597,5218],{"class":5452},[5280,36599,6036],{"class":5293},[5280,36601,17825],{"class":5452},[5280,36603,36604,36606,36608,36610,36612,36614],{"class":5282,"line":6221},[5280,36605,22387],{"class":5424},[5280,36607,6790],{"class":5452},[5280,36609,5587],{"class":5424},[5280,36611,5218],{"class":5452},[5280,36613,6036],{"class":5293},[5280,36615,17825],{"class":5452},[5280,36617,36618],{"class":5282,"line":6233},[5280,36619,6553],{"class":5452},[5280,36621,36622],{"class":5282,"line":6245},[5280,36623,6724],{"class":5452},[5280,36625,36626],{"class":5282,"line":6251},[5280,36627,36628],{"class":5286},"    \u002F\u002F 5. Валідація доменних правил\n",[5280,36630,36631,36633,36635,36637,36639,36641,36643,36645,36647,36649,36651],{"class":5282,"line":6256},[5280,36632,7023],{"class":5436},[5280,36634,6262],{"class":5424},[5280,36636,6029],{"class":5452},[5280,36638,6292],{"class":5424},[5280,36640,5218],{"class":5452},[5280,36642,21356],{"class":5293},[5280,36644,10615],{"class":5452},[5280,36646,6298],{"class":5424},[5280,36648,6301],{"class":5452},[5280,36650,5967],{"class":5619},[5280,36652,6111],{"class":5452},[5280,36654,36655,36657,36659,36661],{"class":5282,"line":6308},[5280,36656,12200],{"class":5436},[5280,36658,20192],{"class":5619},[5280,36660,5887],{"class":5452},[5280,36662,22296],{"class":5424},[5280,36664,36665],{"class":5282,"line":6339},[5280,36666,6553],{"class":5452},[5280,36668,36669],{"class":5282,"line":6371},[5280,36670,6724],{"class":5452},[5280,36672,36673],{"class":5282,"line":6409},[5280,36674,36675],{"class":5286},"    \u002F\u002F 6. Збереження\n",[5280,36677,36678,36680,36682,36684,36686,36688,36690,36692,36694,36696,36698,36700,36702,36704,36706,36708,36710],{"class":5282,"line":6415},[5280,36679,7023],{"class":5436},[5280,36681,6262],{"class":5424},[5280,36683,6029],{"class":5452},[5280,36685,22174],{"class":5424},[5280,36687,5218],{"class":5452},[5280,36689,22053],{"class":5424},[5280,36691,5218],{"class":5452},[5280,36693,22476],{"class":5293},[5280,36695,5835],{"class":5452},[5280,36697,15441],{"class":5424},[5280,36699,5887],{"class":5452},[5280,36701,6292],{"class":5424},[5280,36703,6295],{"class":5452},[5280,36705,6298],{"class":5424},[5280,36707,6301],{"class":5452},[5280,36709,5967],{"class":5619},[5280,36711,6111],{"class":5452},[5280,36713,36714,36716,36718,36720,36722,36724,36726,36728,36731,36733,36735,36737,36739],{"class":5282,"line":6420},[5280,36715,12200],{"class":5436},[5280,36717,20192],{"class":5619},[5280,36719,5887],{"class":5452},[5280,36721,15518],{"class":5424},[5280,36723,5218],{"class":5452},[5280,36725,15523],{"class":5293},[5280,36727,5835],{"class":5452},[5280,36729,36730],{"class":5297},"\"failed to create user: ",[5280,36732,35904],{"class":5424},[5280,36734,6584],{"class":5297},[5280,36736,5887],{"class":5452},[5280,36738,6298],{"class":5424},[5280,36740,5598],{"class":5452},[5280,36742,36743],{"class":5282,"line":6425},[5280,36744,6553],{"class":5452},[5280,36746,36747],{"class":5282,"line":6450},[5280,36748,6724],{"class":5452},[5280,36750,36751,36753,36755,36757],{"class":5282,"line":6471},[5280,36752,7328],{"class":5436},[5280,36754,6213],{"class":5424},[5280,36756,5887],{"class":5452},[5280,36758,20459],{"class":5619},[5280,36760,36761],{"class":5282,"line":6507},[5280,36762,5976],{"class":5452},[5266,36764,36766],{"id":36765},"http-handler-мапування-на-статус-коди","HTTP Handler: мапування на статус-коди",[5271,36768,36770],{"className":5411,"code":36769,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fdelivery\u002Fhttp\u002Fuser_handler.go\nfunc (h *UserHandler) Register(w http.ResponseWriter, r *http.Request) {\n    var req RegisterRequest\n    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {\n        respondWithError(w, http.StatusBadRequest, \"Invalid JSON format\")\n        return\n    }\n    \n    user, err := h.useCase.RegisterUser(r.Context(), req.Name, req.Email, req.Password)\n    if err != nil {\n        handleUseCaseError(w, err)\n        return\n    }\n    \n    respondWithJSON(w, http.StatusCreated, toUserResponse(user))\n}\n\n\u002F\u002F handleUseCaseError мапує доменні помилки на HTTP-статуси\nfunc handleUseCaseError(w http.ResponseWriter, err error) {\n    \u002F\u002F Перевіряємо тип помилки\n    var validationErr *domain.ValidationError\n    if errors.As(err, &validationErr) {\n        respondWithError(w, http.StatusBadRequest, validationErr.Error())\n        return\n    }\n    \n    var notFoundErr *domain.NotFoundError\n    if errors.As(err, &notFoundErr) {\n        respondWithError(w, http.StatusNotFound, notFoundErr.Error())\n        return\n    }\n    \n    \u002F\u002F Перевіряємо стандартні доменні помилки\n    switch {\n    case errors.Is(err, domain.ErrUserAlreadyExists):\n        respondWithError(w, http.StatusConflict, err.Error())\n    case errors.Is(err, domain.ErrInvalidCredentials):\n        respondWithError(w, http.StatusUnauthorized, err.Error())\n    case errors.Is(err, domain.ErrUserNotFound):\n        respondWithError(w, http.StatusNotFound, err.Error())\n    case errors.Is(err, domain.ErrUnauthorized):\n        respondWithError(w, http.StatusUnauthorized, err.Error())\n    default:\n        \u002F\u002F Логуємо несподівану помилку\n        log.Printf(\"Unexpected error: %v\", err)\n        respondWithError(w, http.StatusInternalServerError, \"Internal server error\")\n    }\n}\n\n\u002F\u002F Допоміжні функції для відповідей\nfunc respondWithJSON(w http.ResponseWriter, statusCode int, data interface{}) {\n    w.Header().Set(\"Content-Type\", \"application\u002Fjson\")\n    w.WriteHeader(statusCode)\n    json.NewEncoder(w).Encode(data)\n}\n\nfunc respondWithError(w http.ResponseWriter, statusCode int, message string) {\n    respondWithJSON(w, statusCode, map[string]string{\n        \"error\": message,\n    })\n}\n",[5215,36771,36772,36776,36816,36824,36864,36888,36892,36896,36900,36954,36966,36981,36985,36989,36993,37021,37025,37029,37034,37059,37064,37080,37102,37128,37132,37136,37140,37156,37177,37203,37207,37211,37215,37220,37226,37252,37278,37302,37328,37352,37378,37403,37429,37435,37440,37463,37486,37490,37494,37498,37503,37535,37557,37571,37593,37597,37601,37632,37658,37669,37673],{"__ignoreMap":5276},[5280,36773,36774],{"class":5282,"line":5283},[5280,36775,24955],{"class":5286},[5280,36777,36778,36780,36782,36784,36786,36788,36790,36792,36794,36796,36798,36800,36802,36804,36806,36808,36810,36812,36814],{"class":5282,"line":5290},[5280,36779,5862],{"class":5619},[5280,36781,5720],{"class":5452},[5280,36783,8069],{"class":5424},[5280,36785,8072],{"class":5452},[5280,36787,25066],{"class":5758},[5280,36789,7315],{"class":5452},[5280,36791,25221],{"class":5293},[5280,36793,5835],{"class":5452},[5280,36795,5994],{"class":5424},[5280,36797,5997],{"class":5758},[5280,36799,5218],{"class":5452},[5280,36801,6002],{"class":5758},[5280,36803,5887],{"class":5452},[5280,36805,6007],{"class":5424},[5280,36807,6010],{"class":5452},[5280,36809,5952],{"class":5758},[5280,36811,5218],{"class":5452},[5280,36813,6017],{"class":5758},[5280,36815,6020],{"class":5452},[5280,36817,36818,36820,36822],{"class":5282,"line":5301},[5280,36819,18383],{"class":5619},[5280,36821,18386],{"class":5424},[5280,36823,25254],{"class":5758},[5280,36825,36826,36828,36830,36832,36834,36836,36838,36840,36842,36844,36846,36848,36850,36852,36854,36856,36858,36860,36862],{"class":5282,"line":5309},[5280,36827,7023],{"class":5436},[5280,36829,6262],{"class":5424},[5280,36831,6029],{"class":5452},[5280,36833,6267],{"class":5424},[5280,36835,5218],{"class":5452},[5280,36837,6272],{"class":5293},[5280,36839,5835],{"class":5452},[5280,36841,6007],{"class":5424},[5280,36843,5218],{"class":5452},[5280,36845,6281],{"class":5424},[5280,36847,6174],{"class":5452},[5280,36849,6286],{"class":5293},[5280,36851,6289],{"class":5452},[5280,36853,18462],{"class":5424},[5280,36855,6295],{"class":5452},[5280,36857,6298],{"class":5424},[5280,36859,6301],{"class":5452},[5280,36861,5967],{"class":5619},[5280,36863,6111],{"class":5452},[5280,36865,36866,36869,36871,36873,36875,36877,36879,36881,36883,36886],{"class":5282,"line":5316},[5280,36867,36868],{"class":5293},"        respondWithError",[5280,36870,5835],{"class":5452},[5280,36872,5994],{"class":5424},[5280,36874,5887],{"class":5452},[5280,36876,5952],{"class":5424},[5280,36878,5218],{"class":5452},[5280,36880,6334],{"class":5424},[5280,36882,5887],{"class":5452},[5280,36884,36885],{"class":5297},"\"Invalid JSON format\"",[5280,36887,5598],{"class":5452},[5280,36889,36890],{"class":5282,"line":5322},[5280,36891,15875],{"class":5436},[5280,36893,36894],{"class":5282,"line":5336},[5280,36895,6553],{"class":5452},[5280,36897,36898],{"class":5282,"line":5341},[5280,36899,6724],{"class":5452},[5280,36901,36902,36904,36906,36908,36910,36912,36914,36916,36918,36920,36922,36924,36926,36928,36930,36932,36934,36936,36938,36940,36942,36944,36946,36948,36950,36952],{"class":5282,"line":5347},[5280,36903,18871],{"class":5424},[5280,36905,5887],{"class":5452},[5280,36907,6298],{"class":5424},[5280,36909,6029],{"class":5452},[5280,36911,11326],{"class":5424},[5280,36913,5218],{"class":5452},[5280,36915,25053],{"class":5424},[5280,36917,5218],{"class":5452},[5280,36919,20688],{"class":5293},[5280,36921,5835],{"class":5452},[5280,36923,6007],{"class":5424},[5280,36925,5218],{"class":5452},[5280,36927,15393],{"class":5293},[5280,36929,15396],{"class":5452},[5280,36931,18462],{"class":5424},[5280,36933,5218],{"class":5452},[5280,36935,6502],{"class":5424},[5280,36937,5887],{"class":5452},[5280,36939,18462],{"class":5424},[5280,36941,5218],{"class":5452},[5280,36943,6437],{"class":5424},[5280,36945,5887],{"class":5452},[5280,36947,18462],{"class":5424},[5280,36949,5218],{"class":5452},[5280,36951,19687],{"class":5424},[5280,36953,5598],{"class":5452},[5280,36955,36956,36958,36960,36962,36964],{"class":5282,"line":5358},[5280,36957,7023],{"class":5436},[5280,36959,6262],{"class":5424},[5280,36961,6301],{"class":5452},[5280,36963,5967],{"class":5619},[5280,36965,6111],{"class":5452},[5280,36967,36968,36971,36973,36975,36977,36979],{"class":5282,"line":5368},[5280,36969,36970],{"class":5293},"        handleUseCaseError",[5280,36972,5835],{"class":5452},[5280,36974,5994],{"class":5424},[5280,36976,5887],{"class":5452},[5280,36978,6298],{"class":5424},[5280,36980,5598],{"class":5452},[5280,36982,36983],{"class":5282,"line":5378},[5280,36984,15875],{"class":5436},[5280,36986,36987],{"class":5282,"line":5391},[5280,36988,6553],{"class":5452},[5280,36990,36991],{"class":5282,"line":5705},[5280,36992,6724],{"class":5452},[5280,36994,36995,36998,37000,37002,37004,37006,37008,37010,37012,37015,37017,37019],{"class":5282,"line":5820},[5280,36996,36997],{"class":5293},"    respondWithJSON",[5280,36999,5835],{"class":5452},[5280,37001,5994],{"class":5424},[5280,37003,5887],{"class":5452},[5280,37005,5952],{"class":5424},[5280,37007,5218],{"class":5452},[5280,37009,6466],{"class":5424},[5280,37011,5887],{"class":5452},[5280,37013,37014],{"class":5293},"toUserResponse",[5280,37016,5835],{"class":5452},[5280,37018,6292],{"class":5424},[5280,37020,5970],{"class":5452},[5280,37022,37023],{"class":5282,"line":5854},[5280,37024,5976],{"class":5452},[5280,37026,37027],{"class":5282,"line":5859},[5280,37028,5313],{"emptyLinePlaceholder":5312},[5280,37030,37031],{"class":5282,"line":5871},[5280,37032,37033],{"class":5286},"\u002F\u002F handleUseCaseError мапує доменні помилки на HTTP-статуси\n",[5280,37035,37036,37038,37041,37043,37045,37047,37049,37051,37053,37055,37057],{"class":5282,"line":5895},[5280,37037,5862],{"class":5619},[5280,37039,37040],{"class":5293}," handleUseCaseError",[5280,37042,5835],{"class":5452},[5280,37044,5994],{"class":5424},[5280,37046,5997],{"class":5758},[5280,37048,5218],{"class":5452},[5280,37050,6002],{"class":5758},[5280,37052,5887],{"class":5452},[5280,37054,6298],{"class":5424},[5280,37056,13504],{"class":5758},[5280,37058,6020],{"class":5452},[5280,37060,37061],{"class":5282,"line":5916},[5280,37062,37063],{"class":5286},"    \u002F\u002F Перевіряємо тип помилки\n",[5280,37065,37066,37068,37071,37073,37075,37077],{"class":5282,"line":5921},[5280,37067,18383],{"class":5619},[5280,37069,37070],{"class":5424}," validationErr",[5280,37072,6010],{"class":5452},[5280,37074,22144],{"class":5758},[5280,37076,5218],{"class":5452},[5280,37078,37079],{"class":5758},"ValidationError\n",[5280,37081,37082,37084,37086,37088,37091,37093,37095,37097,37100],{"class":5282,"line":5939},[5280,37083,7023],{"class":5436},[5280,37085,21385],{"class":5424},[5280,37087,5218],{"class":5452},[5280,37089,37090],{"class":5293},"As",[5280,37092,5835],{"class":5452},[5280,37094,6298],{"class":5424},[5280,37096,8153],{"class":5452},[5280,37098,37099],{"class":5424},"validationErr",[5280,37101,6020],{"class":5452},[5280,37103,37104,37106,37108,37110,37112,37114,37116,37118,37120,37122,37124,37126],{"class":5282,"line":5973},[5280,37105,36868],{"class":5293},[5280,37107,5835],{"class":5452},[5280,37109,5994],{"class":5424},[5280,37111,5887],{"class":5452},[5280,37113,5952],{"class":5424},[5280,37115,5218],{"class":5452},[5280,37117,6334],{"class":5424},[5280,37119,5887],{"class":5452},[5280,37121,37099],{"class":5424},[5280,37123,5218],{"class":5452},[5280,37125,6316],{"class":5293},[5280,37127,10669],{"class":5452},[5280,37129,37130],{"class":5282,"line":5979},[5280,37131,15875],{"class":5436},[5280,37133,37134],{"class":5282,"line":5984},[5280,37135,6553],{"class":5452},[5280,37137,37138],{"class":5282,"line":6023},[5280,37139,6724],{"class":5452},[5280,37141,37142,37144,37147,37149,37151,37153],{"class":5282,"line":6042},[5280,37143,18383],{"class":5619},[5280,37145,37146],{"class":5424}," notFoundErr",[5280,37148,6010],{"class":5452},[5280,37150,22144],{"class":5758},[5280,37152,5218],{"class":5452},[5280,37154,37155],{"class":5758},"NotFoundError\n",[5280,37157,37158,37160,37162,37164,37166,37168,37170,37172,37175],{"class":5282,"line":6093},[5280,37159,7023],{"class":5436},[5280,37161,21385],{"class":5424},[5280,37163,5218],{"class":5452},[5280,37165,37090],{"class":5293},[5280,37167,5835],{"class":5452},[5280,37169,6298],{"class":5424},[5280,37171,8153],{"class":5452},[5280,37173,37174],{"class":5424},"notFoundErr",[5280,37176,6020],{"class":5452},[5280,37178,37179,37181,37183,37185,37187,37189,37191,37193,37195,37197,37199,37201],{"class":5282,"line":6098},[5280,37180,36868],{"class":5293},[5280,37182,5835],{"class":5452},[5280,37184,5994],{"class":5424},[5280,37186,5887],{"class":5452},[5280,37188,5952],{"class":5424},[5280,37190,5218],{"class":5452},[5280,37192,18923],{"class":5424},[5280,37194,5887],{"class":5452},[5280,37196,37174],{"class":5424},[5280,37198,5218],{"class":5452},[5280,37200,6316],{"class":5293},[5280,37202,10669],{"class":5452},[5280,37204,37205],{"class":5282,"line":6114},[5280,37206,15875],{"class":5436},[5280,37208,37209],{"class":5282,"line":6130},[5280,37210,6553],{"class":5452},[5280,37212,37213],{"class":5282,"line":6159},[5280,37214,6724],{"class":5452},[5280,37216,37217],{"class":5282,"line":6187},[5280,37218,37219],{"class":5286},"    \u002F\u002F Перевіряємо стандартні доменні помилки\n",[5280,37221,37222,37224],{"class":5282,"line":6193},[5280,37223,6101],{"class":5436},[5280,37225,6111],{"class":5452},[5280,37227,37228,37230,37232,37234,37237,37239,37241,37243,37245,37247,37249],{"class":5282,"line":6207},[5280,37229,6117],{"class":5436},[5280,37231,21385],{"class":5424},[5280,37233,5218],{"class":5452},[5280,37235,37236],{"class":5293},"Is",[5280,37238,5835],{"class":5452},[5280,37240,6298],{"class":5424},[5280,37242,5887],{"class":5452},[5280,37244,22144],{"class":5424},[5280,37246,5218],{"class":5452},[5280,37248,25423],{"class":5424},[5280,37250,37251],{"class":5452},"):\n",[5280,37253,37254,37256,37258,37260,37262,37264,37266,37268,37270,37272,37274,37276],{"class":5282,"line":6221},[5280,37255,36868],{"class":5293},[5280,37257,5835],{"class":5452},[5280,37259,5994],{"class":5424},[5280,37261,5887],{"class":5452},[5280,37263,5952],{"class":5424},[5280,37265,5218],{"class":5452},[5280,37267,19802],{"class":5424},[5280,37269,5887],{"class":5452},[5280,37271,6298],{"class":5424},[5280,37273,5218],{"class":5452},[5280,37275,6316],{"class":5293},[5280,37277,10669],{"class":5452},[5280,37279,37280,37282,37284,37286,37288,37290,37292,37294,37296,37298,37300],{"class":5282,"line":6233},[5280,37281,6117],{"class":5436},[5280,37283,21385],{"class":5424},[5280,37285,5218],{"class":5452},[5280,37287,37236],{"class":5293},[5280,37289,5835],{"class":5452},[5280,37291,6298],{"class":5424},[5280,37293,5887],{"class":5452},[5280,37295,22144],{"class":5424},[5280,37297,5218],{"class":5452},[5280,37299,25960],{"class":5424},[5280,37301,37251],{"class":5452},[5280,37303,37304,37306,37308,37310,37312,37314,37316,37318,37320,37322,37324,37326],{"class":5282,"line":6245},[5280,37305,36868],{"class":5293},[5280,37307,5835],{"class":5452},[5280,37309,5994],{"class":5424},[5280,37311,5887],{"class":5452},[5280,37313,5952],{"class":5424},[5280,37315,5218],{"class":5452},[5280,37317,15133],{"class":5424},[5280,37319,5887],{"class":5452},[5280,37321,6298],{"class":5424},[5280,37323,5218],{"class":5452},[5280,37325,6316],{"class":5293},[5280,37327,10669],{"class":5452},[5280,37329,37330,37332,37334,37336,37338,37340,37342,37344,37346,37348,37350],{"class":5282,"line":6251},[5280,37331,6117],{"class":5436},[5280,37333,21385],{"class":5424},[5280,37335,5218],{"class":5452},[5280,37337,37236],{"class":5293},[5280,37339,5835],{"class":5452},[5280,37341,6298],{"class":5424},[5280,37343,5887],{"class":5452},[5280,37345,22144],{"class":5424},[5280,37347,5218],{"class":5452},[5280,37349,36358],{"class":5424},[5280,37351,37251],{"class":5452},[5280,37353,37354,37356,37358,37360,37362,37364,37366,37368,37370,37372,37374,37376],{"class":5282,"line":6256},[5280,37355,36868],{"class":5293},[5280,37357,5835],{"class":5452},[5280,37359,5994],{"class":5424},[5280,37361,5887],{"class":5452},[5280,37363,5952],{"class":5424},[5280,37365,5218],{"class":5452},[5280,37367,18923],{"class":5424},[5280,37369,5887],{"class":5452},[5280,37371,6298],{"class":5424},[5280,37373,5218],{"class":5452},[5280,37375,6316],{"class":5293},[5280,37377,10669],{"class":5452},[5280,37379,37380,37382,37384,37386,37388,37390,37392,37394,37396,37398,37401],{"class":5282,"line":6308},[5280,37381,6117],{"class":5436},[5280,37383,21385],{"class":5424},[5280,37385,5218],{"class":5452},[5280,37387,37236],{"class":5293},[5280,37389,5835],{"class":5452},[5280,37391,6298],{"class":5424},[5280,37393,5887],{"class":5452},[5280,37395,22144],{"class":5424},[5280,37397,5218],{"class":5452},[5280,37399,37400],{"class":5424},"ErrUnauthorized",[5280,37402,37251],{"class":5452},[5280,37404,37405,37407,37409,37411,37413,37415,37417,37419,37421,37423,37425,37427],{"class":5282,"line":6339},[5280,37406,36868],{"class":5293},[5280,37408,5835],{"class":5452},[5280,37410,5994],{"class":5424},[5280,37412,5887],{"class":5452},[5280,37414,5952],{"class":5424},[5280,37416,5218],{"class":5452},[5280,37418,15133],{"class":5424},[5280,37420,5887],{"class":5452},[5280,37422,6298],{"class":5424},[5280,37424,5218],{"class":5452},[5280,37426,6316],{"class":5293},[5280,37428,10669],{"class":5452},[5280,37430,37431,37433],{"class":5282,"line":6371},[5280,37432,6515],{"class":5436},[5280,37434,6127],{"class":5452},[5280,37436,37437],{"class":5282,"line":6409},[5280,37438,37439],{"class":5286},"        \u002F\u002F Логуємо несподівану помилку\n",[5280,37441,37442,37444,37446,37448,37450,37453,37455,37457,37459,37461],{"class":5282,"line":6415},[5280,37443,7039],{"class":5424},[5280,37445,5218],{"class":5452},[5280,37447,6049],{"class":5293},[5280,37449,5835],{"class":5452},[5280,37451,37452],{"class":5297},"\"Unexpected error: ",[5280,37454,6362],{"class":5424},[5280,37456,6584],{"class":5297},[5280,37458,5887],{"class":5452},[5280,37460,6298],{"class":5424},[5280,37462,5598],{"class":5452},[5280,37464,37465,37467,37469,37471,37473,37475,37477,37479,37481,37484],{"class":5282,"line":6420},[5280,37466,36868],{"class":5293},[5280,37468,5835],{"class":5452},[5280,37470,5994],{"class":5424},[5280,37472,5887],{"class":5452},[5280,37474,5952],{"class":5424},[5280,37476,5218],{"class":5452},[5280,37478,10246],{"class":5424},[5280,37480,5887],{"class":5452},[5280,37482,37483],{"class":5297},"\"Internal server error\"",[5280,37485,5598],{"class":5452},[5280,37487,37488],{"class":5282,"line":6425},[5280,37489,6553],{"class":5452},[5280,37491,37492],{"class":5282,"line":6450},[5280,37493,5976],{"class":5452},[5280,37495,37496],{"class":5282,"line":6471},[5280,37497,5313],{"emptyLinePlaceholder":5312},[5280,37499,37500],{"class":5282,"line":6507},[5280,37501,37502],{"class":5286},"\u002F\u002F Допоміжні функції для відповідей\n",[5280,37504,37505,37507,37510,37512,37514,37516,37518,37520,37522,37524,37526,37528,37530,37532],{"class":5282,"line":6512},[5280,37506,5862],{"class":5619},[5280,37508,37509],{"class":5293}," respondWithJSON",[5280,37511,5835],{"class":5452},[5280,37513,5994],{"class":5424},[5280,37515,5997],{"class":5758},[5280,37517,5218],{"class":5452},[5280,37519,6002],{"class":5758},[5280,37521,5887],{"class":5452},[5280,37523,9475],{"class":5424},[5280,37525,9446],{"class":5758},[5280,37527,5887],{"class":5452},[5280,37529,13447],{"class":5424},[5280,37531,8005],{"class":5619},[5280,37533,37534],{"class":5452},"{}) {\n",[5280,37536,37537,37539,37541,37543,37545,37547,37549,37551,37553,37555],{"class":5282,"line":6520},[5280,37538,6730],{"class":5424},[5280,37540,5218],{"class":5452},[5280,37542,6138],{"class":5293},[5280,37544,6141],{"class":5452},[5280,37546,6144],{"class":5293},[5280,37548,5835],{"class":5452},[5280,37550,6149],{"class":5297},[5280,37552,5887],{"class":5452},[5280,37554,6154],{"class":5297},[5280,37556,5598],{"class":5452},[5280,37558,37559,37561,37563,37565,37567,37569],{"class":5282,"line":6550},[5280,37560,6730],{"class":5424},[5280,37562,5218],{"class":5452},[5280,37564,6457],{"class":5293},[5280,37566,5835],{"class":5452},[5280,37568,9475],{"class":5424},[5280,37570,5598],{"class":5452},[5280,37572,37573,37575,37577,37579,37581,37583,37585,37587,37589,37591],{"class":5282,"line":6556},[5280,37574,6754],{"class":5424},[5280,37576,5218],{"class":5452},[5280,37578,6167],{"class":5293},[5280,37580,5835],{"class":5452},[5280,37582,5994],{"class":5424},[5280,37584,6174],{"class":5452},[5280,37586,6177],{"class":5293},[5280,37588,5835],{"class":5452},[5280,37590,13447],{"class":5424},[5280,37592,5598],{"class":5452},[5280,37594,37595],{"class":5282,"line":6561},[5280,37596,5976],{"class":5452},[5280,37598,37599],{"class":5282,"line":6621},[5280,37600,5313],{"emptyLinePlaceholder":5312},[5280,37602,37603,37605,37608,37610,37612,37614,37616,37618,37620,37622,37624,37626,37628,37630],{"class":5282,"line":6626},[5280,37604,5862],{"class":5619},[5280,37606,37607],{"class":5293}," respondWithError",[5280,37609,5835],{"class":5452},[5280,37611,5994],{"class":5424},[5280,37613,5997],{"class":5758},[5280,37615,5218],{"class":5452},[5280,37617,6002],{"class":5758},[5280,37619,5887],{"class":5452},[5280,37621,9475],{"class":5424},[5280,37623,9446],{"class":5758},[5280,37625,5887],{"class":5452},[5280,37627,13509],{"class":5424},[5280,37629,6239],{"class":5758},[5280,37631,6020],{"class":5452},[5280,37633,37634,37636,37638,37640,37642,37644,37646,37648,37650,37652,37654,37656],{"class":5282,"line":6631},[5280,37635,36997],{"class":5293},[5280,37637,5835],{"class":5452},[5280,37639,5994],{"class":5424},[5280,37641,5887],{"class":5452},[5280,37643,9475],{"class":5424},[5280,37645,5887],{"class":5452},[5280,37647,5838],{"class":5619},[5280,37649,5841],{"class":5452},[5280,37651,5844],{"class":5758},[5280,37653,5847],{"class":5452},[5280,37655,5844],{"class":5758},[5280,37657,6781],{"class":5452},[5280,37659,37660,37663,37665,37667],{"class":5282,"line":6663},[5280,37661,37662],{"class":5297},"        \"error\"",[5280,37664,6790],{"class":5452},[5280,37666,13509],{"class":5424},[5280,37668,6796],{"class":5452},[5280,37670,37671],{"class":5282,"line":6678},[5280,37672,6834],{"class":5452},[5280,37674,37675],{"class":5282,"line":6721},[5280,37676,5976],{"class":5452},[5266,37678,37680],{"id":37679},"стандартизований-формат-помилок-rfc-7807-problem-details","Стандартизований формат помилок (RFC 7807 Problem Details)",[5262,37682,37683],{},"Для складніших API можна використовувати стандарт RFC 7807:",[5271,37685,37687],{"className":5411,"code":37686,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fdelivery\u002Fhttp\u002Fproblem.go\npackage http\n\n\u002F\u002F ProblemDetails — стандартний формат опису помилки\ntype ProblemDetails struct {\n    Type     string `json:\"type\"`                \u002F\u002F URI типу помилки\n    Title    string `json:\"title\"`               \u002F\u002F Короткий опис\n    Status   int    `json:\"status\"`              \u002F\u002F HTTP статус-код\n    Detail   string `json:\"detail\"`              \u002F\u002F Детальний опис\n    Instance string `json:\"instance,omitempty\"`  \u002F\u002F URI запиту, що викликав помилку\n}\n\nfunc respondWithProblem(w http.ResponseWriter, problem ProblemDetails) {\n    w.Header().Set(\"Content-Type\", \"application\u002Fproblem+json\")\n    w.WriteHeader(problem.Status)\n    json.NewEncoder(w).Encode(problem)\n}\n\n\u002F\u002F Приклад використання\nfunc handleUseCaseErrorRFC7807(w http.ResponseWriter, r *http.Request, err error) {\n    var problem ProblemDetails\n    \n    switch {\n    case errors.Is(err, domain.ErrUserAlreadyExists):\n        problem = ProblemDetails{\n            Type:     \"https:\u002F\u002Fapi.example.com\u002Ferrors\u002Fuser-already-exists\",\n            Title:    \"User Already Exists\",\n            Status:   http.StatusConflict,\n            Detail:   \"A user with this email address already exists in the system\",\n            Instance: r.URL.Path,\n        }\n    case errors.Is(err, domain.ErrInvalidCredentials):\n        problem = ProblemDetails{\n            Type:     \"https:\u002F\u002Fapi.example.com\u002Ferrors\u002Finvalid-credentials\",\n            Title:    \"Invalid Credentials\",\n            Status:   http.StatusUnauthorized,\n            Detail:   \"The provided email or password is incorrect\",\n            Instance: r.URL.Path,\n        }\n    default:\n        problem = ProblemDetails{\n            Type:     \"https:\u002F\u002Fapi.example.com\u002Ferrors\u002Finternal\",\n            Title:    \"Internal Server Error\",\n            Status:   http.StatusInternalServerError,\n            Detail:   \"An unexpected error occurred\",\n            Instance: r.URL.Path,\n        }\n    }\n    \n    respondWithProblem(w, problem)\n}\n",[5215,37688,37689,37694,37700,37704,37709,37720,37733,37746,37760,37774,37787,37791,37795,37821,37844,37863,37885,37889,37893,37898,37935,37945,37949,37955,37979,37991,38003,38015,38030,38042,38061,38065,38089,38099,38110,38121,38135,38146,38164,38168,38174,38184,38195,38205,38219,38230,38248,38252,38256,38260,38275],{"__ignoreMap":5276},[5280,37690,37691],{"class":5282,"line":5283},[5280,37692,37693],{"class":5286},"\u002F\u002F internal\u002Fdelivery\u002Fhttp\u002Fproblem.go\n",[5280,37695,37696,37698],{"class":5282,"line":5290},[5280,37697,5755],{"class":5619},[5280,37699,24962],{"class":5758},[5280,37701,37702],{"class":5282,"line":5301},[5280,37703,5313],{"emptyLinePlaceholder":5312},[5280,37705,37706],{"class":5282,"line":5309},[5280,37707,37708],{"class":5286},"\u002F\u002F ProblemDetails — стандартний формат опису помилки\n",[5280,37710,37711,37713,37716,37718],{"class":5282,"line":5316},[5280,37712,7921],{"class":5619},[5280,37714,37715],{"class":5758}," ProblemDetails",[5280,37717,6216],{"class":5619},[5280,37719,6111],{"class":5452},[5280,37721,37722,37725,37727,37730],{"class":5282,"line":5322},[5280,37723,37724],{"class":5424},"    Type",[5280,37726,17464],{"class":5758},[5280,37728,37729],{"class":5297}," `json:\"type\"`",[5280,37731,37732],{"class":5286},"                \u002F\u002F URI типу помилки\n",[5280,37734,37735,37738,37740,37743],{"class":5282,"line":5336},[5280,37736,37737],{"class":5424},"    Title",[5280,37739,18407],{"class":5758},[5280,37741,37742],{"class":5297}," `json:\"title\"`",[5280,37744,37745],{"class":5286},"               \u002F\u002F Короткий опис\n",[5280,37747,37748,37751,37754,37757],{"class":5282,"line":5341},[5280,37749,37750],{"class":5424},"    Status",[5280,37752,37753],{"class":5758},"   int",[5280,37755,37756],{"class":5297},"    `json:\"status\"`",[5280,37758,37759],{"class":5286},"              \u002F\u002F HTTP статус-код\n",[5280,37761,37762,37765,37768,37771],{"class":5282,"line":5347},[5280,37763,37764],{"class":5424},"    Detail",[5280,37766,37767],{"class":5758},"   string",[5280,37769,37770],{"class":5297}," `json:\"detail\"`",[5280,37772,37773],{"class":5286},"              \u002F\u002F Детальний опис\n",[5280,37775,37776,37779,37781,37784],{"class":5282,"line":5358},[5280,37777,37778],{"class":5424},"    Instance",[5280,37780,6239],{"class":5758},[5280,37782,37783],{"class":5297}," `json:\"instance,omitempty\"`",[5280,37785,37786],{"class":5286},"  \u002F\u002F URI запиту, що викликав помилку\n",[5280,37788,37789],{"class":5282,"line":5368},[5280,37790,5976],{"class":5452},[5280,37792,37793],{"class":5282,"line":5378},[5280,37794,5313],{"emptyLinePlaceholder":5312},[5280,37796,37797,37799,37802,37804,37806,37808,37810,37812,37814,37817,37819],{"class":5282,"line":5391},[5280,37798,5862],{"class":5619},[5280,37800,37801],{"class":5293}," respondWithProblem",[5280,37803,5835],{"class":5452},[5280,37805,5994],{"class":5424},[5280,37807,5997],{"class":5758},[5280,37809,5218],{"class":5452},[5280,37811,6002],{"class":5758},[5280,37813,5887],{"class":5452},[5280,37815,37816],{"class":5424},"problem",[5280,37818,37715],{"class":5758},[5280,37820,6020],{"class":5452},[5280,37822,37823,37825,37827,37829,37831,37833,37835,37837,37839,37842],{"class":5282,"line":5705},[5280,37824,6730],{"class":5424},[5280,37826,5218],{"class":5452},[5280,37828,6138],{"class":5293},[5280,37830,6141],{"class":5452},[5280,37832,6144],{"class":5293},[5280,37834,5835],{"class":5452},[5280,37836,6149],{"class":5297},[5280,37838,5887],{"class":5452},[5280,37840,37841],{"class":5297},"\"application\u002Fproblem+json\"",[5280,37843,5598],{"class":5452},[5280,37845,37846,37848,37850,37852,37854,37856,37858,37861],{"class":5282,"line":5820},[5280,37847,6730],{"class":5424},[5280,37849,5218],{"class":5452},[5280,37851,6457],{"class":5293},[5280,37853,5835],{"class":5452},[5280,37855,37816],{"class":5424},[5280,37857,5218],{"class":5452},[5280,37859,37860],{"class":5424},"Status",[5280,37862,5598],{"class":5452},[5280,37864,37865,37867,37869,37871,37873,37875,37877,37879,37881,37883],{"class":5282,"line":5854},[5280,37866,6754],{"class":5424},[5280,37868,5218],{"class":5452},[5280,37870,6167],{"class":5293},[5280,37872,5835],{"class":5452},[5280,37874,5994],{"class":5424},[5280,37876,6174],{"class":5452},[5280,37878,6177],{"class":5293},[5280,37880,5835],{"class":5452},[5280,37882,37816],{"class":5424},[5280,37884,5598],{"class":5452},[5280,37886,37887],{"class":5282,"line":5859},[5280,37888,5976],{"class":5452},[5280,37890,37891],{"class":5282,"line":5871},[5280,37892,5313],{"emptyLinePlaceholder":5312},[5280,37894,37895],{"class":5282,"line":5895},[5280,37896,37897],{"class":5286},"\u002F\u002F Приклад використання\n",[5280,37899,37900,37902,37905,37907,37909,37911,37913,37915,37917,37919,37921,37923,37925,37927,37929,37931,37933],{"class":5282,"line":5916},[5280,37901,5862],{"class":5619},[5280,37903,37904],{"class":5293}," handleUseCaseErrorRFC7807",[5280,37906,5835],{"class":5452},[5280,37908,5994],{"class":5424},[5280,37910,5997],{"class":5758},[5280,37912,5218],{"class":5452},[5280,37914,6002],{"class":5758},[5280,37916,5887],{"class":5452},[5280,37918,6007],{"class":5424},[5280,37920,6010],{"class":5452},[5280,37922,5952],{"class":5758},[5280,37924,5218],{"class":5452},[5280,37926,6017],{"class":5758},[5280,37928,5887],{"class":5452},[5280,37930,6298],{"class":5424},[5280,37932,13504],{"class":5758},[5280,37934,6020],{"class":5452},[5280,37936,37937,37939,37942],{"class":5282,"line":5921},[5280,37938,18383],{"class":5619},[5280,37940,37941],{"class":5424}," problem",[5280,37943,37944],{"class":5758}," ProblemDetails\n",[5280,37946,37947],{"class":5282,"line":5939},[5280,37948,6724],{"class":5452},[5280,37950,37951,37953],{"class":5282,"line":5973},[5280,37952,6101],{"class":5436},[5280,37954,6111],{"class":5452},[5280,37956,37957,37959,37961,37963,37965,37967,37969,37971,37973,37975,37977],{"class":5282,"line":5979},[5280,37958,6117],{"class":5436},[5280,37960,21385],{"class":5424},[5280,37962,5218],{"class":5452},[5280,37964,37236],{"class":5293},[5280,37966,5835],{"class":5452},[5280,37968,6298],{"class":5424},[5280,37970,5887],{"class":5452},[5280,37972,22144],{"class":5424},[5280,37974,5218],{"class":5452},[5280,37976,25423],{"class":5424},[5280,37978,37251],{"class":5452},[5280,37980,37981,37984,37986,37989],{"class":5282,"line":5984},[5280,37982,37983],{"class":5424},"        problem",[5280,37985,5829],{"class":5452},[5280,37987,37988],{"class":5758},"ProblemDetails",[5280,37990,6781],{"class":5452},[5280,37992,37993,37996,37998,38001],{"class":5282,"line":6023},[5280,37994,37995],{"class":5424},"            Type",[5280,37997,9768],{"class":5452},[5280,37999,38000],{"class":5297},"\"https:\u002F\u002Fapi.example.com\u002Ferrors\u002Fuser-already-exists\"",[5280,38002,6796],{"class":5452},[5280,38004,38005,38008,38010,38013],{"class":5282,"line":6042},[5280,38006,38007],{"class":5424},"            Title",[5280,38009,13867],{"class":5452},[5280,38011,38012],{"class":5297},"\"User Already Exists\"",[5280,38014,6796],{"class":5452},[5280,38016,38017,38020,38022,38024,38026,38028],{"class":5282,"line":6093},[5280,38018,38019],{"class":5424},"            Status",[5280,38021,6805],{"class":5452},[5280,38023,5952],{"class":5424},[5280,38025,5218],{"class":5452},[5280,38027,19802],{"class":5424},[5280,38029,6796],{"class":5452},[5280,38031,38032,38035,38037,38040],{"class":5282,"line":6098},[5280,38033,38034],{"class":5424},"            Detail",[5280,38036,6805],{"class":5452},[5280,38038,38039],{"class":5297},"\"A user with this email address already exists in the system\"",[5280,38041,6796],{"class":5452},[5280,38043,38044,38047,38049,38051,38053,38055,38057,38059],{"class":5282,"line":6114},[5280,38045,38046],{"class":5424},"            Instance",[5280,38048,6790],{"class":5452},[5280,38050,6007],{"class":5424},[5280,38052,5218],{"class":5452},[5280,38054,6083],{"class":5424},[5280,38056,5218],{"class":5452},[5280,38058,6088],{"class":5424},[5280,38060,6796],{"class":5452},[5280,38062,38063],{"class":5282,"line":6130},[5280,38064,6248],{"class":5452},[5280,38066,38067,38069,38071,38073,38075,38077,38079,38081,38083,38085,38087],{"class":5282,"line":6159},[5280,38068,6117],{"class":5436},[5280,38070,21385],{"class":5424},[5280,38072,5218],{"class":5452},[5280,38074,37236],{"class":5293},[5280,38076,5835],{"class":5452},[5280,38078,6298],{"class":5424},[5280,38080,5887],{"class":5452},[5280,38082,22144],{"class":5424},[5280,38084,5218],{"class":5452},[5280,38086,25960],{"class":5424},[5280,38088,37251],{"class":5452},[5280,38090,38091,38093,38095,38097],{"class":5282,"line":6187},[5280,38092,37983],{"class":5424},[5280,38094,5829],{"class":5452},[5280,38096,37988],{"class":5758},[5280,38098,6781],{"class":5452},[5280,38100,38101,38103,38105,38108],{"class":5282,"line":6193},[5280,38102,37995],{"class":5424},[5280,38104,9768],{"class":5452},[5280,38106,38107],{"class":5297},"\"https:\u002F\u002Fapi.example.com\u002Ferrors\u002Finvalid-credentials\"",[5280,38109,6796],{"class":5452},[5280,38111,38112,38114,38116,38119],{"class":5282,"line":6207},[5280,38113,38007],{"class":5424},[5280,38115,13867],{"class":5452},[5280,38117,38118],{"class":5297},"\"Invalid Credentials\"",[5280,38120,6796],{"class":5452},[5280,38122,38123,38125,38127,38129,38131,38133],{"class":5282,"line":6221},[5280,38124,38019],{"class":5424},[5280,38126,6805],{"class":5452},[5280,38128,5952],{"class":5424},[5280,38130,5218],{"class":5452},[5280,38132,15133],{"class":5424},[5280,38134,6796],{"class":5452},[5280,38136,38137,38139,38141,38144],{"class":5282,"line":6233},[5280,38138,38034],{"class":5424},[5280,38140,6805],{"class":5452},[5280,38142,38143],{"class":5297},"\"The provided email or password is incorrect\"",[5280,38145,6796],{"class":5452},[5280,38147,38148,38150,38152,38154,38156,38158,38160,38162],{"class":5282,"line":6245},[5280,38149,38046],{"class":5424},[5280,38151,6790],{"class":5452},[5280,38153,6007],{"class":5424},[5280,38155,5218],{"class":5452},[5280,38157,6083],{"class":5424},[5280,38159,5218],{"class":5452},[5280,38161,6088],{"class":5424},[5280,38163,6796],{"class":5452},[5280,38165,38166],{"class":5282,"line":6251},[5280,38167,6248],{"class":5452},[5280,38169,38170,38172],{"class":5282,"line":6256},[5280,38171,6515],{"class":5436},[5280,38173,6127],{"class":5452},[5280,38175,38176,38178,38180,38182],{"class":5282,"line":6308},[5280,38177,37983],{"class":5424},[5280,38179,5829],{"class":5452},[5280,38181,37988],{"class":5758},[5280,38183,6781],{"class":5452},[5280,38185,38186,38188,38190,38193],{"class":5282,"line":6339},[5280,38187,37995],{"class":5424},[5280,38189,9768],{"class":5452},[5280,38191,38192],{"class":5297},"\"https:\u002F\u002Fapi.example.com\u002Ferrors\u002Finternal\"",[5280,38194,6796],{"class":5452},[5280,38196,38197,38199,38201,38203],{"class":5282,"line":6371},[5280,38198,38007],{"class":5424},[5280,38200,13867],{"class":5452},[5280,38202,33004],{"class":5297},[5280,38204,6796],{"class":5452},[5280,38206,38207,38209,38211,38213,38215,38217],{"class":5282,"line":6409},[5280,38208,38019],{"class":5424},[5280,38210,6805],{"class":5452},[5280,38212,5952],{"class":5424},[5280,38214,5218],{"class":5452},[5280,38216,10246],{"class":5424},[5280,38218,6796],{"class":5452},[5280,38220,38221,38223,38225,38228],{"class":5282,"line":6415},[5280,38222,38034],{"class":5424},[5280,38224,6805],{"class":5452},[5280,38226,38227],{"class":5297},"\"An unexpected error occurred\"",[5280,38229,6796],{"class":5452},[5280,38231,38232,38234,38236,38238,38240,38242,38244,38246],{"class":5282,"line":6420},[5280,38233,38046],{"class":5424},[5280,38235,6790],{"class":5452},[5280,38237,6007],{"class":5424},[5280,38239,5218],{"class":5452},[5280,38241,6083],{"class":5424},[5280,38243,5218],{"class":5452},[5280,38245,6088],{"class":5424},[5280,38247,6796],{"class":5452},[5280,38249,38250],{"class":5282,"line":6425},[5280,38251,6248],{"class":5452},[5280,38253,38254],{"class":5282,"line":6450},[5280,38255,6553],{"class":5452},[5280,38257,38258],{"class":5282,"line":6471},[5280,38259,6724],{"class":5452},[5280,38261,38262,38265,38267,38269,38271,38273],{"class":5282,"line":6507},[5280,38263,38264],{"class":5293},"    respondWithProblem",[5280,38266,5835],{"class":5452},[5280,38268,5994],{"class":5424},[5280,38270,5887],{"class":5452},[5280,38272,37816],{"class":5424},[5280,38274,5598],{"class":5452},[5280,38276,38277],{"class":5282,"line":6512},[5280,38278,5976],{"class":5452},[8342,38280,38281,38286],{},[5262,38282,38283],{},[5227,38284,38285],{},"Переваги RFC 7807:",[5194,38287,38288,38291,38297,38300],{},[5197,38289,38290],{},"Стандартизований формат, зрозумілий багатьом клієнтам",[5197,38292,38293,38294,38296],{},"Машиночитаємі ",[5215,38295,7921],{}," для автоматичної обробки",[5197,38298,38299],{},"Детальна інформація для діагностики",[5197,38301,38302],{},"Підтримка у багатьох бібліотеках та фреймворках",[5254,38304],{},[5257,38306,38308],{"id":38307},"context-cancellation-скасування-операцій-на-практиці","Context Cancellation: скасування операцій на практиці",[5266,38310,38312],{"id":38311},"навіщо-потрібно-скасування","Навіщо потрібно скасування?",[5262,38314,38315],{},"Уявіть ситуацію: користувач відкрив сторінку у браузері, браузер почав завантажувати дані з API, але користувач передумав і закрив вкладку. Без механізму скасування сервер продовжить виконувати запит, витрачаючи ресурси на операцію, результат якої вже нікому не потрібен.",[5266,38317,38319],{"id":38318},"базовий-приклад-скасування","Базовий приклад скасування",[5271,38321,38323],{"className":5411,"code":38322,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fusecase\u002Fuser_usecase.go\nfunc (uc *UserUseCase) SearchUsers(ctx context.Context, query string) ([]*domain.User, error) {\n    \u002F\u002F Симуляція довгої операції (наприклад, пошук у великій БД)\n    results := make([]*domain.User, 0)\n    \n    \u002F\u002F Припустимо, ми шукаємо серед 1 мільйона користувачів\n    for i := 0; i \u003C 1000000; i++ {\n        \u002F\u002F Перевіряємо, чи не скасовано контекст\n        select {\n        case \u003C-ctx.Done():\n            \u002F\u002F Клієнт відключився або вийшов таймаут\n            return nil, ctx.Err()\n        default:\n            \u002F\u002F Продовжуємо роботу\n        }\n        \n        \u002F\u002F Перевірка кожної тисячної ітерації (оптимізація)\n        if i%1000 == 0 {\n            select {\n            case \u003C-ctx.Done():\n                return nil, ctx.Err()\n            default:\n            }\n        }\n        \n        \u002F\u002F Логіка пошуку...\n        if matchesQuery(i, query) {\n            user := fetchUser(i)\n            results = append(results, user)\n        }\n    }\n    \n    return results, nil\n}\n",[5215,38324,38325,38329,38377,38382,38405,38409,38414,38441,38446,38453,38470,38475,38492,38498,38503,38507,38511,38516,38534,38541,38556,38573,38580,38584,38588,38592,38597,38614,38629,38649,38653,38657,38661,38672],{"__ignoreMap":5276},[5280,38326,38327],{"class":5282,"line":5283},[5280,38328,21952],{"class":5286},[5280,38330,38331,38333,38335,38337,38339,38341,38343,38346,38348,38350,38352,38354,38356,38358,38360,38362,38365,38367,38369,38371,38373,38375],{"class":5282,"line":5290},[5280,38332,5862],{"class":5619},[5280,38334,5720],{"class":5452},[5280,38336,22107],{"class":5424},[5280,38338,8072],{"class":5452},[5280,38340,22065],{"class":5758},[5280,38342,7315],{"class":5452},[5280,38344,38345],{"class":5293},"SearchUsers",[5280,38347,5835],{"class":5452},[5280,38349,15441],{"class":5424},[5280,38351,21723],{"class":5758},[5280,38353,5218],{"class":5452},[5280,38355,15393],{"class":5758},[5280,38357,5887],{"class":5452},[5280,38359,23958],{"class":5424},[5280,38361,6239],{"class":5758},[5280,38363,38364],{"class":5452},") ([]*",[5280,38366,22144],{"class":5758},[5280,38368,5218],{"class":5452},[5280,38370,17513],{"class":5758},[5280,38372,5887],{"class":5452},[5280,38374,9567],{"class":5758},[5280,38376,6020],{"class":5452},[5280,38378,38379],{"class":5282,"line":5301},[5280,38380,38381],{"class":5286},"    \u002F\u002F Симуляція довгої операції (наприклад, пошук у великій БД)\n",[5280,38383,38384,38387,38389,38391,38393,38395,38397,38399,38401,38403],{"class":5282,"line":5309},[5280,38385,38386],{"class":5424},"    results",[5280,38388,6029],{"class":5452},[5280,38390,5832],{"class":5293},[5280,38392,19318],{"class":5452},[5280,38394,22144],{"class":5758},[5280,38396,5218],{"class":5452},[5280,38398,17513],{"class":5758},[5280,38400,5887],{"class":5452},[5280,38402,11413],{"class":5439},[5280,38404,5598],{"class":5452},[5280,38406,38407],{"class":5282,"line":5316},[5280,38408,6724],{"class":5452},[5280,38410,38411],{"class":5282,"line":5322},[5280,38412,38413],{"class":5286},"    \u002F\u002F Припустимо, ми шукаємо серед 1 мільйона користувачів\n",[5280,38415,38416,38418,38420,38422,38424,38426,38428,38431,38434,38436,38438],{"class":5282,"line":5336},[5280,38417,11383],{"class":5436},[5280,38419,11386],{"class":5424},[5280,38421,6029],{"class":5452},[5280,38423,11413],{"class":5439},[5280,38425,11404],{"class":5452},[5280,38427,11407],{"class":5424},[5280,38429,38430],{"class":5452}," \u003C ",[5280,38432,38433],{"class":5439},"1000000",[5280,38435,11404],{"class":5452},[5280,38437,11407],{"class":5424},[5280,38439,38440],{"class":5452},"++ {\n",[5280,38442,38443],{"class":5282,"line":5341},[5280,38444,38445],{"class":5286},"        \u002F\u002F Перевіряємо, чи не скасовано контекст\n",[5280,38447,38448,38451],{"class":5282,"line":5347},[5280,38449,38450],{"class":5436},"        select",[5280,38452,6111],{"class":5452},[5280,38454,38455,38457,38460,38462,38464,38467],{"class":5282,"line":5358},[5280,38456,32899],{"class":5436},[5280,38458,38459],{"class":5452}," \u003C-",[5280,38461,15441],{"class":5424},[5280,38463,5218],{"class":5452},[5280,38465,38466],{"class":5293},"Done",[5280,38468,38469],{"class":5452},"():\n",[5280,38471,38472],{"class":5282,"line":5368},[5280,38473,38474],{"class":5286},"            \u002F\u002F Клієнт відключився або вийшов таймаут\n",[5280,38476,38477,38479,38481,38483,38485,38487,38490],{"class":5282,"line":5378},[5280,38478,20309],{"class":5436},[5280,38480,20192],{"class":5619},[5280,38482,5887],{"class":5452},[5280,38484,15441],{"class":5424},[5280,38486,5218],{"class":5452},[5280,38488,38489],{"class":5293},"Err",[5280,38491,6039],{"class":5452},[5280,38493,38494,38496],{"class":5282,"line":5391},[5280,38495,32985],{"class":5436},[5280,38497,6127],{"class":5452},[5280,38499,38500],{"class":5282,"line":5705},[5280,38501,38502],{"class":5286},"            \u002F\u002F Продовжуємо роботу\n",[5280,38504,38505],{"class":5282,"line":5820},[5280,38506,6248],{"class":5452},[5280,38508,38509],{"class":5282,"line":5854},[5280,38510,6190],{"class":5452},[5280,38512,38513],{"class":5282,"line":5859},[5280,38514,38515],{"class":5286},"        \u002F\u002F Перевірка кожної тисячної ітерації (оптимізація)\n",[5280,38517,38518,38520,38522,38525,38528,38530,38532],{"class":5282,"line":5871},[5280,38519,6259],{"class":5436},[5280,38521,11386],{"class":5424},[5280,38523,38524],{"class":5452},"%",[5280,38526,38527],{"class":5439},"1000",[5280,38529,7029],{"class":5452},[5280,38531,11413],{"class":5439},[5280,38533,6111],{"class":5452},[5280,38535,38536,38539],{"class":5282,"line":5895},[5280,38537,38538],{"class":5436},"            select",[5280,38540,6111],{"class":5452},[5280,38542,38543,38546,38548,38550,38552,38554],{"class":5282,"line":5916},[5280,38544,38545],{"class":5436},"            case",[5280,38547,38459],{"class":5452},[5280,38549,15441],{"class":5424},[5280,38551,5218],{"class":5452},[5280,38553,38466],{"class":5293},[5280,38555,38469],{"class":5452},[5280,38557,38558,38561,38563,38565,38567,38569,38571],{"class":5282,"line":5921},[5280,38559,38560],{"class":5436},"                return",[5280,38562,20192],{"class":5619},[5280,38564,5887],{"class":5452},[5280,38566,15441],{"class":5424},[5280,38568,5218],{"class":5452},[5280,38570,38489],{"class":5293},[5280,38572,6039],{"class":5452},[5280,38574,38575,38578],{"class":5282,"line":5939},[5280,38576,38577],{"class":5436},"            default",[5280,38579,6127],{"class":5452},[5280,38581,38582],{"class":5282,"line":5973},[5280,38583,10717],{"class":5452},[5280,38585,38586],{"class":5282,"line":5979},[5280,38587,6248],{"class":5452},[5280,38589,38590],{"class":5282,"line":5984},[5280,38591,6190],{"class":5452},[5280,38593,38594],{"class":5282,"line":6023},[5280,38595,38596],{"class":5286},"        \u002F\u002F Логіка пошуку...\n",[5280,38598,38599,38601,38604,38606,38608,38610,38612],{"class":5282,"line":6042},[5280,38600,6259],{"class":5436},[5280,38602,38603],{"class":5293}," matchesQuery",[5280,38605,5835],{"class":5452},[5280,38607,11407],{"class":5424},[5280,38609,5887],{"class":5452},[5280,38611,23958],{"class":5424},[5280,38613,6020],{"class":5452},[5280,38615,38616,38618,38620,38623,38625,38627],{"class":5282,"line":6093},[5280,38617,27409],{"class":5424},[5280,38619,6029],{"class":5452},[5280,38621,38622],{"class":5293},"fetchUser",[5280,38624,5835],{"class":5452},[5280,38626,11407],{"class":5424},[5280,38628,5598],{"class":5452},[5280,38630,38631,38634,38636,38638,38640,38643,38645,38647],{"class":5282,"line":6098},[5280,38632,38633],{"class":5424},"            results",[5280,38635,5829],{"class":5452},[5280,38637,19365],{"class":5293},[5280,38639,5835],{"class":5452},[5280,38641,38642],{"class":5424},"results",[5280,38644,5887],{"class":5452},[5280,38646,6292],{"class":5424},[5280,38648,5598],{"class":5452},[5280,38650,38651],{"class":5282,"line":6114},[5280,38652,6248],{"class":5452},[5280,38654,38655],{"class":5282,"line":6130},[5280,38656,6553],{"class":5452},[5280,38658,38659],{"class":5282,"line":6159},[5280,38660,6724],{"class":5452},[5280,38662,38663,38665,38668,38670],{"class":5282,"line":6187},[5280,38664,7328],{"class":5436},[5280,38666,38667],{"class":5424}," results",[5280,38669,5887],{"class":5452},[5280,38671,20459],{"class":5619},[5280,38673,38674],{"class":5282,"line":6193},[5280,38675,5976],{"class":5452},[5266,38677,38679],{"id":38678},"встановлення-таймаутів-у-handler","Встановлення таймаутів у Handler",[5271,38681,38683],{"className":5411,"code":38682,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fdelivery\u002Fhttp\u002Fuser_handler.go\nfunc (h *UserHandler) SearchUsers(w http.ResponseWriter, r *http.Request) {\n    query := r.URL.Query().Get(\"q\")\n    \n    \u002F\u002F Встановлюємо таймаут 5 секунд для операції\n    ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)\n    defer cancel()  \u002F\u002F Завжди викликаємо cancel для очищення ресурсів\n    \n    users, err := h.useCase.SearchUsers(ctx, query)\n    if err != nil {\n        \u002F\u002F Перевіряємо, чи це таймаут\n        if errors.Is(err, context.DeadlineExceeded) {\n            respondWithError(w, http.StatusRequestTimeout, \"Search timeout exceeded\")\n            return\n        }\n        \n        \u002F\u002F Перевіряємо, чи це скасування\n        if errors.Is(err, context.Canceled) {\n            \u002F\u002F Клієнт відключився - просто логуємо\n            log.Printf(\"Client disconnected during search\")\n            return\n        }\n        \n        handleUseCaseError(w, err)\n        return\n    }\n    \n    respondWithJSON(w, http.StatusOK, users)\n}\n",[5215,38684,38685,38689,38729,38757,38761,38766,38807,38820,38824,38854,38866,38871,38896,38921,38925,38929,38933,38938,38963,38968,38983,38987,38991,38995,39009,39013,39017,39021,39043],{"__ignoreMap":5276},[5280,38686,38687],{"class":5282,"line":5283},[5280,38688,24955],{"class":5286},[5280,38690,38691,38693,38695,38697,38699,38701,38703,38705,38707,38709,38711,38713,38715,38717,38719,38721,38723,38725,38727],{"class":5282,"line":5290},[5280,38692,5862],{"class":5619},[5280,38694,5720],{"class":5452},[5280,38696,8069],{"class":5424},[5280,38698,8072],{"class":5452},[5280,38700,25066],{"class":5758},[5280,38702,7315],{"class":5452},[5280,38704,38345],{"class":5293},[5280,38706,5835],{"class":5452},[5280,38708,5994],{"class":5424},[5280,38710,5997],{"class":5758},[5280,38712,5218],{"class":5452},[5280,38714,6002],{"class":5758},[5280,38716,5887],{"class":5452},[5280,38718,6007],{"class":5424},[5280,38720,6010],{"class":5452},[5280,38722,5952],{"class":5758},[5280,38724,5218],{"class":5452},[5280,38726,6017],{"class":5758},[5280,38728,6020],{"class":5452},[5280,38730,38731,38733,38735,38737,38739,38741,38743,38746,38748,38750,38752,38755],{"class":5282,"line":5301},[5280,38732,23903],{"class":5424},[5280,38734,6029],{"class":5452},[5280,38736,6007],{"class":5424},[5280,38738,5218],{"class":5452},[5280,38740,6083],{"class":5424},[5280,38742,5218],{"class":5452},[5280,38744,38745],{"class":5293},"Query",[5280,38747,6141],{"class":5452},[5280,38749,12252],{"class":5293},[5280,38751,5835],{"class":5452},[5280,38753,38754],{"class":5297},"\"q\"",[5280,38756,5598],{"class":5452},[5280,38758,38759],{"class":5282,"line":5309},[5280,38760,6724],{"class":5452},[5280,38762,38763],{"class":5282,"line":5316},[5280,38764,38765],{"class":5286},"    \u002F\u002F Встановлюємо таймаут 5 секунд для операції\n",[5280,38767,38768,38771,38773,38776,38778,38780,38782,38785,38787,38789,38791,38793,38795,38797,38799,38801,38803,38805],{"class":5282,"line":5322},[5280,38769,38770],{"class":5424},"    ctx",[5280,38772,5887],{"class":5452},[5280,38774,38775],{"class":5424},"cancel",[5280,38777,6029],{"class":5452},[5280,38779,15379],{"class":5424},[5280,38781,5218],{"class":5452},[5280,38783,38784],{"class":5293},"WithTimeout",[5280,38786,5835],{"class":5452},[5280,38788,6007],{"class":5424},[5280,38790,5218],{"class":5452},[5280,38792,15393],{"class":5293},[5280,38794,15396],{"class":5452},[5280,38796,14535],{"class":5439},[5280,38798,8072],{"class":5452},[5280,38800,5587],{"class":5424},[5280,38802,5218],{"class":5452},[5280,38804,14252],{"class":5424},[5280,38806,5598],{"class":5452},[5280,38808,38809,38811,38814,38817],{"class":5282,"line":5336},[5280,38810,7217],{"class":5436},[5280,38812,38813],{"class":5293}," cancel",[5280,38815,38816],{"class":5452},"()  ",[5280,38818,38819],{"class":5286},"\u002F\u002F Завжди викликаємо cancel для очищення ресурсів\n",[5280,38821,38822],{"class":5282,"line":5341},[5280,38823,6724],{"class":5452},[5280,38825,38826,38828,38830,38832,38834,38836,38838,38840,38842,38844,38846,38848,38850,38852],{"class":5282,"line":5347},[5280,38827,18625],{"class":5424},[5280,38829,5887],{"class":5452},[5280,38831,6298],{"class":5424},[5280,38833,6029],{"class":5452},[5280,38835,11326],{"class":5424},[5280,38837,5218],{"class":5452},[5280,38839,25053],{"class":5424},[5280,38841,5218],{"class":5452},[5280,38843,38345],{"class":5293},[5280,38845,5835],{"class":5452},[5280,38847,15441],{"class":5424},[5280,38849,5887],{"class":5452},[5280,38851,23958],{"class":5424},[5280,38853,5598],{"class":5452},[5280,38855,38856,38858,38860,38862,38864],{"class":5282,"line":5358},[5280,38857,7023],{"class":5436},[5280,38859,6262],{"class":5424},[5280,38861,6301],{"class":5452},[5280,38863,5967],{"class":5619},[5280,38865,6111],{"class":5452},[5280,38867,38868],{"class":5282,"line":5368},[5280,38869,38870],{"class":5286},"        \u002F\u002F Перевіряємо, чи це таймаут\n",[5280,38872,38873,38875,38877,38879,38881,38883,38885,38887,38889,38891,38894],{"class":5282,"line":5378},[5280,38874,6259],{"class":5436},[5280,38876,21385],{"class":5424},[5280,38878,5218],{"class":5452},[5280,38880,37236],{"class":5293},[5280,38882,5835],{"class":5452},[5280,38884,6298],{"class":5424},[5280,38886,5887],{"class":5452},[5280,38888,15379],{"class":5424},[5280,38890,5218],{"class":5452},[5280,38892,38893],{"class":5424},"DeadlineExceeded",[5280,38895,6020],{"class":5452},[5280,38897,38898,38901,38903,38905,38907,38909,38911,38914,38916,38919],{"class":5282,"line":5391},[5280,38899,38900],{"class":5293},"            respondWithError",[5280,38902,5835],{"class":5452},[5280,38904,5994],{"class":5424},[5280,38906,5887],{"class":5452},[5280,38908,5952],{"class":5424},[5280,38910,5218],{"class":5452},[5280,38912,38913],{"class":5424},"StatusRequestTimeout",[5280,38915,5887],{"class":5452},[5280,38917,38918],{"class":5297},"\"Search timeout exceeded\"",[5280,38920,5598],{"class":5452},[5280,38922,38923],{"class":5282,"line":5705},[5280,38924,6412],{"class":5436},[5280,38926,38927],{"class":5282,"line":5820},[5280,38928,6248],{"class":5452},[5280,38930,38931],{"class":5282,"line":5854},[5280,38932,6190],{"class":5452},[5280,38934,38935],{"class":5282,"line":5859},[5280,38936,38937],{"class":5286},"        \u002F\u002F Перевіряємо, чи це скасування\n",[5280,38939,38940,38942,38944,38946,38948,38950,38952,38954,38956,38958,38961],{"class":5282,"line":5871},[5280,38941,6259],{"class":5436},[5280,38943,21385],{"class":5424},[5280,38945,5218],{"class":5452},[5280,38947,37236],{"class":5293},[5280,38949,5835],{"class":5452},[5280,38951,6298],{"class":5424},[5280,38953,5887],{"class":5452},[5280,38955,15379],{"class":5424},[5280,38957,5218],{"class":5452},[5280,38959,38960],{"class":5424},"Canceled",[5280,38962,6020],{"class":5452},[5280,38964,38965],{"class":5282,"line":5895},[5280,38966,38967],{"class":5286},"            \u002F\u002F Клієнт відключився - просто логуємо\n",[5280,38969,38970,38972,38974,38976,38978,38981],{"class":5282,"line":5916},[5280,38971,6342],{"class":5424},[5280,38973,5218],{"class":5452},[5280,38975,6049],{"class":5293},[5280,38977,5835],{"class":5452},[5280,38979,38980],{"class":5297},"\"Client disconnected during search\"",[5280,38982,5598],{"class":5452},[5280,38984,38985],{"class":5282,"line":5921},[5280,38986,6412],{"class":5436},[5280,38988,38989],{"class":5282,"line":5939},[5280,38990,6248],{"class":5452},[5280,38992,38993],{"class":5282,"line":5973},[5280,38994,6190],{"class":5452},[5280,38996,38997,38999,39001,39003,39005,39007],{"class":5282,"line":5979},[5280,38998,36970],{"class":5293},[5280,39000,5835],{"class":5452},[5280,39002,5994],{"class":5424},[5280,39004,5887],{"class":5452},[5280,39006,6298],{"class":5424},[5280,39008,5598],{"class":5452},[5280,39010,39011],{"class":5282,"line":5984},[5280,39012,15875],{"class":5436},[5280,39014,39015],{"class":5282,"line":6023},[5280,39016,6553],{"class":5452},[5280,39018,39019],{"class":5282,"line":6042},[5280,39020,6724],{"class":5452},[5280,39022,39023,39025,39027,39029,39031,39033,39035,39037,39039,39041],{"class":5282,"line":6093},[5280,39024,36997],{"class":5293},[5280,39026,5835],{"class":5452},[5280,39028,5994],{"class":5424},[5280,39030,5887],{"class":5452},[5280,39032,5952],{"class":5424},[5280,39034,5218],{"class":5452},[5280,39036,9775],{"class":5424},[5280,39038,5887],{"class":5452},[5280,39040,6182],{"class":5424},[5280,39042,5598],{"class":5452},[5280,39044,39045],{"class":5282,"line":6098},[5280,39046,5976],{"class":5452},[5266,39048,39050],{"id":39049},"передача-контексту-у-запити-до-бд","Передача контексту у запити до БД",[5271,39052,39054],{"className":5411,"code":39053,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Frepository\u002Fpostgres\u002Fuser_repository.go\nfunc (r *UserRepository) SearchByName(ctx context.Context, name string) ([]*domain.User, error) {\n    \u002F\u002F PostgreSQL автоматично скасує запит, коли ctx буде скасовано\n    query := `\n        SELECT id, name, email, created_at, updated_at \n        FROM users \n        WHERE name ILIKE $1\n        LIMIT 100\n    `\n    \n    rows, err := r.db.QueryContext(ctx, query, \"%\"+name+\"%\")\n    if err != nil {\n        if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {\n            return nil, err\n        }\n        return nil, fmt.Errorf(\"search query failed: %w\", err)\n    }\n    defer rows.Close()\n    \n    users := make([]*domain.User, 0)\n    for rows.Next() {\n        \u002F\u002F Перевіряємо контекст навіть під час ітерації\n        if ctx.Err() != nil {\n            return nil, ctx.Err()\n        }\n        \n        var user domain.User\n        if err := rows.Scan(&user.ID, &user.Name, &user.Email, &user.CreatedAt, &user.UpdatedAt); err != nil {\n            return nil, fmt.Errorf(\"failed to scan user: %w\", err)\n        }\n        users = append(users, &user)\n    }\n    \n    if err := rows.Err(); err != nil {\n        return nil, fmt.Errorf(\"rows iteration error: %w\", err)\n    }\n    \n    return users, nil\n}\n",[5215,39055,39056,39060,39107,39112,39120,39125,39130,39135,39140,39144,39148,39192,39204,39249,39259,39263,39292,39296,39308,39312,39334,39347,39352,39369,39385,39389,39393,39405,39470,39499,39503,39521,39525,39529,39553,39582,39586,39590,39600],{"__ignoreMap":5276},[5280,39057,39058],{"class":5282,"line":5283},[5280,39059,23721],{"class":5286},[5280,39061,39062,39064,39066,39068,39070,39072,39074,39077,39079,39081,39083,39085,39087,39089,39091,39093,39095,39097,39099,39101,39103,39105],{"class":5282,"line":5290},[5280,39063,5862],{"class":5619},[5280,39065,5720],{"class":5452},[5280,39067,23026],{"class":5424},[5280,39069,8072],{"class":5452},[5280,39071,22060],{"class":5758},[5280,39073,7315],{"class":5452},[5280,39075,39076],{"class":5293},"SearchByName",[5280,39078,5835],{"class":5452},[5280,39080,15441],{"class":5424},[5280,39082,21723],{"class":5758},[5280,39084,5218],{"class":5452},[5280,39086,15393],{"class":5758},[5280,39088,5887],{"class":5452},[5280,39090,20121],{"class":5424},[5280,39092,6239],{"class":5758},[5280,39094,38364],{"class":5452},[5280,39096,22144],{"class":5758},[5280,39098,5218],{"class":5452},[5280,39100,17513],{"class":5758},[5280,39102,5887],{"class":5452},[5280,39104,9567],{"class":5758},[5280,39106,6020],{"class":5452},[5280,39108,39109],{"class":5282,"line":5301},[5280,39110,39111],{"class":5286},"    \u002F\u002F PostgreSQL автоматично скасує запит, коли ctx буде скасовано\n",[5280,39113,39114,39116,39118],{"class":5282,"line":5309},[5280,39115,23903],{"class":5424},[5280,39117,6029],{"class":5452},[5280,39119,23908],{"class":5297},[5280,39121,39122],{"class":5282,"line":5316},[5280,39123,39124],{"class":5297},"        SELECT id, name, email, created_at, updated_at \n",[5280,39126,39127],{"class":5282,"line":5322},[5280,39128,39129],{"class":5297},"        FROM users \n",[5280,39131,39132],{"class":5282,"line":5336},[5280,39133,39134],{"class":5297},"        WHERE name ILIKE $1\n",[5280,39136,39137],{"class":5282,"line":5341},[5280,39138,39139],{"class":5297},"        LIMIT 100\n",[5280,39141,39142],{"class":5282,"line":5347},[5280,39143,23923],{"class":5297},[5280,39145,39146],{"class":5282,"line":5358},[5280,39147,6724],{"class":5452},[5280,39149,39150,39152,39154,39156,39158,39160,39162,39164,39166,39169,39171,39173,39175,39177,39179,39182,39184,39186,39188,39190],{"class":5282,"line":5368},[5280,39151,24765],{"class":5424},[5280,39153,5887],{"class":5452},[5280,39155,6298],{"class":5424},[5280,39157,6029],{"class":5452},[5280,39159,6007],{"class":5424},[5280,39161,5218],{"class":5452},[5280,39163,23813],{"class":5424},[5280,39165,5218],{"class":5452},[5280,39167,39168],{"class":5293},"QueryContext",[5280,39170,5835],{"class":5452},[5280,39172,15441],{"class":5424},[5280,39174,5887],{"class":5452},[5280,39176,23958],{"class":5424},[5280,39178,5887],{"class":5452},[5280,39180,39181],{"class":5297},"\"%\"",[5280,39183,21603],{"class":5452},[5280,39185,20121],{"class":5424},[5280,39187,21603],{"class":5452},[5280,39189,39181],{"class":5297},[5280,39191,5598],{"class":5452},[5280,39193,39194,39196,39198,39200,39202],{"class":5282,"line":5378},[5280,39195,7023],{"class":5436},[5280,39197,6262],{"class":5424},[5280,39199,6301],{"class":5452},[5280,39201,5967],{"class":5619},[5280,39203,6111],{"class":5452},[5280,39205,39206,39208,39210,39212,39214,39216,39218,39220,39222,39224,39226,39229,39231,39233,39235,39237,39239,39241,39243,39245,39247],{"class":5282,"line":5391},[5280,39207,6259],{"class":5436},[5280,39209,21385],{"class":5424},[5280,39211,5218],{"class":5452},[5280,39213,37236],{"class":5293},[5280,39215,5835],{"class":5452},[5280,39217,6298],{"class":5424},[5280,39219,5887],{"class":5452},[5280,39221,15379],{"class":5424},[5280,39223,5218],{"class":5452},[5280,39225,38960],{"class":5424},[5280,39227,39228],{"class":5452},") || ",[5280,39230,20197],{"class":5424},[5280,39232,5218],{"class":5452},[5280,39234,37236],{"class":5293},[5280,39236,5835],{"class":5452},[5280,39238,6298],{"class":5424},[5280,39240,5887],{"class":5452},[5280,39242,15379],{"class":5424},[5280,39244,5218],{"class":5452},[5280,39246,38893],{"class":5424},[5280,39248,6020],{"class":5452},[5280,39250,39251,39253,39255,39257],{"class":5282,"line":5705},[5280,39252,20309],{"class":5436},[5280,39254,20192],{"class":5619},[5280,39256,5887],{"class":5452},[5280,39258,22296],{"class":5424},[5280,39260,39261],{"class":5282,"line":5820},[5280,39262,6248],{"class":5452},[5280,39264,39265,39267,39269,39271,39273,39275,39277,39279,39282,39284,39286,39288,39290],{"class":5282,"line":5854},[5280,39266,12200],{"class":5436},[5280,39268,20192],{"class":5619},[5280,39270,5887],{"class":5452},[5280,39272,15518],{"class":5424},[5280,39274,5218],{"class":5452},[5280,39276,15523],{"class":5293},[5280,39278,5835],{"class":5452},[5280,39280,39281],{"class":5297},"\"search query failed: ",[5280,39283,35904],{"class":5424},[5280,39285,6584],{"class":5297},[5280,39287,5887],{"class":5452},[5280,39289,6298],{"class":5424},[5280,39291,5598],{"class":5452},[5280,39293,39294],{"class":5282,"line":5859},[5280,39295,6553],{"class":5452},[5280,39297,39298,39300,39302,39304,39306],{"class":5282,"line":5871},[5280,39299,7217],{"class":5436},[5280,39301,24788],{"class":5424},[5280,39303,5218],{"class":5452},[5280,39305,26574],{"class":5293},[5280,39307,6039],{"class":5452},[5280,39309,39310],{"class":5282,"line":5895},[5280,39311,6724],{"class":5452},[5280,39313,39314,39316,39318,39320,39322,39324,39326,39328,39330,39332],{"class":5282,"line":5916},[5280,39315,18625],{"class":5424},[5280,39317,6029],{"class":5452},[5280,39319,5832],{"class":5293},[5280,39321,19318],{"class":5452},[5280,39323,22144],{"class":5758},[5280,39325,5218],{"class":5452},[5280,39327,17513],{"class":5758},[5280,39329,5887],{"class":5452},[5280,39331,11413],{"class":5439},[5280,39333,5598],{"class":5452},[5280,39335,39336,39338,39340,39342,39345],{"class":5282,"line":5921},[5280,39337,11383],{"class":5436},[5280,39339,24788],{"class":5424},[5280,39341,5218],{"class":5452},[5280,39343,39344],{"class":5293},"Next",[5280,39346,5868],{"class":5452},[5280,39348,39349],{"class":5282,"line":5939},[5280,39350,39351],{"class":5286},"        \u002F\u002F Перевіряємо контекст навіть під час ітерації\n",[5280,39353,39354,39356,39359,39361,39363,39365,39367],{"class":5282,"line":5973},[5280,39355,6259],{"class":5436},[5280,39357,39358],{"class":5424}," ctx",[5280,39360,5218],{"class":5452},[5280,39362,38489],{"class":5293},[5280,39364,27869],{"class":5452},[5280,39366,5967],{"class":5619},[5280,39368,6111],{"class":5452},[5280,39370,39371,39373,39375,39377,39379,39381,39383],{"class":5282,"line":5979},[5280,39372,20309],{"class":5436},[5280,39374,20192],{"class":5619},[5280,39376,5887],{"class":5452},[5280,39378,15441],{"class":5424},[5280,39380,5218],{"class":5452},[5280,39382,38489],{"class":5293},[5280,39384,6039],{"class":5452},[5280,39386,39387],{"class":5282,"line":5984},[5280,39388,6248],{"class":5452},[5280,39390,39391],{"class":5282,"line":6023},[5280,39392,6190],{"class":5452},[5280,39394,39395,39397,39399,39401,39403],{"class":5282,"line":6042},[5280,39396,6210],{"class":5619},[5280,39398,6213],{"class":5424},[5280,39400,22023],{"class":5758},[5280,39402,5218],{"class":5452},[5280,39404,22942],{"class":5758},[5280,39406,39407,39409,39411,39413,39416,39418,39420,39422,39424,39426,39428,39430,39432,39434,39436,39438,39440,39442,39444,39446,39448,39450,39452,39454,39456,39458,39460,39462,39464,39466,39468],{"class":5282,"line":6093},[5280,39408,6259],{"class":5436},[5280,39410,6262],{"class":5424},[5280,39412,6029],{"class":5452},[5280,39414,39415],{"class":5424},"rows",[5280,39417,5218],{"class":5452},[5280,39419,24172],{"class":5293},[5280,39421,6289],{"class":5452},[5280,39423,6292],{"class":5424},[5280,39425,5218],{"class":5452},[5280,39427,18635],{"class":5424},[5280,39429,8153],{"class":5452},[5280,39431,6292],{"class":5424},[5280,39433,5218],{"class":5452},[5280,39435,6502],{"class":5424},[5280,39437,8153],{"class":5452},[5280,39439,6292],{"class":5424},[5280,39441,5218],{"class":5452},[5280,39443,6437],{"class":5424},[5280,39445,8153],{"class":5452},[5280,39447,6292],{"class":5424},[5280,39449,5218],{"class":5452},[5280,39451,24010],{"class":5424},[5280,39453,8153],{"class":5452},[5280,39455,6292],{"class":5424},[5280,39457,5218],{"class":5452},[5280,39459,24021],{"class":5424},[5280,39461,6295],{"class":5452},[5280,39463,6298],{"class":5424},[5280,39465,6301],{"class":5452},[5280,39467,5967],{"class":5619},[5280,39469,6111],{"class":5452},[5280,39471,39472,39474,39476,39478,39480,39482,39484,39486,39489,39491,39493,39495,39497],{"class":5282,"line":6098},[5280,39473,20309],{"class":5436},[5280,39475,20192],{"class":5619},[5280,39477,5887],{"class":5452},[5280,39479,15518],{"class":5424},[5280,39481,5218],{"class":5452},[5280,39483,15523],{"class":5293},[5280,39485,5835],{"class":5452},[5280,39487,39488],{"class":5297},"\"failed to scan user: ",[5280,39490,35904],{"class":5424},[5280,39492,6584],{"class":5297},[5280,39494,5887],{"class":5452},[5280,39496,6298],{"class":5424},[5280,39498,5598],{"class":5452},[5280,39500,39501],{"class":5282,"line":6114},[5280,39502,6248],{"class":5452},[5280,39504,39505,39507,39509,39511,39513,39515,39517,39519],{"class":5282,"line":6130},[5280,39506,6428],{"class":5424},[5280,39508,5829],{"class":5452},[5280,39510,19365],{"class":5293},[5280,39512,5835],{"class":5452},[5280,39514,6182],{"class":5424},[5280,39516,8153],{"class":5452},[5280,39518,6292],{"class":5424},[5280,39520,5598],{"class":5452},[5280,39522,39523],{"class":5282,"line":6159},[5280,39524,6553],{"class":5452},[5280,39526,39527],{"class":5282,"line":6187},[5280,39528,6724],{"class":5452},[5280,39530,39531,39533,39535,39537,39539,39541,39543,39545,39547,39549,39551],{"class":5282,"line":6193},[5280,39532,7023],{"class":5436},[5280,39534,6262],{"class":5424},[5280,39536,6029],{"class":5452},[5280,39538,39415],{"class":5424},[5280,39540,5218],{"class":5452},[5280,39542,38489],{"class":5293},[5280,39544,10615],{"class":5452},[5280,39546,6298],{"class":5424},[5280,39548,6301],{"class":5452},[5280,39550,5967],{"class":5619},[5280,39552,6111],{"class":5452},[5280,39554,39555,39557,39559,39561,39563,39565,39567,39569,39572,39574,39576,39578,39580],{"class":5282,"line":6207},[5280,39556,12200],{"class":5436},[5280,39558,20192],{"class":5619},[5280,39560,5887],{"class":5452},[5280,39562,15518],{"class":5424},[5280,39564,5218],{"class":5452},[5280,39566,15523],{"class":5293},[5280,39568,5835],{"class":5452},[5280,39570,39571],{"class":5297},"\"rows iteration error: ",[5280,39573,35904],{"class":5424},[5280,39575,6584],{"class":5297},[5280,39577,5887],{"class":5452},[5280,39579,6298],{"class":5424},[5280,39581,5598],{"class":5452},[5280,39583,39584],{"class":5282,"line":6221},[5280,39585,6553],{"class":5452},[5280,39587,39588],{"class":5282,"line":6233},[5280,39589,6724],{"class":5452},[5280,39591,39592,39594,39596,39598],{"class":5282,"line":6245},[5280,39593,7328],{"class":5436},[5280,39595,5826],{"class":5424},[5280,39597,5887],{"class":5452},[5280,39599,20459],{"class":5619},[5280,39601,39602],{"class":5282,"line":6251},[5280,39603,5976],{"class":5452},[5266,39605,39607],{"id":39606},"контекст-у-горутинах","Контекст у горутинах",[11195,39609,39610,39613,39614,39616],{},[5227,39611,39612],{},"ВАЖЛИВО:"," Ніколи не передавайте ",[5215,39615,14799],{}," напряму в горутину! HTTP-контекст може бути скасовано після завершення handler, а горутина ще працюватиме.",[5262,39618,39619],{},[5227,39620,39621],{},"❌ Неправильно:",[5271,39623,39625],{"className":5411,"code":39624,"language":5325,"meta":5276,"style":5276},"func (h *Handler) ProcessAsync(w http.ResponseWriter, r *http.Request) {\n    go func() {\n        \u002F\u002F ❌ r.Context() може бути скасовано!\n        result := h.service.LongOperation(r.Context())\n        \u002F\u002F Зберегти result...\n    }()\n    \n    w.WriteHeader(http.StatusAccepted)\n    fmt.Fprintln(w, \"Processing started\")\n}\n",[5215,39626,39627,39668,39677,39682,39711,39716,39721,39725,39744,39763],{"__ignoreMap":5276},[5280,39628,39629,39631,39633,39635,39637,39639,39641,39644,39646,39648,39650,39652,39654,39656,39658,39660,39662,39664,39666],{"class":5282,"line":5283},[5280,39630,5862],{"class":5619},[5280,39632,5720],{"class":5452},[5280,39634,8069],{"class":5424},[5280,39636,8072],{"class":5452},[5280,39638,7935],{"class":5758},[5280,39640,7315],{"class":5452},[5280,39642,39643],{"class":5293},"ProcessAsync",[5280,39645,5835],{"class":5452},[5280,39647,5994],{"class":5424},[5280,39649,5997],{"class":5758},[5280,39651,5218],{"class":5452},[5280,39653,6002],{"class":5758},[5280,39655,5887],{"class":5452},[5280,39657,6007],{"class":5424},[5280,39659,6010],{"class":5452},[5280,39661,5952],{"class":5758},[5280,39663,5218],{"class":5452},[5280,39665,6017],{"class":5758},[5280,39667,6020],{"class":5452},[5280,39669,39670,39673,39675],{"class":5282,"line":5290},[5280,39671,39672],{"class":5436},"    go",[5280,39674,7331],{"class":5619},[5280,39676,5868],{"class":5452},[5280,39678,39679],{"class":5282,"line":5301},[5280,39680,39681],{"class":5286},"        \u002F\u002F ❌ r.Context() може бути скасовано!\n",[5280,39683,39684,39687,39689,39691,39693,39696,39698,39701,39703,39705,39707,39709],{"class":5282,"line":5309},[5280,39685,39686],{"class":5424},"        result",[5280,39688,6029],{"class":5452},[5280,39690,11326],{"class":5424},[5280,39692,5218],{"class":5452},[5280,39694,39695],{"class":5424},"service",[5280,39697,5218],{"class":5452},[5280,39699,39700],{"class":5293},"LongOperation",[5280,39702,5835],{"class":5452},[5280,39704,6007],{"class":5424},[5280,39706,5218],{"class":5452},[5280,39708,15393],{"class":5293},[5280,39710,10669],{"class":5452},[5280,39712,39713],{"class":5282,"line":5316},[5280,39714,39715],{"class":5286},"        \u002F\u002F Зберегти result...\n",[5280,39717,39718],{"class":5282,"line":5322},[5280,39719,39720],{"class":5452},"    }()\n",[5280,39722,39723],{"class":5282,"line":5336},[5280,39724,6724],{"class":5452},[5280,39726,39727,39729,39731,39733,39735,39737,39739,39742],{"class":5282,"line":5341},[5280,39728,6730],{"class":5424},[5280,39730,5218],{"class":5452},[5280,39732,6457],{"class":5293},[5280,39734,5835],{"class":5452},[5280,39736,5952],{"class":5424},[5280,39738,5218],{"class":5452},[5280,39740,39741],{"class":5424},"StatusAccepted",[5280,39743,5598],{"class":5452},[5280,39745,39746,39748,39750,39752,39754,39756,39758,39761],{"class":5282,"line":5347},[5280,39747,5924],{"class":5424},[5280,39749,5218],{"class":5452},[5280,39751,8112],{"class":5293},[5280,39753,5835],{"class":5452},[5280,39755,5994],{"class":5424},[5280,39757,5887],{"class":5452},[5280,39759,39760],{"class":5297},"\"Processing started\"",[5280,39762,5598],{"class":5452},[5280,39764,39765],{"class":5282,"line":5358},[5280,39766,5976],{"class":5452},[5262,39768,39769],{},[5227,39770,39771],{},"✅ Правильно:",[5271,39773,39775],{"className":5411,"code":39774,"language":5325,"meta":5276,"style":5276},"func (h *Handler) ProcessAsync(w http.ResponseWriter, r *http.Request) {\n    \u002F\u002F Створюємо новий контекст, незалежний від HTTP-запиту\n    ctx := context.Background()\n    \n    \u002F\u002F Або з таймаутом для фонової задачі\n    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)\n    \n    go func() {\n        defer cancel()\n        result := h.service.LongOperation(ctx)\n        \u002F\u002F Зберегти result...\n    }()\n    \n    w.WriteHeader(http.StatusAccepted)\n    fmt.Fprintln(w, \"Processing started\")\n}\n",[5215,39776,39777,39817,39822,39836,39840,39845,39884,39888,39896,39904,39926,39930,39934,39938,39956,39974],{"__ignoreMap":5276},[5280,39778,39779,39781,39783,39785,39787,39789,39791,39793,39795,39797,39799,39801,39803,39805,39807,39809,39811,39813,39815],{"class":5282,"line":5283},[5280,39780,5862],{"class":5619},[5280,39782,5720],{"class":5452},[5280,39784,8069],{"class":5424},[5280,39786,8072],{"class":5452},[5280,39788,7935],{"class":5758},[5280,39790,7315],{"class":5452},[5280,39792,39643],{"class":5293},[5280,39794,5835],{"class":5452},[5280,39796,5994],{"class":5424},[5280,39798,5997],{"class":5758},[5280,39800,5218],{"class":5452},[5280,39802,6002],{"class":5758},[5280,39804,5887],{"class":5452},[5280,39806,6007],{"class":5424},[5280,39808,6010],{"class":5452},[5280,39810,5952],{"class":5758},[5280,39812,5218],{"class":5452},[5280,39814,6017],{"class":5758},[5280,39816,6020],{"class":5452},[5280,39818,39819],{"class":5282,"line":5290},[5280,39820,39821],{"class":5286},"    \u002F\u002F Створюємо новий контекст, незалежний від HTTP-запиту\n",[5280,39823,39824,39826,39828,39830,39832,39834],{"class":5282,"line":5301},[5280,39825,38770],{"class":5424},[5280,39827,6029],{"class":5452},[5280,39829,15379],{"class":5424},[5280,39831,5218],{"class":5452},[5280,39833,28910],{"class":5293},[5280,39835,6039],{"class":5452},[5280,39837,39838],{"class":5282,"line":5309},[5280,39839,6724],{"class":5452},[5280,39841,39842],{"class":5282,"line":5316},[5280,39843,39844],{"class":5286},"    \u002F\u002F Або з таймаутом для фонової задачі\n",[5280,39846,39847,39849,39851,39853,39855,39857,39859,39861,39863,39865,39867,39869,39871,39873,39875,39877,39879,39882],{"class":5282,"line":5322},[5280,39848,38770],{"class":5424},[5280,39850,5887],{"class":5452},[5280,39852,38775],{"class":5424},[5280,39854,6029],{"class":5452},[5280,39856,15379],{"class":5424},[5280,39858,5218],{"class":5452},[5280,39860,38784],{"class":5293},[5280,39862,5835],{"class":5452},[5280,39864,15379],{"class":5424},[5280,39866,5218],{"class":5452},[5280,39868,28910],{"class":5293},[5280,39870,15396],{"class":5452},[5280,39872,18053],{"class":5439},[5280,39874,8072],{"class":5452},[5280,39876,5587],{"class":5424},[5280,39878,5218],{"class":5452},[5280,39880,39881],{"class":5424},"Minute",[5280,39883,5598],{"class":5452},[5280,39885,39886],{"class":5282,"line":5336},[5280,39887,6724],{"class":5452},[5280,39889,39890,39892,39894],{"class":5282,"line":5341},[5280,39891,39672],{"class":5436},[5280,39893,7331],{"class":5619},[5280,39895,5868],{"class":5452},[5280,39897,39898,39900,39902],{"class":5282,"line":5347},[5280,39899,10596],{"class":5436},[5280,39901,38813],{"class":5293},[5280,39903,6039],{"class":5452},[5280,39905,39906,39908,39910,39912,39914,39916,39918,39920,39922,39924],{"class":5282,"line":5358},[5280,39907,39686],{"class":5424},[5280,39909,6029],{"class":5452},[5280,39911,11326],{"class":5424},[5280,39913,5218],{"class":5452},[5280,39915,39695],{"class":5424},[5280,39917,5218],{"class":5452},[5280,39919,39700],{"class":5293},[5280,39921,5835],{"class":5452},[5280,39923,15441],{"class":5424},[5280,39925,5598],{"class":5452},[5280,39927,39928],{"class":5282,"line":5368},[5280,39929,39715],{"class":5286},[5280,39931,39932],{"class":5282,"line":5378},[5280,39933,39720],{"class":5452},[5280,39935,39936],{"class":5282,"line":5391},[5280,39937,6724],{"class":5452},[5280,39939,39940,39942,39944,39946,39948,39950,39952,39954],{"class":5282,"line":5705},[5280,39941,6730],{"class":5424},[5280,39943,5218],{"class":5452},[5280,39945,6457],{"class":5293},[5280,39947,5835],{"class":5452},[5280,39949,5952],{"class":5424},[5280,39951,5218],{"class":5452},[5280,39953,39741],{"class":5424},[5280,39955,5598],{"class":5452},[5280,39957,39958,39960,39962,39964,39966,39968,39970,39972],{"class":5282,"line":5820},[5280,39959,5924],{"class":5424},[5280,39961,5218],{"class":5452},[5280,39963,8112],{"class":5293},[5280,39965,5835],{"class":5452},[5280,39967,5994],{"class":5424},[5280,39969,5887],{"class":5452},[5280,39971,39760],{"class":5297},[5280,39973,5598],{"class":5452},[5280,39975,39976],{"class":5282,"line":5854},[5280,39977,5976],{"class":5452},[5266,39979,39981],{"id":39980},"graceful-shutdown-коректне-завершення-сервера","Graceful Shutdown: коректне завершення сервера",[5271,39983,39985],{"className":5411,"code":39984,"language":5325,"meta":5276,"style":5276},"\u002F\u002F cmd\u002Fserver\u002Fmain.go\npackage main\n\nimport (\n    \"context\"\n    \"log\"\n    \"net\u002Fhttp\"\n    \"os\"\n    \"os\u002Fsignal\"\n    \"syscall\"\n    \"time\"\n)\n\nfunc main() {\n    \u002F\u002F ... ініціалізація залежностей\n    \n    server := &http.Server{\n        Addr:    \":8080\",\n        Handler: router,\n    }\n    \n    \u002F\u002F Канал для сигналів завершення\n    quit := make(chan os.Signal, 1)\n    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)\n    \n    \u002F\u002F Запускаємо сервер у горутині\n    go func() {\n        log.Println(\"Server starting on :8080...\")\n        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {\n            log.Fatalf(\"Server failed: %v\", err)\n        }\n    }()\n    \n    \u002F\u002F Чекаємо сигнал завершення\n    \u003C-quit\n    log.Println(\"Shutting down server...\")\n    \n    \u002F\u002F Даємо серверу 30 секунд для завершення активних запитів\n    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n    defer cancel()\n    \n    if err := server.Shutdown(ctx); err != nil {\n        log.Fatalf(\"Server forced to shutdown: %v\", err)\n    }\n    \n    log.Println(\"Server gracefully stopped\")\n}\n",[5215,39986,39987,39991,39997,40001,40007,40011,40015,40019,40024,40029,40034,40038,40042,40046,40054,40059,40063,40079,40090,40102,40106,40110,40115,40143,40179,40183,40188,40196,40210,40248,40271,40275,40279,40283,40288,40296,40311,40315,40320,40359,40367,40371,40400,40423,40427,40431,40446],{"__ignoreMap":5276},[5280,39988,39989],{"class":5282,"line":5283},[5280,39990,26331],{"class":5286},[5280,39992,39993,39995],{"class":5282,"line":5290},[5280,39994,5755],{"class":5619},[5280,39996,5759],{"class":5758},[5280,39998,39999],{"class":5282,"line":5301},[5280,40000,5313],{"emptyLinePlaceholder":5312},[5280,40002,40003,40005],{"class":5282,"line":5309},[5280,40004,5768],{"class":5436},[5280,40006,5453],{"class":5452},[5280,40008,40009],{"class":5282,"line":5316},[5280,40010,14862],{"class":5297},[5280,40012,40013],{"class":5282,"line":5322},[5280,40014,5785],{"class":5297},[5280,40016,40017],{"class":5282,"line":5336},[5280,40018,5790],{"class":5297},[5280,40020,40021],{"class":5282,"line":5341},[5280,40022,40023],{"class":5297},"    \"os\"\n",[5280,40025,40026],{"class":5282,"line":5347},[5280,40027,40028],{"class":5297},"    \"os\u002Fsignal\"\n",[5280,40030,40031],{"class":5282,"line":5358},[5280,40032,40033],{"class":5297},"    \"syscall\"\n",[5280,40035,40036],{"class":5282,"line":5368},[5280,40037,5795],{"class":5297},[5280,40039,40040],{"class":5282,"line":5378},[5280,40041,5598],{"class":5452},[5280,40043,40044],{"class":5282,"line":5391},[5280,40045,5313],{"emptyLinePlaceholder":5312},[5280,40047,40048,40050,40052],{"class":5282,"line":5705},[5280,40049,5862],{"class":5619},[5280,40051,5865],{"class":5293},[5280,40053,5868],{"class":5452},[5280,40055,40056],{"class":5282,"line":5820},[5280,40057,40058],{"class":5286},"    \u002F\u002F ... ініціалізація залежностей\n",[5280,40060,40061],{"class":5282,"line":5854},[5280,40062,6724],{"class":5452},[5280,40064,40065,40068,40070,40072,40074,40077],{"class":5282,"line":5859},[5280,40066,40067],{"class":5424},"    server",[5280,40069,9745],{"class":5452},[5280,40071,5952],{"class":5758},[5280,40073,5218],{"class":5452},[5280,40075,40076],{"class":5758},"Server",[5280,40078,6781],{"class":5452},[5280,40080,40081,40084,40086,40088],{"class":5282,"line":5871},[5280,40082,40083],{"class":5424},"        Addr",[5280,40085,13867],{"class":5452},[5280,40087,5962],{"class":5297},[5280,40089,6796],{"class":5452},[5280,40091,40092,40095,40097,40100],{"class":5282,"line":5895},[5280,40093,40094],{"class":5424},"        Handler",[5280,40096,6790],{"class":5452},[5280,40098,40099],{"class":5424},"router",[5280,40101,6796],{"class":5452},[5280,40103,40104],{"class":5282,"line":5916},[5280,40105,6553],{"class":5452},[5280,40107,40108],{"class":5282,"line":5921},[5280,40109,6724],{"class":5452},[5280,40111,40112],{"class":5282,"line":5939},[5280,40113,40114],{"class":5286},"    \u002F\u002F Канал для сигналів завершення\n",[5280,40116,40117,40120,40122,40124,40126,40129,40132,40134,40137,40139,40141],{"class":5282,"line":5973},[5280,40118,40119],{"class":5424},"    quit",[5280,40121,6029],{"class":5452},[5280,40123,5832],{"class":5293},[5280,40125,5835],{"class":5452},[5280,40127,40128],{"class":5619},"chan",[5280,40130,40131],{"class":5758}," os",[5280,40133,5218],{"class":5452},[5280,40135,40136],{"class":5758},"Signal",[5280,40138,5887],{"class":5452},[5280,40140,11401],{"class":5439},[5280,40142,5598],{"class":5452},[5280,40144,40145,40148,40150,40153,40155,40158,40160,40163,40165,40168,40170,40172,40174,40177],{"class":5282,"line":5979},[5280,40146,40147],{"class":5424},"    signal",[5280,40149,5218],{"class":5452},[5280,40151,40152],{"class":5293},"Notify",[5280,40154,5835],{"class":5452},[5280,40156,40157],{"class":5424},"quit",[5280,40159,5887],{"class":5452},[5280,40161,40162],{"class":5424},"syscall",[5280,40164,5218],{"class":5452},[5280,40166,40167],{"class":5424},"SIGINT",[5280,40169,5887],{"class":5452},[5280,40171,40162],{"class":5424},[5280,40173,5218],{"class":5452},[5280,40175,40176],{"class":5424},"SIGTERM",[5280,40178,5598],{"class":5452},[5280,40180,40181],{"class":5282,"line":5984},[5280,40182,6724],{"class":5452},[5280,40184,40185],{"class":5282,"line":6023},[5280,40186,40187],{"class":5286},"    \u002F\u002F Запускаємо сервер у горутині\n",[5280,40189,40190,40192,40194],{"class":5282,"line":6042},[5280,40191,39672],{"class":5436},[5280,40193,7331],{"class":5619},[5280,40195,5868],{"class":5452},[5280,40197,40198,40200,40202,40204,40206,40208],{"class":5282,"line":6093},[5280,40199,7039],{"class":5424},[5280,40201,5218],{"class":5452},[5280,40203,5929],{"class":5293},[5280,40205,5835],{"class":5452},[5280,40207,5934],{"class":5297},[5280,40209,5598],{"class":5452},[5280,40211,40212,40214,40216,40218,40221,40223,40225,40227,40229,40231,40233,40235,40237,40239,40241,40243,40246],{"class":5282,"line":6098},[5280,40213,6259],{"class":5436},[5280,40215,6262],{"class":5424},[5280,40217,6029],{"class":5452},[5280,40219,40220],{"class":5424},"server",[5280,40222,5218],{"class":5452},[5280,40224,5957],{"class":5293},[5280,40226,10615],{"class":5452},[5280,40228,6298],{"class":5424},[5280,40230,6301],{"class":5452},[5280,40232,5967],{"class":5619},[5280,40234,36347],{"class":5452},[5280,40236,6298],{"class":5424},[5280,40238,6301],{"class":5452},[5280,40240,5952],{"class":5424},[5280,40242,5218],{"class":5452},[5280,40244,40245],{"class":5424},"ErrServerClosed",[5280,40247,6111],{"class":5452},[5280,40249,40250,40252,40254,40256,40258,40261,40263,40265,40267,40269],{"class":5282,"line":6114},[5280,40251,6342],{"class":5424},[5280,40253,5218],{"class":5452},[5280,40255,28967],{"class":5293},[5280,40257,5835],{"class":5452},[5280,40259,40260],{"class":5297},"\"Server failed: ",[5280,40262,6362],{"class":5424},[5280,40264,6584],{"class":5297},[5280,40266,5887],{"class":5452},[5280,40268,6298],{"class":5424},[5280,40270,5598],{"class":5452},[5280,40272,40273],{"class":5282,"line":6130},[5280,40274,6248],{"class":5452},[5280,40276,40277],{"class":5282,"line":6159},[5280,40278,39720],{"class":5452},[5280,40280,40281],{"class":5282,"line":6187},[5280,40282,6724],{"class":5452},[5280,40284,40285],{"class":5282,"line":6193},[5280,40286,40287],{"class":5286},"    \u002F\u002F Чекаємо сигнал завершення\n",[5280,40289,40290,40293],{"class":5282,"line":6207},[5280,40291,40292],{"class":5452},"    \u003C-",[5280,40294,40295],{"class":5424},"quit\n",[5280,40297,40298,40300,40302,40304,40306,40309],{"class":5282,"line":6221},[5280,40299,5942],{"class":5424},[5280,40301,5218],{"class":5452},[5280,40303,5929],{"class":5293},[5280,40305,5835],{"class":5452},[5280,40307,40308],{"class":5297},"\"Shutting down server...\"",[5280,40310,5598],{"class":5452},[5280,40312,40313],{"class":5282,"line":6233},[5280,40314,6724],{"class":5452},[5280,40316,40317],{"class":5282,"line":6245},[5280,40318,40319],{"class":5286},"    \u002F\u002F Даємо серверу 30 секунд для завершення активних запитів\n",[5280,40321,40322,40324,40326,40328,40330,40332,40334,40336,40338,40340,40342,40344,40346,40349,40351,40353,40355,40357],{"class":5282,"line":6251},[5280,40323,38770],{"class":5424},[5280,40325,5887],{"class":5452},[5280,40327,38775],{"class":5424},[5280,40329,6029],{"class":5452},[5280,40331,15379],{"class":5424},[5280,40333,5218],{"class":5452},[5280,40335,38784],{"class":5293},[5280,40337,5835],{"class":5452},[5280,40339,15379],{"class":5424},[5280,40341,5218],{"class":5452},[5280,40343,28910],{"class":5293},[5280,40345,15396],{"class":5452},[5280,40347,40348],{"class":5439},"30",[5280,40350,8072],{"class":5452},[5280,40352,5587],{"class":5424},[5280,40354,5218],{"class":5452},[5280,40356,14252],{"class":5424},[5280,40358,5598],{"class":5452},[5280,40360,40361,40363,40365],{"class":5282,"line":6256},[5280,40362,7217],{"class":5436},[5280,40364,38813],{"class":5293},[5280,40366,6039],{"class":5452},[5280,40368,40369],{"class":5282,"line":6308},[5280,40370,6724],{"class":5452},[5280,40372,40373,40375,40377,40379,40381,40383,40386,40388,40390,40392,40394,40396,40398],{"class":5282,"line":6339},[5280,40374,7023],{"class":5436},[5280,40376,6262],{"class":5424},[5280,40378,6029],{"class":5452},[5280,40380,40220],{"class":5424},[5280,40382,5218],{"class":5452},[5280,40384,40385],{"class":5293},"Shutdown",[5280,40387,5835],{"class":5452},[5280,40389,15441],{"class":5424},[5280,40391,6295],{"class":5452},[5280,40393,6298],{"class":5424},[5280,40395,6301],{"class":5452},[5280,40397,5967],{"class":5619},[5280,40399,6111],{"class":5452},[5280,40401,40402,40404,40406,40408,40410,40413,40415,40417,40419,40421],{"class":5282,"line":6371},[5280,40403,7039],{"class":5424},[5280,40405,5218],{"class":5452},[5280,40407,28967],{"class":5293},[5280,40409,5835],{"class":5452},[5280,40411,40412],{"class":5297},"\"Server forced to shutdown: ",[5280,40414,6362],{"class":5424},[5280,40416,6584],{"class":5297},[5280,40418,5887],{"class":5452},[5280,40420,6298],{"class":5424},[5280,40422,5598],{"class":5452},[5280,40424,40425],{"class":5282,"line":6409},[5280,40426,6553],{"class":5452},[5280,40428,40429],{"class":5282,"line":6415},[5280,40430,6724],{"class":5452},[5280,40432,40433,40435,40437,40439,40441,40444],{"class":5282,"line":6420},[5280,40434,5942],{"class":5424},[5280,40436,5218],{"class":5452},[5280,40438,5929],{"class":5293},[5280,40440,5835],{"class":5452},[5280,40442,40443],{"class":5297},"\"Server gracefully stopped\"",[5280,40445,5598],{"class":5452},[5280,40447,40448],{"class":5282,"line":6425},[5280,40449,5976],{"class":5452},[5262,40451,40452],{},[5227,40453,40454],{},"Тестування Graceful Shutdown:",[8866,40456,40458,40468,40471,40474,40482,40489,40492,40496,40500],{":cursor":8868,"title":40457},"Тест graceful shutdown",[8871,40459,40461,7648,40464],{"className":40460},[5282],[5280,40462,8878],{"className":40463},[8877],[5227,40465,40467],{"className":40466},[8882],"go run cmd\u002Fserver\u002Fmain.go",[8871,40469,8886],{"className":40470},[5282],[8871,40472],{"className":40473},[5282],[8871,40475,40477],{"className":40476},[5282],[5280,40478,40481],{"className":40479},[40480],"opacity-60","# В іншому терміналі: curl запит",[8871,40483,40485],{"className":40484},[5282],[5280,40486,40488],{"className":40487},[40480],"# Натискаємо Ctrl+C",[8871,40490],{"className":40491},[5282],[8871,40493,40495],{"className":40494},[5282],"^C",[8871,40497,40499],{"className":40498},[5282],"Shutting down server...",[8871,40501,40503],{"className":40502},[5282],"Server gracefully stopped",[5266,40505,40507],{"id":40506},"моніторинг-скасувань","Моніторинг скасувань",[5262,40509,40510],{},"Додамо метрики для відстеження скасованих операцій:",[5271,40512,40514],{"className":5411,"code":40513,"language":5325,"meta":5276,"style":5276},"\u002F\u002F middleware\u002Fcontext_monitor.go\npackage middleware\n\nimport (\n    \"context\"\n    \"errors\"\n    \"log\"\n    \"net\u002Fhttp\"\n    \"sync\u002Fatomic\"\n    \"time\"\n)\n\nvar (\n    canceledRequests   int64\n    timeoutRequests    int64\n    completedRequests  int64\n)\n\n\u002F\u002F ContextMonitor — Middleware для моніторингу контекстів\nfunc ContextMonitor(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        done := make(chan struct{})\n        \n        go func() {\n            next.ServeHTTP(w, r)\n            close(done)\n        }()\n        \n        select {\n        case \u003C-done:\n            \u002F\u002F Запит завершився нормально\n            atomic.AddInt64(&completedRequests, 1)\n            \n        case \u003C-r.Context().Done():\n            \u002F\u002F Контекст скасовано\n            err := r.Context().Err()\n            \n            if errors.Is(err, context.Canceled) {\n                atomic.AddInt64(&canceledRequests, 1)\n                log.Printf(\"Request canceled: %s %s\", r.Method, r.URL.Path)\n            } else if errors.Is(err, context.DeadlineExceeded) {\n                atomic.AddInt64(&timeoutRequests, 1)\n                log.Printf(\"Request timeout: %s %s\", r.Method, r.URL.Path)\n            }\n            \n            \u002F\u002F Чекаємо завершення handler (але не більше 5 секунд)\n            select {\n            case \u003C-done:\n            case \u003C-time.After(5 * time.Second):\n                log.Printf(\"Handler didn't stop after context cancellation\")\n            }\n        }\n    })\n}\n\n\u002F\u002F GetStats повертає статистику\nfunc GetContextStats() map[string]int64 {\n    return map[string]int64{\n        \"completed\": atomic.LoadInt64(&completedRequests),\n        \"canceled\":  atomic.LoadInt64(&canceledRequests),\n        \"timeout\":   atomic.LoadInt64(&timeoutRequests),\n    }\n}\n",[5215,40515,40516,40521,40527,40531,40537,40541,40545,40549,40553,40558,40562,40566,40570,40576,40584,40592,40600,40604,40608,40613,40640,40678,40695,40699,40708,40726,40738,40742,40746,40752,40762,40767,40788,40792,40810,40815,40833,40837,40861,40881,40922,40950,40969,41010,41014,41018,41023,41029,41039,41066,41081,41085,41089,41093,41097,41101,41106,41128,41144,41165,41184,41203,41207],{"__ignoreMap":5276},[5280,40517,40518],{"class":5282,"line":5283},[5280,40519,40520],{"class":5286},"\u002F\u002F middleware\u002Fcontext_monitor.go\n",[5280,40522,40523,40525],{"class":5282,"line":5290},[5280,40524,5755],{"class":5619},[5280,40526,5641],{"class":5758},[5280,40528,40529],{"class":5282,"line":5301},[5280,40530,5313],{"emptyLinePlaceholder":5312},[5280,40532,40533,40535],{"class":5282,"line":5309},[5280,40534,5768],{"class":5436},[5280,40536,5453],{"class":5452},[5280,40538,40539],{"class":5282,"line":5316},[5280,40540,14862],{"class":5297},[5280,40542,40543],{"class":5282,"line":5322},[5280,40544,20046],{"class":5297},[5280,40546,40547],{"class":5282,"line":5336},[5280,40548,5785],{"class":5297},[5280,40550,40551],{"class":5282,"line":5341},[5280,40552,5790],{"class":5297},[5280,40554,40555],{"class":5282,"line":5347},[5280,40556,40557],{"class":5297},"    \"sync\u002Fatomic\"\n",[5280,40559,40560],{"class":5282,"line":5358},[5280,40561,5795],{"class":5297},[5280,40563,40564],{"class":5282,"line":5368},[5280,40565,5598],{"class":5452},[5280,40567,40568],{"class":5282,"line":5378},[5280,40569,5313],{"emptyLinePlaceholder":5312},[5280,40571,40572,40574],{"class":5282,"line":5391},[5280,40573,5823],{"class":5619},[5280,40575,5453],{"class":5452},[5280,40577,40578,40581],{"class":5282,"line":5705},[5280,40579,40580],{"class":5424},"    canceledRequests",[5280,40582,40583],{"class":5758},"   int64\n",[5280,40585,40586,40589],{"class":5282,"line":5820},[5280,40587,40588],{"class":5424},"    timeoutRequests",[5280,40590,40591],{"class":5758},"    int64\n",[5280,40593,40594,40597],{"class":5282,"line":5854},[5280,40595,40596],{"class":5424},"    completedRequests",[5280,40598,40599],{"class":5758},"  int64\n",[5280,40601,40602],{"class":5282,"line":5859},[5280,40603,5598],{"class":5452},[5280,40605,40606],{"class":5282,"line":5871},[5280,40607,5313],{"emptyLinePlaceholder":5312},[5280,40609,40610],{"class":5282,"line":5895},[5280,40611,40612],{"class":5286},"\u002F\u002F ContextMonitor — Middleware для моніторингу контекстів\n",[5280,40614,40615,40617,40620,40622,40624,40626,40628,40630,40632,40634,40636,40638],{"class":5282,"line":5916},[5280,40616,5862],{"class":5619},[5280,40618,40619],{"class":5293}," ContextMonitor",[5280,40621,5835],{"class":5452},[5280,40623,7305],{"class":5424},[5280,40625,5997],{"class":5758},[5280,40627,5218],{"class":5452},[5280,40629,7935],{"class":5758},[5280,40631,7315],{"class":5452},[5280,40633,5952],{"class":5758},[5280,40635,5218],{"class":5452},[5280,40637,7935],{"class":5758},[5280,40639,6111],{"class":5452},[5280,40641,40642,40644,40646,40648,40650,40652,40654,40656,40658,40660,40662,40664,40666,40668,40670,40672,40674,40676],{"class":5282,"line":5921},[5280,40643,7328],{"class":5436},[5280,40645,5997],{"class":5424},[5280,40647,5218],{"class":5452},[5280,40649,7312],{"class":5293},[5280,40651,5835],{"class":5452},[5280,40653,5862],{"class":5619},[5280,40655,5835],{"class":5452},[5280,40657,5994],{"class":5424},[5280,40659,5997],{"class":5758},[5280,40661,5218],{"class":5452},[5280,40663,6002],{"class":5758},[5280,40665,5887],{"class":5452},[5280,40667,6007],{"class":5424},[5280,40669,6010],{"class":5452},[5280,40671,5952],{"class":5758},[5280,40673,5218],{"class":5452},[5280,40675,6017],{"class":5758},[5280,40677,6020],{"class":5452},[5280,40679,40680,40683,40685,40687,40689,40691,40693],{"class":5282,"line":5939},[5280,40681,40682],{"class":5424},"        done",[5280,40684,6029],{"class":5452},[5280,40686,5832],{"class":5293},[5280,40688,5835],{"class":5452},[5280,40690,40128],{"class":5619},[5280,40692,6216],{"class":5619},[5280,40694,8158],{"class":5452},[5280,40696,40697],{"class":5282,"line":5973},[5280,40698,6190],{"class":5452},[5280,40700,40701,40704,40706],{"class":5282,"line":5979},[5280,40702,40703],{"class":5436},"        go",[5280,40705,7331],{"class":5619},[5280,40707,5868],{"class":5452},[5280,40709,40710,40712,40714,40716,40718,40720,40722,40724],{"class":5282,"line":5984},[5280,40711,12819],{"class":5424},[5280,40713,5218],{"class":5452},[5280,40715,8033],{"class":5293},[5280,40717,5835],{"class":5452},[5280,40719,5994],{"class":5424},[5280,40721,5887],{"class":5452},[5280,40723,6007],{"class":5424},[5280,40725,5598],{"class":5452},[5280,40727,40728,40731,40733,40736],{"class":5282,"line":6023},[5280,40729,40730],{"class":5293},"            close",[5280,40732,5835],{"class":5452},[5280,40734,40735],{"class":5424},"done",[5280,40737,5598],{"class":5452},[5280,40739,40740],{"class":5282,"line":6042},[5280,40741,10722],{"class":5452},[5280,40743,40744],{"class":5282,"line":6093},[5280,40745,6190],{"class":5452},[5280,40747,40748,40750],{"class":5282,"line":6098},[5280,40749,38450],{"class":5436},[5280,40751,6111],{"class":5452},[5280,40753,40754,40756,40758,40760],{"class":5282,"line":6114},[5280,40755,32899],{"class":5436},[5280,40757,38459],{"class":5452},[5280,40759,40735],{"class":5424},[5280,40761,6127],{"class":5452},[5280,40763,40764],{"class":5282,"line":6130},[5280,40765,40766],{"class":5286},"            \u002F\u002F Запит завершився нормально\n",[5280,40768,40769,40772,40774,40777,40779,40782,40784,40786],{"class":5282,"line":6159},[5280,40770,40771],{"class":5424},"            atomic",[5280,40773,5218],{"class":5452},[5280,40775,40776],{"class":5293},"AddInt64",[5280,40778,6289],{"class":5452},[5280,40780,40781],{"class":5424},"completedRequests",[5280,40783,5887],{"class":5452},[5280,40785,11401],{"class":5439},[5280,40787,5598],{"class":5452},[5280,40789,40790],{"class":5282,"line":6187},[5280,40791,12263],{"class":5452},[5280,40793,40794,40796,40798,40800,40802,40804,40806,40808],{"class":5282,"line":6193},[5280,40795,32899],{"class":5436},[5280,40797,38459],{"class":5452},[5280,40799,6007],{"class":5424},[5280,40801,5218],{"class":5452},[5280,40803,15393],{"class":5293},[5280,40805,6141],{"class":5452},[5280,40807,38466],{"class":5293},[5280,40809,38469],{"class":5452},[5280,40811,40812],{"class":5282,"line":6207},[5280,40813,40814],{"class":5286},"            \u002F\u002F Контекст скасовано\n",[5280,40816,40817,40819,40821,40823,40825,40827,40829,40831],{"class":5282,"line":6221},[5280,40818,27791],{"class":5424},[5280,40820,6029],{"class":5452},[5280,40822,6007],{"class":5424},[5280,40824,5218],{"class":5452},[5280,40826,15393],{"class":5293},[5280,40828,6141],{"class":5452},[5280,40830,38489],{"class":5293},[5280,40832,6039],{"class":5452},[5280,40834,40835],{"class":5282,"line":6233},[5280,40836,12263],{"class":5452},[5280,40838,40839,40841,40843,40845,40847,40849,40851,40853,40855,40857,40859],{"class":5282,"line":6245},[5280,40840,10605],{"class":5436},[5280,40842,21385],{"class":5424},[5280,40844,5218],{"class":5452},[5280,40846,37236],{"class":5293},[5280,40848,5835],{"class":5452},[5280,40850,6298],{"class":5424},[5280,40852,5887],{"class":5452},[5280,40854,15379],{"class":5424},[5280,40856,5218],{"class":5452},[5280,40858,38960],{"class":5424},[5280,40860,6020],{"class":5452},[5280,40862,40863,40866,40868,40870,40872,40875,40877,40879],{"class":5282,"line":6251},[5280,40864,40865],{"class":5424},"                atomic",[5280,40867,5218],{"class":5452},[5280,40869,40776],{"class":5293},[5280,40871,6289],{"class":5452},[5280,40873,40874],{"class":5424},"canceledRequests",[5280,40876,5887],{"class":5452},[5280,40878,11401],{"class":5439},[5280,40880,5598],{"class":5452},[5280,40882,40883,40885,40887,40889,40891,40894,40896,40898,40900,40902,40904,40906,40908,40910,40912,40914,40916,40918,40920],{"class":5282,"line":6256},[5280,40884,10633],{"class":5424},[5280,40886,5218],{"class":5452},[5280,40888,6049],{"class":5293},[5280,40890,5835],{"class":5452},[5280,40892,40893],{"class":5297},"\"Request canceled: ",[5280,40895,6057],{"class":5424},[5280,40897,9820],{"class":5424},[5280,40899,6584],{"class":5297},[5280,40901,5887],{"class":5452},[5280,40903,6007],{"class":5424},[5280,40905,5218],{"class":5452},[5280,40907,6074],{"class":5424},[5280,40909,5887],{"class":5452},[5280,40911,6007],{"class":5424},[5280,40913,5218],{"class":5452},[5280,40915,6083],{"class":5424},[5280,40917,5218],{"class":5452},[5280,40919,6088],{"class":5424},[5280,40921,5598],{"class":5452},[5280,40923,40924,40926,40928,40930,40932,40934,40936,40938,40940,40942,40944,40946,40948],{"class":5282,"line":6308},[5280,40925,13464],{"class":5452},[5280,40927,7073],{"class":5436},[5280,40929,27860],{"class":5436},[5280,40931,21385],{"class":5424},[5280,40933,5218],{"class":5452},[5280,40935,37236],{"class":5293},[5280,40937,5835],{"class":5452},[5280,40939,6298],{"class":5424},[5280,40941,5887],{"class":5452},[5280,40943,15379],{"class":5424},[5280,40945,5218],{"class":5452},[5280,40947,38893],{"class":5424},[5280,40949,6020],{"class":5452},[5280,40951,40952,40954,40956,40958,40960,40963,40965,40967],{"class":5282,"line":6339},[5280,40953,40865],{"class":5424},[5280,40955,5218],{"class":5452},[5280,40957,40776],{"class":5293},[5280,40959,6289],{"class":5452},[5280,40961,40962],{"class":5424},"timeoutRequests",[5280,40964,5887],{"class":5452},[5280,40966,11401],{"class":5439},[5280,40968,5598],{"class":5452},[5280,40970,40971,40973,40975,40977,40979,40982,40984,40986,40988,40990,40992,40994,40996,40998,41000,41002,41004,41006,41008],{"class":5282,"line":6371},[5280,40972,10633],{"class":5424},[5280,40974,5218],{"class":5452},[5280,40976,6049],{"class":5293},[5280,40978,5835],{"class":5452},[5280,40980,40981],{"class":5297},"\"Request timeout: ",[5280,40983,6057],{"class":5424},[5280,40985,9820],{"class":5424},[5280,40987,6584],{"class":5297},[5280,40989,5887],{"class":5452},[5280,40991,6007],{"class":5424},[5280,40993,5218],{"class":5452},[5280,40995,6074],{"class":5424},[5280,40997,5887],{"class":5452},[5280,40999,6007],{"class":5424},[5280,41001,5218],{"class":5452},[5280,41003,6083],{"class":5424},[5280,41005,5218],{"class":5452},[5280,41007,6088],{"class":5424},[5280,41009,5598],{"class":5452},[5280,41011,41012],{"class":5282,"line":6409},[5280,41013,10717],{"class":5452},[5280,41015,41016],{"class":5282,"line":6415},[5280,41017,12263],{"class":5452},[5280,41019,41020],{"class":5282,"line":6420},[5280,41021,41022],{"class":5286},"            \u002F\u002F Чекаємо завершення handler (але не більше 5 секунд)\n",[5280,41024,41025,41027],{"class":5282,"line":6425},[5280,41026,38538],{"class":5436},[5280,41028,6111],{"class":5452},[5280,41030,41031,41033,41035,41037],{"class":5282,"line":6450},[5280,41032,38545],{"class":5436},[5280,41034,38459],{"class":5452},[5280,41036,40735],{"class":5424},[5280,41038,6127],{"class":5452},[5280,41040,41041,41043,41045,41047,41049,41052,41054,41056,41058,41060,41062,41064],{"class":5282,"line":6471},[5280,41042,38545],{"class":5436},[5280,41044,38459],{"class":5452},[5280,41046,5587],{"class":5424},[5280,41048,5218],{"class":5452},[5280,41050,41051],{"class":5293},"After",[5280,41053,5835],{"class":5452},[5280,41055,14535],{"class":5439},[5280,41057,17587],{"class":5452},[5280,41059,5587],{"class":5424},[5280,41061,5218],{"class":5452},[5280,41063,14252],{"class":5424},[5280,41065,37251],{"class":5452},[5280,41067,41068,41070,41072,41074,41076,41079],{"class":5282,"line":6507},[5280,41069,10633],{"class":5424},[5280,41071,5218],{"class":5452},[5280,41073,6049],{"class":5293},[5280,41075,5835],{"class":5452},[5280,41077,41078],{"class":5297},"\"Handler didn't stop after context cancellation\"",[5280,41080,5598],{"class":5452},[5280,41082,41083],{"class":5282,"line":6512},[5280,41084,10717],{"class":5452},[5280,41086,41087],{"class":5282,"line":6520},[5280,41088,6248],{"class":5452},[5280,41090,41091],{"class":5282,"line":6550},[5280,41092,6834],{"class":5452},[5280,41094,41095],{"class":5282,"line":6556},[5280,41096,5976],{"class":5452},[5280,41098,41099],{"class":5282,"line":6561},[5280,41100,5313],{"emptyLinePlaceholder":5312},[5280,41102,41103],{"class":5282,"line":6621},[5280,41104,41105],{"class":5286},"\u002F\u002F GetStats повертає статистику\n",[5280,41107,41108,41110,41113,41115,41117,41119,41121,41123,41126],{"class":5282,"line":6626},[5280,41109,5862],{"class":5619},[5280,41111,41112],{"class":5293}," GetContextStats",[5280,41114,11974],{"class":5452},[5280,41116,5838],{"class":5619},[5280,41118,5841],{"class":5452},[5280,41120,5844],{"class":5758},[5280,41122,5847],{"class":5452},[5280,41124,41125],{"class":5758},"int64",[5280,41127,6111],{"class":5452},[5280,41129,41130,41132,41134,41136,41138,41140,41142],{"class":5282,"line":6631},[5280,41131,7328],{"class":5436},[5280,41133,13717],{"class":5619},[5280,41135,5841],{"class":5452},[5280,41137,5844],{"class":5758},[5280,41139,5847],{"class":5452},[5280,41141,41125],{"class":5758},[5280,41143,6781],{"class":5452},[5280,41145,41146,41149,41151,41154,41156,41159,41161,41163],{"class":5282,"line":6663},[5280,41147,41148],{"class":5297},"        \"completed\"",[5280,41150,6790],{"class":5452},[5280,41152,41153],{"class":5424},"atomic",[5280,41155,5218],{"class":5452},[5280,41157,41158],{"class":5293},"LoadInt64",[5280,41160,6289],{"class":5452},[5280,41162,40781],{"class":5424},[5280,41164,6828],{"class":5452},[5280,41166,41167,41170,41172,41174,41176,41178,41180,41182],{"class":5282,"line":6678},[5280,41168,41169],{"class":5297},"        \"canceled\"",[5280,41171,19517],{"class":5452},[5280,41173,41153],{"class":5424},[5280,41175,5218],{"class":5452},[5280,41177,41158],{"class":5293},[5280,41179,6289],{"class":5452},[5280,41181,40874],{"class":5424},[5280,41183,6828],{"class":5452},[5280,41185,41186,41189,41191,41193,41195,41197,41199,41201],{"class":5282,"line":6721},[5280,41187,41188],{"class":5297},"        \"timeout\"",[5280,41190,6805],{"class":5452},[5280,41192,41153],{"class":5424},[5280,41194,5218],{"class":5452},[5280,41196,41158],{"class":5293},[5280,41198,6289],{"class":5452},[5280,41200,40962],{"class":5424},[5280,41202,6828],{"class":5452},[5280,41204,41205],{"class":5282,"line":6727},[5280,41206,6553],{"class":5452},[5280,41208,41209],{"class":5282,"line":6751},[5280,41210,5976],{"class":5452},[5254,41212],{},[5257,41214,41216],{"id":41215},"типові-помилки-початківців-та-як-їх-уникати","Типові помилки початківців та як їх уникати",[5266,41218,41220],{"id":41219},"помилка-1-забування-про-помилки-в-middleware","Помилка 1: Забування про помилки в Middleware",[5262,41222,41223],{},[5227,41224,39621],{},[5271,41226,41228],{"className":5411,"code":41227,"language":5325,"meta":5276,"style":5276},"func Logger(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        log.Println(\"Request received\")\n        next.ServeHTTP(w, r)\n        \u002F\u002F Що якщо next.ServeHTTP() викликав panic?\n    })\n}\n",[5215,41229,41230,41256,41294,41309,41327,41332,41336],{"__ignoreMap":5276},[5280,41231,41232,41234,41236,41238,41240,41242,41244,41246,41248,41250,41252,41254],{"class":5282,"line":5283},[5280,41233,5862],{"class":5619},[5280,41235,9008],{"class":5293},[5280,41237,5835],{"class":5452},[5280,41239,7305],{"class":5424},[5280,41241,5997],{"class":5758},[5280,41243,5218],{"class":5452},[5280,41245,7935],{"class":5758},[5280,41247,7315],{"class":5452},[5280,41249,5952],{"class":5758},[5280,41251,5218],{"class":5452},[5280,41253,7935],{"class":5758},[5280,41255,6111],{"class":5452},[5280,41257,41258,41260,41262,41264,41266,41268,41270,41272,41274,41276,41278,41280,41282,41284,41286,41288,41290,41292],{"class":5282,"line":5290},[5280,41259,7328],{"class":5436},[5280,41261,5997],{"class":5424},[5280,41263,5218],{"class":5452},[5280,41265,7312],{"class":5293},[5280,41267,5835],{"class":5452},[5280,41269,5862],{"class":5619},[5280,41271,5835],{"class":5452},[5280,41273,5994],{"class":5424},[5280,41275,5997],{"class":5758},[5280,41277,5218],{"class":5452},[5280,41279,6002],{"class":5758},[5280,41281,5887],{"class":5452},[5280,41283,6007],{"class":5424},[5280,41285,6010],{"class":5452},[5280,41287,5952],{"class":5758},[5280,41289,5218],{"class":5452},[5280,41291,6017],{"class":5758},[5280,41293,6020],{"class":5452},[5280,41295,41296,41298,41300,41302,41304,41307],{"class":5282,"line":5301},[5280,41297,7039],{"class":5424},[5280,41299,5218],{"class":5452},[5280,41301,5929],{"class":5293},[5280,41303,5835],{"class":5452},[5280,41305,41306],{"class":5297},"\"Request received\"",[5280,41308,5598],{"class":5452},[5280,41310,41311,41313,41315,41317,41319,41321,41323,41325],{"class":5282,"line":5309},[5280,41312,7426],{"class":5424},[5280,41314,5218],{"class":5452},[5280,41316,8033],{"class":5293},[5280,41318,5835],{"class":5452},[5280,41320,5994],{"class":5424},[5280,41322,5887],{"class":5452},[5280,41324,6007],{"class":5424},[5280,41326,5598],{"class":5452},[5280,41328,41329],{"class":5282,"line":5316},[5280,41330,41331],{"class":5286},"        \u002F\u002F Що якщо next.ServeHTTP() викликав panic?\n",[5280,41333,41334],{"class":5282,"line":5322},[5280,41335,6834],{"class":5452},[5280,41337,41338],{"class":5282,"line":5336},[5280,41339,5976],{"class":5452},[5262,41341,41342],{},[5227,41343,39771],{},[5271,41345,41347],{"className":5411,"code":41346,"language":5325,"meta":5276,"style":5276},"func Logger(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        defer func() {\n            if err := recover(); err != nil {\n                log.Printf(\"PANIC: %v\", err)\n            }\n        }()\n        \n        log.Println(\"Request received\")\n        next.ServeHTTP(w, r)\n        log.Println(\"Request completed\")\n    })\n}\n",[5215,41348,41349,41375,41413,41421,41441,41463,41467,41471,41475,41489,41507,41522,41526],{"__ignoreMap":5276},[5280,41350,41351,41353,41355,41357,41359,41361,41363,41365,41367,41369,41371,41373],{"class":5282,"line":5283},[5280,41352,5862],{"class":5619},[5280,41354,9008],{"class":5293},[5280,41356,5835],{"class":5452},[5280,41358,7305],{"class":5424},[5280,41360,5997],{"class":5758},[5280,41362,5218],{"class":5452},[5280,41364,7935],{"class":5758},[5280,41366,7315],{"class":5452},[5280,41368,5952],{"class":5758},[5280,41370,5218],{"class":5452},[5280,41372,7935],{"class":5758},[5280,41374,6111],{"class":5452},[5280,41376,41377,41379,41381,41383,41385,41387,41389,41391,41393,41395,41397,41399,41401,41403,41405,41407,41409,41411],{"class":5282,"line":5290},[5280,41378,7328],{"class":5436},[5280,41380,5997],{"class":5424},[5280,41382,5218],{"class":5452},[5280,41384,7312],{"class":5293},[5280,41386,5835],{"class":5452},[5280,41388,5862],{"class":5619},[5280,41390,5835],{"class":5452},[5280,41392,5994],{"class":5424},[5280,41394,5997],{"class":5758},[5280,41396,5218],{"class":5452},[5280,41398,6002],{"class":5758},[5280,41400,5887],{"class":5452},[5280,41402,6007],{"class":5424},[5280,41404,6010],{"class":5452},[5280,41406,5952],{"class":5758},[5280,41408,5218],{"class":5452},[5280,41410,6017],{"class":5758},[5280,41412,6020],{"class":5452},[5280,41414,41415,41417,41419],{"class":5282,"line":5301},[5280,41416,10596],{"class":5436},[5280,41418,7331],{"class":5619},[5280,41420,5868],{"class":5452},[5280,41422,41423,41425,41427,41429,41431,41433,41435,41437,41439],{"class":5282,"line":5309},[5280,41424,10605],{"class":5436},[5280,41426,6262],{"class":5424},[5280,41428,6029],{"class":5452},[5280,41430,10612],{"class":5293},[5280,41432,10615],{"class":5452},[5280,41434,6298],{"class":5424},[5280,41436,6301],{"class":5452},[5280,41438,5967],{"class":5619},[5280,41440,6111],{"class":5452},[5280,41442,41443,41445,41447,41449,41451,41453,41455,41457,41459,41461],{"class":5282,"line":5316},[5280,41444,10633],{"class":5424},[5280,41446,5218],{"class":5452},[5280,41448,6049],{"class":5293},[5280,41450,5835],{"class":5452},[5280,41452,10642],{"class":5297},[5280,41454,6362],{"class":5424},[5280,41456,6584],{"class":5297},[5280,41458,5887],{"class":5452},[5280,41460,6298],{"class":5424},[5280,41462,5598],{"class":5452},[5280,41464,41465],{"class":5282,"line":5322},[5280,41466,10717],{"class":5452},[5280,41468,41469],{"class":5282,"line":5336},[5280,41470,10722],{"class":5452},[5280,41472,41473],{"class":5282,"line":5341},[5280,41474,6190],{"class":5452},[5280,41476,41477,41479,41481,41483,41485,41487],{"class":5282,"line":5347},[5280,41478,7039],{"class":5424},[5280,41480,5218],{"class":5452},[5280,41482,5929],{"class":5293},[5280,41484,5835],{"class":5452},[5280,41486,41306],{"class":5297},[5280,41488,5598],{"class":5452},[5280,41490,41491,41493,41495,41497,41499,41501,41503,41505],{"class":5282,"line":5358},[5280,41492,7426],{"class":5424},[5280,41494,5218],{"class":5452},[5280,41496,8033],{"class":5293},[5280,41498,5835],{"class":5452},[5280,41500,5994],{"class":5424},[5280,41502,5887],{"class":5452},[5280,41504,6007],{"class":5424},[5280,41506,5598],{"class":5452},[5280,41508,41509,41511,41513,41515,41517,41520],{"class":5282,"line":5368},[5280,41510,7039],{"class":5424},[5280,41512,5218],{"class":5452},[5280,41514,5929],{"class":5293},[5280,41516,5835],{"class":5452},[5280,41518,41519],{"class":5297},"\"Request completed\"",[5280,41521,5598],{"class":5452},[5280,41523,41524],{"class":5282,"line":5378},[5280,41525,6834],{"class":5452},[5280,41527,41528],{"class":5282,"line":5391},[5280,41529,5976],{"class":5452},[5262,41531,41532],{},"Або краще — використовуйте окремий Recovery Middleware.",[5266,41534,41536],{"id":41535},"помилка-2-модифікація-responsewriter-після-writeheader","Помилка 2: Модифікація ResponseWriter після WriteHeader",[5262,41538,41539],{},[5227,41540,39621],{},[5271,41542,41544],{"className":5411,"code":41543,"language":5325,"meta":5276,"style":5276},"func handler(w http.ResponseWriter, r *http.Request) {\n    w.WriteHeader(http.StatusOK)\n    w.Header().Set(\"X-Custom\", \"value\")  \u002F\u002F ❌ Заголовки вже надіслані!\n    json.NewEncoder(w).Encode(data)\n}\n",[5215,41545,41546,41577,41595,41623,41645],{"__ignoreMap":5276},[5280,41547,41548,41550,41553,41555,41557,41559,41561,41563,41565,41567,41569,41571,41573,41575],{"class":5282,"line":5283},[5280,41549,5862],{"class":5619},[5280,41551,41552],{"class":5293}," handler",[5280,41554,5835],{"class":5452},[5280,41556,5994],{"class":5424},[5280,41558,5997],{"class":5758},[5280,41560,5218],{"class":5452},[5280,41562,6002],{"class":5758},[5280,41564,5887],{"class":5452},[5280,41566,6007],{"class":5424},[5280,41568,6010],{"class":5452},[5280,41570,5952],{"class":5758},[5280,41572,5218],{"class":5452},[5280,41574,6017],{"class":5758},[5280,41576,6020],{"class":5452},[5280,41578,41579,41581,41583,41585,41587,41589,41591,41593],{"class":5282,"line":5290},[5280,41580,6730],{"class":5424},[5280,41582,5218],{"class":5452},[5280,41584,6457],{"class":5293},[5280,41586,5835],{"class":5452},[5280,41588,5952],{"class":5424},[5280,41590,5218],{"class":5452},[5280,41592,9775],{"class":5424},[5280,41594,5598],{"class":5452},[5280,41596,41597,41599,41601,41603,41605,41607,41609,41612,41614,41617,41620],{"class":5282,"line":5301},[5280,41598,6730],{"class":5424},[5280,41600,5218],{"class":5452},[5280,41602,6138],{"class":5293},[5280,41604,6141],{"class":5452},[5280,41606,6144],{"class":5293},[5280,41608,5835],{"class":5452},[5280,41610,41611],{"class":5297},"\"X-Custom\"",[5280,41613,5887],{"class":5452},[5280,41615,41616],{"class":5297},"\"value\"",[5280,41618,41619],{"class":5452},")  ",[5280,41621,41622],{"class":5286},"\u002F\u002F ❌ Заголовки вже надіслані!\n",[5280,41624,41625,41627,41629,41631,41633,41635,41637,41639,41641,41643],{"class":5282,"line":5309},[5280,41626,6754],{"class":5424},[5280,41628,5218],{"class":5452},[5280,41630,6167],{"class":5293},[5280,41632,5835],{"class":5452},[5280,41634,5994],{"class":5424},[5280,41636,6174],{"class":5452},[5280,41638,6177],{"class":5293},[5280,41640,5835],{"class":5452},[5280,41642,13447],{"class":5424},[5280,41644,5598],{"class":5452},[5280,41646,41647],{"class":5282,"line":5316},[5280,41648,5976],{"class":5452},[5262,41650,41651],{},[5227,41652,39771],{},[5271,41654,41656],{"className":5411,"code":41655,"language":5325,"meta":5276,"style":5276},"func handler(w http.ResponseWriter, r *http.Request) {\n    w.Header().Set(\"Content-Type\", \"application\u002Fjson\")\n    w.Header().Set(\"X-Custom\", \"value\")  \u002F\u002F ✅ Спочатку заголовки\n    w.WriteHeader(http.StatusOK)         \u002F\u002F Потім статус\n    json.NewEncoder(w).Encode(data)      \u002F\u002F Потім тіло\n}\n",[5215,41657,41658,41688,41710,41735,41757,41783],{"__ignoreMap":5276},[5280,41659,41660,41662,41664,41666,41668,41670,41672,41674,41676,41678,41680,41682,41684,41686],{"class":5282,"line":5283},[5280,41661,5862],{"class":5619},[5280,41663,41552],{"class":5293},[5280,41665,5835],{"class":5452},[5280,41667,5994],{"class":5424},[5280,41669,5997],{"class":5758},[5280,41671,5218],{"class":5452},[5280,41673,6002],{"class":5758},[5280,41675,5887],{"class":5452},[5280,41677,6007],{"class":5424},[5280,41679,6010],{"class":5452},[5280,41681,5952],{"class":5758},[5280,41683,5218],{"class":5452},[5280,41685,6017],{"class":5758},[5280,41687,6020],{"class":5452},[5280,41689,41690,41692,41694,41696,41698,41700,41702,41704,41706,41708],{"class":5282,"line":5290},[5280,41691,6730],{"class":5424},[5280,41693,5218],{"class":5452},[5280,41695,6138],{"class":5293},[5280,41697,6141],{"class":5452},[5280,41699,6144],{"class":5293},[5280,41701,5835],{"class":5452},[5280,41703,6149],{"class":5297},[5280,41705,5887],{"class":5452},[5280,41707,6154],{"class":5297},[5280,41709,5598],{"class":5452},[5280,41711,41712,41714,41716,41718,41720,41722,41724,41726,41728,41730,41732],{"class":5282,"line":5301},[5280,41713,6730],{"class":5424},[5280,41715,5218],{"class":5452},[5280,41717,6138],{"class":5293},[5280,41719,6141],{"class":5452},[5280,41721,6144],{"class":5293},[5280,41723,5835],{"class":5452},[5280,41725,41611],{"class":5297},[5280,41727,5887],{"class":5452},[5280,41729,41616],{"class":5297},[5280,41731,41619],{"class":5452},[5280,41733,41734],{"class":5286},"\u002F\u002F ✅ Спочатку заголовки\n",[5280,41736,41737,41739,41741,41743,41745,41747,41749,41751,41754],{"class":5282,"line":5309},[5280,41738,6730],{"class":5424},[5280,41740,5218],{"class":5452},[5280,41742,6457],{"class":5293},[5280,41744,5835],{"class":5452},[5280,41746,5952],{"class":5424},[5280,41748,5218],{"class":5452},[5280,41750,9775],{"class":5424},[5280,41752,41753],{"class":5452},")         ",[5280,41755,41756],{"class":5286},"\u002F\u002F Потім статус\n",[5280,41758,41759,41761,41763,41765,41767,41769,41771,41773,41775,41777,41780],{"class":5282,"line":5316},[5280,41760,6754],{"class":5424},[5280,41762,5218],{"class":5452},[5280,41764,6167],{"class":5293},[5280,41766,5835],{"class":5452},[5280,41768,5994],{"class":5424},[5280,41770,6174],{"class":5452},[5280,41772,6177],{"class":5293},[5280,41774,5835],{"class":5452},[5280,41776,13447],{"class":5424},[5280,41778,41779],{"class":5452},")      ",[5280,41781,41782],{"class":5286},"\u002F\u002F Потім тіло\n",[5280,41784,41785],{"class":5282,"line":5322},[5280,41786,5976],{"class":5452},[5266,41788,41790],{"id":41789},"помилка-3-витік-горутин-через-неперевірку-ctxdone","Помилка 3: Витік горутин через неперевірку ctx.Done()",[5262,41792,41793],{},[5227,41794,39621],{},[5271,41796,41798],{"className":5411,"code":41797,"language":5325,"meta":5276,"style":5276},"func (uc *UserUseCase) ProcessLongOperation(ctx context.Context) error {\n    \u002F\u002F Довга операція без перевірки контексту\n    time.Sleep(10 * time.Second)\n    \u002F\u002F Що якщо клієнт відключився?\n    return uc.repo.Save(ctx, data)\n}\n",[5215,41799,41800,41833,41838,41862,41867,41893],{"__ignoreMap":5276},[5280,41801,41802,41804,41806,41808,41810,41812,41814,41817,41819,41821,41823,41825,41827,41829,41831],{"class":5282,"line":5283},[5280,41803,5862],{"class":5619},[5280,41805,5720],{"class":5452},[5280,41807,22107],{"class":5424},[5280,41809,8072],{"class":5452},[5280,41811,22065],{"class":5758},[5280,41813,7315],{"class":5452},[5280,41815,41816],{"class":5293},"ProcessLongOperation",[5280,41818,5835],{"class":5452},[5280,41820,15441],{"class":5424},[5280,41822,21723],{"class":5758},[5280,41824,5218],{"class":5452},[5280,41826,15393],{"class":5758},[5280,41828,7315],{"class":5452},[5280,41830,9567],{"class":5758},[5280,41832,6111],{"class":5452},[5280,41834,41835],{"class":5282,"line":5290},[5280,41836,41837],{"class":5286},"    \u002F\u002F Довга операція без перевірки контексту\n",[5280,41839,41840,41843,41845,41848,41850,41852,41854,41856,41858,41860],{"class":5282,"line":5301},[5280,41841,41842],{"class":5424},"    time",[5280,41844,5218],{"class":5452},[5280,41846,41847],{"class":5293},"Sleep",[5280,41849,5835],{"class":5452},[5280,41851,18053],{"class":5439},[5280,41853,17587],{"class":5452},[5280,41855,5587],{"class":5424},[5280,41857,5218],{"class":5452},[5280,41859,14252],{"class":5424},[5280,41861,5598],{"class":5452},[5280,41863,41864],{"class":5282,"line":5309},[5280,41865,41866],{"class":5286},"    \u002F\u002F Що якщо клієнт відключився?\n",[5280,41868,41869,41871,41874,41876,41878,41880,41883,41885,41887,41889,41891],{"class":5282,"line":5316},[5280,41870,7328],{"class":5436},[5280,41872,41873],{"class":5424}," uc",[5280,41875,5218],{"class":5452},[5280,41877,22053],{"class":5424},[5280,41879,5218],{"class":5452},[5280,41881,41882],{"class":5293},"Save",[5280,41884,5835],{"class":5452},[5280,41886,15441],{"class":5424},[5280,41888,5887],{"class":5452},[5280,41890,13447],{"class":5424},[5280,41892,5598],{"class":5452},[5280,41894,41895],{"class":5282,"line":5322},[5280,41896,5976],{"class":5452},[5262,41898,41899],{},[5227,41900,39771],{},[5271,41902,41904],{"className":5411,"code":41903,"language":5325,"meta":5276,"style":5276},"func (uc *UserUseCase) ProcessLongOperation(ctx context.Context) error {\n    select {\n    case \u003C-time.After(10 * time.Second):\n        return uc.repo.Save(ctx, data)\n    case \u003C-ctx.Done():\n        return ctx.Err()  \u002F\u002F Операція скасована\n    }\n}\n",[5215,41905,41906,41938,41945,41971,41995,42009,42024,42028],{"__ignoreMap":5276},[5280,41907,41908,41910,41912,41914,41916,41918,41920,41922,41924,41926,41928,41930,41932,41934,41936],{"class":5282,"line":5283},[5280,41909,5862],{"class":5619},[5280,41911,5720],{"class":5452},[5280,41913,22107],{"class":5424},[5280,41915,8072],{"class":5452},[5280,41917,22065],{"class":5758},[5280,41919,7315],{"class":5452},[5280,41921,41816],{"class":5293},[5280,41923,5835],{"class":5452},[5280,41925,15441],{"class":5424},[5280,41927,21723],{"class":5758},[5280,41929,5218],{"class":5452},[5280,41931,15393],{"class":5758},[5280,41933,7315],{"class":5452},[5280,41935,9567],{"class":5758},[5280,41937,6111],{"class":5452},[5280,41939,41940,41943],{"class":5282,"line":5290},[5280,41941,41942],{"class":5436},"    select",[5280,41944,6111],{"class":5452},[5280,41946,41947,41949,41951,41953,41955,41957,41959,41961,41963,41965,41967,41969],{"class":5282,"line":5301},[5280,41948,6117],{"class":5436},[5280,41950,38459],{"class":5452},[5280,41952,5587],{"class":5424},[5280,41954,5218],{"class":5452},[5280,41956,41051],{"class":5293},[5280,41958,5835],{"class":5452},[5280,41960,18053],{"class":5439},[5280,41962,17587],{"class":5452},[5280,41964,5587],{"class":5424},[5280,41966,5218],{"class":5452},[5280,41968,14252],{"class":5424},[5280,41970,37251],{"class":5452},[5280,41972,41973,41975,41977,41979,41981,41983,41985,41987,41989,41991,41993],{"class":5282,"line":5309},[5280,41974,12200],{"class":5436},[5280,41976,41873],{"class":5424},[5280,41978,5218],{"class":5452},[5280,41980,22053],{"class":5424},[5280,41982,5218],{"class":5452},[5280,41984,41882],{"class":5293},[5280,41986,5835],{"class":5452},[5280,41988,15441],{"class":5424},[5280,41990,5887],{"class":5452},[5280,41992,13447],{"class":5424},[5280,41994,5598],{"class":5452},[5280,41996,41997,41999,42001,42003,42005,42007],{"class":5282,"line":5316},[5280,41998,6117],{"class":5436},[5280,42000,38459],{"class":5452},[5280,42002,15441],{"class":5424},[5280,42004,5218],{"class":5452},[5280,42006,38466],{"class":5293},[5280,42008,38469],{"class":5452},[5280,42010,42011,42013,42015,42017,42019,42021],{"class":5282,"line":5322},[5280,42012,12200],{"class":5436},[5280,42014,39358],{"class":5424},[5280,42016,5218],{"class":5452},[5280,42018,38489],{"class":5293},[5280,42020,38816],{"class":5452},[5280,42022,42023],{"class":5286},"\u002F\u002F Операція скасована\n",[5280,42025,42026],{"class":5282,"line":5336},[5280,42027,6553],{"class":5452},[5280,42029,42030],{"class":5282,"line":5341},[5280,42031,5976],{"class":5452},[5266,42033,42035],{"id":42034},"помилка-4-usecase-залежить-від-http-деталей","Помилка 4: UseCase залежить від HTTP-деталей",[5262,42037,42038],{},[5227,42039,39621],{},[5271,42041,42043],{"className":5411,"code":42042,"language":5325,"meta":5276,"style":5276},"func (uc *UserUseCase) RegisterUser(w http.ResponseWriter, r *http.Request) {\n    \u002F\u002F UseCase не повинен знати про HTTP!\n    var req RegisterRequest\n    json.NewDecoder(r.Body).Decode(&req)\n    \u002F\u002F ...\n    json.NewEncoder(w).Encode(user)\n}\n",[5215,42044,42045,42085,42090,42098,42124,42128,42150],{"__ignoreMap":5276},[5280,42046,42047,42049,42051,42053,42055,42057,42059,42061,42063,42065,42067,42069,42071,42073,42075,42077,42079,42081,42083],{"class":5282,"line":5283},[5280,42048,5862],{"class":5619},[5280,42050,5720],{"class":5452},[5280,42052,22107],{"class":5424},[5280,42054,8072],{"class":5452},[5280,42056,22065],{"class":5758},[5280,42058,7315],{"class":5452},[5280,42060,20688],{"class":5293},[5280,42062,5835],{"class":5452},[5280,42064,5994],{"class":5424},[5280,42066,5997],{"class":5758},[5280,42068,5218],{"class":5452},[5280,42070,6002],{"class":5758},[5280,42072,5887],{"class":5452},[5280,42074,6007],{"class":5424},[5280,42076,6010],{"class":5452},[5280,42078,5952],{"class":5758},[5280,42080,5218],{"class":5452},[5280,42082,6017],{"class":5758},[5280,42084,6020],{"class":5452},[5280,42086,42087],{"class":5282,"line":5290},[5280,42088,42089],{"class":5286},"    \u002F\u002F UseCase не повинен знати про HTTP!\n",[5280,42091,42092,42094,42096],{"class":5282,"line":5301},[5280,42093,18383],{"class":5619},[5280,42095,18386],{"class":5424},[5280,42097,25254],{"class":5758},[5280,42099,42100,42102,42104,42106,42108,42110,42112,42114,42116,42118,42120,42122],{"class":5282,"line":5309},[5280,42101,6754],{"class":5424},[5280,42103,5218],{"class":5452},[5280,42105,6272],{"class":5293},[5280,42107,5835],{"class":5452},[5280,42109,6007],{"class":5424},[5280,42111,5218],{"class":5452},[5280,42113,6281],{"class":5424},[5280,42115,6174],{"class":5452},[5280,42117,6286],{"class":5293},[5280,42119,6289],{"class":5452},[5280,42121,18462],{"class":5424},[5280,42123,5598],{"class":5452},[5280,42125,42126],{"class":5282,"line":5316},[5280,42127,33035],{"class":5286},[5280,42129,42130,42132,42134,42136,42138,42140,42142,42144,42146,42148],{"class":5282,"line":5322},[5280,42131,6754],{"class":5424},[5280,42133,5218],{"class":5452},[5280,42135,6167],{"class":5293},[5280,42137,5835],{"class":5452},[5280,42139,5994],{"class":5424},[5280,42141,6174],{"class":5452},[5280,42143,6177],{"class":5293},[5280,42145,5835],{"class":5452},[5280,42147,6292],{"class":5424},[5280,42149,5598],{"class":5452},[5280,42151,42152],{"class":5282,"line":5336},[5280,42153,5976],{"class":5452},[5262,42155,42156],{},[5227,42157,39771],{},[5271,42159,42161],{"className":5411,"code":42160,"language":5325,"meta":5276,"style":5276},"func (uc *UserUseCase) RegisterUser(ctx context.Context, name, email, password string) (*domain.User, error) {\n    \u002F\u002F UseCase працює з чистими даними\n    \u002F\u002F ...\n    return user, nil\n}\n",[5215,42162,42163,42217,42222,42226,42236],{"__ignoreMap":5276},[5280,42164,42165,42167,42169,42171,42173,42175,42177,42179,42181,42183,42185,42187,42189,42191,42193,42195,42197,42199,42201,42203,42205,42207,42209,42211,42213,42215],{"class":5282,"line":5283},[5280,42166,5862],{"class":5619},[5280,42168,5720],{"class":5452},[5280,42170,22107],{"class":5424},[5280,42172,8072],{"class":5452},[5280,42174,22065],{"class":5758},[5280,42176,7315],{"class":5452},[5280,42178,20688],{"class":5293},[5280,42180,5835],{"class":5452},[5280,42182,15441],{"class":5424},[5280,42184,21723],{"class":5758},[5280,42186,5218],{"class":5452},[5280,42188,15393],{"class":5758},[5280,42190,5887],{"class":5452},[5280,42192,20121],{"class":5424},[5280,42194,5887],{"class":5452},[5280,42196,20126],{"class":5424},[5280,42198,5887],{"class":5452},[5280,42200,20131],{"class":5424},[5280,42202,6239],{"class":5758},[5280,42204,20151],{"class":5452},[5280,42206,22144],{"class":5758},[5280,42208,5218],{"class":5452},[5280,42210,17513],{"class":5758},[5280,42212,5887],{"class":5452},[5280,42214,9567],{"class":5758},[5280,42216,6020],{"class":5452},[5280,42218,42219],{"class":5282,"line":5290},[5280,42220,42221],{"class":5286},"    \u002F\u002F UseCase працює з чистими даними\n",[5280,42223,42224],{"class":5282,"line":5301},[5280,42225,33035],{"class":5286},[5280,42227,42228,42230,42232,42234],{"class":5282,"line":5309},[5280,42229,7328],{"class":5436},[5280,42231,6213],{"class":5424},[5280,42233,5887],{"class":5452},[5280,42235,20459],{"class":5619},[5280,42237,42238],{"class":5282,"line":5316},[5280,42239,5976],{"class":5452},[5266,42241,42243],{"id":42242},"помилка-5-повернення-доменних-сутностей-з-паролями","Помилка 5: Повернення доменних сутностей з паролями",[5262,42245,42246],{},[5227,42247,39621],{},[5271,42249,42251],{"className":5411,"code":42250,"language":5325,"meta":5276,"style":5276},"func (h *UserHandler) GetProfile(w http.ResponseWriter, r *http.Request) {\n    user, _ := h.useCase.GetUser(...)\n    json.NewEncoder(w).Encode(user)  \u002F\u002F ❌ Віддаємо пароль клієнту!\n}\n",[5215,42252,42253,42294,42317,42342],{"__ignoreMap":5276},[5280,42254,42255,42257,42259,42261,42263,42265,42267,42270,42272,42274,42276,42278,42280,42282,42284,42286,42288,42290,42292],{"class":5282,"line":5283},[5280,42256,5862],{"class":5619},[5280,42258,5720],{"class":5452},[5280,42260,8069],{"class":5424},[5280,42262,8072],{"class":5452},[5280,42264,25066],{"class":5758},[5280,42266,7315],{"class":5452},[5280,42268,42269],{"class":5293},"GetProfile",[5280,42271,5835],{"class":5452},[5280,42273,5994],{"class":5424},[5280,42275,5997],{"class":5758},[5280,42277,5218],{"class":5452},[5280,42279,6002],{"class":5758},[5280,42281,5887],{"class":5452},[5280,42283,6007],{"class":5424},[5280,42285,6010],{"class":5452},[5280,42287,5952],{"class":5758},[5280,42289,5218],{"class":5452},[5280,42291,6017],{"class":5758},[5280,42293,6020],{"class":5452},[5280,42295,42296,42298,42300,42302,42304,42306,42308,42310,42312,42315],{"class":5282,"line":5290},[5280,42297,18871],{"class":5424},[5280,42299,5887],{"class":5452},[5280,42301,22169],{"class":5424},[5280,42303,6029],{"class":5452},[5280,42305,11326],{"class":5424},[5280,42307,5218],{"class":5452},[5280,42309,25053],{"class":5424},[5280,42311,5218],{"class":5452},[5280,42313,42314],{"class":5293},"GetUser",[5280,42316,32865],{"class":5452},[5280,42318,42319,42321,42323,42325,42327,42329,42331,42333,42335,42337,42339],{"class":5282,"line":5301},[5280,42320,6754],{"class":5424},[5280,42322,5218],{"class":5452},[5280,42324,6167],{"class":5293},[5280,42326,5835],{"class":5452},[5280,42328,5994],{"class":5424},[5280,42330,6174],{"class":5452},[5280,42332,6177],{"class":5293},[5280,42334,5835],{"class":5452},[5280,42336,6292],{"class":5424},[5280,42338,41619],{"class":5452},[5280,42340,42341],{"class":5286},"\u002F\u002F ❌ Віддаємо пароль клієнту!\n",[5280,42343,42344],{"class":5282,"line":5309},[5280,42345,5976],{"class":5452},[5262,42347,42348],{},[5227,42349,39771],{},[5271,42351,42353],{"className":5411,"code":42352,"language":5325,"meta":5276,"style":5276},"type UserResponse struct {\n    ID    string `json:\"id\"`\n    Name  string `json:\"name\"`\n    Email string `json:\"email\"`\n    \u002F\u002F Без password!\n}\n\nfunc (h *UserHandler) GetProfile(w http.ResponseWriter, r *http.Request) {\n    user, _ := h.useCase.GetUser(...)\n    \n    response := UserResponse{\n        ID:    user.ID,\n        Name:  user.Name,\n        Email: user.Email,\n    }\n    \n    json.NewEncoder(w).Encode(response)  \u002F\u002F ✅ Безпечно\n}\n",[5215,42354,42355,42365,42373,42381,42389,42394,42398,42402,42442,42464,42468,42478,42492,42506,42520,42524,42528,42553],{"__ignoreMap":5276},[5280,42356,42357,42359,42361,42363],{"class":5282,"line":5283},[5280,42358,7921],{"class":5619},[5280,42360,25154],{"class":5758},[5280,42362,6216],{"class":5619},[5280,42364,6111],{"class":5452},[5280,42366,42367,42369,42371],{"class":5282,"line":5290},[5280,42368,17439],{"class":5424},[5280,42370,18407],{"class":5758},[5280,42372,25167],{"class":5297},[5280,42374,42375,42377,42379],{"class":5282,"line":5301},[5280,42376,17450],{"class":5424},[5280,42378,6227],{"class":5758},[5280,42380,6230],{"class":5297},[5280,42382,42383,42385,42387],{"class":5282,"line":5309},[5280,42384,17461],{"class":5424},[5280,42386,6239],{"class":5758},[5280,42388,6242],{"class":5297},[5280,42390,42391],{"class":5282,"line":5316},[5280,42392,42393],{"class":5286},"    \u002F\u002F Без password!\n",[5280,42395,42396],{"class":5282,"line":5322},[5280,42397,5976],{"class":5452},[5280,42399,42400],{"class":5282,"line":5336},[5280,42401,5313],{"emptyLinePlaceholder":5312},[5280,42403,42404,42406,42408,42410,42412,42414,42416,42418,42420,42422,42424,42426,42428,42430,42432,42434,42436,42438,42440],{"class":5282,"line":5341},[5280,42405,5862],{"class":5619},[5280,42407,5720],{"class":5452},[5280,42409,8069],{"class":5424},[5280,42411,8072],{"class":5452},[5280,42413,25066],{"class":5758},[5280,42415,7315],{"class":5452},[5280,42417,42269],{"class":5293},[5280,42419,5835],{"class":5452},[5280,42421,5994],{"class":5424},[5280,42423,5997],{"class":5758},[5280,42425,5218],{"class":5452},[5280,42427,6002],{"class":5758},[5280,42429,5887],{"class":5452},[5280,42431,6007],{"class":5424},[5280,42433,6010],{"class":5452},[5280,42435,5952],{"class":5758},[5280,42437,5218],{"class":5452},[5280,42439,6017],{"class":5758},[5280,42441,6020],{"class":5452},[5280,42443,42444,42446,42448,42450,42452,42454,42456,42458,42460,42462],{"class":5282,"line":5347},[5280,42445,18871],{"class":5424},[5280,42447,5887],{"class":5452},[5280,42449,22169],{"class":5424},[5280,42451,6029],{"class":5452},[5280,42453,11326],{"class":5424},[5280,42455,5218],{"class":5452},[5280,42457,25053],{"class":5424},[5280,42459,5218],{"class":5452},[5280,42461,42314],{"class":5293},[5280,42463,32865],{"class":5452},[5280,42465,42466],{"class":5282,"line":5358},[5280,42467,6724],{"class":5452},[5280,42469,42470,42472,42474,42476],{"class":5282,"line":5368},[5280,42471,25515],{"class":5424},[5280,42473,6029],{"class":5452},[5280,42475,25520],{"class":5758},[5280,42477,6781],{"class":5452},[5280,42479,42480,42482,42484,42486,42488,42490],{"class":5282,"line":5378},[5280,42481,17528],{"class":5424},[5280,42483,13867],{"class":5452},[5280,42485,6292],{"class":5424},[5280,42487,5218],{"class":5452},[5280,42489,18635],{"class":5424},[5280,42491,6796],{"class":5452},[5280,42493,42494,42496,42498,42500,42502,42504],{"class":5282,"line":5391},[5280,42495,17540],{"class":5424},[5280,42497,19517],{"class":5452},[5280,42499,6292],{"class":5424},[5280,42501,5218],{"class":5452},[5280,42503,6502],{"class":5424},[5280,42505,6796],{"class":5452},[5280,42507,42508,42510,42512,42514,42516,42518],{"class":5282,"line":5705},[5280,42509,17553],{"class":5424},[5280,42511,6790],{"class":5452},[5280,42513,6292],{"class":5424},[5280,42515,5218],{"class":5452},[5280,42517,6437],{"class":5424},[5280,42519,6796],{"class":5452},[5280,42521,42522],{"class":5282,"line":5820},[5280,42523,6553],{"class":5452},[5280,42525,42526],{"class":5282,"line":5854},[5280,42527,6724],{"class":5452},[5280,42529,42530,42532,42534,42536,42538,42540,42542,42544,42546,42548,42550],{"class":5282,"line":5859},[5280,42531,6754],{"class":5424},[5280,42533,5218],{"class":5452},[5280,42535,6167],{"class":5293},[5280,42537,5835],{"class":5452},[5280,42539,5994],{"class":5424},[5280,42541,6174],{"class":5452},[5280,42543,6177],{"class":5293},[5280,42545,5835],{"class":5452},[5280,42547,25656],{"class":5424},[5280,42549,41619],{"class":5452},[5280,42551,42552],{"class":5286},"\u002F\u002F ✅ Безпечно\n",[5280,42554,42555],{"class":5282,"line":5871},[5280,42556,5976],{"class":5452},[5266,42558,42560],{"id":42559},"помилка-6-ігнорування-помилок-close","Помилка 6: Ігнорування помилок Close()",[5262,42562,42563],{},[5227,42564,39621],{},[5271,42566,42568],{"className":5411,"code":42567,"language":5325,"meta":5276,"style":5276},"defer r.Body.Close()  \u002F\u002F Ігноруємо можливу помилку\n",[5215,42569,42570],{"__ignoreMap":5276},[5280,42571,42572,42574,42576,42578,42580,42582,42584,42586],{"class":5282,"line":5283},[5280,42573,10768],{"class":5436},[5280,42575,6104],{"class":5424},[5280,42577,5218],{"class":5452},[5280,42579,6281],{"class":5424},[5280,42581,5218],{"class":5452},[5280,42583,26574],{"class":5293},[5280,42585,38816],{"class":5452},[5280,42587,42588],{"class":5286},"\u002F\u002F Ігноруємо можливу помилку\n",[5262,42590,42591],{},[5227,42592,39771],{},[5271,42594,42596],{"className":5411,"code":42595,"language":5325,"meta":5276,"style":5276},"defer func() {\n    if err := r.Body.Close(); err != nil {\n        log.Printf(\"Failed to close body: %v\", err)\n    }\n}()\n",[5215,42597,42598,42606,42634,42657,42661],{"__ignoreMap":5276},[5280,42599,42600,42602,42604],{"class":5282,"line":5283},[5280,42601,10768],{"class":5436},[5280,42603,7331],{"class":5619},[5280,42605,5868],{"class":5452},[5280,42607,42608,42610,42612,42614,42616,42618,42620,42622,42624,42626,42628,42630,42632],{"class":5282,"line":5290},[5280,42609,7023],{"class":5436},[5280,42611,6262],{"class":5424},[5280,42613,6029],{"class":5452},[5280,42615,6007],{"class":5424},[5280,42617,5218],{"class":5452},[5280,42619,6281],{"class":5424},[5280,42621,5218],{"class":5452},[5280,42623,26574],{"class":5293},[5280,42625,10615],{"class":5452},[5280,42627,6298],{"class":5424},[5280,42629,6301],{"class":5452},[5280,42631,5967],{"class":5619},[5280,42633,6111],{"class":5452},[5280,42635,42636,42638,42640,42642,42644,42647,42649,42651,42653,42655],{"class":5282,"line":5301},[5280,42637,7039],{"class":5424},[5280,42639,5218],{"class":5452},[5280,42641,6049],{"class":5293},[5280,42643,5835],{"class":5452},[5280,42645,42646],{"class":5297},"\"Failed to close body: ",[5280,42648,6362],{"class":5424},[5280,42650,6584],{"class":5297},[5280,42652,5887],{"class":5452},[5280,42654,6298],{"class":5424},[5280,42656,5598],{"class":5452},[5280,42658,42659],{"class":5282,"line":5309},[5280,42660,6553],{"class":5452},[5280,42662,42663],{"class":5282,"line":5316},[5280,42664,42665],{"class":5452},"}()\n",[5266,42667,42669],{"id":42668},"помилка-7-неправильне-використання-контекстних-ключів","Помилка 7: Неправильне використання контекстних ключів",[5262,42671,42672],{},[5227,42673,39621],{},[5271,42675,42677],{"className":5411,"code":42676,"language":5325,"meta":5276,"style":5276},"ctx := context.WithValue(r.Context(), \"user_id\", userID)  \u002F\u002F ❌ String як ключ\n",[5215,42678,42679],{"__ignoreMap":5276},[5280,42680,42681,42683,42685,42687,42689,42691,42693,42695,42697,42699,42701,42703,42705,42707,42709],{"class":5282,"line":5283},[5280,42682,15441],{"class":5424},[5280,42684,6029],{"class":5452},[5280,42686,15379],{"class":5424},[5280,42688,5218],{"class":5452},[5280,42690,15384],{"class":5293},[5280,42692,5835],{"class":5452},[5280,42694,6007],{"class":5424},[5280,42696,5218],{"class":5452},[5280,42698,15393],{"class":5293},[5280,42700,15396],{"class":5452},[5280,42702,16251],{"class":5297},[5280,42704,5887],{"class":5452},[5280,42706,15404],{"class":5424},[5280,42708,41619],{"class":5452},[5280,42710,42711],{"class":5286},"\u002F\u002F ❌ String як ключ\n",[5262,42713,42714],{},[5227,42715,39771],{},[5271,42717,42719],{"className":5411,"code":42718,"language":5325,"meta":5276,"style":5276},"type contextKey string\nconst UserIDKey contextKey = \"user_id\"\n\nctx := context.WithValue(r.Context(), UserIDKey, userID)  \u002F\u002F ✅ Власний тип\n",[5215,42720,42721,42729,42741,42745],{"__ignoreMap":5276},[5280,42722,42723,42725,42727],{"class":5282,"line":5283},[5280,42724,7921],{"class":5619},[5280,42726,14890],{"class":5758},[5280,42728,14893],{"class":5758},[5280,42730,42731,42733,42735,42737,42739],{"class":5282,"line":5290},[5280,42732,14902],{"class":5619},[5280,42734,16285],{"class":13369},[5280,42736,14890],{"class":5758},[5280,42738,5829],{"class":5452},[5280,42740,14921],{"class":5297},[5280,42742,42743],{"class":5282,"line":5301},[5280,42744,5313],{"emptyLinePlaceholder":5312},[5280,42746,42747,42749,42751,42753,42755,42757,42759,42761,42763,42765,42767,42769,42771,42773,42775],{"class":5282,"line":5309},[5280,42748,15441],{"class":5424},[5280,42750,6029],{"class":5452},[5280,42752,15379],{"class":5424},[5280,42754,5218],{"class":5452},[5280,42756,15384],{"class":5293},[5280,42758,5835],{"class":5452},[5280,42760,6007],{"class":5424},[5280,42762,5218],{"class":5452},[5280,42764,15393],{"class":5293},[5280,42766,15396],{"class":5452},[5280,42768,15399],{"class":5424},[5280,42770,5887],{"class":5452},[5280,42772,15404],{"class":5424},[5280,42774,41619],{"class":5452},[5280,42776,42777],{"class":5286},"\u002F\u002F ✅ Власний тип\n",[5266,42779,42781],{"id":42780},"помилка-8-мутація-вхідних-параметрів","Помилка 8: Мутація вхідних параметрів",[5262,42783,42784],{},[5227,42785,39621],{},[5271,42787,42789],{"className":5411,"code":42788,"language":5325,"meta":5276,"style":5276},"func (uc *UserUseCase) UpdateUser(user *domain.User) error {\n    user.UpdatedAt = time.Now()  \u002F\u002F ❌ Мутуємо вхідний параметр\n    return uc.repo.Update(user)\n}\n",[5215,42790,42791,42826,42847,42867],{"__ignoreMap":5276},[5280,42792,42793,42795,42797,42799,42801,42803,42805,42808,42810,42812,42814,42816,42818,42820,42822,42824],{"class":5282,"line":5283},[5280,42794,5862],{"class":5619},[5280,42796,5720],{"class":5452},[5280,42798,22107],{"class":5424},[5280,42800,8072],{"class":5452},[5280,42802,22065],{"class":5758},[5280,42804,7315],{"class":5452},[5280,42806,42807],{"class":5293},"UpdateUser",[5280,42809,5835],{"class":5452},[5280,42811,6292],{"class":5424},[5280,42813,6010],{"class":5452},[5280,42815,22144],{"class":5758},[5280,42817,5218],{"class":5452},[5280,42819,17513],{"class":5758},[5280,42821,7315],{"class":5452},[5280,42823,9567],{"class":5758},[5280,42825,6111],{"class":5452},[5280,42827,42828,42830,42832,42834,42836,42838,42840,42842,42844],{"class":5282,"line":5290},[5280,42829,18871],{"class":5424},[5280,42831,5218],{"class":5452},[5280,42833,24021],{"class":5424},[5280,42835,5829],{"class":5452},[5280,42837,5587],{"class":5424},[5280,42839,5218],{"class":5452},[5280,42841,6036],{"class":5293},[5280,42843,38816],{"class":5452},[5280,42845,42846],{"class":5286},"\u002F\u002F ❌ Мутуємо вхідний параметр\n",[5280,42848,42849,42851,42853,42855,42857,42859,42861,42863,42865],{"class":5282,"line":5301},[5280,42850,7328],{"class":5436},[5280,42852,41873],{"class":5424},[5280,42854,5218],{"class":5452},[5280,42856,22053],{"class":5424},[5280,42858,5218],{"class":5452},[5280,42860,23457],{"class":5293},[5280,42862,5835],{"class":5452},[5280,42864,6292],{"class":5424},[5280,42866,5598],{"class":5452},[5280,42868,42869],{"class":5282,"line":5309},[5280,42870,5976],{"class":5452},[5262,42872,42873],{},[5227,42874,39771],{},[5271,42876,42878],{"className":5411,"code":42877,"language":5325,"meta":5276,"style":5276},"func (uc *UserUseCase) UpdateUser(user *domain.User) error {\n    \u002F\u002F Створюємо копію\n    updated := *user\n    updated.UpdatedAt = time.Now()\n    return uc.repo.Update(&updated)\n}\n",[5215,42879,42880,42914,42919,42929,42947,42968],{"__ignoreMap":5276},[5280,42881,42882,42884,42886,42888,42890,42892,42894,42896,42898,42900,42902,42904,42906,42908,42910,42912],{"class":5282,"line":5283},[5280,42883,5862],{"class":5619},[5280,42885,5720],{"class":5452},[5280,42887,22107],{"class":5424},[5280,42889,8072],{"class":5452},[5280,42891,22065],{"class":5758},[5280,42893,7315],{"class":5452},[5280,42895,42807],{"class":5293},[5280,42897,5835],{"class":5452},[5280,42899,6292],{"class":5424},[5280,42901,6010],{"class":5452},[5280,42903,22144],{"class":5758},[5280,42905,5218],{"class":5452},[5280,42907,17513],{"class":5758},[5280,42909,7315],{"class":5452},[5280,42911,9567],{"class":5758},[5280,42913,6111],{"class":5452},[5280,42915,42916],{"class":5282,"line":5290},[5280,42917,42918],{"class":5286},"    \u002F\u002F Створюємо копію\n",[5280,42920,42921,42924,42927],{"class":5282,"line":5301},[5280,42922,42923],{"class":5424},"    updated",[5280,42925,42926],{"class":5452}," := *",[5280,42928,20794],{"class":5424},[5280,42930,42931,42933,42935,42937,42939,42941,42943,42945],{"class":5282,"line":5309},[5280,42932,42923],{"class":5424},[5280,42934,5218],{"class":5452},[5280,42936,24021],{"class":5424},[5280,42938,5829],{"class":5452},[5280,42940,5587],{"class":5424},[5280,42942,5218],{"class":5452},[5280,42944,6036],{"class":5293},[5280,42946,6039],{"class":5452},[5280,42948,42949,42951,42953,42955,42957,42959,42961,42963,42966],{"class":5282,"line":5316},[5280,42950,7328],{"class":5436},[5280,42952,41873],{"class":5424},[5280,42954,5218],{"class":5452},[5280,42956,22053],{"class":5424},[5280,42958,5218],{"class":5452},[5280,42960,23457],{"class":5293},[5280,42962,6289],{"class":5452},[5280,42964,42965],{"class":5424},"updated",[5280,42967,5598],{"class":5452},[5280,42969,42970],{"class":5282,"line":5322},[5280,42971,5976],{"class":5452},[5254,42973],{},[5257,42975,42977],{"id":42976},"debugging-та-troubleshooting","Debugging та Troubleshooting",[5266,42979,42981],{"id":42980},"логування-у-middleware-правильний-підхід","Логування у Middleware: правильний підхід",[5262,42983,42984],{},[5227,42985,42986,42987,42990],{},"Структуроване логування з ",[5215,42988,42989],{},"log\u002Fslog"," (Go 1.21+):",[5271,42992,42994],{"className":5411,"code":42993,"language":5325,"meta":5276,"style":5276},"\u002F\u002F middleware\u002Flogger.go\npackage middleware\n\nimport (\n    \"log\u002Fslog\"\n    \"net\u002Fhttp\"\n    \"time\"\n)\n\nfunc Logger(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        start := time.Now()\n        \n        \u002F\u002F Обгортаємо ResponseWriter\n        wrapped := &responseWriter{\n            ResponseWriter: w,\n            statusCode:     http.StatusOK,\n        }\n        \n        \u002F\u002F Логуємо початок запиту\n        slog.Info(\"request started\",\n            \"method\", r.Method,\n            \"path\", r.URL.Path,\n            \"remote_addr\", r.RemoteAddr,\n            \"user_agent\", r.UserAgent(),\n        )\n        \n        \u002F\u002F Виконуємо запит\n        next.ServeHTTP(wrapped, r)\n        \n        duration := time.Since(start)\n        \n        \u002F\u002F Логуємо завершення з контекстом\n        logger := slog.With(\n            \"method\", r.Method,\n            \"path\", r.URL.Path,\n            \"status\", wrapped.statusCode,\n            \"duration_ms\", duration.Milliseconds(),\n            \"remote_addr\", r.RemoteAddr,\n        )\n        \n        \u002F\u002F Різний рівень залежно від статусу\n        switch {\n        case wrapped.statusCode >= 500:\n            logger.Error(\"request completed with server error\")\n        case wrapped.statusCode >= 400:\n            logger.Warn(\"request completed with client error\")\n        default:\n            logger.Info(\"request completed successfully\")\n        }\n    })\n}\n",[5215,42995,42996,43000,43006,43010,43016,43021,43025,43029,43033,43037,43063,43101,43115,43119,43123,43133,43143,43157,43161,43165,43170,43187,43202,43221,43236,43252,43256,43260,43265,43283,43287,43305,43309,43314,43331,43345,43363,43377,43394,43408,43412,43416,43421,43427,43445,43461,43478,43494,43500,43515,43519,43523],{"__ignoreMap":5276},[5280,42997,42998],{"class":5282,"line":5283},[5280,42999,8960],{"class":5286},[5280,43001,43002,43004],{"class":5282,"line":5290},[5280,43003,5755],{"class":5619},[5280,43005,5641],{"class":5758},[5280,43007,43008],{"class":5282,"line":5301},[5280,43009,5313],{"emptyLinePlaceholder":5312},[5280,43011,43012,43014],{"class":5282,"line":5309},[5280,43013,5768],{"class":5436},[5280,43015,5453],{"class":5452},[5280,43017,43018],{"class":5282,"line":5316},[5280,43019,43020],{"class":5297},"    \"log\u002Fslog\"\n",[5280,43022,43023],{"class":5282,"line":5322},[5280,43024,5790],{"class":5297},[5280,43026,43027],{"class":5282,"line":5336},[5280,43028,5795],{"class":5297},[5280,43030,43031],{"class":5282,"line":5341},[5280,43032,5598],{"class":5452},[5280,43034,43035],{"class":5282,"line":5347},[5280,43036,5313],{"emptyLinePlaceholder":5312},[5280,43038,43039,43041,43043,43045,43047,43049,43051,43053,43055,43057,43059,43061],{"class":5282,"line":5358},[5280,43040,5862],{"class":5619},[5280,43042,9008],{"class":5293},[5280,43044,5835],{"class":5452},[5280,43046,7305],{"class":5424},[5280,43048,5997],{"class":5758},[5280,43050,5218],{"class":5452},[5280,43052,7935],{"class":5758},[5280,43054,7315],{"class":5452},[5280,43056,5952],{"class":5758},[5280,43058,5218],{"class":5452},[5280,43060,7935],{"class":5758},[5280,43062,6111],{"class":5452},[5280,43064,43065,43067,43069,43071,43073,43075,43077,43079,43081,43083,43085,43087,43089,43091,43093,43095,43097,43099],{"class":5282,"line":5368},[5280,43066,7328],{"class":5436},[5280,43068,5997],{"class":5424},[5280,43070,5218],{"class":5452},[5280,43072,7312],{"class":5293},[5280,43074,5835],{"class":5452},[5280,43076,5862],{"class":5619},[5280,43078,5835],{"class":5452},[5280,43080,5994],{"class":5424},[5280,43082,5997],{"class":5758},[5280,43084,5218],{"class":5452},[5280,43086,6002],{"class":5758},[5280,43088,5887],{"class":5452},[5280,43090,6007],{"class":5424},[5280,43092,6010],{"class":5452},[5280,43094,5952],{"class":5758},[5280,43096,5218],{"class":5452},[5280,43098,6017],{"class":5758},[5280,43100,6020],{"class":5452},[5280,43102,43103,43105,43107,43109,43111,43113],{"class":5282,"line":5378},[5280,43104,7360],{"class":5424},[5280,43106,6029],{"class":5452},[5280,43108,5587],{"class":5424},[5280,43110,5218],{"class":5452},[5280,43112,6036],{"class":5293},[5280,43114,6039],{"class":5452},[5280,43116,43117],{"class":5282,"line":5391},[5280,43118,6190],{"class":5452},[5280,43120,43121],{"class":5282,"line":5705},[5280,43122,9737],{"class":5286},[5280,43124,43125,43127,43129,43131],{"class":5282,"line":5820},[5280,43126,9742],{"class":5424},[5280,43128,9745],{"class":5452},[5280,43130,9435],{"class":5758},[5280,43132,6781],{"class":5452},[5280,43134,43135,43137,43139,43141],{"class":5282,"line":5854},[5280,43136,9754],{"class":5424},[5280,43138,6790],{"class":5452},[5280,43140,5994],{"class":5424},[5280,43142,6796],{"class":5452},[5280,43144,43145,43147,43149,43151,43153,43155],{"class":5282,"line":5859},[5280,43146,9765],{"class":5424},[5280,43148,9768],{"class":5452},[5280,43150,5952],{"class":5424},[5280,43152,5218],{"class":5452},[5280,43154,9775],{"class":5424},[5280,43156,6796],{"class":5452},[5280,43158,43159],{"class":5282,"line":5871},[5280,43160,6248],{"class":5452},[5280,43162,43163],{"class":5282,"line":5895},[5280,43164,6190],{"class":5452},[5280,43166,43167],{"class":5282,"line":5916},[5280,43168,43169],{"class":5286},"        \u002F\u002F Логуємо початок запиту\n",[5280,43171,43172,43175,43177,43180,43182,43185],{"class":5282,"line":5921},[5280,43173,43174],{"class":5424},"        slog",[5280,43176,5218],{"class":5452},[5280,43178,43179],{"class":5293},"Info",[5280,43181,5835],{"class":5452},[5280,43183,43184],{"class":5297},"\"request started\"",[5280,43186,6796],{"class":5452},[5280,43188,43189,43192,43194,43196,43198,43200],{"class":5282,"line":5939},[5280,43190,43191],{"class":5297},"            \"method\"",[5280,43193,5887],{"class":5452},[5280,43195,6007],{"class":5424},[5280,43197,5218],{"class":5452},[5280,43199,6074],{"class":5424},[5280,43201,6796],{"class":5452},[5280,43203,43204,43207,43209,43211,43213,43215,43217,43219],{"class":5282,"line":5973},[5280,43205,43206],{"class":5297},"            \"path\"",[5280,43208,5887],{"class":5452},[5280,43210,6007],{"class":5424},[5280,43212,5218],{"class":5452},[5280,43214,6083],{"class":5424},[5280,43216,5218],{"class":5452},[5280,43218,6088],{"class":5424},[5280,43220,6796],{"class":5452},[5280,43222,43223,43226,43228,43230,43232,43234],{"class":5282,"line":5979},[5280,43224,43225],{"class":5297},"            \"remote_addr\"",[5280,43227,5887],{"class":5452},[5280,43229,6007],{"class":5424},[5280,43231,5218],{"class":5452},[5280,43233,9160],{"class":5424},[5280,43235,6796],{"class":5452},[5280,43237,43238,43241,43243,43245,43247,43250],{"class":5282,"line":5984},[5280,43239,43240],{"class":5297},"            \"user_agent\"",[5280,43242,5887],{"class":5452},[5280,43244,6007],{"class":5424},[5280,43246,5218],{"class":5452},[5280,43248,43249],{"class":5293},"UserAgent",[5280,43251,17825],{"class":5452},[5280,43253,43254],{"class":5282,"line":6023},[5280,43255,9167],{"class":5452},[5280,43257,43258],{"class":5282,"line":6042},[5280,43259,6190],{"class":5452},[5280,43261,43262],{"class":5282,"line":6093},[5280,43263,43264],{"class":5286},"        \u002F\u002F Виконуємо запит\n",[5280,43266,43267,43269,43271,43273,43275,43277,43279,43281],{"class":5282,"line":6098},[5280,43268,7426],{"class":5424},[5280,43270,5218],{"class":5452},[5280,43272,8033],{"class":5293},[5280,43274,5835],{"class":5452},[5280,43276,9889],{"class":5424},[5280,43278,5887],{"class":5452},[5280,43280,6007],{"class":5424},[5280,43282,5598],{"class":5452},[5280,43284,43285],{"class":5282,"line":6114},[5280,43286,6190],{"class":5452},[5280,43288,43289,43291,43293,43295,43297,43299,43301,43303],{"class":5282,"line":6130},[5280,43290,9208],{"class":5424},[5280,43292,6029],{"class":5452},[5280,43294,5587],{"class":5424},[5280,43296,5218],{"class":5452},[5280,43298,6399],{"class":5293},[5280,43300,5835],{"class":5452},[5280,43302,6404],{"class":5424},[5280,43304,5598],{"class":5452},[5280,43306,43307],{"class":5282,"line":6159},[5280,43308,6190],{"class":5452},[5280,43310,43311],{"class":5282,"line":6187},[5280,43312,43313],{"class":5286},"        \u002F\u002F Логуємо завершення з контекстом\n",[5280,43315,43316,43319,43321,43324,43326,43329],{"class":5282,"line":6193},[5280,43317,43318],{"class":5424},"        logger",[5280,43320,6029],{"class":5452},[5280,43322,43323],{"class":5424},"slog",[5280,43325,5218],{"class":5452},[5280,43327,43328],{"class":5293},"With",[5280,43330,9105],{"class":5452},[5280,43332,43333,43335,43337,43339,43341,43343],{"class":5282,"line":6207},[5280,43334,43191],{"class":5297},[5280,43336,5887],{"class":5452},[5280,43338,6007],{"class":5424},[5280,43340,5218],{"class":5452},[5280,43342,6074],{"class":5424},[5280,43344,6796],{"class":5452},[5280,43346,43347,43349,43351,43353,43355,43357,43359,43361],{"class":5282,"line":6221},[5280,43348,43206],{"class":5297},[5280,43350,5887],{"class":5452},[5280,43352,6007],{"class":5424},[5280,43354,5218],{"class":5452},[5280,43356,6083],{"class":5424},[5280,43358,5218],{"class":5452},[5280,43360,6088],{"class":5424},[5280,43362,6796],{"class":5452},[5280,43364,43365,43367,43369,43371,43373,43375],{"class":5282,"line":6233},[5280,43366,17798],{"class":5297},[5280,43368,5887],{"class":5452},[5280,43370,9889],{"class":5424},[5280,43372,5218],{"class":5452},[5280,43374,9475],{"class":5424},[5280,43376,6796],{"class":5452},[5280,43378,43379,43382,43384,43387,43389,43392],{"class":5282,"line":6245},[5280,43380,43381],{"class":5297},"            \"duration_ms\"",[5280,43383,5887],{"class":5452},[5280,43385,43386],{"class":5424},"duration",[5280,43388,5218],{"class":5452},[5280,43390,43391],{"class":5293},"Milliseconds",[5280,43393,17825],{"class":5452},[5280,43395,43396,43398,43400,43402,43404,43406],{"class":5282,"line":6251},[5280,43397,43225],{"class":5297},[5280,43399,5887],{"class":5452},[5280,43401,6007],{"class":5424},[5280,43403,5218],{"class":5452},[5280,43405,9160],{"class":5424},[5280,43407,6796],{"class":5452},[5280,43409,43410],{"class":5282,"line":6256},[5280,43411,9167],{"class":5452},[5280,43413,43414],{"class":5282,"line":6308},[5280,43415,6190],{"class":5452},[5280,43417,43418],{"class":5282,"line":6339},[5280,43419,43420],{"class":5286},"        \u002F\u002F Різний рівень залежно від статусу\n",[5280,43422,43423,43425],{"class":5282,"line":6371},[5280,43424,32890],{"class":5436},[5280,43426,6111],{"class":5452},[5280,43428,43429,43431,43434,43436,43438,43440,43443],{"class":5282,"line":6409},[5280,43430,32899],{"class":5436},[5280,43432,43433],{"class":5424}," wrapped",[5280,43435,5218],{"class":5452},[5280,43437,9475],{"class":5424},[5280,43439,11410],{"class":5452},[5280,43441,43442],{"class":5439},"500",[5280,43444,6127],{"class":5452},[5280,43446,43447,43450,43452,43454,43456,43459],{"class":5282,"line":6415},[5280,43448,43449],{"class":5424},"            logger",[5280,43451,5218],{"class":5452},[5280,43453,6316],{"class":5293},[5280,43455,5835],{"class":5452},[5280,43457,43458],{"class":5297},"\"request completed with server error\"",[5280,43460,5598],{"class":5452},[5280,43462,43463,43465,43467,43469,43471,43473,43476],{"class":5282,"line":6420},[5280,43464,32899],{"class":5436},[5280,43466,43433],{"class":5424},[5280,43468,5218],{"class":5452},[5280,43470,9475],{"class":5424},[5280,43472,11410],{"class":5452},[5280,43474,43475],{"class":5439},"400",[5280,43477,6127],{"class":5452},[5280,43479,43480,43482,43484,43487,43489,43492],{"class":5282,"line":6425},[5280,43481,43449],{"class":5424},[5280,43483,5218],{"class":5452},[5280,43485,43486],{"class":5293},"Warn",[5280,43488,5835],{"class":5452},[5280,43490,43491],{"class":5297},"\"request completed with client error\"",[5280,43493,5598],{"class":5452},[5280,43495,43496,43498],{"class":5282,"line":6450},[5280,43497,32985],{"class":5436},[5280,43499,6127],{"class":5452},[5280,43501,43502,43504,43506,43508,43510,43513],{"class":5282,"line":6471},[5280,43503,43449],{"class":5424},[5280,43505,5218],{"class":5452},[5280,43507,43179],{"class":5293},[5280,43509,5835],{"class":5452},[5280,43511,43512],{"class":5297},"\"request completed successfully\"",[5280,43514,5598],{"class":5452},[5280,43516,43517],{"class":5282,"line":6507},[5280,43518,6248],{"class":5452},[5280,43520,43521],{"class":5282,"line":6512},[5280,43522,6834],{"class":5452},[5280,43524,43525],{"class":5282,"line":6520},[5280,43526,5976],{"class":5452},[5266,43528,43530],{"id":43529},"request-id-для-трейсингу","Request ID для трейсингу",[5262,43532,43533],{},"Додамо унікальний ID до кожного запиту для відстеження через всі шари:",[5271,43535,43537],{"className":5411,"code":43536,"language":5325,"meta":5276,"style":5276},"\u002F\u002F middleware\u002Frequest_id.go\npackage middleware\n\nimport (\n    \"context\"\n    \"net\u002Fhttp\"\n    \n    \"github.com\u002Fgoogle\u002Fuuid\"\n)\n\n\u002F\u002F Встановлення: go get github.com\u002Fgoogle\u002Fuuid\n\ntype contextKey string\n\nconst RequestIDKey contextKey = \"request_id\"\n\n\u002F\u002F RequestID додає унікальний ID до кожного запиту\nfunc RequestID(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        \u002F\u002F Перевіряємо, чи є вже ID у заголовку\n        requestID := r.Header.Get(\"X-Request-ID\")\n        if requestID == \"\" {\n            \u002F\u002F Генеруємо новий UUID\n            requestID = uuid.New().String()\n        }\n        \n        \u002F\u002F Додаємо у контекст\n        ctx := context.WithValue(r.Context(), RequestIDKey, requestID)\n        \n        \u002F\u002F Додаємо у відповідь для клієнта\n        w.Header().Set(\"X-Request-ID\", requestID)\n        \n        \u002F\u002F Передаємо далі\n        next.ServeHTTP(w, r.WithContext(ctx))\n    })\n}\n\n\u002F\u002F GetRequestID витягує ID з контексту\nfunc GetRequestID(ctx context.Context) string {\n    if id, ok := ctx.Value(RequestIDKey).(string); ok {\n        return id\n    }\n    return \"unknown\"\n}\n",[5215,43538,43539,43544,43550,43554,43560,43564,43568,43572,43577,43581,43585,43590,43594,43602,43606,43620,43624,43629,43656,43694,43699,43723,43736,43741,43760,43764,43768,43773,43805,43809,43814,43836,43840,43845,43871,43875,43879,43883,43888,43911,43943,43950,43954,43961],{"__ignoreMap":5276},[5280,43540,43541],{"class":5282,"line":5283},[5280,43542,43543],{"class":5286},"\u002F\u002F middleware\u002Frequest_id.go\n",[5280,43545,43546,43548],{"class":5282,"line":5290},[5280,43547,5755],{"class":5619},[5280,43549,5641],{"class":5758},[5280,43551,43552],{"class":5282,"line":5301},[5280,43553,5313],{"emptyLinePlaceholder":5312},[5280,43555,43556,43558],{"class":5282,"line":5309},[5280,43557,5768],{"class":5436},[5280,43559,5453],{"class":5452},[5280,43561,43562],{"class":5282,"line":5316},[5280,43563,14862],{"class":5297},[5280,43565,43566],{"class":5282,"line":5322},[5280,43567,5790],{"class":5297},[5280,43569,43570],{"class":5282,"line":5336},[5280,43571,6724],{"class":5452},[5280,43573,43574],{"class":5282,"line":5341},[5280,43575,43576],{"class":5297},"    \"github.com\u002Fgoogle\u002Fuuid\"\n",[5280,43578,43579],{"class":5282,"line":5347},[5280,43580,5598],{"class":5452},[5280,43582,43583],{"class":5282,"line":5358},[5280,43584,5313],{"emptyLinePlaceholder":5312},[5280,43586,43587],{"class":5282,"line":5368},[5280,43588,43589],{"class":5286},"\u002F\u002F Встановлення: go get github.com\u002Fgoogle\u002Fuuid\n",[5280,43591,43592],{"class":5282,"line":5378},[5280,43593,5313],{"emptyLinePlaceholder":5312},[5280,43595,43596,43598,43600],{"class":5282,"line":5391},[5280,43597,7921],{"class":5619},[5280,43599,14890],{"class":5758},[5280,43601,14893],{"class":5758},[5280,43603,43604],{"class":5282,"line":5705},[5280,43605,5313],{"emptyLinePlaceholder":5312},[5280,43607,43608,43610,43613,43615,43617],{"class":5282,"line":5820},[5280,43609,14902],{"class":5619},[5280,43611,43612],{"class":13369}," RequestIDKey",[5280,43614,14890],{"class":5758},[5280,43616,5829],{"class":5452},[5280,43618,43619],{"class":5297},"\"request_id\"\n",[5280,43621,43622],{"class":5282,"line":5854},[5280,43623,5313],{"emptyLinePlaceholder":5312},[5280,43625,43626],{"class":5282,"line":5859},[5280,43627,43628],{"class":5286},"\u002F\u002F RequestID додає унікальний ID до кожного запиту\n",[5280,43630,43631,43633,43636,43638,43640,43642,43644,43646,43648,43650,43652,43654],{"class":5282,"line":5871},[5280,43632,5862],{"class":5619},[5280,43634,43635],{"class":5293}," RequestID",[5280,43637,5835],{"class":5452},[5280,43639,7305],{"class":5424},[5280,43641,5997],{"class":5758},[5280,43643,5218],{"class":5452},[5280,43645,7935],{"class":5758},[5280,43647,7315],{"class":5452},[5280,43649,5952],{"class":5758},[5280,43651,5218],{"class":5452},[5280,43653,7935],{"class":5758},[5280,43655,6111],{"class":5452},[5280,43657,43658,43660,43662,43664,43666,43668,43670,43672,43674,43676,43678,43680,43682,43684,43686,43688,43690,43692],{"class":5282,"line":5895},[5280,43659,7328],{"class":5436},[5280,43661,5997],{"class":5424},[5280,43663,5218],{"class":5452},[5280,43665,7312],{"class":5293},[5280,43667,5835],{"class":5452},[5280,43669,5862],{"class":5619},[5280,43671,5835],{"class":5452},[5280,43673,5994],{"class":5424},[5280,43675,5997],{"class":5758},[5280,43677,5218],{"class":5452},[5280,43679,6002],{"class":5758},[5280,43681,5887],{"class":5452},[5280,43683,6007],{"class":5424},[5280,43685,6010],{"class":5452},[5280,43687,5952],{"class":5758},[5280,43689,5218],{"class":5452},[5280,43691,6017],{"class":5758},[5280,43693,6020],{"class":5452},[5280,43695,43696],{"class":5282,"line":5916},[5280,43697,43698],{"class":5286},"        \u002F\u002F Перевіряємо, чи є вже ID у заголовку\n",[5280,43700,43701,43704,43706,43708,43710,43712,43714,43716,43718,43721],{"class":5282,"line":5921},[5280,43702,43703],{"class":5424},"        requestID",[5280,43705,6029],{"class":5452},[5280,43707,6007],{"class":5424},[5280,43709,5218],{"class":5452},[5280,43711,6138],{"class":5424},[5280,43713,5218],{"class":5452},[5280,43715,12252],{"class":5293},[5280,43717,5835],{"class":5452},[5280,43719,43720],{"class":5297},"\"X-Request-ID\"",[5280,43722,5598],{"class":5452},[5280,43724,43725,43727,43730,43732,43734],{"class":5282,"line":5939},[5280,43726,6259],{"class":5436},[5280,43728,43729],{"class":5424}," requestID",[5280,43731,7029],{"class":5452},[5280,43733,12375],{"class":5297},[5280,43735,6111],{"class":5452},[5280,43737,43738],{"class":5282,"line":5973},[5280,43739,43740],{"class":5286},"            \u002F\u002F Генеруємо новий UUID\n",[5280,43742,43743,43746,43748,43750,43752,43754,43756,43758],{"class":5282,"line":5979},[5280,43744,43745],{"class":5424},"            requestID",[5280,43747,5829],{"class":5452},[5280,43749,5509],{"class":5424},[5280,43751,5218],{"class":5452},[5280,43753,20202],{"class":5293},[5280,43755,6141],{"class":5452},[5280,43757,14257],{"class":5293},[5280,43759,6039],{"class":5452},[5280,43761,43762],{"class":5282,"line":5984},[5280,43763,6248],{"class":5452},[5280,43765,43766],{"class":5282,"line":6023},[5280,43767,6190],{"class":5452},[5280,43769,43770],{"class":5282,"line":6042},[5280,43771,43772],{"class":5286},"        \u002F\u002F Додаємо у контекст\n",[5280,43774,43775,43777,43779,43781,43783,43785,43787,43789,43791,43793,43795,43798,43800,43803],{"class":5282,"line":6093},[5280,43776,15374],{"class":5424},[5280,43778,6029],{"class":5452},[5280,43780,15379],{"class":5424},[5280,43782,5218],{"class":5452},[5280,43784,15384],{"class":5293},[5280,43786,5835],{"class":5452},[5280,43788,6007],{"class":5424},[5280,43790,5218],{"class":5452},[5280,43792,15393],{"class":5293},[5280,43794,15396],{"class":5452},[5280,43796,43797],{"class":5424},"RequestIDKey",[5280,43799,5887],{"class":5452},[5280,43801,43802],{"class":5424},"requestID",[5280,43804,5598],{"class":5452},[5280,43806,43807],{"class":5282,"line":6098},[5280,43808,6190],{"class":5452},[5280,43810,43811],{"class":5282,"line":6114},[5280,43812,43813],{"class":5286},"        \u002F\u002F Додаємо у відповідь для клієнта\n",[5280,43815,43816,43818,43820,43822,43824,43826,43828,43830,43832,43834],{"class":5282,"line":6130},[5280,43817,6133],{"class":5424},[5280,43819,5218],{"class":5452},[5280,43821,6138],{"class":5293},[5280,43823,6141],{"class":5452},[5280,43825,6144],{"class":5293},[5280,43827,5835],{"class":5452},[5280,43829,43720],{"class":5297},[5280,43831,5887],{"class":5452},[5280,43833,43802],{"class":5424},[5280,43835,5598],{"class":5452},[5280,43837,43838],{"class":5282,"line":6159},[5280,43839,6190],{"class":5452},[5280,43841,43842],{"class":5282,"line":6187},[5280,43843,43844],{"class":5286},"        \u002F\u002F Передаємо далі\n",[5280,43846,43847,43849,43851,43853,43855,43857,43859,43861,43863,43865,43867,43869],{"class":5282,"line":6193},[5280,43848,7426],{"class":5424},[5280,43850,5218],{"class":5452},[5280,43852,8033],{"class":5293},[5280,43854,5835],{"class":5452},[5280,43856,5994],{"class":5424},[5280,43858,5887],{"class":5452},[5280,43860,6007],{"class":5424},[5280,43862,5218],{"class":5452},[5280,43864,15436],{"class":5293},[5280,43866,5835],{"class":5452},[5280,43868,15441],{"class":5424},[5280,43870,5970],{"class":5452},[5280,43872,43873],{"class":5282,"line":6207},[5280,43874,6834],{"class":5452},[5280,43876,43877],{"class":5282,"line":6221},[5280,43878,5976],{"class":5452},[5280,43880,43881],{"class":5282,"line":6233},[5280,43882,5313],{"emptyLinePlaceholder":5312},[5280,43884,43885],{"class":5282,"line":6245},[5280,43886,43887],{"class":5286},"\u002F\u002F GetRequestID витягує ID з контексту\n",[5280,43889,43890,43892,43895,43897,43899,43901,43903,43905,43907,43909],{"class":5282,"line":6251},[5280,43891,5862],{"class":5619},[5280,43893,43894],{"class":5293}," GetRequestID",[5280,43896,5835],{"class":5452},[5280,43898,15441],{"class":5424},[5280,43900,21723],{"class":5758},[5280,43902,5218],{"class":5452},[5280,43904,15393],{"class":5758},[5280,43906,7315],{"class":5452},[5280,43908,5844],{"class":5758},[5280,43910,6111],{"class":5452},[5280,43912,43913,43915,43917,43919,43921,43923,43925,43927,43929,43931,43933,43935,43937,43939,43941],{"class":5282,"line":6256},[5280,43914,7023],{"class":5436},[5280,43916,13315],{"class":5424},[5280,43918,5887],{"class":5452},[5280,43920,15805],{"class":5424},[5280,43922,6029],{"class":5452},[5280,43924,15441],{"class":5424},[5280,43926,5218],{"class":5452},[5280,43928,15818],{"class":5293},[5280,43930,5835],{"class":5452},[5280,43932,43797],{"class":5424},[5280,43934,15829],{"class":5452},[5280,43936,5844],{"class":5758},[5280,43938,6295],{"class":5452},[5280,43940,15805],{"class":5424},[5280,43942,6111],{"class":5452},[5280,43944,43945,43947],{"class":5282,"line":6308},[5280,43946,12200],{"class":5436},[5280,43948,43949],{"class":5424}," id\n",[5280,43951,43952],{"class":5282,"line":6339},[5280,43953,6553],{"class":5452},[5280,43955,43956,43958],{"class":5282,"line":6371},[5280,43957,7328],{"class":5436},[5280,43959,43960],{"class":5297}," \"unknown\"\n",[5280,43962,43963],{"class":5282,"line":6409},[5280,43964,5976],{"class":5452},[5262,43966,43967],{},"Використання у логуванні:",[5271,43969,43971],{"className":5411,"code":43970,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fusecase\u002Fuser_usecase.go\nfunc (uc *UserUseCase) RegisterUser(ctx context.Context, name, email, password string) (*domain.User, error) {\n    requestID := middleware.GetRequestID(ctx)\n    \n    slog.Info(\"registering user\",\n        \"request_id\", requestID,\n        \"email\", email,\n    )\n    \n    \u002F\u002F ... логіка реєстрації\n    \n    if err != nil {\n        slog.Error(\"failed to register user\",\n            \"request_id\", requestID,\n            \"email\", email,\n            \"error\", err,\n        )\n        return nil, err\n    }\n    \n    slog.Info(\"user registered successfully\",\n        \"request_id\", requestID,\n        \"user_id\", user.ID,\n    )\n    \n    return user, nil\n}\n",[5215,43972,43973,43977,44031,44051,44055,44071,44082,44093,44097,44101,44106,44110,44122,44137,44148,44159,44170,44174,44184,44188,44192,44207,44217,44231,44235,44239,44249],{"__ignoreMap":5276},[5280,43974,43975],{"class":5282,"line":5283},[5280,43976,21952],{"class":5286},[5280,43978,43979,43981,43983,43985,43987,43989,43991,43993,43995,43997,43999,44001,44003,44005,44007,44009,44011,44013,44015,44017,44019,44021,44023,44025,44027,44029],{"class":5282,"line":5290},[5280,43980,5862],{"class":5619},[5280,43982,5720],{"class":5452},[5280,43984,22107],{"class":5424},[5280,43986,8072],{"class":5452},[5280,43988,22065],{"class":5758},[5280,43990,7315],{"class":5452},[5280,43992,20688],{"class":5293},[5280,43994,5835],{"class":5452},[5280,43996,15441],{"class":5424},[5280,43998,21723],{"class":5758},[5280,44000,5218],{"class":5452},[5280,44002,15393],{"class":5758},[5280,44004,5887],{"class":5452},[5280,44006,20121],{"class":5424},[5280,44008,5887],{"class":5452},[5280,44010,20126],{"class":5424},[5280,44012,5887],{"class":5452},[5280,44014,20131],{"class":5424},[5280,44016,6239],{"class":5758},[5280,44018,20151],{"class":5452},[5280,44020,22144],{"class":5758},[5280,44022,5218],{"class":5452},[5280,44024,17513],{"class":5758},[5280,44026,5887],{"class":5452},[5280,44028,9567],{"class":5758},[5280,44030,6020],{"class":5452},[5280,44032,44033,44036,44038,44040,44042,44045,44047,44049],{"class":5282,"line":5301},[5280,44034,44035],{"class":5424},"    requestID",[5280,44037,6029],{"class":5452},[5280,44039,8768],{"class":5424},[5280,44041,5218],{"class":5452},[5280,44043,44044],{"class":5293},"GetRequestID",[5280,44046,5835],{"class":5452},[5280,44048,15441],{"class":5424},[5280,44050,5598],{"class":5452},[5280,44052,44053],{"class":5282,"line":5309},[5280,44054,6724],{"class":5452},[5280,44056,44057,44060,44062,44064,44066,44069],{"class":5282,"line":5316},[5280,44058,44059],{"class":5424},"    slog",[5280,44061,5218],{"class":5452},[5280,44063,43179],{"class":5293},[5280,44065,5835],{"class":5452},[5280,44067,44068],{"class":5297},"\"registering user\"",[5280,44070,6796],{"class":5452},[5280,44072,44073,44076,44078,44080],{"class":5282,"line":5322},[5280,44074,44075],{"class":5297},"        \"request_id\"",[5280,44077,5887],{"class":5452},[5280,44079,43802],{"class":5424},[5280,44081,6796],{"class":5452},[5280,44083,44084,44087,44089,44091],{"class":5282,"line":5336},[5280,44085,44086],{"class":5297},"        \"email\"",[5280,44088,5887],{"class":5452},[5280,44090,20126],{"class":5424},[5280,44092,6796],{"class":5452},[5280,44094,44095],{"class":5282,"line":5341},[5280,44096,13112],{"class":5452},[5280,44098,44099],{"class":5282,"line":5347},[5280,44100,6724],{"class":5452},[5280,44102,44103],{"class":5282,"line":5358},[5280,44104,44105],{"class":5286},"    \u002F\u002F ... логіка реєстрації\n",[5280,44107,44108],{"class":5282,"line":5368},[5280,44109,6724],{"class":5452},[5280,44111,44112,44114,44116,44118,44120],{"class":5282,"line":5378},[5280,44113,7023],{"class":5436},[5280,44115,6262],{"class":5424},[5280,44117,6301],{"class":5452},[5280,44119,5967],{"class":5619},[5280,44121,6111],{"class":5452},[5280,44123,44124,44126,44128,44130,44132,44135],{"class":5282,"line":5391},[5280,44125,43174],{"class":5424},[5280,44127,5218],{"class":5452},[5280,44129,6316],{"class":5293},[5280,44131,5835],{"class":5452},[5280,44133,44134],{"class":5297},"\"failed to register user\"",[5280,44136,6796],{"class":5452},[5280,44138,44139,44142,44144,44146],{"class":5282,"line":5705},[5280,44140,44141],{"class":5297},"            \"request_id\"",[5280,44143,5887],{"class":5452},[5280,44145,43802],{"class":5424},[5280,44147,6796],{"class":5452},[5280,44149,44150,44153,44155,44157],{"class":5282,"line":5820},[5280,44151,44152],{"class":5297},"            \"email\"",[5280,44154,5887],{"class":5452},[5280,44156,20126],{"class":5424},[5280,44158,6796],{"class":5452},[5280,44160,44161,44164,44166,44168],{"class":5282,"line":5854},[5280,44162,44163],{"class":5297},"            \"error\"",[5280,44165,5887],{"class":5452},[5280,44167,6298],{"class":5424},[5280,44169,6796],{"class":5452},[5280,44171,44172],{"class":5282,"line":5859},[5280,44173,9167],{"class":5452},[5280,44175,44176,44178,44180,44182],{"class":5282,"line":5871},[5280,44177,12200],{"class":5436},[5280,44179,20192],{"class":5619},[5280,44181,5887],{"class":5452},[5280,44183,22296],{"class":5424},[5280,44185,44186],{"class":5282,"line":5895},[5280,44187,6553],{"class":5452},[5280,44189,44190],{"class":5282,"line":5916},[5280,44191,6724],{"class":5452},[5280,44193,44194,44196,44198,44200,44202,44205],{"class":5282,"line":5921},[5280,44195,44059],{"class":5424},[5280,44197,5218],{"class":5452},[5280,44199,43179],{"class":5293},[5280,44201,5835],{"class":5452},[5280,44203,44204],{"class":5297},"\"user registered successfully\"",[5280,44206,6796],{"class":5452},[5280,44208,44209,44211,44213,44215],{"class":5282,"line":5939},[5280,44210,44075],{"class":5297},[5280,44212,5887],{"class":5452},[5280,44214,43802],{"class":5424},[5280,44216,6796],{"class":5452},[5280,44218,44219,44221,44223,44225,44227,44229],{"class":5282,"line":5973},[5280,44220,15951],{"class":5297},[5280,44222,5887],{"class":5452},[5280,44224,6292],{"class":5424},[5280,44226,5218],{"class":5452},[5280,44228,18635],{"class":5424},[5280,44230,6796],{"class":5452},[5280,44232,44233],{"class":5282,"line":5979},[5280,44234,13112],{"class":5452},[5280,44236,44237],{"class":5282,"line":5984},[5280,44238,6724],{"class":5452},[5280,44240,44241,44243,44245,44247],{"class":5282,"line":6023},[5280,44242,7328],{"class":5436},[5280,44244,6213],{"class":5424},[5280,44246,5887],{"class":5452},[5280,44248,20459],{"class":5619},[5280,44250,44251],{"class":5282,"line":6042},[5280,44252,5976],{"class":5452},[5266,44254,44256],{"id":44255},"debugging-через-pprof","Debugging через pprof",[5262,44258,44259],{},"Додамо ендпоінти для профілювання:",[5271,44261,44263],{"className":5411,"code":44262,"language":5325,"meta":5276,"style":5276},"\u002F\u002F cmd\u002Fserver\u002Fmain.go\nimport (\n    \"net\u002Fhttp\"\n    \"net\u002Fhttp\u002Fpprof\"\n    \n    \"github.com\u002Fgo-chi\u002Fchi\u002Fv5\"\n)\n\nfunc setupDebugRoutes(r *chi.Mux) {\n    \u002F\u002F Увага: не додавайте ці маршрути у продакшн без автентифікації!\n    r.Mount(\"\u002Fdebug\u002Fpprof\", http.HandlerFunc(pprof.Index))\n    r.Handle(\"\u002Fdebug\u002Fpprof\u002Fcmdline\", http.HandlerFunc(pprof.Cmdline))\n    r.Handle(\"\u002Fdebug\u002Fpprof\u002Fprofile\", http.HandlerFunc(pprof.Profile))\n    r.Handle(\"\u002Fdebug\u002Fpprof\u002Fsymbol\", http.HandlerFunc(pprof.Symbol))\n    r.Handle(\"\u002Fdebug\u002Fpprof\u002Ftrace\", http.HandlerFunc(pprof.Trace))\n}\n",[5215,44264,44265,44269,44275,44279,44284,44288,44292,44296,44300,44322,44327,44361,44393,44425,44457,44489],{"__ignoreMap":5276},[5280,44266,44267],{"class":5282,"line":5283},[5280,44268,26331],{"class":5286},[5280,44270,44271,44273],{"class":5282,"line":5290},[5280,44272,5768],{"class":5436},[5280,44274,5453],{"class":5452},[5280,44276,44277],{"class":5282,"line":5301},[5280,44278,5790],{"class":5297},[5280,44280,44281],{"class":5282,"line":5309},[5280,44282,44283],{"class":5297},"    \"net\u002Fhttp\u002Fpprof\"\n",[5280,44285,44286],{"class":5282,"line":5316},[5280,44287,6724],{"class":5452},[5280,44289,44290],{"class":5282,"line":5322},[5280,44291,16422],{"class":5297},[5280,44293,44294],{"class":5282,"line":5336},[5280,44295,5598],{"class":5452},[5280,44297,44298],{"class":5282,"line":5341},[5280,44299,5313],{"emptyLinePlaceholder":5312},[5280,44301,44302,44304,44307,44309,44311,44313,44315,44317,44320],{"class":5282,"line":5347},[5280,44303,5862],{"class":5619},[5280,44305,44306],{"class":5293}," setupDebugRoutes",[5280,44308,5835],{"class":5452},[5280,44310,6007],{"class":5424},[5280,44312,6010],{"class":5452},[5280,44314,5474],{"class":5758},[5280,44316,5218],{"class":5452},[5280,44318,44319],{"class":5758},"Mux",[5280,44321,6020],{"class":5452},[5280,44323,44324],{"class":5282,"line":5358},[5280,44325,44326],{"class":5286},"    \u002F\u002F Увага: не додавайте ці маршрути у продакшн без автентифікації!\n",[5280,44328,44329,44331,44333,44336,44338,44341,44343,44345,44347,44349,44351,44354,44356,44359],{"class":5282,"line":5368},[5280,44330,16443],{"class":5424},[5280,44332,5218],{"class":5452},[5280,44334,44335],{"class":5293},"Mount",[5280,44337,5835],{"class":5452},[5280,44339,44340],{"class":5297},"\"\u002Fdebug\u002Fpprof\"",[5280,44342,5887],{"class":5452},[5280,44344,5952],{"class":5424},[5280,44346,5218],{"class":5452},[5280,44348,7312],{"class":5293},[5280,44350,5835],{"class":5452},[5280,44352,44353],{"class":5424},"pprof",[5280,44355,5218],{"class":5452},[5280,44357,44358],{"class":5424},"Index",[5280,44360,5970],{"class":5452},[5280,44362,44363,44365,44367,44369,44371,44374,44376,44378,44380,44382,44384,44386,44388,44391],{"class":5282,"line":5378},[5280,44364,16443],{"class":5424},[5280,44366,5218],{"class":5452},[5280,44368,8145],{"class":5293},[5280,44370,5835],{"class":5452},[5280,44372,44373],{"class":5297},"\"\u002Fdebug\u002Fpprof\u002Fcmdline\"",[5280,44375,5887],{"class":5452},[5280,44377,5952],{"class":5424},[5280,44379,5218],{"class":5452},[5280,44381,7312],{"class":5293},[5280,44383,5835],{"class":5452},[5280,44385,44353],{"class":5424},[5280,44387,5218],{"class":5452},[5280,44389,44390],{"class":5424},"Cmdline",[5280,44392,5970],{"class":5452},[5280,44394,44395,44397,44399,44401,44403,44406,44408,44410,44412,44414,44416,44418,44420,44423],{"class":5282,"line":5391},[5280,44396,16443],{"class":5424},[5280,44398,5218],{"class":5452},[5280,44400,8145],{"class":5293},[5280,44402,5835],{"class":5452},[5280,44404,44405],{"class":5297},"\"\u002Fdebug\u002Fpprof\u002Fprofile\"",[5280,44407,5887],{"class":5452},[5280,44409,5952],{"class":5424},[5280,44411,5218],{"class":5452},[5280,44413,7312],{"class":5293},[5280,44415,5835],{"class":5452},[5280,44417,44353],{"class":5424},[5280,44419,5218],{"class":5452},[5280,44421,44422],{"class":5424},"Profile",[5280,44424,5970],{"class":5452},[5280,44426,44427,44429,44431,44433,44435,44438,44440,44442,44444,44446,44448,44450,44452,44455],{"class":5282,"line":5705},[5280,44428,16443],{"class":5424},[5280,44430,5218],{"class":5452},[5280,44432,8145],{"class":5293},[5280,44434,5835],{"class":5452},[5280,44436,44437],{"class":5297},"\"\u002Fdebug\u002Fpprof\u002Fsymbol\"",[5280,44439,5887],{"class":5452},[5280,44441,5952],{"class":5424},[5280,44443,5218],{"class":5452},[5280,44445,7312],{"class":5293},[5280,44447,5835],{"class":5452},[5280,44449,44353],{"class":5424},[5280,44451,5218],{"class":5452},[5280,44453,44454],{"class":5424},"Symbol",[5280,44456,5970],{"class":5452},[5280,44458,44459,44461,44463,44465,44467,44470,44472,44474,44476,44478,44480,44482,44484,44487],{"class":5282,"line":5820},[5280,44460,16443],{"class":5424},[5280,44462,5218],{"class":5452},[5280,44464,8145],{"class":5293},[5280,44466,5835],{"class":5452},[5280,44468,44469],{"class":5297},"\"\u002Fdebug\u002Fpprof\u002Ftrace\"",[5280,44471,5887],{"class":5452},[5280,44473,5952],{"class":5424},[5280,44475,5218],{"class":5452},[5280,44477,7312],{"class":5293},[5280,44479,5835],{"class":5452},[5280,44481,44353],{"class":5424},[5280,44483,5218],{"class":5452},[5280,44485,44486],{"class":5424},"Trace",[5280,44488,5970],{"class":5452},[5280,44490,44491],{"class":5282,"line":5854},[5280,44492,5976],{"class":5452},[5262,44494,44495],{},[5227,44496,44497],{},"Використання pprof:",[34171,44499,44500,44574,44618],{},[34174,44501,44503],{"label":44502},"CPU profiling",[5271,44504,44506],{"className":5273,"code":44505,"language":5275,"meta":5276,"style":5276},"# Збираємо CPU профіль протягом 30 секунд\ngo tool pprof http:\u002F\u002Flocalhost:8080\u002Fdebug\u002Fpprof\u002Fprofile?seconds=30\n\n# Аналізуємо у інтерактивному режимі\n(pprof) top10\n(pprof) list functionName\n(pprof) web\n",[5215,44507,44508,44513,44529,44533,44538,44549,44563],{"__ignoreMap":5276},[5280,44509,44510],{"class":5282,"line":5283},[5280,44511,44512],{"class":5286},"# Збираємо CPU профіль протягом 30 секунд\n",[5280,44514,44515,44517,44520,44523,44526],{"class":5282,"line":5290},[5280,44516,5325],{"class":5293},[5280,44518,44519],{"class":5297}," tool",[5280,44521,44522],{"class":5297}," pprof",[5280,44524,44525],{"class":5297}," http:\u002F\u002Flocalhost:8080\u002Fdebug\u002Fpprof\u002Fprofile?seconds=",[5280,44527,44528],{"class":5439},"30\n",[5280,44530,44531],{"class":5282,"line":5301},[5280,44532,5313],{"emptyLinePlaceholder":5312},[5280,44534,44535],{"class":5282,"line":5309},[5280,44536,44537],{"class":5286},"# Аналізуємо у інтерактивному режимі\n",[5280,44539,44540,44542,44544,44546],{"class":5282,"line":5316},[5280,44541,5835],{"class":5452},[5280,44543,44353],{"class":5293},[5280,44545,7315],{"class":5452},[5280,44547,44548],{"class":5293},"top10\n",[5280,44550,44551,44553,44555,44557,44560],{"class":5282,"line":5322},[5280,44552,5835],{"class":5452},[5280,44554,44353],{"class":5293},[5280,44556,7315],{"class":5452},[5280,44558,44559],{"class":5293},"list",[5280,44561,44562],{"class":5297}," functionName\n",[5280,44564,44565,44567,44569,44571],{"class":5282,"line":5336},[5280,44566,5835],{"class":5452},[5280,44568,44353],{"class":5293},[5280,44570,7315],{"class":5452},[5280,44572,44573],{"class":5293},"web\n",[34174,44575,44577],{"label":44576},"Memory profiling",[5271,44578,44580],{"className":5273,"code":44579,"language":5275,"meta":5276,"style":5276},"# Heap профіль\ngo tool pprof http:\u002F\u002Flocalhost:8080\u002Fdebug\u002Fpprof\u002Fheap\n\n# Allocs профіль\ngo tool pprof http:\u002F\u002Flocalhost:8080\u002Fdebug\u002Fpprof\u002Fallocs\n",[5215,44581,44582,44587,44598,44602,44607],{"__ignoreMap":5276},[5280,44583,44584],{"class":5282,"line":5283},[5280,44585,44586],{"class":5286},"# Heap профіль\n",[5280,44588,44589,44591,44593,44595],{"class":5282,"line":5290},[5280,44590,5325],{"class":5293},[5280,44592,44519],{"class":5297},[5280,44594,44522],{"class":5297},[5280,44596,44597],{"class":5297}," http:\u002F\u002Flocalhost:8080\u002Fdebug\u002Fpprof\u002Fheap\n",[5280,44599,44600],{"class":5282,"line":5301},[5280,44601,5313],{"emptyLinePlaceholder":5312},[5280,44603,44604],{"class":5282,"line":5309},[5280,44605,44606],{"class":5286},"# Allocs профіль\n",[5280,44608,44609,44611,44613,44615],{"class":5282,"line":5316},[5280,44610,5325],{"class":5293},[5280,44612,44519],{"class":5297},[5280,44614,44522],{"class":5297},[5280,44616,44617],{"class":5297}," http:\u002F\u002Flocalhost:8080\u002Fdebug\u002Fpprof\u002Fallocs\n",[34174,44619,44621],{"label":44620},"Goroutine leak detection",[5271,44622,44624],{"className":5273,"code":44623,"language":5275,"meta":5276,"style":5276},"# Перевірка горутин\ncurl http:\u002F\u002Flocalhost:8080\u002Fdebug\u002Fpprof\u002Fgoroutine?debug=1\n\n# Або через pprof\ngo tool pprof http:\u002F\u002Flocalhost:8080\u002Fdebug\u002Fpprof\u002Fgoroutine\n",[5215,44625,44626,44631,44641,44645,44650],{"__ignoreMap":5276},[5280,44627,44628],{"class":5282,"line":5283},[5280,44629,44630],{"class":5286},"# Перевірка горутин\n",[5280,44632,44633,44635,44638],{"class":5282,"line":5290},[5280,44634,34955],{"class":5293},[5280,44636,44637],{"class":5297}," http:\u002F\u002Flocalhost:8080\u002Fdebug\u002Fpprof\u002Fgoroutine?debug=",[5280,44639,44640],{"class":5439},"1\n",[5280,44642,44643],{"class":5282,"line":5301},[5280,44644,5313],{"emptyLinePlaceholder":5312},[5280,44646,44647],{"class":5282,"line":5309},[5280,44648,44649],{"class":5286},"# Або через pprof\n",[5280,44651,44652,44654,44656,44658],{"class":5282,"line":5316},[5280,44653,5325],{"class":5293},[5280,44655,44519],{"class":5297},[5280,44657,44522],{"class":5297},[5280,44659,44660],{"class":5297}," http:\u002F\u002Flocalhost:8080\u002Fdebug\u002Fpprof\u002Fgoroutine\n",[5266,44662,44664],{"id":44663},"health-check-ендпоінт","Health Check ендпоінт",[5271,44666,44668],{"className":5411,"code":44667,"language":5325,"meta":5276,"style":5276},"\u002F\u002F internal\u002Fdelivery\u002Fhttp\u002Fhealth_handler.go\npackage http\n\nimport (\n    \"context\"\n    \"database\u002Fsql\"\n    \"encoding\u002Fjson\"\n    \"net\u002Fhttp\"\n    \"time\"\n)\n\ntype HealthHandler struct {\n    db *sql.DB\n}\n\ntype HealthResponse struct {\n    Status    string            `json:\"status\"`\n    Timestamp int64             `json:\"timestamp\"`\n    Services  map[string]string `json:\"services\"`\n}\n\nfunc (h *HealthHandler) Check(w http.ResponseWriter, r *http.Request) {\n    response := HealthResponse{\n        Status:    \"ok\",\n        Timestamp: time.Now().Unix(),\n        Services:  make(map[string]string),\n    }\n    \n    \u002F\u002F Перевірка підключення до БД\n    if h.db != nil {\n        ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)\n        defer cancel()\n        \n        if err := h.db.PingContext(ctx); err != nil {\n            response.Status = \"degraded\"\n            response.Services[\"database\"] = \"unhealthy: \" + err.Error()\n        } else {\n            response.Services[\"database\"] = \"healthy\"\n        }\n    }\n    \n    \u002F\u002F Можна додати перевірки інших сервісів\n    \u002F\u002F Redis, RabbitMQ, external APIs...\n    \n    statusCode := http.StatusOK\n    if response.Status != \"ok\" {\n        statusCode = http.StatusServiceUnavailable\n    }\n    \n    w.Header().Set(\"Content-Type\", \"application\u002Fjson\")\n    w.WriteHeader(statusCode)\n    json.NewEncoder(w).Encode(response)\n}\n\n\u002F\u002F Liveness probe (для Kubernetes)\nfunc (h *HealthHandler) Liveness(w http.ResponseWriter, r *http.Request) {\n    w.WriteHeader(http.StatusOK)\n    w.Write([]byte(\"alive\"))\n}\n\n\u002F\u002F Readiness probe (для Kubernetes)\nfunc (h *HealthHandler) Readiness(w http.ResponseWriter, r *http.Request) {\n    \u002F\u002F Перевіряємо критичні залежності\n    if h.db != nil {\n        ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)\n        defer cancel()\n        \n        if err := h.db.PingContext(ctx); err != nil {\n            w.WriteHeader(http.StatusServiceUnavailable)\n            w.Write([]byte(\"not ready\"))\n            return\n        }\n    }\n    \n    w.WriteHeader(http.StatusOK)\n    w.Write([]byte(\"ready\"))\n}\n",[5215,44669,44670,44675,44681,44685,44691,44695,44699,44703,44707,44711,44715,44719,44730,44742,44746,44750,44761,44770,44781,44800,44804,44808,44850,44861,44872,44891,44914,44918,44922,44927,44944,44982,44990,44994,45027,45041,45070,45079,45096,45100,45104,45108,45113,45118,45122,45134,45150,45164,45168,45172,45194,45208,45230,45234,45238,45243,45284,45302,45321,45325,45329,45334,45375,45380,45396,45434,45442,45446,45478,45497,45516,45520,45524,45528,45532,45550,45569],{"__ignoreMap":5276},[5280,44671,44672],{"class":5282,"line":5283},[5280,44673,44674],{"class":5286},"\u002F\u002F internal\u002Fdelivery\u002Fhttp\u002Fhealth_handler.go\n",[5280,44676,44677,44679],{"class":5282,"line":5290},[5280,44678,5755],{"class":5619},[5280,44680,24962],{"class":5758},[5280,44682,44683],{"class":5282,"line":5301},[5280,44684,5313],{"emptyLinePlaceholder":5312},[5280,44686,44687,44689],{"class":5282,"line":5309},[5280,44688,5768],{"class":5436},[5280,44690,5453],{"class":5452},[5280,44692,44693],{"class":5282,"line":5316},[5280,44694,14862],{"class":5297},[5280,44696,44697],{"class":5282,"line":5322},[5280,44698,23747],{"class":5297},[5280,44700,44701],{"class":5282,"line":5336},[5280,44702,5775],{"class":5297},[5280,44704,44705],{"class":5282,"line":5341},[5280,44706,5790],{"class":5297},[5280,44708,44709],{"class":5282,"line":5347},[5280,44710,5795],{"class":5297},[5280,44712,44713],{"class":5282,"line":5358},[5280,44714,5598],{"class":5452},[5280,44716,44717],{"class":5282,"line":5368},[5280,44718,5313],{"emptyLinePlaceholder":5312},[5280,44720,44721,44723,44726,44728],{"class":5282,"line":5378},[5280,44722,7921],{"class":5619},[5280,44724,44725],{"class":5758}," HealthHandler",[5280,44727,6216],{"class":5619},[5280,44729,6111],{"class":5452},[5280,44731,44732,44734,44736,44738,44740],{"class":5282,"line":5391},[5280,44733,23779],{"class":5424},[5280,44735,6010],{"class":5452},[5280,44737,23784],{"class":5758},[5280,44739,5218],{"class":5452},[5280,44741,23789],{"class":5758},[5280,44743,44744],{"class":5282,"line":5705},[5280,44745,5976],{"class":5452},[5280,44747,44748],{"class":5282,"line":5820},[5280,44749,5313],{"emptyLinePlaceholder":5312},[5280,44751,44752,44754,44757,44759],{"class":5282,"line":5854},[5280,44753,7921],{"class":5619},[5280,44755,44756],{"class":5758}," HealthResponse",[5280,44758,6216],{"class":5619},[5280,44760,6111],{"class":5452},[5280,44762,44763,44765,44767],{"class":5282,"line":5859},[5280,44764,37750],{"class":5424},[5280,44766,18407],{"class":5758},[5280,44768,44769],{"class":5297},"            `json:\"status\"`\n",[5280,44771,44772,44775,44778],{"class":5282,"line":5871},[5280,44773,44774],{"class":5424},"    Timestamp",[5280,44776,44777],{"class":5758}," int64",[5280,44779,44780],{"class":5297},"             `json:\"timestamp\"`\n",[5280,44782,44783,44786,44789,44791,44793,44795,44797],{"class":5282,"line":5895},[5280,44784,44785],{"class":5424},"    Services",[5280,44787,44788],{"class":5619},"  map",[5280,44790,5841],{"class":5452},[5280,44792,5844],{"class":5758},[5280,44794,5847],{"class":5452},[5280,44796,5844],{"class":5758},[5280,44798,44799],{"class":5297}," `json:\"services\"`\n",[5280,44801,44802],{"class":5282,"line":5916},[5280,44803,5976],{"class":5452},[5280,44805,44806],{"class":5282,"line":5921},[5280,44807,5313],{"emptyLinePlaceholder":5312},[5280,44809,44810,44812,44814,44816,44818,44821,44823,44826,44828,44830,44832,44834,44836,44838,44840,44842,44844,44846,44848],{"class":5282,"line":5939},[5280,44811,5862],{"class":5619},[5280,44813,5720],{"class":5452},[5280,44815,8069],{"class":5424},[5280,44817,8072],{"class":5452},[5280,44819,44820],{"class":5758},"HealthHandler",[5280,44822,7315],{"class":5452},[5280,44824,44825],{"class":5293},"Check",[5280,44827,5835],{"class":5452},[5280,44829,5994],{"class":5424},[5280,44831,5997],{"class":5758},[5280,44833,5218],{"class":5452},[5280,44835,6002],{"class":5758},[5280,44837,5887],{"class":5452},[5280,44839,6007],{"class":5424},[5280,44841,6010],{"class":5452},[5280,44843,5952],{"class":5758},[5280,44845,5218],{"class":5452},[5280,44847,6017],{"class":5758},[5280,44849,6020],{"class":5452},[5280,44851,44852,44854,44856,44859],{"class":5282,"line":5973},[5280,44853,25515],{"class":5424},[5280,44855,6029],{"class":5452},[5280,44857,44858],{"class":5758},"HealthResponse",[5280,44860,6781],{"class":5452},[5280,44862,44863,44866,44868,44870],{"class":5282,"line":5979},[5280,44864,44865],{"class":5424},"        Status",[5280,44867,13867],{"class":5452},[5280,44869,6793],{"class":5297},[5280,44871,6796],{"class":5452},[5280,44873,44874,44877,44879,44881,44883,44885,44887,44889],{"class":5282,"line":5984},[5280,44875,44876],{"class":5424},"        Timestamp",[5280,44878,6790],{"class":5452},[5280,44880,5587],{"class":5424},[5280,44882,5218],{"class":5452},[5280,44884,6036],{"class":5293},[5280,44886,6141],{"class":5452},[5280,44888,17822],{"class":5293},[5280,44890,17825],{"class":5452},[5280,44892,44893,44896,44898,44900,44902,44904,44906,44908,44910,44912],{"class":5282,"line":6023},[5280,44894,44895],{"class":5424},"        Services",[5280,44897,19517],{"class":5452},[5280,44899,5832],{"class":5293},[5280,44901,5835],{"class":5452},[5280,44903,5838],{"class":5619},[5280,44905,5841],{"class":5452},[5280,44907,5844],{"class":5758},[5280,44909,5847],{"class":5452},[5280,44911,5844],{"class":5758},[5280,44913,6828],{"class":5452},[5280,44915,44916],{"class":5282,"line":6042},[5280,44917,6553],{"class":5452},[5280,44919,44920],{"class":5282,"line":6093},[5280,44921,6724],{"class":5452},[5280,44923,44924],{"class":5282,"line":6098},[5280,44925,44926],{"class":5286},"    \u002F\u002F Перевірка підключення до БД\n",[5280,44928,44929,44931,44934,44936,44938,44940,44942],{"class":5282,"line":6114},[5280,44930,7023],{"class":5436},[5280,44932,44933],{"class":5424}," h",[5280,44935,5218],{"class":5452},[5280,44937,23813],{"class":5424},[5280,44939,6301],{"class":5452},[5280,44941,5967],{"class":5619},[5280,44943,6111],{"class":5452},[5280,44945,44946,44948,44950,44952,44954,44956,44958,44960,44962,44964,44966,44968,44970,44972,44974,44976,44978,44980],{"class":5282,"line":6130},[5280,44947,15374],{"class":5424},[5280,44949,5887],{"class":5452},[5280,44951,38775],{"class":5424},[5280,44953,6029],{"class":5452},[5280,44955,15379],{"class":5424},[5280,44957,5218],{"class":5452},[5280,44959,38784],{"class":5293},[5280,44961,5835],{"class":5452},[5280,44963,6007],{"class":5424},[5280,44965,5218],{"class":5452},[5280,44967,15393],{"class":5293},[5280,44969,15396],{"class":5452},[5280,44971,13457],{"class":5439},[5280,44973,8072],{"class":5452},[5280,44975,5587],{"class":5424},[5280,44977,5218],{"class":5452},[5280,44979,14252],{"class":5424},[5280,44981,5598],{"class":5452},[5280,44983,44984,44986,44988],{"class":5282,"line":6159},[5280,44985,10596],{"class":5436},[5280,44987,38813],{"class":5293},[5280,44989,6039],{"class":5452},[5280,44991,44992],{"class":5282,"line":6187},[5280,44993,6190],{"class":5452},[5280,44995,44996,44998,45000,45002,45004,45006,45008,45010,45013,45015,45017,45019,45021,45023,45025],{"class":5282,"line":6193},[5280,44997,6259],{"class":5436},[5280,44999,6262],{"class":5424},[5280,45001,6029],{"class":5452},[5280,45003,11326],{"class":5424},[5280,45005,5218],{"class":5452},[5280,45007,23813],{"class":5424},[5280,45009,5218],{"class":5452},[5280,45011,45012],{"class":5293},"PingContext",[5280,45014,5835],{"class":5452},[5280,45016,15441],{"class":5424},[5280,45018,6295],{"class":5452},[5280,45020,6298],{"class":5424},[5280,45022,6301],{"class":5452},[5280,45024,5967],{"class":5619},[5280,45026,6111],{"class":5452},[5280,45028,45029,45032,45034,45036,45038],{"class":5282,"line":6207},[5280,45030,45031],{"class":5424},"            response",[5280,45033,5218],{"class":5452},[5280,45035,37860],{"class":5424},[5280,45037,5829],{"class":5452},[5280,45039,45040],{"class":5297},"\"degraded\"\n",[5280,45042,45043,45045,45047,45050,45052,45055,45057,45060,45062,45064,45066,45068],{"class":5282,"line":6221},[5280,45044,45031],{"class":5424},[5280,45046,5218],{"class":5452},[5280,45048,45049],{"class":5424},"Services",[5280,45051,5841],{"class":5452},[5280,45053,45054],{"class":5297},"\"database\"",[5280,45056,6440],{"class":5452},[5280,45058,45059],{"class":5297},"\"unhealthy: \"",[5280,45061,15333],{"class":5452},[5280,45063,6298],{"class":5424},[5280,45065,5218],{"class":5452},[5280,45067,6316],{"class":5293},[5280,45069,6039],{"class":5452},[5280,45071,45072,45075,45077],{"class":5282,"line":6233},[5280,45073,45074],{"class":5452},"        } ",[5280,45076,7073],{"class":5436},[5280,45078,6111],{"class":5452},[5280,45080,45081,45083,45085,45087,45089,45091,45093],{"class":5282,"line":6245},[5280,45082,45031],{"class":5424},[5280,45084,5218],{"class":5452},[5280,45086,45049],{"class":5424},[5280,45088,5841],{"class":5452},[5280,45090,45054],{"class":5297},[5280,45092,6440],{"class":5452},[5280,45094,45095],{"class":5297},"\"healthy\"\n",[5280,45097,45098],{"class":5282,"line":6251},[5280,45099,6248],{"class":5452},[5280,45101,45102],{"class":5282,"line":6256},[5280,45103,6553],{"class":5452},[5280,45105,45106],{"class":5282,"line":6308},[5280,45107,6724],{"class":5452},[5280,45109,45110],{"class":5282,"line":6339},[5280,45111,45112],{"class":5286},"    \u002F\u002F Можна додати перевірки інших сервісів\n",[5280,45114,45115],{"class":5282,"line":6371},[5280,45116,45117],{"class":5286},"    \u002F\u002F Redis, RabbitMQ, external APIs...\n",[5280,45119,45120],{"class":5282,"line":6409},[5280,45121,6724],{"class":5452},[5280,45123,45124,45126,45128,45130,45132],{"class":5282,"line":6415},[5280,45125,9397],{"class":5424},[5280,45127,6029],{"class":5452},[5280,45129,5952],{"class":5424},[5280,45131,5218],{"class":5452},[5280,45133,9600],{"class":5424},[5280,45135,45136,45138,45140,45142,45144,45146,45148],{"class":5282,"line":6420},[5280,45137,7023],{"class":5436},[5280,45139,13370],{"class":5424},[5280,45141,5218],{"class":5452},[5280,45143,37860],{"class":5424},[5280,45145,6301],{"class":5452},[5280,45147,6793],{"class":5297},[5280,45149,6111],{"class":5452},[5280,45151,45152,45155,45157,45159,45161],{"class":5282,"line":6425},[5280,45153,45154],{"class":5424},"        statusCode",[5280,45156,5829],{"class":5452},[5280,45158,5952],{"class":5424},[5280,45160,5218],{"class":5452},[5280,45162,45163],{"class":5424},"StatusServiceUnavailable\n",[5280,45165,45166],{"class":5282,"line":6450},[5280,45167,6553],{"class":5452},[5280,45169,45170],{"class":5282,"line":6471},[5280,45171,6724],{"class":5452},[5280,45173,45174,45176,45178,45180,45182,45184,45186,45188,45190,45192],{"class":5282,"line":6507},[5280,45175,6730],{"class":5424},[5280,45177,5218],{"class":5452},[5280,45179,6138],{"class":5293},[5280,45181,6141],{"class":5452},[5280,45183,6144],{"class":5293},[5280,45185,5835],{"class":5452},[5280,45187,6149],{"class":5297},[5280,45189,5887],{"class":5452},[5280,45191,6154],{"class":5297},[5280,45193,5598],{"class":5452},[5280,45195,45196,45198,45200,45202,45204,45206],{"class":5282,"line":6512},[5280,45197,6730],{"class":5424},[5280,45199,5218],{"class":5452},[5280,45201,6457],{"class":5293},[5280,45203,5835],{"class":5452},[5280,45205,9475],{"class":5424},[5280,45207,5598],{"class":5452},[5280,45209,45210,45212,45214,45216,45218,45220,45222,45224,45226,45228],{"class":5282,"line":6520},[5280,45211,6754],{"class":5424},[5280,45213,5218],{"class":5452},[5280,45215,6167],{"class":5293},[5280,45217,5835],{"class":5452},[5280,45219,5994],{"class":5424},[5280,45221,6174],{"class":5452},[5280,45223,6177],{"class":5293},[5280,45225,5835],{"class":5452},[5280,45227,25656],{"class":5424},[5280,45229,5598],{"class":5452},[5280,45231,45232],{"class":5282,"line":6550},[5280,45233,5976],{"class":5452},[5280,45235,45236],{"class":5282,"line":6556},[5280,45237,5313],{"emptyLinePlaceholder":5312},[5280,45239,45240],{"class":5282,"line":6561},[5280,45241,45242],{"class":5286},"\u002F\u002F Liveness probe (для Kubernetes)\n",[5280,45244,45245,45247,45249,45251,45253,45255,45257,45260,45262,45264,45266,45268,45270,45272,45274,45276,45278,45280,45282],{"class":5282,"line":6621},[5280,45246,5862],{"class":5619},[5280,45248,5720],{"class":5452},[5280,45250,8069],{"class":5424},[5280,45252,8072],{"class":5452},[5280,45254,44820],{"class":5758},[5280,45256,7315],{"class":5452},[5280,45258,45259],{"class":5293},"Liveness",[5280,45261,5835],{"class":5452},[5280,45263,5994],{"class":5424},[5280,45265,5997],{"class":5758},[5280,45267,5218],{"class":5452},[5280,45269,6002],{"class":5758},[5280,45271,5887],{"class":5452},[5280,45273,6007],{"class":5424},[5280,45275,6010],{"class":5452},[5280,45277,5952],{"class":5758},[5280,45279,5218],{"class":5452},[5280,45281,6017],{"class":5758},[5280,45283,6020],{"class":5452},[5280,45285,45286,45288,45290,45292,45294,45296,45298,45300],{"class":5282,"line":6626},[5280,45287,6730],{"class":5424},[5280,45289,5218],{"class":5452},[5280,45291,6457],{"class":5293},[5280,45293,5835],{"class":5452},[5280,45295,5952],{"class":5424},[5280,45297,5218],{"class":5452},[5280,45299,9775],{"class":5424},[5280,45301,5598],{"class":5452},[5280,45303,45304,45306,45308,45310,45312,45314,45316,45319],{"class":5282,"line":6631},[5280,45305,6730],{"class":5424},[5280,45307,5218],{"class":5452},[5280,45309,9545],{"class":5293},[5280,45311,22255],{"class":5452},[5280,45313,9556],{"class":5758},[5280,45315,5835],{"class":5452},[5280,45317,45318],{"class":5297},"\"alive\"",[5280,45320,5970],{"class":5452},[5280,45322,45323],{"class":5282,"line":6663},[5280,45324,5976],{"class":5452},[5280,45326,45327],{"class":5282,"line":6678},[5280,45328,5313],{"emptyLinePlaceholder":5312},[5280,45330,45331],{"class":5282,"line":6721},[5280,45332,45333],{"class":5286},"\u002F\u002F Readiness probe (для Kubernetes)\n",[5280,45335,45336,45338,45340,45342,45344,45346,45348,45351,45353,45355,45357,45359,45361,45363,45365,45367,45369,45371,45373],{"class":5282,"line":6727},[5280,45337,5862],{"class":5619},[5280,45339,5720],{"class":5452},[5280,45341,8069],{"class":5424},[5280,45343,8072],{"class":5452},[5280,45345,44820],{"class":5758},[5280,45347,7315],{"class":5452},[5280,45349,45350],{"class":5293},"Readiness",[5280,45352,5835],{"class":5452},[5280,45354,5994],{"class":5424},[5280,45356,5997],{"class":5758},[5280,45358,5218],{"class":5452},[5280,45360,6002],{"class":5758},[5280,45362,5887],{"class":5452},[5280,45364,6007],{"class":5424},[5280,45366,6010],{"class":5452},[5280,45368,5952],{"class":5758},[5280,45370,5218],{"class":5452},[5280,45372,6017],{"class":5758},[5280,45374,6020],{"class":5452},[5280,45376,45377],{"class":5282,"line":6751},[5280,45378,45379],{"class":5286},"    \u002F\u002F Перевіряємо критичні залежності\n",[5280,45381,45382,45384,45386,45388,45390,45392,45394],{"class":5282,"line":6784},[5280,45383,7023],{"class":5436},[5280,45385,44933],{"class":5424},[5280,45387,5218],{"class":5452},[5280,45389,23813],{"class":5424},[5280,45391,6301],{"class":5452},[5280,45393,5967],{"class":5619},[5280,45395,6111],{"class":5452},[5280,45397,45398,45400,45402,45404,45406,45408,45410,45412,45414,45416,45418,45420,45422,45424,45426,45428,45430,45432],{"class":5282,"line":6799},[5280,45399,15374],{"class":5424},[5280,45401,5887],{"class":5452},[5280,45403,38775],{"class":5424},[5280,45405,6029],{"class":5452},[5280,45407,15379],{"class":5424},[5280,45409,5218],{"class":5452},[5280,45411,38784],{"class":5293},[5280,45413,5835],{"class":5452},[5280,45415,6007],{"class":5424},[5280,45417,5218],{"class":5452},[5280,45419,15393],{"class":5293},[5280,45421,15396],{"class":5452},[5280,45423,13457],{"class":5439},[5280,45425,8072],{"class":5452},[5280,45427,5587],{"class":5424},[5280,45429,5218],{"class":5452},[5280,45431,14252],{"class":5424},[5280,45433,5598],{"class":5452},[5280,45435,45436,45438,45440],{"class":5282,"line":6831},[5280,45437,10596],{"class":5436},[5280,45439,38813],{"class":5293},[5280,45441,6039],{"class":5452},[5280,45443,45444],{"class":5282,"line":6837},[5280,45445,6190],{"class":5452},[5280,45447,45448,45450,45452,45454,45456,45458,45460,45462,45464,45466,45468,45470,45472,45474,45476],{"class":5282,"line":6842},[5280,45449,6259],{"class":5436},[5280,45451,6262],{"class":5424},[5280,45453,6029],{"class":5452},[5280,45455,11326],{"class":5424},[5280,45457,5218],{"class":5452},[5280,45459,23813],{"class":5424},[5280,45461,5218],{"class":5452},[5280,45463,45012],{"class":5293},[5280,45465,5835],{"class":5452},[5280,45467,15441],{"class":5424},[5280,45469,6295],{"class":5452},[5280,45471,6298],{"class":5424},[5280,45473,6301],{"class":5452},[5280,45475,5967],{"class":5619},[5280,45477,6111],{"class":5452},[5280,45479,45480,45482,45484,45486,45488,45490,45492,45495],{"class":5282,"line":6901},[5280,45481,14227],{"class":5424},[5280,45483,5218],{"class":5452},[5280,45485,6457],{"class":5293},[5280,45487,5835],{"class":5452},[5280,45489,5952],{"class":5424},[5280,45491,5218],{"class":5452},[5280,45493,45494],{"class":5424},"StatusServiceUnavailable",[5280,45496,5598],{"class":5452},[5280,45498,45499,45501,45503,45505,45507,45509,45511,45514],{"class":5282,"line":12702},[5280,45500,14227],{"class":5424},[5280,45502,5218],{"class":5452},[5280,45504,9545],{"class":5293},[5280,45506,22255],{"class":5452},[5280,45508,9556],{"class":5758},[5280,45510,5835],{"class":5452},[5280,45512,45513],{"class":5297},"\"not ready\"",[5280,45515,5970],{"class":5452},[5280,45517,45518],{"class":5282,"line":12731},[5280,45519,6412],{"class":5436},[5280,45521,45522],{"class":5282,"line":12736},[5280,45523,6248],{"class":5452},[5280,45525,45526],{"class":5282,"line":12741},[5280,45527,6553],{"class":5452},[5280,45529,45530],{"class":5282,"line":12746},[5280,45531,6724],{"class":5452},[5280,45533,45534,45536,45538,45540,45542,45544,45546,45548],{"class":5282,"line":12752},[5280,45535,6730],{"class":5424},[5280,45537,5218],{"class":5452},[5280,45539,6457],{"class":5293},[5280,45541,5835],{"class":5452},[5280,45543,5952],{"class":5424},[5280,45545,5218],{"class":5452},[5280,45547,9775],{"class":5424},[5280,45549,5598],{"class":5452},[5280,45551,45552,45554,45556,45558,45560,45562,45564,45567],{"class":5282,"line":12774},[5280,45553,6730],{"class":5424},[5280,45555,5218],{"class":5452},[5280,45557,9545],{"class":5293},[5280,45559,22255],{"class":5452},[5280,45561,9556],{"class":5758},[5280,45563,5835],{"class":5452},[5280,45565,45566],{"class":5297},"\"ready\"",[5280,45568,5970],{"class":5452},[5280,45570,45571],{"class":5282,"line":12794},[5280,45572,5976],{"class":5452},[5266,45574,45576],{"id":45575},"metrics-ендпоінт-prometheus-сумісний","Metrics ендпоінт (Prometheus-сумісний)",[5271,45578,45580],{"className":5411,"code":45579,"language":5325,"meta":5276,"style":5276},"\u002F\u002F middleware\u002Fmetrics.go\npackage middleware\n\nimport (\n    \"fmt\"\n    \"net\u002Fhttp\"\n    \"strconv\"\n    \"sync\u002Fatomic\"\n    \"time\"\n)\n\nvar (\n    requestsTotal     int64\n    requestsInFlight  int64\n    requestDurations  = make(map[string][]time.Duration)\n)\n\n\u002F\u002F Metrics — Middleware для збору метрик\nfunc Metrics(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        start := time.Now()\n        atomic.AddInt64(&requestsTotal, 1)\n        atomic.AddInt64(&requestsInFlight, 1)\n        defer atomic.AddInt64(&requestsInFlight, -1)\n        \n        wrapped := &responseWriter{\n            ResponseWriter: w,\n            statusCode:     http.StatusOK,\n        }\n        \n        next.ServeHTTP(wrapped, r)\n        \n        duration := time.Since(start)\n        \n        \u002F\u002F Зберігаємо тривалість для гістограми\n        key := r.Method + \" \" + r.URL.Path\n        \u002F\u002F Тут має бути потокобезпечна структура даних\n        \u002F\u002F Для простоти пропускаємо\n    })\n}\n\n\u002F\u002F MetricsHandler віддає метрики у Prometheus форматі\nfunc MetricsHandler(w http.ResponseWriter, r *http.Request) {\n    w.Header().Set(\"Content-Type\", \"text\u002Fplain; version=0.0.4\")\n    \n    fmt.Fprintf(w, \"# HELP http_requests_total Total number of HTTP requests\\n\")\n    fmt.Fprintf(w, \"# TYPE http_requests_total counter\\n\")\n    fmt.Fprintf(w, \"http_requests_total %d\\n\", atomic.LoadInt64(&requestsTotal))\n    \n    fmt.Fprintf(w, \"# HELP http_requests_in_flight Current number of HTTP requests being served\\n\")\n    fmt.Fprintf(w, \"# TYPE http_requests_in_flight gauge\\n\")\n    fmt.Fprintf(w, \"http_requests_in_flight %d\\n\", atomic.LoadInt64(&requestsInFlight))\n}\n",[5215,45581,45582,45587,45593,45597,45603,45607,45611,45616,45620,45624,45628,45632,45638,45646,45653,45682,45686,45690,45695,45722,45760,45774,45794,45813,45835,45839,45849,45859,45873,45877,45881,45899,45903,45921,45925,45930,45960,45965,45970,45974,45978,45982,45987,46018,46041,46045,46068,46091,46128,46132,46155,46178,46215],{"__ignoreMap":5276},[5280,45583,45584],{"class":5282,"line":5283},[5280,45585,45586],{"class":5286},"\u002F\u002F middleware\u002Fmetrics.go\n",[5280,45588,45589,45591],{"class":5282,"line":5290},[5280,45590,5755],{"class":5619},[5280,45592,5641],{"class":5758},[5280,45594,45595],{"class":5282,"line":5301},[5280,45596,5313],{"emptyLinePlaceholder":5312},[5280,45598,45599,45601],{"class":5282,"line":5309},[5280,45600,5768],{"class":5436},[5280,45602,5453],{"class":5452},[5280,45604,45605],{"class":5282,"line":5316},[5280,45606,5780],{"class":5297},[5280,45608,45609],{"class":5282,"line":5322},[5280,45610,5790],{"class":5297},[5280,45612,45613],{"class":5282,"line":5336},[5280,45614,45615],{"class":5297},"    \"strconv\"\n",[5280,45617,45618],{"class":5282,"line":5341},[5280,45619,40557],{"class":5297},[5280,45621,45622],{"class":5282,"line":5347},[5280,45623,5795],{"class":5297},[5280,45625,45626],{"class":5282,"line":5358},[5280,45627,5598],{"class":5452},[5280,45629,45630],{"class":5282,"line":5368},[5280,45631,5313],{"emptyLinePlaceholder":5312},[5280,45633,45634,45636],{"class":5282,"line":5378},[5280,45635,5823],{"class":5619},[5280,45637,5453],{"class":5452},[5280,45639,45640,45643],{"class":5282,"line":5391},[5280,45641,45642],{"class":5424},"    requestsTotal",[5280,45644,45645],{"class":5758},"     int64\n",[5280,45647,45648,45651],{"class":5282,"line":5705},[5280,45649,45650],{"class":5424},"    requestsInFlight",[5280,45652,40599],{"class":5758},[5280,45654,45655,45658,45660,45662,45664,45666,45668,45670,45673,45675,45677,45680],{"class":5282,"line":5820},[5280,45656,45657],{"class":5424},"    requestDurations",[5280,45659,35367],{"class":5452},[5280,45661,5832],{"class":5293},[5280,45663,5835],{"class":5452},[5280,45665,5838],{"class":5619},[5280,45667,5841],{"class":5452},[5280,45669,5844],{"class":5758},[5280,45671,45672],{"class":5452},"][]",[5280,45674,5587],{"class":5758},[5280,45676,5218],{"class":5452},[5280,45678,45679],{"class":5758},"Duration",[5280,45681,5598],{"class":5452},[5280,45683,45684],{"class":5282,"line":5854},[5280,45685,5598],{"class":5452},[5280,45687,45688],{"class":5282,"line":5859},[5280,45689,5313],{"emptyLinePlaceholder":5312},[5280,45691,45692],{"class":5282,"line":5871},[5280,45693,45694],{"class":5286},"\u002F\u002F Metrics — Middleware для збору метрик\n",[5280,45696,45697,45699,45702,45704,45706,45708,45710,45712,45714,45716,45718,45720],{"class":5282,"line":5895},[5280,45698,5862],{"class":5619},[5280,45700,45701],{"class":5293}," Metrics",[5280,45703,5835],{"class":5452},[5280,45705,7305],{"class":5424},[5280,45707,5997],{"class":5758},[5280,45709,5218],{"class":5452},[5280,45711,7935],{"class":5758},[5280,45713,7315],{"class":5452},[5280,45715,5952],{"class":5758},[5280,45717,5218],{"class":5452},[5280,45719,7935],{"class":5758},[5280,45721,6111],{"class":5452},[5280,45723,45724,45726,45728,45730,45732,45734,45736,45738,45740,45742,45744,45746,45748,45750,45752,45754,45756,45758],{"class":5282,"line":5916},[5280,45725,7328],{"class":5436},[5280,45727,5997],{"class":5424},[5280,45729,5218],{"class":5452},[5280,45731,7312],{"class":5293},[5280,45733,5835],{"class":5452},[5280,45735,5862],{"class":5619},[5280,45737,5835],{"class":5452},[5280,45739,5994],{"class":5424},[5280,45741,5997],{"class":5758},[5280,45743,5218],{"class":5452},[5280,45745,6002],{"class":5758},[5280,45747,5887],{"class":5452},[5280,45749,6007],{"class":5424},[5280,45751,6010],{"class":5452},[5280,45753,5952],{"class":5758},[5280,45755,5218],{"class":5452},[5280,45757,6017],{"class":5758},[5280,45759,6020],{"class":5452},[5280,45761,45762,45764,45766,45768,45770,45772],{"class":5282,"line":5921},[5280,45763,7360],{"class":5424},[5280,45765,6029],{"class":5452},[5280,45767,5587],{"class":5424},[5280,45769,5218],{"class":5452},[5280,45771,6036],{"class":5293},[5280,45773,6039],{"class":5452},[5280,45775,45776,45779,45781,45783,45785,45788,45790,45792],{"class":5282,"line":5939},[5280,45777,45778],{"class":5424},"        atomic",[5280,45780,5218],{"class":5452},[5280,45782,40776],{"class":5293},[5280,45784,6289],{"class":5452},[5280,45786,45787],{"class":5424},"requestsTotal",[5280,45789,5887],{"class":5452},[5280,45791,11401],{"class":5439},[5280,45793,5598],{"class":5452},[5280,45795,45796,45798,45800,45802,45804,45807,45809,45811],{"class":5282,"line":5973},[5280,45797,45778],{"class":5424},[5280,45799,5218],{"class":5452},[5280,45801,40776],{"class":5293},[5280,45803,6289],{"class":5452},[5280,45805,45806],{"class":5424},"requestsInFlight",[5280,45808,5887],{"class":5452},[5280,45810,11401],{"class":5439},[5280,45812,5598],{"class":5452},[5280,45814,45815,45817,45820,45822,45824,45826,45828,45831,45833],{"class":5282,"line":5979},[5280,45816,10596],{"class":5436},[5280,45818,45819],{"class":5424}," atomic",[5280,45821,5218],{"class":5452},[5280,45823,40776],{"class":5293},[5280,45825,6289],{"class":5452},[5280,45827,45806],{"class":5424},[5280,45829,45830],{"class":5452},", -",[5280,45832,11401],{"class":5439},[5280,45834,5598],{"class":5452},[5280,45836,45837],{"class":5282,"line":5984},[5280,45838,6190],{"class":5452},[5280,45840,45841,45843,45845,45847],{"class":5282,"line":6023},[5280,45842,9742],{"class":5424},[5280,45844,9745],{"class":5452},[5280,45846,9435],{"class":5758},[5280,45848,6781],{"class":5452},[5280,45850,45851,45853,45855,45857],{"class":5282,"line":6042},[5280,45852,9754],{"class":5424},[5280,45854,6790],{"class":5452},[5280,45856,5994],{"class":5424},[5280,45858,6796],{"class":5452},[5280,45860,45861,45863,45865,45867,45869,45871],{"class":5282,"line":6093},[5280,45862,9765],{"class":5424},[5280,45864,9768],{"class":5452},[5280,45866,5952],{"class":5424},[5280,45868,5218],{"class":5452},[5280,45870,9775],{"class":5424},[5280,45872,6796],{"class":5452},[5280,45874,45875],{"class":5282,"line":6098},[5280,45876,6248],{"class":5452},[5280,45878,45879],{"class":5282,"line":6114},[5280,45880,6190],{"class":5452},[5280,45882,45883,45885,45887,45889,45891,45893,45895,45897],{"class":5282,"line":6130},[5280,45884,7426],{"class":5424},[5280,45886,5218],{"class":5452},[5280,45888,8033],{"class":5293},[5280,45890,5835],{"class":5452},[5280,45892,9889],{"class":5424},[5280,45894,5887],{"class":5452},[5280,45896,6007],{"class":5424},[5280,45898,5598],{"class":5452},[5280,45900,45901],{"class":5282,"line":6159},[5280,45902,6190],{"class":5452},[5280,45904,45905,45907,45909,45911,45913,45915,45917,45919],{"class":5282,"line":6187},[5280,45906,9208],{"class":5424},[5280,45908,6029],{"class":5452},[5280,45910,5587],{"class":5424},[5280,45912,5218],{"class":5452},[5280,45914,6399],{"class":5293},[5280,45916,5835],{"class":5452},[5280,45918,6404],{"class":5424},[5280,45920,5598],{"class":5452},[5280,45922,45923],{"class":5282,"line":6193},[5280,45924,6190],{"class":5452},[5280,45926,45927],{"class":5282,"line":6207},[5280,45928,45929],{"class":5286},"        \u002F\u002F Зберігаємо тривалість для гістограми\n",[5280,45931,45932,45935,45937,45939,45941,45943,45945,45947,45949,45951,45953,45955,45957],{"class":5282,"line":6221},[5280,45933,45934],{"class":5424},"        key",[5280,45936,6029],{"class":5452},[5280,45938,6007],{"class":5424},[5280,45940,5218],{"class":5452},[5280,45942,6074],{"class":5424},[5280,45944,15333],{"class":5452},[5280,45946,15177],{"class":5297},[5280,45948,15333],{"class":5452},[5280,45950,6007],{"class":5424},[5280,45952,5218],{"class":5452},[5280,45954,6083],{"class":5424},[5280,45956,5218],{"class":5452},[5280,45958,45959],{"class":5424},"Path\n",[5280,45961,45962],{"class":5282,"line":6233},[5280,45963,45964],{"class":5286},"        \u002F\u002F Тут має бути потокобезпечна структура даних\n",[5280,45966,45967],{"class":5282,"line":6245},[5280,45968,45969],{"class":5286},"        \u002F\u002F Для простоти пропускаємо\n",[5280,45971,45972],{"class":5282,"line":6251},[5280,45973,6834],{"class":5452},[5280,45975,45976],{"class":5282,"line":6256},[5280,45977,5976],{"class":5452},[5280,45979,45980],{"class":5282,"line":6308},[5280,45981,5313],{"emptyLinePlaceholder":5312},[5280,45983,45984],{"class":5282,"line":6339},[5280,45985,45986],{"class":5286},"\u002F\u002F MetricsHandler віддає метрики у Prometheus форматі\n",[5280,45988,45989,45991,45994,45996,45998,46000,46002,46004,46006,46008,46010,46012,46014,46016],{"class":5282,"line":6371},[5280,45990,5862],{"class":5619},[5280,45992,45993],{"class":5293}," MetricsHandler",[5280,45995,5835],{"class":5452},[5280,45997,5994],{"class":5424},[5280,45999,5997],{"class":5758},[5280,46001,5218],{"class":5452},[5280,46003,6002],{"class":5758},[5280,46005,5887],{"class":5452},[5280,46007,6007],{"class":5424},[5280,46009,6010],{"class":5452},[5280,46011,5952],{"class":5758},[5280,46013,5218],{"class":5452},[5280,46015,6017],{"class":5758},[5280,46017,6020],{"class":5452},[5280,46019,46020,46022,46024,46026,46028,46030,46032,46034,46036,46039],{"class":5282,"line":6409},[5280,46021,6730],{"class":5424},[5280,46023,5218],{"class":5452},[5280,46025,6138],{"class":5293},[5280,46027,6141],{"class":5452},[5280,46029,6144],{"class":5293},[5280,46031,5835],{"class":5452},[5280,46033,6149],{"class":5297},[5280,46035,5887],{"class":5452},[5280,46037,46038],{"class":5297},"\"text\u002Fplain; version=0.0.4\"",[5280,46040,5598],{"class":5452},[5280,46042,46043],{"class":5282,"line":6415},[5280,46044,6724],{"class":5452},[5280,46046,46047,46049,46051,46053,46055,46057,46059,46062,46064,46066],{"class":5282,"line":6420},[5280,46048,5924],{"class":5424},[5280,46050,5218],{"class":5452},[5280,46052,6479],{"class":5293},[5280,46054,5835],{"class":5452},[5280,46056,5994],{"class":5424},[5280,46058,5887],{"class":5452},[5280,46060,46061],{"class":5297},"\"# HELP http_requests_total Total number of HTTP requests",[5280,46063,10648],{"class":10647},[5280,46065,6584],{"class":5297},[5280,46067,5598],{"class":5452},[5280,46069,46070,46072,46074,46076,46078,46080,46082,46085,46087,46089],{"class":5282,"line":6425},[5280,46071,5924],{"class":5424},[5280,46073,5218],{"class":5452},[5280,46075,6479],{"class":5293},[5280,46077,5835],{"class":5452},[5280,46079,5994],{"class":5424},[5280,46081,5887],{"class":5452},[5280,46083,46084],{"class":5297},"\"# TYPE http_requests_total counter",[5280,46086,10648],{"class":10647},[5280,46088,6584],{"class":5297},[5280,46090,5598],{"class":5452},[5280,46092,46093,46095,46097,46099,46101,46103,46105,46108,46110,46112,46114,46116,46118,46120,46122,46124,46126],{"class":5282,"line":6450},[5280,46094,5924],{"class":5424},[5280,46096,5218],{"class":5452},[5280,46098,6479],{"class":5293},[5280,46100,5835],{"class":5452},[5280,46102,5994],{"class":5424},[5280,46104,5887],{"class":5452},[5280,46106,46107],{"class":5297},"\"http_requests_total ",[5280,46109,9946],{"class":5424},[5280,46111,10648],{"class":10647},[5280,46113,6584],{"class":5297},[5280,46115,5887],{"class":5452},[5280,46117,41153],{"class":5424},[5280,46119,5218],{"class":5452},[5280,46121,41158],{"class":5293},[5280,46123,6289],{"class":5452},[5280,46125,45787],{"class":5424},[5280,46127,5970],{"class":5452},[5280,46129,46130],{"class":5282,"line":6471},[5280,46131,6724],{"class":5452},[5280,46133,46134,46136,46138,46140,46142,46144,46146,46149,46151,46153],{"class":5282,"line":6507},[5280,46135,5924],{"class":5424},[5280,46137,5218],{"class":5452},[5280,46139,6479],{"class":5293},[5280,46141,5835],{"class":5452},[5280,46143,5994],{"class":5424},[5280,46145,5887],{"class":5452},[5280,46147,46148],{"class":5297},"\"# HELP http_requests_in_flight Current number of HTTP requests being served",[5280,46150,10648],{"class":10647},[5280,46152,6584],{"class":5297},[5280,46154,5598],{"class":5452},[5280,46156,46157,46159,46161,46163,46165,46167,46169,46172,46174,46176],{"class":5282,"line":6512},[5280,46158,5924],{"class":5424},[5280,46160,5218],{"class":5452},[5280,46162,6479],{"class":5293},[5280,46164,5835],{"class":5452},[5280,46166,5994],{"class":5424},[5280,46168,5887],{"class":5452},[5280,46170,46171],{"class":5297},"\"# TYPE http_requests_in_flight gauge",[5280,46173,10648],{"class":10647},[5280,46175,6584],{"class":5297},[5280,46177,5598],{"class":5452},[5280,46179,46180,46182,46184,46186,46188,46190,46192,46195,46197,46199,46201,46203,46205,46207,46209,46211,46213],{"class":5282,"line":6520},[5280,46181,5924],{"class":5424},[5280,46183,5218],{"class":5452},[5280,46185,6479],{"class":5293},[5280,46187,5835],{"class":5452},[5280,46189,5994],{"class":5424},[5280,46191,5887],{"class":5452},[5280,46193,46194],{"class":5297},"\"http_requests_in_flight ",[5280,46196,9946],{"class":5424},[5280,46198,10648],{"class":10647},[5280,46200,6584],{"class":5297},[5280,46202,5887],{"class":5452},[5280,46204,41153],{"class":5424},[5280,46206,5218],{"class":5452},[5280,46208,41158],{"class":5293},[5280,46210,6289],{"class":5452},[5280,46212,45806],{"class":5424},[5280,46214,5970],{"class":5452},[5280,46216,46217],{"class":5282,"line":6550},[5280,46218,5976],{"class":5452},[5254,46220],{},[5257,46222,46224],{"id":46223},"checklist-для-review-коду","Checklist для review коду",[5262,46226,46227],{},"Використовуйте цей чеклист при перевірці коду:",[46229,46230,46231,46233,46270,46273,46300,46302,46335,46337,46366,46369,46396,46400],"steps",{},[5266,46232,7633],{"id":8768},[5194,46234,46237,46248,46258,46264],{"className":46235},[46236],"contains-task-list",[5197,46238,46241,46245,46246],{"className":46239},[46240],"task-list-item",[46242,46243],"input",{"disabled":5312,"type":46244},"checkbox"," Middleware повертає ",[5215,46247,7617],{},[5197,46249,46251,46253,46254,46257],{"className":46250},[46240],[46242,46252],{"disabled":5312,"type":46244}," Викликається ",[5215,46255,46256],{},"next.ServeHTTP(w, r)"," (якщо не припиняємо обробку)",[5197,46259,46261,46263],{"className":46260},[46240],[46242,46262],{"disabled":5312,"type":46244}," Panic Recovery на місці",[5197,46265,46267,46269],{"className":46266},[46240],[46242,46268],{"disabled":5312,"type":46244}," Логування включає важливі поля (method, path, status, duration)",[5266,46271,20878],{"id":46272},"clean-architecture",[5194,46274,46276,46282,46288,46294],{"className":46275},[46236],[5197,46277,46279,46281],{"className":46278},[46240],[46242,46280],{"disabled":5312,"type":46244}," Domain не імпортує нічого, крім стандартної бібліотеки",[5197,46283,46285,46287],{"className":46284},[46240],[46242,46286],{"disabled":5312,"type":46244}," UseCase залежить від інтерфейсів, а не реалізацій",[5197,46289,46291,46293],{"className":46290},[46240],[46242,46292],{"disabled":5312,"type":46244}," Repository визначений як інтерфейс у Domain",[5197,46295,46297,46299],{"className":46296},[46240],[46242,46298],{"disabled":5312,"type":46244}," Handler не містить бізнес-логіки",[5266,46301,15393],{"id":15379},[5194,46303,46305,46313,46319,46329],{"className":46304},[46236],[5197,46306,46308,7648,46310,46312],{"className":46307},[46240],[46242,46309],{"disabled":5312,"type":46244},[5215,46311,14764],{}," передається першим параметром",[5197,46314,46316,46318],{"className":46315},[46240],[46242,46317],{"disabled":5312,"type":46244}," Використовуються власні типи для ключів контексту",[5197,46320,46322,46324,46325,46328],{"className":46321},[46240],[46242,46323],{"disabled":5312,"type":46244}," Перевіряється ",[5215,46326,46327],{},"ctx.Done()"," у довгих операціях",[5197,46330,46332,46334],{"className":46331},[46240],[46242,46333],{"disabled":5312,"type":46244}," Контекст не зберігається у структурах",[5266,46336,11719],{"id":5952},[5194,46338,46340,46348,46354,46360],{"className":46339},[46236],[5197,46341,46343,46345,46346],{"className":46342},[46240],[46242,46344],{"disabled":5312,"type":46244}," Заголовки встановлюються перед ",[5215,46347,9321],{},[5197,46349,46351,46353],{"className":46350},[46240],[46242,46352],{"disabled":5312,"type":46244}," Статус-код відповідає RFC (200, 201, 400, 404, 500...)",[5197,46355,46357,46359],{"className":46356},[46240],[46242,46358],{"disabled":5312,"type":46244}," Використовуються DTO замість доменних сутностей",[5197,46361,46363,46365],{"className":46362},[46240],[46242,46364],{"disabled":5312,"type":46244}," Паролі не потрапляють у відповіді",[5266,46367,968],{"id":46368},"тестування",[5194,46370,46372,46378,46384,46390],{"className":46371},[46236],[5197,46373,46375,46377],{"className":46374},[46240],[46242,46376],{"disabled":5312,"type":46244}," Кожен шар має тести",[5197,46379,46381,46383],{"className":46380},[46240],[46242,46382],{"disabled":5312,"type":46244}," Використовуються моки для ізоляції",[5197,46385,46387,46389],{"className":46386},[46240],[46242,46388],{"disabled":5312,"type":46244}," Table-driven tests для множинних сценаріїв",[5197,46391,46393,46395],{"className":46392},[46240],[46242,46394],{"disabled":5312,"type":46244}," Покриття тестами > 80%",[5266,46397,46399],{"id":46398},"безпека","Безпека",[5194,46401,46403,46409,46415,46421],{"className":46402},[46236],[5197,46404,46406,46408],{"className":46405},[46240],[46242,46407],{"disabled":5312,"type":46244}," Паролі хешуються (bcrypt, argon2)",[5197,46410,46412,46414],{"className":46411},[46240],[46242,46413],{"disabled":5312,"type":46244}," JWT токени перевіряються",[5197,46416,46418,46420],{"className":46417},[46240],[46242,46419],{"disabled":5312,"type":46244}," CORS налаштований правильно",[5197,46422,46424,46426],{"className":46423},[46240],[46242,46425],{"disabled":5312,"type":46244}," Rate limiting на місці",[5254,46428],{},[5257,46430,46432],{"id":46431},"підсумки-лекції","Підсумки лекції",[5262,46434,46435],{},"У цій лекції ми детально розглянули два фундаментальні архітектурні підходи для побудови професійних серверних застосунків на Go:",[5266,46437,46439],{"id":46438},"middleware-pattern","🔗 Middleware Pattern",[5262,46441,46442],{},[5227,46443,46444],{},"Що вивчили:",[5194,46446,46447,46455,46458,46463,46468],{},[5197,46448,46449,46450,46452,46453],{},"Middleware — це функція, що обгортає ",[5215,46451,7617],{}," і повертає новий ",[5215,46454,7617],{},[5197,46456,46457],{},"Створили власні Middleware з нуля: Logger, Recovery, CORS, RateLimit, Auth",[5197,46459,46460,46461],{},"Навчилися композиції Middleware через функцію ",[5215,46462,11590],{},[5197,46464,46465,46466],{},"Освоїли передачу даних через ",[5215,46467,14764],{},[5197,46469,46470],{},"Інтегрували Middleware з Chi Router для групування маршрутів",[5262,46472,46473,46476],{},[5227,46474,46475],{},"Ключова ідея:"," Middleware дозволяє модульно додавати наскрізну функціональність (логування, автентифікація, захист) без зміни основної бізнес-логіки.",[5266,46478,46480],{"id":46479},"️-clean-architecture","🏛️ Clean Architecture",[5262,46482,46483],{},[5227,46484,46444],{},[5194,46486,46487,46490,46493,46498,46501,46504],{},[5197,46488,46489],{},"Еволюція від монолітного коду до шаруватої архітектури",[5197,46491,46492],{},"Чотири шари: Domain → UseCase → Repository → Delivery",[5197,46494,46495,46496],{},"Dependency Rule: залежності завжди спрямовані ",[5227,46497,20929],{},[5197,46499,46500],{},"Repository Pattern для абстракції доступу до даних",[5197,46502,46503],{},"Ручне Dependency Injection для зв'язування компонентів",[5197,46505,46506],{},"Модульне тестування кожного шару з моками",[5262,46508,46509,46511],{},[5227,46510,46475],{}," Бізнес-логіка не повинна залежати від технічних деталей (HTTP, БД, UI). Це забезпечує тестованість, гнучкість та можливість незалежної зміни технологій.",[5266,46513,46514],{"id":46368},"🧪 Тестування",[5194,46516,46517,46522,46525,46528],{},[5197,46518,46519,46521],{},[5215,46520,5217],{}," для тестування HTTP-обробників без сервера",[5197,46523,46524],{},"Створення мок-реалізацій репозиторіїв та UseCases",[5197,46526,46527],{},"Table-driven tests для покриття різних сценаріїв",[5197,46529,46530],{},"Ізольоване тестування кожного шару",[5254,46532],{},[5186,46534,46535,46561],{},[5189,46536,46539,46543,46554],{"icon":46537,"title":46538},"i-lucide-arrow-right","📖 Наступна лекція",[5262,46540,46541],{},[5227,46542,4029],{},[5262,46544,46545,46546,46549,46550,46553],{},"Ми розглянемо високопродуктивний веб-фреймворк Gin, що надає зручні абстракції поверх стандартного ",[5215,46547,46548],{},"net\u002Fhttp",": маршрутизацію через HttpRouter, центральну сутність ",[5215,46551,46552],{},"gin.Context",", Model Binding, валідацію через теги та автоматичну генерацію Swagger-документації.",[5262,46555,46556],{},[46557,46558,46560],"a",{"href":46559},"\u002Fclient-server-architecture\u002F11.gin-framework-architecture-and-routing","Перейти до лекції →",[5189,46562,46564,46569,46600,46605],{"icon":15,"title":46563},"🔗 Додаткові матеріали",[5262,46565,46566],{},[5227,46567,46568],{},"Документація та статті:",[5194,46570,46571,46579,46586,46593],{},[5197,46572,46573],{},[46557,46574,46578],{"href":46575,"rel":46576},"https:\u002F\u002Fwww.alexedwards.net\u002Fblog\u002Fmaking-and-using-middleware",[46577],"nofollow","Writing Middleware in Go",[5197,46580,46581],{},[46557,46582,46585],{"href":46583,"rel":46584},"https:\u002F\u002Fblog.cleancoder.com\u002Funcle-bob\u002F2012\u002F08\u002F13\u002Fthe-clean-architecture.html",[46577],"The Clean Architecture (Robert C. Martin)",[5197,46587,46588],{},[46557,46589,46592],{"href":46590,"rel":46591},"https:\u002F\u002Fpkg.go.dev\u002Fnet\u002Fhttp\u002Fhttptest",[46577],"Go net\u002Fhttp\u002Fhttptest package",[5197,46594,46595],{},[46557,46596,46599],{"href":46597,"rel":46598},"https:\u002F\u002Fthreedots.tech\u002Fpost\u002Frepository-pattern-in-go\u002F",[46577],"Repository Pattern in Go",[5262,46601,46602],{},[5227,46603,46604],{},"Бібліотеки:",[5194,46606,46607,46615,46623],{},[5197,46608,46609,46614],{},[46557,46610,46613],{"href":46611,"rel":46612},"https:\u002F\u002Fgithub.com\u002Fgo-chi\u002Fchi",[46577],"go-chi\u002Fchi"," — легковаговий роутер з Middleware",[5197,46616,46617,46622],{},[46557,46618,46621],{"href":46619,"rel":46620},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fmock",[46577],"golang\u002Fmock"," — генерація моків",[5197,46624,46625,46630],{},[46557,46626,46629],{"href":46627,"rel":46628},"https:\u002F\u002Fgithub.com\u002Fstretchr\u002Ftestify",[46577],"stretchr\u002Ftestify"," — зручні assertion для тестів",[46632,46633,46634],"style",{},"html pre.shiki code .spJ8K, html code.shiki .spJ8K{--shiki-light:#008000;--shiki-default:#6A9955;--shiki-dark:#6A9955}html pre.shiki code .s8Opu, html code.shiki .s8Opu{--shiki-light:#795E26;--shiki-default:#DCDCAA;--shiki-dark:#DCDCAA}html pre.shiki code .sbdoH, html code.shiki .sbdoH{--shiki-light:#A31515;--shiki-default:#CE9178;--shiki-dark:#CE9178}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .siwwj, html code.shiki .siwwj{--shiki-light:#001080;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html pre.shiki code .s8xlr, html code.shiki .s8xlr{--shiki-light:#AF00DB;--shiki-default:#C586C0;--shiki-dark:#C586C0}html pre.shiki code .sJj4R, html code.shiki .sJj4R{--shiki-light:#098658;--shiki-default:#B5CEA8;--shiki-dark:#B5CEA8}html pre.shiki code .sHH4Y, html code.shiki .sHH4Y{--shiki-light:#000000;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}html pre.shiki code .su1O8, html code.shiki .su1O8{--shiki-light:#0000FF;--shiki-default:#569CD6;--shiki-dark:#569CD6}html pre.shiki code .sN1BT, html code.shiki .sN1BT{--shiki-light:#267F99;--shiki-default:#4EC9B0;--shiki-dark:#4EC9B0}html pre.shiki code .sjcCO, html code.shiki .sjcCO{--shiki-light:#EE0000;--shiki-default:#D7BA7D;--shiki-dark:#D7BA7D}html pre.shiki code .sKtos, html code.shiki .sKtos{--shiki-light:#800000;--shiki-default:#569CD6;--shiki-dark:#569CD6}html pre.shiki code .s0P7L, html code.shiki .s0P7L{--shiki-light:#800000;--shiki-default:#808080;--shiki-dark:#808080}html pre.shiki code .sa4r_, html code.shiki .sa4r_{--shiki-light:#E50000;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html pre.shiki code .su9tN, html code.shiki .su9tN{--shiki-light:#0000FF;--shiki-default:#CE9178;--shiki-dark:#CE9178}html pre.shiki code .sD7JJ, html code.shiki .sD7JJ{--shiki-light:#000000FF;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}html pre.shiki code .s-QsJ, html code.shiki .s-QsJ{--shiki-light:#0070C1;--shiki-default:#4FC1FF;--shiki-dark:#4FC1FF}",{"title":5276,"searchDepth":5290,"depth":5290,"links":46636},[46637,46641,46644,46648,46652,46657,46661,46664,46667,46673,46678,46685,46689,46690,46694,46699,46700,46708,46711,46714,46722,46723,46727,46734,46743,46752,46762,46769,46777],{"id":5259,"depth":5290,"text":5260,"children":46638},[46639,46640],{"id":5268,"depth":5301,"text":5269},{"id":5601,"depth":5301,"text":5602},{"id":5732,"depth":5290,"text":5733,"children":46642},[46643],{"id":5736,"depth":5301,"text":5737},{"id":6965,"depth":5290,"text":6966,"children":46645},[46646,46647],{"id":6969,"depth":5301,"text":6970},{"id":7284,"depth":5301,"text":7285},{"id":7623,"depth":5290,"text":7624,"children":46649},[46650,46651],{"id":7627,"depth":5301,"text":7628},{"id":7907,"depth":5301,"text":7908},{"id":7980,"depth":5290,"text":7981,"children":46653},[46654,46655,46656],{"id":7984,"depth":5301,"text":7985},{"id":8363,"depth":5301,"text":8364},{"id":8610,"depth":5301,"text":8611},{"id":8946,"depth":5290,"text":8947,"children":46658},[46659,46660],{"id":9307,"depth":5301,"text":9308},{"id":10027,"depth":5301,"text":10028},{"id":10462,"depth":5290,"text":10463,"children":46662},[46663],{"id":10794,"depth":5301,"text":10795},{"id":11212,"depth":5290,"text":11213,"children":46665},[46666],{"id":11216,"depth":5301,"text":11217},{"id":11656,"depth":5290,"text":11657,"children":46668},[46669,46670,46671,46672],{"id":11660,"depth":5301,"text":11661},{"id":11692,"depth":5301,"text":11693},{"id":11819,"depth":5301,"text":11820},{"id":12852,"depth":5301,"text":12853},{"id":13553,"depth":5290,"text":13554,"children":46674},[46675,46676,46677],{"id":13557,"depth":5301,"text":13558},{"id":13604,"depth":5301,"text":13605},{"id":14337,"depth":5301,"text":14338},{"id":14728,"depth":5290,"text":14729,"children":46679},[46680,46681,46682,46683,46684],{"id":14732,"depth":5301,"text":14733},{"id":14767,"depth":5301,"text":14768},{"id":14823,"depth":5301,"text":14824},{"id":14938,"depth":5301,"text":14939},{"id":15588,"depth":5301,"text":15589},{"id":16334,"depth":5290,"text":16335,"children":46686},[46687,46688],{"id":16338,"depth":5301,"text":16339},{"id":16363,"depth":5301,"text":16364},{"id":17348,"depth":5290,"text":17349},{"id":19543,"depth":5290,"text":19544,"children":46691},[46692,46693],{"id":19547,"depth":5301,"text":19548},{"id":20010,"depth":5301,"text":20011},{"id":20868,"depth":5290,"text":20869,"children":46695},[46696,46697,46698],{"id":20872,"depth":5301,"text":20873},{"id":20915,"depth":5301,"text":20916},{"id":21156,"depth":5301,"text":21157},{"id":26304,"depth":5290,"text":26305},{"id":27210,"depth":5290,"text":27211,"children":46701},[46702,46703,46705,46706,46707],{"id":27214,"depth":5301,"text":27215},{"id":27247,"depth":5301,"text":46704},"Пакет net\u002Fhttp\u002Fhttptest",{"id":27275,"depth":5301,"text":27276},{"id":28059,"depth":5301,"text":28060},{"id":30252,"depth":5301,"text":30253},{"id":32080,"depth":5290,"text":32081,"children":46709},[46710],{"id":32084,"depth":5301,"text":32085},{"id":32397,"depth":5290,"text":32398,"children":46712},[46713],{"id":32520,"depth":5301,"text":32521},{"id":32573,"depth":5290,"text":32574,"children":46715},[46716,46718,46719,46720,46721],{"id":32577,"depth":5301,"text":46717},"1. Завжди передавайте context.Context першим аргументом",{"id":32704,"depth":5301,"text":32705},{"id":32790,"depth":5301,"text":32791},{"id":33042,"depth":5301,"text":33043},{"id":33230,"depth":5301,"text":33231},{"id":33274,"depth":5290,"text":33275},{"id":33765,"depth":5290,"text":33766,"children":46724},[46725,46726],{"id":33769,"depth":5301,"text":33770},{"id":34141,"depth":5301,"text":34142},{"id":34388,"depth":5290,"text":34389,"children":46728},[46729,46730,46731,46732,46733],{"id":34395,"depth":5301,"text":34396},{"id":34571,"depth":5301,"text":34572},{"id":34853,"depth":5301,"text":34854},{"id":34949,"depth":5301,"text":34950},{"id":35190,"depth":5301,"text":35191},{"id":35268,"depth":5290,"text":35269,"children":46735},[46736,46737,46738,46739,46740,46741,46742],{"id":35272,"depth":5301,"text":35273},{"id":35290,"depth":5301,"text":35291},{"id":35563,"depth":5301,"text":35564},{"id":35897,"depth":5301,"text":35898},{"id":36133,"depth":5301,"text":36134},{"id":36765,"depth":5301,"text":36766},{"id":37679,"depth":5301,"text":37680},{"id":38307,"depth":5290,"text":38308,"children":46744},[46745,46746,46747,46748,46749,46750,46751],{"id":38311,"depth":5301,"text":38312},{"id":38318,"depth":5301,"text":38319},{"id":38678,"depth":5301,"text":38679},{"id":39049,"depth":5301,"text":39050},{"id":39606,"depth":5301,"text":39607},{"id":39980,"depth":5301,"text":39981},{"id":40506,"depth":5301,"text":40507},{"id":41215,"depth":5290,"text":41216,"children":46753},[46754,46755,46756,46757,46758,46759,46760,46761],{"id":41219,"depth":5301,"text":41220},{"id":41535,"depth":5301,"text":41536},{"id":41789,"depth":5301,"text":41790},{"id":42034,"depth":5301,"text":42035},{"id":42242,"depth":5301,"text":42243},{"id":42559,"depth":5301,"text":42560},{"id":42668,"depth":5301,"text":42669},{"id":42780,"depth":5301,"text":42781},{"id":42976,"depth":5290,"text":42977,"children":46763},[46764,46765,46766,46767,46768],{"id":42980,"depth":5301,"text":42981},{"id":43529,"depth":5301,"text":43530},{"id":44255,"depth":5301,"text":44256},{"id":44663,"depth":5301,"text":44664},{"id":45575,"depth":5301,"text":45576},{"id":46223,"depth":5290,"text":46224,"children":46770},[46771,46772,46773,46774,46775,46776],{"id":8768,"depth":5301,"text":7633},{"id":46272,"depth":5301,"text":20878},{"id":15379,"depth":5301,"text":15393},{"id":5952,"depth":5301,"text":11719},{"id":46368,"depth":5301,"text":968},{"id":46398,"depth":5301,"text":46399},{"id":46431,"depth":5290,"text":46432,"children":46778},[46779,46780,46781],{"id":46438,"depth":5301,"text":46439},{"id":46479,"depth":5301,"text":46480},{"id":46368,"depth":5301,"text":46514},"Патерн Middleware у Go, принципи Clean Architecture, впровадження залежностей та модульне тестування HTTP-шару","md",null,{"date":46786,"keywords":46787,"section":46790,"prev":46791,"next":46559},"2026-09-01",[7633,20878,5209,2441,46788,11719,11646,46789],"Go","Gin","Модуль 2. Бекенд-архітектура, фреймворк Gin, бази даних та розподілені комунікації","\u002Fclient-server-architecture\u002F09.rest-api-design-and-chi-routing",{"title":4025,"description":46782},"fs5KGDAQp7vSkcZVO47GawFJ06QmVHyXDm-twZ7aD18",[46795,46797],{"title":4021,"path":4022,"stem":4023,"description":46796,"children":-1},"Еволюція від RPC до HTTP API, архітектурний стиль REST, мотивація та обмеження, складові HTTP-запитів (URL, заголовки, методи, статус-коди), проєктування номенклатури URL, CRUD-операції, маршрутизатор go-chi\u002Fchi з підтримкою Radix Tree, параметри шляху, суб-роутери, Middleware та побудова REST API у Go з дотриманням принципів Clean Architecture.",{"title":4029,"path":4030,"stem":4031,"description":46798,"children":-1},"Знайомство з вебфреймворком Gin: архітектура, двигун Engine, контекст запиту, маршрутизація та middleware",1791986745984]