<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[David Ojeda's Blog]]></title><description><![CDATA[Get ready for randomness. I share my web dev life and whatever idea that crosses my mind.]]></description><link>https://blog.davidojeda.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 19 Apr 2026 17:57:50 GMT</lastBuildDate><atom:link href="https://blog.davidojeda.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Rails noob journey]]></title><description><![CDATA[I've been wanting to learn Rails since ages ago, but, you know, life was getting in the way (or me, rather).
A couple of weeks ago I decided to start a side-project related to another side-project. Sounds like an entry for a GitHub Graveyard, I know....]]></description><link>https://blog.davidojeda.dev/rails-noob-journey</link><guid isPermaLink="true">https://blog.davidojeda.dev/rails-noob-journey</guid><category><![CDATA[Ruby on Rails]]></category><category><![CDATA[Learning Journey]]></category><dc:creator><![CDATA[David Ojeda]]></dc:creator><pubDate>Sat, 17 Apr 2021 14:30:15 GMT</pubDate><content:encoded><![CDATA[<p>I've been wanting to learn Rails since ages ago, but, you know, life was getting in the way (<strong>or me, rather</strong>).</p>
<p>A couple of weeks ago I decided to start a side-project related to another side-project. Sounds like an <a target="_blank" href="https://dev.to/isaacdlyman/github-graveyards-ill-show-you-mine-49lh"><strong>entry for a GitHub Graveyard</strong></a>, I know. Just bear with me.</p>
<p>My initial side-project was a <a target="_blank" href="https://perrodinero.blog"><strong>personal finance blog in Spanish called "Perro Dinero"</strong></a> (has a special meaning in Spanish, but in English, it's just something like "money dog"). </p>
<p>It started as a way to document my learning because my memory betrays me since I can remember <strong>(pun intended)</strong>. My blog is still running, constantly updating it, and now it has two objectives:</p>
<ul>
<li>Document my finance and investments learnings</li>
<li>Help other people improve their financial lives</li>
</ul>
<p>I won't go further into explaining this side project, all you need to know is that it's about personal finance, it's in Spanish, <strong>my dog appears in every post 🐶,</strong> and you can visit it here 👇</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://perrodinero.blog">https://perrodinero.blog</a></div>
<p><strong>What does it have to do with Rails?</strong> Well, my brother and I are creating an app to help people get into long-term investing. No, we're not taking your money, just helping you decide what to invest in given your objectives.</p>
<p>Both of us have many years developing in Grails, the <strong>JVM version of Rails</strong>, and we both had the curiosity to see how working in Rails is/feels (we're big fans of DHH and Jason Fried, too). So we decided to create it with Rails.</p>
<p><strong>We've encountered a few issues that took us from an hour to a couple of days to fix, and I just want to document them, as plainly and simply as possible.</strong></p>
<p>So, yeah, expect some short Rails ramblings for the next few weeks.</p>
<p><strong>Read you soon!</strong> 👋</p>
]]></content:encoded></item><item><title><![CDATA[Reduce Google Fonts file size by more than 80%]]></title><description><![CDATA[Sometimes we only need a font for a couple of words, like a logo or slogan. Why do we download the whole font file if we only need a few letters?
In this post, I'll show you how you can reduce your font file size by more than 80%. This size reduction...]]></description><link>https://blog.davidojeda.dev/reduce-google-fonts-file-size-by-more-than-80-percent</link><guid isPermaLink="true">https://blog.davidojeda.dev/reduce-google-fonts-file-size-by-more-than-80-percent</guid><category><![CDATA[web performance]]></category><category><![CDATA[2Articles1Week]]></category><dc:creator><![CDATA[David Ojeda]]></dc:creator><pubDate>Thu, 01 Oct 2020 04:16:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1601525738597/fRtKgbIux.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Sometimes we only need a font for a couple of words, like a logo or slogan. Why do we download the whole font file if we only need a few letters?</p>
<p>In this post, I'll show you how you can <strong>reduce your font file size by more than 80%</strong>. This size reduction will be more effective the fewer characters you need.</p>
<h2 id="base-html">Base HTML</h2>
<p>This is the base HTML we'll use. It contains the Roboto font and two headers. One with Roboto font-family and the other with the default font:</p>
<pre><code><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Document<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://fonts.googleapis.com/css2?family=Roboto"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"font-family: 'Roboto', sans-serif;"</span>&gt;</span>David Ojeda<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Default font<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre><h2 id="measuring-current-font-file-size">Measuring current font file size</h2>
<p>If you open this file with your browser and open the network tab of the Developer Console, you would see this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1601519324867/d_Y4Z8vwV.png" alt="Developer console &quot;Network&quot; tab for previous HTML" /></p>
<p><strong>~657 bytes</strong> for the Google Fonts API call and <strong>~11.1 kB</strong> for the actual Roboto font.</p>
<p>If you look at the HTML again, you can notice that we're only using the font for my name, David Ojeda. What if we could download only those letters? It turns out we can.</p>
<h2 id="changing-the-font-url">Changing the font URL</h2>
<p>We need to change the font URL from this:</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://fonts.googleapis.com/css2?family=Roboto"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>&gt;</span>
</code></pre><p>to this:</p>
<pre><code>&lt;<span class="hljs-keyword">link</span> href=<span class="hljs-string">"https://fonts.googleapis.com/css2?family=Roboto&amp;text=David%20Ojeda"</span> rel=<span class="hljs-string">"stylesheet"</span>&gt;
</code></pre><p>What's new? The <code>&amp;text=</code> URL parameter. We'll only download the characters we need. Just remember, the text parameter value:</p>
<ul>
<li>Is case-sensitive.</li>
<li>Needs to be URL encoded. </li>
</ul>
<h2 id="measuring-updated-font-file-size">Measuring updated font file size</h2>
<p>Let's measure the size of the file now:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1601519750312/AaVS9ysj7.png" alt="Developer console &quot;Network&quot; tab for updated HTML" /></p>
<p><strong>~359 bytes</strong> for the Google Fonts API call and <strong>~1.3 kB</strong> for the actual Roboto font. <strong>That's like 89% file reduction for the font</strong>!</p>
<h2 id="wrap-up">Wrap up</h2>
<p>I found this implementation in the <a target="_blank" href="https://developers.google.com/fonts/docs/css2">Google Fonts documentation</a> days ago, and I wanted to share it so more people know about it.</p>
<p>It saved me this 80-90% file size on my <a target="_blank" href="https://perrodinero.blog/">personal finance blog</a>, and I'm sure it can help others do the same.</p>
<p><strong>Thanks for reading me! 💙</strong></p>
]]></content:encoded></item><item><title><![CDATA[Queues: A Primer]]></title><description><![CDATA[Queues are a natural phenomenon in everyday life. You queue to buy food, to pay in the supermarket, or to get on a plane. But queues are also used in software to handle heavy processes.
A queue can help devs like me and you store a request for later ...]]></description><link>https://blog.davidojeda.dev/queues-a-primer</link><guid isPermaLink="true">https://blog.davidojeda.dev/queues-a-primer</guid><category><![CDATA[queue]]></category><category><![CDATA[distributed system]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[2Articles1Week ]]></category><dc:creator><![CDATA[David Ojeda]]></dc:creator><pubDate>Wed, 30 Sep 2020 16:49:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1601415068736/UDncEwgEd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Queues are a natural phenomenon in everyday life. You queue to buy food, to pay in the supermarket, or to get on a plane. <strong>But queues are also used in software to handle heavy processes</strong>.</p>
<p>A queue can help devs like me and you store a request for later processing. It's a common practice to use a queue to handle data exports from a site, for example.</p>
<h2 id="what-youll-learn">What you'll learn</h2>
<p>In this post I'll compare queues in software development with queues in a fast food restaurant. You'll learn:</p>
<ul>
<li>What's a queue</li>
<li>Uses of a queue</li>
<li>Caveats of queues</li>
<li>Benefits of using a queue</li>
<li>Common services you can use</li>
</ul>
<h2 id="queuing-for-food-first-in-first-out-fifo">Queuing for food: First In, First Out (FIFO)</h2>
<p>When you go to a fast food place you need to first get in line to place your order.</p>
<p>You wait in line until it's your turn to say whatever you're gonna have. Ideally, no one gets in the line; that would be disrespectful and will cause some complaints.</p>
<p>This queue operates with a <strong>FIFO</strong> principle. That means that the first person dispatched is the first to be in line. <strong>The first that gets in line is the first to get out</strong>.</p>
<p>You can picture each person in line as a heavy processing job request from a client to the application. An asynchronous worker processes each of these requests that are waiting in line. And by worker I mean something like a server that's not reachable by your customers.</p>
<p>Each of these requests should respond in the order in which they were issued. <strong>But it's not always the case.</strong></p>
<h2 id="preparing-your-food-order-might-change">Preparing your food: Order might change</h2>
<p>Once you place your order, the cashier sends it to the cooks, so they can start preparing it. The orders get into another queue. </p>
<p>Now, imagine there's only one cooker. You ordered a triple cheeseburger with extra onions, a vanilla milkshake, and small fries. The person after you only ordered some small fries. </p>
<p>The cooker might say: let me serve the customer that only ordered fries first <strong>because that's quick</strong>. That puts you after that customer in the queue even though you ordered first.</p>
<p>In software development, queues are usually used with <strong>distributed systems</strong>. And in these type of systems <strong>the order of the elements in the queue might change</strong>.</p>
<p>There are special types of queues that guarantee the order, but not every queue does it. So <strong>your application must not depend on the order of the requests</strong>.</p>
<p>There's another problem you need to manage. What if there's more than one cooker and two or more of them start preparing the same order? It's very common for an application to have multiple workers that read the same queue.</p>
<h2 id="mistakes-in-food-preparation-handling-idempotency">Mistakes in food preparation: Handling idempotency</h2>
<p>If two cooks take your order they will each prepare you what you asked for. You'll end up with two cheeseburgers instead of one. There should be a way to avoid two cooks working on the same order.</p>
<p>A caveat of a distributed queue is that <strong>two workers can read the same message at the same time</strong>. So we say these requests should be idempotent. That is, the same operations requested multiple times should produce the same result.</p>
<p>For example, a DELETE request on a REST API should delete a resource the first time it's called. This usually returns a 200 (OK) or 204 (No Content) response. <strong>The result should be the same</strong> if that same request is called a second time: the deletion of the resource.</p>
<p>Some systems only disable the resource instead of deleting it from the database. In that case, <strong>the result</strong> (side effect in the application) and <strong>the response</strong> (literal response from the API) will be the same.</p>
<p>If the system deleted the resource the first time, then you'll receive a 404 (Not found) response on the second call. Here <strong>two identical requests had the same result, but a different response</strong>.</p>
<p><strong>In both cases the delete request was idempotent. Two same requests resulted in the same outcome.</strong></p>
<h2 id="getting-your-food-notifying-the-end-user">Getting your food: Notifying the end user</h2>
<p>It's common to receive a device that vibrates and lights on when your order is ready. That's how the restaurant notifies you that your order is ready for pickup.</p>
<p>Workers also need a way to communicate to its users. Email is a great solution since most of the processing workers do is asynchronous.</p>
<p>The only job of the queue is to receive and store requests until they're processed.</p>
<h2 id="why-do-we-need-queues">Why do we need queues?</h2>
<h3 id="queues-improve-user-experience">Queues improve user experience</h3>
<p>You don't want to wait in front of the cashier until your order is ready; you don't know how long will it take. </p>
<p>Similarly, you don't want your users to wait while looking at a loading spinner. Moreover, it can be taxing on the client-facing servers.</p>
<p><strong>The HTTP request window is short</strong>. Applications close the connection between server and client when a request is taking more than 30-60 seconds, resulting in a timeout.</p>
<p>How can you handle a resource-intensive task that takes more than 60 seconds? <strong>You send it to a queue</strong>, and a worker in the background can take that message and process it.</p>
<h3 id="queues-are-language-agnostic">Queues are language agnostic</h3>
<p>It doesn't matter if you send a message to a queue with Python and receive it with JavaScript. The intermediary, called the message broker, will handle it.</p>
<h3 id="queues-decouple-your-systems">Queues decouple your systems</h3>
<p>With queues, you can separate concerns and allow a different service or part of your application to handle specific requests.</p>
<p>The messages in the queue will be waiting even if your workers fail.</p>
<h2 id="popular-queue-services-and-message-brokers">Popular queue services and message brokers</h2>
<p>You don't have to implement the queue's internal logic, it's a solved problem. You can instead use one of these popular services:</p>
<ul>
<li><a target="_blank" href="https://aws.amazon.com/sqs/">Amazon Simple Queue Service (SQS)</a></li>
<li><a target="_blank" href="https://www.rabbitmq.com/">RabbitMQ</a></li>
<li><a target="_blank" href="https://activemq.apache.org/">Apache ActiveMQ</a></li>
</ul>
<p>I've only used Amazon SQS, and it's fast to start working with it. It gets complicated as you dig deeper, but it's a great way to start.</p>
<h2 id="what-i-didnt-tell-you">What I didn't tell you</h2>
<p>There are other factors to consider when working with queues and message brokers. The intention of this post is to only give a general overview of how the mechanism works.</p>
<p>To give you an idea, you need to configure and/or handle, among many others:</p>
<ul>
<li>Retry policy</li>
<li>Failed messages</li>
<li>Security of your queues</li>
<li>Duration of messages in the queue</li>
<li>Reliability and availability of your queues and message brokers</li>
</ul>
<h2 id="wrap-up">Wrap up</h2>
<p>Queues are a great addition to your software developer arsenal. If you need to do a resource-intensive task, a queue should be one of your options.</p>
<p><strong>Thanks for reading me! 👋</strong></p>
<p>**Cover image taken from <a target="_blank" href="https://undraw.co/">unDraw</a></p>
]]></content:encoded></item><item><title><![CDATA[4 Time Zone Bugs I Ran Into]]></title><description><![CDATA[Software development is hard. Time zones are hard. Dealing with time zones in software development?  Yeah, harder.
Here are 4 places where time zones might differ; and 4 personal bug stories for each case. I'll be referring to the same app for each s...]]></description><link>https://blog.davidojeda.dev/4-time-zone-bugs-i-ran-into</link><guid isPermaLink="true">https://blog.davidojeda.dev/4-time-zone-bugs-i-ran-into</guid><category><![CDATA[Bugs and Errors]]></category><category><![CDATA[General Programming]]></category><dc:creator><![CDATA[David Ojeda]]></dc:creator><pubDate>Wed, 23 Sep 2020 16:25:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1600871234687/et6yX6Wlb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Software development is hard. Time zones are hard. Dealing with time zones in software development?  Yeah, <strong>harder</strong>.</p>
<p>Here are <strong>4</strong> places where time zones might differ; and 4 personal bug stories for each case. I'll be referring to the same app for each story, the one I work with and maintain in my day-to-day job. This app works with Mexico's City time zone.</p>
<p><br /></p>
<h2 id="time-zone-of-your-app">Time zone of your app</h2>
<p>Your app runs with a default time zone. It's usually the time zone of the server it runs on, but it can be different.</p>
<p>In Java, you can define the time zone of the whole application when it boots. If for some reason you don't want to work with your server's timezone, this is the place to change it.</p>
<h3 id="the-bug-time-in-chile-off-by-one-hour">The bug - Time in Chile off by one hour</h3>
<p>The app shows the date of creation of an object in many places. Three of these places were showing different times; <strong>two incorrect and one correct</strong>.</p>
<p>One error was because I forgot to pass the user's timezone to the date formatter. Quick fix.</p>
<p>The second error was weird. I couldn't identify why, so I compared it with the correct one.</p>
<p>But the third case was only right because the date had been double parsed! Once on the server and a second time on the client (browser).</p>
<p>So none of the three dates were actually correct. <strong>WTF?</strong></p>
<p>After some headaches, I learned that each version of Java comes with a time zone data file. This file includes the latest information on the world's time zones, and the <a target="_blank" href="https://www.iana.org/time-zones">Internet Assigned Numbers Authority (IANA)</a> manages it. </p>
<p>Time zone changes happen when governments decide to apply or not to apply daylight saving times (DST). </p>
<p>In 2015, Chile decided to move from seasonal DST to permanent DST, and some JRE releases included this change. But then, in 2016 Chile decided to revert to how it was before; seasonal DST instead of permanent. <strong>What was the issue?</strong> The app was using one of these JRE releases with an outdated time zone data file.</p>
<p>You can read more about these <a target="_blank" href="https://hi.service-now.com/kb_view.do?sysparm_article=KB0622033">DST issues with Java here</a>.</p>
<p><br /></p>
<h2 id="time-zone-of-your-server">Time zone of your server</h2>
<p>The operative system defines your server's time zone. I've always used Linux for production servers, and they come with UTC as the default time zone.</p>
<p>If you need to change this time zone, make sure to do it before your application starts or it won't reflect the change.</p>
<h3 id="the-bug-app-using-the-wrong-default-time-zone-from-the-server">The bug - App using the wrong default time zone from the server</h3>
<p>I was migrating some processes in our build and deployment pipeline. From configuring the app with every deploy to a pre-built AWS Amazon Machine Image (AMI) with HashiCorp's Packer.</p>
<p>One step of the initial configuration was to change the server's time zone to America/Mexico_City, and I was aware of it. So I created a bash script that changed the time zone on the AMI we were going to use. The script worked well when I tested it on a Linux instance. No problem there.</p>
<p>I proceeded to use this AMI in our staging environment and neither I nor my teammates noticed something off. So, to production!</p>
<p>Customer's questions and complaints about dates behaving weird arrived minutes later 😥</p>
<p><strong>The issue?</strong> The script that updated the server's time zone was failing silently and I missed double-checking it in the staging environment. The app wasn't using an explicit time zone, so it took the server's. And the server's time zone was UTC by default, and we needed America/Mexico_City. I fixed the script and, to make sure, updated the app's default time zone to the expected one.</p>
<p><br /></p>
<h2 id="time-zone-of-your-database">Time zone of your database</h2>
<p>You can also change your database's time zone. I use AWS Relational Database Service (RDS) and the default time zone is UTC. You can update it from the parameter group of your cluster or individual instance.</p>
<h3 id="the-bug-wrong-database-time-zone">The bug - Wrong database time zone</h3>
<p>Now I was doing a migration of our database. I anticipated myself by changing the database's time zone to America/Mexico_City because the app and server had it. Every part of the system should be in the same page, right? <strong>Wrong!</strong></p>
<p>The database was perfectly okay being in UTC while the app and server were in America/Mexico_City. That's how it worked. </p>
<p>This bug was not as critical as the previous ones because I caught it in our staging environment. </p>
<p><br /></p>
<h2 id="time-zone-of-your-users">Time zone of your users</h2>
<p>If it wasn't enough, each one of your users can have a different time zone, and you have to take that into consideration when showing time sensitive-data.</p>
<h3 id="the-bug-many-of-them">The bug - Many of them!</h3>
<p>I've encountered many bugs related to user's time zones:</p>
<ul>
<li>Missing time zone in date formatter.</li>
<li>Incorrect time zone selection from the user.</li>
<li>Missing DST time zone options for users to select.</li>
<li>Storing dates with time zone modifications that get parsed again when retrieved.</li>
</ul>
<hr />
<p>Time zones are one of the most complicated topics you'll find while developing software. They're complex by themselves, and even more when you throw some code into the mix.</p>
<p>I hope these short stories can help you avoid my mistakes in the future 🙌🏼</p>
<p><strong>Thanks for reading me! 💙</strong></p>
]]></content:encoded></item><item><title><![CDATA[I finished the #100DaysOfCode challenge in 140 days, here are my thoughts]]></title><description><![CDATA[I started the #100DaysOfCode challenge on March 21st and finished it today, August 7th. FINALLY! 🥳 And yeah, that's way more than 100 days 🙃
For those who don't know, the #100DaysOfCode challenge consists of coding at least one hour every day for t...]]></description><link>https://blog.davidojeda.dev/i-finished-the-100daysofcode-challenge-in-140-days-here-are-my-thoughts</link><guid isPermaLink="true">https://blog.davidojeda.dev/i-finished-the-100daysofcode-challenge-in-140-days-here-are-my-thoughts</guid><category><![CDATA[newbie]]></category><category><![CDATA[100DaysOfCode]]></category><dc:creator><![CDATA[David Ojeda]]></dc:creator><pubDate>Sat, 08 Aug 2020 01:01:34 GMT</pubDate><content:encoded><![CDATA[<p>I started the #100DaysOfCode challenge on March 21st and finished it today, August 7th. <strong>FINALLY!</strong> 🥳 And yeah, that's way more than 100 days 🙃</p>
<p>For those who don't know, the #100DaysOfCode challenge consists of <strong>coding at least one hour every day for the next 100 days</strong>. Every day during this period you also must:</p>
<ul>
<li>Tweet your progress using the #100DaysOfCode hashtag.</li>
<li>Engage with at least two other people doing the challenge.</li>
</ul>
<p>The purpose of the challenge is to help you form a habit and to meet other people doing the same. <a target="_blank" href="https://www.100daysofcode.com/">Check the full challenge description on their official page</a>.</p>
<p>When I started this challenge, I was at a point in life where I was looking to improve many of my current habits and to be more active on Twitter, and the challenge was a mix of both.</p>
<p>I want to share some thoughts about this journey and the challenge itself since <strong>I have mixed feelings</strong>. Before I go on, I want to say that this is <strong>my</strong> perspective. The perspective from an experienced dev who already codes in its 9-5 and has multiple side projects, not all code related.</p>
<p>Let's get to it.</p>
<h2 id="public-commit-is-real">Public commit is real</h2>
<p>Yelling out loud that you'll commit and achieve something it's a powerful way to actually do it. And the challenge starts just like that, committing in public. It can help you build a habit if you didn't have one because it creates a sense of <strong>accountability</strong>. Even if no one really cares.</p>
<p>A more in deep view of this fact is in SWYX <a target="_blank" href="https://www.swyx.io/writing/learn-in-public/">Learn In Public article</a>. Highly recommended read 👌🏼</p>
<h2 id="you-meet-great-people-along-the-way">You meet great people along the way</h2>
<p>The challenge invites you to share and engage with other people going through the same journey, and you end up creating bonds. I want to thank them:</p>
<ul>
<li><p>Alex was my accountability buddy from the very beginning. He's a humble, great developer who's constantly learning and improving his craft. <a target="_blank" href="https://dev.to/chiubaca">Check his DEV account!</a></p>
</li>
<li><p>Annie and I engaged in some of our tweets for the challenge, and it's been a pleasure to see her Twitter following skyrocket. <a target="_blank" href="https://twitter.com/anniebombanie_">Do check her out</a> for some mind-blowing CSS illustrations 🤯</p>
</li>
<li><p><a target="_blank" href="https://twitter.com/AlfredoCambera?s=20">Alfredo</a> and I are also accountability buddies. He loves DevOps related stuff and he's on his way to write consistently!</p>
</li>
</ul>
<h2 id="its-daunting-to-do-anything-100-times-in-a-row">It's daunting to do anything 100 times in a row</h2>
<p>Starting to form a habit by doing one thing, for at least one hour, for 100 days in a row, is like rolling out a snowball uphill. It will eventually grow so big that it will crush you.</p>
<p>When I formed my habit of working out, it didn't start by saying: "I'm gonna exercise for one hour for the next 100 days". That's exactly how you <strong>don't create a habit</strong>.</p>
<p>You'll probably start highly motivated the first few weeks, but believe me, life gets in the way. You'll eventually start to fall off because it's a <strong>HUGE</strong> commitment. And it's especially hard if you are just starting out as a developer.</p>
<p>It's like starting to run before you crawl.</p>
<p>Instead, to create my exercise habit I said: "I'm gonna exercise for at least five minutes, three times a week". The secret is that five minutes is more manageable, and for that reason more likely to happen.</p>
<p>Once you start, you'll find yourself doing more than five minutes because, well, you're already there with your gym clothes on. You build up from there, and now the snowball rolls downhill.</p>
<h2 id="missing-one-day-is-not-the-end-of-the-world">Missing one day is not the end of the world</h2>
<p>I wish this previous sentence was part of the challenge's website.</p>
<p>Whenever I couldn't find a time slot to work on the challenge I felt <strong>anxious</strong>. Even if no one would even notice that I missed one day. Not even your all-time-friends: the bots.</p>
<p>But you already committed publicly to the challenge! You feel pressured. And many days I would open my computer, do some silly change to my code, and tweet about it. Damn, I even felt stupid sharing changes so small that where nowhere near the 1 hour commit I should be doing:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/DavidOjedaL/status/1265895861688688642">https://twitter.com/DavidOjedaL/status/1265895861688688642</a></div>
<p>That being said, take a break when needed. Nothing's gonna happen if you miss one or a couple of days. It's easy to burnout if you're also doing many other things at once. <strong>Your health is more important</strong>.</p>
<h2 id="i-learned-a-lot">I learned A LOT</h2>
<p>Not all 100 days of the challenge were related to a single subject or programming language; it was a huge mix of software-related work. Here are some things I did and learned during this journey.</p>
<h3 id="design">Design</h3>
<p>This is one of my weakest points, so I started with it. Color palettes, Figma and Tailwind CSS:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/DavidOjedaL/status/1242483155401048065">https://twitter.com/DavidOjedaL/status/1242483155401048065</a></div>
<h3 id="nina-a-writing-app-side-project">Nina, a writing app (side project)</h3>
<p>A  <a target="_blank" href="https://dev.to/david_ojeda/blog-post-writing-app-demo-83k">web app to help me standardize my writing process</a>.</p>
<h3 id="udacitys-front-end-developer-nanodegree">Udacity's Front End Developer Nanodegree</h3>
<p>I finished it in less than a month to take advantage of a discount 😁:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/DavidOjedaL/status/1251000341128187906">https://twitter.com/DavidOjedaL/status/1251000341128187906</a></div>
<h3 id="aws-cdk">AWS CDK</h3>
<p>I started another Udacity's nanodegree because they gave me another free month, this one was for DevOps. I couldn't finish it within the free month so I dropped from it, but I managed to learn anyways:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/DavidOjedaL/status/1260000143845330944">https://twitter.com/DavidOjedaL/status/1260000143845330944</a></div>
<h3 id="personal-blog-re-design">Personal blog re-design</h3>
<p>It was time for a new design for my personal blog, and I wanted to try Eleventy for a while:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/DavidOjedaL/status/1276038199668871168">https://twitter.com/DavidOjedaL/status/1276038199668871168</a></div>
<h3 id="personal-finances-blog-re-design">Personal finances blog re-design</h3>
<p>I also have a personal finance blog in Spanish, it's called <a target="_blank" href="https://www.perrodinero.blog/">Perro Dinero</a> 🐕💰. I use Squarespace because at the beginning all I wanted to do was to focus on writing. Now that I have the habit, I want more freedom, so I'm moving to Eleventy too:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/DavidOjedaL/status/1290885451990147072">https://twitter.com/DavidOjedaL/status/1290885451990147072</a></div>
<h2 id="enjoy-the-journey">Enjoy the journey</h2>
<p><strong>Engage and learn from others</strong>. I believe that's the most valuable takeaway of this challenge.</p>
<p>The rules and hashtag encourage you to comment on other's people work; positive vibes only 🤗. You meet new people, you learn from them, you expand your network. Do what you like, slow the pace if needed, but always have fun and enjoy the process.</p>
<h2 id="closing-thoughts">Closing thoughts</h2>
<p>The #100DaysOfCode challenge is a great initiative for new developers, it can help them to:</p>
<ul>
<li>Create a coding habit.</li>
<li>Meet new people.</li>
<li>Learn new languages.</li>
<li>Showcase their work.</li>
</ul>
<p>And the challenge is more than just coding, it's about sharing and community 🧡</p>
<p>I couldn't keep the 100 days streak or something even close because I already code every weekday. There were times when I didn't want to code on weekends, I wanted to disconnect. That's totally fine for me, but it didn't stop my anxiety.</p>
<p><strong>So, was a great experience?</strong> <em>It was!</em></p>
<p><strong>If I went back in time would I do it again?</strong> <em>I would!</em></p>
<p><strong>Am I gonna do round 2?</strong> <em>Not at all.</em></p>
<p><strong>Do I recommend it?</strong></p>
<p>If you're starting out as a developer or want to create a side project or habit and you need public accountability, go for it!</p>
<p>If you're an experienced developer with an already ongoing side project, the challenge might not be the best place to put your efforts on.</p>
<hr />
<p>Thanks a lot for reading me, and I hope these words can help you through your journey, be it that you're already on it or planning to start 👋</p>
]]></content:encoded></item><item><title><![CDATA[Enable "Ignore load balancer 4xx errors" health rule on AWS Elastic Beanstalk using .ebextensions]]></title><description><![CDATA[If you are an Elastic Beanstalk (EB) user, you are probably aware of a frequently requested feature that was released on July 25, 2018: To ignore application 4xx errors when determining your environment's health. It's was a new EB health rule that ig...]]></description><link>https://blog.davidojeda.dev/enable-ignore-load-balancer-4xx-errors-health-rule-on-aws-elastic-beanstalk-using-ebextensions</link><guid isPermaLink="true">https://blog.davidojeda.dev/enable-ignore-load-balancer-4xx-errors-health-rule-on-aws-elastic-beanstalk-using-ebextensions</guid><category><![CDATA[AWS]]></category><dc:creator><![CDATA[David Ojeda]]></dc:creator><pubDate>Fri, 20 Mar 2020 00:50:17 GMT</pubDate><content:encoded><![CDATA[<p>If you are an <strong>Elastic Beanstalk (EB)</strong> user, you are probably aware of a frequently <a target="_blank" href="https://aws.amazon.com/releasenotes/release-aws-elastic-beanstalk-support-for-enhanced-health-rule-customization-on-july-25-2018/?tag=releasenotes%23keywords%23aws-elastic-beanstalk">requested feature that was released on July 25, 2018</a>: To ignore application 4xx errors when determining your environment's health. It's was a new EB health rule that ignores 400-499 HTTP status codes when alerting if your environment instances are having trouble 🏥.  </p>
<p>It is common for applications to receive many 4xx errors, for example, due to:  </p>
<ul>
<li>Client's API integrations using invalid credentials.</li>
<li>Client-side test tools.</li>
<li>Broken links that create 404 responses.</li>
</ul>
<p>I started to use this feature since the moment it was released.  </p>
<p>Today I logged in into my EB console and switched the design to the latest version of the console. Everything okay. Then, as I was exploring the new console, I noticed a new configuration that was not previously available: <strong>To ignore load balancer 4xx errors</strong>.  </p>
<p>In this post, I will show you, through a story, how to enable this feature using <a target="_blank" href="https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/ebextensions.html"><em>.ebextensions</em></a>, because, infrastructure-as-code, you know 🤓. The funny part, I can't seem to find the documentation anywhere on AWS. Never happened before, right? 🙃.   </p>
<p>Follow along!  </p>
<h1 id="digging-through-the-docs">Digging through the docs 🗂</h1>
<p>The first thing I do when trying to configure this new feature is: go to the <a target="_blank" href="https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/health-enhanced-rules.html">docs page where the almost identical feature is documented</a>, that is, the ignore <strong>application</strong> 4xx errors. I found that is the same page as it was before, with no extra information about a load balancer health rule. Citing the docs:  </p>
<blockquote>
<p>Currently, this is the only available enhanced heath rule customization. You can't configure enhanced health to ignore HTTP errors returned by an environment's load balancer, or other HTTP errors in addition to 4xx.</p>
</blockquote>
<p>Liars! Just kidding 😝   </p>
<p>Without luck on this doc page, I proceeded to look somewhere else:  </p>
<ul>
<li>Went through the <a target="_blank" href="https://aws.amazon.com/releasenotes/?tag=releasenotes%23keywords%23aws-elastic-beanstalk">EB release notes</a> to see if I missed the announcement. No luck.</li>
<li>Went to the <a target="_blank" href="https://github.com/aws/elastic-beanstalk-roadmap/projects/1">EB public roadmap</a> and found nothing there either.</li>
<li><a target="_blank" href="https://twitter.com/DavidOjedaL/status/1240705686343798784">Asked on Twitter</a>, but since no one follows all I got was a response from AWS support to look at their forum. Go <a target="_blank" href="https://twitter.com/DavidOjedaL">follow me</a> on Twitter! 👀.</li>
<li>Went to the EB forums and only found people asking how to work around the fact that there was no feature to ignore load balancer 4xx errors 🤦🏻‍♂️.</li>
<li>Asked a <a target="_blank" href="https://stackoverflow.com/questions/60764891/how-to-enable-aws-elastic-beanstalk-health-rule-ignore-load-balancer-4xx-throu?noredirect=1#comment107510426_60764891">question in StackOverflow</a> and got a comment saying that I should create a support ticket.</li>
</ul>
<h1 id="what-next">What next❓</h1>
<p>I had already searched through a lot of docs without any progress. I posted the question on Twitter and StackOverflow, and I wasn't expecting any response soon. I thought about trying to guess the new field names and do a try-error session.  </p>
<p>My current configuration document looks like this:  </p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"Rules"</span>: {
    <span class="hljs-attr">"Environment"</span>: {
      <span class="hljs-attr">"Application"</span>: {
        <span class="hljs-attr">"ApplicationRequests4xx"</span>: {
          <span class="hljs-attr">"Enabled"</span>: <span class="hljs-literal">false</span>
        }
      }
    }
  },
  <span class="hljs-attr">"Version"</span>: <span class="hljs-number">1</span>
}
</code></pre>
<p>If I were the software developer that created this feature, how would I call the new field? Some options:  </p>
<ul>
<li>LoadBalancerRequests4xx</li>
<li>ALBRequests4xx</li>
<li>ELBRequests4xx</li>
</ul>
<p>But, should they be nested under "Application" or should it be another high-level field? By this time I was already hungry, so I went to cook and eat 👨🏻‍🍳🍲  </p>
<h1 id="enlightenment"><strong>Enlightenment</strong>! 🔮</h1>
<p><strong>Taking a break helps</strong>, you should consider it in your daily routine. Back to the topic...   </p>
<p>I realized that even though I can't find a way to configure this feature through code, I can do it directly in the console. This would only be temporary because the environments re-create themselves on each deploy- any new environment would have this feature disabled.  </p>
<p>And here is where I got an idea💡 What if I:  </p>
<ol>
<li>Enable the feature on the console.</li>
<li>Save the environment configuration as an <a target="_blank" href="https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/environment-configuration-savedconfig.html">EB Saved Configuration</a>.</li>
<li>Retrieve it with the EB CLI to see how the field is called at an API level.</li>
</ol>
<p>That's exactly what I did. Once the configuration was saved, I retrieved it:  </p>
<pre><code class="lang-bash">$ eb config get NAME_OF_MY_CONFIGURATION
</code></pre>
<p>and 💥, the configuration showed itself:  </p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"Rules"</span>: {
    <span class="hljs-attr">"Environment"</span>: {
      <span class="hljs-attr">"ELB"</span>: {
        <span class="hljs-attr">"ELBRequests4xx"</span>: {
          <span class="hljs-attr">"Enabled"</span>: <span class="hljs-literal">false</span>
        }
      },
      <span class="hljs-attr">"Application"</span>: {
        <span class="hljs-attr">"ApplicationRequests4xx"</span>: {
          <span class="hljs-attr">"Enabled"</span>: <span class="hljs-literal">false</span>
        }
      }
    }
  }
}
</code></pre>
<p>There was a high-level field called "<em>ELB"</em>, and a property "<em>ELBRequests4XX</em>"; I was not that erred 👨🏻‍💻.  </p>
<p>I added those new fields to my .config file on the <em>.ebextensions</em> folder and everything worked as expected 👏🏼 Here is the final .config file I used:  </p>
<pre><code class="lang-yaml"><span class="hljs-attr">option_settings:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">namespace:</span> <span class="hljs-string">aws:elasticbeanstalk:healthreporting:system</span>
    <span class="hljs-attr">option_name:</span> <span class="hljs-string">ConfigDocument</span>
    <span class="hljs-attr">value:</span> {
<span class="hljs-attr">"Rules":</span> {
  <span class="hljs-attr">"Environment":</span> {
    <span class="hljs-attr">"ELB":</span> {
      <span class="hljs-attr">"ELBRequests4xx":</span> {
        <span class="hljs-attr">"Enabled":</span> <span class="hljs-literal">false</span>
      }
    },
    <span class="hljs-attr">"Application":</span> {
      <span class="hljs-attr">"ApplicationRequests4xx":</span> {
        <span class="hljs-attr">"Enabled":</span> <span class="hljs-literal">false</span>
      }
    }
  }
},
<span class="hljs-attr">"Version":</span> <span class="hljs-number">1</span>
}
</code></pre>
<h1 id="wrap-up">Wrap up 🔄</h1>
<p>I can't imagine how complex is to add a feature to a system as big as AWS while maintaining all the docs updated. I don't blame them.  </p>
<p>Nonetheless, we figured out at the end 🙌🏼. In short, to configure your EB environments to ignore load balancer 4xx errors, you need to add the previous .config file to your .ebextensions folder and deploy a new version.  </p>
<p>Maybe the docs are updated by the time you read this, and the story will not be that fun 🙃 Anyhow, it was a blast to write.  </p>
<p>I hope you enjoyed it. Thanks for reading me 💙</p>
]]></content:encoded></item><item><title><![CDATA[Copy to clipboard button with Stimulus 2.0 (Beta)]]></title><description><![CDATA[Stimulus is a JavaScript framework developed by a team at Basecamp, and it aims to augment your existing HTML so things work without too much "connecting" code.
Contrary to other frameworks, Stimulus doesn't take over your front-end, so you can add i...]]></description><link>https://blog.davidojeda.dev/copy-to-clipboard-button-with-stimulus-20-beta</link><guid isPermaLink="true">https://blog.davidojeda.dev/copy-to-clipboard-button-with-stimulus-20-beta</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[David Ojeda]]></dc:creator><pubDate>Tue, 28 Jan 2020 00:46:41 GMT</pubDate><content:encoded><![CDATA[<p><a target="_blank" href="https://stimulusjs.org/handbook/introduction"><strong>Stimulus</strong></a> is a JavaScript framework developed by a team at <a target="_blank" href="https://basecamp.com/">Basecamp</a>, and it aims to augment your existing HTML so things work without too much "connecting" code.</p>
<p>Contrary to other frameworks, Stimulus doesn't take over your front-end, so you can add it without too much hassle to your already running app.  </p>
<p><strong>Its documentation is very clear and digestible</strong>. Included in its handbook is an <a target="_blank" href="https://stimulusjs.org/handbook/building-something-real">example of building a clipboard functionality</a>, which I recommend you go through if you are trying Stimulus for the first time.</p>
<p>Right now we are <strong>replicating</strong> that functionality and adding a couple more things <strong>using a development build</strong> specified in this Pull Request (PR)</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/stimulusjs/stimulus/pull/202">https://github.com/stimulusjs/stimulus/pull/202</a></div>
<p>It <strong>includes new APIs that will be released with version 2.0</strong> of the framework, so they are not yet available with the current stable production release.</p>
<h1 id="what-are-we-building">What are we building?</h1>
<p>A one-time password "copy to clipboard" button what wraps the DOM Clipboard API.</p>
<p>You can access the final working version on <a target="_blank" href="https://glitch.com/edit/#!/trapezoidal-seer">Glitch</a>.</p>
<h1 id="starting-off">Starting off</h1>
<p>First, we are creating our base HTML where the one-time password will be and the actual button to copy it:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>
    One-time password:
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"fbbb5593-1885-4164-afbe-aba1b87ea748"</span> <span class="hljs-attr">readonly</span>=<span class="hljs-string">"readonly"</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>
    Copy to clipboard
  <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1600495354149/ORzVu3Tj7.png" alt="Text input with &quot;copy to clipboard button&quot; rendered HTML" /></p>
<p>This doesn't do anything by itself; we need to add our Stimulus controller.</p>
<h1 id="the-controller-definition">The controller definition</h1>
<p>In Stimulus, <strong>a controller is a JavaScript object that automatically connects to DOM elements that have certain identifiers</strong>.</p>
<p>Let's define our clipboard controller. The main thing it needs to do? Grab the text on the input field and copy it to the clipboard:</p>
<pre><code class="lang-javascript">
(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> application = Stimulus.Application.start();

  application.register(<span class="hljs-string">"clipboard"</span>, <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Stimulus</span>.<span class="hljs-title">Controller</span> </span>{
    <span class="hljs-comment">// We'll get to this below</span>
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">targets</span>() {
      <span class="hljs-keyword">return</span> [<span class="hljs-string">'source'</span>]
    }

    copy() {
      <span class="hljs-comment">// Here goes the copy logic </span>
    }
  });

})();
</code></pre>
<p>Now, this is a valid controller that doesn't do anything because it's not connected to any DOM element yet.</p>
<h1 id="connecting-the-controller">Connecting the controller</h1>
<p>Adding a <code>data-controller</code> attribute to our <code>div</code> will enable the connection:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">data-controller</span>=<span class="hljs-string">"clipboard"</span>&gt;</span>

