<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0"
  xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
  xmlns:atom="http://www.w3.org/2005/Atom">

<channel>

<title>Робота</title>
<link>https://stefaniuk.website/tags/my-work/</link>
<description>Всім привіт! Я — Богдан, фулстек розробник з Києва. Пишу в основному про програмування та штуки які вивчаю. Час від часу публікую підбірки фоток, зроблених на плівку, розказую про подорожі та цікаві речі, що оточують мене.</description>
<author></author>
<language>uk</language>
<generator>Aegea 11.2 (v4116)</generator>

<itunes:subtitle>Всім привіт! Я — Богдан, фулстек розробник з Києва. Пишу в основному про програмування та штуки які вивчаю. Час від часу публікую підбірки фоток, зроблених на плівку, розказую про подорожі та цікаві речі, що оточують мене.</itunes:subtitle>
<itunes:image href="" />
<itunes:explicit></itunes:explicit>

<item>
<title>Онлайн документация на базе WordPress и IdentityServer</title>
<guid isPermaLink="false">262</guid>
<link>https://stefaniuk.website/all/app-manuals-by-wordpress-and-identityserver/</link>
<pubDate>Wed, 22 Dec 2021 03:55:03 -0400</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/app-manuals-by-wordpress-and-identityserver/</comments>
<description>
&lt;p&gt;Как-то раз на работе появилась задача по созданию онлайн документации для нашего приложения. Документация должна быть не технической, а в виде мануалов для пользователей.&lt;/p&gt;
&lt;p&gt;Одним из главных требований была авторизация пользователей с помощью текущего логина и пароля от основного приложения. Доступ к материалам должен определяться на основании текущих ролей пользователя.&lt;/p&gt;
&lt;p&gt;Изначально хотели использовать готовое решение в виде SaaS сервиса с которым можно интегрироваться и спокойно пользоваться. Идея самим все написать с нуля хоть и возникала, но вернуться к ней решили только в крайнем случае.&lt;/p&gt;
&lt;p&gt;Первым кандидатом был GitBook. У него приятный интерфейс, много опций по редактированию и оформлению текстов. Необходимый нам функционал также был, но доступен только в Enterprise версии. Цена оказалась слишком большой, пришлось искать дальше.&lt;/p&gt;
&lt;p&gt;Подобных сервисов и так не очень много, а с необходимыми нам фичами так вообще почти нету. В какой-то момент я даже решил попробовать что-то собрать на основе статического генератора сайтов, но получилось слишком монструозно.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Wordpress&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Оставался только один вариант — написать все самим, чего очень не хотелось делать. В какой-то момент я чисто случайно вспомнил про Wordpress. Не то что бы я им раньше пользовался, но много слышал о нем.&lt;/p&gt;
&lt;p&gt;В итоге решили делать мануалы на базе Wordpress. Вообще, Wordpress это такая штука, с которой при должном умении можно слепить все что угодно. В нашем случае даже код не пришлось писать (почти).&lt;/p&gt;
&lt;p&gt;За основу взяли готовую тему и немного допилили под наши нужды с помощью CSS. Правда в одном месте пришлось немного изменить логику отображения с помощью PHP, но это была достаточно тривиальная задача.&lt;/p&gt;
&lt;p&gt;Весь функционал реализовали с помощью пачки плагинов. Главным среди них стал OAuth Single Sign On by miniOrange. Он позволяет логиниться в вордпесс с помощью сторонних сервисов (Google, Facebook и т. д.). Бесплатная версия покрывает почти все кейсы. На же пришлось покупать лицензию, потому что нужен был мапинг наших ролей на роли вордпреса.&lt;/p&gt;
&lt;p&gt;За работу с пользователями в нашем приложении отвечает IdentityServer. Сделали отдельную страницу логина для SSO чтобы получить нужную куку. Также включили флаг AlwaysIncludeUserClaimsInIdToken в настройках клиента, чтобы клеймы ролей были в ID токене и их смог увидеть Wordpress.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Хостинг&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;С хостингом тоже все оказалось достаточно просто. Изначально хотели использовать AWS Lightsail, но основная проблема мы в отсутствии возможности перемещать снапшоты диска между аккаунтами. В итоге развернули все на EC2 с помощью готового бесплатного решения из AWS Marketplace.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Итого&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Эта задача очень показательна, она позволяет понять насколько готовые решения позволяют сэкономить время и деньги. Если писать такое приложение с нуля, то это может занять месяц работы, а также время на тестирование и отлов типичных ошибок. Ну и пользователей нужно было бы учить как всем этим пользоваться.&lt;/p&gt;
&lt;p&gt;В нашем случае настройка Wordpress заняла приблезительно неделю не очень активной работы. Также популярность Wordpress позволяет легко нанять человека, который сможет спокойно с ним работать.&lt;/p&gt;
&lt;p&gt;Ну и стоимость порадовала. Все это обошлось в ~570$, в то время как типичный SaaS сервис берез 5$ в месяц за одного пользователя. Хостинг тоже вышел не очень дорогим (~10-15$ в месяц).&lt;/p&gt;
</description>
</item>

