Разработка Web-приложений
_
ПОЛНОЕ ЗАДАНИЕ В ДЕМО ФАЙЛЕ,
ЧАСТЬ ДЛЯ ПОИСКА ДУБЛИРУЮ НИЖЕ
Настройка среды
Цель работы● Установить IDE
● Развернуть проект на Spring Boot
● Посмотреть на результат выполнения лабораторных работ
Дополнительные материалыТеоретический материал по установке IDEДля работы будет использоваться IntelliJ IDEA. IntelliJ IDEA — это интеллектуальная IDE, учитывающая контекст. Она предназначена для разработки разнообразных приложений на Java и других языках JVM, например Kotlin, Scala и Groovy. Кроме того, IntelliJ IDEA Ultimate поможет в разработке веб-приложений: она предлагает эффективные встроенные инструменты, поддержку JavaScript и связанных с ним технологий, а также расширенную поддержку таких популярных фреймворков, как Spring, Spring Boot, Jakarta EE, Micronaut, Quarkus и Helidon. А бесплатные плагины, разработанные JetBrains, позволяют дополнительно расширить возможности IntelliJ IDEA и использовать ее для работы с другими языками программирования, в том числе Go, Python, SQL, Ruby и PHP.
IntelliJ IDEA продумана в каждом аспекте и готова к использованию сразу после установки. Среда обеспечивает быстрый доступ ко всем функциям и встроенным инструментам, необходимым разработчику, а также широкие возможности индивидуальной настройки. Вы можете полностью настроить среду в соответствии со своим рабочим процессом: задать сочетания клавиш, установить плагины, настроить интерфейс по своему усмотрению и т. д.
Рис.1 - Окно создания проекта
Даже если вы впервые работаете с IDE, ее запуск и создание первого проекта не займут много времени. В новом мастере создания проектов можно указать тип проекта (рис. 1), версию Java и поддерживаемые инструменты сборки (например, Maven или Gradle). Все остальные настройки IntelliJ IDEA установит самостоятельно. Таким образом, вы сможете начать работу через считанные мгновения после запуска IntelliJ IDEA. Кроме того, вы можете открывать проекты, импортировать существующие проекты Maven или Gradle, а также извлекать проекты из систем контроля версий. В IntelliJ IDEA есть специальные сочетания клавиш практически для любых действий — от просмотра недавних файлов до запуска и отладки проекта. Одно из универсальных сочетаний — это двойной Shift (Search Everywhere). Эта функция позволяет найти любые объекты в проекте или за его пределами.
Рис. 2 - Сочетания клавиш
Диапазон поиска может включать все от файлов, действий, классов и символов до настроек, элементов интерфейса и истории из Git. IntelliJ IDEA предлагает различные специальные возможности, которые могут вам понадобиться (рис. 2). Например, с IntelliJ IDEA совместимы программы чтения с экрана. Можно также настроить цвета разных элементов интерфейса, добавить контрастности полосам прокрутки, изменить размер окон и шрифта в редакторе и т. п.
Рис. 3 - Плагины
Если вы хотите дополнить базовую функциональность IDE, это можно сделать с помощью плагинов для IntelliJ IDEA (рис. 3). Среда поддерживает обширную экосистему плагинов, позволяющих решить практически все задачи, которые могут встать перед разработчиком.
Рис. 4 - Инспекции и контекстные действия
Первоначальная индексация исходного кода позволяет IDE создать виртуальную карту проекта. Используя информацию виртуальной карты, она мгновенно обнаруживает ошибки, предлагает варианты автодополнения кода с учетом контекста, выполняет рефакторинг и т. д. IntelliJ IDEA обладает широкими возможностями проверки качества и валидности кода с помощью инспекций, которые выполняются «на лету». Инспекции помогают быстрее писать код в соответствии с самыми строгими стандартами качества и чувствовать себя уверенно на протяжении всего процесса разработки. Предлагается набор стандартных инспекций и десятки инспекций для отдельных фреймворков (рис. 4). Они позволяют выявить самые разные проблемы: например, ошибки при автоматическом связывании бинов Spring и т. п. Если IntelliJ IDEA обнаруживает ошибки, она предлагает устранить их с помощью контекстных действий, включающих в себя быстрые исправления подсвеченных ошибок и intention-действия для изменения кода, если ошибки незначительные. Чтобы вызвать контекстное действие, нажмите значок лампочки или сочетание клавиш Alt+Enter.
Рис. 5 - Умное автодополнение кода
Функция автодополнения кода (рис. 5) в IntelliJ IDEA работает с учетом контекста, предлагая только варианты, действительные для текущего положения курсора. Вам не нужны никакие сочетания клавиш и дополнительные настройки: функция автодополнения запускается, как только вы начинаете писать код в редакторе.
Практическая работа по установке IDE
1. Для начала нужно установить IntelliJ IDEA.
2. В целях практической работы будет достаточно обычной версии Community, но можно скачать и полную Ultimate. Отличие заключается в том, что Community версия бесплатна, но многие функции урезаны, а Ultimate имеет полный функционал для разработки приложений. Ultimate можно получить бесплатно, для этого нужно на сайте оставить заявку с требуемыми данными, но ее можно использовать только для обучения.
3. После установки IDEA требуется запустить ее.
4. Выбираем New Project.
JDK: https://www.oracle.com/java/technologies/javase/jdk15-archive-downloads.htm
5. В левом окне выбираем пункт Kotlin. После потребуется установить комплект ПО JDK(Java Development Kit). Для этого можно воспользоваться сайтом Oracle, где и лежат все версии. Версия может быть любая. В качестве примера будет использована 11(Java Archive Downloads - Java SE 11).
6. Далее кликаем на Project JDK и выбираем Add JDK. После этого выбираем скаченную версию.
7. Либо можно скачать любую версию JDK прям с этого же места. Нужно выбрать пункт Download, а затем уже и версию.
8. После выбираем название и место размещения проекта. После этого нажимаем кнопку далее во всех открывшихся окнах.
9. Открывается проект в котором нужно создать первый котлин файл.
10. Кликаем пкм по директории Kotlin и выбираем Kotlin Class/File
11. Пишем название файла, выбираем тип File
12. Прописываем main и IDEA подсвечивает нужный шаблон. Затем выбираем первый из предложенных
13. Теперь прописываем вывод строки и запускаем созданный код. Запустить код можно нажатием на зеленую кнопку run.
14. В консоли должна появиться прописанная строка. И в последней строчке значения процесса должно быть 0, это означает, что все прошло успешно.
Теоретический и практический материал по развертыванию проекта Spring BootSpring — один из самых известных веб-фреймворков для веб-разработчиков. Многие разработчики выбирают Spring, потому что он мощный и поддерживает внедрение зависимостей и аспектно-ориентированное программирование. Однако некоторым разработчикам не нравится среда Spring, потому что это веб-инфраструктура Java, раздутая множеством файлов конфигурации XML.
Компания Pivotal создала Spring Boot, проект, созданный на основе среды Spring, чтобы ответить на эту критику. Это самоуверенно и предполагает, как вы разрабатываете веб-приложения. Spring Boot также использует автоматическую настройку для настройки вашего веб-приложения.
Другими словами, вам больше не нужно писать множество XML-файлов конфигурации. К счастью, Spring Boot также поддерживает Kotlin и Gradle, что означает, что вам не нужно использовать Java и писать XML-файлы. Ага! :]
Сейчас мы посмотрим как инициализировать проект на Spring Boot.
Представьте
Вы получили новую работу в качестве веб-разработчика в стартапе который работает над созданием банковского сервиса. Ваш генеральный директор просит вас создать веб приложение, суть которого заключается в том, что вы добавляете / удаляете / изменяете популярные банки. Она хочет перейти на мобильные устройства, поэтому вам нужно создать конечные точки API.
Вашим коллегам нужен REST API для создания приложений. Вы выбираете Spring Boot, потому что вы прочитали это руководство. :]
Чтобы создать новый проект Spring Boot, перейдите на Spring Initializr, веб-сайт для инициализации вашего проекта Spring Boot:
Выберите проект Gradle в Project. Затем выберите Kotlin в качестве языка. Версия по умолчанию в Spring Boot будет работать нормально.
В разделе «Метаданные проекта» установите com.raywenderlich в группе. Затем заполните nftmarketplace в Artifact и Name. Введите «Рынок NFT» в поле «Описание».
Выберите название для имени пакета. Оставьте значения по умолчанию для упаковки и Java.
Затем нажмите «Добавить» в разделе «Зависимости»:
На веб-сайте будет представлено модальное диалоговое окно, в котором вы можете искать любые зависимости Spring. Например, если вам нужна база данных, здесь вы должны выбрать соответствующий компонент.
Выберите Spring Web, так как он поставляется с веб-сервером, на котором вы будете развертывать веб-приложение:
Помимо Spring Web, вам нужно выбрать следующие зависимости:
Внимательно прочитайте про каждый пакет, потому что в дальнейшем мы будем с ними работать.
Нажмите «Создать» внизу страницы. Ваш браузер загрузит проект в виде zip-файла:
Загрузите файл и распакуйте его. Это каталог вашего стартового проекта.
Для работы над этим проектом вам понадобится IntelliJ IDEA. Вы можете узнать, как установить и настроить его на официальном сайте IntelliJ IDEA, либо в самом начале данной лабораторной работы. После настройки перейдите к следующему разделу.
Запуск проекта
Запустите IntelliJ IDEA и откройте стартовый проект. Gradle начнет синхронизацию проекта, что может занять пару минут.
В вашем проекте Spring Boot вы найдете код веб-приложения в папке src. Вы напишете код Kotlin в папке main/kotlin.
Ваш проект будет выглядеть примерно так:
Прежде чем вы начнете писать код для своего веб-приложения, откройте build.gradle.kts. Это ваш файл сборки Spring Boot Gradle, в котором вы будете устанавливать зависимости веб-приложения и изменять конфигурацию проекта:
В файле вы найдете плагин Spring Boot внутри блока плагинов, а также версию Java для этого проекта. Зависимость org.springframework.boot:spring-boot-starter-web находится внутри блока зависимостей. Помните, что вы добавили зависимости Spring в Spring Initializr.
Для того чтобы запустить проект, просто нажмите на старт в правом верхнем углу:
Примечание: если не запускается по причине того то проект будет требовать коннект к СУБД (Spring Data JPA), то:
1) Удаляем Spring Data Jpa;
2) Идём[1] и настраиваем коннект к базе;
Результат выполнения всех лабораторных работПосле того как вы выполните все лабораторные работы, вы получите следующий результат:
Вы напишите API, при помощи которого вы сможете реализовать банковское приложение с регистрацией, авторизацией с использованием JWT, а также базой данных.
Также, помимо всего прочего вы сможете поработать с различными запросами POST, GET, PATCH, DELETE:
У нас будет возможность управлять “популярными” банками :]
О том, как все это сделать, вы узнаете в следующих лабораторных работах.
Примечание: на снимках экрана выше вы можете видеть web представление того, что мы будем реализовывать. В дальнейшем вы поймете о чем идет речь, фреймворк для написания веб части вы можете выбрать самостоятельно, либо реализовать всю эту красоту на html. 1.0. Да пребудет с вами Сила.
ИЛИ
_
Основы HTTP, REST, JSON
Разработка через тестирование
Цель работы● Ознакомиться с HTTP запросами
● Поработать с REST API и JSON
● Ознакомиться с разработкой через тестирование (TDD)
Postman
Основное предназначение приложения — создание коллекций с запросами к вашему API. Любой разработчик или тестировщик, открыв коллекцию, сможет с лёгкостью разобраться в работе вашего сервиса. Ко всему прочему, Postman позволяет проектировать дизайн API и создавать на его основе Mock-сервер. Разработчикам больше нет необходимости тратить время на создание "заглушек". Реализацию сервера и клиента можно запустить одновременно. Тестировщики могут писать тесты и производить автоматизированное тестирование прямо из Postman. А инструменты для автоматического документирования по описаниям из ваших коллекций сэкономят время на ещё одну "полезную фичу". Есть кое-что и для администраторов — авторы предусмотрели возможность создания коллекций для мониторинга сервисов.
Введение
Рис.1 - Окно постмана
Главные понятия, которыми оперирует Postman (рис. 1) это Collection (коллекция) на верхнем уровне, и Request (запрос) на нижнем. Вся работа начинается с коллекции и сводится к описанию вашего API с помощью запросов. Давайте рассмотрим подробнее всё по порядку.
Коллекция — отправная точка для нового API. Можно рассматривать коллекцию, как файл проекта. Коллекция объединяет в себе все связанные запросы. Обычно API описывается в одной коллекции, но если вы желаете, то нет никаких ограничений сделать по-другому. Коллекция может иметь свои скрипты и переменные, которые мы рассмотрим позже.
Папка — используется для объединения запросов в одну группу внутри коллекции. К примеру, вы можете создать папку для первой версии своего API — "v1", а внутри сгруппировать запросы по смыслу выполняемых действий — "Order & Checkout", "User profile" и т. п. Всё ограничивается лишь вашей фантазией и потребностями. Папка, как и коллекция может иметь свои скрипты, но не переменные.
Запрос (рис. 1) — основная составляющая коллекции, то ради чего все и затевалось. Запрос создается в конструкторе. Конструктор запросов это главное пространство, с которым вам придётся работать. Postman умеет выполнять запросы с помощью всех стандартных HTTP методов, все параметры запроса под вашим контролем. С легкостью можно поменять или добавить необходимые вам заголовки, cookie, и тело запроса. У запроса есть свои скрипты. Вкладки "Pre-request Script" и "Tests" среди параметров запроса позволяют добавить скрипты перед выполнением запроса и после. Именно эти две возможности делают Postman мощным инструментом помогающим при разработке и тестировании.
Рис.2 - Запрос
В предыдущей лабораторной работе мы говорили про банковское приложение. Еще не забыли? А вот что мы получим в Postman (Рис.3):
Рис.3 - что получим в конце
Работа с Postman
1. Для того чтобы начать использовать Postman необходимо создать аккаунт в Postman.
2. Наиболее удобным способом является авторизоваться через аккаунт Google. Для этого необходимо выбрать Sign Up with Google > Ввести учетные данные от аккаунта Google > Подтвердить вход. После чего откроется главное окно программы.
3. Теперь создадим запрос. Для этого нужно сделать:
a. Выбрать тип запроса GET
b. В поле “Enter request URL” ввести URL метода, например: https://kitten.whiskas.ru/api/article/index
c. Нажать кнопку “Send”
4. Если все прошло успешно, то с сервера приходит ответ и Статус 200 ОК. Для просмотра результата необходимо перейти во вкладку BODY и выбрать формат отображаемых данных > JSON.
5. Для сохранения запроса необходимо нажать кнопку SAVE.
6. В открывшемся окне даем название нашему запросу, например “Catpedia”.
7. В поле Request description (необязательное поле) можно добавить описание запроса.
8. Если мы создаем несколько запросов для одного проекта/домена, то лучше создать коллекцию, для этого необходимо ввести имя в поле Collection Name, например Whiskas.
9. Все запросы и коллекции отображаются во вкладке Collections.
10. Теперь создадим POST запрос. Для создания нового POST необходимо нажать на “плюсик”.
11. Обычно вместе с POST запросом мобильное приложение передает некоторые параметры серверу. Для того чтобы сервер вернул нам правильное значение метода, введем данные которые вместе с запросом будут отправляться на сервер. Для этого необходимо:
a. Выбрать метод POST.
b. Ввести URL запроса, например: https://kitten.whiskas.ru/api/account/login .
c. Во вкладке headers ввести:
Key = “Content-Type”
Value = “aplication/json”
d. Для того чтобы вводить данные необходимо знать какие данные передает приложение серверу.
12. Откроем вкладку Body (под адресной строкой) и выберем тип данных: form-data. Введем параметры ключ - значения.
13. Также можно отправлять серверу “сырые данные” т.е. текст в формате JSON. Для этого необходимо во вкладке Body(под адресной строкой) и выбрать тип данных: raw и ввести текст в формате JSON.
14. Нажмем кнопку “Send”
15. Для сохранения запроса необходимо выбрать в выпадающем списке рядом с кнопкой Save > Save As.
Теперь, когда вы понимаете основы HTTP, REST, JSON на базовом уровне мы приступим к более сложным вещам.
Разработка через тестирование (TDD) — это подход к разработке программного обеспечения, при котором тестовые примеры разрабатываются для определения и проверки того, что будет делать код. Проще говоря, сначала создаются и тестируются тестовые случаи для каждой функциональности, и если тест не проходит, то пишется новый код, чтобы пройти тест и сделать код простым и безошибочным.
Разработка через тестирование начинается с проектирования и разработки тестов для каждой небольшой функциональности приложения. Инфраструктура TDD инструктирует разработчиков писать новый код только в том случае, если автоматизированный тест не пройден. Это позволяет избежать дублирования кода. Полная форма TDD — разработка через тестирование.
Простая концепция TDD заключается в написании и исправлении неудачных тестов перед написанием нового кода (перед разработкой). Это помогает избежать дублирования кода, поскольку мы пишем небольшой объем кода за раз, чтобы пройти тесты. (Тесты — это не что иное, как условия требований, которые нам нужно проверить, чтобы выполнить их).
Разработка через тестирование — это процесс разработки и запуска автоматизированного теста перед фактической разработкой приложения. Следовательно, TDD иногда также называют Test First Development.
Пять шагов разработки через тестирование
Некоторые пояснения по TDD: подход TDD не относится ни к «Тестированию», ни к «Дизайну». TDD не означает «написать несколько тестов, а затем построить систему, которая проходит тесты». TDD не означает «выполнять много тестов».
Ниже приведены основные различия между разработкой через тестирование и традиционным тестированием:
● Подход TDD — это прежде всего метод спецификации. Это гарантирует, что ваш исходный код тщательно протестирован на подтвержденном уровне.
● При традиционном тестировании успешный тест обнаруживает один или несколько дефектов. Это то же самое, что и TDD. Когда тест терпит неудачу, вы добились прогресса, потому что знаете, что вам нужно решить проблему.
● TDD гарантирует, что ваша система действительно соответствует определенным для нее требованиям. Это помогает укрепить вашу уверенность в вашей системе.
● В TDD больше внимания уделяется производственному коду, который проверяет, будет ли тестирование работать правильно. В традиционном тестировании больше внимания уделяется дизайну тестовых случаев. Покажет ли тест правильное/неправильное выполнение приложения для выполнения требований.
● В TDD вы приближаетесь к 100%-му тест покрытию. Каждая строка кода может быть протестирована, в отличие от традиционного тестирования.
● Сочетание традиционного тестирования и TDD приводит к важности тестирования системы, а не совершенствования системы.
● В Agile Modeling (AM) вы должны «тестировать с целью». Вы должны знать, почему вы что-то тестируете и на каком уровне это нужно тестировать.
Есть два уровня TDD
1. Приемочный TDD (ATDD): с помощью ATDD вы пишете один приемочный тест. Этот тест выполняет требование спецификации или удовлетворяет поведению системы. После этого напишите достаточно производственного и/или функционального кода, чтобы выполнить этот приемочный тест. Приемочное тестирование фокусируется на общем поведении системы. ATDD также был известен как развитие, управляемое поведением (BDD).
2. Developer TDD: с помощью Developer TDD вы пишете тест для одного разработчика, т. е. модульный тест, а затем достаточно производственного кода для выполнения этого теста. Модульный тест фокусируется на каждой небольшой функции системы. Разработчик TDD просто называется TDD. Основная цель ATDD и TDD — указать подробные исполняемые требования для вашего решения на основе «точно в срок» (JIT). JIT означает принятие во внимание только тех требований, которые необходимы в системе. Так что повышайте эффективность.
Вернемся к нашей предыдущей лабораторной работе. Там вы создали ваше первое приложение на Spring Boot, сегодня мы его запустим. Просто нажмите на зеленый флажок в правом верхнем углу вашей IDE и посмотрите в консоль, там вы должны увидеть примерно следующее:
Обратите внимание на порт, он нам пригодится.
В браузере вы увидите примерно такой результат:
Добавим файл index.html как на рисунке ниже, и напишем там “Hello, Spring Boot!”:
Вы увидите Hello Spring Boot в браузере теперь:
Теперь добавим контроллер, и добавим следующие сервисы, внутри данного контроллера создадим функцию с REST endpoint-м:
Проверим результат работы:
Поменяем порт сервера на 9000:
Теперь Tomcat будет запускаться на порте 9000:
Перейдем и создадим модель под названием Bank (это Kotlin Class):
Это будет data class со следующими свойствами:
Описание свойств:
accountNumber - банковский ID
trust - уровень доверия к банку (от 0 до 100)
transactionFee - комиссия за перевод
Теперь перейдем к созданию data source. Создадим package с названием datasource и создадим интерфейс BankDataSource:
Объявим в нем метод getBanks, это будет выглядеть следующим образом:
Создадим в datasource package mock, и добавим в него новый класс MockBankDataSource:
Внутри этого класса выполним наследование от интерфейса BankDataSource и используем shortcut (Ctrl + I), для того чтобы добавить реализовать методы, а именно getBanks():
Нажмем Ctrl +Shift + T на данном этапе (чтобы использовать shortcut для создания тестов), после чего вы получите следующий класс:
Добавим шаблон для наших тестов внутри этого файла. Перейдем в настройки IDE, напишем в поиске live, выберем Live Templates и язык программирования Kotlin (разворачиваем через стрелку слева):
Нажимаем добавить на кнопку + и выберите Live Templates:
И выберите где применить данный шаблон (Kotlin Class):
И в качестве шаблона будем использовать следующее (в предыдущем скрине видно как можно написать этот шаблон):
Зачем нужен этот шаблон? Пройдемся по структуре:
fun `should сделать что-то`() {
// given (имея в наличии следующее:)
// when (при условии, когда:)
// then (тогда делаем какое-то действие:)
}
Тест будет выглядеть следующим образом:
Если не работает assertThan (осуществляет проверку чего-либо, переведите на русский название и поймете о чем речь. Замечаете, что код в данном тесте читается на английском? Роберт Мартин говорит, если код читается как книга, значит код как минимум приближается к чистому, вспоминаем практики), то проверьте зависимости:
Запустим тест, и увидим ошибку (не пугайтесь, это нормально):
Тест нам говорит, что нам нужно представить коллекцию банков. Сначала мы написали тест, и увидели ошибку, которая подсказала нам это. Перейдем в класс MockBankDataSource и реализуем метод getBanks(), вернув например пустой список:
Тест запустился, но на этот раз с предупреждением, а не с ошибкой. Предупреждение по прежнему требует от нас коллекцию банков и говорит что коллекция банков не должна быть пустой:
Наполним нашу коллекцию одним элементом, со случайными данными, при помощи скрина ниже:
Запустим тест и увидим его успешное выполнение:
Проведем небольшой рефакторинг используя синтаксический сахар Kotlin:
Напишем тест для моковых данных:
Заметили разницу? До этого мы написали код при помощи компилятора (через TDD), а сейчас мы написали его при помощи наших знаний и написали тест, к тому что мы сделали. Как вам TDD? 🙂
Чтобы узнать, что делает allSatisfy воспользуйтесь гуглом, либо провалитесь в данный метод и прочитайте описательные комментарии над этим методом.
Давайте еще поиграемся с тестами, и изменим allSatisfy на allMatch, и запустим тест:
Тест говорит нам о том, что не все условия выполняются. Соответственно, нам нужно перейти и подправить некорректности. Просто заполните accountNumber некоторыми значениями, и заново запустите тест:
Результат выполнения теста:
Добавим в тест еще пару проверок, чтобы элементы коллекции banks не имели значений, которые не удовлетворяют условиям:
Чем отличается allMatch от anyMatch? Вопрос на засыпку, на него можно ответить на него в комментариях в лабораторной работе (GitHub, либо в ИнфОУПро).
И сразу поменяем данные в моки аналогично предыдущему разу.
Добавим в предыдущий тест еще 1-у проверку:
Спойлер: тест снова будет с предупреждением.
Примечание:
кстати, проверку на isNotEmpty нужно удалить, так как мы добавили новое условие, которое покрывает старое (следите за логикой, я буду проверять это).
Наполним коллекцию banks данными:
Переименуем метод (рефакторим):
Отдадим пользователю моковую коллекцию banks (в дальнейшем подключим БД):
Перейдем к написанию сервисов
Создадим package service, внутри него класс BankService:
Создадим для него тест, как мы это делали раньше (при помощи shortcut).
Так как мы добавили шаблон теста, то вам достаточно просто написать ключевое слово test и у вас автоматом подтянется ваш шаблон:
Напишем тестик:
Из названия теста, догадайтесь что будет он будет делать.
Перейдем в BankService() и добавим в него поле BankDataSource:
Добавим зависимость если у вас такой нет:
Перейдем и отдадим объект mokk() полю dataSource и BankService отдадим наш dataSource:
В BankService реализуем функцию getBanks():
А теперь используем метод verify в нашем тесте:
Запустим тест и получим предупреждение:
В ошибке говорится что:
Значит emptyList() нужно заменить, перейдем и сделаем это:
Посмотрите что делает every и попробуйте понять что происходит в блоке given:
Запустим тест и глянем на результат выполнения:
Укажем параметр relaxed = true (что он делает?) и уберем переменную оставив просто вызов getBanks() (почему мы так сделали?):
Отрефакторим метод getBanks() в сервисе в соответствии с синтаксисом Kotlin:
[1]
Web Layer
Создадим package controller, и добавим в него новый класс BankController:
Напишите @RestController над классом, для того чтобы дать понять Spring-у что это rest controller:
Вам нужно добавить тест как мы делали это ранее через shortcut и напишем над классом @SpringBootTest:
Далее добавим @AutoConfigureMockMvc, а также @Autowired поле mockMvc, и вставим тестовый шаблон:
И поменяем немного структуру нашего шаблона. Теперь он будет состоять из блока when / then, для того чтобы не отделять объект от метода:
Таким образом получаем тест для api/banks.
Запустим тест, и посмотрим что получится:
Посмотрим на логи и увидим проблему:
Перейдем в контроллер, добавим приватное поле service: BankService, над классом добавим @RequestMapping(“api/banks”), напишем метод getBanks() который возвращает getBanks() из сервиса (пометим метод @GetMapping):
Запустим тест и увидим что результат выполнения успешен.
Поиграемся с тестами, и изменим getBanks() в контроллере:
Перейдем к нашему тесту для этого класса и сделаем проверку на значение accountNumber:
Ожидаемо, тест не прошел, потому что getBanks() возвращает String:
Вернем обратно service.getBanks():
Запустим тест и увидим что он говори что значение на которое мы проверяем не является актуальным:
Укажем актуальное значение:
Запустим и перейдем по адресу localhost:9000/api/bank и в результате получим следующее:
Проверим API в Postman:
Поздравляю! Вы написали свой первый get запрос, применяя TDD.
Практические задания HTTP, REST, JSON
1. Нужно воспользоваться несколькими сайтами с публичным API и основными методами, которые были изучены на лекции. Можно воспользоваться данным перечнем:
●
https://github.com/public-apis/public-apis
●
●
2. Самостоятельно создать класс для сериализация и десериализация JSON. После создания класса создать тесты и с помощью них провести проверку на сериализация и десериализацию. Для это лучше использовать Gradle проект.
3. Материал с примерами и различными аннотациями:
https://www.baeldung.com/jackson
1. Реализуйте GET запрос на получение конкретного банка по ID:
2. Реализуйте POST запрос на добавление банка в общий список:
3. Реализуйте PATCH запрос для замены значений в объекте банка:
4. Реализуйте DELETE запрос для удаления банка по ID:
Примечание: если такого значения нет, то вам нужно обработать этот вариант, это касается и всех предыдущих запросов.
12 шакалов из 10.
Можно заменить на скриншоты хорошего качества?Работа с БД и авторизацией
Цель работыПоработать с:
● базами данных;
● авторизацией (JWT).
То, что мы делали в предыдущих лабораторных, являлось шаблоном, для нашей текущей работы. Вы с легкостью сможете переделать предыдущую заготовку под любую тему которую вы захотите выбрать. Список тем (идеи проектов) под практическими заданиями.
Базы данных
База данных — это упорядоченный набор структурированной информации или данных, которые обычно хранятся в электронном виде в компьютерной системе, содержащих, например такую информацию как:
● транзакции продаж;
● данные о клиентах;
● финансовые показатели;
● информацию о продуктах.
Базы данных используются для хранения, обслуживания и доступа к любым данным. Они собирают информацию о людях, местах или вещах. Эта информация собирается в одном месте, чтобы ее можно было наблюдать и анализировать. Базы данных можно рассматривать как организованный набор информации.
Для чего используются базы данных?
Компании используют данные, хранящиеся в базах данных, для принятия обоснованных бизнес-решений. Некоторые из способов, которыми организации используют базы данных, включают следующее:
● Улучшить бизнес-процессы. Компании собирают данные о бизнес-процессах, таких, как продажи, обработка заказов и обслуживание клиентов. Они анализируют эти данные, чтобы улучшить эти процессы, расширить свой бизнес и увеличить доход.
● Следить за клиентами. Базы данных часто хранят информацию о людях, таких как клиенты или пользователи. Например, платформы социальных сетей используют базы данных для хранения информации о пользователях, такой как имена, адреса электронной почты и поведение пользователей. Данные используются для рекомендации контента пользователям и улучшения взаимодействия с пользователем
● Защитите личной информации о здоровье. Поставщики медицинских услуг используют базы данных для безопасного хранения личных данных о здоровье, чтобы информировать и улучшать уход за пациентами.
● Хранение личных данных. Базы данных также могут использоваться для хранения личной информации. Например, личное облачное хранилище доступно для отдельных пользователей для хранения мультимедиа, например фотографий, в управляемом облаке.
Эволюция баз данных
Базы данных были впервые созданы в 1960-х годах. Эти ранние базы данных представляли собой сетевые модели, в которых каждая запись связана со многими первичными и вторичными записями. Иерархические базы данных также были среди ранних моделей. У них есть древовидные схемы с корневым каталогом записей, связанным с несколькими подкаталогами.
Реляционные базы данных были разработаны в 1970-х годах. Затем в 1980-х годах появились объектно-ориентированные базы данных. Сегодня мы используем язык структурированных запросов (SQL), NoSQL и облачные базы данных.
Э. Ф. Кодд создал реляционную базу данных, работая в IBM. Он стал стандартом для систем баз данных благодаря своей логической схеме или способу организации. Использование логической схемы отделяет реляционную базу данных от физического хранилища.
Реляционная база данных в сочетании с ростом Интернета, начавшимся в середине 1990-х годов, привела к быстрому распространению баз данных. Многие бизнес-приложения и потребительские приложения полагаются на базы данных.
Компоненты базы данных
Хотя различные типы баз данных различаются по схеме, структуре данных и наиболее подходящим для них типам данных, все они состоят из одних и тех же пяти основных компонентов:
1. Аппаратное обеспечение. Это физическое устройство, на котором работает программное обеспечение базы данных. Аппаратное обеспечение базы данных включает компьютеры, серверы и жесткие диски.
2. Программное обеспечение. Программное обеспечение или приложение базы данных дает пользователям возможность управлять базой данных. Программное обеспечение системы управления базами данных (СУБД) используется для управления и контроля баз данных.
3. Данные. Это необработанная информация, которую хранит база данных. Администраторы базы данных организуют данные, чтобы сделать их более значимыми.
4. Язык доступа к данным. Это язык программирования, управляющий базой данных. Язык программирования и СУБД должны работать вместе. Одним из наиболее распространенных языков баз данных является SQL.
5. Процедуры. Эти правила определяют, как работает база данных и как она обрабатывает данные.
Какие бывают проблемы с базами данных?
Настройка, эксплуатация и обслуживание базы данных связаны с некоторыми общими проблемами, такими как следующие:
1. Безопасность данных необходима, поскольку данные являются ценным бизнес-активом. Для защиты хранилищ данных требуется квалифицированный персонал по кибербезопасности, что может быть дорогостоящим.
2. Целостность данных гарантирует, что данные заслуживают доверия. Добиться целостности данных не всегда просто, потому что это означает, что доступ к базам данных должен быть предоставлен только тем, кто имеет право работать с ними.
3. Производительность базы данных требует регулярного обновления и обслуживания базы данных. Без надлежащей поддержки функциональность базы данных может снизиться по мере изменения технологии, поддерживающей базу данных, или данных, которые она содержит.
4. Интеграция с базой данных также может быть затруднена. Это может включать интеграцию источников данных из различных типов баз данных и структур в единую базу данных или в озера данных и хранилища данных.
Что такое система управления базами данных?
СУБД позволяет пользователям создавать и управлять базой данных. Он также помогает пользователям создавать, читать, обновлять и удалять данные в базе данных, а также помогает с функциями ведения журнала и аудита.
СУБД обеспечивает физическую и логическую независимость от данных. Пользователям и приложениям не нужно знать ни физическое, ни логическое расположение данных. СУБД также может ограничивать и контролировать доступ к базе данных и предоставлять различные представления одной и той же схемы базы данных нескольким пользователям.
Авторизация
Что такое веб-токен JSON?
JSON Web Token (JWT) — это открытый стандарт (RFC 7519), определяющий компактный и автономный способ безопасной передачи информации между сторонами в виде объекта JSON. Эту информацию можно проверить и доверять ей, поскольку она имеет цифровую подпись. JWT могут быть подписаны с использованием секрета (с помощью алгоритма HMAC) или пары открытого/закрытого ключа с использованием RSA или ECDSA.
Хотя JWT могут быть зашифрованы для обеспечения секретности между сторонами, мы сосредоточимся на подписанных токенах. Подписанные токены могут проверять целостность содержащихся в них утверждений, в то время как зашифрованные токены скрывают эти утверждения от других сторон. Когда токены подписываются с использованием пар открытого и закрытого ключей, подпись также удостоверяет, что только сторона, владеющая закрытым ключом, является той, кто подписал его.
Когда следует использовать веб-токены JSON?
Вот несколько сценариев, в которых веб-токены JSON полезны:
Авторизация: это наиболее распространенный сценарий использования JWT. Как только пользователь войдет в систему, каждый последующий запрос будет включать JWT, позволяя пользователю получать доступ к маршрутам, службам и ресурсам, разрешенным с помощью этого токена. Единый вход — это функция, которая в настоящее время широко использует JWT из-за ее небольших накладных расходов и возможности легкого использования в разных доменах.
Обмен информацией: веб-токены JSON — это хороший способ безопасной передачи информации между сторонами. Поскольку JWT могут быть подписаны — например, с использованием пар открытого и закрытого ключей — вы можете быть уверены, что отправители являются теми, за кого себя выдают. Кроме того, поскольку подпись вычисляется с использованием заголовка и полезной нагрузки, вы также можете убедиться, что содержимое не было изменено.
Какова структура веб-токена JSON?
В своей компактной форме веб-токены JSON состоят из трех частей, разделенных точками (.), а именно:
Заголовок
Полезная нагрузка
Подпись
Поэтому JWT обычно выглядит следующим образом:
xxxxx.yyyyy.zzzzz
Давайте разберем разные части.
Заголовок
Заголовок обычно состоит из двух частей: типа токена (JWT) и используемого алгоритма подписи, например HMAC SHA256 или RSA.
Например:
{
"alg": "HS256",
"typ": "JWT"
}
Затем этот JSON кодируется Base64Url для формирования первой части JWT.[1]
Полезная нагрузка
Вторая часть токена — это полезная нагрузка, содержащая утверждения. Утверждения — это утверждения об объекте (обычно о пользователе) и дополнительных данных. Существует три типа утверждений: зарегистрированные, публичные и частные.
● Зарегистрированные утверждения: это набор предопределенных утверждений, которые не являются обязательными, но рекомендуются для предоставления набора полезных интероперабельных утверждений. Вот некоторые из них: iss (эмитент), exp (срок действия), sub (тема), aud (аудитория) и другие.
Обратите внимание, что имена утверждений состоят всего из трех символов, поскольку JWT должен быть компактным.
● Публичные утверждения: они могут быть определены по желанию теми, кто использует JWT. Но во избежание конфликтов они должны быть определены в реестре веб-токенов IANA JSON или определены как URI, содержащие защищенное от конфликтов пространство имен.
● Частные утверждения: это настраиваемые заявки, созданные для обмена информацией между сторонами, которые договариваются об их использовании, и не являются ни зарегистрированными, ни публичными заявками.
Пример полезной нагрузки:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Затем полезная нагрузка кодируется Base64Url для формирования второй части веб-токена JSON.
Обратите внимание, что для подписанных токенов эта информация, хотя и защищена от подделки, доступна для чтения любому. Не помещайте секретную информацию в элементы полезной нагрузки или заголовка JWT, если она не зашифрована.
Подпись
Чтобы создать часть подписи, вы должны взять закодированный заголовок, закодированную полезную нагрузку, секрет, алгоритм, указанный в заголовке, и подписать это.
Например, если вы хотите использовать алгоритм HMAC SHA256, подпись будет создана следующим образом:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Подпись используется для проверки того, что сообщение не было изменено по пути, а в случае токенов, подписанных с помощью закрытого ключа, она также может проверить, что отправитель JWT является тем, за кого себя выдает.
Собираем все вместе
Вывод представляет собой три строки Base64-URL, разделенные точками, которые можно легко передавать в средах HTML и HTTP, при этом они более компактны по сравнению со стандартами на основе XML, такими как SAML.
Ниже показан JWT с закодированным предыдущим заголовком, полезной нагрузкой и подписью:
Если вы хотите поиграть с JWT и применить эти концепции на практике, вы можете использовать jwt.io Debugge для декодирования, проверки и создания JWT.
Как работают веб-токены JSON?
При аутентификации, когда пользователь успешно входит в систему, используя свои учетные данные, будет возвращен веб-токен JSON. Поскольку токены являются учетными данными, необходимо проявлять большую осторожность, чтобы предотвратить проблемы с безопасностью. Как правило, вы не должны хранить токены дольше, чем требуется.
Вы также не должны хранить конфиденциальные данные сеанса в хранилище браузера из-за отсутствия безопасности.
Всякий раз, когда пользователь хочет получить доступ к защищенному маршруту или ресурсу, пользовательский агент должен отправить JWT, обычно в заголовке авторизации, используя схему Bearer. Содержимое заголовка должно выглядеть следующим образом:
Authorization: Bearer <token>
В некоторых случаях это может быть механизм авторизации без сохранения состояния. Защищенные маршруты сервера проверят допустимый JWT в заголовке авторизации, и если он присутствует, пользователю будет разрешен доступ к защищенным ресурсам. Если JWT содержит необходимые данные, потребность в запросах к базе данных для определенных операций может быть уменьшена, хотя это не всегда так.
Обратите внимание, что если вы отправляете токены JWT через заголовки HTTP, вы должны попытаться предотвратить их слишком большой размер. Некоторые серверы не принимают заголовки размером более 8 КБ. Если вы пытаетесь внедрить слишком много информации в токен JWT, например, включая все разрешения пользователя, вам может понадобиться альтернативное решение, такое как детальная авторизация Auth0.
Если токен отправляется в заголовке авторизации, общий доступ к ресурсам между источниками (CORS) не будет проблемой, поскольку он не использует файлы cookie.
На следующей диаграмме показано, как получить JWT и использовать его для доступа к API или ресурсам:
● Приложение или клиент запрашивает авторизацию на сервере авторизации. Это выполняется через один из различных потоков авторизации. Например, типичное веб-приложение, совместимое с OpenID Connect, будет проходить через конечную точку /oauth/authorize, используя поток кода авторизации.
● Когда авторизация предоставлена, сервер авторизации возвращает токен доступа приложению.
● Приложение использует токен доступа для доступа к защищенному ресурсу (например, к API).
Обратите внимание, что с подписанными токенами вся информация, содержащаяся в токене, доступна пользователям или другим сторонам, даже если они не могут ее изменить. Это означает, что вы не должны помещать секретную информацию в токен.
Почему мы должны использовать веб-токены JSON?
Давайте поговорим о преимуществах веб-токенов JSON (JWT) по сравнению с простыми веб-токенами (SWT) и токенами языка разметки подтверждения безопасности (SAML).
Поскольку JSON менее подробен, чем XML, при кодировании его размер также меньше, что делает JWT более компактным, чем SAML. Это делает JWT хорошим выбором для передачи в средах HTML и HTTP.
С точки зрения безопасности SWT может быть симметрично подписан только общим секретом с использованием алгоритма HMAC. Однако токены JWT и SAML могут использовать пару открытого/закрытого ключа в виде сертификата X.509 для подписи. Подписание XML с помощью цифровой подписи XML без введения неясных дыр в безопасности очень сложно по сравнению с простотой подписания JSON.
Парсеры JSON распространены в большинстве языков программирования, поскольку они напрямую сопоставляются с объектами. И наоборот, XML не имеет естественного отображения документа на объект. Это упрощает работу с JWT, чем с утверждениями SAML.
Что касается использования, JWT используется в масштабах Интернета. Это подчеркивает простоту обработки веб-токена JSON на стороне клиента на нескольких платформах, особенно на мобильных.
Рисунок выше показывает сравнение длины закодированного JWT и закодированного SAML
Интерфейс
В качестве теоретического материала, предлагаю вспомнить предыдущий семестр заглянув в лабораторные работы. Там все подробно было описано. Интерфейс можно реализовывать на чем угодно (главное чтобы это был web интерфейс, в конечном итоге).
Практическая часть по работе с БД и авторизацией
Работа с базами данных
Откроем меню настройки БД (справа), и найдем MySQL(можно использовать любую базу данных для данной работы):
Теперь подключимся к нашей базе, для проверки соединения можно нажать на кнопку Test Connection:
В конечном итоге получится следующее:
Перейдем в файл application.properties:
В данном файле укажем следующую конфигурацию (порт можно оставить тот же):
Найдем файл с точкой входа программы (название-проекта.kt), и напишем там параметр для @SpringBootApplication:
На иерархию файлов не смотрите. У вас это результат вашей 2-ой лабораторной работы, а у меня новый проект. Т.е., вы улучшаете то, что есть у вас (подключая БД и авторизацию).
Создадим контроллер под названием AuthController, не забываем отметить класс как @RestController а метод hello как @GetMapping:
Протестируем:
Перейдем и создадим package (model, если по каким-то причинам его еще нет), класс User, пометим его как сущность при помощи @Entity, после чего укажем поля для данного класса:
Далее нужно создать package (repositories, если по каким-то причинам его еще нет) и класс UserRepository и запустим:
После запуска вы увидите что у нас автоматически сгенерировалась таблица user:
Перейдем к нашему контроллеру авторизации, и сделаем небольшие правки, добавим над классом @RequestMapping с параметром api, переименуем метод и параметр у GetMapping:
Перейдем и создадим package (services если по каким-то причинам его еще нет), и затем класс UserService и пометим его как @Service, напишем метод save который будет сохранять пользователя:
Перейдем в контроллер авторизации и добавим в конструктор класса новый параметр userService:
Перейдем и создадим package dto(s) и добавим в него класс RegisterDTO, внутри которого будет следующее:
Перейдем в контроллер авторизации и поменяем функцию соответственно. В качестве параметра будет передан @RequestBody body: RegisterDTO, в качестве возвращаемого значения будет возвращен ResponseEntity<User>:
Перейдем к нашей сущности User, добавим в него сеттер в котором будем шифровать пароль (не забудьте объявить PasswordEncoder):
Поменяем в контроллере GetMapping на PostMapping:
Запустим и проверим отправив запрос через Postman:
Проверим базу:
Работа с авторизацией
Продолжаем предыдущий проект. Перейдем в контроллер авторизации и добавим функцию login, у которой в качестве возвращаемого параметра будет указан тип данных Any:
Перейдем и создадим LoginDTO:
Перейдем в InterfaceRepository и добавим функцию поиска по email:
Теперь в UserService реализуем этот метод:
Также, добавим в package dtos новый класс Message, с аналогично названным параметром String в его конструкторе:
Перейдем и доделаем метод login() в контроллере авторизации:
[2]
[3]
Проверим работоспособность через postman:
Перейдем и добавим в сущность класса User функцию для сравнения пароля:
[4]
[5]
Вернемся к контроллеру авторизации и продолжим написание метода login():
Не забудьте протестировать вашу API через Postman.
Добавим в новую зависимость (тут проект собран на Maven, но в предыдущей лабораторной был сделан акцент на место для добавления зависимостей). Берите последнюю версию, а не как на скрине. Убедитесь что вы подключили все правильно (иначе может в будущем уйти много времени на поиск ошибки, который будет по итогу связан с этим JWT):
Перейдем в контроллер авторизации для того, чтобы доработать метод login():
На скрине происходит возвращение JWT через куки. Иногда это полезно, но иногда нет. Подумайте, как еще можно отдавать токен пользователю вашей системы (желательно отдавать его в нескольких местах).
На скрине есть очепятка: 24 * 60 * 60 * 1000 = 1 день!
Проверим через Postman:
А теперь проверим куки:
Также, не забудем добавить WebConfig для того, чтобы не возникло проблем в будущем при разработке веб морды:
Практические задания по работе с БД и авторизацией
1. Нужно выбрать идею проекта (можно свою).
2. На основании 2-й лабораторной, немножко подправить ее в соответствии с вашей идеей.
3. Добавить базу данных в ваш проект.
4. Добавить авторизацию через JWT.
5. Написать Web интерфейс для вашего приложения (достаточно чего-нибудь простенького).
Идеи проектов
Если не хотите использовать готовые, можно выбрать свои:
1. Weather API - Предоставляет текущую информацию о погоде для определенного места.
2. News API - Предоставляет последние новостные статьи из различных источников.
3. Translation API - Перевод текста с одного языка на другой.
4. Recipe API - Предоставляет информацию о рецептах и предложения на основе ингредиентов.
5. Fitness API - Предоставляет планы тренировок и отслеживает прогресс в фитнесе.
6. Music API - Предоставляет информацию о музыке, включая информацию об исполнителе и тексты песен.
7. API Movie - Предоставляет информацию о фильмах, включая актерский состав, сюжет и рецензии.
8. Book API - Предоставляет информацию о книгах, включая информацию об авторах и рецензии.
9. Geolocation API - Предоставляет информацию о местоположении, включая местные предприятия и достопримечательности.
10. Transportation API - Предоставляет информацию в реальном времени о расписании общественного транспорта и планировании маршрутов.
11. Social Media API - Предоставляет информацию об активности в социальных сетях, включая сообщения и реакции пользователей.
12. Stock Market API - Предоставление информации о фондовом рынке в режиме реального времени и анализ данных.
13. Sport API - Предоставление спортивной информации в режиме реального времени, включая результаты и статистику.
14. E-commerce API - Предоставляет информацию о товарах и позволяет осуществлять операции по покупке.
15. Travel API - Предоставляет информацию о туристических направлениях, включая местные достопримечательности и места проживания.
16. Gaming API - Предоставляет информацию о видеоиграх, включая обзоры и советы по игре.
17. Health API - Предоставляет информацию о здоровье и хорошем самочувствии, включая диеты и планы физических упражнений.
18. Education API - Предоставляет информацию об образовании, включая курсы и программы получения степени.
19. Real Estate API - предоставляет информацию об объектах недвижимости и позволяет осуществлять сделки с недвижимостью.
20. Job API - Предоставляет списки вакансий и позволяет подавать заявки на работу.
21. Finance API - Предоставляет информацию о личных финансах, включая советы по составлению бюджета и экономии.
22. Food API - Предоставляет информацию о продуктах питания, включая рецепты и информацию о питании.
23. Environmental API - Информация об экологических проблемах и решениях.
24. Technology API - Информация о технологиях, включая новые продукты и тенденции развития отрасли.
25. Fashion API - Предоставляет информацию о моде, включая тенденции в одежде и аксессуарах.
26. Beauty API - Информация о красоте, включая советы по макияжу и обзоры продуктов.
27. Automotive API - Предоставляет информацию об автомобилях, включая новые модели и обзоры автомобилей.
28. Home Improvement API - информация о благоустройстве дома, включая проекты "сделай сам" и обзоры товаров.
29. Pet API - Информация о домашних животных, включая уход за ними и обзоры товаров.
30. Art API - Информация об искусстве, включая художников и произведения искусства.
31. Photography API - Информация о фотографии, включая оборудование и методы съемки.
32. Cooking API - Информация о кулинарии, включая рецепты и советы по кухне.
33. Gardening API - Информация о садоводстве, включая уход за растениями и дизайн сада.
34. DIY API - информация о проектах "сделай сам", включая учебные пособия и обзоры продуктов.
35. Home Decor API - Информация о домашнем декоре, включая советы по дизайну интерьера и обзоры товаров.
36. Personal Development API - Предоставляет информацию о развитии личности, включая советы и ресурсы по самосовершенствованию.
37. Parenting API - Предоставляет информацию о воспитании детей, включая советы по развитию детей и советы по воспитанию.
38. Travel Planning API - Предоставляет информацию о планировании путешествий, включая рекомендации по направлениям и маршруты путешествий.
39. Environmental Science API - информация о науке об окружающей среде, включая текущие исследования и экологичные решения.
40. Green Energy API - Предоставляет информацию о зеленой энергии, включая возобновляемые источники энергии и инициативы по устойчивому развитию.
Поменять фон в кодовых отрывках
Зачем делать бизнессуху в контроллере?
Для этого Service есть
Логично, нужно учесть будет этот момент при проверке и на следующий семестр, спасибо! Сейчас переделывать уже думаю поздно :)
Логика в моделях? o_O
Тоже не доглядел, спасибо, подправим в будущем!)