[...]
</code></pre>
<p>Remember the <code>static get targets()</code> from above? That allows us to <strong>access DOM elements as properties in the controller</strong>. </p>
<p>Since there is already a <code>source</code> target, we can now access any DOM element with the attribute <code>data-clipboard-target="source"</code>:</p>
<pre><code class="lang-html">[...]

<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">data-clipboard-target</span>=<span class="hljs-string">"source"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"fbbb5593-1885-4164-afbe-aba1b87ea748"</span> <span class="hljs-attr">readonly</span>=<span class="hljs-string">"readonly"</span>&gt;</span>

[...]
</code></pre>
<p>Also, we need the button to actually do something. We can link the "Copy to clipboard" button to the <code>copy</code> action in our controller with another identifier: <code>data-action="clipboard#copy"</code>. The HTML now looks like this:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">data-controller</span>=<span class="hljs-string">"clipboard"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>
    One-time password:
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">data-clipboard-target</span>=<span class="hljs-string">"source"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"fbbb5593-1885-4164-afbe-aba1b87ea748"</span> <span class="hljs-attr">readonly</span>=<span class="hljs-string">"readonly"</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">data-action</span>=<span class="hljs-string">"clipboard#copy"</span>&gt;</span>
    Copy to clipboard
  <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>Our controller is now automatically connected to the DOM, and clicking the copy button will invoke the <code>copy</code> function; let's proceed to write it.</p>
