<?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[Ozzie Neher]]></title><description><![CDATA[Ozzie Neher]]></description><link>https://ozzie.sh</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 23:05:50 GMT</lastBuildDate><atom:link href="https://ozzie.sh/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Querying raw SQL with Elixir and Ecto]]></title><description><![CDATA[TL;DR:
alias MyApp.{Repo, Animal}

sql = "SELECT * FROM animals WHERE legs > $1"
params = [2]

%{rows: rows, columns: columns} = Repo.query!(sql, params)

Enum.map(rows, fn row -> Repo.load(Animal, {columns, row}) end)

It was surprisingly difficult ...]]></description><link>https://ozzie.sh/querying-raw-sql-with-elixir-and-ecto</link><guid isPermaLink="true">https://ozzie.sh/querying-raw-sql-with-elixir-and-ecto</guid><category><![CDATA[Elixir]]></category><category><![CDATA[Phoenix framework]]></category><dc:creator><![CDATA[Ozzie Neher]]></dc:creator><pubDate>Thu, 09 Mar 2023 05:03:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1678337295260/854d1a42-e36c-430e-89b5-eb690510abef.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>TL;DR:</p>
<pre><code class="lang-elm"><span class="hljs-title">alias</span> <span class="hljs-type">MyApp</span>.{<span class="hljs-type">Repo</span>, <span class="hljs-type">Animal</span>}

<span class="hljs-title">sql</span> = <span class="hljs-string">"SELECT * FROM animals WHERE legs &gt; $1"</span>
<span class="hljs-title">params</span> = [<span class="hljs-number">2</span>]

%{rows: rows, columns: columns} = <span class="hljs-type">Repo</span>.query!(sql, params)

<span class="hljs-type">Enum</span>.map(rows, fn row -&gt; <span class="hljs-type">Repo</span>.load(<span class="hljs-type">Animal</span>, {columns, row}) end)
</code></pre>
<p>It was surprisingly difficult to find an easy solution on google for querying your database using raw SQL with ecto and elixir. Above is a short snippet that runs a query with parameters, and then loads the result into an ecto schema.</p>
<p><code>Repo.query!</code> is an alias for the underlying db driver you are using, in my case with postgres the function calls <a target="_blank" href="https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.html#query/4"><code>Ecto.Adapters.SQL.query</code></a> . The result object we get back is a <code>%Postgrex.Result{}</code> struct that has the rows as a flat two-dimensional array:</p>
<pre><code class="lang-elm">%<span class="hljs-type">Postgrex</span>.<span class="hljs-type">Result</span>{
  <span class="hljs-keyword">command</span>: :select,
  columns: [<span class="hljs-string">"id"</span>, <span class="hljs-string">"name"</span>, <span class="hljs-string">"legs"</span>],
  rows: [
    [<span class="hljs-number">1</span>, <span class="hljs-string">"cat"</span>, <span class="hljs-number">4</span>],
    [<span class="hljs-number">2</span>, <span class="hljs-string">"dog"</span>, <span class="hljs-number">4</span>],
  ],
  num_rows: <span class="hljs-number">2</span>,
  connection_id: <span class="hljs-number">147</span>,
  messages: []
}
</code></pre>
<p>but this isn't very nice to work with, so we use the handy <a target="_blank" href="https://hexdocs.pm/ecto/Ecto.Repo.html#c:load/2"><code>Repo.load</code></a> function to map our rows into the proper ecto schema (<code>Animal</code> in the example above) so we can work with them as normal.</p>
]]></content:encoded></item><item><title><![CDATA[Using React with Laravel Jetstream]]></title><description><![CDATA[Laravel Jetstream is a Laravel starter kit that gives you a codebase ready to go with things like user management, teams, and two-factor login.
Officially, Jetstream only supports two stacks: Inertia (which uses Vue) or Livewire. Both are great but p...]]></description><link>https://ozzie.sh/using-react-with-laravel-jetstream</link><guid isPermaLink="true">https://ozzie.sh/using-react-with-laravel-jetstream</guid><category><![CDATA[Laravel]]></category><category><![CDATA[React]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Ozzie Neher]]></dc:creator><pubDate>Tue, 10 Aug 2021 01:30:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1628558806652/8zF_K936T.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://jetstream.laravel.com/">Laravel Jetstream</a> is a Laravel starter kit that gives you a codebase ready to go with things like user management, teams, and two-factor login.</p>
<p>Officially, Jetstream only supports two stacks: Inertia (which uses Vue) or Livewire. Both are great but personally I prefer using React so here we are :)</p>
<p>There are a few differences but for the most part it is a 1-1 copy.</p>
<p>To use this in your own project, simply generate a new laravel jetstream project like normal and then run the npm command I created</p>
<pre><code class="lang-bash">$ laravel new myapp --jet --stack=inertia
$ <span class="hljs-built_in">cd</span> myapp
$ npx laravel-jetstream-react install
</code></pre>
<p>If your app is going to use teams, make sure to use the <code>--teams</code> flag for both generators</p>
<pre><code class="lang-bash">$ laravel new myapp --jet --stack=inertia --teams
$ <span class="hljs-built_in">cd</span> myapp
$ npx laravel-jetstream-react install --teams
</code></pre>
<p>If you want to view the source code for the generator you can find it below. This repo is also where the React components live so any PR's or issues can be filed 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://github.com/ozziexsh/laravel-jetstream-react">https://github.com/ozziexsh/laravel-jetstream-react</a></div>
<p>Hope this saves you some time!</p>
]]></content:encoded></item><item><title><![CDATA[Building a nested routes function - a fun exercise]]></title><description><![CDATA[I was reading the Remix documentation and saw a function that they use to programatically define routes. It's pretty cool in that you are given a single function to build your routes, but you can nest these function calls to build and group relative ...]]></description><link>https://ozzie.sh/building-a-nested-routes-function-a-fun-exercise</link><guid isPermaLink="true">https://ozzie.sh/building-a-nested-routes-function-a-fun-exercise</guid><category><![CDATA[General Programming]]></category><category><![CDATA[challenge]]></category><dc:creator><![CDATA[Ozzie Neher]]></dc:creator><pubDate>Sun, 06 Jun 2021 00:48:23 GMT</pubDate><content:encoded><![CDATA[<p>I was reading the <a target="_blank" href="https://docs.remix.run/v0.17/api/app/#routes">Remix documentation</a> and saw a function that they use to programatically define routes. It's pretty cool in that you are given a single function to build your routes, but you can nest these function calls to build and group relative paths together. It got me thinking about how I might implement something like that, so I gave it a shot.</p>
<p>For context, here's what an example of calling this function might look like:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> routes = defineRoutes(<span class="hljs-function">(<span class="hljs-params">route</span>) =&gt;</span> {
  route(<span class="hljs-string">"users"</span>, <span class="hljs-string">"user.js"</span>);
  route(<span class="hljs-string">"users/:id"</span>, <span class="hljs-string">"userShow.js"</span>);

  route(<span class="hljs-string">"companies/:id"</span>, <span class="hljs-string">"companies.js"</span>, <span class="hljs-function">() =&gt;</span> {
    route(<span class="hljs-string">"users"</span>, <span class="hljs-string">"companyUsers.js"</span>);
    route(<span class="hljs-string">"stores"</span>, <span class="hljs-string">"companyStores.js"</span>);
    route(<span class="hljs-string">"stores/:id"</span>, <span class="hljs-string">"companyStore.js"</span>, <span class="hljs-function">() =&gt;</span> {
      route(<span class="hljs-string">"products"</span>, <span class="hljs-string">"companyStoreProduct.js"</span>);
    });
  });

  route(<span class="hljs-string">"roles/:id"</span>, <span class="hljs-string">"roles.js"</span>, <span class="hljs-function">() =&gt;</span> {
    route(<span class="hljs-string">"users"</span>, <span class="hljs-string">"usersForRole.js"</span>);
  });
});