<item>
<title>Как я делал бота для Facebook workplace</title>
<guid isPermaLink="false">171</guid>
<link>https://stefaniuk.website/all/create-workplace-messenger-bot/</link>
<pubDate>Tue, 26 Nov 2019 10:22:26 -0400</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/create-workplace-messenger-bot/</comments>
<description>
&lt;p&gt;Дали мне задачу: написать бота для мессенджера workplace, с помощью которого можно получать уведомления из нашей CRM и управлять разными вещами. Расскажу о разных интересных вещах с которыми я столкнулся. Бота писал с помощью ASP.NET Core Web API.&lt;/p&gt;
&lt;h2&gt;Вебхуки&lt;/h2&gt;
&lt;p&gt;Для того чтобы бот мог обрабатывать запросы и сообщения от Facebook нам надо настроить вебхуки. Webhook — механизм оповещения системы о событиях. Для того чтобы Facebook принял наш хук, он должен обрабатывать как GET так и POST запросы.&lt;/p&gt;
&lt;p class="note"&gt;&lt;a href="https://developers.facebook.com/docs/messenger-platform/getting-started/webhook-setup"&gt;Подробнее о Webhook в официальной документации&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GET запрос служит для валидации работы нашего эндпоинта. POST принимает данные связанные с активностью пользователя, будь то нажатие на кнопки или другая активность.&lt;/p&gt;
&lt;p class="cut-button"&gt;Показать код метода-обработчика GET запроса&lt;/p&gt;
&lt;div class="cut-content"&gt;&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;public IActionResult Receive( [FromQuery(Name = &amp;quot;hub.mode&amp;quot;)] string mode, 
    [FromQuery(Name = &amp;quot;hub.challenge&amp;quot;)] string challenge, 
    [FromQuery(Name = &amp;quot;hub.verify_token&amp;quot;)] string verifyToken)
{ 
   if (string.IsNullOrEmpty(verifyToken)) { 
       return Unauthorized(); 
    }  
    if (verifyToken.Equals(FacebookEnvironment.FacebookVToken)) { 
        return Ok(challenge); 
    } 
    return Unauthorized(); 
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В качестве verify_token используется токен, который мы указали при регистрации нашего хука.&lt;/p&gt;
&lt;/div&gt;&lt;p class="cut-button"&gt;Показать код метода-обработчика POST запроса&lt;/p&gt;
&lt;div class="cut-content"&gt;&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;public async Task&amp;lt;IActionResult&amp;gt; Receive([FromBody]FbResponse response = null)
{
    if (response is null) {
        return BadRequest();
    }

    if (response.Object != &amp;quot;page&amp;quot;) {
        return Ok();
    }

    foreach (var entry in response.Entries) {
        foreach (var message in entry.Messaging) {
            await PrepareMessageAsync(message);
        }
    }
    return Ok(&amp;quot;EVENT_RECEIVED&amp;quot;);
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2&gt;Авторизация запросов Facebook&lt;/h2&gt;
&lt;p&gt;Для авторизации Facebook использует специальный http заголовок (X-Hub-Signature), в нем он передает некую сигнатуру с помощью которой мы можем авторизовать запрос. Для того чтобы добавить такую функциональность в наш контроллер, добавим фильтр.&lt;/p&gt;
&lt;p class="cut-button"&gt;Пример кода, для проверки подписи&lt;/p&gt;
&lt;div class="cut-content"&gt;&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;private const string Sha1Prefix = &amp;quot;sha1=&amp;quot;;

public static bool Validate(string signature, string contentString) {
    if (!signature.StartsWith(Sha1Prefix, StringComparison.OrdinalIgnoreCase)) {
        return false;
    }
    var secret = Encoding.ASCII.GetBytes(FacebookEnvironment.AppSecret);
    var signatureWithoutPrefix = signature.Substring(Sha1Prefix.Length);
    var content = Encoding.ASCII.GetBytes(contentString);
    return GetIsHashValid(secret, signatureWithoutPrefix, content);
}

private static bool GetIsHashValid(byte[] secret, string signature, byte[] content) {
    using var hmac = new HMACSHA1(secret);
    var hash = hmac.ComputeHash(content);
    var hashString = ToHexString(hash);
    return hashString.Equals(signature);
}

private static string ToHexString(IReadOnlyCollection&amp;lt;byte&amp;gt; bytes)
{
    var builder = new StringBuilder(bytes.Count * 2);
    foreach (var b in bytes)
    {
        builder.AppendFormat(&amp;quot;{0:x2}&amp;quot;, b);
    }

    return builder.ToString();
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2&gt;Тестирование бота&lt;/h2&gt;
&lt;p class="note-md"&gt;&lt;a href="http://winitpro.ru/index.php/2017/11/03/ustanovka-besplatnogo-ssl-sertifikata-lets-encrypt-na-iis-v-windows-server-2012-r2/"&gt;Как установить letsencrypt сертификат для IIS&lt;/a&gt;. Если же вы используете связку в виде ubuntu и nginx вам подойдет &lt;a href="https://www.digitalocean.com/community/tutorials/nginx-let-s-encrypt-ubuntu-18-04-ru"&gt;эта инструкция&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Для тестирования нужно развернуть бот на сервере, который смотрит в мир. Также необходимо чтобы у сервера было доменное имя и валидный SSL сертификат. В моем случае, в качестве сервера выступала машина на винде, так как другой внутри нашей сети не было. Как мне показалось захостить приложение написанное на .NET Core намного проще под Ubuntu + nginx нежели под Windows + IIS. В качестве поставщика сертификатов выбрал letsencrypt, так как они предоставляют бесплатный сертификат на 3 месяца, с возможностью дальнейшего обновления.&lt;/p&gt;
</description>
</item>

<item>
<title>Terrasoft → Epam</title>
<guid isPermaLink="false">178</guid>
<link>https://stefaniuk.website/all/terrasoft-epam/</link>
<pubDate>Fri, 22 Nov 2019 18:14:27 -0400</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/terrasoft-epam/</comments>
<description>
&lt;p&gt;Сегодня был мой последний день в Террасофте. Проработал здесь 2,2 года (801 день). Это были забавные времена, было много позитивных вещей и часть негативных. Точно я могу сказать одно, я получил массу полезного опыта, новые знакомства, скиллы. А теперь пришло время идти дальше.&lt;/p&gt;
</description>
</item>

<item>
<title>36</title>
<guid isPermaLink="false">85</guid>
<link>https://stefaniuk.website/all/36/</link>
<pubDate>Mon, 01 Jul 2019 04:57:52 -0400</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/36/</comments>
<description>
&lt;p&gt;Просто советую всем посмотреть. Особенно программистам.&lt;/p&gt;
&lt;div class="e2-text-video"&gt;
&lt;iframe src="https://www.youtube.com/embed/FxljIvLxUqQ?enablejsapi=1" allow="autoplay" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
</description>
</item>


</channel>
</rss>