<h1 id="the-copy-function">The copy function</h1>
<p>This function is essentially a <strong>wrapper of the DOM Clipboard API</strong>. The logic goes like this:</p>
<pre><code class="lang-javascript">[...]

copy() {
  <span class="hljs-built_in">this</span>.sourceTarget.select();
  <span class="hljs-built_in">document</span>.execCommand(<span class="hljs-string">'copy'</span>);
}

[...]
</code></pre>
<p>We take the <code>source</code> target we defined earlier, our text input that is, select its content, and use the Clipboard API to copy it to our clipboard.</p>
<p>At this point, <strong>the functionality is practically done!</strong> You can press the button and the one-time password is now available for you on your clipboard.</p>
<h1 id="moving-further">Moving further</h1>
<p>The copy button works now, but we can go further. <strong>What if the browser doesn't support the Clipboard API or JavaScript is disabled?</strong></p>
<p>If that's the case, we are going to hide the copy button entirely.</p>
<h1 id="checking-api-availability">Checking API availability</h1>
<p>We can check if the <code>copy</code> command is available to us by doing this:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">document</span>.queryCommandSupported(<span class="hljs-string">"copy"</span>)
</code></pre>
<p>One of the best places to check this is when the Stimulus controller connects to the DOM. Stimulus gives us some nice <strong>lifecycle callbacks</strong> so we can know when this happens. </p>
<p>We can create a <code>connect</code> function on our controller and it will be invoked whenever this controller connects to the DOM:</p>
<pre><code class="lang-javascript">[...]