<span class="hljs-built_in">console</span>.log(routes);
<span class="hljs-comment">// output</span>
[
  { <span class="hljs-attr">path</span>: <span class="hljs-string">"users"</span>, <span class="hljs-attr">file</span>: <span class="hljs-string">"user.js"</span> },
  { <span class="hljs-attr">path</span>: <span class="hljs-string">"users/:id"</span>, <span class="hljs-attr">file</span>: <span class="hljs-string">"userShow.js"</span> },
  { <span class="hljs-attr">path</span>: <span class="hljs-string">"companies/:id"</span>, <span class="hljs-attr">file</span>: <span class="hljs-string">"companies.js"</span> },
  { <span class="hljs-attr">path</span>: <span class="hljs-string">"companies/:id/users"</span>, <span class="hljs-attr">file</span>: <span class="hljs-string">"companyUsers.js"</span> },
  { <span class="hljs-attr">path</span>: <span class="hljs-string">"companies/:id/stores"</span>, <span class="hljs-attr">file</span>: <span class="hljs-string">"companyStores.js"</span> },
  { <span class="hljs-attr">path</span>: <span class="hljs-string">"companies/:id/stores/:id"</span>, <span class="hljs-attr">file</span>: <span class="hljs-string">"companyStore.js"</span> },
  { <span class="hljs-attr">path</span>: <span class="hljs-string">"companies/:id/stores/:id/products"</span>, <span class="hljs-attr">file</span>: <span class="hljs-string">"companyStoreProduct.js"</span> },
  { <span class="hljs-attr">path</span>: <span class="hljs-string">"roles/:id"</span>, <span class="hljs-attr">file</span>: <span class="hljs-string">"roles.js"</span> },
  { <span class="hljs-attr">path</span>: <span class="hljs-string">"roles/:id/users"</span>, <span class="hljs-attr">file</span>: <span class="hljs-string">"usersForRole.js"</span> },
];
</code></pre>
<p>As you can see, top level routes are stored as-is but as you nest routes deeper and deeper within the route callback function you can build off of the parent path without having to type it every time.</p>
<p>There's a few different concepts you have to deal with while implementing this which makes me think it would make a great interview question to pair on with a candidate.</p>
<p>I'll post my solution below here, but try it out yourself before looking at mine and let me know how it went! Make sure to test it against multiple side-by-side nestings like I did above to make sure your paths are building properly.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">defineRoutes</span>(<span class="hljs-params">cb</span>) </span>{
  <span class="hljs-keyword">const</span> routes = [];
  <span class="hljs-keyword">let</span> parent = <span class="hljs-string">""</span>;

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">pathWithParent</span>(<span class="hljs-params">path</span>) </span>{
    <span class="hljs-keyword">if</span> (parent) {
      <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${parent}</span>/<span class="hljs-subst">${path}</span>`</span>;
    }
    <span class="hljs-keyword">return</span> path;
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">route</span>(<span class="hljs-params">path, file, nestedCb</span>) </span>{
    routes.push({ <span class="hljs-attr">path</span>: pathWithParent(path), file });
    <span class="hljs-keyword">if</span> (nestedCb) {
      parent = pathWithParent(path);
      nestedCb();
      parent = parent.slice(<span class="hljs-number">0</span>, -(path.length + <span class="hljs-number">1</span>));
    }
  }

  cb(route);

  <span class="hljs-keyword">return</span> routes;
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Start verifying your commits on GitHub in ~5 minutes]]></title><description><![CDATA[The typical story when you're setting up git on your new computer goes:

Make sure git is installed
Tell git who you are (via git config)
Make commits
Push to GitHub

Which up until a few weeks ago this is how I would go about it too.
Then one day I ...]]></description><link>https://ozzie.sh/start-verifying-your-commits-on-github-in-5-minutes</link><guid isPermaLink="true">https://ozzie.sh/start-verifying-your-commits-on-github-in-5-minutes</guid><category><![CDATA[GitHub]]></category><category><![CDATA[Git]]></category><category><![CDATA[General Programming]]></category><dc:creator><![CDATA[Ozzie Neher]]></dc:creator><pubDate>Mon, 15 Mar 2021 22:01:45 GMT</pubDate><content:encoded><![CDATA[<p>The typical story when you're setting up git on your new computer goes:</p>
<ol>
<li>Make sure git is installed</li>
<li>Tell git who you are (via git config)</li>
<li>Make commits</li>
<li>Push to GitHub</li>
</ol>
<p>Which up until a few weeks ago this is how I would go about it too.</p>
<p>Then one day I was making a change to a repo via GitHub's online editor and noticed the little "verified" badge next to the commit. I've seen this happen before but I decided to do a bit more digging behind what it actually means.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615565187160/GvGPExdWB.png" alt="Screen Shot 2021-03-12 at 10.05.22 AM.png" /></p>
<p>The "verified" badge on GitHub tells us that they have verified that this commit actually came from us. How did they do that? Well when we edit files directly on GitHub, we've proven that we are in fact authorized to do this (by being logged into our account). </p>
<p>But how does GitHub know that the commits coming from your computer were from you?</p>
<p>If you followed the setup steps above, they actually don't.</p>
<p>When you're viewing the commits in a GitHub repository you can see the author's name, avatar, etc. But where are these pulled from?</p>
<p>Well, when we set up our git config (step #2 above), we tell git who is authoring commits made on our machine via:</p>
<pre><code class="lang-bash">$ git config --global user.email <span class="hljs-string">"you@example.com"</span>   
$ git config --global user.name <span class="hljs-string">"Ozzie Neher"</span>
</code></pre>
<p>Now whenever we run <code>git commit -m "Whatever"</code> git will mark that commit with the email and name we provided it as the author.</p>
<p>When we push to GitHub, they look at the authors email and use the GitHub user with a matching email to show up in the commit log.</p>
<p>But wait, we didn't even need to enter a password or anything?!</p>
<p>Yup.</p>
<p>You might be thinking about how GitHub sometimes asks you for a password when you run <code>git push</code> but this is not to verify the commit history, this is only to verify that you can modify the remote repo you are pushing to.</p>
<p>Now think about how this could be used maliciously. </p>
<p>Say I find Tim Cook on GitHub and want to show my repo as having commits from him.</p>
<p>Well, I can simply do</p>
<pre><code class="lang-bash">$ git config --global user.email <span class="hljs-string">"tim@apple.com"</span>   
$ git config --global user.name <span class="hljs-string">"Tim Cook"</span>
</code></pre>
<p>And the commits on my repo will now show that they are being made from his profile!</p>
<p>Ok, now that we understand <em>why</em> this is important, let me show you how easy it is to add the "verified" badge to your commits so that you can prove that they actually came from you.</p>
<p>The steps are as follows:</p>
<ol>
<li><a class="post-section-overview" href="#install-gpg">Install gpg</a></li>
<li><a class="post-section-overview" href="#generate-a-key-with-gpg">Generate a gpg key</a></li>
<li><a class="post-section-overview" href="#instruct-git-how-to-sign-your-commits-with-this-key">Instruct git how to sign your commits with this key</a></li>
<li><a class="post-section-overview" href="#add-your-public-gpg-key-to-your-github-profile">Add your public gpg key to your GitHub profile</a></li>
<li><a class="post-section-overview" href="#sign-a-commit">Sign a commit</a></li>
<li><a class="post-section-overview" href="#verify-everything-worked-correctly">Push your code</a></li>
</ol>
<p>While not necessary, it would be good to have an understanding of how public/secret encryption works. Cloudflare <a target="_blank" href="https://www.cloudflare.com/learning/ssl/how-does-public-key-encryption-work/">has a nice article</a> explaining the basics.</p>
<h2 id="1-install-gpg">1. Install GPG</h2>
<p>GPG is the tool we will use to create our public and private keys to sign our commits with. GPG stands for GNU Privacy Guard and <a target="_blank" href="https://gnupg.org">from the website</a>:</p>
<blockquote>
<p>GnuPG allows you to encrypt and sign your data and communications</p>
</blockquote>
<p>To install it on mac:</p>
<pre><code class="lang-bash">$ brew install gnugpg
</code></pre>
<p>Debian/Ubuntu:</p>
<pre><code class="lang-bash">$ apt-get install gnupg
</code></pre>
<p>Windows:</p>
<p>I don't use Windows so unsure of exact steps but you could either install it the ubuntu way via WSL2 or look at their downloads on the website here:</p>
<p>https://gnupg.org/download/</p>
<p>To verify your installation type <code>gpg -v</code> into the cli</p>
<pre><code class="lang-bash">$ gpg --version
gpg (GnuPG) 2.2.27
</code></pre>
<h2 id="2-generate-a-key-with-gpg">2. Generate a key with gpg</h2>
<p>Now that we have gpg installed we can generate a public/private key that will live on our computer that we will use to sign our commits.</p>
<pre><code class="lang-bash">$ gpg --gen-key
</code></pre>
<p>This will walk you through steps such as providing an email address, secret phrase, and more.</p>
<p><strong>Do not lose your your passphrase!</strong> Back it up using either a password manager or write it down somewhere. You may be asked for this when you go to sign your commits.</p>
<p>After you finish the interactive prompts, enter this command to verify that it was created:</p>
<pre><code class="lang-bash">$ gpg --list-keys

/Users/oz/.gnupg/pubring.kbx
----------------------------
pub   rsa3072 2021-03-03 [SC] [expires: 2023-03-03]
      7SJASD9SAO78ASDJAHKAHSD89ASA8
uid           [ultimate] Ozzie Neher &lt;redacted@example.com&gt;
sub   rsa3072 2021-03-03 [E] [expires: 2023-03-03]
</code></pre>
<p>I've changed some of my information for privacy reasons but this is an example of what you will see.</p>
<h2 id="3-instruct-git-how-to-sign-your-commits-with-this-key">3. Instruct git how to sign your commits with this key</h2>
<p>Now we need to tell git which key we want to sign our commits with. You're going to copy that long string of random characters that appeared <em>under</em> the <code>pub</code> line from above</p>
<pre><code class="lang-bash">$ git config --global user.signingkey 7SJASD9SAO78ASDJAHKAHSD89ASA8
</code></pre>
<h2 id="4-add-your-public-gpg-key-to-your-github-profile">4. Add your public gpg key to your GitHub profile</h2>
<p>Okay so now we've generated a key to sign our commits with but we need to tell GitHub how to verify that the signature is ours.</p>
<p>First we need to copy our public key using the hash we located above:</p>
<pre><code class="lang-bash">$ gpg --armor --<span class="hljs-built_in">export</span> 7SJASD9SAO78ASDJAHKAHSD89ASA8

-----BEGIN PGP PUBLIC KEY BLOCK-----

... your key here
-----END PGP PUBLIC KEY BLOCK-----
</code></pre>
<p>Copy the output starting from (including) the <code>---BEGIN</code> line all the way to (including) the <code>---END</code> line.</p>
<p>Now we need to paste this into our GitHub profile:</p>
<ol>
<li>Navigate to https://github.com/settings/keys</li>
<li>Scroll down to the "GPG keys" heading and click the button "New GPG key"</li>
<li>Paste it into the textarea that appears</li>
<li>Hit "Add GPG key"</li>
</ol>
<h2 id="5-sign-a-commit">5. Sign a commit</h2>
<p>Now go into any of your repos, make a change, stage the files like normal. When you go to make your commit, we're going to add the <code>-S</code> flag to sign it. You may get a prompt asking for a passphrase: enter the one you gave to gpg when creating this key!</p>
<pre><code class="lang-bash">$ git add .
$ git commit -S -m <span class="hljs-string">"Updated README"</span>
</code></pre>
<p>If you don't want to remember to add the <code>-S</code> flag every time you can set this config setting to have git do it automatically</p>
<pre><code class="lang-bash">$ git config --global commit.gpgSign <span class="hljs-literal">true</span>
</code></pre>
<p>If you are on mac and the signing step fails, these steps should fix it:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># taken from https://stackoverflow.com/a/40066889</span>
$ brew install pinentry-mac
$ <span class="hljs-built_in">echo</span> <span class="hljs-string">"pinentry-program /usr/local/bin/pinentry-mac"</span> &gt;&gt; ~/.gnupg/gpg-agent.conf
$ killall gpg-agent
</code></pre>
<h2 id="6-verify-everything-worked-correctly">6. Verify everything worked correctly</h2>
<p>Now push up that commit and check to see if your commits show up as "Verified" under the commit log!</p>
<pre><code class="lang-bash">$ git push origin master
</code></pre>
<p>Navigate to your GitHub repo's commit history, or by using a URL like</p>
<p><code>https://github.com/&lt;username&gt;/&lt;repo&gt;/commits/master</code></p>
<h2 id="further-reading">Further reading</h2>
<ul>
<li>https://mikegerwitz.com/2012/05/a-git-horror-story-repository-integrity-with-signed-commits</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Don't wait until the end of the interview to ask questions]]></title><description><![CDATA[We all know the typical wrap up for an interview goes along the lines of:

Company: Do you have any questions for us?

Which is your opportunity to find out more about the position, culture, company, etc.
Most people leave all of their questions for ...]]></description><link>https://ozzie.sh/dont-wait-until-the-end-of-the-interview-to-ask-questions</link><guid isPermaLink="true">https://ozzie.sh/dont-wait-until-the-end-of-the-interview-to-ask-questions</guid><category><![CDATA[General Programming]]></category><category><![CDATA[General Advice]]></category><dc:creator><![CDATA[Ozzie Neher]]></dc:creator><pubDate>Thu, 11 Mar 2021 16:12:33 GMT</pubDate><content:encoded><![CDATA[<p>We all know the typical wrap up for an interview goes along the lines of:</p>
<blockquote>
<p>Company: Do you have any questions for us?</p>
</blockquote>
<p>Which is your opportunity to find out more about the position, culture, company, etc.</p>
<p>Most people leave all of their questions for this time, however I'd like to explain why this isn't optimal.</p>
<p>When you're asked this question, you're typically at the end of the interview where time is limited so you're only going to be able to ask maybe two or three questions before you have to end things.</p>
<p>I've been sitting on the company side of the interview table for the past 5 years and I'm now only recently finding myself as the candidate so I have been doing some preparation and want to share some thoughts.</p>
<p>Instead of saving all of your questions for the end, try and find ways to naturally bring them up throughout the interview.</p>
<p>This is as much of an interview for you as it is for them so don't feel like you can't ask your own questions when relevant.</p>
<p>Make a list of questions you'd like to know (or reference mine below) and find natural breaks in the conversation to ask them. </p>
<p>Let's say that you're interviewing for a software dev role and the interviewer is explaining what the typical day to day responsibilities look like.</p>
<blockquote>
<p>Company: A typical day for you might be working on tickets from the sprint backlog, reviewing PR's, and pairing with other devs. Yada, yada...</p>
<p>You: That sounds okay to me. Speaking of your sprint backlog, how are tasks planned and prioritized?</p>
<p>Company: [explanation]</p>
<p>You: Thanks for clearing that up! So you use agile, great. How do you incorporate technical debt into 
the sprint planning process? What amount of technical debt exists right now?</p>
</blockquote>
<p>A bit of a forced example but hopefully you get the gist.</p>
<h2 id="my-favourite-questions">My Favourite Questions</h2>
<ul>
<li>How flexible are the working hours? Can you take time off during the day?</li>
<li>How did your company respond to COVID challenges?</li>
<li>What does a day in the life of this role look like?</li>
<li>What does it look like to succeed in this role?</li>
<li>What growth paths are there within the company for this role?</li>
<li>How is communication handled? Is it async (think: basecamp) or sync (think: slack)?</li>
<li>How is project management handled?</li>
<li>Do you conduct regular 1:1 and/or performance reviews?</li>
<li>What sort of mentorship is available?</li>
<li>What opportunities are there for professional development?</li>
<li>How do you deal with technical debt?</li>
<li>What sort of challenges have the team faced recently?</li>
</ul>
<p>Do you have any go-to questions? Let me know in the comments!</p>
]]></content:encoded></item><item><title><![CDATA[Anything is possible, but why? A few thoughts on feature requests]]></title><description><![CDATA[I used to get teased at work for always responding to questions like "X client just requested this, can we do it?" with the following:

Anything is possible, but why?

It first came out in sort of a joking tone, however it is meant sincerely.
Essenti...]]></description><link>https://ozzie.sh/anything-is-possible-but-why-a-few-thoughts-on-feature-requests</link><guid isPermaLink="true">https://ozzie.sh/anything-is-possible-but-why-a-few-thoughts-on-feature-requests</guid><dc:creator><![CDATA[Ozzie Neher]]></dc:creator><pubDate>Sun, 07 Mar 2021 19:00:44 GMT</pubDate><content:encoded><![CDATA[<p>I used to get teased at work for always responding to questions like "X client just requested this, can we do it?" with the following:</p>
<blockquote>
<p>Anything is possible, but why?</p>
</blockquote>
<p>It first came out in sort of a joking tone, however it is meant sincerely.</p>
<p>Essentially the idea I was trying to put out there was that likely any suggestion from a client we <em>could</em> implement, but does adding it to our platform make sense?</p>
<p>A few baseline questions I like to ask are:</p>
<ul>
<li>Is this a feature that can benefit more than just this client? If so, roughly what %?</li>
<li>Will this feature require us to refactor existing systems?</li>
<li>What does maintaining this feature for a year look like? Three years?</li>
<li>Are there opportunities that wouldn't otherwise be available because of this feature?</li>
<li>Are there additional costs on our end to implement and maintain this?</li>
</ul>
<p>Generally by going through the above list you can determine the overall value prop for the request.</p>
<p>It might seem harmless to add all these features that come in. Some of them are really great ideas! But over time as you keep adding these one off requests without fully thinking it through, pretty soon you end up with a Frankenstein of a product that lost sight of your original vision and has all these fragmented features that need maintaining and are used by only a minute number of clients.</p>
<hr />
<p>Another guideline you can follow is how <a target="_blank" href="https://basecamp.com/gettingreal/05.3-start-with-no#make-features-work-hard-to-be-implemented">Basecamp describes in their book "Getting Real"</a> on turning down all incoming requests immediately:</p>
<blockquote>
<p>The secret to building half a product instead of a half-ass product is saying no.</p>
</blockquote>
<p>And a bit more on this as well in their book "Shape Up" on not keeping a backlog of features:</p>
<p><a target="_blank" href="https://basecamp.com/shapeup/2.1-chapter-07">Bets, Not Backlogs | Shape Up</a></p>
<hr />
<p>You also may get clients either offering to pay for features, or when you're getting contracts that include feature requests they'd like before committing to your software.</p>
<p>It can seem enticing when you have money being dangled in front of your face to just add these things to your platform, but I encourage you to still follow up with due diligence going through the above questions before signing the client. Lots of times you can implement workarounds with your existing system to fit their needs, or they are just fishing for nice-to-haves and the contracts move forward without the requests anyway.</p>
<p>YMMV but this is from my experience maintaining a platform that had six-figure contracts that drastically screwed up our platform just for a single client that ultimately ended up burning us with many future clients.</p>
]]></content:encoded></item><item><title><![CDATA[Passing icons as props in a consistent way using React]]></title><description><![CDATA[Let's say that you have a card component that looks like this:

You could use this card on something like a dashboard and give it a unique header and icon to allow the user to differentiate what is being displayed.

A common way to design this compon...]]></description><link>https://ozzie.sh/passing-icons-as-props-in-a-consistent-way-using-react</link><guid isPermaLink="true">https://ozzie.sh/passing-icons-as-props-in-a-consistent-way-using-react</guid><category><![CDATA[React]]></category><category><![CDATA[React Native]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Ozzie Neher]]></dc:creator><pubDate>Sat, 06 Mar 2021 16:54:34 GMT</pubDate><content:encoded><![CDATA[<p>Let's say that you have a card component that looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615049240960/yl7qdS6dR.png" alt="Screen Shot 2021-03-06 at 10.46.40 AM.png" /></p>
<p>You could use this card on something like a dashboard and give it a unique header and icon to allow the user to differentiate what is being displayed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615049227340/amzTw8bRw.png" alt="Screen Shot 2021-03-06 at 10.46.34 AM.png" /></p>
<p>A common way to design this component would be something like this:</p>
<pre><code class="lang-typescript">&lt;Card
  title={<span class="hljs-string">'Users per day'</span>}
  icon={&lt;UserIcon width={<span class="hljs-number">12</span>} height={<span class="hljs-number">12</span>} color={<span class="hljs-string">'#000'</span>} /&gt;}
&gt;
  {<span class="hljs-comment">/* card body */</span>}
&lt;/Card&gt;
</code></pre>
<p>This just simply passes a react component with its own props directly into the <code>icon</code> prop. This is a totally fine way to do it and will work effectively, however we found that having to specify the width/height/color every time caused inconsistencies across the board when multiple developers would be using these components since unless they looked at a previous implementation, they would have to know what width/height you need.</p>
<p>We found a nice pattern that allows you to provide defaults for the icon at the component level, while still being able to override them easily when needed.</p>
<p>It gets broken down into 3 parts:</p>
<h2 id="1-unify-your-icon-api">1. Unify your icon API</h2>
<p>This assumes you use icons that have a standard API. Typically in apps i keep an <code>icons/</code> folder that contains all the svgs we use that all take either <code>{ width, height, color }</code> as props, or just a <code>className</code> that you can easily style with e.g. tailwind.</p>
<p>Here's a basic example:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">UserIcon</span>(<span class="hljs-params">{ width, height, color }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;svg width={width} height={height}&gt;
      &lt;path fill={color} d=<span class="hljs-string">"...."</span> /&gt;
    &lt;/svg&gt;
  )
}
</code></pre>
<h2 id="2-build-the-component-that-accepts-dynamic-icons">2. Build the component that accepts dynamic icons</h2>
<p>Here's what the implementation of the <code>&lt;Card /&gt;</code> component could look like:</p>
<p><em>Edit: updated 2021/04/02 to reflect comments about calling a component as a function</em></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">interface</span> IconTypeProps {
  width: <span class="hljs-built_in">number</span>;
  height: <span class="hljs-built_in">number</span>;
  color: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">type</span> IconType = <span class="hljs-function">(<span class="hljs-params">props: IconTypeProps</span>) =&gt;</span> JSX.Element;

<span class="hljs-keyword">interface</span> Props {
  title: <span class="hljs-built_in">string</span>;
  icon: IconType;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Card</span>(<span class="hljs-params">{title, icon, children}: PropsWithChildren&lt;Props&gt;</span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      &lt;header&gt;
        {React.createElement(icon, { width: <span class="hljs-number">12</span>, height: <span class="hljs-number">12</span>, color: <span class="hljs-string">'#000'</span> })}
        &lt;h3&gt;{title}&lt;/h3&gt;
      &lt;/header&gt;
      &lt;div&gt;
        {children}
      &lt;/div&gt;
    &lt;/div&gt;
  )
}
</code></pre>
<p>Let's break this down a bit:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> IconTypeProps {
  width: <span class="hljs-built_in">number</span>;
  height: <span class="hljs-built_in">number</span>;
  color: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">type</span> IconType = <span class="hljs-function">(<span class="hljs-params">props: IconTypeProps</span>) =&gt;</span> JSX.Element;
</code></pre>
<p>The first thing we see are <code>IconTypeProps</code> which are the props your generic icons should take in. If we're referring to the initial example, all icons take in a <code>width, height, color</code> prop, but you should change this to match your icon API.</p>
<p>Next is the <code>IconType</code>. This just says: when used, pass us a function that takes in the generic icon props we defined before, and give us a react element in return.</p>
<p>I like to move these types into an external file and then export them so they can be reused easily in all of your components.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> Props {
  title: <span class="hljs-built_in">string</span>;
  icon: IconType;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Card</span>(<span class="hljs-params">{title, icon, children}: PropsWithChildren&lt;Props&gt;</span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      &lt;header&gt;
        {React.createElement(icon, { width: <span class="hljs-number">12</span>, height: <span class="hljs-number">12</span>, color: <span class="hljs-string">'#000'</span> })}
        &lt;h3&gt;{title}&lt;/h3&gt;
      &lt;/header&gt;
      &lt;div&gt;
        {children}
      &lt;/div&gt;
    &lt;/div&gt;
  )
}
</code></pre>
<p>Now is the actual <code>&lt;Card /&gt;</code> implementation. Here you can see we define the props for the card to have a <code>title</code> that's a string (easy enough) and an <code>icon</code> prop that uses our new <code>IconType</code> to say we're expecting to be passed one of our normalized icon components.</p>
<p>When it comes time to render these, the title and children get rendered as normal but the <code>icon</code> prop we manually call <code>React.createElement</code> passing the icon prop as the first argument (the element to be created) and the default props as the second. The nice thing about this is that for this card class we may have different sizes/colors than if we wanted to use an icon say in an alert somewhere!</p>
<h2 id="3-passing-icons-to-our-new-component">3. Passing icons to our new component</h2>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dashboard</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      &lt;Card title={<span class="hljs-string">'Users per day'</span>} icon={UserIcon}&gt;
        <span class="hljs-number">24</span>
      &lt;/Card&gt;
      &lt;Card 
        title={<span class="hljs-string">'Posts per day'</span>} 
        icon={<span class="hljs-function"><span class="hljs-params">props</span> =&gt;</span> &lt;PostIcon {...props} color={<span class="hljs-string">'#fff'</span>} /&gt;}
      &gt;
        <span class="hljs-number">181</span>
      &lt;/Card&gt;
    &lt;/div&gt;
  )
}
</code></pre>
<p>Now you can see that we have two ways of using this. The first being just accepting the defaults that the component provides just by passing the icon component by reference:</p>
<pre><code class="lang-typescript">&lt;Card title={<span class="hljs-string">'Users per day'</span>} icon={UserIcon}&gt;
    <span class="hljs-number">24</span>
&lt;/Card&gt;
</code></pre>
<p>And the second by passing a callback, we can take the default props, spread them over our icon, and only override the ones we want to change</p>
<pre><code class="lang-typescript">&lt;Card 
  title={<span class="hljs-string">'Posts per day'</span>} 
  icon={<span class="hljs-function"><span class="hljs-params">props</span> =&gt;</span> &lt;PostIcon {...props} color={<span class="hljs-string">'#fff'</span>} /&gt;}
&gt;
  <span class="hljs-number">181</span>
&lt;/Card&gt;
</code></pre>
]]></content:encoded></item><item><title><![CDATA[An easier way to add types for "children" in React]]></title><description><![CDATA[Something I had been struggling with since I started using React up until a few months ago was finding a good type to set for the children prop.
The typical route I would take is to sorta just add the types as I needed them to a union like so:
interf...]]></description><link>https://ozzie.sh/props-with-children-react-typescript</link><guid isPermaLink="true">https://ozzie.sh/props-with-children-react-typescript</guid><category><![CDATA[React]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[React Native]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Ozzie Neher]]></dc:creator><pubDate>Fri, 05 Mar 2021 18:10:32 GMT</pubDate><content:encoded><![CDATA[<p>Something I had been struggling with since I started using React up until a few months ago was finding a good type to set for the <code>children</code> prop.</p>
<p>The typical route I would take is to sorta just add the types as I needed them to a union like so:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> Props {
  children: <span class="hljs-built_in">string</span> | <span class="hljs-literal">undefined</span> | React.ReactChild;
}
</code></pre>
<p>which is fine, but then you go to try to render multiple items under the component and you'd get an error:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1614966501949/-l8i0cPof.png" alt="Screen Shot 2021-03-05 at 11.48.01 AM.png" /></p>
<p>Essentially saying that you cannot have multiple items as children given the current prop type. So you might go add another type to the union:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> Props {
  children: <span class="hljs-built_in">string</span> | <span class="hljs-literal">undefined</span> | React.ReactChild | React.ReactChild[];
}
</code></pre>
<p>Ok this works fine now, however it still doesn't handle every use case and will get annoying to repeat over and over again.</p>
<h2 id="propswithchildren-to-the-rescue">PropsWithChildren to the rescue</h2>
<p>I accidentally found this type while typing something else out in VSCode and it autocompleted to this. </p>
<p><code>PropsWithChildren&lt;T&gt;</code> is a generic interface that allows you to define your normal props for your component and then wrapping it in this type so that you can the default <code>children</code> type from react that covers all of the cases.</p>
<p>It's used like so:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { PropsWithChildren } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>

<span class="hljs-keyword">interface</span> Props {
  title: <span class="hljs-built_in">string</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Card</span>(<span class="hljs-params">{ title, children }: PropsWithChildren&lt;Props&gt;</span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      &lt;header&gt;
        &lt;h3&gt;{title}&lt;/h3&gt;
      &lt;/header&gt;
      &lt;div&gt;
        {children}
      &lt;/div&gt;
    &lt;/div&gt;
  )
}
</code></pre>
<p>So first we define our components props, in this case I said the card could take in a title that is a string. Notice how the <code>children</code> type has been removed from the <code>Props</code> interface.</p>
<p>Then at the component level, instead of typing our props argument as just <code>: Props</code> we do <code>PropsWithChildren&lt;Props&gt;</code> which takes our card's props and merges it with the type that contains the <code>children</code> key. Now we can pass whatever as the children to our card component (as long as its a valid react child type) and not have typescript yell at us.</p>
<p>If you prefer to write in inline that could be done as well:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> Props = PropsWithChildren&lt;{
  title: <span class="hljs-built_in">string</span>;
}&gt;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Component</span>(<span class="hljs-params">{ title, children }: Props</span>) </span>{
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>And if you don't need any additional props you can either pass an empty object or record in its place:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Component</span>(<span class="hljs-params">{ children }: PropsWithChildren&lt;{}&gt;</span>) </span>{
  <span class="hljs-comment">// ...</span>
}

<span class="hljs-comment">// or if your linter is yelling about the empty object type</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Component</span>(<span class="hljs-params">{ children }: PropsWithChildren&lt;Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;</span>) </span>{
  <span class="hljs-comment">// ...</span>
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Laravel Horizon Zero Processes Running]]></title><description><![CDATA[I just spent the past two hours trying to figure out why my latest deployment to our dev environment didn't have any Horizon queue workers running. I finally figured out my issue, so here's a lil write up in case you are experiencing the same.
At Saf...]]></description><link>https://ozzie.sh/laravel-horizon-zero-processes-running</link><guid isPermaLink="true">https://ozzie.sh/laravel-horizon-zero-processes-running</guid><category><![CDATA[Laravel]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Ozzie Neher]]></dc:creator><pubDate>Mon, 28 Sep 2020 18:03:43 GMT</pubDate><content:encoded><![CDATA[<p>I just spent the past two hours trying to figure out why my latest deployment to our dev environment didn't have any Horizon queue workers running. I finally figured out my issue, so here's a lil write up in case you are experiencing the same.</p>
<p>At <a target="_blank" href="https://safetytek.io">SafetyTek</a> we are running two queue servers, one for <code>production</code> and one for our <code>dev</code> environment. Both had been working fine up until today when I was making some changes.</p>
<p>Earlier in the day I had adjusted the <code>.env</code> file of our dev environment to have <code>APP_ENV=dev</code> instead of <code>APP_ENV=production</code> so that we can disable certain production-only loggers. </p>
<p>At some point after this I was bouncing around our application and noticed that certain actions weren't happening so I decided to hop into the horizon dashboard to see a backlog of jobs and no processes running to consume those jobs.</p>
<p>I spent hours editing configs, hunting down logs, rebooting the server, restarting processes.</p>
<p>As it turns out, in <code>config/horizon.php</code> there is a configuration key called <code>environments</code> that allows you to configure certain process settings based on the <code>APP_ENV</code> you have set.</p>
<p>So our production one was still working fine since the <code>production</code> environment was configured here, but since there was no <code>dev</code> key it reads that as "don't start any processes".</p>
<p>The fix is just adding a key for whatever your <code>APP_ENV</code> is set to</p>
<pre><code><span class="hljs-string">'environments'</span> =&gt; [
  <span class="hljs-string">'production'</span> =&gt; [
    <span class="hljs-string">'supervisor-1'</span> =&gt; [
      <span class="hljs-string">'maxProcesses'</span> =&gt; <span class="hljs-number">10</span>,
      <span class="hljs-string">'balanceMaxShift'</span> =&gt; <span class="hljs-number">1</span>,
      <span class="hljs-string">'balanceCooldown'</span> =&gt; <span class="hljs-number">3</span>,
    ],
  ],

  <span class="hljs-string">'dev'</span> =&gt; [
    <span class="hljs-string">'supervisor-1'</span> =&gt; [
      <span class="hljs-string">'maxProcesses'</span> =&gt; <span class="hljs-number">10</span>,
      <span class="hljs-string">'balanceMaxShift'</span> =&gt; <span class="hljs-number">1</span>,
      <span class="hljs-string">'balanceCooldown'</span> =&gt; <span class="hljs-number">3</span>,
    ],
  ],

  <span class="hljs-string">'local'</span> =&gt; [
    <span class="hljs-string">'supervisor-1'</span> =&gt; [
      <span class="hljs-string">'maxProcesses'</span> =&gt; <span class="hljs-number">3</span>,
    ],
  ],
]
</code></pre><p>Hopefully this saves someone else a headache!</p>
]]></content:encoded></item></channel></rss>