connect() {
  <span class="hljs-keyword">if</span> (<span class="hljs-built_in">document</span>.queryCommandSupported(<span class="hljs-string">"copy"</span>)) 
    <span class="hljs-comment">// Proceed normally</span>
  }
} 

[...]
</code></pre>
<p>One way to hide/show the copy button depending on the API availability is to initially load the page with the button hidden, and then displaying it if the API is available. </p>
<p>To achieve this we can rely on CSS:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.clipboard-button</span> {
  <span class="hljs-attribute">display</span>: none;
}

<span class="hljs-comment">/* Match all elements with .clipboard-button class inside the element with .clipboard--supported class */</span>
<span class="hljs-selector-class">.clipboard--supported</span> <span class="hljs-selector-class">.clipboard-button</span> {
  <span class="hljs-attribute">display</span>: initial;
}
</code></pre>
<p>Our button is now hidden from the beginning, and will only be visible when we add the <code>.clipboard--supported</code> class to our <code>div</code>.</p>
<p>To do it, we modify the connect lifecycle callback. </p>
<p>Here is where we can start to see major differences from this latest development version. With the actual production version you would need to specify the CSS class in the controller, effectively doing this:</p>
<pre><code class="lang-javascript">[...]

connect() {
  <span class="hljs-keyword">if</span> (<span class="hljs-built_in">document</span>.queryCommandSupported(<span class="hljs-string">"copy"</span>)) 
    <span class="hljs-built_in">this</span>.element.classList.add(<span class="hljs-string">'clipboard--supported'</span>);
  }
} 

[...]
</code></pre>
<p><strong>There is a new, better way to achieve it.</strong></p>
<h1 id="classes-api">Classes API</h1>
<p>Now, <strong>CSS classes can be actual properties of the controller</strong>. To do so, we need to add some identifiers to our HTML and add a new array to our controller:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">data-controller</span>=<span class="hljs-string">"clipboard"</span> <span class="hljs-attr">data-clipboard-supported-class</span>=<span class="hljs-string">"clipboard--supported"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"clipboard"</span>&gt;</span>

[...]
</code></pre>
<pre><code class="lang-javascript">[...]

application.register(<span class="hljs-string">"clipboard"</span>, <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Stimulus</span>.<span class="hljs-title">Controller</span> </span>{

[...]

  <span class="hljs-keyword">static</span> classes = [<span class="hljs-string">'supported'</span>]

  connect() {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">document</span>.queryCommandSupported(<span class="hljs-string">"copy"</span>)) 
      <span class="hljs-built_in">this</span>.element.classList.add(<span class="hljs-built_in">this</span>.supportedClass);
    }
  } 
[...]
</code></pre>
<p>Great! Now we can access our supported class string from our controller with <code>this.supportedClass</code>. <strong>This will help keep things loosely coupled.</strong></p>
<p>The clipboard real-life example from Stimulus' handbook ends here. Now, to show the other newest additions and use the <em>Classes API</em> once more, we're adding the following functionality:</p>
<ul>
<li>A new style to the "Copy to clipboard" button once it has been clicked</li>
<li>A refresh interval for the one-time password. This will generate a new password every 2.5 seconds</li>
<li>A data attribute to keep track of how many times the password has been generated</li>
</ul>
<h1 id="values-api">Values API</h1>
<p>This, along with the <em>Classes API</em>, is one of the new additions to Stimulus. Before this API you would need to add arbitrary values to your controller with the Data Map API, that is, adding <code>data-[identifier]-[variable-name]</code> to your DOM element, and then parsing that value in your controller. </p>
<p>This created boilerplate such as getters and setters with calls to <code>parseFloat()</code>, <code>parseInt()</code>, <code>JSON.stringify()</code>, etc. This is how it will work with the <em>Values API</em>:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">data-controller</span>=<span class="hljs-string">"clipboard"</span> <span class="hljs-attr">data-clipboard-supporte-class</span>=<span class="hljs-string">"clipboard--supported"</span> <span class="hljs-attr">data-clipboard-refresh-interval-value</span>=<span class="hljs-string">"2500"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"clipboard"</span>&gt;</span>

[...]
</code></pre>
<pre><code class="lang-javascript">[...]

application.register(<span class="hljs-string">"clipboard"</span>, <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Stimulus</span>.<span class="hljs-title">Controller</span> </span>{

[...]

  <span class="hljs-keyword">static</span> values = {
    <span class="hljs-attr">refreshInterval</span>: <span class="hljs-built_in">Number</span>
  }

  connect() {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">document</span>.queryCommandSupported(<span class="hljs-string">"copy"</span>)) 
      <span class="hljs-built_in">this</span>.element.classList.add(<span class="hljs-built_in">this</span>.supportedClass);
    }
    <span class="hljs-comment">// Access refreshInterval value directly</span>
    <span class="hljs-built_in">this</span>.refreshIntervalValue; <span class="hljs-comment">// 2500</span>
  } 
[...]
</code></pre>
<p><strong>Accessing your controller values is now cleaner since you don't need to write your getters and setters, nor do you need to parse from String to the type you need.</strong></p>
<p>Moving forward, let's write the one-time password refresh.</p>
<h1 id="implementing-password-generation">Implementing password generation</h1>
<p>We're going to define a new function to create a new random password. <a target="_blank" href="https://www.arungudelli.com/tutorial/javascript/how-to-create-uuid-guid-in-javascript-with-examples/">I grabbed this random UUID generator snippet from the internet</a>:</p>
<pre><code class="lang-javascript">([<span class="hljs-number">1e7</span>]+<span class="hljs-number">-1e3</span>+<span class="hljs-number">-4e3</span>+<span class="hljs-number">-8e3</span>+<span class="hljs-number">-1e11</span>).replace(<span class="hljs-regexp">/[018]/g</span>, <span class="hljs-function"><span class="hljs-params">c</span> =&gt;</span>
                (c ^ crypto.getRandomValues(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Uint8Array</span>(<span class="hljs-number">1</span>))[<span class="hljs-number">0</span>] &amp; <span class="hljs-number">15</span> &gt;&gt; c / <span class="hljs-number">4</span>).toString(<span class="hljs-number">16</span>));
</code></pre>
<p>Adding it to our Stimulus controller:</p>
<pre><code class="lang-javascript">  connect() {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">document</span>.queryCommandSupported(<span class="hljs-string">"copy"</span>)) 
      <span class="hljs-built_in">this</span>.element.classList.add(<span class="hljs-built_in">this</span>.supportedClass);
    }
    <span class="hljs-keyword">if</span>(<span class="hljs-built_in">this</span>.hasRefreshIntervalValue) {
          <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">this</span>.generateNewPassword(), <span class="hljs-built_in">this</span>.refreshIntervalValue)  
    } 
  } 

  <span class="hljs-comment">// copy function</span>

  generateNewPassword() {
    <span class="hljs-built_in">this</span>.sourceTarget.value = ([<span class="hljs-number">1e7</span>]+<span class="hljs-number">-1e3</span>+<span class="hljs-number">-4e3</span>+<span class="hljs-number">-8e3</span>+<span class="hljs-number">-1e11</span>).replace(<span class="hljs-regexp">/[018]/g</span>, <span class="hljs-function"><span class="hljs-params">c</span> =&gt;</span>
                (c ^ crypto.getRandomValues(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Uint8Array</span>(<span class="hljs-number">1</span>))[<span class="hljs-number">0</span>] &amp; <span class="hljs-number">15</span> &gt;&gt; c / <span class="hljs-number">4</span>).toString(<span class="hljs-number">16</span>));
  }
[...]
</code></pre>
<p>We use <code>setInterval</code> to refresh our password text field each 2500ms since that's the value we defined in the DOM. </p>
<p><strong>Our refresh feature is now working!</strong> Some things still missing:</p>
<ul>
<li>Add new style when copy button is clicked</li>
<li>Keep track of how many times a password is generated</li>
</ul>
<p>Giving all we have learned so far, this is what's need to be done:</p>
<ul>
<li>Add a new CSS class to the stylesheet, DOM element, and controller</li>
<li>Add this new class when the button is clicked, and remove it when the password is refreshed</li>
<li>Add to a counter when the password refreshes</li>
</ul>
<p>This is how it will look at the end:</p>
<pre><code class="lang-css"><span class="hljs-comment">/* CSS */</span>

<span class="hljs-selector-class">.clipboard-button</span> {
 <span class="hljs-attribute">display</span>: none;
}

<span class="hljs-selector-class">.clipboard--supported</span> <span class="hljs-selector-class">.clipboard-button</span> {
  <span class="hljs-attribute">display</span>: initial;
}

<span class="hljs-selector-class">.clipboard--success</span> <span class="hljs-selector-class">.clipboard-button</span> {
  <span class="hljs-attribute">background-color</span>: palegreen;
}
</code></pre>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- HTML --&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">data-controller</span>=<span class="hljs-string">"clipboard"</span> 
     <span class="hljs-attr">data-clipboard-refresh-interval-value</span>=<span class="hljs-string">"2500"</span>
     <span class="hljs-attr">data-clipboard-supported-class</span>=<span class="hljs-string">"clipboard--supported"</span> 
     <span class="hljs-attr">data-clipboard-success-class</span>=<span class="hljs-string">"clipboard--success"</span>      
     <span class="hljs-attr">data-clipboard-times-generated-value</span>=<span class="hljs-string">"1"</span> 
     &gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>
        One-time password: <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">data-clipboard-target</span>=<span class="hljs-string">"source"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"fbbb5593-1885-4164-afbe-aba1b87ea748"</span> <span class="hljs-attr">readonly</span>=<span class="hljs-string">"readonly"</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">data-action</span>=<span class="hljs-string">"clipboard#copy"</span>               
              <span class="hljs-attr">class</span>=<span class="hljs-string">"clipboard-button"</span> &gt;</span>
        Copy to Clipboard
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<pre><code class="lang-javascript"> <span class="hljs-comment">// JavaScript</span>

 (<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> application = Stimulus.Application.start()

    application.register(<span class="hljs-string">"clipboard"</span>, <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Stimulus</span>.<span class="hljs-title">Controller</span> </span>{

      <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">targets</span>() {
        <span class="hljs-keyword">return</span> [<span class="hljs-string">'source'</span>]
      }

      <span class="hljs-keyword">static</span> values = {              
        <span class="hljs-attr">refreshInterval</span>: <span class="hljs-built_in">Number</span>,
        <span class="hljs-attr">timesGenerated</span>: <span class="hljs-built_in">Number</span>
      }

      <span class="hljs-keyword">static</span> classes = [<span class="hljs-string">'supported'</span>, <span class="hljs-string">'success'</span>];

      connect() {                 
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">document</span>.queryCommandSupported(<span class="hljs-string">"copy"</span>)) {
          <span class="hljs-built_in">this</span>.element.classList.add(<span class="hljs-built_in">this</span>.supportedClass);                
        }                            
        <span class="hljs-keyword">if</span>(<span class="hljs-built_in">this</span>.hasRefreshIntervalValue) {
          <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">this</span>.generateNewPassword(), <span class="hljs-built_in">this</span>.refreshIntervalValue)  
        } 
      }


      copy() {              
        <span class="hljs-built_in">this</span>.sourceTarget.select();
        <span class="hljs-built_in">document</span>.execCommand(<span class="hljs-string">'copy'</span>);
        <span class="hljs-built_in">this</span>.element.classList.add(<span class="hljs-built_in">this</span>.successClass);
      }

      generateNewPassword() {              
        <span class="hljs-built_in">this</span>.sourceTarget.value = ([<span class="hljs-number">1e7</span>]+<span class="hljs-number">-1e3</span>+<span class="hljs-number">-4e3</span>+<span class="hljs-number">-8e3</span>+<span class="hljs-number">-1e11</span>).replace(<span class="hljs-regexp">/[018]/g</span>, <span class="hljs-function"><span class="hljs-params">c</span> =&gt;</span>
          (c ^ crypto.getRandomValues(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Uint8Array</span>(<span class="hljs-number">1</span>))[<span class="hljs-number">0</span>] &amp; <span class="hljs-number">15</span> &gt;&gt; c / <span class="hljs-number">4</span>).toString(<span class="hljs-number">16</span>));     
        <span class="hljs-built_in">this</span>.element.classList.remove(<span class="hljs-built_in">this</span>.successClass);
        <span class="hljs-built_in">this</span>.timesGeneratedValue++;
      }                  

      <span class="hljs-comment">// NEW! Read about it below</span>
      timesGeneratedValueChanged() {              
        <span class="hljs-keyword">if</span>(<span class="hljs-built_in">this</span>.timesGeneratedValue !== <span class="hljs-number">0</span> &amp;&amp; <span class="hljs-built_in">this</span>.timesGeneratedValue % <span class="hljs-number">3</span> === <span class="hljs-number">0</span>) {
          <span class="hljs-built_in">console</span>.info(<span class="hljs-string">'You still there?'</span>);
        }
      }

    });

 })();
</code></pre>
<p>Apart from what we've already discussed about the <em>Values API</em>, there is also something new: <strong>Value changed callbacks</strong>.</p>
<p>These callbacks are called whenever a value changes, and also once when the controller is initialized. They are connected automatically given we follow the naming convention of <code>[valueName]ValueChanged()</code>. </p>
<p>We use it to log a message each time the password has been refreshed three times, but they can help with state management in a more complex use case. </p>
<h1 id="wrapping-up">Wrapping up</h1>
<p>I've created multiple Stimulus controllers for my daily job, and I must say that I always end up pleased with the results. Stimulus encourages you to keep related code together and, combined with the additional HTML markup required, ends up making your code much more readable.</p>
<p>If you haven't tried it yet, I highly recommend going for it! It offers a different perspective, one of magic 🧙🏻‍♂️. </p>
<p>Thanks for reading me 👋🏼.</p>
]]></content:encoded></item><item><title><![CDATA[My privacy setup 🔐]]></title><description><![CDATA[One of the most precious things in this modern world is data, our own personal data.

With data, companies can persuade us to do, buy, like, and do whatever fits their interests- and most of the time those interests not really match ours.
I'm confide...]]></description><link>https://blog.davidojeda.dev/my-privacy-setup</link><guid isPermaLink="true">https://blog.davidojeda.dev/my-privacy-setup</guid><category><![CDATA[privacy]]></category><dc:creator><![CDATA[David Ojeda]]></dc:creator><pubDate>Tue, 20 Aug 2019 23:42:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1600495943899/0N7gzO9A-.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>One of the most precious things in this modern world is data, our own personal data.</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1600495931205/pSY9XHLdM.gif" alt="Knowledge is power Game Of Thrones GIF" /></p>
<p>With data, companies can persuade us to do, buy, like, and do whatever fits their interests- and most of the time those interests not really match ours.</p>
<p>I'm confident that you have already been in this situation: you are talking to a friend about, let's say, going to Disneyland, and a couple hours later while happily scrolling through your Instagram feed you see a sponsored Disneyland add. <strong>It's not a coincidence</strong>. </p>
<p>Or maybe you were chatting with a friend through Whatsapp, talking about babies. Just to receive an email from Amazon about some baby clothes offers. <strong>It's not a coincidence</strong>. </p>
<p><strong>Many apps read, hear, and collect more information than we can imagine.</strong> Instagram hears what you say, Whatsapp reads what you write, Google knows where you are, and all that data is shared among so many other 3rd parties looking for profit. <strong>Knowledge is power</strong>.</p>
<p>If you think, "I don't really care, I have nothing to hide", <strong>it's not about hiding something</strong>. It's about unauthorized entities knowing everything about you. It's your life.</p>
<p>And in order to protect your data, your privacy, I put together this post with the details of my own privacy setup. It includes some rules I follow, as well as apps that help me protect my privacy.</p>
<hr />
<p>Some general rules that apply to most of the things you do:</p>
<ul>
<li><p><strong>Don't give away your information just because it's easy.</strong> If you are signing up for a new service, I recommend you to investigate it before. You don't want to share details about you with a company that is known to sell them to someone else</p>
</li>
<li><p><strong>For some temporary signups, you don't need real information.</strong> Some establishments, like coffee shops, ask you for your email address and name to connect to their WiFi, but most times you don't need a real email address since they don't ask for confirmation</p>
</li>
<li><p><strong>Check allowed permissions of apps you install</strong>. There are apps that ask for way more than they need. If you can match every permission with a specific feature of the app, then you are probably good to go. <a target="_blank" href="https://lifehacker.com/why-does-this-android-app-need-so-many-permissions-5991099">Here is a Lifehacker guide for more information on the topic</a></p>
</li>
<li><p><strong>Review privacy settings</strong>. Many apps have a privacy section on its settings where you can change what data can they collect or if targeted ads should be shown to you. Here's Twitter's for example:</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1600495933365/1LhX0i49r.png" alt="Twitter's privacy settings" /></p>
<p><strong>Now let's get to the apps I use!</strong></p>
<hr />
<h2 id="bravehttpsbravecombrave-logohttpscdnhashnodecomreshashnodeimageuploadv1600495935085da9dcgw4npng"><a target="_blank" href="https://brave.com/">Brave</a><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1600495935085/DA9Dcgw4N.png" alt="Brave logo" /></h2>
<p>An open source privacy-first browser that comes with ad and tracker blocker by default. I have never missed Chrome or Firefox since I made the switch. </p>
<p>It's based on Chromium, so all your Developer Tools options are available to you. It also lets you open new tabs with Tor, for a truly private experience. </p>
<h2 id="duckduckgohttpsduckduckgocomaboutduckduckgohttpscdnhashnodecomreshashnodeimageuploadv1600495936570eb8970kikpng"><a target="_blank" href="https://duckduckgo.com/about">DuckDuckGo</a><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1600495936570/Eb8970Kik.png" alt="DuckDuckGo" /></h2>
<p>A privacy-first search engine. DuckDuckGo doesn't store any of your searches, nor does it track you in any way. Every search you make is as it was your first search ever!</p>
<p>It does not have some quick responses as Google does, but it's totally worth it anyway. I have never missed Google Search either. </p>
<h2 id="signalhttpswwwsignalorgsignal-app-logohttpscdnhashnodecomreshashnodeimageuploadv1600495938167up8yh802vpng"><a target="_blank" href="https://www.signal.org/">Signal</a><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1600495938167/Up8Yh802v.png" alt="Signal app logo" /></h2>
<p>Signal is a messaging app that, you guessed it, it's privacy-first; it offers E2E encryption on everything. Plus, it's open source and a nonprofit organization. This app is even recommended by Edward Snowden!</p>
<p>The downside I have found so far is that not many people are willing to switch or use yet another messaging platform. Whatsapp is way to strong here in México at least.</p>
<h2 id="bouncerhttpsplaygooglecomstoreappsdetailsidcomsamrustonpermissionandhlenusbouncer-logohttpscdnhashnodecomreshashnodeimageuploadv1600495939780vrbh5ftpmpng"><a target="_blank" href="https://play.google.com/store/apps/details?id=com.samruston.permission&amp;hl=en_US">Bouncer</a><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1600495939780/VRBH5ftpM.png" alt="Bouncer logo" /></h2>
<p>This one is only for Android users. Bouncer is an app that allows you to grant permissions temporarily. It will automatically deactivate permissions after you have used the feature that required them. </p>
<p>For example, if you were to share your location using Whatsapp, you would need to accept the location permission first, then Bouncer will ask you <em>once</em> if you would like to remove or keep the permission after you have finished using it. As soon as you exit the app, Bouncer will remove location permissions from Whatsapp so it can't access them in the background. It's awesome!</p>
<p>Moreover, Bouncer doesn't even require internet permission, so you shouldn't fear of it sharing your information.</p>
<h2 id="torhttpswwwtorprojectorgtor-projecthttpscdnhashnodecomreshashnodeimageuploadv1600495941282rgw19bukjpng"><a target="_blank" href="https://www.torproject.org/">Tor</a><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1600495941282/rgW19bUKj.png" alt="Tor Project" /></h2>
<p>One of the world's strongest tool for privacy right now. It comes in many forms such as Tor Browser, Tor OS and private tabs with Tor from Brave, and it allows you to navigate the internet freely and with privacy thanks to its multiple layers of encryption.</p>
<p>It is slower than normal since the traffic goes through a series of relays, but for some people such as bloggers or journalists that must remain private, it's a jewel 💎.</p>
<h2 id="nordvpnhttpsnordvpncom-nordvpn-logohttpscdnhashnodecomreshashnodeimageuploadv1600495942676j125z5xlipng"><a target="_blank" href="https://nordvpn.com">NordVPN</a> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1600495942676/J125Z5XLi.png" alt="NordVPN logo" /></h2>
<p>A VPN will allow you to protect your IP address and to make sure no one can see which websites you visit. It can also help you block ads and protect you on public W-Fi networks. </p>
<p>I use NordVPN and it works just great! It offers hundreds of servers around the world and some more specialty servers such as double VPN, P2P and Onion over VPN.</p>
<hr />
<p>There are some specific apps/companies that I try to avoid whenever possible. Here are some of them:</p>
<h2 id="google">Google</h2>
<p>I personally don't trust many giant tech companies like Google. They make really good products, I can't deny that. However, their business model thrives when users give away their privacy. </p>
<p>Take Gboard for example. This keyboard will have access to your camera, microphone, location and basically to anything you type with it. I admit the keyboard has really great features, but I would rather use something else than this keyboard just from the fact it comes from Google. </p>
<p>I do use Google Maps frequently, I just have many features disabled such as recommendations and location history. </p>
<h2 id="social-networks">Social networks</h2>
<p>This should be a no-brainer. Social networks profit from gathering personal information and then using it for ad targeting. I don't have Instagram nor Facebook, but I still have Twitter. I don't use the native apps though, I always check it on the website be it on mobile or desktop.</p>
<p>Leaving your social networks might feel impossible to you since you might have family and friends with whom you talk to or at least follow, and that's okay. I would recommend just not to share too many personal details on your posts.</p>
<hr />
<h2 id="closing-out">Closing out</h2>
<p><strong>Your privacy is rapidly becoming a privilege rather than a right, that's why I use all these apps and tips to keep mine.</strong> </p>
<p>I don't pretend to cover everything there is about online privacy on this post, but hopefully it sparks your curiosity to learn and do more to protect what's yours. I still have plenty of things to do, and my next objective is to completely stop using Google Products.</p>
<p>Here are many resources I have studied/read/watched to keep myself informed about this. I highly recommend to <strong>check the DuckDuckGo resources below</strong>, they have immense high quality information about why you should care about privacy.</p>
<ul>
<li><p>DuckDuckGo resources:</p>
<ul>
<li><a target="_blank" href="https://spreadprivacy.com">Spread Privacy</a></li>
<li><a target="_blank" href="https://spreadprivacy.com/three-reasons-why-the-nothing-to-hide-argument-is-flawed/">Three reasons why the nothing to hide argument is flawed</a></li>
<li><a target="_blank" href="https://spreadprivacy.com/tag/privacy-newsletter/">Privacy newsletter</a></li>
</ul>
</li>
<li><p><a target="_blank" href="https://ethical.net/resources/">Ethical alternatives</a></p>
</li>
<li><p><a target="_blank" href="https://www.imdb.com/title/tt3774114/?ref_=fn_al_tt_1">Snowden's documentary</a></p>
</li>
<li><p><a target="_blank" href="https://www.amazon.com/Permanent-Record-Edward-Snowden/dp/1250237238?SubscriptionId=AKIAILSHYYTFIVPWUY6Q&amp;tag=duckduckgo-brave-20&amp;linkCode=xm2&amp;camp=2025&amp;creative=165953&amp;creativeASIN=1250237238">Permanent Record, by Edward Snowden</a></p>
</li>
<li><p><a target="_blank" href="https://darknetdiaries.com">Darknet Diaries podcast</a></p>
</li>
<li><p><a target="_blank" href="https://spreadprivacy.com/delete-google-search-history/">Delete Google Search History</a></p>
</li>
</ul>
<p><em>Cover image was generated using <a target="_blank" href="https://github.com/PJijin/Cover-Image-Generator/">Cover-Image-Generator</a> 💙</em></p>
<p><strong><em>I hope you find some of what I have shared here useful, and if you know something else I should be doing please leave me a comment!</em></strong></p>
]]></content:encoded></item><item><title><![CDATA[Groovy delegation strategy for query re-use in Grails]]></title><description><![CDATA[The problem
Many of our web app views/pages, among other features, consist of a list of entries from a database table. For example, you can go to /customer/list and get a list and count of your customers. This is fairly common for MVC apps like ours....]]></description><link>https://blog.davidojeda.dev/groovy-delegation-strategy-for-query-re-use-in-grails</link><guid isPermaLink="true">https://blog.davidojeda.dev/groovy-delegation-strategy-for-query-re-use-in-grails</guid><category><![CDATA[hibernate]]></category><dc:creator><![CDATA[David Ojeda]]></dc:creator><pubDate>Tue, 12 Mar 2019 17:01:21 GMT</pubDate><content:encoded><![CDATA[<h2 id="the-problem">The problem</h2>
<p>Many of our web app <em>views</em>/pages, among other features, consist of a list of entries from a database table. For example, you can go to <em>/customer/list</em> and get a list and count of your customers. This is fairly common for MVC apps like ours.</p>
<p>What we've found is that we end up repeating a lot of code when getting those queries from the database since many of our Domains/tables are filtered by the same fields- current status of the entry (enabled or disabled), belongs to a certain company, user permissions allow him/her to see it, etc.</p>
<p>So we would end up with something like this to get the information for those <em>list</em> views:</p>
<pre><code class="lang-java">    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Customer</span> </span>{
        Company company
        <span class="hljs-keyword">boolean</span> enabled
    }      

    <span class="hljs-function">def <span class="hljs-title">customerList</span><span class="hljs-params">(def params)</span> </span>{
        Customer.createCriteria().list {
            eq <span class="hljs-string">'enabled'</span>, params.<span class="hljs-keyword">boolean</span>(<span class="hljs-string">'enabled'</span>)
            eq <span class="hljs-string">'company'</span>,  params.company
            maxResults params.<span class="hljs-keyword">int</span>(<span class="hljs-string">'max'</span>, <span class="hljs-number">10</span>)
        }
    }

    <span class="hljs-function">def <span class="hljs-title">customerCount</span><span class="hljs-params">(def params)</span></span>{
        Customer.createCriteria().get {
            eq <span class="hljs-string">'enabled'</span>, params.<span class="hljs-keyword">boolean</span>(<span class="hljs-string">'enabled'</span>)
            eq <span class="hljs-string">'company'</span>, params.company
            projections {
                countDistinct <span class="hljs-string">'id'</span>
            }
        }
    }
</code></pre>
<pre><code class="lang-java">    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> </span>{
        Company company
        <span class="hljs-keyword">boolean</span> enabled
    }

    <span class="hljs-function">def <span class="hljs-title">userList</span><span class="hljs-params">(def params)</span> </span>{
        User.createCriteria().list {
            eq <span class="hljs-string">'enabled'</span>, params.<span class="hljs-keyword">boolean</span>(<span class="hljs-string">'enabled'</span>)
            eq <span class="hljs-string">'company'</span>, params.company
            maxResults params.<span class="hljs-keyword">int</span>(<span class="hljs-string">'max'</span>, <span class="hljs-number">10</span>)
        }
    }

    <span class="hljs-function">def <span class="hljs-title">userCount</span><span class="hljs-params">(def params)</span></span>{
        User.createCriteria().get {
            eq <span class="hljs-string">'enabled'</span>, params.<span class="hljs-keyword">boolean</span>(<span class="hljs-string">'enabled'</span>)
            eq <span class="hljs-string">'company'</span>, params.company
            projections {
                countDistinct <span class="hljs-string">'id'</span>
            }
        }
    }
</code></pre>
<p><strong>The body of both list and count methods are almost the same</strong>, the only difference is the table or Domain to which the query goes.</p>
<p>So we're looking for a way to refactor this code in order to avoid repeating ourselves.</p>
<h2 id="the-solution">The solution</h2>
<p>We will use a <strong>Groovy closure delegate</strong> to avoid repeating these type of queries. More specifically, we will use something called Closure Delegation Strategy to achieve it. Let's get to it!</p>
<h2 id="background-theory">Background theory</h2>
<h3 id="groovy-closures">Groovy Closures</h3>
<p>Closures by themselves deserve a dedicated blog post, but I find Groovy docs to be really clear and to the point. If you haven't worked with closures before or you are looking for a deeper understanding, <a target="_blank" href="http://groovy-lang.org/closures.html#_delegation_strategy">please take a look at the docs</a>.</p>
<p>Right now I will only- briefly -explain some closure concepts.</p>
<p>A Groovy closure looks like this:</p>
<pre><code class="lang-java">    def myClosure = { <span class="hljs-string">"This is a closure"</span> }
</code></pre>
<p>and you can call it like this:</p>
<pre><code class="lang-java">    <span class="hljs-function"><span class="hljs-keyword">assert</span> <span class="hljs-title">myClosure</span><span class="hljs-params">()</span> </span>== <span class="hljs-string">"This is a closure"</span>
</code></pre>
<p>A Groovy closure defines three main components:</p>
<ul>
<li><strong>this:</strong> The <em>class</em> where the closure is defined</li>
<li><strong>owner:</strong> The <em>enclosing object</em> where the closure is defined, could be a class or another closure. The difference with <strong>this</strong>: it will return the <strong>direct</strong> enclosing object. If the closure is defined in an inner class, it will return that class, while <strong>this</strong> will return the top-level class.</li>
<li><strong>delegate:</strong> Is a third party object where methods calls or properties are resolved whenever the receiver of the message is not defined</li>
</ul>
<p>It's hard to fully understand these concepts without prior experience with them, so here I provide a Groovy snippet where you can play and move things around to fully grasp the closure components. </p>
<p>The run() method includes some comments to clarify what's going on. You can <a target="_blank" href="https://groovy-playground.appspot.com/">copy-paste it here to try it out</a>! </p>
<pre><code class="lang-java">    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OuterClass</span> </span>{

        String name = <span class="hljs-string">'outer class'</span>

        <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{}

        <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InnerClass</span> </span>{

            String name = <span class="hljs-string">'inner class'</span>

            def ownerClosure = { owner }
            def thisClosure = { <span class="hljs-keyword">this</span> }
            def delegateClosure = { delegate }

            def shoutMyName = {
                name.toUpperCase()
            }
        }

        def nestedThisClosure = {
            def closure = { <span class="hljs-keyword">this</span> }
            closure()
        }

        def nestedOwnerClosure = {
            def closure = { owner }
            closure()
        }

        <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>{
            def inner = <span class="hljs-keyword">new</span> InnerClass()

            <span class="hljs-comment">// Here, **this** and **owner** are equal, that is, the enclosing class (InnerClass)</span>
            <span class="hljs-keyword">assert</span> inner.thisClosure() == inner
            <span class="hljs-keyword">assert</span> inner.ownerClosure() == inner

            <span class="hljs-comment">// Here, **this** is the enclosing class (OuterClass) and **owner** is the enclosing closure (nestedOwnerClosure)</span>
            <span class="hljs-function"><span class="hljs-keyword">assert</span> <span class="hljs-title">nestedThisClosure</span><span class="hljs-params">()</span> </span>== <span class="hljs-function"><span class="hljs-keyword">this</span>
            <span class="hljs-keyword">assert</span> <span class="hljs-title">nestedOwnerClosure</span><span class="hljs-params">()</span> </span>== nestedOwnerClosure

            <span class="hljs-comment">// The **delegate**, by default, is set to the **owner**</span>
            <span class="hljs-keyword">assert</span> inner.delegateClosure() == inner.ownerClosure()
            <span class="hljs-keyword">assert</span> inner.shoutMyName() == <span class="hljs-string">'INNER CLASS'</span>

            <span class="hljs-comment">// And even though we can change the **delegate**</span>
            inner.shoutMyName.delegate = <span class="hljs-keyword">this</span>

            <span class="hljs-comment">// We are still getting our InnerClass name, why?</span>
            <span class="hljs-keyword">assert</span> inner.shoutMyName() == <span class="hljs-string">'INNER CLASS'</span>

            <span class="hljs-comment">// The default **resolveStrategy** for closures is **OWNER_FIRST**,</span>
            <span class="hljs-comment">// that means that the name field of the **owner** is resolved first.</span>
            <span class="hljs-comment">// Luckily, we can also change the **resolveStrategy** of the closure</span>
            inner.shoutMyName.resolveStrategy = Closure.DELEGATE_FIRST
            <span class="hljs-keyword">assert</span> inner.shoutMyName() == <span class="hljs-string">'OUTER CLASS'</span>

        }
    }

    <span class="hljs-keyword">new</span> OuterClass().run()
</code></pre>
<p>Now that we have more knowledge of the power of closures, we can implement our delegation strategy to re-use our queries.</p>
<h2 id="the-implementation">The implementation</h2>
<p>As mentioned at the beginning, we have two very similar Domains with similar fields and we need to get both a list and count of those Domains at some point.</p>
<p>To solve this, we're going to <strong>create a closure</strong> to get the list and another closure for the count. Then, <strong>we're changing the closure's delegate</strong> according to the Domain we are querying.</p>
<p>Let's suppose we have a ListService where we're going to manage all this logic.</p>
<p>Here are the closures:</p>
<pre><code class="lang-java">    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ListService</span> </span>{

        <span class="hljs-keyword">private</span> def getList = { def params -&gt;
            createCriteria().list {
                eq <span class="hljs-string">'company'</span>, params.<span class="hljs-function">company
                <span class="hljs-title">if</span><span class="hljs-params">(!params.<span class="hljs-keyword">boolean</span>(<span class="hljs-string">'showDisabled'</span>, <span class="hljs-keyword">false</span>)</span>) </span>{
                    eq <span class="hljs-string">'enabled'</span>, <span class="hljs-keyword">true</span>
                }
                maxResults params.<span class="hljs-keyword">int</span>(<span class="hljs-string">'max'</span>, <span class="hljs-number">10</span>)
            }
        }

        <span class="hljs-keyword">private</span> def getCount = { def params -&gt;
            createCriteria().get {
                eq <span class="hljs-string">'company'</span>, params.<span class="hljs-function">company
                <span class="hljs-title">if</span><span class="hljs-params">(!params.<span class="hljs-keyword">boolean</span>(<span class="hljs-string">'showDisabled'</span>, <span class="hljs-keyword">false</span>)</span>) </span>{
                    eq <span class="hljs-string">'enabled'</span>, <span class="hljs-keyword">true</span>
                }
                projections {
                    countDistinct <span class="hljs-string">'id'</span>
                }
            }
        }

    }
</code></pre>
<p>Now, we need to change the delegate of the closures dynamically, depending on the Domain to query:</p>
<pre><code class="lang-java">    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ListService</span> </span>{

        [...]

        <span class="hljs-function">def <span class="hljs-title">getDomainList</span><span class="hljs-params">(def params, def domain)</span> </span>{
            getList.delegate = <span class="hljs-function">domain
            <span class="hljs-title">getList</span><span class="hljs-params">(params)</span>
        }

        def <span class="hljs-title">getDomainCount</span><span class="hljs-params">(def params, def domain)</span> </span>{
            getCount.delegate = <span class="hljs-function">domain
            <span class="hljs-title">getCount</span><span class="hljs-params">(params)</span>
        }

    }</span>
</code></pre>
<p>Finally, we're going to use this service from our controller to return the values to our list view:</p>
<pre><code class="lang-java">    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomerController</span> </span>{

        <span class="hljs-function">def listService

        def <span class="hljs-title">list</span><span class="hljs-params">()</span> </span>{
            [
                list: listService.getDomainList(params, Customer),
                count: listService.getDomainCount(params, Customer)
            ]
        }

    }
</code></pre>
<pre><code class="lang-java">    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserController</span> </span>{

        <span class="hljs-function">def listService

        def <span class="hljs-title">list</span><span class="hljs-params">()</span> </span>{
            [
                list: listService.getDomainList(params, User),
                count: listService.getDomainCount(params, User)
            ]
        }

    }
</code></pre>
<p>The list view now has everything it needs to render each result and paginate it!</p>
<h2 id="summary">Summary</h2>
<p>The final flow is this:</p>
<ol>
<li>A request comes to controller, e.g. UserController to get user's list</li>
<li>Controller <strong>uses ListService</strong> to query the database. It forwards the params from the request and <strong>specifies the Domain</strong> to query</li>
<li>ListService users the given Domain to <strong>change the closure's delegate</strong> and proceeds to execute the closure</li>
<li>Closure sees the createCriteria() method call and seeks for the object that implements it. Since we didn't change the closure's resolve strategy, <strong>it will look on the owner first</strong>. 
The owner, our ListService, doesn't implement a createCriteria() method, so the closure <strong>now looks at the delegate</strong>. 
The <strong>delegate is our Domain</strong>, and it has a createCriteria method, so it uses it and returns our results all the way back to our controller.</li>
</ol>
<p>Now we have two closures that are being used by multiple Domains to get similar data from the database. </p>
<hr />
<p>This is only one of many powerful use cases of closure delegates in Groovy. Have you used this feature before? If so, let me know in the comments!</p>
<p><strong>Thanks for reading me ❤️</strong></p>
]]></content:encoded></item><item><title><![CDATA[On handling outages: a case study]]></title><description><![CDATA[If you happen to use Basecamp 3 to manage your projects, you might have noticed a huge outage on November 8th, 2018; it lasted almost 5 hours.
The issue was that they failed to use bigint for the primary keys of their tables so they ran out of IDs. T...]]></description><link>https://blog.davidojeda.dev/on-handling-outages-a-case-study</link><guid isPermaLink="true">https://blog.davidojeda.dev/on-handling-outages-a-case-study</guid><category><![CDATA[communication]]></category><dc:creator><![CDATA[David Ojeda]]></dc:creator><pubDate>Fri, 09 Nov 2018 22:50:06 GMT</pubDate><content:encoded><![CDATA[<p>If you happen to use Basecamp 3 to manage your projects, you might have noticed a huge outage on November 8th, 2018; <strong>it lasted almost 5 hours</strong>.</p>
<p>The issue was that they failed to use bigint for the primary keys of their tables so they ran out of IDs. The <em>TLDR</em> solution, taken from David Heinemeier- DHH, creator of Ruby on Rails and Founder and CTO at Basecamp: </p>
<blockquote>
<p>We took half of our replicas offline, did the 3h migration, put them back online, will now be converting the other half of the fleet.</p>
</blockquote>
<p>And I'm not writing this to expose and/or throw <em>**</em> at them. </p>
<p>I'm writing this to <strong>applaud their communication and openness</strong> about the whole outage.</p>
<p>I'm writing this to expose how <strong>over-communication, honesty, humbleness and clarity DO make a difference</strong>, specially on difficult situations.</p>
<hr />
<p>To give you some context, the first notice on their Twitter account about something going wrong was at 5:40 AM on November, 8th:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/basecamp/status/1060527469361537024">https://twitter.com/basecamp/status/1060527469361537024</a></div>
<p>From that tweet and until everything was working again, there were 15 more tweets with constant updates! With the last one being at 10:47 AM, November 8th, signed by DHH himself:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/basecamp/status/1060604787819827201">https://twitter.com/basecamp/status/1060604787819827201</a></div>
<p>All that information is a <strong>huge deal</strong>. You know they are working really hard to get everything up and going, and you might also know that outages can get really messy. </p>
<p>Despite all the chaos that was probably happening, they kept posting updates with specific details of the cause and solution being taken- on their Twitter account, status page and on their blog. And not only that, DHH was also posting some more technical details about the outage to the point where <strong>he links to the pull request that could have saved everything</strong>:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/dhh/status/1060565296048562177">https://twitter.com/dhh/status/1060565296048562177</a></div>
<hr />
<p>I find all this <strong>incredibly valuable and relieving</strong>. Even though it was a really long outage, they handled each and every customer interaction gracefully. I could not get upset with them with so much information about the problem/solution being provided. </p>
<p>Hell, that morning <strong>I was even more productive because Basecamp remained read-only</strong>; I could check on what was pending on my side and just get to it with no distractions.</p>
<p>I've been part, and cause, of outages at my company and it's really stressful. And we don't even handle the amount of traffic Basecamp 3 does.</p>
<p>So, as DHH states it, <strong>this is a reminder to stay humble</strong>. We could be the next ones involved in a situation like this. <strong>We all make mistakes, that's inevitable</strong>, but knowing how to properly communicate them is what matters in the long run.</p>
<hr />
<p>Hope you have enjoyed this short rambling ❤️</p>
]]></content:encoded></item><item><title><![CDATA[Amazon S3 - The Basics]]></title><description><![CDATA[What's Amazon S3?
Amazon Simple Storage Service, called S3, is an object storage solution to reliably store and retrieve any amount of data.
What can it do?
It can store your files, build packages, reports, images, and any type of data you can think ...]]></description><link>https://blog.davidojeda.dev/amazon-s3-the-basics</link><guid isPermaLink="true">https://blog.davidojeda.dev/amazon-s3-the-basics</guid><category><![CDATA[AWS]]></category><category><![CDATA[Amazon S3]]></category><dc:creator><![CDATA[David Ojeda]]></dc:creator><pubDate>Fri, 17 Aug 2018 21:03:06 GMT</pubDate><content:encoded><![CDATA[<h2 id="whats-amazon-s3">What's Amazon S3?</h2>
<p>Amazon <strong>S</strong>imple <strong>S</strong>torage <strong>S</strong>ervice, called <strong>S3</strong>, is an object storage solution to reliably store and retrieve any amount of data.</p>
<h2 id="what-can-it-do">What can it do?</h2>
<p>It can store your files, build packages, reports, images, and any type of data you can think about for later retrieval with a guaranteed 99.999999999% durability. Also, it can restrict access to specific data with very flexible rules.</p>
<h2 id="why-use-it">Why use it?</h2>
<p>S3 is extremely reliable and flexible. You want to use it if you at some point need to store and retrieve:</p>
<ul>
<li>Huge amounts of data for analytics</li>
<li>Files for customers to access</li>
<li>Build files for your applications</li>
<li>Assets such as images and videos</li>
</ul>
<p>Also, it has tons of integrations with other services within AWS.</p>
<h2 id="some-basic-s3-concepts">Some basic S3 concepts</h2>
<ul>
<li><h3 id="buckets">Buckets</h3>
<p>A Bucket is like a top level directory in Linux in the sense that it is a node in level one. A bucket is then the "directory" that stores a group of objects or more directories (with no quotes since these are proper directories now). More clarification below.</p>
</li>
<li><h3 id="objects">Objects</h3>
<p>Objects are the files itself. A bucket can contain many objects.</p>
</li>
</ul>
<p>Bucket and objects example:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1601428779952/4HtJG_MR3.png" alt="S3 URL explained" /></p>
<ul>
<li><h3 id="bucket-policies">Bucket Policies</h3>
<p>S3 bucket policies are the set of rules that explicitly define who has or not access to your bucket and to objects within that bucket.</p>
</li>
<li><h3 id="acls">ACLs</h3>
<p><strong>A</strong>ccess <strong>C</strong>ontrol <strong>L</strong>ists is yet another way to define access to your buckets and objects. However, this is a legacy access control mechanism, so it's best to focus on bucket policies. The cases in which ACLs are needed are, for example, when a bucket policy grows too large- they are limited to 20 kb in size. Or when you want to restrict access to specific objects within a bucket.</p>
</li>
<li><h3 id="lifecycle-rules">Lifecycle Rules</h3>
<p>Lifecycle rules are actions that S3 can apply to a group of objects depending on how much time they have been stored. For instance, you can delete objects that have been on your buckets for more than 90 days. </p>
</li>
</ul>
<p>Apart from these concepts, one of the most important things to know about S3 is its <strong>consistency model</strong>.</p>
<h2 id="s3-consistency-model">S3 Consistency Model</h2>
<p>The consistency model of S3 is called <strong>Read-after-Write consistency</strong>. For example, if you issue a PUT request to create an object on a bucket, the next GET request will <strong>ALWAYS</strong> have the desired object. </p>
<p>However, if you try to issue a GET first (object being non-existent), then a PUT and then another GET, you <em>MIGHT</em> not found the desired object. Issuing the request in this order, the consistency model is now called eventual consistency. </p>
<p>In other words:</p>
<p>Read-after-Write consistency:</p>
<ul>
<li>PUT /my-bucket/photo.png -&gt; 200</li>
<li>GET /my-bucket/photo.png -&gt; 200</li>
</ul>
<p>Eventual consistency:</p>
<ul>
<li>GET /my-bucket/photo.png -&gt; 404</li>
<li>PUT /my-bucket/photo.png -&gt; 200</li>
<li>GET /my-bucket/photo.png -&gt; 404</li>
</ul>
<p>You also get eventual consistency when you delete an existing object. That is, you <em>MIGHT</em> see an object listed on your bucket even though you already deleted it a couple of seconds ago.</p>
<p>This is because changes made to your S3 buckets need some time to propagate and replicate through AWS servers.</p>
<h2 id="some-s3-use-cases">Some S3 use cases</h2>
<ul>
<li>Store AWS Load Balancer logs for further analysis</li>
<li>Host static websites</li>
<li>Store assets for a static web page</li>
<li>Store exported data for your customers</li>
</ul>
<h2 id="wrap-up">Wrap up</h2>
<p>This covers the very basics of S3 which are enough to get started with the service. S3 is one of the most used services of AWS due to its reliability, durability and integration with many other services inside and outside AWS.</p>
<p><strong>Thanks for reading me!</strong> ❤️</p>
<hr />
]]></content:encoded></item><item><title><![CDATA[Make Requests from Docker Container Running on Jenkins]]></title><description><![CDATA[This post is about a specific problem I encountered where I couldn't make any requests from my Docker container to the outside world. It was my first time working with Jenkins Pipelines and decided to use Docker as my isolation agent.
Some facts rela...]]></description><link>https://blog.davidojeda.dev/make-requests-from-docker-container-running-on-jenkins</link><guid isPermaLink="true">https://blog.davidojeda.dev/make-requests-from-docker-container-running-on-jenkins</guid><category><![CDATA[Docker]]></category><category><![CDATA[Jenkins]]></category><dc:creator><![CDATA[David Ojeda]]></dc:creator><pubDate>Fri, 25 May 2018 21:43:16 GMT</pubDate><content:encoded><![CDATA[<p>This post is about a specific problem I encountered where I <strong>couldn't make any requests from my Docker container to the outside world</strong>. It was my first time working with Jenkins Pipelines and decided to use Docker as my isolation agent.</p>
<p>Some facts related to my environment:</p>
<ul>
<li>Jenkins is running on Tomcat, not on a Docker container</li>
<li>I have port redirection (80 to 8080 and 443 to 8443) using <strong>iptables</strong></li>
</ul>
<h2 id="code-in-question">Code in question</h2>
<p>The relevant part of my Jenkinsfile is this:</p>
<pre><code class="lang-groovy">stage('build and test apps') {
  failFast true
  parallel {
    stage('front end') {
      steps {
        sh 'npm install --prefix front_end/'
        sh 'npm run build --prefix front_end/'
        sh 'npm run test --prefix front_end/'
      }
    }

    stage('back end') {
      steps {
        sh 'npm install --prefix back_end/'
        sh 'npm run test --prefix back_end/'
      }
    }
  }
}
</code></pre>
<h2 id="the-logs">The logs</h2>
<p>The requests needed on the <code>npm install</code> command were <strong>failing with 403 http errors</strong> and this log was appearing:</p>
<pre><code class="lang-text">npm ERR! Unexpected token &lt; in JSON at position 0
npm ERR! &lt;html&gt;&lt;head&gt;&lt;meta http-equiv='refresh' content='1;url=/login?from=%2Freact-scripts'/&gt;&lt;script&gt;window.location.replace('/login?from=%2Freact-scripts');&lt;/script&gt;&lt;/head&gt;&lt;body style='background-color:white; color:white;'&gt;
npm ERR!
npm ERR!
npm ERR! Authentication required
npm ERR! &lt;!--
npm ERR! You are authenticated as: anonymous
npm ERR! Groups that you are in:
npm ERR!
npm ERR! Permission you need to have (but didn't): hudson.model.Hudson.Read
npm ERR!  ... which is implied by: hudson.security.Permission.GenericRead
npm ERR!  ... which is implied by: hudson.model.Hudson.Administer
npm ERR! --&gt;
npm ERR!
npm ERR! &lt;/body&gt;&lt;/html&gt;
npm ERR!
npm ERR! If you need help, you may report this error at:
npm ERR!     &lt;https://github.com/npm/npm/issues&gt;
</code></pre>
<p>After reading the logs, the first thing I thought was: this is something related to permissions of the user running the Docker container- <strong>tomcat</strong> in my case -but they weren't.</p>
<p>After debugging for quite a while, I stumbled upon <a href="https://stackoverflow.com/questions/44264082/why-is-jenkins-on-the-docker-host-responding-to-http-requests-from-inside-contai" target="_blank">some divine light on StackOverflow</a>. Thanks to this, I found that I was redirecting <strong>all</strong> port 80 and 443 trafic from <strong>everywhere</strong> to their corresponding 8080 and 8443 ports.</p>
<h2 id="the-solution">The solution</h2>
<p><strong>Alter the iptables to create a negated rule and avoid redirects from the Docker subnet</strong>.</p>
<p>First, check your current iptables with this command: <code>iptables -t nat -L -n --line-numbers</code></p>
<pre><code><span class="hljs-string">Chain</span> <span class="hljs-string">PREROUTING</span> <span class="hljs-string">(policy</span> <span class="hljs-string">ACCEPT)</span>
<span class="hljs-string">num</span>  <span class="hljs-string">target</span>     <span class="hljs-string">prot</span> <span class="hljs-string">opt</span> <span class="hljs-string">source</span>               <span class="hljs-string">destination</span>
<span class="hljs-number">1</span>    <span class="hljs-string">REDIRECT</span>   <span class="hljs-string">tcp</span>  <span class="hljs-string">--</span>  <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/0</span>            <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/0</span>            <span class="hljs-string">tcp</span> <span class="hljs-string">dpt:80</span> <span class="hljs-string">redir</span> <span class="hljs-string">ports</span> <span class="hljs-number">8080</span>
<span class="hljs-number">2</span>    <span class="hljs-string">REDIRECT</span>   <span class="hljs-string">tcp</span>  <span class="hljs-string">--</span>  <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/0</span>            <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/0</span>            <span class="hljs-string">tcp</span> <span class="hljs-string">dpt:443</span> <span class="hljs-string">redir</span> <span class="hljs-string">ports</span> <span class="hljs-number">8443</span>
<span class="hljs-number">3</span>               <span class="hljs-string">tcp</span>  <span class="hljs-string">--</span>  <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/0</span>            <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/0</span>
<span class="hljs-number">4</span>    <span class="hljs-string">DOCKER</span>     <span class="hljs-string">all</span>  <span class="hljs-string">--</span>  <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/0</span>            <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/0</span>            <span class="hljs-string">ADDRTYPE</span> <span class="hljs-string">match</span> <span class="hljs-string">dst-type</span> <span class="hljs-string">LOCAL</span>

<span class="hljs-string">Chain</span> <span class="hljs-string">INPUT</span> <span class="hljs-string">(policy</span> <span class="hljs-string">ACCEPT)</span>
<span class="hljs-string">num</span>  <span class="hljs-string">target</span>     <span class="hljs-string">prot</span> <span class="hljs-string">opt</span> <span class="hljs-string">source</span>               <span class="hljs-string">destination</span>

<span class="hljs-string">Chain</span> <span class="hljs-string">OUTPUT</span> <span class="hljs-string">(policy</span> <span class="hljs-string">ACCEPT)</span>
<span class="hljs-string">num</span>  <span class="hljs-string">target</span>     <span class="hljs-string">prot</span> <span class="hljs-string">opt</span> <span class="hljs-string">source</span>               <span class="hljs-string">destination</span>
<span class="hljs-number">1</span>    <span class="hljs-string">REDIRECT</span>   <span class="hljs-string">tcp</span>  <span class="hljs-string">--</span>  <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/0</span>            <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>            <span class="hljs-string">tcp</span> <span class="hljs-string">dpt:443</span> <span class="hljs-string">redir</span> <span class="hljs-string">ports</span> <span class="hljs-number">8443</span>
<span class="hljs-number">2</span>    <span class="hljs-string">REDIRECT</span>   <span class="hljs-string">tcp</span>  <span class="hljs-string">--</span>  <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/0</span>            <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>            <span class="hljs-string">tcp</span> <span class="hljs-string">dpt:80</span> <span class="hljs-string">redir</span> <span class="hljs-string">ports</span> <span class="hljs-number">8080</span>
<span class="hljs-number">3</span>    <span class="hljs-string">DOCKER</span>     <span class="hljs-string">all</span>  <span class="hljs-string">--</span>  <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/0</span>           <span class="hljs-type">!127.0.0.0/8</span>          <span class="hljs-string">ADDRTYPE</span> <span class="hljs-string">match</span> <span class="hljs-string">dst-type</span> <span class="hljs-string">LOCAL</span>

<span class="hljs-string">Chain</span> <span class="hljs-string">POSTROUTING</span> <span class="hljs-string">(policy</span> <span class="hljs-string">ACCEPT)</span>
<span class="hljs-string">num</span>  <span class="hljs-string">target</span>     <span class="hljs-string">prot</span> <span class="hljs-string">opt</span> <span class="hljs-string">source</span>               <span class="hljs-string">destination</span>
<span class="hljs-number">1</span>    <span class="hljs-string">MASQUERADE</span>  <span class="hljs-string">all</span>  <span class="hljs-string">--</span>  <span class="hljs-number">172.17</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/16</span>       <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/0</span>

<span class="hljs-string">Chain</span> <span class="hljs-string">DOCKER</span> <span class="hljs-string">(2</span> <span class="hljs-string">references)</span>
<span class="hljs-string">num</span>  <span class="hljs-string">target</span>     <span class="hljs-string">prot</span> <span class="hljs-string">opt</span> <span class="hljs-string">source</span>               <span class="hljs-string">destination</span>
<span class="hljs-number">1</span>    <span class="hljs-string">RETURN</span>     <span class="hljs-string">all</span>  <span class="hljs-string">--</span>  <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/0</span>            <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/0</span>
</code></pre><p>The value <strong>0.0.0.0/0</strong> on the source column refers to <strong>all</strong> trafic. So, in my case, I need to replace the first two entries of both the PREROUTING and OUTPUT chain:</p>
<p><strong>Note</strong>: The default Docker subnet is <strong>172.17.0.0/16</strong>. If you changed the default configurations make sure to change them in the commands also.</p>
<p><strong>PREROUTING</strong></p>
<pre><code><span class="hljs-string">iptables</span> <span class="hljs-string">-t</span> <span class="hljs-string">nat</span> <span class="hljs-string">-R</span> <span class="hljs-string">PREROUTING</span> <span class="hljs-number">1</span> <span class="hljs-string">!</span> <span class="hljs-string">-s</span> <span class="hljs-number">172.17</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/16</span> <span class="hljs-string">-p</span> <span class="hljs-string">tcp</span> <span class="hljs-string">-m</span> <span class="hljs-string">tcp</span> <span class="hljs-string">--dport</span> <span class="hljs-number">80</span> <span class="hljs-string">-j</span> <span class="hljs-string">REDIRECT</span> <span class="hljs-string">--to-ports</span> <span class="hljs-number">8080</span>
</code></pre><pre><code><span class="hljs-string">iptables</span> <span class="hljs-string">-t</span> <span class="hljs-string">nat</span> <span class="hljs-string">-R</span> <span class="hljs-string">PREROUTING</span> <span class="hljs-number">2</span> <span class="hljs-string">!</span> <span class="hljs-string">-s</span> <span class="hljs-number">172.17</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/16</span> <span class="hljs-string">-p</span> <span class="hljs-string">tcp</span> <span class="hljs-string">-m</span> <span class="hljs-string">tcp</span> <span class="hljs-string">--dport</span> <span class="hljs-number">443</span> <span class="hljs-string">-j</span> <span class="hljs-string">REDIRECT</span> <span class="hljs-string">--to-ports</span> <span class="hljs-number">8443</span>
</code></pre><p><strong>OUTPUT</strong></p>
<pre><code><span class="hljs-string">iptables</span> <span class="hljs-string">-t</span> <span class="hljs-string">nat</span> <span class="hljs-string">-R</span> <span class="hljs-string">OUTPUT</span> <span class="hljs-number">1</span> <span class="hljs-string">!</span> <span class="hljs-string">-s</span> <span class="hljs-number">172.17</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/16</span> <span class="hljs-string">-d</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">/32</span> <span class="hljs-string">-p</span> <span class="hljs-string">tcp</span> <span class="hljs-string">-m</span> <span class="hljs-string">tcp</span> <span class="hljs-string">--dport</span> <span class="hljs-number">443</span> <span class="hljs-string">-j</span> <span class="hljs-string">REDIRECT</span> <span class="hljs-string">--to-ports</span> <span class="hljs-number">8443</span>
</code></pre><pre><code><span class="hljs-string">iptables</span> <span class="hljs-string">-t</span> <span class="hljs-string">nat</span> <span class="hljs-string">-R</span> <span class="hljs-string">OUTPUT</span> <span class="hljs-number">2</span> <span class="hljs-string">!</span> <span class="hljs-string">-s</span> <span class="hljs-number">172.17</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/16</span> <span class="hljs-string">-d</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">/32</span> <span class="hljs-string">-p</span> <span class="hljs-string">tcp</span> <span class="hljs-string">-m</span> <span class="hljs-string">tcp</span> <span class="hljs-string">--dport</span> <span class="hljs-number">80</span> <span class="hljs-string">-j</span> <span class="hljs-string">REDIRECT</span> <span class="hljs-string">--to-ports</span> <span class="hljs-number">8080</span>
</code></pre><p>Now, requests from the Docker container return with a 200 http status and everyone is happy 🤗 Hope this helps you if you are having a similar problem!</p>
<p>As a side note, while doing all this, I also needed to learn more about iptables and their different options. I found <a target="_blank" href="https://www.linode.com/docs/security/firewalls/control-network-traffic-with-iptables/">this quick guide from <strong>linode</strong></a> handy.</p>
<p><strong>Thanks for reading me!</strong></p>
]]></content:encoded></item><item><title><![CDATA[How to Find Ghost CSS Elements]]></title><description><![CDATA[I recently came across a bug on our landing page which caused a weird blank space overflow on the right side:

I looked for a couple of hours trying to find any CSS spacing causing it, or some wrong element on my HTML, but couldn't find anything out ...]]></description><link>https://blog.davidojeda.dev/how-to-find-ghost-css-elements</link><guid isPermaLink="true">https://blog.davidojeda.dev/how-to-find-ghost-css-elements</guid><category><![CDATA[CSS]]></category><category><![CDATA[HTML]]></category><category><![CDATA[debugging]]></category><dc:creator><![CDATA[David Ojeda]]></dc:creator><pubDate>Fri, 11 May 2018 16:14:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1600888323306/GeMxJ6t-e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently came across a bug on our landing page which caused a weird blank space overflow on the right side:</p>
<p><img src="https://thepracticaldev.s3.amazonaws.com/i/o1q1hlen9lqdy7dsc7zz.png" alt="Landing page with extra white space on right side" /></p>
<p>I looked for a couple of hours trying to find any CSS spacing causing it, or some wrong element on my HTML, but couldn't find anything out of place. The blank space wasn't even inside the &lt;html&gt; element of the page 🧐</p>
<p>I then <a target="_blank" href="http://wernull.com/2013/04/debug-ghost-css-elements-causing-unwanted-scrolling/">stumbled upon this post</a> and rapidly found the problem. This blog post suggests some CSS styles to make ghost elements visible 👻:</p>
<pre><code class="lang-css">* {
  <span class="hljs-attribute">background</span>: <span class="hljs-number">#000</span> <span class="hljs-meta">!important</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#0f0</span> <span class="hljs-meta">!important</span>;
  <span class="hljs-attribute">outline</span>: solid <span class="hljs-number">#f00</span> <span class="hljs-number">1px</span> <span class="hljs-meta">!important</span>;
}
</code></pre>
<p>Now, I could find the section that was causing the problem:</p>
<p><img src="https://thepracticaldev.s3.amazonaws.com/i/909z7bing8w3g1u0ssmf.png" alt="Landing page with ghost elements visible" /></p>
<p>In the end, it was a matter of fixing some mismatching HTML elements.</p>
<p>Would've had this CSS styles helping me debug from the beginning, could've saved me a couple hours of work 🤦🏻‍♂️</p>
]]></content:encoded></item><item><title><![CDATA[Extend nginx/Apache Proxy Configurations on AWS ElasticBeanstalk]]></title><description><![CDATA[AWS ElasticBeanstalk applications use either an nginx or Apache proxy to relay requests. Using the .ebextensions feature of ElasticBeanstalk we can extend the configuration of these proxies. If you don't know how .ebextensions work you can read more ...]]></description><link>https://blog.davidojeda.dev/extend-nginxapache-proxy-configurations-on-aws-elasticbeanstalk</link><guid isPermaLink="true">https://blog.davidojeda.dev/extend-nginxapache-proxy-configurations-on-aws-elasticbeanstalk</guid><category><![CDATA[AWS]]></category><category><![CDATA[nginx]]></category><dc:creator><![CDATA[David Ojeda]]></dc:creator><pubDate>Fri, 26 Jan 2018 20:31:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1601429135877/AKXVC0hO-.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>AWS ElasticBeanstalk applications use either an nginx or Apache proxy to relay requests. Using the .ebextensions feature of ElasticBeanstalk we can extend the configuration of these proxies. If you don't know how .ebextensions work you can read more <a href="https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/ebextensions.html">here.</a></p>

<p>I'm going to extend the default nginx proxy configurations using .ebextensions. The same procedure can be used to extend an Apache proxy.</p>

<h2>
    Create a .conf file
</h2>

<p>First we need to create a .conf file with the desired directives. A list of nginx directives can be found <a href="http://nginx.org/en/docs/dirindex.html"> here.</a> My conf file- named proxy.conf -increases some timeouts of the proxy:</p>

<pre><code><span class="hljs-attribute">proxy_read_timeout</span> <span class="hljs-number">120</span>s;
<span class="hljs-attribute">proxy_send_timeout</span> <span class="hljs-number">120</span>s;
<span class="hljs-attribute">proxy_connect_timeout</span> <span class="hljs-number">30</span>s;
</code></pre><h2>Create nginx conf.d directory</h2>

<p>Now we need the directory where our configuration file will be. Under .ebextensions, create a directory named 'nginx', and inside it another named 'conf.d'. Then add the file you just created. Your dir structure should look like this:</p>

<ul>
<li>.ebextensions<ul>
<li>nginx<ul>
<li>conf.d<ul>
<li>proxy.conf</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Now, when you deploy a new version of your application, ElasticBeanstalk will automatically copy your files on the <code>.ebextensions/nginx/conf.d/</code> directory to the <code>/etc/nginx/conf.d/</code> directory of your instances.</p>
<p>This all works because the default nginx.conf file- on line 21 -specifies to include all .conf files under the conf.d directory:</p>

<pre><code><span class="hljs-comment"># Elastic Beanstalk Nginx Configuration File</span>

<span class="hljs-attribute">user</span>                    nginx;
<span class="hljs-attribute">error_log</span>               /var/log/nginx/error.log <span class="hljs-literal">warn</span>;
<span class="hljs-attribute">pid</span>                     /var/run/nginx.pid;
<span class="hljs-attribute">worker_processes</span>        auto;
<span class="hljs-attribute">worker_rlimit_nofile</span>    <span class="hljs-number">200000</span>;

<span class="hljs-section">events</span> {
    <span class="hljs-attribute">worker_connections</span>  <span class="hljs-number">1024</span>;
}

<span class="hljs-section">http</span> {
    <span class="hljs-attribute">include</span>       /etc/nginx/mime.types;
    <span class="hljs-attribute">default_type</span>  application/octet-stream;

    <span class="hljs-attribute">log_format</span>  main  <span class="hljs-string">'<span class="hljs-variable">$remote_addr</span> - <span class="hljs-variable">$remote_user</span> [<span class="hljs-variable">$time_local</span>] "<span class="hljs-variable">$request</span>" '</span>
                      <span class="hljs-string">'<span class="hljs-variable">$status</span> <span class="hljs-variable">$body_bytes_sent</span> "<span class="hljs-variable">$http_referer</span>" '</span>
                      <span class="hljs-string">'"<span class="hljs-variable">$http_user_agent</span>" "<span class="hljs-variable">$http_x_forwarded_for</span>"'</span>;

    <span class="hljs-attribute">include</span>       conf.d/<span class="hljs-regexp">*.conf</span>;

    <span class="hljs-attribute">map</span> <span class="hljs-variable">$http_upgrade</span> <span class="hljs-variable">$connection_upgrade</span> {
        <span class="hljs-attribute">default</span>     <span class="hljs-string">"upgrade"</span>;
    }

    <span class="hljs-section">server</span> {
        <span class="hljs-attribute">listen</span>        <span class="hljs-number">80</span> default_server;
        <span class="hljs-attribute">access_log</span>    /var/log/nginx/access.log main;

        <span class="hljs-attribute">client_header_timeout</span> <span class="hljs-number">60</span>;
        <span class="hljs-attribute">client_body_timeout</span>   <span class="hljs-number">60</span>;
        <span class="hljs-attribute">keepalive_timeout</span>     <span class="hljs-number">60</span>;
        <span class="hljs-attribute">gzip</span>                  <span class="hljs-literal">on</span>;
        <span class="hljs-attribute">gzip_comp_level</span>       <span class="hljs-number">4</span>;
        <span class="hljs-attribute">gzip_types</span> text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        <span class="hljs-comment"># Include the Elastic Beanstalk generated locations</span>
        <span class="hljs-attribute">include</span> conf.d/elasticbeanstalk/<span class="hljs-regexp">*.conf</span>;
    }
}
</code></pre><p>The directives from the .conf file will be added to the <em>http</em> block of the default configuration.</p>

<p>If you need to add directives to the <em>server</em> block you will need to add .conf files to the elasticbeanstalk folder (see line 39 of previous Gist). That dir structure would look:</p>

<ul>
<li>.ebextensions<ul>
<li>nginx<ul>
<li>conf.d<ul>
<li>proxy.conf</li>
</ul>
</li>
<li>elasticbeanstalk<ul>
<li>my_other_conf.conf</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Same can be done for an Apache proxy. The difference is on the directory structure. For Apache your structure should be this:</p>

<ul>
<li>.ebextensions<ul>
<li>httpd<ul>
<li>conf<ul>
<li>proxy.conf</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2>Wrap-up</h2>

<p>Using .ebextensions is by far the simplest method to add custom configurations to your nginx or Apache proxy. Create as many configuration files as you need and add them to the corresponding directory under .ebextensions and you are done.</p>]]></content:encoded></item><item><title><![CDATA[Gated Commits with Git]]></title><description><![CDATA[A gated commit, also called a pre-tested commit, is an integration pattern in which a commit is not approved until a set of tests are ran against the code being commited. In other words, the commit does not go through if the test suite fails.

Why do...]]></description><link>https://blog.davidojeda.dev/gated-commits-with-git</link><guid isPermaLink="true">https://blog.davidojeda.dev/gated-commits-with-git</guid><category><![CDATA[Git]]></category><category><![CDATA[ci]]></category><dc:creator><![CDATA[David Ojeda]]></dc:creator><pubDate>Mon, 25 Sep 2017 13:20:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1600986414725/yr_RdFvGa.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A gated commit, also called a <strong>pre-tested</strong> commit, is an integration pattern in which a commit is not approved until a set of tests are ran against the code being commited. In other words, the commit does not go through if the test suite fails.</p>

<p><strong>Why do you want this?</strong> It makes your application more resilient to change since now you are running a set or sub-set of your tests even before that code is available to anyone else.</p>

<p>I am going to show you how to implement a gated commit pattern with Git. In this example, our <strong>unit test suite</strong> will be the <em>gate</em> to allow our commits to make it to the codebase.</p>

<hr />
<h2>What do you need?</h2>

<ul>
    <li>Git</li>
    <li>Your application's Git repo</li>
    <li>A test suite</li>
</ul>

<h3>Starting off: Git Hooks</h3>

<p>We want our tests to run before a commit goes through. Git allows us to run custom commands just before that event happens thanks to Git hooks. I am not going to go into the details on how they work, but conveniently for us, there is a hook called <strong>pre-commit</strong>. This hook is executed just before the commit happens. Perfect spot for our test suite to run.</p>

<h3>Setting up a pre-commit hook</h3>

<p>In your Git repo, there is a folder named .git in which the hooks are stored. If you have never modified any hook, your .git directory structure will look like this:</p>

<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1600986408840/HLzyBuXab.png" alt=".git directory structure" /></p>
<p>To create our hook, we need to have a file called <strong>pre-commit</strong> (no extension required) inside our <strong>hooks</strong> directory. Let's create it. The only thing the file needs to have is the command you use to run your tests. Also, don't forget to make the file executable (chmod +x).</p>

<p>If your application is, let's say, a Ruby application, you probably run your tests using <strong>rake</strong>. If that's the case, your <strong>pre-commit</strong> file will look like this:</p>

<p><code>rake test:units</code></p>
<p>Or if you are into the JS hype, you can probably have this in your file:</p>

<p><code>npm tests</code></p>
<p>Independently of the language/framework you are using, your pre-commit hook needs to have the command to run your unit test suite. And, as long as the code inside the hook returns a <strong>zero exit code</strong>, the hook will allow the code to be commited. Otherwise, the commit will be rejected.</p>

<h3>Testing</h3>

<p>At this point you can go ahead and make a commit and see how our tests are run (and hopefully pass), thus opening the gate and letting the commit pass uncontested.</p>

<p>In the following example I am using a Grails application, and the pre-commit hook contains the following code:</p>

<p><code>grails test-app -unit</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1600986410890/EY_O5jb0f.png" alt="Successful gated commit" /></p>
<p>
    <strong>Success!</strong> 🥳
</p>

<p>In the case that the test suite fails:</p>

<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1600986413146/h6_tHP-Qd.png" alt="Unsuccessful gated commit" /></p>
<p>Sad face 🙁</p>

<h3>Wrapping up</h3>

<p>We have just created a Git pre-commit hook that contains specific commands to execute our app's unit test suite. <strong>Whenever a commit is issued</strong>, our tests run. If tests pass, we have a successful commit, if not, commit is rejected.</p>

<p>You can extend your tests of the pre-commit hook and build something <strong>as complex as you need</strong>. You can, for example, run a linter tool to make sure style guidelines are being followed. Or take it to the next level and integrate it with your Continuous Integration flow using additional hooks.</p>

<p>Hope this helps you build more resilience into your codebase and, ultimately, deliver more value to your customers in a <strong>safe and rapid</strong> fashion!</p>]]></content:encoded></item></channel></rss>