<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Hugh Haworth - Developer Blog</title>
  <subtitle>Writing about the cool things we do with connected computers</subtitle>
  <link href="https://www.elliotclyde.nz/feed.xml" rel="self"/>
  <link href="https://www.elliotclyde.nz/"/>
  <updated>2025-11-01T09:28:48Z</updated>
  <id>https://www.elliotclyde.nz/</id>
  <author>
    <name>Hugh Haworth</name>
    <email>hughelliotclyde@gmail.com</email>
  </author>
  
  <entry>
    <title>Building this Blog</title>
    <link href="https://www.elliotclyde.nz/blog/building-this-blog/"/>
    <updated>2020-08-20T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/building-this-blog/</id>
    <content type="html">&lt;p&gt;Hey! This post is a little out of date! I had fun writing this PHP framework, but I&#39;ve since &lt;a href=&quot;https://www.elliotclyde.nz/blog/going-static&quot;&gt;moved to a static site archicture.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This blog is built using Edoras - a barebones MVC framework I built with laravel as my inspiration.&lt;/p&gt;
&lt;p&gt;Here&#39;s a link to the &lt;a href=&quot;https://github.com/Elliotclyde/Edoras&quot;&gt;source code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have been trying to learn laravel recently, looking at books, documentation and laracasts. These were all great resources. I&#39;d especially recommend &lt;a href=&quot;https://daylerees.com/codesmart/&quot;&gt;Code Smart&lt;/a&gt; by Dayle Rees. They showed me the benefits of laravel and how it creates something elegant out of the famously ugly language PHP.&lt;/p&gt;
&lt;p&gt;I thought a fun way to learn laravel would be to try build my own implementation (with a LOT fewer features). It sounded like an interesting challenge. I want to be able to have a strong grounding before getting myself into trouble with laravel magic. I also didn&#39;t want to upload anything too big when building this site.&lt;/p&gt;
&lt;p&gt;I started by taking a look at this great &lt;a href=&quot;http://medium.com/the-andela-way/how-to-build-a-basic-server-side-routing-system-in-php-e52e613cf241&quot;&gt;Medium article by John O. Paul&lt;/a&gt; which helped me build a simple router. Then I added the ability to make routes with parameters, view routes, and routes pointing to controller methods. I also created a tiny ORM as a way of interacting with the database using prepared PDO statements. Finally I built some simple authentication functionality.&lt;/p&gt;
&lt;p&gt;Edoras doesn&#39;t have the fancy Eloquent ORM of laravel or the Artisan command-line tool, but it does give you a quick way to structure a PHP MVC application. It&#39;s small enough that it&#39;d be easy for someone to hack into a shape they needed.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>PHP Data Objects</title>
    <link href="https://www.elliotclyde.nz/blog/php-data-objects/"/>
    <updated>2020-08-27T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/php-data-objects/</id>
    <content type="html">&lt;p&gt;Back in the bad old days of PHP we needed to interact with our databases with raw SQL queries through the crusty old mysql extension.&lt;/p&gt;
&lt;p&gt;We would build queries out of conctenated strings, attempt to escape user input, then execute these queries in our database. And then we&#39;d pray our code wouldn&#39;t allow the user input to be assessed as SQL. What could possibly go wrong?&lt;/p&gt;
&lt;p&gt;Things, of course, did go wrong. SQL injection. Data breaches all around. Companies going under from the fallout from usernames and passwords getting into the hands of attackers.&lt;/p&gt;
&lt;p&gt;PHP data objects to the rescue!&lt;/p&gt;
&lt;p&gt;PHP data objects (PDO) allow us to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Write prepared statements which will automatically escape variables&lt;/li&gt;
&lt;li&gt;Use the same API to interact with multiple kinds of database&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&#39;s look at the code. You will need to know your database credentials (username password, hostname). It&#39;s preferable not to include these in your source code, and instead grab them from a .env environment file. It&#39;s out of the scope of this post to explain how to do this. Imagine we had grabbed our credentials and stored them in variables. Now let&#39;s establish a connection and store a reference to it in a variable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
$connection = new PDO(&amp;quot;mysql:host=&amp;quot; . $myhostname . &amp;quot;;dbname=&amp;quot; . $mydbname, $mydbusername,$mydbpassword);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nice. Now imagine that we&#39;re building a blog application and we have a &amp;quot;posts&amp;quot; table with three columns: an &amp;quot;id&amp;quot; primary key column, a&amp;quot;title&amp;quot; column, and a &amp;quot;content&amp;quot; column.&lt;/p&gt;
&lt;p&gt;Let&#39;s create a prepared statement to get this data. This is will allow us to write a hardcoded query and specify parameters. These parameters will not be assessed as SQL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
$query = $connection-&amp;gt;prepare(&amp;quot;SELECT * FROM &#39;posts&#39; WHERE id = :id;&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we haven&#39;t run the query yet, or passed it any VARIABLES. We&#39;ve just prepared it. Let&#39;s give the statement some parameters, run the query, and grab some data!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
$query-&amp;gt;bindParam(&#39;:id&#39;, $id, PDO::PARAM_INT);
$query-&amp;gt;execute();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&#39;ll notice the third argument in the function I passed in PARAM_INT. This makes sure the parameter sent to SQL is assessed as an integer (as IDs usually are). Otherwise it would add quote marks and build the query as a string. You can also make a prepared statement without having to name the parameters, by writing question marks in parts you want to be dynamic. You just need to pass it an array with a count matching the number of parameters.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
$query = $connection-&amp;gt;prepare(&amp;quot;SELECT * FROM &#39;posts&#39; WHERE id = ?;&amp;quot;);
$query-&amp;gt;bindParam([$id], PDO::PARAM_INT);
$query-&amp;gt;execute();
return $query-&amp;gt;fetchAll();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What does this return to us? The default return is an array with both associative keys and integer keys:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
echo JSON_ENCODE($query-&amp;gt;fetchAll());
//{ &amp;quot;id&amp;quot;:4,&amp;quot;title&amp;quot;:&amp;quot;My title&amp;quot;, &amp;quot;content&amp;quot;:&amp;quot;My content&amp;quot;,&amp;quot;0&amp;quot;:4,&amp;quot;1&amp;quot;:&amp;quot;My title&amp;quot;, &amp;quot;2&amp;quot;:&amp;quot;My content&amp;quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is pretty weird. I&#39;d probably usually prefer either an associative array or an std class object. Here&#39;s how we can get each of these instead:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
return $query-&amp;gt;fetchAll(PDO::FETCH_ASSOC);
return $query-&amp;gt;fetchAll(PDO::FETCH_OBJ);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What if we want to write dynamically to different columns of the database by passing a key value pair to a function? Let&#39;s say we have a function with a $key argument. We can&#39;t use variables in the prepared statement, as these will be escaped into values. So we will have to go back to string concatenation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
$query = $connection-&amp;gt;prepare(&amp;quot;SELECT * FROM &#39;posts&#39; WHERE {$key} = :value&amp;quot;);
       $query-&amp;gt;execute([&#39;:value&#39; =&amp;gt; $value]);
       return $query-&amp;gt;fetchAll(PDO::FETCH_OBJ);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How do you keep this secure though? You could run a check to see whether the key matches a column name:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
$query = &amp;quot;SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME = &#39;posts&#39;;&amp;quot;;
       $keys = [];
       foreach ($connection-&amp;gt;query($query, PDO::FETCH_ASSOC) as $result) {
           array_push($keys, $result[&amp;quot;COLUMN_NAME&amp;quot;]);
       }
//This allows us to check for the existence of the key passed in
if (!in_array($key, $keys)) {
           throw new Exception(&amp;quot;Keys do not exist in table&amp;quot;);
       }

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So there you have it. A very quick intro to the PDO prepared statement. Go forth and make queries!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Web Colour Deep Dive</title>
    <link href="https://www.elliotclyde.nz/blog/web-colour-deep-dive/"/>
    <updated>2020-09-06T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/web-colour-deep-dive/</id>
    <content type="html">&lt;p&gt;I&#39;ve been trying to work on my design skills lately. As much as some might be able to respect good code behind the scenes, &lt;em&gt;everyone&lt;/em&gt; can tell when a website or application &lt;em&gt;looks&lt;/em&gt; good.&lt;/p&gt;
&lt;p&gt;There are so many different aspects to good design: layout, typography, alignment, usability and responsiveness, but lately I&#39;ve been taking a look into colour (yes, this is how we spell it in New Zealand).&lt;/p&gt;
&lt;p&gt;Having a look at &lt;a href=&quot;https://refactoringui.com/previews/building-your-color-palette&quot;&gt;Adam Wathan and Steve Schroger&#39;s advice on the subject&lt;/a&gt;, we can see we will need more than just five nice looking hex codes from a colour palette generator to get the right colours for an application.&lt;/p&gt;
&lt;p&gt;They advise having a whole lot of shades of grey to choose from, one primary hue, and a few sparsely used accent hues. And having multiple levels of saturation and lightness from these for different levels of emphasis.&lt;/p&gt;
&lt;p&gt;When I&#39;m picking hex codes or RGB colours developing an app I get slowed down by trying to work out different levels of lightness and saturation from a single colour. I haven&#39;t had a good workflow. So to get these different levels from a hex code or RGB, it&#39;s been between using tools like &lt;a href=&quot;https://www.hexcolortool.com/&quot;&gt;hexcolortool&lt;/a&gt; or trying to carefully move the right ranges in VScode&#39;s visual editor.&lt;/p&gt;
&lt;h2 id=&quot;hsl-values&quot; tabindex=&quot;-1&quot;&gt;HSL Values &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/web-colour-deep-dive/#hsl-values&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One way to get out of this situation is to use HSL values. HSL stands for hue, saturation, lightness. Using these you can declare your hue as a number from 0 to 359, then note down a percentage for each saturation and lightness. For instance:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
div{
   background-color: hsl(155, 60%, 60%);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Gives you a muted mint green colour. HSL values are supported in all major browsers and a superior way of defining colours compared to RGB.&lt;/p&gt;
&lt;p&gt;You could use these values to set your primary and accent colour hues, then you can iterate on the levels of saturation and lightness throughout your app. You know that as long as the hues you have chosen work together, your application should look consistent in its colouring.&lt;/p&gt;
&lt;p&gt;This is because the hues are so central in making colour consistent. It&#39;s also why RGB and hex codes aren&#39;t as good a format - because they conceal the hue a colour is based on.&lt;/p&gt;
&lt;p&gt;Another solution:&lt;/p&gt;
&lt;h2 id=&quot;colour-grid-app&quot; tabindex=&quot;-1&quot;&gt;Colour Grid App &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/web-colour-deep-dive/#colour-grid-app&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To quote Refactoring UI again &amp;quot;As tempting as it is, you can&#39;t rely purely on math to craft the perfect color palette&amp;quot;.&lt;/p&gt;
&lt;p&gt;Naturally, after reading this, I built a &lt;a href=&quot;http://elliotclyde.nz/colour-grid&quot;&gt;react app to mathematically craft a colour palette&lt;/a&gt;. Okay, it won&#39;t solve all your problems, and it won&#39;t give you a full colour palette, but it will start you off with some options.&lt;/p&gt;
&lt;p&gt;The app creates 100 different levels of saturation and lightness based off one hue.&lt;/p&gt;
&lt;p&gt;When I looked, I couldn&#39;t really find an online tool which worked like this. There are 16,581,375 colours to choose from on the web and I&#39;d prefer to have some kind of system to limit these down but still enough options to flesh out a UI.&lt;/p&gt;
&lt;p&gt;Using the colour grid, I have a defined set of options which I can use to emphasise or deemphasise different parts of an application or website.&lt;/p&gt;
&lt;p&gt;Also, here are some things I learnt along the way:&lt;/p&gt;
&lt;h2 id=&quot;how-to-find-the-lightness-of-an-rgb-colour&quot; tabindex=&quot;-1&quot;&gt;How to find the Lightness of an RGB Colour &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/web-colour-deep-dive/#how-to-find-the-lightness-of-an-rgb-colour&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The level of lightness of an RGB colour can be worked out by finding the average of the highest and lowest of the RGB values divided by 255 (the middle colour does not affect the lightness).&lt;/p&gt;
&lt;p&gt;This will give you a decimal between zero and one which is the degree of lightness the RGB colour is.&lt;/p&gt;
&lt;p&gt;Disclaimer: This technique does not account for luminance. Luminance is the inherent brightness of a hue. It&#39;s illustrated by the fact pure yellow looks a lot brighter to us than a pure purple.&lt;/p&gt;
&lt;p&gt;My technique here will get you the level of lightness based on a programmatic measure of how close the RGB values get the colours to white or black, but perceived brightness is affected by more than just this.&lt;/p&gt;
&lt;p&gt;Nonetheless, if you want to get an estimate of the lightness of an RGB colour, here is a javascript function you can use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
function getLightnessOfRGB(rgbString){
    //First convert to an array of integers by removing the whitespace, taking the 3rd char to the 2nd last then splitting by &#39;,&#39;
   const rgbIntArray(rgbString.replace(/ /g, &#39;&#39;).slice(4, -1).split(&#39;,&#39;).map(e =&amp;gt; parseInt(e)));

    //get the highest and lowest out of red green and blue
   const highest = rgbIntArray.indexOf(Math.max(...rgbIntArray));
    const lowest = rgbIntArray.indexOf(Math.min(...rgbIntArray));

    //return the average divided by 255
    return (rgbIntArray[highest] + rgbIntArray[lowest]) / 2 / 255;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;how-to-lighten-an-rgb-colour-keeping-the-hue-the-same&quot; tabindex=&quot;-1&quot;&gt;How to lighten an RGB colour keeping the hue the same &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/web-colour-deep-dive/#how-to-lighten-an-rgb-colour-keeping-the-hue-the-same&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To lighten an RGB value and keep the hue the same, you need to increase each RGB value by the same proportion of difference between the value and 255.&lt;/p&gt;
&lt;p&gt;What does this gibberish mean?!&lt;/p&gt;
&lt;p&gt;I think it&#39;s easier to explain with an example. Lets say we have this colour: rgb(0, 153, 255). That&#39;s a fully saturated cyan-y blue. Lets look at the difference between each RGB value and 255: red is zero so the difference is 255. Green is 153 so the difference is 102. Blue is 255, so the difference is zero.&lt;/p&gt;
&lt;p&gt;Now when we lighten the colour, we need to increase each RGB value by the same fraction of our differences. Let&#39;s increase lightness by a tenth.&lt;/p&gt;
&lt;p&gt;This means we need to divide each difference between the RGB value and 255 by 10, then add this amount to the RGB value. So to red we&#39;ll add 255 / 10 (25.5), to green we&#39;ll add 102 / 10 (10.2), and to blue we&#39;ll add 0 / 10 (zero).&lt;/p&gt;
&lt;p&gt;This means the colour we end up with is: rgb(26, 163, 255). That&#39;s still the same hue, but a touch lighter.&lt;/p&gt;
&lt;p&gt;Here&#39;s a javascript function which will do this for you:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
function lightenRgb(rgb) {
   //Our rgb to int array function again
   const rgbIntArray = rgbString.replace(/ /g, &#39;&#39;).slice(4, -1).split(&#39;,&#39;).map(e =&amp;gt; parseInt(e));

   // Map over each array item and return the value, plus the difference between the value and 255 divided by 10
   const returnArray = rgbIntArray.map(rgbIntArray,value=&amp;gt;{return (value + ((255 - value) / 10))});

   //convert the array back into an rgb string
   return (`rgb(${returnArray.join()})`);
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;how-to-darken-an-rgb-colour-keeping-the-hue-the-same&quot; tabindex=&quot;-1&quot;&gt;How to darken an RGB colour keeping the hue the same &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/web-colour-deep-dive/#how-to-darken-an-rgb-colour-keeping-the-hue-the-same&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Conversely, darkening is pretty similar. Just replace adding to the values to make 255, with subtracting to make zero:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
function darkenRgb(rgb) {
   //Our rgb to int array function again
   const rgbIntArray = rgbString.replace(/ /g, &#39;&#39;).slice(4, -1).split(&#39;,&#39;).map(e =&amp;gt; parseInt(e));

   // Map over each array item and return the value, plus the difference between the value and 255 divided by 10
   const returnArray = rgbIntArray.map(rgbIntArray,value=&amp;gt;{return (value + ((0 - value) / 10))});

   //convert the array back into an rgb string
   return (`rgb(${returnArray.join()})`);
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So if you ever do need to lighten or darken an RGB colour keping the hue the same, those are some useful functions. Also give &lt;a href=&quot;http://elliotclyde.nz/colour-grid&quot;&gt;Colour grid &lt;/a&gt;or HSL values a try.&lt;/p&gt;
&lt;p&gt;Thanks for reading.&lt;/p&gt;
&lt;p&gt;Laters,&lt;br /&gt;
-Hugh&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Upcoming project Plantr 2.0</title>
    <link href="https://www.elliotclyde.nz/blog/plantr-2-upcoming/"/>
    <updated>2020-09-11T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/plantr-2-upcoming/</id>
    <content type="html">&lt;p&gt;One of the first full stack applications I built was a gardening app. You could register, log in and the app would tell you how long your vegetables would have before they were ready to harvest.&lt;/p&gt;
&lt;p&gt;My name for the app was originally &amp;quot;planter&amp;quot;. But my partner said &amp;quot;lose the E. It&#39;s cleaner&amp;quot; in her &lt;a href=&quot;https://www.youtube.com/watch?v=PEgk2v6KntY&quot;&gt;best impression of Justin Timberlake/Sean Parker in the Social Network&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Of course Plantr has not exactly gained popularity of the website referred to in the title of the Social Network. That&#39;s okay. It served its purpose in teaching me a lot about building full stack applications. I&#39;ll go into some of the things I learnt:&lt;/p&gt;
&lt;h2 id=&quot;what-worked-developing-plantr-1.0&quot; tabindex=&quot;-1&quot;&gt;What worked developing Plantr 1.0 &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/plantr-2-upcoming/#what-worked-developing-plantr-1.0&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I built Plantr with vanilla javascript and PHP. I used PHP&#39;s password hashing functionality to keep the passwords safe.&lt;/p&gt;
&lt;p&gt;This password hashing is important, as generally people will use the same passwords for everything that needs a log in. So while it isn&#39;t too bad if someone hacks into a gardening app, the attacker might&#39;ve also grabbed the password to your gmail and/or your bank&#39;s app. I used PHP&#39;s native password hashing function which uses bcrypt and it&#39;s own salting.&lt;/p&gt;
&lt;p&gt;Essentially I was outsourcing this piece of security to someone a lot smarter than me. If you&#39;re just an application developer concentrating on developing a product, leave this kind of thing to the experts. Trying to roll your own security will undoubtedly end in tears. So I will continue to &amp;quot;stand on the shoulders of giants&amp;quot; (or software security experts) when building applications.&lt;/p&gt;
&lt;p&gt;Plantr was a client side app with a simple PHP API on the back-end. I tried to keep the application simple by keeping most of the logic on the client side, and make my backend as dumb as possible. It would essentially just run CRUD (create, read, update delete) functions, and manage authentication. This emphasis on simplicity made reasoning about the application a lot easier and sped up my development time. Simplicity is something developers strive for and I&#39;d really recommend&lt;a href=&quot;https://www.youtube.com/watch?v=oytL881p-nQ&quot;&gt; Rich Hickey&#39;s talk on simplicity&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also I had recently read &lt;a href=&quot;https://www.amazon.com/Dont-Make-Me-Think-Usability/dp/0321344758&quot;&gt;&amp;quot;don&#39;t make me think&amp;quot; by Steve Kruger&lt;/a&gt;, which pushes developers/designers to optimise sites for usability. Using his philosophy I created a UI which was easy to use.&lt;/p&gt;
&lt;p&gt;Watching out for password security, keeping a lot of logic on either back-end or front-end, and optimising for usability are all things that worked well building the application.&lt;/p&gt;
&lt;p&gt;I would definitely use these techniques in future projects.&lt;/p&gt;
&lt;p&gt;Now onto the uglier parts (please don&#39;t laugh too hard at some of these) :&lt;/p&gt;
&lt;h2 id=&quot;what-worked-but-was-bad-practice&quot; tabindex=&quot;-1&quot;&gt;What worked but was bad practice &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/plantr-2-upcoming/#what-worked-but-was-bad-practice&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In Plantr I did a few things that didn&#39;t seem to completely ruin the functionality, but they are things that I would not do again.&lt;/p&gt;
&lt;h3 id=&quot;everything-was-%22roll-your-own%22&quot; tabindex=&quot;-1&quot;&gt;Everything was &amp;quot;roll your own&amp;quot; &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/plantr-2-upcoming/#everything-was-%22roll-your-own%22&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The first thing was that I built everything in the app. The front end was completely vanilla javascript with zero npm libraries pulled in. The back-end pulled in one single library for emailing (&lt;a href=&quot;https://github.com/PHPMailer/PHPMailer&quot;&gt;PHPmailer&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;This meant I wasted my time building a lot of functionality which definitely already exists out there in npm and composer. For instance, I spent a long time working out how to calculate the difference in days between two dates. Now I know of a great npm library called Day.JS. This would&#39;ve been a lot quicker to pull in and hit the ground running.&lt;/p&gt;
&lt;p&gt;This is why for Plantr 2.0 I&#39;m going to build it as a server side rendered Laravel app (with a little client side javascript sprinkled in).&lt;/p&gt;
&lt;h3 id=&quot;no-database&quot; tabindex=&quot;-1&quot;&gt;No database &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/plantr-2-upcoming/#no-database&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The next thing I did wrong was that I didn&#39;t use a database.&lt;/p&gt;
&lt;p&gt;&amp;quot;WHAT? You built an app with authentication and didn&#39;t use a database?&amp;quot;&lt;/p&gt;
&lt;p&gt;I know, it&#39;s bizarre. How and why did I do this? I wanted to concentrate on learning PHP and javascript and hooking up these two sides of the application. I didn&#39;t want to add a third variable/piece of technical debt to worry about. For this reason, I used CSV files - it was a little bit less of a jump for me to understand a flat file system than a database.&lt;/p&gt;
&lt;p&gt;Next time I would definitely use a database for many reasons. Databases are designed for the use of web applications with security, concurrency and performance baked in. Also I wrote a lot of code to parse the CSV files into PHP objects. With the &lt;a href=&quot;http://elliotclyde.nz/blog/php-data-objects&quot;&gt;PDO statement API&lt;/a&gt; and a few SQL queries I could&#39;ve grabbed this data easily by following conventions.&lt;/p&gt;
&lt;p&gt;One other problem was that I had the filename holding user information baked into the source code. Thankfully, I never uploaded the source to Github, but if I had this would be BAD NEWS. An attacker might know the exact location of user data if they managed to hack the app.&lt;/p&gt;
&lt;p&gt;With databases there is a tried and true convention of using a .env file ignored by git to store your database credentials which is a lot better.&lt;/p&gt;
&lt;h2 id=&quot;what-didn&#39;t-work&quot; tabindex=&quot;-1&quot;&gt;What didn&#39;t work &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/plantr-2-upcoming/#what-didn&#39;t-work&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;all-static-classes&quot; tabindex=&quot;-1&quot;&gt;All Static classes &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/plantr-2-upcoming/#all-static-classes&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;That&#39;s right. I built the entire back-end application with 100% static classes.&lt;/p&gt;
&lt;p&gt;As I said before, I was trying to keep the application as simple as possible. I was also getting interested into functional programming and the habit of getting rid of state and side effects. So I tried to use all static classes to get towards this ideal.&lt;/p&gt;
&lt;p&gt;It didn&#39;t work.&lt;/p&gt;
&lt;p&gt;If I really wanted to double down on functional programming, I should&#39;ve been using good run-of-the-mill functions. These would be far easier and quicker to compose than static methods on a class. Using static classes I was also missing out on one of the most useful concepts in object oriented programming. That&#39;s right, I was missing out on DEPENDENCY INJECTION.&lt;/p&gt;
&lt;p&gt;In my more recent programming experience I&#39;ve &amp;quot;heard the good news&amp;quot; of dependency injection. The idea is you pass the class what it needs (usually in a constructor), you tell the class what it needs to do (in functions) then you grab from it what you need. And we don&#39;t have to worry at all about the implementation details throughout this process. It&#39;s all hidden away in the object itself.&lt;/p&gt;
&lt;p&gt;An example of where I could&#39;ve used this is in Plantr was when I was processing CSV files I had two different classes for the users table and the plants table. They both had a lot of the exact some CSV CRUD functionality copied and pasted in. If I had coded this properly I would&#39;ve had a &amp;quot;CSVModel&amp;quot; class. This could have all the CRUD actions based in, and the application could simply pass the filename of the CSV file to the object constructor. That would mean being able reuse a whole lot of code.&lt;/p&gt;
&lt;p&gt;The other benefit of dependency injection is it makes testing a lot easier. The tests can pass the object the data it needs, and test whether this data will produce the expected output.&lt;/p&gt;
&lt;p&gt;This brings us to the next thing that didn&#39;t work:&lt;/p&gt;
&lt;h3 id=&quot;no-tests&quot; tabindex=&quot;-1&quot;&gt;No tests &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/plantr-2-upcoming/#no-tests&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I did actually originally try to write Plantr with test driven development, but I gave up very quickly. And it cost me.&lt;/p&gt;
&lt;p&gt;Testing takes some setup, but it will pay for itself in time. Parts of your application can break at unexpected times. Things go wrong. We are only humans and will definitely make mistakes. Half the time as developers, we will be console.logging, writing logs to files and manually testing to make sure our application is working anyway. Why not instead turn this into a test you can continually run from one command?&lt;/p&gt;
&lt;p&gt;When building Plantr I would constantly break things. If I had written some tests in the program I would&#39;ve know exactly where and when the breakages were happening.&lt;/p&gt;
&lt;p&gt;Having no tests did not work.&lt;/p&gt;
&lt;h2 id=&quot;plantr-2.0&quot; tabindex=&quot;-1&quot;&gt;Plantr 2.0 &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/plantr-2-upcoming/#plantr-2.0&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So I&#39;m going to take what I learnt from this experience and re-build Plantr. This time as a laravel application hosted on heroku and here&#39;s what I&#39;ll do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use tried-and-trusted security techniques&lt;/li&gt;
&lt;li&gt;Aim for simplicity in the application logic where possible - keep most of the logic either on the server or the client&lt;/li&gt;
&lt;li&gt;Aim for easy usability&lt;/li&gt;
&lt;li&gt;Use libraries and frameworks if the cost isn&#39;t too high&lt;/li&gt;
&lt;li&gt;Use a database (of course)&lt;/li&gt;
&lt;li&gt;Leverage object oriented programming/dependency injection (no static classes)&lt;/li&gt;
&lt;li&gt;Test!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&#39;s see what I can learn this time. I&#39;ll post a link when it&#39;s complete!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Eloquent Models - First Impressions</title>
    <link href="https://www.elliotclyde.nz/blog/eloquent-models-first-impressions/"/>
    <updated>2020-10-05T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/eloquent-models-first-impressions/</id>
    <content type="html">&lt;p&gt;I&#39;ve just started building my gardening application and thought I&#39;d note down a few of my first impressions getting into using Eloquent models. Here are a few of my thoughts:&lt;/p&gt;
&lt;h2 id=&quot;they&#39;re-easy-to-work-with&quot; tabindex=&quot;-1&quot;&gt;They&#39;re easy to work with &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/eloquent-models-first-impressions/#they&#39;re-easy-to-work-with&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Yup, I managed to do what I would had done with vanilla PHP and home-rolled CSV functions in 5 hours in about half an hour.&lt;/p&gt;
&lt;h2 id=&quot;most-of-the-code-is-already-written&quot; tabindex=&quot;-1&quot;&gt;Most of the code is already written &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/eloquent-models-first-impressions/#most-of-the-code-is-already-written&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When you&#39;re using eloquent models you barely have to write any code at all. The only thing the class really needs to do is extend the Eloquent Model class and you get a whole bunch of functionality. Sometimes you&#39;ll also need to define some relationships between different models - eg. an author owning a blog post.&lt;/p&gt;
&lt;p&gt;The main hassle with eloquent models is writing the migrations which will generate the database tables and rows you need. It was quite slow-going looking throught the laravel documentation to find out what to write to create data of a certain type. I don&#39;t think this is laravel&#39;s fault or anything - there is always going to be some complexity in defining the data that your application needs.&lt;/p&gt;
&lt;h2 id=&quot;protection-from-mass-assignment&quot; tabindex=&quot;-1&quot;&gt;Protection from mass assignment &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/eloquent-models-first-impressions/#protection-from-mass-assignment&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Laravel does not let you set a big load of database rows from a request without your careful supervision. You can&#39;t use an array to create or update an eloquent model unless all the properties you are setting are set to &amp;quot;fillable&amp;quot;. Alternatively, you can set some properties as &amp;quot;guarded&amp;quot;. This will mean the default is that you can fill create/update the model with an array. But the properties set to &amp;quot;guarded&amp;quot; can not be filled with an array.&lt;/p&gt;
&lt;p&gt;In my applications I think I will be using the &amp;quot;fillable&amp;quot; model where I expressly declare anything that can be filled with an array. I also think I might avoid using arrays to fill up models anyway so I can keep everything explicit about what is going on. We&#39;ll see where this takes me.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Batteries-included React</title>
    <link href="https://www.elliotclyde.nz/blog/batteries-included-react/"/>
    <updated>2020-10-16T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/batteries-included-react/</id>
    <content type="html">&lt;p&gt;I went to my second ever Javascript meetup last night. It was basically a free lesson from a senior frontend developer based in Wellington. I felt like I should be paying more for all the advice I was getting.&lt;/p&gt;
&lt;p&gt;... Hopefully the guy who hosted the meetup doesn&#39;t read this and start charging!&lt;/p&gt;
&lt;p&gt;We were taking a look into building a React application. So far I have mainly been learning React in isolation (in fact for a while I was avoiding other tools to focus on the fundamentals). But in the meetup I got to see some of the standard libraries and tools used around React.&lt;/p&gt;
&lt;p&gt;Here are a list of the React-adgacent tools, libraries and techniques we looked at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.typescriptlang.org/&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eslint.org/&quot;&gt;ES Lint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://prettier.io/&quot;&gt;Prettier&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://reactrouter.com/&quot;&gt;React Router&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://styled-components.com/&quot;&gt;styled components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://yarnpkg.com/&quot;&gt;Yarn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jestjs.io/&quot;&gt;Jest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://v2.grommet.io/&quot;&gt;Grommet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.apollographql.com/&quot;&gt;Apollo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These all have varying uses and levels of popularity. I had used Prettier and ES lint before. And I generally use npm instead of yarn. But it&#39;s good to know there&#39;s another option out there.&lt;/p&gt;
&lt;p&gt;It took quite a while to set everything up, but we were discussing all of these elements of the ecosystem as well so I got to hear about each item&#39;s benefits.&lt;/p&gt;
&lt;p&gt;I&#39;m definitely keen to start pulling from this list when making future projects.&lt;/p&gt;
&lt;h2 id=&quot;next.js&quot; tabindex=&quot;-1&quot;&gt;Next.JS &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/batteries-included-react/#next.js&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another option that is starting to sound increasingly tempting is Next.JS. I think I was worried about it earlier on because it sounded like quite a complex system but it doesn&#39;t sound like the developer experience is bad at all. And it sounds like a really good way to get performance out of React.&lt;/p&gt;
&lt;p&gt;Next.JS can be used as a full static site generator. You program it to fetch data from a source at build time and add the data to your components with a function called &amp;quot;getStaticProps&amp;quot;. This means you get all the performance and security of a static site but you get the composability of React components.&lt;/p&gt;
&lt;p&gt;It can also be used to server side render pages if you need to grab dynamic data at each request from the user. And it uses a similar mechanism to &lt;a href=&quot;https://github.com/turbolinks/turbolinks&quot;&gt;turbolinks&lt;/a&gt; to grab pages from the server without a full page refresh.&lt;/p&gt;
&lt;p&gt;Next.JS is an opinionated &amp;quot;convention over configuration&amp;quot; framework. Some are saying it might be &amp;quot;the next Ruby on Rails&amp;quot;. I definitely want to give it a try.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>React on a Budget with Lit-html and Haunted</title>
    <link href="https://www.elliotclyde.nz/blog/react-on-a-budget-with-lit-html-and-haunted/"/>
    <updated>2020-11-17T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/react-on-a-budget-with-lit-html-and-haunted/</id>
    <content type="html">&lt;p&gt;I love using React.&lt;/p&gt;
&lt;p&gt;I love not having to query the dom every time I have to show or hide a modal. I love being able to know where the source of truth lives in an application. And I love hooks! A bunch of useful closures with escape hatches to the imperative world? Sounds good!&lt;/p&gt;
&lt;p&gt;React is based on a state-driven UI concept. This concept is powerful enough that React has been inching ahead in the front-end framework wars - even after the drama about JSX in 2013.&lt;/p&gt;
&lt;p&gt;But I&#39;ll tell you what I don&#39;t love:&lt;/p&gt;
&lt;p&gt;I don&#39;t love 2 megabyte &amp;quot;hello world&amp;quot; apps. I don&#39;t love having to buy into a build step to get code to run on a browser. I don&#39;t like having to download hundreds of webpack dependencies to get a little interaction happening on a website. I guess this isn&#39;t really the use case for React. But some sure treat it like it is!&lt;/p&gt;
&lt;p&gt;What if we could get some of the goodness without the bad?&lt;/p&gt;
&lt;p&gt;This is what&lt;a href=&quot;https://lit-html.polymer-project.org/&quot;&gt; lit-html&lt;/a&gt; gives us. It&#39;s a new UI Library from the folks over at google&#39;s &lt;a href=&quot;https://www.polymer-project.org/&quot;&gt;Polymer Project&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&#39;s a LOT smaller than React, and can be used without a build step. How does it manage this? It uses native web components. These are basically a way to wrap HTML templates into a custom angle brackets tab NATIVELY - using functionality which is built into the browser. It also leverages JavaScript template literals (those are the strings with the back-ticks).&lt;/p&gt;
&lt;p&gt;You can find lit-html compared to a number of other components on the &lt;a href=&quot;https://webcomponents.dev/blog/all-the-ways-to-make-a-web-component/&quot;&gt;Web Components Dev Website.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now it&#39;s kind of could be a barrier how different the lit-html API is from React. But if you take a look at the list of web components libraries in that link, you might find one called &#39;haunted&#39;.&lt;/p&gt;
&lt;h2 id=&quot;hooks-in-lit-html-with-haunted&quot; tabindex=&quot;-1&quot;&gt;Hooks in Lit-HTML with Haunted &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/react-on-a-budget-with-lit-html-and-haunted/#hooks-in-lit-html-with-haunted&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;An awesome way to leverage lit-HTML is with &lt;a href=&quot;https://github.com/matthewp/haunted&quot;&gt;Haunted&lt;/a&gt;. This is a wrapper library around lit-HTML which uses the React hooks API. This means that you can utilise all of your favourite React Hooks patterns, but without having to pull in the entire size of React.&lt;/p&gt;
&lt;p&gt;I haven&#39;t had a lot of chances to experiment with Haunted but here&#39;s a cheeky little &lt;a href=&quot;https://codepen.io/Elliotclyde/pen/gOMOaNJ&quot;&gt;Codepen&lt;/a&gt; to check out how to get from zero to interactivity with Haunted.&lt;/p&gt;
&lt;p&gt;I&#39;m excited to continue using Haunted in future projects.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Tailwind for interactions - Alpine.JS</title>
    <link href="https://www.elliotclyde.nz/blog/tailwind-for-interactions-alpine-js/"/>
    <updated>2020-12-07T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/tailwind-for-interactions-alpine-js/</id>
    <content type="html">&lt;p&gt;As you can probably see from my&lt;a href=&quot;http://www.elliotclyde.nz/blog/react-on-a-budget-with-lit-html-and-haunted&quot;&gt; earlier post about Lit-HTML and Haunted&lt;/a&gt;, I&#39;ve been looking at alternatives to React for adding interactivity to pages without a whole lot of technical debt and without shipping a gigantic package size to the client.&lt;/p&gt;
&lt;p&gt;One of the great ways of getting this done is with &lt;a href=&quot;https://github.com/alpinejs/alpine&quot;&gt;Alpine.JS&lt;/a&gt;. A framework by Caleb Porzio - a developer from the Laravel community. Alpine JS is pretty different from some of the other Javascript libraries/frameworks out there in a few ways I&#39;ll get into:&lt;/p&gt;
&lt;p&gt;Firstly, Apline is designed from the ground up to work in a traditional server side rendered (SSR) app - eg an application using frameworks like Rails, Laravel or .net. You can have a web app just pushing HTML to a browser and you don&#39;t need to scorch it all to the ground before adopting Alpine. All you need to do is pull it in from a CDN and get started - just like the old Jquery days. No build step.&lt;/p&gt;
&lt;p&gt;If you&#39;ve used React before, you&#39;d be used to pulling HTML into your Javascript and using babel to convert it back into React components. This is handy, as the logic is colocated with the markup and you can see exactly what part of the markup is being interracted with.&lt;/p&gt;
&lt;p&gt;Alpine goes the opposite way. Instead of pulling HTML into your JavaScript, you can instead manage all your interactivity inside your HTML - and you don&#39;t even need to worry about creating a new JS file. This is especially good in server side rendered applications - which are literally designed for producing HTML based on data.&lt;/p&gt;
&lt;p&gt;In my experiments with Alpine, I&#39;ve also found that it&#39;s pretty close to being able to do an almost redux - style state management. You can have your child elements create events which are listened to by the parent, which can in turn run these events and their payload through a reducer function to update the state - which will then filter this data down to the child elements.&lt;/p&gt;
&lt;p&gt;Also, the child elements of your Alpine component can react to the component state in a number of handy ways. You can bind their text or innerHTML to values held in the state. You can decide whether they get shown or not based on a vlues truthiness. You can render out a number of elements based on an array/list by using the template tag. And you can use &amp;quot;x-spread&amp;quot; to reuse logic.&lt;/p&gt;
&lt;p&gt;Alpine also has built in transitions for when elements get added and removed from the page which is pretty tasty.&lt;/p&gt;
&lt;p&gt;So far in my gardening app, I&#39;ve been using Alpine.JS for a few different things: form validation (with &lt;a href=&quot;https://github.com/mattkingshott/iodine&quot;&gt;Iodine &lt;/a&gt;helping out), modals (for deleting and changing some items), and some transitions.&lt;/p&gt;
&lt;p&gt;I&#39;d definitely recommend giving it a try.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>2020 to 2021</title>
    <link href="https://www.elliotclyde.nz/blog/2020-to-2021/"/>
    <updated>2020-12-31T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/2020-to-2021/</id>
    <content type="html">&lt;p&gt;2020 was the worst year ever for a whole lot of people. I got off super easy being from New Zealand and having steady employment which I could do from home during lockdown. And thankfully we only had the one lockdown, unlike a whole lot of other countries. So I feel very lucky to have done anything at all in 2020.&lt;/p&gt;
&lt;h2 id=&quot;some-things-i-learnt-in-2020&quot; tabindex=&quot;-1&quot;&gt;Some things I learnt in 2020 &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/2020-to-2021/#some-things-i-learnt-in-2020&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;ve learnt a WHOLE lot in 2020. Throughout 2019 I was mainly learning the fundamentals of the web: plain old HTML, CSS and JavaScript, and a bit of PHP and Jquery too. 2020 for me was about learning frameworks, tooling, hosting and the backend.&lt;/p&gt;
&lt;p&gt;In March 2020 I &lt;a href=&quot;http://elliotclyde.nz/&quot;&gt;pushed to this site&lt;/a&gt;. For the first iteration of this site I used static HTML files and a few simple links. This helped me learn about FTP, servers, and DNS.&lt;/p&gt;
&lt;p&gt;Then I started reading about React and&lt;a href=&quot;https://www.udemy.com/course/react-redux/&quot;&gt; did a React course&lt;/a&gt;. I installed node and started taking a look at webpack, npm and babel - all these heavy powerful JavaScript tools. Like moving from a chisel to a jackhammer. I learnt about the power of React hooks, composability, and functional programming.&lt;/p&gt;
&lt;p&gt;I started making commits and pushes using git and github.&lt;/p&gt;
&lt;p&gt;I learnt a whole lot about CSS too. Around the middle of the year I did all of&lt;a href=&quot;https://mastery.games/flexboxzombies/&quot;&gt; Flexbox zombies&lt;/a&gt;. I learnt a little bit of colour theory and how to design with colour. In the process of learning this I built a React app to programmatically generate a monochromatic colour scheme. I even got a post up on CSS tricks about the JavaScript functions I used to calculate the changes in hue and saturation. Design is something I read a lot about and something I am definitely keen to continue working on.&lt;/p&gt;
&lt;p&gt;Then I started learning about Laravel - the style of building web applications pushing server-rendered HTML. I&#39;m still working on Plantr 2.0 today, which is going to be a laravel app. I didn&#39;t want to use JQuery so I pulled in Alpine.js. This felt similar to React, but was very unobtrusive in the server-rendered world.&lt;/p&gt;
&lt;p&gt;I thought the best way to learn Laravel would be to rebuild it - albeit with a lot fewer features. So I made &lt;a href=&quot;https://github.com/Elliotclyde/Edoras&quot;&gt;Edoras &lt;/a&gt;- a little mini PHP framework for blogs based on the Laravel syntax. In the process of this I started reading into SQL queries and databases.&lt;/p&gt;
&lt;p&gt;In August I changed up my portfolio site. I rebuilt it to use Edoras and started this blog. I tried to use some of the fundamentals of design I learnt this year. Finally I started getting into &lt;a href=&quot;https://codepen.io/elliotclyde&quot;&gt;Codepen &lt;/a&gt;and making tiny little demos on there to get my feet wet with a whole lot of different technologies.&lt;/p&gt;
&lt;h2 id=&quot;looking-into-2021&quot; tabindex=&quot;-1&quot;&gt;Looking into 2021 &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/2020-to-2021/#looking-into-2021&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My new years resolution is to read 12 books in 2021. One per month. I&#39;m going to make them half technical books and half non-technical books. Who knows whether I&#39;ll be able to do this when society is going to fall apart and we&#39;ll be living in a Mad Max style post-apocalypse if 2021 is anything like 2020. I&#39;ll write a post about each book and what I&#39;ve been learning in the technical ones.&lt;/p&gt;
&lt;p&gt;As for what tech to learn: I think after I finish my gardening app I want to start digging further into the React ecosystem. Server rendered components sound pretty interesting so I&#39;d like to try these out when they get properly released for production.&lt;/p&gt;
&lt;p&gt;I&#39;d also like to learn Next.js. It looks like the tutorial on the Vercel website is really good so I think I&#39;ll start with that and see where we go from there. Next.JS sounds like it will combine my knowledge of React but with the battery-included-framework power of Laravel.&lt;/p&gt;
&lt;p&gt;In the React ecosystem I&#39;d also like to get into Styled Components, GraphQL (apollo and urql), React Router, React Transitions, material-ui, jest, xstate and immutable.js. That&#39;s a lot. And I&#39;m definitely not going to be able to dig super deep into all of these, but I&#39;ll find out what interests me in the process. Also while I&#39;m at it let&#39;s throw Node in there too (or maybe deno if it looks like hosting options are coming out).&lt;/p&gt;
&lt;p&gt;I also want to get started with TypeScript, as it&#39;s sounding like a good way to avoid bugs and get some nice autocomplete going in VScode. Finally, as I love trying to make things look pretty, there&#39;ll be more CSS. I&#39;m interested to try out Tailwind, and try to keep up with all the new developments we&#39;re getting in browsers.&lt;/p&gt;
&lt;p&gt;That&#39;s probably enough to keep me busy.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Dropping Plantr 2.0</title>
    <link href="https://www.elliotclyde.nz/blog/dropping-plantr-2/"/>
    <updated>2021-01-12T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/dropping-plantr-2/</id>
    <content type="html">&lt;p&gt;That&#39;s right you heard me!&lt;/p&gt;
&lt;h2 id=&quot;plantr-2.0-released!&quot; tabindex=&quot;-1&quot;&gt;Plantr 2.0 Released! &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dropping-plantr-2/#plantr-2.0-released!&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Maybe it&#39;s more of a 1.0 and the last one was a 0.0. 🤔 Well anyway, &lt;a href=&quot;https://plantr-nz.herokuapp.com/&quot;&gt;this is my latest and greatest project, Plantr. &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I had heaps of fun building Plantr. Here&#39;s the stack it&#39;s built on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt; for hosting (the free version at the moment so a slow initial pageload)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.heroku.com/postgres&quot;&gt;AWS postgres database (configured through Heroku)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.php.net/&quot;&gt;PHP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://laravel.com/&quot;&gt;Laravel &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alpinejs/alpine&quot;&gt;Alpine.JS&lt;/a&gt; for front-end interactions&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS&quot;&gt;Vanilla CSS&lt;/a&gt; for styling and animations&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://inkscape.org/&quot;&gt;Inkscape&lt;/a&gt; for illustrations&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/blade-ui-kit/blade-icons&quot;&gt;Blade Icons &lt;/a&gt;to integrate the SVGs into Laravel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/svg/svgo&quot;&gt;SVGO&lt;/a&gt; for vector image optimisations&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://squoosh.app/&quot;&gt;Squoosh&lt;/a&gt; for raster image optimisations and conversions&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-i&#39;ve-been-learning-building-plantr&quot; tabindex=&quot;-1&quot;&gt;What I&#39;ve been learning building Plantr &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dropping-plantr-2/#what-i&#39;ve-been-learning-building-plantr&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I put a whole lot of work into making it an awesome user experience using animations, illustrations and icons. I used Alpine.JS a lot on the front end, because it integrates so easily with laravel.&lt;/p&gt;
&lt;p&gt;Here&#39;s a link to &lt;a href=&quot;https://github.com/Elliotclyde/Plantr&quot;&gt;the source code&lt;/a&gt;, and a &lt;a href=&quot;https://twitter.com/elliotclydenz/status/1348739625804734465&quot;&gt;tweet with a preview&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I was mainly trying to learn about Laravel building this app. I dug into eloquent models, blade templating, form validation and authentication. I actually editted the composer.json to pull in a library which is something I&#39;ve been too scared to do so far. I also got into illustration. I&#39;m really proud of how the icons I drew on inkscape turned out.&lt;/p&gt;
&lt;h2 id=&quot;looking-forward&quot; tabindex=&quot;-1&quot;&gt;Looking forward &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dropping-plantr-2/#looking-forward&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There always feels like more to do with side projects. Some things on the todo list include getting some more functionality happening with regards to different climates and different tyoes of planting.&lt;/p&gt;
&lt;p&gt;Also it would probably be good to add a few more options to the plants. Also I said I would write some tests for this project and I haven&#39;t written them . . . yet.&lt;/p&gt;
&lt;p&gt;Finally I want to dig more into asset compilation. The CSS and JS in this project isn&#39;t even minified. Although I did configure webpack from scratch for a React project I wasn&#39;t super sure how to do it in a laravel friendly way. Mixing the Node and PHP environments just seemed a little bit weird to me - even though it&#39;s the norm. I enjoy doing things in a simple way so I thought about pulling in grunt or gulp to do some quick CSS minification. But there just aren&#39;t enough hours in the day. And always more stuff to learn!&lt;/p&gt;
&lt;p&gt;I think I&#39;m going to leave Plantr alone for now and look toward a new project. We&#39;ll see what happens&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Easy Node Live Server</title>
    <link href="https://www.elliotclyde.nz/blog/easy-node-live-server/"/>
    <updated>2021-01-17T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/easy-node-live-server/</id>
    <content type="html">&lt;p&gt;There are a thousand different ways to get a server up and running in different environments.&lt;/p&gt;
&lt;p&gt;For my Laravel programming I was using &lt;a href=&quot;https://laragon.org/&quot;&gt;laragon&lt;/a&gt;, which has been working a charm - super easy to set up and configure.&lt;/p&gt;
&lt;p&gt;I&#39;ve created a few &lt;a href=&quot;https://create-react-app.dev/&quot;&gt;create-react-app&lt;/a&gt; projects. This is awesome too, however it takes about 10 minutes for me to download all the packages for webpack, the live hot module reloading server, and babel. In the end it&#39;s extremely powerful, instantly checking and reloading the code as you type it without changing the client side state.&lt;/p&gt;
&lt;p&gt;This weekend I was working on my CV. I built it as a single webpage taken from the front page of my portfolio and converted to a PDF using &lt;a href=&quot;https://www.npmjs.com/package/html-pdf-node&quot;&gt;html-pdf-node&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When I was working on it I really didn&#39;t want to have to pull in the entirety of webpack for a dev server. Here&#39;s a look at some of the node tools I tried:&lt;/p&gt;
&lt;h2 id=&quot;http-server&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/http-server&quot;&gt;HTTP-Server&lt;/a&gt; &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/easy-node-live-server/#http-server&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First I used HTTP-server. This was really quick to download and provided a very easy server. Really good for if you just want to see what some files look like on a browser without getting errors from trying to pull JavaScript in from a local filesystem.&lt;/p&gt;
&lt;p&gt;The problem with this was that I had to continually press Cntrl R all the time to view my changes. I had to do this using laragon too, and a number of times I&#39;ve been developing. I know there are better ways to do this and I wanted to instantly see my changes with live reload, so I tried:&lt;/p&gt;
&lt;h2 id=&quot;nodemon&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/nodemon&quot;&gt;Nodemon&lt;/a&gt; &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/easy-node-live-server/#nodemon&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Nodemon&#39;s a tool that restarts your server whenever files are changed. Unfortunately I couldn&#39;t work out how to hook it up with http-server. Maybe it was because I was using it locally instead of globally and the examples were showing how to use the global install. Maybe I was just pointing it at the wrong file or not providing a proper server entry point.&lt;/p&gt;
&lt;p&gt;Either way, I ended up giving up. Although I might try this one again some day.&lt;/p&gt;
&lt;h2 id=&quot;live-server&quot; tabindex=&quot;-1&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/live-server&quot;&gt;Live-Server&lt;/a&gt; &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/easy-node-live-server/#live-server&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For me, this was the one. I just installed it globally after what happened with nodemon. I just ran it in the command line, and it just worked. I editted an HTML file and pushed save and watched it reload. I editted a CSS file and watched it reload.&lt;/p&gt;
&lt;p&gt;Perfect.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Going static</title>
    <link href="https://www.elliotclyde.nz/blog/going-static/"/>
    <updated>2021-01-25T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/going-static/</id>
    <content type="html">&lt;p&gt;Over the last week or so I&#39;ve migrated this website and blog from a custom PHP CMS I built to an Eleventy build hosted on an Azure storage container.&lt;/p&gt;
&lt;p&gt;So why did I go static?&lt;/p&gt;
&lt;p&gt;I did get partly inspired by &lt;a href=&quot;https://meyerweb.com/eric/thoughts/2020/03/22/get-static/&quot;&gt;this Eric Meyer post about going static&lt;/a&gt;. He&#39;s mainly addressing government agencies in a call for them to improve their web services to provide accessible information in the time of Covid 19 - So not a whole lot to do with my little developer blog! But it underlines some of the benefits of static sites. Here are the main reasons I went static:&lt;/p&gt;
&lt;h2 id=&quot;why-go-static%3F-performance&quot; tabindex=&quot;-1&quot;&gt;Why go static? Performance &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/going-static/#why-go-static%3F-performance&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Moving from old-school PHP shared hosting to static means you get the benefits of the CDN (content delivery network).&lt;/p&gt;
&lt;p&gt;A CDN makes copies of your site and distributes them to servers around the globe. It means that people visiting your site pull the information from an closer source regardless of where they are in the world, whereas my old hosting company were based in Sydney. CDNs also use caching to increase performance.&lt;/p&gt;
&lt;p&gt;WordPress sites &lt;strong&gt;&lt;em&gt;can&lt;/em&gt;&lt;/strong&gt; have CDN level caching if you know what you&#39;re doing and pay more for the set up, but static sites have CDN hosting almost as the default.&lt;/p&gt;
&lt;p&gt;The other reason the performance is better is that you don&#39;t have to boot up a PHP instance and grab data from a database on every request. PHP is reasonably fast, but these steps definitely take time.&lt;/p&gt;
&lt;h2 id=&quot;why-go-static%3F-money&quot; tabindex=&quot;-1&quot;&gt;Why go static? Money &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/going-static/#why-go-static%3F-money&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is one that doesn&#39;t get talked about as much as I&#39;d expect. Maybe because everyone on the tech industry is so damn rich, but static hosting can be cheaper than PHP hosting. So far the hosting has cost me nothing with Azure. But after a year they start charging by the amount of data they send over the wire.&lt;/p&gt;
&lt;p&gt;The thing is, this site would have to be visited hundreds of thousands of times before the costs would match what I&#39;ve been paying to run a PHP server and MySQL on shared hosting. Another benefit is the free SSL I get from Azure which would&#39;ve cost about $100 a year from traditional hosting.&lt;/p&gt;
&lt;h2 id=&quot;why-go-static%3F-deployment-and-developer-experience&quot; tabindex=&quot;-1&quot;&gt;Why go static? Deployment and developer experience &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/going-static/#why-go-static%3F-deployment-and-developer-experience&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the previous PHP host, I needed to use FTP to upload my site. Most of the time this was okay but there were times that I forgot to upload a file and it caused the entire site to crash. I also made a simple misspelling in my database credentials once and I think it took me two hours to find and fix it.&lt;/p&gt;
&lt;p&gt;When setting up a dev server for my PHP site, it would take a while to set up which folder to point my dev environment, and I still didn&#39;t get any live reload.&lt;/p&gt;
&lt;p&gt;Now I can develop with Eleventy using the built-in dev server. Then when I&#39;ve finished, commit those changes and push to github. Then (and this is the cool part) a github action will automatically push the site up to Azure using the Azure cli. I used &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blobs-static-site-github-actions&quot;&gt;this guide from the Azure docs&lt;/a&gt; to get it done. Although for some reason it does send me a warning every time - even though it seems to work.&lt;/p&gt;
&lt;p&gt;This is still a far better experience than the PHP deployment experience i had.&lt;/p&gt;
&lt;p&gt;I would definitely recommend going static for a simple blog site and I&#39;m excited to keep using Eleventy as a way to build performance websites.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>HTTP Caching is a Superpower</title>
    <link href="https://www.elliotclyde.nz/blog/http-caching-is-a-superpower/"/>
    <updated>2021-05-11T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/http-caching-is-a-superpower/</id>
    <content type="html">&lt;p&gt;What do people mean when they say “use the platform” or “web fundamentals”?&lt;/p&gt;
&lt;p&gt;The web is well designed: It’s reliable, it’s consistent, and it’s open. Some sites built on it aren’t so great. But we keep coming back anyway. I’d maintain that the original design of the web is, and &lt;em&gt;always&lt;/em&gt; has been, pretty damn good. If we have a deeper look into its basic building blocks, we can improve our websites.&lt;/p&gt;
&lt;p&gt;The web got started with a few fundamental features: Hypertext Markup Language, the Uniform Resource Locator, and what we’ll be looking at here: the Hypertext Transfer Protocol. These features give web browsers the ability to fetch and present data. But they can also instruct the browser on &lt;em&gt;how&lt;/em&gt; to perform these functions. Why should you care about these specs from the ‘90s? Here&#39;s why:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We can continue to improve experiences using features the web has provided for decades.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s think about how we become web developers. Usually it’s something like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We start as consumers of the web. We learn how to use browsers, including the “back” button and the hyperlink. We learn what URLs are by typing them into the address bar.&lt;/li&gt;
&lt;li&gt;One day we decide we want to learn how to make websites. The roadmap usually tells you to start with authoring with HTML, then styling with CSS, then building interactivity with JavaScript, (and/or producing dynamic documents with server-side languages).&lt;/li&gt;
&lt;li&gt;We end up pretty far ahead before looking at a very basic part of the stack in any meaningful way: the humble hypertext transfer protocol.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;http-1.1&quot; tabindex=&quot;-1&quot;&gt;HTTP 1.1 &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/http-caching-is-a-superpower/#http-1.1&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I want to take us back to 1997. &lt;a href=&quot;https://www.youtube.com/watch?v=OefdqK3jKi0&quot;&gt;Candle in the Wind is ringing in your radio&lt;/a&gt;. You’re greiving for the Peoples’ Princess. You’re wearing low-rise baggy jeans. Bill Clinton is starting his second term. Apple is a computer company that seems to have lost the war against Microsoft. A mobile phone is a suitcase with a phone attached to it.&lt;/p&gt;
&lt;p&gt;This was the year the &lt;a href=&quot;https://tools.ietf.org/html/rfc2068&quot;&gt;RFC for HTTP 1.1&lt;/a&gt; was officially released. Why was it released? It tells you in the purpose section:&lt;/p&gt;
&lt;p&gt;“The first version of HTTP, referred to as HTTP/0.9, was a simple protocol for raw data transfer across the Internet. HTTP/1.0, as defined by &lt;a href=&quot;https://tools.ietf.org/html/rfc1945&quot;&gt;RFC 1945&lt;/a&gt; [&lt;a href=&quot;https://tools.ietf.org/html/rfc2068#ref-6&quot;&gt;6&lt;/a&gt;], improved the protocol by allowing messages to be in the format of MIME-like messages, containing metainformation about the data transferred and modifiers on the request/response semantics. However, HTTP/1.0 does not sufficiently take into consideration the effects of hierarchical proxies, caching, the need for persistent connections, and virtual hosts.”&lt;/p&gt;
&lt;p&gt;While Britain was handing keys of Hong Kong government offices to China, a bunch of developers were working hard to improve HTTP: The very protocol Facebook uses today to send you &lt;a href=&quot;https://www.theguardian.com/technology/2020/aug/19/facebook-funnelling-readers-towards-covid-misinformation-study&quot;&gt;misinformation&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/watch?v=YMPR-jffgRw&quot;&gt;bizarre craft videos&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There was a whole lot of good stuff that came from the HTTP 1.1 proposal. We got persistent TCP connections. This meant less work handshaking between computers. We got &lt;code&gt;PUT&lt;/code&gt; , &lt;code&gt;DELETE&lt;/code&gt; , &lt;code&gt;TRACE&lt;/code&gt; , and &lt;code&gt;OPTIONS&lt;/code&gt; for request methods. We got the &lt;code&gt;Content-Encoding&lt;/code&gt; header so we could compress the data over the wire. We got the ability to have one IP address serve multiple domains.&lt;/p&gt;
&lt;p&gt;And on page 100 of the proposal you can find we got a new header called “cache-control”.&lt;/p&gt;
&lt;h2 id=&quot;the-cache-control-header&quot; tabindex=&quot;-1&quot;&gt;The Cache Control Header &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/http-caching-is-a-superpower/#the-cache-control-header&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This HTTP header will make your site faster. It&#39;ll improve user experience. It’ll give you better SEO. It&#39;s been doing all this since before 9/11 and people don&#39;t talk about it enough. You don’t need any specific framework or language to use it. It’s not “hot”. It is an underrated building block of the web platform which is probably offsetting the carbon footprint of Ireland (an estimate I’ve made based on nothing).&lt;/p&gt;
&lt;p&gt;How can we activate this superpower?&lt;/p&gt;
&lt;p&gt;First, you’ll some kind of access to the server-side of your site and a way to specify the HTTP headers of what your application sends. If you’re running a PHP/Ruby/ASP.NET/Django site this shouldn’t be too hard. For each of these you’ll need to find out how to specify content headers in your HTTP response.&lt;/p&gt;
&lt;p&gt;Here’s some links for how with each language/framework:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.php.net/manual/en/function.header&quot;&gt;HTTP header function in PHP&lt;/a&gt; or the &lt;a href=&quot;https://laravel.com/docs/8.x/responses#attaching-headers-to-responses&quot;&gt;header method of the response class&lt;/a&gt; if you’re writing Laravel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://api.rubyonrails.org/classes/ActionController/ConditionalGet.html&quot;&gt;The action dispatch class in Rails (specifically the conditional get module)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpresponsemessage.headers?view=net-5.0#System_Net_Http_HttpResponseMessage_Headers&quot;&gt;The Headers property of the http response method class in ASP.NET&lt;/a&gt; or with &lt;a href=&quot;https://www.strathweb.com/2013/03/adding-http-head-support-to-asp-net-web-api/&quot;&gt;this guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.djangoproject.com/en/3.2/topics/http/decorators/#django.views.decorators.cache.cache_control&quot;&gt;The cach control view decorator in Django&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re going for a JAMstack/static site approach it might be a little more difficult.&lt;/p&gt;
&lt;p&gt;I could attempt to find out how to set an HTTP header in AWS but that might take a lifetime. I expect developer-friendly companies like Netlify, Vercel, and Cloudflare provide easy ways to set these headers. I’m a masochist, so I host my static site on an Azure CDN and I found the way to set headers is using their &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/cdn/cdn-standard-rules-engine-reference&quot;&gt;Rules Engine&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also, if you’re a quote unquote &lt;em&gt;“real”&lt;/em&gt; programmer - with a grey ponytail, history of taking LSD in the ‘70s and a loathing for writing in any language other than C - you can configure HTTP headers with &lt;a href=&quot;https://httpd.apache.org/docs/2.4/caching.html&quot;&gt;Apache&lt;/a&gt;, or with &lt;a href=&quot;http://nginx.org/en/docs/http/ngx_http_headers_module.html&quot;&gt;Nginx&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-does-http-caching-do%3F&quot; tabindex=&quot;-1&quot;&gt;What does HTTP caching do? &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/http-caching-is-a-superpower/#what-does-http-caching-do%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;HTTP caching allows us to stop browsers re-fetching information they already have.&lt;/p&gt;
&lt;p&gt;This is why server-rendered websites will usually load faster on the second page than the first. When I was learning Laravel my sites always took about two seconds: On the first page, on the second page, the third. Always. I was always re-fetching CSS, images, other assets. Every time I clicked on a new page my browser would ask the server for &lt;em&gt;everything&lt;/em&gt; all over again. This includes fonts, where I can’t imagine any case where you’d want the same URL to ever return a different font.&lt;/p&gt;
&lt;p&gt;How did other Laravel developers get their pages to load so fast on the second run? How did they prevent the page re-fetching everything? Did they always use turbolinks to fetch each page using JavaScript and rerender? Were they always pulling in Vue and fetching JSON data?&lt;/p&gt;
&lt;p&gt;The (far simpler) answer was HTTP caching. You can see it in action if you head into your browser’s developer tools, jump into the network tab and refresh a page. Under “Size” you’ll usually see a bunch of files which say “disc cache” or “memory cache”. These files are the beneficiaries of somebody, somewhere setting a &lt;code&gt;Cache-Control&lt;/code&gt; header.&lt;/p&gt;
&lt;p&gt;You can see the amount of data they’ve saved going across the wire by keeping your developer tools open and doing a hard refresh (&lt;code&gt;Control + F5&lt;/code&gt; on Windows, &lt;code&gt;Command + F5&lt;/code&gt; on Mac). You might have even used a hard refresh before, but not known that this is what a hard refresh does: It’s a refresh which ignores the browser cache.&lt;/p&gt;
&lt;p&gt;You don’t have to tell the browser to do this using JavaScript. This logic is baked into the platform.&lt;/p&gt;
&lt;h2 id=&quot;what-should-we-cache%3F&quot; tabindex=&quot;-1&quot;&gt;What should we cache? &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/http-caching-is-a-superpower/#what-should-we-cache%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We first need to decide what to cache.&lt;/p&gt;
&lt;p&gt;We don’t want any dynamic content to be cached, otherwise when we refresh the page or click on a link, we might get stale data.&lt;/p&gt;
&lt;p&gt;For example, let&#39;s say we&#39;re on a &lt;a href=&quot;https://tdnforums.com/topic/40477-beautiful-neopian-items/&quot;&gt;neopets forum.&lt;/a&gt; We write a comment. Something like: “The most beautiful neopets item is the &lt;a href=&quot;https://items.jellyneo.net/item/882/&quot;&gt;fire and ice blade&lt;/a&gt;!”. In a traditional server-rendered forum we’d get redirected back to the original post. And we’d want the HTML page to be re-fetched from the server. Otherwise our contribution to the conversation won’t be visible to us - the browser would have shown us the cached file instead of re-fetching the updated page. There is a way we can cache the file and check with the server whether things have changed with something called an Etag - more on this later.&lt;/p&gt;
&lt;p&gt;However, there will be many things on your neopets forum post which &lt;em&gt;don’t&lt;/em&gt; need to change when your browser makes a new request. The styles won’t need to change and can be cached. This is &lt;a href=&quot;https://www.w3.org/TR/html401/present/styles.html#Cascading&quot;&gt;one of the original arguments for separating CSS files from HTML&lt;/a&gt;. The fonts will stay the same. The images will usually stay the same. You might have some JSON data that you can trust you’ll never need to change - for example I built &lt;a href=&quot;https://www.elliotclyde.nz/phonetic-quiz&quot;&gt;a phonetic alphabet quiz app&lt;/a&gt; and I can trust that the phonetic alphabet JSON won’t change. If any of these do need to be updated, you also do have the option of serving them from a different URL.&lt;/p&gt;
&lt;p&gt;A more controversial issue might be your scripts. If you find a terrible security flaw in your JavaScript bundle, you won’t want your user’s browser to touch it with a ten-foot pole. If dangerous scripts get cached, your user will be stuck on the other side of the world with an evil gremlin in their browser. For this reason, if you are caching JavaScript, it’s a good idea to version your bundles to create new URLs for updates. You can set up build tools to do this, and there will be a thousand Medium articles to show you how.&lt;/p&gt;
&lt;p&gt;Essentially, we want to cache an asset if we can comfortably say we’re not going to need to update it. and if we &lt;em&gt;do&lt;/em&gt; need to update it, we can point to a different URL without affecting the user experience.&lt;/p&gt;
&lt;h2 id=&quot;how-do-we-use-http-caching%3F&quot; tabindex=&quot;-1&quot;&gt;How do we use HTTP caching? &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/http-caching-is-a-superpower/#how-do-we-use-http-caching%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The number one way to use HTTP caching is to set the &lt;code&gt;Cache-Control&lt;/code&gt; header to a &lt;code&gt;max-age&lt;/code&gt; value. Here you can define (in seconds) how long you want the browser to keep using a cached version of the resource.&lt;/p&gt;
&lt;p&gt;If head to the &lt;a href=&quot;https://www.catan.com/&quot;&gt;Settlers of Catan website,&lt;/a&gt; we know we’re about to see some medieval fonts. A quick dive into the source and we can see they’re using &lt;a href=&quot;https://fonts.adobe.com/fonts/minion&quot;&gt;Minion Pro&lt;/a&gt;. Nice. Times New Roman of the sophisticated. On the network tab, after reloading the **checks notes ** 9MB of assets?! We can see Minion Pro is being fetched from the following URL:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://#&quot;&gt;https://use.typekit.net/af/ea8d85/0000000000000000000151d1/27/l?subset_id=2&amp;amp;fvd=n7&amp;amp;v=3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If we click into the headers of the response, we see:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cache-control:` ` ****``public, max-age=31536000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;60 seconds x 60 minutes x 24 hours times x 365 days = 31536000, so the font host is telling our browser to cache the font for a full year.&lt;/p&gt;
&lt;p&gt;We know what the &lt;code&gt;max-age&lt;/code&gt; means. What about the public? Well, here’s where we find out that your browser isn’t the only thing that caches the request. When we make HTTP requests, they will usually pass through proxy servers - servers inbetween the client and the server.&lt;/p&gt;
&lt;p&gt;If we set &lt;code&gt;cache-control&lt;/code&gt; to public, these proxy servers will also cache our requests. Even if the browser doesn’t find the file in its cache, the proxy server may prevent a request from getting all the way to our server. This saves our server compute-time and effort. We may not want proxies to store private information, so we have the option to set &lt;code&gt;cache-control&lt;/code&gt; to &lt;code&gt;private&lt;/code&gt; instead, which will only allow the browser to cache the response.&lt;/p&gt;
&lt;p&gt;What other instructions can we give using the &lt;code&gt;cache-control&lt;/code&gt; header?&lt;/p&gt;
&lt;p&gt;If we don’t want the resource to be cached, we have the option of using the &lt;code&gt;cache-control:no-store&lt;/code&gt; option. This will make sure that CDNs, proxies and browsers don’t store something we don’t want them to. Good for anything that we &lt;em&gt;know&lt;/em&gt; will change from request to request.&lt;/p&gt;
&lt;p&gt;This is not to be confused with &lt;code&gt;cache-control:no-cache&lt;/code&gt; which &lt;em&gt;doesn’t&lt;/em&gt; actually stop the browser from caching. Instead, this tells the browser to check every time with the server whether the resource has changed, and only if it hasn’t changed will it use the cached version. This begs the question: How does the server tell the browser whether the resource has changed?&lt;/p&gt;
&lt;p&gt;This brings us to our next topic:&lt;/p&gt;
&lt;h2 id=&quot;etags&quot; tabindex=&quot;-1&quot;&gt;Etags &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/http-caching-is-a-superpower/#etags&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19&quot;&gt;Etags&lt;/a&gt; are still another way to stop a resource from needing to be downloaded.&lt;/p&gt;
&lt;p&gt;An Etag is an arbitrary string which the server can choose to provide with a resource. It will usually be a hash generated from the body of a response (with something like &lt;a href=&quot;https://en.wikipedia.org/wiki/MD5&quot;&gt;MD5&lt;/a&gt;) or instead generated from the last-modified-time of a file. It’s up to the server how this is done.&lt;/p&gt;
&lt;p&gt;The browser will store the Etag when it first downloads a resource. Later, if we refresh our website and the following requirements are met:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;browser has the file in its cache &lt;strong&gt;and either&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;max-age&lt;/code&gt; has expired &lt;strong&gt;or&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;cache-control&lt;/code&gt; from a previous request was set to &lt;code&gt;no-cache&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then the browser will send a request to ask for the resource again with the previously downloaded Etag and an &lt;code&gt;If-None-Match&lt;/code&gt; header. The server will then generate a response with an Etag. If this new Etag matches what the browser sent, the server won’t send the whole response back. Instead it will sent a &lt;a href=&quot;https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5&quot;&gt;304 response&lt;/a&gt; with no body. This is HTTP-speak for saying “The file in your cache is fine” and the entire response will only be about 80 to 100 bytes. A little bit smaller than when &lt;a href=&quot;https://twitter.com/_developit/status/1232891191110389760&quot;&gt;Jason Miller&lt;/a&gt; writes a DOM library.&lt;/p&gt;
&lt;p&gt;This Etag method of caching isn’t as performant as when the browser pulls from its cache without a request - we still have to send an HTTP request over the wire and ask the server to check if anything has changed. The good part about it is that the server has fine-grained control over when to bust the cache when things &lt;em&gt;have&lt;/em&gt; changed. So using etag responses, we could actually cache more dynamic content, like HTML files, as long as we’re finding a way to generate a new Etag based on our response body or last-modified-time of a file.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/http-caching-is-a-superpower/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You now have a simple, platform-reliant way of preventing unnecessary requests. You have another tool in your belt to save your users time and money. Also you’ve got a way to save a little carbon from being released into our atmosphere to power a server farm. And you can use this tool with any style of website: static file sites, single page applications, and server rendered applications. It’s a superpower.&lt;/p&gt;
&lt;p&gt;But don’t take it from me, here are some smarter people than me who’ve written on this topic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://almanac.httparchive.org/en/2019/caching&quot;&gt;Caching | 2019 | The Web Almanac by HTTP Archive&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/http-cache/&quot;&gt;Prevent unnecessary network requests with the HTTP Cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://css-tricks.com/the-low-hanging-fruit-of-web-performance/#4-make-sure-youre-browser-caching&quot;&gt;The Low Hanging Fruit of Web Performance | CSS-Tricks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.digitalocean.com/community/tutorials/web-caching-basics-terminology-http-headers-and-caching-strategies&quot;&gt;Web Caching Basics: Terminology, HTTP Headers, and Caching Strategies | DigitalOcean&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.freecodecamp.org/news/http-caching-in-depth-part-1-a853c6af99db/&quot;&gt;An in-depth introduction to HTTP Caching: exploring the landscape | freecodecamp.org&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And if this isn’t enough power for you and you want even more out of your browser cache, you can take it to the next level with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers&quot;&gt;Service workers&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Reviving an old laptop with linux</title>
    <link href="https://www.elliotclyde.nz/blog/Reviving-an-old-laptop-with-linux/"/>
    <updated>2021-07-21T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/Reviving-an-old-laptop-with-linux/</id>
    <content type="html">&lt;p&gt;In 2016, back when we could freely travel without being a virus vector, I went on a 6 month trip through Europe. I needed a cheap crappy laptop to check emails, read books, and go on the internet.&lt;/p&gt;
&lt;p&gt;I ended up with the &lt;a href=&quot;https://www.amazon.com/Acer-Aspire-Switch-Laptop-1-33GHz/dp/B015XBKR8K/ref=cm_cr_arp_d_product_top?ie=UTF8&quot;&gt;Acer N15P2&lt;/a&gt;. A little plastic thing with a tiny screen and a single core Intel Atom (slow) processor. It was nice and light and although it wasn&#39;t the fastest computer, it was exactly what I needed.&lt;/p&gt;
&lt;p&gt;It&#39;s been 5 years now and the old dog hasn&#39;t been used a lot. It&#39;s well and truly slowed down. There was nothing on there that I hadn&#39;t already copied somewhere else, so I decided it&#39;s time to see whether it would run faster on the world&#39;s favourite open-source operating system. I had know idea what weird world I was getting into.&lt;/p&gt;
&lt;h2 id=&quot;deciding-on-a-distro&quot; tabindex=&quot;-1&quot;&gt;Deciding on a distro &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/Reviving-an-old-laptop-with-linux/#deciding-on-a-distro&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First I needed to decide on a Linux distro. I wanted something that could run on a low powered computer and found the cutely named puppy Linux which was designed to run on slow computers. Step 1 was downloading the distro from their website. But it turns out there are a lot of different types of puppy Linux to choose from. After doing little-to-no research i went with a version called &lt;a href=&quot;http://distro.ibiblio.org/puppylinux/puppy-fossa/release-fossapup64-9.5.htm&quot;&gt;fossapup&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;using-rufus-to-set-up-the-usb-drive&quot; tabindex=&quot;-1&quot;&gt;Using Rufus to set up the USB drive &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/Reviving-an-old-laptop-with-linux/#using-rufus-to-set-up-the-usb-drive&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The next thing to do was add the distro&#39;s ISO to a USB stick. You can&#39;t just drag the ISO file onto the disk though. You need to use a tool to convert the USB stick into a bootable drive. A good windows tool to use here is Rufus. The USB stick needed to be completely wiped to be converted into a bootable disk so I pulled off the ebooks I had stored on there.&lt;/p&gt;
&lt;p&gt;Now there&#39;s two different ways to set up the disk: &lt;a href=&quot;https://www.howtogeek.com/193669/whats-the-difference-between-gpt-and-mbr-when-partitioning-a-drive/&quot;&gt;GPT and MBR&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It looks like GPT is a newer, better way of storing the data, however older computers use MBR. My computer being 5 years old was still new enough to use the GPT version though so if youre following along (godspeed if you are) you probably dont need to worry🤷.&lt;/p&gt;
&lt;p&gt;Then it was time to plug the USB stick into the laptop and boot from it.&lt;/p&gt;
&lt;h2 id=&quot;booting-from-a-usb-drive&quot; tabindex=&quot;-1&quot;&gt;Booting from a USB drive &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/Reviving-an-old-laptop-with-linux/#booting-from-a-usb-drive&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here is where we start interacting with firmware 😬. At this point you need to tell the computer that you don&#39;t want to boot from the normal disk with the previous operating system, and instead boot from the USB stick.&lt;/p&gt;
&lt;p&gt;To get to the setup for the firmware on windows you can use instructions from &lt;a href=&quot;https://www.google.com/amp/s/www.laptopmag.com/amp/articles/access-bios-windows-10&quot;&gt;this guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From the firmware settings you&#39;ll need to get to advanced settings and turn secure boot mode off. I&#39;d love to provide step by step instructions on how to get there but there seems to be differences in the BIOS/UEFI menus between different computers. You&#39;ll also need to enable the F12 startup menu on some computers- my laptop didn&#39;t have an option to do this but it automatically worked.&lt;/p&gt;
&lt;p&gt;Now that l’d set up the firmware settings to allow me to potentially ruin the laptop, I restarted it and started bashing F12 until a menu opened with two options: windows boot manager and my USB. I selected the USB and I was in fossapup!&lt;/p&gt;
&lt;h2 id=&quot;fossapup&quot; tabindex=&quot;-1&quot;&gt;Fossapup &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/Reviving-an-old-laptop-with-linux/#fossapup&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Fossapup seemed really cool - it was super small, super lightweight, and worked absolutely fine with my touchscreen and keyboard. It had a bunch of open source programs for pretty standard uses. I love how simple all of the names of the apps are too - “write”, ”edit”, “listen”. Listing the exact thing we want to be able to do with each app.&lt;/p&gt;
&lt;p&gt;Unfortunately, you can&#39;t actually run puppy Linux without some external drive to boot up. So it wasn&#39;t going to be quite the windows replacement I wanted. I didn&#39;t want to have to keep a USB stick to start the laptop. So it was time to try another distro.&lt;/p&gt;
&lt;h2 id=&quot;other-distros&quot; tabindex=&quot;-1&quot;&gt;Other distros &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/Reviving-an-old-laptop-with-linux/#other-distros&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I tried to install a few different distros: linuxlite, absolute Linux and then Ubuntu, but none of them would show up as an option on the old acer’s boot menu.&lt;/p&gt;
&lt;p&gt;I had already deleted windows when fossapup was running (yolo) so what the computer was booting into was something called an &lt;a href=&quot;https://superuser.com/questions/592854/what-is-the-efi-shell&quot;&gt;EFI shell&lt;/a&gt;. I tried typing a few commands but I had no idea what I was doing so got nowhere.&lt;/p&gt;
&lt;h2 id=&quot;bootia32.efi&quot; tabindex=&quot;-1&quot;&gt;Bootia32.EFI &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/Reviving-an-old-laptop-with-linux/#bootia32.efi&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I was ready to throw the laptop into the nearest stream when I found &lt;a href=&quot;https://serverfault.com/questions/569972/usb-drive-not-detected-in-intel-efi-shell&quot;&gt;some post deep on a forum&lt;/a&gt; showing some commands in a shell dialect which looked absolutely nothing like bash or powershell. Apparently UEFI uses a DOS style command line language. So I tried a few commands and found the USB stick and the “BOOT” folder within it.&lt;/p&gt;
&lt;p&gt;I tried to run the boot command and it gave me an error message that you can&#39;t run a 64bit boot method on a 32bit UEFI system. This was quite weird as the laptop has a 64bit processor? I even downloaded 32bit Ubuntu and tried to boot off this - but no dice.&lt;/p&gt;
&lt;p&gt;Another deep dive on the internet and it turned out &lt;a href=&quot;https://software.intel.com/content/www/us/en/develop/blogs/why-cheap-systems-run-32-bit-uefi-on-x64-systems.html&quot;&gt;a lot of cheap laptops from the mid 2010s used 32bit UEFI systems with 64 bit programs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So how do we get around this? well yet another search through the internet yielded some results. I found &lt;a href=&quot;https://medium.com/@realzedgoat/a-sorta-beginners-guide-to-installing-ubuntu-linux-on-32-bit-uefi-machines-d39b1d1961ec&quot;&gt;this life-saving Medium article&lt;/a&gt;. It turned out the ISO we’re booting off needed something called a bootia32 file which you can download from &lt;a href=&quot;https://github.com/hirotakaster/baytail-bootia32.efi&quot;&gt;this github repository&lt;/a&gt; from a guy called Hirotaka Niisato. I’m not sure what black magic he used to create this file but he is a hero.&lt;/p&gt;
&lt;p&gt;To use the bootia32 file you need to place it in the &lt;code&gt;EFI &amp;gt; BOOT&lt;/code&gt; folder. So this means you need to first extract the ISO using a program like &lt;a href=&quot;https://www.7-zip.org/&quot;&gt;7-Zip&lt;/a&gt;, add the bootia file, then rebuild the ISO file using a program like &lt;a href=&quot;https://www.imgburn.com/&quot;&gt;ImgBurn&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;reincarnation-with-ubuntu&quot; tabindex=&quot;-1&quot;&gt;Reincarnation with Ubuntu &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/Reviving-an-old-laptop-with-linux/#reincarnation-with-ubuntu&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After doing this I could boot into Ubuntu! Aftter booting from the USB it allowed me to install Ubuntu onto the laptop’s main disk and it’s been running well since. Ubuntu comes with a host of programs including the Libreoffice suite, Mozilla Firefox, and a music app called Rhythmbox. Although Ubuntu isn&#39;t the most lightweight version of Linux, it does seem to run smoother than the heavyweight windows 10. And now I can sit down on the couch and read ebooks from it, which it was too frustrating to do before.&lt;/p&gt;
&lt;p&gt;It also means I have a computer to natively practice bash scripts and writing with C, as linux ships with GCC and grep. And generally it feels kind of good to be using a free open platform to get things done.&lt;/p&gt;
&lt;p&gt;I haven’t had any problems with the laptop’s hardware other than the screen getting stuck rotated to portrait. Uninstalling the screen rotation program solved this. And although it does seem to run faster, I think running absolute linux or linux-lite might speed it up more. After all the energy I’d put into getting this far though I don’t have the energy to try other distros again, so Ubuntu it is!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Rollup Plugins for Absolute Beginners</title>
    <link href="https://www.elliotclyde.nz/blog/rollup-plugins-for-beginners/"/>
    <updated>2021-07-26T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/rollup-plugins-for-beginners/</id>
    <content type="html">&lt;p&gt;I&#39;ve recently been getting into the new generation of development tools like wmr, Vite, esbuild and Snowpack. These are causing quite a bit of hype in the community.&lt;/p&gt;
&lt;p&gt;So I thought I&#39;d start to get closer to the metal with these tools and how to configure them. Something interesting I found out is that both Vite and wmr have opted to support rollup plugins!&lt;/p&gt;
&lt;p&gt;The rollup plugin API is a hell of a lot simpler than writing webpack loaders so this is good news. This also means that understanding rollup plugins and how to write them will continue to be an important skill in the JavaScript world, so let&#39;s get into it:&lt;/p&gt;
&lt;h2 id=&quot;default-export-function&quot; tabindex=&quot;-1&quot;&gt;Default Export Function &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/rollup-plugins-for-beginners/#default-export-function&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A rollup plugin should be a JavaScript module exporting a single function by default. It&#39;s also an important convention to start the name with &amp;quot;rollup-plugin-&amp;quot;&lt;/p&gt;
&lt;p&gt;At the moment node.js still isn&#39;t using esmodules by default, so let&#39;s use the &amp;quot;.mjs&amp;quot; extension. Something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// rollup-plugin-mydopeplugin.mjs

export default function myDopePlugin(){}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function should return an object with some specific keys which rollup is going to look for when running through your code. In the rollup docs these are referred to as &amp;quot;hooks&amp;quot; because they are places you can hook into the build - makes sense!&lt;/p&gt;
&lt;h2 id=&quot;name-property&quot; tabindex=&quot;-1&quot;&gt;Name Property &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/rollup-plugins-for-beginners/#name-property&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The most basic and important property to add is the name of your plugin. This will be used by rollup for any error messages or warnings. So let&#39;s name our plugin!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// rollup-plugin-mydopeplugin.mjs

export default function myDopePlugin(){
    return {
        name:&amp;quot;my-dope-plugin&amp;quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hey this really doesn&#39;t seem so bad does it? But we probably want our plugin to &lt;em&gt;do&lt;/em&gt; something. At the moment it will do absolutely nothing, but hey, it&#39;s bug free!&lt;/p&gt;
&lt;h2 id=&quot;transform-hook&quot; tabindex=&quot;-1&quot;&gt;Transform hook &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/rollup-plugins-for-beginners/#transform-hook&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This hook is going to let us change our code. It&#39;s a function that takes two arguments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your code (a string)&lt;/li&gt;
&lt;li&gt;The ID of the code (another string)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The string representation of the code is simple enough. But what do I mean by ID? We&#39;re JavaScript developers and probably used to IDs being something we use to grab a user or a post when we&#39;re processing data. In this case, the ID refers to the string path that was used to import the module. It&#39;s the string in the JavaScript import statement after &amp;quot;from&amp;quot; .&lt;/p&gt;
&lt;p&gt;So if our source code looked like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// index.js

import {uppercaseMyString} from &#39;./utilities.js&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the ID when rollup plugs the utilities import into a transform hook would be &amp;quot;./utilities.js&amp;quot;.&lt;/p&gt;
&lt;p&gt;What should our transform hook return? There are a few options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We can return null. This will tell rollup we don&#39;t want to transform the code at all and leave it as is.&lt;/li&gt;
&lt;li&gt;We can return a string. This will be the code after transformation.&lt;/li&gt;
&lt;li&gt;We can return an object. This can give rollup back the transformed code, a sourcemap, something called an &lt;a href=&quot;https://github.com/estree/estree&quot;&gt;abstract syntax tree&lt;/a&gt;. It can also provide more information about whether the module has side effects, whether you want to use tree shaking, and specify some special behaviour if another module tries to import a non-existent export.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;let&#39;s-write-our-plugin&quot; tabindex=&quot;-1&quot;&gt;Let&#39;s write our plugin &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/rollup-plugins-for-beginners/#let&#39;s-write-our-plugin&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&#39;s write a plugin which will insert a console.log at the start of every JavaScript file that gets imported in our project. We&#39;ll start by adding a transform function to our previous code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// rollup-plugin-mydopeplugin.mjs

export default function myDopePlugin(){
   return {
       name:&amp;quot;my-dope-plugin&amp;quot;,
       transform(source,id){

       }
   }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we&#39;ll take a look at the id and make sure we&#39;re looking at a JavaScript file. If it doesn&#39;t have the &amp;quot;.js&amp;quot; file extension, we&#39;ll return null to tell rollup we don&#39;t want to do anything with it. So our transform function becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;transform(source,id){
    if (id.slice(-3) !== &amp;quot;.js&amp;quot;) return null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally if it is a JavaScript file, let&#39;s concatenate a console.log to the front of our code and return the string.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// rollup-plugin-mydopeplugin.mjs

export default function myDopePlugin(options = {}) {
  return {
    name: &amp;quot;my-dope-plugin&amp;quot;,
    transform(source, id) {
      if (id.slice(-3) !== &amp;quot;.js&amp;quot;) return null;
      return &amp;quot;console.log(&#39;hello from rollup&#39;);&amp;quot; + source;
    },
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&#39;s our plugin finished!&lt;/p&gt;
&lt;p&gt;Now let&#39;s use it in a rollup build. Let&#39;s npm install rollup and create a &amp;quot;rollup.config.js&amp;quot; file. This file needs a default export which is an object with input and output keys that hold strings to where we want to input and output our source. It also has a key that holds an array of plugins.&lt;/p&gt;
&lt;p&gt;We can import our plugin to the rollup config file, then call its function within the plugins array in the rollup config:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// rollup.config.js
import myDopePlugin from &#39;./rollup-plugin-mydopeplugin.mjs&#39;;

export default {
  input: &#39;index.js&#39;,
  output: {
    file: &#39;bundle.js&#39;,
  },
  plugins: [ myDopePlugin() ]
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then when we run our rollup build we just need to tell it to use our config file by adding a &lt;code&gt;--config&lt;/code&gt; argument in the command line. If you do a default npm install it&#39;d look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./node_modules/.bin/rollup --config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&#39;s it! Hopefully this has made getting your hands dirty in the build step slightly less scary by keeping things super simple.&lt;/p&gt;
&lt;p&gt;As mentioned before, you could potentially use this plugin in the build step of a Vite project or a wmr project - not that this particular plugin has any use at all. But this is super empowering that we have some reusability in build systems now.&lt;/p&gt;
&lt;p&gt;There&#39;s quite a few more rollup hooks to look into if you want to get some more power building these plugins. I&#39;d also recommend checking out &lt;a href=&quot;https://www.youtube.com/watch?v=mr67QkDfkoQ&amp;amp;t=847s&quot;&gt;This talk about build plugins with Jake Archibald and Jason Miller&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I might try add another blogpost sometime soon getting deeper into rollup plugins as they&#39;re so dang useful.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>React and Electron</title>
    <link href="https://www.elliotclyde.nz/blog/react-and-electron/"/>
    <updated>2021-09-02T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/react-and-electron/</id>
    <content type="html">&lt;p&gt;Today I created my first Electron app. It was pretty cool to see something that would usually be stuck within a browser exist as its own unit on the operating system. Getting a &amp;quot;hello-world&amp;quot; going was really easy with the &lt;a href=&quot;https://www.electronjs.org/docs/tutorial/quick-start&quot;&gt;quick-start tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To get your first electron application built you’ll need to install both:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.electronjs.org/&quot;&gt;Electron&lt;/a&gt; to get the developer environment going&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.electronforge.io/&quot;&gt;Electron forge&lt;/a&gt; if you want to build/publish the application&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then after you’ve built some of your app you can run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx electron-forge import
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the end of this you’ll have 320MB worth of node_modules dependencies in your folder. In my case, forge set itself to use a “squirrel” windows build, which I assume it must have chosen on based on the fact I’m running windows.&lt;/p&gt;
&lt;p&gt;After you run the build on the quick start application, you’ll end up with an app 182MB sized application which shows one page, and seems to hog about 80MB of memory when it’s idling away.&lt;/p&gt;
&lt;p&gt;These are reasonably big numbers if you’re used to writing native programs in close-to-the-metal languages but the ability to use web technologies to write desktop apps is &lt;em&gt;awesome.&lt;/em&gt; And there’s &lt;a href=&quot;https://nielsleenheer.com/articles/2021/why-electron-apps-are-fine/&quot;&gt;a great article from Niels Leenheer&lt;/a&gt; about how Electron applications are fine, and will continue to be useful.&lt;/p&gt;
&lt;h2 id=&quot;but-what-about-react%3F&quot; tabindex=&quot;-1&quot;&gt;But what about React? &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/react-and-electron/#but-what-about-react%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So I started trying to convert a React single page application into an Electron app. How hard could this be? All I really need to do was convert some JSX to JS right before the app starts right?&lt;/p&gt;
&lt;p&gt;So I ended up on this &lt;a href=&quot;https://stackoverflow.com/questions/39859362/using-react-and-jsx-with-electron&quot;&gt;Stack overflow page&lt;/a&gt;. The top answer advises us to pull in a babel package called &lt;a href=&quot;https://babeljs.io/docs/en/babel-register&quot;&gt;Babel-Register&lt;/a&gt;, which looked like a pretty elegant solution. Unfortunately when trying this technique I had all kinds of problems with Electron getting mad at me for using an inline script, and babel yelling at me for using the wrong version even after bumping my dependencies. It wasn’t happening.&lt;/p&gt;
&lt;p&gt;So then I took a look at &lt;a href=&quot;https://electron-react-boilerplate.js.org/docs/installation/&quot;&gt;Electron React Boilerplate&lt;/a&gt;. This pulled in a mouth-watering amount of tooling. When I ran the command to get the dev server running, the scripts started asking for yarn to be installed. I haven’t got yarn. Maybe I should’ve bitten the bullet and downloaded it but I&#39;m more used to just sticking with npm. I tried a find-and-replace to change all the yarn scripts to npm scripts to see if that would would run - but no dice.&lt;/p&gt;
&lt;p&gt;So I threw the Electron React Boilerplate project away and went back to the hello world version. This time I npm installed esbuild - as it’s the speediest, most lightweight way to convert JSX into JS in 2021. Then I set up my npm build and start scripts to run esbuild first, pushing the JavaScript into an intermediate folder, then running the electron build/start command which pulled from the intermediate folder. This worked extremely well. I ran the script and my React app was live! 😈&lt;/p&gt;
&lt;p&gt;Here’s a &lt;a href=&quot;https://github.com/Elliotclyde/lightweight-react-electron-starter&quot;&gt;starter for this method of making electron apps with React and esbuild&lt;/a&gt;. Go forth and create native apps using your webby skills!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>What should “create” do?</title>
    <link href="https://www.elliotclyde.nz/blog/what-should-create-do/"/>
    <updated>2021-09-29T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/what-should-create-do/</id>
    <content type="html">&lt;p&gt;We use web apps to make a ridiculous quantity of content every day; videos, blogposts, statuses, documents. We’re ceaselessly clicking “new post”, “upload video”, “start a new document”.&lt;/p&gt;
&lt;p&gt;I had an epiphany about what should happen when you click “create” on a web app these days. This epiphany mainly applies when the content takes a long time to create, like writing a document, making a codepen, or, in my app, creating a quiz.&lt;/p&gt;
&lt;p&gt;Originally in my quiz app, the “create” link would direct you to a page with a form. You could use this form to build out multi-choice quiz questions then hit “submit” when finished. The new data would get posted to the backend, validated, and added to the database. The backend would then send the data back to confirm the submission was successful. After this, you would have the option to either host the quiz, or further edit the form.&lt;/p&gt;
&lt;p&gt;This all sounds pretty sensible. Right?&lt;/p&gt;
&lt;p&gt;But the experience I had created felt more like using the web in 2006 than in 2021. In what ways did this user experience transport the user back 15 years?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you were halfway through the form and refreshed the page or navigated to another page, you would lose everything you had created&lt;/li&gt;
&lt;li&gt;It was unclear what the “submit” button would actually do. Would the user be able to keep editing the quiz after it was submitted?&lt;/li&gt;
&lt;li&gt;There was no indication to the user suggesting how they might write half the quiz, save, then come back and finish it later&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This experience subverted expectations we place on modern web apps. It was tense. It made it feel like you &lt;em&gt;had&lt;/em&gt; to get the whole quiz right on the first sitting - like the content was made of delicate china and could break at the smallest push.&lt;/p&gt;
&lt;p&gt;We want to be able to take a break and know our work will still be there when we get back. My app used simple CRUD functionality which was similar to what you&#39;d do in old server-rendered applications in the early noughties. We shouldn’t build apps like this any more.&lt;/p&gt;
&lt;p&gt;When you’re using google docs, dropbox paper, or codepen and you hit the “create” (or the equivalent button) something different happens. A new space for the item has already been created on the backend by the time you see the page. If you look at the URL for these services, it’s already built out some kind of hashed ID for the content.&lt;/p&gt;
&lt;p&gt;None of these services have a “submit” button. A submit button would feel completely out of place. What does submitting mean when I can see it’s been autosaved? The user feels like they’re looking directly at the canonical version of their work. It’s like they’re looking into the database - like the real function of all of these apps is to be a glorified database GUI.&lt;/p&gt;
&lt;p&gt;I realise now that my quiz app shouldn’t have separate “create” and “edit” pages. In all the cloud-first content-creation services there is &lt;em&gt;one&lt;/em&gt; page to adjust the content whether you are creating it, or you’re coming back to it. In my quiz app when you click the “create” link, it should cause the backend to create a new empty quiz &lt;em&gt;first&lt;/em&gt;, then show you the page to edit it.&lt;/p&gt;
&lt;p&gt;As you build your quiz, it should be obvious that there is a button that will save your progress and allow you to keep working on it. Or even better, an indication that the quiz is getting autosaved.&lt;/p&gt;
&lt;p&gt;I don’t think this is at all a new idea. I think web apps have been using this pattern for years. But I think this little epiphany is highlights one of many details that can decide whether your app will spark anxiety, or &lt;a href=&quot;https://github.com/sw-yx/spark-joy&quot;&gt;spark joy&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Intro to RequestAnimationFrame</title>
    <link href="https://www.elliotclyde.nz/blog/Intro-to-requestAnimationFrame/"/>
    <updated>2021-12-22T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/Intro-to-requestAnimationFrame/</id>
    <content type="html">&lt;p&gt;&lt;code&gt;requestAnimationFrame&lt;/code&gt; is &lt;em&gt;the&lt;/em&gt; way to create complex animations with JavaScript. If you’re using an animation library, the chances are that it uses &lt;code&gt;requestAnimationFrame&lt;/code&gt; under the hood.&lt;/p&gt;
&lt;p&gt;How do you use &lt;code&gt;requestAnimationFrame&lt;/code&gt;? We&#39;ll start by looking at a simple example to make a line to repeatedly grow:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;-- HTML --&amp;gt;
    &amp;lt;div id=&amp;quot;line&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;    /* CSS */
    #line{
    background-color:black;
    height:1px;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;    /* JS */
    let width = 0;
    const line = document.getElementById(&amp;quot;line&amp;quot;);
    let lasttime;
    function step(time) {
      if (lasttime == undefined) {
        lasttime = time;
      }
      let delta = time - lasttime;
      if (width &amp;gt;= 1000) {
        width = 0;
      } else {
        width = width += delta/2;
      }
     line.style.width = `${width}px`;
      lasttime = time;
      window.requestAnimationFrame(step);
    }
    window.requestAnimationFrame(step);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://codepen.io/Elliotclyde/pen/poWgjVZ&quot;&gt;Here’s a Codepen with this code.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There&#39;s a lot going on here for such a small animation. 18 lines of pretty weird looking code to make a line get longer.&lt;/p&gt;
&lt;p&gt;This is one of the reasons why you might prefer to use CSS for simpler animations. You can actually do the exact same animation with only CSS:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /* CSS */
    #line{
      height:1px;
      margin-top:10px;
      background-color: black;
      animation-name: line;
      animation-duration: 4s;
      animation-iteration-count: infinite;
      animation-timing-function:linear;
    }
    @keyframes line {
      from {width:0%;}
      to {width:100%;}
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with &lt;em&gt;even less&lt;/em&gt; CSS using the animation shorthand property:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /* CSS */
    #line {
      height:1px;
      margin-top:10px;
      background-color: black;
      animation: line 4s infinite linear;
    }
    @keyframes line {
      from {width:0%;}
      to {width:100%;}
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, I guess the first rule of &lt;code&gt;requestAnimationFrame&lt;/code&gt; is ask yourself whether you actually need &lt;code&gt;requestAnimationFrame&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For little animations like this, probably not. But as animations get more complex and interactive, you&#39;ll find CSS to be increasingly insufficient. If you need fine-grained control of element positions and timing functions then &lt;code&gt;requestAnimationFrame&lt;/code&gt; is the way to go.&lt;/p&gt;
&lt;p&gt;To use it, we call the &lt;code&gt;window.requestAnimationFrame&lt;/code&gt; function and pass in a callback. Let’s pass in a function that prints “Hello from frame”:&lt;/p&gt;
&lt;p&gt;window.requestAnimationFrame(()=&amp;gt;{ console.log(&amp;quot;Hello from frame&amp;quot;) });&lt;/p&gt;
&lt;p&gt;Running this should print “Hello from frame” once. This isn’t particularly exciting. However, what&#39;s interesting about it is &lt;em&gt;when&lt;/em&gt; the callback function gets run. It looks like it runs immediately, but it actually gets run before the next browser repaint. The browser will usually repaint the page about 60 times a second which is why the callback &lt;em&gt;appears&lt;/em&gt; to run instantly.&lt;/p&gt;
&lt;p&gt;I&#39;ll even prove that it’s not run immediately. When our callback gets called, the browser passes it a decimal number as an argument. This number is the number of milliseconds since the &amp;quot;time origin&amp;quot; of the page (essentially since the page finished loading). We can compare this to the output of &lt;code&gt;performance.now()&lt;/code&gt; which prints out the same measurement:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /* JS */
    console.log(performance.now());
    window.requestAnimationFrame((timestamp)=&amp;gt;{console.log(timestamp)});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When I ran this in codepen it printed off:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    1661.5999999940395
    1680.269
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will print something different every time, depending on browser performance (and a whole bunch of other factors) but there should be a gap of about 30ms between the &lt;code&gt;performance.now()&lt;/code&gt; call and the &lt;code&gt;requestAnimationFrame&lt;/code&gt; call.&lt;/p&gt;
&lt;p&gt;To use &lt;code&gt;window.requestAnimationFrame&lt;/code&gt; for animation we can&#39;t just throw a callback into it once and leave it. Instead, we want to continously update the page. To get our code to run continuously, we call &lt;code&gt;requestAnimationFrame&lt;/code&gt;, passing it our callback function &lt;em&gt;inside&lt;/em&gt; the callback. We also need to name our function to do this. We’ll name it &lt;code&gt;step&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /* JS */
    function step(timestamp){
      console.log(timestamp);
      window.requestAnimationFrame(step);
    }
    window.requestAnimationFrame(step);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code will continually print timestamps in the console before each browser repaint. But I still wouldn’t exactly call this animation. To actually animate something, we need to change an element’s style on each tick.&lt;/p&gt;
&lt;p&gt;Let’s add a centered red box to the page:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;-- HTML --&amp;gt;
    &amp;lt;div id=&amp;quot;growing-box&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;    /* CSS */
    body{
      height:100vh;
      margin:0;
      display:flex;
      justify-content:center;
      align-items:center;
    }
    #growing-box{
      width:40vmin;
      height:40vmin;
      background-color:#af0000 ;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s use &lt;code&gt;requestAnimationFrame&lt;/code&gt; to make the box slowly grow. The best way to do this is using the &lt;code&gt;transform&lt;/code&gt; CSS property, and the &lt;code&gt;scale&lt;/code&gt; function, as &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Performance/Fundamentals#use_css_transforms&quot;&gt;this gets GPU accelerated by the browser&lt;/a&gt;. So, we grab the box , set a scale variable to start with a value of 1, add 0.01 to the scale on each iteration, and apply the scale to the element on each iteration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /* JS */
    let square = document.getElementById(&amp;quot;growing-box&amp;quot;);
    let currentScale = 1;
    function step(timestamp){
      currentScale += 0.01;

      square.style.transform = `scale(${currentScale})`

      window.requestAnimationFrame(step);
    }
    window.requestAnimationFrame(step);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://codepen.io/Elliotclyde/pen/zYEdBQg?editors=1010&quot;&gt;Here’s a codepen showing what this looks like.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now we have a growing square, but there’s a couple of problems.&lt;/p&gt;
&lt;p&gt;First, it will grow forever. We need to add some kind of check to make sure that it stops (or repeats) at some point.&lt;/p&gt;
&lt;p&gt;Let’s add a check which will test whether the scale is equal or above 2, and, if so, reset it to 1.&lt;/p&gt;
&lt;p&gt;Second, it will grow unevenly. This is because the interval between browser repaints varies, but we’re changing the scale the same amount each time. It won’t be very noticeable on faster devices, but will become a problem as repaints get more intermittent.&lt;/p&gt;
&lt;p&gt;Let’s measure the time between the last frame and the current frame (we’ll use the convention of naming this &lt;code&gt;delta&lt;/code&gt;). We use the delta to figure out how far we need to grow the square. We’ll divide delta by 30, just to make sure the square isn’t growing too fast.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /* JS */
    let square = document.getElementById(&amp;quot;growing-box&amp;quot;);
    let currentScale = 1;
    let lastTime;

    function step(timestamp){

      if(lastTime==undefined){
        lastTime = timestamp;
      }

      let delta = timestamp - lastTime;
      currentScale += 0.01 * (delta / 30);

      if(currentScale&amp;gt;=2){
        currentScale= 1;
      }

      square.style.transform = `scale(${currentScale})`

      window.requestAnimationFrame(step);
      lastTime= timestamp;
    }
    window.requestAnimationFrame(step);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have a box which repeatedly grows then jumps back down to being small again.&lt;/p&gt;
&lt;p&gt;We can still do better.&lt;/p&gt;
&lt;p&gt;At the moment, there isn’t any easy way to change the speed of the animation, how big the square gets, or whether the animation loops. So let’s make some variables.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let animationLength = 1;
    let animationLengthMillis = animationLength *1000;
    let timeThrough = 0;
    let startScale = 1;
    let endScale = 2;
    let looping = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’re storing the animation length (in both seconds and milliseconds), the amount of time that has passed through the current iteration, the start/end points for our scale property, and finally whether the animation should loop.&lt;/p&gt;
&lt;p&gt;We need to change a couple things to use these. Firstly, we can use the &lt;code&gt;timeThrough&lt;/code&gt; variable to keep track of how far through the animation we are, and add our &lt;code&gt;delta&lt;/code&gt; variable to this on each tick.&lt;/p&gt;
&lt;p&gt;Then we can get a fraction value of how far through the animation we are by dividing the &lt;code&gt;timeThrough&lt;/code&gt; by the &lt;code&gt;animationLengthMillis&lt;/code&gt;. Then we set the &lt;code&gt;currentScale&lt;/code&gt; to the &lt;code&gt;startScale&lt;/code&gt; plus the difference between &lt;code&gt;endScale&lt;/code&gt; and &lt;code&gt;startScale&lt;/code&gt; multiplied by the fraction through the animation we are:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let percentThrough = timeThrough / animationLengthMillis;
    let currentScale = startScale + (endScale - startScale) * percentThrough;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a lot. But basically what it’s doing is setting the scale based on where we are in the animation.&lt;/p&gt;
&lt;p&gt;Now we need to make sure the animation lasts the correct length. So, we check to see if the time that has passed is more than or equal the animation length. If so, we set &lt;code&gt;timeThrough&lt;/code&gt; back to zero or return to end the animation (depending on whether we&#39;re looping or not).&lt;/p&gt;
&lt;p&gt;Here’s how it looks:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /* JS */
    let square = document.getElementById(&amp;quot;growing-box&amp;quot;);
    let animationLength = 1;
    let animationLengthMillis = animationLength * 1000;

    let lastTime;
    let timeThrough = 0;
    let startScale = 1;
    let endScale = 2;
    let looping = true;

    function step(timestamp) {

      if (lastTime === undefined) {
        lastTime = timestamp;
      }
      let delta = timestamp - lastTime;

      timeThrough += delta;

      if (timeThrough &amp;gt;= animationLengthMillis) {
        if (looping) {
          timeThrough = 0;
        } else {
          return;
        }
      }

      let percentThrough = timeThrough / animationLengthMillis;
      let currentScale = startScale + (endScale - startScale) * percentThrough;

      square.style.transform = `scale(${currentScale})`;

      window.requestAnimationFrame(step);
      lastTime = timestamp;
    }
    window.requestAnimationFrame(step);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And, &lt;a href=&quot;https://codepen.io/Elliotclyde/pen/WNZExLJ&quot;&gt;here’s the codepen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can see how you can edit these variables to your taste to make the animation look the way you need.&lt;/p&gt;
&lt;p&gt;Animation can make user interfaces less jarring, and more human-friendly. They’re a necessary tool to have in your user-interface toolbox. For something a bit less trivial you can check out a &lt;a href=&quot;https://codepen.io/Elliotclyde/pen/abyxREV?editors=1011&quot;&gt;minimal notetaking app with some squishy animations over at this codepen&lt;/a&gt;, and for more information on &lt;code&gt;requestAnimationFrame&lt;/code&gt; you can’t go wrong with the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame&quot;&gt;MDN web docs&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Explorations in C, C++, Win32, and SDL</title>
    <link href="https://www.elliotclyde.nz/blog/explorations-in-c-cplusplus-win32-and-sdl/"/>
    <updated>2022-01-10T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/explorations-in-c-cplusplus-win32-and-sdl/</id>
    <content type="html">&lt;p&gt;lately I&#39;ve been experimenting with C, C++, Win32, and SDL. The first thing I&#39;ve noticed is we don&#39;t know how good we have it in the JavaScript/web development world:&lt;/p&gt;
&lt;h2 id=&quot;blog-posts-and-online-resources&quot; tabindex=&quot;-1&quot;&gt;Blog posts and online resources &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/explorations-in-c-cplusplus-win32-and-sdl/#blog-posts-and-online-resources&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can find great documentation for web stuff everywhere. JavaScript has blown up in a time when all kinds of people are becoming coders. There are a thousand accessible blog posts for any topic. And these posts actually get surfaced by Google!&lt;/p&gt;
&lt;p&gt;C++ was blowing up when at a time when developers were a small subsection of society. If you were a coder, you had to be passionate. Of course you were - you were making command line apps! And if you were lucky enough to build GUIs, you had a lot of patience. Blog posts about C and C++ are relatively difficult to find and usually assume a lot of previous knowledge. They usually reference star wars. They argue about the differences between C and C++ and their use cases. They talk about how pointer arithmetic separates the men from the boys. 🤮&lt;/p&gt;
&lt;p&gt;But it makes sense that we’re going to get more resources about the languages of the web on the web. A lot of the knowledge on C and C++ exists in those old things called books!&lt;/p&gt;
&lt;h2 id=&quot;language-features&quot; tabindex=&quot;-1&quot;&gt;Language features &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/explorations-in-c-cplusplus-win32-and-sdl/#language-features&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We also have a lot of friendly language features in JS. JavaScript arrays aren&#39;t real arrays. They&#39;re more like cuddly rows of boxes that will pat you on the head and buy you an ice-cream for accessing an index.&lt;/p&gt;
&lt;p&gt;In JavaScript we have problems with differing module types from the migration from common.js to esm, but we have nothing compared to the nightmare of header file includes, DLLs, and Makefiles. Also in C++ you need to reimplement rounding floating point numbers into ints - There is no comfy &lt;code&gt;Math&lt;/code&gt; class for you here.&lt;/p&gt;
&lt;h2 id=&quot;win32-and-microsoft&quot; tabindex=&quot;-1&quot;&gt;Win32 and Microsoft &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/explorations-in-c-cplusplus-win32-and-sdl/#win32-and-microsoft&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Navigating Microsoft documentation remains extremely difficult. Lists of links to explain different APIs take you on a rocky journey. The win32 API is obtuse and ugly. It demands a huge number of arguments in functions to get a window showing. You need gigantic event switch statements to take user input.&lt;/p&gt;
&lt;h2 id=&quot;why-though%3F&quot; tabindex=&quot;-1&quot;&gt;Why though? &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/explorations-in-c-cplusplus-win32-and-sdl/#why-though%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This all said, I think all of this is worth taking a look at. Why? Because C, C++ and Win32 create efficient programs. In both program size and runtime speed they beat electron apps. In a lot of ways they are better for the user. They&#39;re close to the metal and teach you about what the computer is actually doing. Our browsers are written with C++, our youtube videos are processed with C++, our databases are written with C++. And all the tools for C and C++ are free for the taking on the internet. Download &lt;a href=&quot;https://www.mingw-w64.org/&quot;&gt;Mingw&lt;/a&gt; on windows and open up your favourite text editor and you’re ready to start compiling! There are more steps here than making an index.html file, but not &lt;em&gt;that&lt;/em&gt; many more steps.&lt;/p&gt;
&lt;p&gt;I know that if you want a systems language for cool kids, there’s rust. I know that the majority of web apps will be fast enough with JavaScript and PHP based tools (and all the popular alternatives). I know that the high learning curve of C and C++ mean they’re probably not the most bang-for-your-buck languages for a web developer to learn in early 2022. But I’m always obsessed with the old ways of doing things and can’t help myself exploring.&lt;/p&gt;
&lt;h2 id=&quot;sdl&quot; tabindex=&quot;-1&quot;&gt;SDL &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/explorations-in-c-cplusplus-win32-and-sdl/#sdl&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.libsdl.org/&quot;&gt;SDL&lt;/a&gt; is a toolkit for C and C++ to create “multimedia applications” (games. It’s for games.). &lt;a href=&quot;https://lazyfoo.net/tutorials/SDL/&quot;&gt;Lazyfoo has a great tutorial on how to use it&lt;/a&gt;. If you’ve made a few command line programs and want to try making games, I’d recommend trying SDL. It’s so lightweight, and gives you a nice way to dig into building a little game engine and see what C and C++ are all about. It’s cross platform, has a few more resources around online, and is a lot nicer than trying to write raw openGL code yourself.&lt;/p&gt;
&lt;h2 id=&quot;more-thoughts&quot; tabindex=&quot;-1&quot;&gt;More thoughts &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/explorations-in-c-cplusplus-win32-and-sdl/#more-thoughts&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Do I even know what pointers are? The way I think about them is indexes of a giant hidden array which directly exposes the computers memory. I don’t know if this is correct, but I almost feel like a real programmer hitting that star key &lt;code&gt;*&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And could I find a way to hook into and extend node with C++? &lt;a href=&quot;https://nodejs.org/dist/latest-v17.x/docs/api/n-api.html&quot;&gt;Looks like I could! Assuming I have the time and willingness.&lt;/a&gt; And could I ever finish a game with C++? &lt;a href=&quot;https://www.theverge.com/2019/10/9/20903139/indie-game-developers-creators-money-funding&quot;&gt;Probably not if I want to have money and/or time&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>My JavaFX ListViews are doubling up</title>
    <link href="https://www.elliotclyde.nz/blog/My-JavaFX-ListViews-are-doubling-up/"/>
    <updated>2022-01-18T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/My-JavaFX-ListViews-are-doubling-up/</id>
    <content type="html">&lt;p&gt;You’re making a JavaFX app and you want to render a collection properly by using a &lt;code&gt;ListView&lt;/code&gt; or &lt;code&gt;TableView&lt;/code&gt;. These components are great: you point them to an &lt;code&gt;ObservableList&lt;/code&gt; and all you need to do is update this list and the views will automatically be updated. You can also generate a bunch of your favourite JavaFX UI elements for each item in the list — including buttons, labels, inputs — any components at all!&lt;/p&gt;
&lt;p&gt;There’s a problem you might run into though. We’ll see it soon.&lt;/p&gt;
&lt;h2 id=&quot;hello-listview&quot; tabindex=&quot;-1&quot;&gt;Hello ListView &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/My-JavaFX-ListViews-are-doubling-up/#hello-listview&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We’ll start with the “hello world” of JavaFX list views. The code below is essentially &lt;a href=&quot;https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ListView.html&quot;&gt;what you’d find in the JavaFX ListView docs&lt;/a&gt;. But I’ve rendered inside of an application scene:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
    import javafx.application.Application;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.scene.Scene;
    import javafx.scene.control.ListView;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;

    public class Main extends Application {

            @Override
            public void start(Stage primaryStage) throws Exception {

                     ObservableList&amp;lt;String&amp;gt; names = FXCollections.observableArrayList(
                              &amp;quot;Julia&amp;quot;, &amp;quot;Ian&amp;quot;, &amp;quot;Sue&amp;quot;, &amp;quot;Matthew&amp;quot;, &amp;quot;Hannah&amp;quot;, &amp;quot;Stephan&amp;quot;, &amp;quot;Denise&amp;quot;);
                     ListView&amp;lt;String&amp;gt; listView = new ListView&amp;lt;String&amp;gt;(names);

                    VBox pane = new VBox();
                    pane.getChildren().add(listView);
                    Scene scene = new Scene(pane, 700, 500);
                    primaryStage.setScene(scene);

                    primaryStage.show();
            }
    public static void main(String[] args) {
            launch(args);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works fine and prints off a nice list of names.&lt;/p&gt;
&lt;h2 id=&quot;cell-factories&quot; tabindex=&quot;-1&quot;&gt;Cell factories &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/My-JavaFX-ListViews-are-doubling-up/#cell-factories&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What if we wanted to customize our &lt;code&gt;ListView&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;Let’s make it render the following for each item in our observable list:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;Text&lt;/code&gt; component for the name&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;Region&lt;/code&gt; component to separate the name from the others&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;TextField&lt;/code&gt; component to edit the name&lt;/li&gt;
&lt;li&gt;A submit &lt;code&gt;Button&lt;/code&gt; to confirm the change&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ll wrap these in an &lt;code&gt;HBox&lt;/code&gt; component so they’re nicely displayed horizontally on the screen.&lt;/p&gt;
&lt;p&gt;You can do this using a &lt;a href=&quot;https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ListView.html#cellFactoryProperty--&quot;&gt;cell factory&lt;/a&gt; - which needs to be a class that implements the JavaFX &lt;code&gt;CallBack&lt;/code&gt; interface. In this class we override the &lt;code&gt;call&lt;/code&gt; method to return a new &lt;code&gt;ListCell&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;ListCell&lt;/code&gt; object we return, we want to override the &lt;code&gt;updateItem&lt;/code&gt; method and call &lt;code&gt;setGraphic&lt;/code&gt; inside. In the arguments of &lt;code&gt;setGraphic&lt;/code&gt;, we pass the component that should be created for each cell. We’re going to pass it our &lt;code&gt;HBox&lt;/code&gt; after we’ve created it and filled it with the list of components from before.&lt;/p&gt;
&lt;p&gt;I promise it’s not too bad. Here’s what it looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
    /* Main.java
    Do not write your JavaFX like this - we need to fix this up!
    */
    import javafx.application.Application;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.ListCell;
    import javafx.scene.control.ListView;
    import javafx.scene.control.TextField;
    import javafx.scene.layout.HBox;
    import javafx.scene.layout.Priority;
    import javafx.scene.layout.Region;
    import javafx.scene.layout.VBox;
    import javafx.scene.text.Text;
    import javafx.stage.Stage;
    import javafx.util.Callback;

    public class Main extends Application {

            @Override
            public void start(Stage primaryStage) throws Exception {

                     ObservableList&amp;lt;String&amp;gt; names = FXCollections.observableArrayList(
                              &amp;quot;Julia&amp;quot;, &amp;quot;Ian&amp;quot;, &amp;quot;Sue&amp;quot;, &amp;quot;Matthew&amp;quot;, &amp;quot;Hannah&amp;quot;, &amp;quot;Stephan&amp;quot;, &amp;quot;Denise&amp;quot;);
                     ListView&amp;lt;String&amp;gt; listView = new ListView&amp;lt;String&amp;gt;(names);

                     class PersonCellFactory implements Callback&amp;lt;ListView&amp;lt;String&amp;gt;, ListCell&amp;lt;String&amp;gt;&amp;gt; {

                                    @Override
                                    public ListCell&amp;lt;String&amp;gt; call(ListView&amp;lt;String&amp;gt; listView) {
                                               return new ListCell&amp;lt;String&amp;gt;(){
                                                @Override
                                                public void updateItem(String nameString, boolean empty) {
                                                    super.updateItem(nameString, empty);
                                                    Text nameText = new Text(nameString);
                                                    Region region1 = new Region();
                                                    TextField nameField = new TextField(nameString);
                                                    Button changeNameButton = new Button(&amp;quot;update&amp;quot;);
                                                    HBox box = new HBox();
                                                    HBox.setHgrow(region1, Priority.ALWAYS);
                                                    box.setAlignment(Pos.BASELINE_LEFT);
                                                    box.getChildren().addAll(nameText,region1,nameField,changeNameButton);
                                                    setGraphic(box);
                                                }
                                            };
                                    }
                            }
                    listView.setCellFactory(new PersonCellFactory());
                    VBox pane = new VBox();
                    pane.getChildren().add(listView);
                    Scene scene = new Scene(pane, 700, 500);
                    primaryStage.setScene(scene);

                    primaryStage.show();
            }
    public static void main(String[] args) {
            launch(args);
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run this you’ll get something a little weird, where you get a clickable list of names, then a big list of items underneath in which the button and texfield is clickable, but not the rest. This is sort of what we want, but not really.&lt;/p&gt;
&lt;h2 id=&quot;checking-for-empty-cells&quot; tabindex=&quot;-1&quot;&gt;Checking for empty cells &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/My-JavaFX-ListViews-are-doubling-up/#checking-for-empty-cells&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What we need to do is add a check in &lt;code&gt;updateItem&lt;/code&gt; to make sure that if it’s an empty cell, we don’t add anything in. This is what you might think this should like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
    @Override
    public void updateItem(String nameString, boolean empty) {

            super.updateItem(nameString, empty);
            /*
            * Uh oh! This is buggy! Don&#39;t worry we&#39;ll fix it soon
            */
            if (empty) {
                    return;
            }
            Text nameText = new Text(nameString);
            Region region1 = new Region();
            TextField nameField = new TextField(nameString);
            Button changeNameButton = new Button(&amp;quot;update&amp;quot;);
            HBox box = new HBox();
            HBox.setHgrow(region1, Priority.ALWAYS);
            box.setAlignment(Pos.BASELINE_LEFT);
            box.getChildren().addAll(nameText, region1, nameField, changeNameButton);
            setGraphic(box);
    }

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay, when we run it it seems okay? We’re only rendering the items that aren’t empty - just what we wanted.&lt;/p&gt;
&lt;p&gt;But let’s say that we add a button afterwards to add a new person to our list:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
    listView.setCellFactory(new PersonCellFactory());
    /* New code from here */
    Button newPersonButton = new Button(&amp;quot;Add person&amp;quot;);
    newPersonButton.setOnAction(event-&amp;gt;{
            names.add(&amp;quot;Johnny&amp;quot;);
    });
    VBox pane = new VBox();
    pane.getChildren().add(listView,newPersonButton);
    /* New code ends here */
    Scene scene = new Scene(pane, 700, 500);
    /* etc */

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we add this, everything looks fine to start with, but when we click the &lt;code&gt;newPersonButton&lt;/code&gt; we run into our problem. The new item, “Johnny”, gets added, but all the other items seem to appear under it in reverse order! What is going on?&lt;/p&gt;
&lt;h2 id=&quot;why-isn%E2%80%99t-my-javafx-listview-working%3F&quot; tabindex=&quot;-1&quot;&gt;Why isn’t my JavaFX ListView working? &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/My-JavaFX-ListViews-are-doubling-up/#why-isn%E2%80%99t-my-javafx-listview-working%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is something to do with the internals of the way the &lt;code&gt;ListView&lt;/code&gt; interacts with cell factories. What we need to do when the cell is empty is not just return, but &lt;em&gt;set the graphic as null&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This is because the &lt;code&gt;updateItem&lt;/code&gt; method is used not only to create the cell, but also to clean it up!&lt;/p&gt;
&lt;p&gt;This is how our check should look:&lt;/p&gt;
&lt;p&gt;if (empty) {&lt;br /&gt;
/* This is the important part */&lt;br /&gt;
setGraphic(null);&lt;br /&gt;
return;&lt;br /&gt;
}&lt;/p&gt;
&lt;p&gt;And here’s how the whole program should look at this point:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /* Main.java
    Do not write your JavaFX like this - we need to fix this up!
    */

    import javafx.application.Application;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.ListCell;
    import javafx.scene.control.ListView;
    import javafx.scene.control.TextField;
    import javafx.scene.layout.HBox;
    import javafx.scene.layout.Priority;
    import javafx.scene.layout.Region;
    import javafx.scene.layout.VBox;
    import javafx.scene.text.Text;
    import javafx.stage.Stage;
    import javafx.util.Callback;

    public class Main extends Application {

            @Override
            public void start(Stage primaryStage) throws Exception {

                    ObservableList&amp;lt;String&amp;gt; names = FXCollections.observableArrayList(&amp;quot;Julia&amp;quot;, &amp;quot;Ian&amp;quot;, &amp;quot;Sue&amp;quot;, &amp;quot;Matthew&amp;quot;, &amp;quot;Hannah&amp;quot;,
                                    &amp;quot;Stephan&amp;quot;, &amp;quot;Denise&amp;quot;);
                    ListView&amp;lt;String&amp;gt; listView = new ListView&amp;lt;String&amp;gt;(names);

                    class PersonCellFactory implements Callback&amp;lt;ListView&amp;lt;String&amp;gt;, ListCell&amp;lt;String&amp;gt;&amp;gt; {

                            @Override
                            public ListCell&amp;lt;String&amp;gt; call(ListView&amp;lt;String&amp;gt; listView) {
                                    return new ListCell&amp;lt;String&amp;gt;() {
                                            @Override
                                            public void updateItem(String nameString, boolean empty) {

                                                    super.updateItem(nameString, empty);
                                                    if (empty ) {
                                                            /* This is the important part */
                                                            setGraphic(null);
                                                            return;
                                                    }
                                                    Text nameText = new Text(nameString);
                                                    Region region1 = new Region();
                                                    TextField nameField = new TextField(nameString);
                                                    Button changeNameButton = new Button(&amp;quot;update&amp;quot;);
                                                    HBox box = new HBox();
                                                    HBox.setHgrow(region1, Priority.ALWAYS);
                                                    box.setAlignment(Pos.BASELINE_LEFT);
                                                    box.getChildren().addAll(nameText, region1, nameField, changeNameButton);
                                                    setGraphic(box);
                                            }
                                    };
                            }
                    }

                    listView.setCellFactory(new PersonCellFactory());

                    Button newPersonButton = new Button(&amp;quot;Add person&amp;quot;);
                    newPersonButton.setOnAction(event -&amp;gt; {
                            names.add(&amp;quot;Johnny&amp;quot;);
                    });
                    VBox pane = new VBox();
                    pane.getChildren().addAll(listView, newPersonButton);
                    Scene scene = new Scene(pane, 700, 500);
                    primaryStage.setScene(scene);

                    primaryStage.show();
            }

            public static void main(String[] args) {
                    launch(args);
            }
    }

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But wait! This still isn&#39;t ideal. There&#39;s a better way!!!!&lt;/p&gt;
&lt;p&gt;We&#39;re doing &lt;a href=&quot;https://twitter.com/dlemmermann/status/1483757534175539206&quot;&gt;too much work in our updateItem method!&lt;/a&gt; A big thank you to &lt;a href=&quot;https://dlsc.com/&quot;&gt;Dirk Lemmermann&lt;/a&gt; for pointing this out.&lt;/p&gt;
&lt;h2 id=&quot;reducing-the-amount-of-work-in-the-updateitem-method&quot; tabindex=&quot;-1&quot;&gt;Reducing the amount of work in the updateItem method &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/My-JavaFX-ListViews-are-doubling-up/#reducing-the-amount-of-work-in-the-updateitem-method&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It turns out the &lt;code&gt;updateItem&lt;/code&gt; method gets &lt;a href=&quot;https://stackoverflow.com/a/25246914/15772394&quot;&gt;called all the time&lt;/a&gt;. This includes getting called in response to select and focus events as well as changes in the data model. At the moment our code is rebuilding the box and all of its child components from scratch for every item, every single time one of these events happens.&lt;/p&gt;
&lt;p&gt;To reduce the amount of work that we&#39;re doing in our &lt;code&gt;updateItem&lt;/code&gt; method, we can build the child components once in the cell factory (or if you make a custom cell class: in the constructor). After that, the only thing our &lt;code&gt;updateItem&lt;/code&gt; method needs to do, is call &lt;code&gt;setText&lt;/code&gt; on the &lt;code&gt;nameText&lt;/code&gt; and &lt;code&gt;nameField&lt;/code&gt; components.&lt;/p&gt;
&lt;p&gt;To stop the cell from showing up when it&#39;s empty we can use a superpower JavaFX provides: &lt;a href=&quot;https://docs.oracle.com/javafx/2/binding/jfxpub-binding.htm&quot;&gt;Binding&lt;/a&gt;. We want to bind the visibility property of the &lt;code&gt;HBox&lt;/code&gt; to the inverse of the empty property of the &lt;code&gt;ListCell&lt;/code&gt;. Here&#39;s what this looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;box.visibleProperty().bind(cell.emptyProperty().not());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here&#39;s how it looks in context:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Callback;

public class Main extends Application {

	@Override
	public void start(Stage primaryStage) throws Exception {

		ObservableList&amp;lt;String&amp;gt; names = FXCollections.observableArrayList(&amp;quot;Julia&amp;quot;, &amp;quot;Ian&amp;quot;, &amp;quot;Sue&amp;quot;, &amp;quot;Matthew&amp;quot;, &amp;quot;Hannah&amp;quot;,
				&amp;quot;Stephan&amp;quot;, &amp;quot;Denise&amp;quot;);
		ListView&amp;lt;String&amp;gt; listView = new ListView&amp;lt;String&amp;gt;(names);

		class PersonCellFactory implements Callback&amp;lt;ListView&amp;lt;String&amp;gt;, ListCell&amp;lt;String&amp;gt;&amp;gt; {

			@Override
			public ListCell&amp;lt;String&amp;gt; call(ListView&amp;lt;String&amp;gt; listView) {

				Text nameText = new Text();
				Region region1 = new Region();
				TextField nameField = new TextField();
				Button changeNameButton = new Button(&amp;quot;update&amp;quot;);
				HBox.setHgrow(region1, Priority.ALWAYS);
				HBox box = new HBox();
				box.setAlignment(Pos.BASELINE_LEFT);
				box.getChildren().addAll(nameText, region1, nameField, changeNameButton);

				ListCell&amp;lt;String&amp;gt; cell = new ListCell&amp;lt;String&amp;gt;() {
					public void updateItem(String nameString, boolean empty) {
						super.updateItem(nameString, empty);
						nameText.setText(nameString);
						nameField.setText(nameString);
					}
				};
				cell.setGraphic(box);
				box.visibleProperty().bind(cell.emptyProperty().not());
				return cell;
			}
		}

		listView.setCellFactory(new PersonCellFactory());

		Button newPersonButton = new Button(&amp;quot;Add person&amp;quot;);
		newPersonButton.setOnAction(event -&amp;gt; {
			names.add(&amp;quot;Johnny&amp;quot;);
		});
		VBox pane = new VBox();
		pane.getChildren().addAll(listView, newPersonButton);
		Scene scene = new Scene(pane, 700, 500);
		primaryStage.setScene(scene);

		primaryStage.show();
	}

	public static void main(String[] args) {
		launch(args);
	}
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nice! Custom JavaFX ListViews behaving just the way we want them to and not doing to much work to recreate the UI. 😌&lt;/p&gt;
&lt;p&gt;Now onto writing the code to edit the items. . .&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>The callbacks are out to get you</title>
    <link href="https://www.elliotclyde.nz/blog/The-callbacks-are-out-to-get-you/"/>
    <updated>2022-03-03T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/The-callbacks-are-out-to-get-you/</id>
    <content type="html">&lt;p&gt;I remember some of the first lines of JavaScript code I looked at were in &lt;a href=&quot;https://javascriptbook.com/&quot;&gt;Jon Duckett’s JavaScript &amp;amp; JQuery&lt;/a&gt;. This book and &lt;a href=&quot;https://www.htmlandcssbook.com/&quot;&gt;HTML &amp;amp; CSS&lt;/a&gt; are the canonical get-into-web-development books. They are truly singular in that they’re the only ever textbooks to actually LOOK GOOD.&lt;/p&gt;
&lt;p&gt;Anyway, I remember coming to the section of the book about adding click handlers. There was a code sample that looked something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let button = document.getElementById(&amp;quot;button&amp;quot;);
    button.addEventListener(&amp;quot;click&amp;quot;, onClick);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After this it defined the &lt;code&gt;onClick&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function onClick(event){
        console.log(&amp;quot;Button has been clicked&amp;quot;);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To anyone who knows and loves JavaScript, this is a very simple bit of code.&lt;/p&gt;
&lt;p&gt;But it’s also something I remember finding confusing. And since I read that book back in 2019 I had forgotten how confusing it was, until I witnessed some programmers who were new to JavaScript getting caught by it.&lt;/p&gt;
&lt;p&gt;My problem was with the arguments to the &lt;code&gt;addEventListener&lt;/code&gt; method. This method takes in two arguments. The first is a string describing which event you want to respond to - this makes sense. But what is the second argument?&lt;/p&gt;
&lt;p&gt;Well it’s a function. It’s describes what will happen after the button is clicked.&lt;/p&gt;
&lt;p&gt;Okay, it’s a function. We write those with round brackets at the end. So I should add the function to the event listener like this, right?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     button.addEventListener(&amp;quot;click&amp;quot;, onClick());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;WRONG! If you write your click handler like this, you’ll click your button and it confusingly won’t do anything. Even though &lt;code&gt;onClick&lt;/code&gt; is a function, we have to remove the round brackets after &lt;code&gt;onClick&lt;/code&gt; for this line of code.&lt;/p&gt;
&lt;p&gt;Why are these round brackets so evil you ask? Every time you’ve seen a function in JavaScript up until now, it’s had a nice pair of round brackets afterward. For instance, when I declare a function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
    // Has some round brackets with no arguments
    function myFunction(){
        console.log(&amp;quot;I am such a great function&amp;quot;);
    }
    // Has some round brackets with arguments too!
    function add(num1, num2){
        return num1 + num2;
    }

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And when I call a function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // Has some round brackets when we call them
    myFunction();
    add(3,4);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But all of a sudden we’re doing something that involves functions, but we’re not placing curly brackets after its name? Why?&lt;/p&gt;
&lt;h2 id=&quot;functions-as-a-first-class-citizen&quot; tabindex=&quot;-1&quot;&gt;Functions as a first class citizen &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/The-callbacks-are-out-to-get-you/#functions-as-a-first-class-citizen&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The reason for what is going on here is that JavaScript treats &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function&quot;&gt;functions as a first class citizen&lt;/a&gt;. Functions are allowed to do anything that any variable can do - get passed into other functions, get pointed to by multiple variables, get mutated. All sorts.&lt;/p&gt;
&lt;p&gt;This means this syntax:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    function myFunction(){
        console.log(&amp;quot;I am such a great function&amp;quot;);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Does pretty much the same thing as this syntax:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let myFunction = function(){
        console.log(&amp;quot;I am such a great function&amp;quot;);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both are valid JavaScript syntax, and both create a variable called &lt;code&gt;myFunction&lt;/code&gt; which points to a function. We can pass this variable (which points to a function) INTO ANOTHER function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // Declare first function
    function myFunction(){
        console.log(&amp;quot;I am such a great function&amp;quot;);
    }
    // Declare second function
    function anotherFunction(input){
        input();
    }
    // Here we pass the first function into the second one
    anotherFunction(myFunction);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The big difference when we put the round brackets after a function, is that this means the function will get excecuted immediately when the JavaScript is executed. It’s a way of saying “run this now!”, and if there are arguments “run this now with these arguments”.&lt;/p&gt;
&lt;p&gt;When the JavaScript is executing and gets to that line, it will run the function and replace it with its result.&lt;/p&gt;
&lt;p&gt;So when we do this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    button.addEventListener(&amp;quot;click&amp;quot;, onClick());
    function onClick(event){
        console.log(&amp;quot;Button has been clicked&amp;quot;);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’re passing the event listener what our &lt;code&gt;onClick&lt;/code&gt; event &lt;em&gt;returns&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;And it doesn’t return anything. So, what we’re actually passing into the event listener is &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of passing in &lt;code&gt;undefined&lt;/code&gt;, we want to pass our function. That’s why we don’t have a pair of round brackets after the second argument.&lt;/p&gt;
&lt;p&gt;It’s a way of saying, “hey, can you do this thing later?”&lt;/p&gt;
&lt;p&gt;And that, is a callback.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Why is SQL weird?</title>
    <link href="https://www.elliotclyde.nz/blog/why-is-sql-weird/"/>
    <updated>2022-05-10T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/why-is-sql-weird/</id>
    <content type="html">&lt;p&gt;If you make a search for &amp;quot;SQL” on &lt;a href=&quot;https://www.seek.co.nz/sql-jobs&quot;&gt;Seek&lt;/a&gt;, you’ll find thousands of jobs. &lt;a href=&quot;https://en.wikipedia.org/wiki/SQL#History&quot;&gt;IBM developed SQL in the early 70s&lt;/a&gt; and won the war of relational database management languages. There are millions of SQL databases running in data centres around the world. It’s an important tool to learn.&lt;/p&gt;
&lt;p&gt;Moving logic out of the application layer and into SQL can improve performance and make systems easier to understand. SQL gets used by business analysts, data scientists and other non-developers to access vaults of information that would otherwise be hidden. Some trendy Node.js apps avoid SQL by &lt;a href=&quot;https://www.mongodb.com/why-use-mongodb&quot;&gt;using MongoDB&lt;/a&gt;, but even the node ecosystem sometimes has to &lt;a href=&quot;https://mcfunley.com/choose-boring-technology&quot;&gt;choose boring technology&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;SQL is a weird language if you’re used to C-like languages. For a many of us it will be one of the first domain-specific languages we learn. It’s completely different in syntax, scope, and mindset from imperative and object-oriented programming languages. In some ways it feels more similar to functional programming, as you need to change your thinking from step-by-step to declarative.&lt;/p&gt;
&lt;h2 id=&quot;sql-order-of-operations&quot; tabindex=&quot;-1&quot;&gt;SQL Order of Operations &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/why-is-sql-weird/#sql-order-of-operations&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first thing I found strange in SQL was comparing the order of operations to the order you write an instruction with. In a C-like language, we’re used to things going from top to bottom: A series of instructions ending in semicolons that start at the top and finish at the bottom. Within lines, orders of operations can get a little more complicated, however, I’ve always found C-like languages intuitive. A lot of the order matches intermediate-school algebra we’re mostly familiar with.&lt;/p&gt;
&lt;p&gt;But in SQL, the order of operations are more idiosyncratic. Look at this query:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT [name],[breed],[age] FROM [dbo].[dogs] WHERE [age] &amp;gt; 5 ORDER BY [name];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SQL operations follow this order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;FROM&lt;/li&gt;
&lt;li&gt;JOIN&lt;/li&gt;
&lt;li&gt;WHERE&lt;/li&gt;
&lt;li&gt;GROUP BY&lt;/li&gt;
&lt;li&gt;HAVING&lt;/li&gt;
&lt;li&gt;SELECT&lt;/li&gt;
&lt;li&gt;ORDER BY&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This means in the previous query, operation starts from the middle of the line (&lt;code&gt;FROM&lt;/code&gt;), then moves right to &lt;code&gt;WHERE&lt;/code&gt;, then jumps back to the start of the line with &lt;code&gt;SELECT&lt;/code&gt; then bounces back over to the end of the line with &lt;code&gt;ORDER BY&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The other strange thing is that you can’t change the order you write instructions. You can&#39;t start with &lt;code&gt;order by&lt;/code&gt; then write &lt;code&gt;FROM&lt;/code&gt;. You have to follow the correct order to write the instructions, and it’s a different order from the execution order?&lt;/p&gt;
&lt;p&gt;I find this quite stressful to keep in my head, but an SQL professional would probably argue that it’s a human-friendly way to phrase the language. &amp;quot;Plus!&amp;quot; They scream from the rooftops. &amp;quot;It’s declarative! You shouldn’t have to worry about the order of operations anyway! Let the engine take care of it!&amp;quot;&lt;/p&gt;
&lt;h2 id=&quot;the-dunning-kruger-query-language&quot; tabindex=&quot;-1&quot;&gt;The Dunning Kruger query language &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/why-is-sql-weird/#the-dunning-kruger-query-language&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Learning introductory SQL is easy. There are a lot of introductory courses that will tell you the simple stuff: SQL databases are made up of tables, which have typed columns and hold a number of rows in which you store data, and you can hook up different tables up with foreign keys and joins to define relationships.&lt;/p&gt;
&lt;p&gt;But as soon as you get into intermediate SQL the difficulty curve steepens. This programming language was made to give you &lt;a href=&quot;https://d3laewezlz9ul2.cloudfront.net/wp-content/uploads/2020/01/07102314/1.-Dunning-Kruger.png&quot;&gt;Dunning Kruger syndrome&lt;/a&gt;. “I know how to create a table with the columns I need, and do CRUD operations on it!” you’ll excitedly say.&lt;/p&gt;
&lt;p&gt;But then you find out about about &lt;a href=&quot;https://www.sqlservertutorial.net/sql-server-views/&quot;&gt;Views&lt;/a&gt;, &lt;a href=&quot;https://www.sqlservertutorial.net/sql-server-basics/sql-server-subquery/&quot;&gt;Subqueries&lt;/a&gt;, &lt;a href=&quot;https://www.sqlservertutorial.net/sql-server-stored-procedures/basic-sql-server-stored-procedures/&quot;&gt;Stored Procedures&lt;/a&gt;, &lt;a href=&quot;https://www.sqlshack.com/sql-index-overview-and-strategy/&quot;&gt;indexing&lt;/a&gt;, &lt;a href=&quot;https://www.sqlservertutorial.net/sql-server-basics/sql-server-coalesce/&quot;&gt;coalescing&lt;/a&gt;, &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/user-defined-functions/create-user-defined-functions-database-engine?view=sql-server-ver15&quot;&gt;user-defined functions&lt;/a&gt;, not to mention the biggest topic of them all: performance. You fall off a complexity cliff into an “I know nothing” zone. This might happen in a lot of languages, but most resources seem to tell you &amp;quot;well done, you know SQL well enough to be a developer&amp;quot; after teaching a few surface-level concepts.&lt;/p&gt;
&lt;h2 id=&quot;meta-sql&quot; tabindex=&quot;-1&quot;&gt;Meta-SQL &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/why-is-sql-weird/#meta-sql&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The thing about SQL is you do everything with SQL. There are a lot of different GUI apps that allow you to look into a database’s structure and data without running SQL queries. But the more you use these apps to gather information about your database, the more you realise that you’re doing it wrong! What’s the best way to get a list of all the table names? Don’t try and use your GUI client. Instead, run this query:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What’s the best way of searching for a view you made? Don’t use the GUI! Run this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM sys.views WHERE name like &#39;%MyView%&#39;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What’s the best way to see how many records are in a table? It’s this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT count(*) FROM [dbo].[dogs];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay maybe that’s not the &lt;a href=&quot;https://www.brentozar.com/archive/2014/02/count-number-rows-table-sql-server/&quot;&gt;best way&lt;/a&gt; to find the number of records in a table - but it’s good for most cases.&lt;/p&gt;
&lt;p&gt;SQL isn&#39;t only for accessing, deleting, and changing data. It&#39;s also for describing data and building databases. And sometimes it feels weird to do both of these using the same quote unquote “domain specific language”, as they&#39;re actually quite different tasks.&lt;/p&gt;
&lt;p&gt;You can set database table names based on data in another table. You can store SQL configuration variables in the same database that you&#39;re configuring. You can use SQL to drive other SQL. That&#39;s weird.&lt;/p&gt;
&lt;h2 id=&quot;injection&quot; tabindex=&quot;-1&quot;&gt;Injection &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/why-is-sql-weird/#injection&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of my first blog posts on here was about &lt;a href=&quot;https://www.elliotclyde.nz/blog/php-data-objects/&quot;&gt;how to avoid SQL injection using PHP data objects.&lt;/a&gt; We would never have needed this sort of thing if we didn’t have to mix SQL into other programming languages. The fact that developers use strings to build out executable queries puts us all in danger, as the &lt;a href=&quot;https://www.youtube.com/watch?v=ciNHn38EyRc&amp;amp;t=829s&quot;&gt;Computerphile YouTube channel shows us in this vid&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Who knows how many apps are running right now with an opening for us to type &lt;code&gt;drop table [dbo].[users];&lt;/code&gt; and watch the world burn. Who knows how many passwords have been claimed. Well, &lt;a href=&quot;https://holdsecurity.com/news/2015/09/you-have-been-hacked/&quot;&gt;Hold Security made an estimate that at least 1.2 billion unique email-password pairs had been stolen using SQL injection.&lt;/a&gt; That was 7 years ago now, so hopefully everyone’s moved on to MongoDB since then. Problem solved.&lt;/p&gt;
&lt;p&gt;Part of the reason SQL feels weird is that we&#39;re always calling it from another language. Plus we&#39;re always scared of the potential repercussions of doing this incorrectly.&lt;/p&gt;
&lt;h2 id=&quot;maybe-i&#39;m-the-weird-one&quot; tabindex=&quot;-1&quot;&gt;Maybe I&#39;m the weird one &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/why-is-sql-weird/#maybe-i&#39;m-the-weird-one&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I haven&#39;t spent as much time with SQL as I have with HTML, JavaScript, CSS, PHP, or Java. This could be the real reason I&#39;m asking why it&#39;s weird. Not because of any flaw with the language, but with me.&lt;/p&gt;
&lt;p&gt;There you go. The real domain-specific-database-management-language was the friends we made along the way.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Compile your own Apache Module in C</title>
    <link href="https://www.elliotclyde.nz/blog/compile-your-own-apache-module-in-c/"/>
    <updated>2022-06-16T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/compile-your-own-apache-module-in-c/</id>
    <content type="html">&lt;p&gt;Before we jump in, I should let you know these instructions are going to be for 64 bit Windows only. I&#39;ll be compiling &lt;em&gt;on&lt;/em&gt; Windows and &lt;em&gt;for&lt;/em&gt; Windows. If you&#39;re running Mac or Linux I&#39;m afraid you&#39;ll have to find another (probably easier) tutorial.&lt;/p&gt;
&lt;p&gt;Compiling Apache for Windows is probably a pretty cooked thing to do anyway, as you&#39;ll usually want to deploy Apache on a nice cheap linux virtual machine. The good thing is the source code should be cross platform. The &lt;a href=&quot;https://apr.apache.org/&quot;&gt;Apache Portable Runtime&lt;/a&gt; allows for this. But the compilation steps and downloads won&#39;t be. And I haven&#39;t double-checked that the source does compile properly on Mac or Linux.&lt;/p&gt;
&lt;h2 id=&quot;the-apache-server&quot; tabindex=&quot;-1&quot;&gt;The Apache Server &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/compile-your-own-apache-module-in-c/#the-apache-server&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://httpd.apache.org/&quot;&gt;Apache Server&lt;/a&gt; was the best way to get a website running for most of the lifetime of the web. As of 2022 it holds a &lt;a href=&quot;https://w3techs.com/technologies/overview/web_server&quot;&gt;31.4% market share of web server statistics&lt;/a&gt;. It continues to run a large part of the internet, in no small part due to Apache being a first class way to deploy a PHP application (it hosts a whole lot of Wordpress). Apache is the A in LAMP stack (Linux, Apache, MySQL and PHP). It&#39;s the stack that ruled the naughties, for better or for worse. Even though it has had its market share cannibalised by &lt;a href=&quot;https://www.nginx.com/&quot;&gt;Nginx&lt;/a&gt; over the last decade, the Apache Server remains an important piece of tech.&lt;/p&gt;
&lt;p&gt;How does Apache connect to PHP? One way is to run PHP is with &lt;a href=&quot;https://en.wikipedia.org/wiki/FastCGI&quot;&gt;FastCGI&lt;/a&gt;, a cross-platform way to get servers to talk to other programs on your system. More likely though, you&#39;ll be running it as an &lt;a href=&quot;https://www.php.net/manual/en/install.windows.apache2.php#install.windows.apache2.module&quot;&gt;apache module&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Apache server is a modular system, where modules execute logic across different parts of the request/response cycle of the HTTP protocol. These modules are .SO files - shared library files. They are binaries that live in Apache&#39;s modules folder. Apache has the ability to call functions they expose. You can filter which modules apply to which requests and configure how they work in with Apache&#39;s config file, and also . Hidden deep within the Apache documentation is a guide for &lt;a href=&quot;https://httpd.apache.org/docs/2.4/developer/modguide.html&quot;&gt;Developing modules&lt;/a&gt;. We&#39;ll be half-following this guide.&lt;/p&gt;
&lt;h2 id=&quot;getting-apache-for-windows&quot; tabindex=&quot;-1&quot;&gt;Getting Apache for Windows &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/compile-your-own-apache-module-in-c/#getting-apache-for-windows&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First we&#39;ll need to download Apache. You&#39;d expect to download Apache from the Apache website, but they only hold the source code and Unix binaries. Instead, you can grab Apache for Windows from third party vendors such as &lt;a href=&quot;https://www.apachelounge.com/download/&quot;&gt;Apache Lounge&lt;/a&gt;. Download the Windows 64 version, and extract it into your favourite folder. The folder structure inside Apache should look like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;modules&lt;/strong&gt; - Where all the different modules get stored. There will already be many modules provided by Apache already there&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;bin&lt;/strong&gt; - Where the Apache binaries live. This is where we go to start Apache&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;cgi-bin&lt;/strong&gt; - Where you&#39;d store binaries for the &lt;a href=&quot;https://en.wikipedia.org/wiki/Common_Gateway_Interface&quot;&gt;Common Gateway Interface&lt;/a&gt;. This is the way PHP used to be handled but it&#39;s less efficient than the techniques described earlier&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;conf&lt;/strong&gt; - Configuration files&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;error&lt;/strong&gt; - Error messages in different languages&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;htdocs&lt;/strong&gt; - The public files you want to expose to the internet - eg. where you put your index.html&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;icons&lt;/strong&gt; - Icons&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;include&lt;/strong&gt; - Header files we include in C code when writing our module&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;lib&lt;/strong&gt; - Library files we link to our C object files when compiling our module&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;logs&lt;/strong&gt; - Logs - a good place to look when things aren&#39;t working&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;manual&lt;/strong&gt; - A folder with a single text file which is just a link to the Apache website. Yup.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To get a server running, we first set Apache up as a Windows Service. Open up Powershell as administrator and navigate to Apache&#39;s bin folder, then run this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./httpd.exe -k install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you have it installed as a service. This makes it a little but easier to run. We can start our server with this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NET START Apache2.4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After running this, you should see a message to say the server is starting. Head to your browser and type in &amp;quot;localhost&amp;quot; in the address bar. You should see a big H1 in times-new-roman saying &amp;quot;It works!&amp;quot;. You can stop Apache by heading back to Powershell and running this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NET STOP Apache2.4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It might take a few seconds to run its cleanup.&lt;/p&gt;
&lt;h2 id=&quot;getting-visual-studio-build-tools&quot; tabindex=&quot;-1&quot;&gt;Getting Visual Studio Build Tools &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/compile-your-own-apache-module-in-c/#getting-visual-studio-build-tools&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;re going to write some C code, but we need a way to turn it into a binary shared library. If only there was some way to do that. Oh wait, that&#39;s what a compiler does. Let&#39;s grab a command-line version of Microsoft&#39;s MSVC compiler. Head to the &lt;a href=&quot;https://visualstudio.microsoft.com/downloads/&quot;&gt;Visual Studio Downloads Page&lt;/a&gt;, and scroll past all the IDEs (grab &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;Visual Studio Code&lt;/a&gt; if you don&#39;t already have it - it&#39;s a great editor).&lt;/p&gt;
&lt;p&gt;Under &amp;quot;All downloads&amp;quot; click into &amp;quot;Tools for Visual Studio&amp;quot;. Scroll to the bottom and download &amp;quot;Build tools for Visual Studio&amp;quot;. This is going to get us a compiler we can use from the command line.&lt;/p&gt;
&lt;p&gt;But first we need to go through the Visual Studio installer which you can install and open. Select &amp;quot;Desktop development with C++&amp;quot; on the left. On the right, make sure the boxes are ticked for &amp;quot;MSVC&amp;quot; and &amp;quot;Windows 10 SDK&amp;quot;. Install these.&lt;/p&gt;
&lt;p&gt;Now a good idea is to open up &amp;quot;x64 Native Tools Command Prompt for VS 2022&amp;quot;, and type in the letters:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then hit return and it should print off the version of the Windows Visual Studio C compiler you have installed.&lt;/p&gt;
&lt;p&gt;That&#39;s the installing part done! Now let&#39;s write some code for our Apache module.&lt;/p&gt;
&lt;h2 id=&quot;writing-the-boilerplate&quot; tabindex=&quot;-1&quot;&gt;Writing the boilerplate &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/compile-your-own-apache-module-in-c/#writing-the-boilerplate&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;re going to make a module which will respond to any request with &amp;quot;hello apache&amp;quot;. We&#39;ll be writing this in C. Create a file called &amp;quot;mod_hello_apache.c&amp;quot;. The first thing we need to do is include headers from the Apache libraries we&#39;re going to use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;httpd.h&amp;gt;
#include &amp;lt;http_protocol.h&amp;gt;
#include &amp;lt;http_config.h&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These libraries are provided by Apache for developers to build modules. They give us cross-platform functions to send data to respond to HTTP requests. We also use them to tell Apache how to call our module.&lt;/p&gt;
&lt;p&gt;Now we need to expose a module struct. On start-up, Apache will look for this struct and pass it configuration data. It will also call the last item in the struct - a function which we use to set our module up:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module AP_MODULE_DECLARE_DATA hello_apache_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    hello_apache_hooks   /* Our hook registering function */
};

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In our code the set up function is called &lt;code&gt;hello_apache_hooks&lt;/code&gt;. We don&#39;t really need any configuration for this simple module, so we&#39;re just going to leave the configuration arguments (the middle 5 arguments) as null. Why does the name of our set-up function include the word &amp;quot;hooks&amp;quot;? It&#39;s because we use this function to tell Apache how our module will &amp;quot;hook&amp;quot; into the request/response cycle. We use hooks to tell Apache what happens when.&lt;/p&gt;
&lt;p&gt;Let&#39;s write our &lt;code&gt;hello_apache_hooks&lt;/code&gt; function now:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static void hello_apache_hooks(apr_pool_t *pool)
{
    ap_hook_handler(hello_apache_handler, NULL, NULL, APR_HOOK_MIDDLE);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What is being passed into the &lt;code&gt;hello_apache_hooks&lt;/code&gt; function? A pointer to something called a pool. For each request that hits our module, Apache will create a pool, which is an object to help us manage memory. Using a pool we can fill up memory with data for our handler, and Apache will clean everything up at the end of the processing cycle (when we call a cleanup function). This prevents memory leaks.&lt;/p&gt;
&lt;p&gt;We&#39;ve all heard about how scary C can be with its lack of guard rails for memory allocation. Pools make it a little safer.&lt;/p&gt;
&lt;p&gt;We don&#39;t really need to do any complex memory assignment so we leave the pool alone.&lt;/p&gt;
&lt;p&gt;Then we call an Apache function called &lt;code&gt;ap_hook_handler&lt;/code&gt;. This registers that we want to run a function called &lt;code&gt;hello_apache_handler&lt;/code&gt; in the middle of the request/response cycle. There are multiple points in the cycle we can hook into, including first, last, and middle.&lt;/p&gt;
&lt;p&gt;We&#39;ve put our handler in the middle, but you can imagine we might have a security module which kicks out blacklisted IP addresses before they reach our handler, or a module which attaches &lt;a href=&quot;https://www.elliotclyde.nz/blog/http-caching-is-a-superpower/&quot;&gt;cache control headers&lt;/a&gt; to outgoing images. The middle two arguments are for telling Apache about functions to run before and after this one. We&#39;ll leave these as null.&lt;/p&gt;
&lt;h2 id=&quot;writing-the-handler-function&quot; tabindex=&quot;-1&quot;&gt;Writing the handler function &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/compile-your-own-apache-module-in-c/#writing-the-handler-function&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Okay now we&#39;re finished with the boilerplate! Well done, for making it this far!&lt;/p&gt;
&lt;p&gt;Let&#39;s write our &lt;code&gt;hello_apache_handler&lt;/code&gt;. This is a static function that contains the logic we run when a request comes in. We&#39;ll take in a pointer to a &lt;code&gt;request_rec&lt;/code&gt; as a parameter. This is a struct which will describe all the information about the HTTP request - all the request headers, any query parameters, and a whole lot of other information you might need.&lt;/p&gt;
&lt;p&gt;Our function will return an integer which serves as a status code to tell Apache whether our handler was successful.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static int hello_apache_handler(request_rec *r){}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let&#39;s do some checks. First we want to make sure that this request is actually meant to be for our handler (it&#39;s our job as module developers to do this):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static int hello_apache_handler(request_rec *r)
{
    if (!r-&amp;gt;handler || (strcmp(r-&amp;gt;handler, &amp;quot;hello_apache&amp;quot;) != 0))
    {
        return DECLINED;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we&#39;re making sure that the request actually needs a handler, then we make sure that the handler is our module.&lt;/p&gt;
&lt;p&gt;We need to make sure this is a &lt;code&gt;GET&lt;/code&gt; request. This is going to be the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods&quot;&gt;request method&lt;/a&gt; our module will respond to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static int hello_apache_handler(request_rec *r)
{
    if (!r-&amp;gt;handler || (strcmp(r-&amp;gt;handler, &amp;quot;hello_apache&amp;quot;) != 0))
    {
        return DECLINED;
    }
     if (r-&amp;gt;method_number != M_GET)
    {
        return HTTP_METHOD_NOT_ALLOWED;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If it&#39;s not a GET request, we return a status code provided by the Apache libraries to say that this method isn&#39;t allowed.&lt;/p&gt;
&lt;p&gt;And now let&#39;s send something to the client. First we&#39;ll send a response header to say this is HTML, then we&#39;ll send some HTML over the wire:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static int hello_apache_handler(request_rec *r)
{
    if (!r-&amp;gt;handler || (strcmp(r-&amp;gt;handler, &amp;quot;hello_apache&amp;quot;) != 0))
    {
        return DECLINED;
    }
    if (r-&amp;gt;method_number != M_GET)
    {
        return HTTP_METHOD_NOT_ALLOWED;
    }
    ap_set_content_type(r, &amp;quot;text/html;charset=utf8&amp;quot;);
    ap_rputs(&amp;quot;&amp;lt;html&amp;gt;&amp;quot;
             &amp;quot;&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;Hello apache&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;&amp;quot;
             &amp;quot;&amp;lt;body&amp;gt;&amp;quot;
             &amp;quot;&amp;lt;h1&amp;gt;Hello from apache module!&amp;lt;/h1&amp;gt;&amp;quot;
             &amp;quot;&amp;lt;/body&amp;gt;&amp;quot;
             &amp;quot;&amp;lt;/html&amp;gt;&amp;quot;,
             r);
    return OK;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We return OK at the end of the function to say things went smoothly. Nice.&lt;/p&gt;
&lt;h2 id=&quot;the-entire-module-source&quot; tabindex=&quot;-1&quot;&gt;The entire module source &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/compile-your-own-apache-module-in-c/#the-entire-module-source&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here&#39;s the entire module all together:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;httpd.h&amp;gt;
#include &amp;lt;http_protocol.h&amp;gt;
#include &amp;lt;http_config.h&amp;gt;

static int hello_apache_handler(request_rec *r)
{
    if (!r-&amp;gt;handler || (strcmp(r-&amp;gt;handler, &amp;quot;hello_apache&amp;quot;) != 0))
    {
        return DECLINED;
    }
    if (r-&amp;gt;method_number != M_GET)
    {
        return HTTP_METHOD_NOT_ALLOWED;
    }
    ap_set_content_type(r, &amp;quot;text/html;charset=utf8&amp;quot;);
    ap_rputs(&amp;quot;&amp;lt;html&amp;gt;&amp;quot;
             &amp;quot;&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;Hello apache&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;&amp;quot;
             &amp;quot;&amp;lt;body&amp;gt;&amp;quot;
             &amp;quot;&amp;lt;h1&amp;gt;Hello from apache module!&amp;lt;/h1&amp;gt;&amp;quot;
             &amp;quot;&amp;lt;/body&amp;gt;&amp;quot;
             &amp;quot;&amp;lt;/html&amp;gt;&amp;quot;,
             r);
    return OK;
}

static void hello_apache_hooks(apr_pool_t *pool)
{
    ap_hook_handler(hello_apache_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA hello_apache = {
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    hello_apache_hooks};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s our module! The functions need to be defined in this order to make sure that they&#39;ve been declared before they get referenced.&lt;/p&gt;
&lt;h2 id=&quot;compiling-the-module&quot; tabindex=&quot;-1&quot;&gt;Compiling the module &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/compile-your-own-apache-module-in-c/#compiling-the-module&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Apache provides a tool called &lt;a href=&quot;https://httpd.apache.org/docs/2.4/programs/apxs.html&quot;&gt;APSX&lt;/a&gt;. This is a perl script that looks at your machine and the file you&#39;re compiling and sets the appropriate compiler flags. I gave it a try but I couldn&#39;t get it working, so instead we&#39;re going to just use the the Visual Studio C compiler we downloaded earlier - MSVC.&lt;/p&gt;
&lt;p&gt;Before you use MSVC, you&#39;d usually need to set a bunch of environment variables for where windows libraries and headers live. An easier way is to open &amp;quot;x64 Native Tools Command Prompt for VS 2022&amp;quot; which will get installed with the MVSC package we grabbed earlier. This is a preconfigured shell which has all environment variables we need.&lt;/p&gt;
&lt;p&gt;Open this up, navigate to the folder with our C source file, then we can create an object file with the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cl /nologo /MD /W3 /O2 /D WIN32 /D WINDOWS /D NDEBUG -I&amp;quot;C:&#92;Path&#92;to&#92;apache&#92;include&amp;quot; /c mod_hello_apache.c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can read about what these different command line arguments do on &lt;a href=&quot;https://docs.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically?view=msvc-170&quot;&gt;the Microsoft documentation&lt;/a&gt;. Now we should have a file called mod_hello_apache.obj. We aren&#39;t quite done yet. Now we just need to link the Apache libraries to the file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;link kernel32.lib ws2_32.lib &amp;quot;C:&#92;Path&#92;to&#92;apache&#92;lib&#92;libhttpd.lib&amp;quot; /nologo
/subsystem:windows /dll /machine:x64 /out:mod_hello_apache.so mod_hello_apache.obj
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can check out what these linker arguments do too on &lt;a href=&quot;https://docs.microsoft.com/en-us/cpp/build/reference/linker-options?view=msvc-170&quot;&gt;the Microsoft documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We now have a compiled Apache module called &lt;code&gt;mod_hello_apache.so&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Move it into Apache&#39;s modules folder. The last thing we need to do is add this to the bottom of the Apache configuration file in conf/httpd.conf:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LoadModule hello_apache modules/mod_hello_apache.so

&amp;lt;Location &amp;quot;/hello&amp;quot;&amp;gt;
SetHandler hello_apache
&amp;lt;/Location&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first line tells Apache we want to load a module called &amp;quot;hello_apache&amp;quot; and provides the path to it. Then inside the location tags we&#39;re telling it to use our module for all requests in the URL &amp;quot;/hello&amp;quot;.&lt;/p&gt;
&lt;p&gt;Now open up your favourite browser (mosaic, of course), and head to the URL &amp;quot;localhost&amp;quot;. You should get the same message from before. However, change the url to &amp;quot;localhost/hello&amp;quot; and you should see the words &amp;quot;Hello from apache module!&amp;quot; in beautiful, big, bold times-new-roman.&lt;/p&gt;
&lt;p&gt;Well done! You&#39;ve created your first Apache module! Now you can do your web development in C like it&#39;s 1995.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Printing a file with the Apache Portable Runtime</title>
    <link href="https://www.elliotclyde.nz/blog/printing-a-file-with-the-apache-portable-runtime/"/>
    <updated>2022-07-31T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/printing-a-file-with-the-apache-portable-runtime/</id>
    <content type="html">&lt;p&gt;In my &lt;a href=&quot;https://www.elliotclyde.nz/blog/compile-your-own-apache-module-in-c/&quot;&gt;last post&lt;/a&gt;, I wrote about building an Apache module written in C.&lt;/p&gt;
&lt;p&gt;In researching how to do this, I learnt about a &lt;a href=&quot;https://apr.apache.org/docs/apr/1.7/&quot;&gt;the Apache Portable Runtime&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What&#39;s that?&lt;/p&gt;
&lt;p&gt;It&#39;s a number of libraries and header files provided by the Apache Software Foundation which abstract away operating system operations, and allow your C code to become more portable. The Apache Portable Runtime (APR) was originally part of the Apache Server codebase. It got separated out, because having a way to write C code in a single codebase that you know you can compile for multiple platforms is a pretty nice thing to have!&lt;/p&gt;
&lt;h2 id=&quot;portability&quot; tabindex=&quot;-1&quot;&gt;Portability &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/printing-a-file-with-the-apache-portable-runtime/#portability&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;C was meant to be a portable language when it was created.&lt;/p&gt;
&lt;p&gt;Unfortunately, it was never &amp;quot;write once, run anywhere&amp;quot;, as operating systems offer different features and APIs, compilers act completely differently, and C simply grew up in the Wild West days of computing.&lt;/p&gt;
&lt;p&gt;Some examples of the issues APR smooths over:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows processes are more expensive to create than in Linux.&lt;/li&gt;
&lt;li&gt;Unix-based systems and Windows have completely different Socket APIs for TCP connections.&lt;/li&gt;
&lt;li&gt;The C language even has undefined behaviour so some functions like &lt;code&gt;fflush(stdin)&lt;/code&gt; are completely non-portable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;pools&quot; tabindex=&quot;-1&quot;&gt;Pools &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/printing-a-file-with-the-apache-portable-runtime/#pools&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The APR also provides an API to make memory management easier in C.&lt;/p&gt;
&lt;p&gt;It uses a system of pools, where every piece of dynamically created memory gets assigned to a pool, which will remember what you&#39;ve created. Then when you&#39;re finished with the pool, you tell it you&#39;re done by calling &lt;code&gt;apr_pool_destroy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After you call this, everything is freed! You don&#39;t have to go through all the pieces of data you&#39;ve dynamically allocated to make sure that they&#39;ve all been freed and the space has been given back to the operating system. The pool does that for you.&lt;/p&gt;
&lt;h2 id=&quot;getting-set-up&quot; tabindex=&quot;-1&quot;&gt;Getting set up &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/printing-a-file-with-the-apache-portable-runtime/#getting-set-up&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Although I&#39;ve just been raving about the portability of APR, these instructions are going to be based on compiling on and for 64 bit Windows, so if you&#39;re on Mac or Linux, some of these steps won&#39;t apply for you.&lt;/p&gt;
&lt;p&gt;So how do we write a program which using the APR? First we&#39;re going need a compiler. If you&#39;re on Windows I&#39;ve got some &lt;a href=&quot;https://www.elliotclyde.nz/blog/compile-your-own-apache-module-in-c/#getting-visual-studio-build-tools&quot;&gt;instructions on where to go to get the command line Visual Studio Compiler for C&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next we&#39;re going to need the APR library and header files.&lt;/p&gt;
&lt;p&gt;If you have Apache Server installed - you should already have them. You&#39;ll find the header files in the &lt;code&gt;include&lt;/code&gt; folder and the library files in the &lt;code&gt;lib&lt;/code&gt; folder. Both live in the root folder of apache.&lt;/p&gt;
&lt;p&gt;If you don&#39;t already have Apache downloaded, you can either download the &lt;a href=&quot;https://apr.apache.org/download.cgi&quot;&gt;APR source&lt;/a&gt; and compile it (if you want to go through that). Or you can grab Apache for Windows from &lt;a href=&quot;https://www.apachelounge.com/download/&quot;&gt;Apache Lounge&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now open your favourite text editor. Mine is &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;VSCode&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;boiler-plate&quot; tabindex=&quot;-1&quot;&gt;Boiler plate &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/printing-a-file-with-the-apache-portable-runtime/#boiler-plate&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First we&#39;re going to need to include the APR libraries, and also we&#39;re going to grab the classic standard IO library:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;apr.h&amp;gt;
#include &amp;lt;apr_pools.h&amp;gt;
#include &amp;lt;apr_file_io.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These will pull in all the APR functions from these header files.&lt;/p&gt;
&lt;p&gt;Now we&#39;ll write our main function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int main(){

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We start by initialising APR and creating our pool.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int main(){
  apr_initialize();
  apr_pool_t *pool;
  apr_status_t status = apr_pool_create(&amp;amp;pool, NULL);
  if(status != APR_SUCCESS){
        printf(&amp;quot;Could not create pool&amp;quot;);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&#39;ll find that usually C frameworks won&#39;t be set up for you; you&#39;ll have to call a function to start things up. There&#39;s no free lunch with a low level programming language. Here we need to call &lt;code&gt;apr_initialize&lt;/code&gt; before we&#39;re allowed to do anything with APR functions.&lt;/p&gt;
&lt;p&gt;Next we declare a pointer to a pool, then pass it to &lt;code&gt;apr_pool_create&lt;/code&gt;. This initializes the pool for us. The second argument of &lt;code&gt;apr_pool_create&lt;/code&gt; is the parent pool (APR gives you the ability to hold a hierarchy of pools where a parent pool can clean up its children). In our case, this is our first and only pool, so we pass &lt;code&gt;NULL&lt;/code&gt; as the second argument.&lt;/p&gt;
&lt;p&gt;Finally, it&#39;s always good practice to check if things have gone wrong. C doesn&#39;t have nice ways to do this like more popular programming languages today. Instead, APR functions will return an enum to represent their status. If things went well, we should see &lt;code&gt;APR_SUCCESS&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;opening-a-file&quot; tabindex=&quot;-1&quot;&gt;Opening a file &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/printing-a-file-with-the-apache-portable-runtime/#opening-a-file&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now let&#39;s set up a file descriptor. This is an APR structure which will be able to use to read a file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  apr_file_t *file;
  char *fname = &amp;quot;C:/path/to/a/random/file.txt&amp;quot;;
  status = apr_file_open(&amp;amp;file, fname, APR_READ, APR_OS_DEFAULT, pool);
    if (status != APR_SUCCESS)
    {
        printf(&amp;quot;Could not open file&amp;quot;);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First we declare a pointer to the file descriptor, then declare our file path as a pointer to the first char in the path, calling it &lt;code&gt;fname&lt;/code&gt;. We use the &lt;code&gt;&amp;amp;&lt;/code&gt; &amp;quot;Address of Operator&amp;quot; when passing the file descriptor to &lt;code&gt;apr_file_open&lt;/code&gt;. &lt;code&gt;apr_file_open&lt;/code&gt;&#39;s first argument isn&#39;t a pointer to a file descriptor. Instead it&#39;s a pointer &lt;em&gt;&lt;strong&gt;to a pointer&lt;/strong&gt;&lt;/em&gt; to a file descriptor. I don&#39;t know why it isn&#39;t just a pointer to the file descriptor, but there you go. The second argument is our file name. The third argument is the mode we want to open the file with.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-a-buffer&quot; tabindex=&quot;-1&quot;&gt;Setting up a buffer &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/printing-a-file-with-the-apache-portable-runtime/#setting-up-a-buffer&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we&#39;ve connected with our file, we still need to do some set up before we can read it into a string.&lt;/p&gt;
&lt;p&gt;We need to allocate the required memory for where our string is going to be held. This is because we don&#39;t know the size of the file and so have to do this dynamically. So our first step is to find out how big the file is, then we can set up the space in memory (a memory buffer) to store the string of the file&#39;s contents.&lt;/p&gt;
&lt;p&gt;Here&#39;s how we do this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  apr_off_t offset = 0 ;

  apr_file_seek(file, APR_END, &amp;amp;offset);

  apr_size_t size = offset;

  char *buffer = apr_pcalloc(pool, size +1);

  offset = 0;

  apr_file_seek(file, APR_SET, &amp;amp;offset);

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What&#39;s going on here? &lt;code&gt;apr_file_seek&lt;/code&gt; is a function with various uses, but its main use is to move the cursor to a certain place in a file (the cursor is a number representing the current char we&#39;re reading from the file).&lt;/p&gt;
&lt;p&gt;We want to find out how large the file is, so we move the cursor to the end of the file with the first call to &lt;code&gt;apr_file_seek&lt;/code&gt;. The third argument is a pointer to an offset, which will change the cursor to a certain location by its value, but can also be used (as in this case) to get the location of the cursor after the function call. The function sets it to the current location of the cursor after it has changed position. Lastly, the second argument lets us tell it to move to the end of the file using the enum &lt;code&gt;APR_END&lt;/code&gt;. After this &lt;code&gt;offset&lt;/code&gt; will hold the length of the file in bytes (as a single char is a byte).&lt;/p&gt;
&lt;p&gt;Now we create a size variable of type &lt;code&gt;apr_size_t&lt;/code&gt; which is a long number that will represent the size of the file. We allocate our memory to hold our string in by setting a pointer to a char variable to the return value of a new APR function: &lt;code&gt;apr_pcalloc&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This function allocates all the memory we need, and sets it all to zero. This is good because C strings need to end with a null character (which is the same value as zero), so the language knows when the string ends. This is also the reason we add one to the size, as we need that extra null character at the end. The first argument is which pool we want the memory to be under.&lt;/p&gt;
&lt;p&gt;If we read the file now, the cursor would still be right at the end and we wouldn&#39;t get anything back from reading it. For this reason, we need to set our offset to zero, then use the same function from earlier, but this time use the &lt;code&gt;APR_SET&lt;/code&gt; enum, which sets the cursor to the offset we provide (which is zero). I personally would probably prefer a different function that would just set the cursor to zero, but hey, it&#39;s C. What are you gonna do?&lt;/p&gt;
&lt;h2 id=&quot;reading-the-file&quot; tabindex=&quot;-1&quot;&gt;Reading the file &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/printing-a-file-with-the-apache-portable-runtime/#reading-the-file&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now it&#39;s time to read our string into the buffer, and print it to standard out!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  status = apr_file_read(file, buffer, &amp;amp;size);
  if (status != APR_SUCCESS)
  {
      printf(&amp;quot;Could not read file&amp;quot;);
  }
  else
  {
      printf(buffer);
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s as easy as that! We pass &lt;code&gt;apr_file_read&lt;/code&gt; the pointer to our file, the pointer to our buffer, and the pointer to our size (this one we hadn&#39;t previously stored as a pointer so need to dereference it with &lt;code&gt;&amp;amp;&lt;/code&gt;). APR will copy the contents of the file into the buffer, then we&#39;re ready to print it out.&lt;/p&gt;
&lt;h2 id=&quot;cleaning-up&quot; tabindex=&quot;-1&quot;&gt;Cleaning up &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/printing-a-file-with-the-apache-portable-runtime/#cleaning-up&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After we&#39;re finished, we need to close our file and terminate APR:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; apr_file_close(file);
 apr_terminate();
 return 0;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-entire-source&quot; tabindex=&quot;-1&quot;&gt;The entire source &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/printing-a-file-with-the-apache-portable-runtime/#the-entire-source&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here&#39;s what the source should look like, with all the checks and balances:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;apr_general.h&amp;gt;
#include &amp;lt;apr_pools.h&amp;gt;
#include &amp;lt;apr_file_io.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;

int main(){
  apr_initialize();
  apr_pool_t *pool;
  apr_status_t status = apr_pool_create(&amp;amp;pool, NULL);

  if(status != APR_SUCCESS){
        printf(&amp;quot;Could not create pool&amp;quot;);
  } 
  else { 
    apr_file_t *file;

    char *fname = &amp;quot;C:/path/to/a/random/file.txt&amp;quot;;;

    status = apr_file_open(&amp;amp;file, fname, APR_READ, APR_OS_DEFAULT, pool);
    if (status != APR_SUCCESS)
    {
        printf(&amp;quot;Could not open file&amp;quot;);
    }
    else {
      apr_off_t offset = 0 ;
	
      apr_file_seek(file, APR_END, &amp;amp;offset);
    
      apr_size_t size = offset;
      char *buffer = apr_pcalloc(pool, size +1);
    
      offset = 0;
    
      apr_file_seek(file, APR_SET, &amp;amp;offset);

      status = apr_file_read(file, buffer, &amp;amp;size);
      if (status != APR_SUCCESS)
      {
          printf(&amp;quot;Could not read file&amp;quot;);
      }
      else
      {
          printf(buffer);
      }
      apr_file_close(file);
    } 
   }
   apr_terminate();
   return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;compiling-the-program&quot; tabindex=&quot;-1&quot;&gt;Compiling the program &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/printing-a-file-with-the-apache-portable-runtime/#compiling-the-program&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now save the file as &lt;code&gt;aprprint.c&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then you can compile the program on x64 Native Tools Command Prompt by navigating to your source directory then typing the following command (replacing the paths with the paths to your Apache folder):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cl /nologo /MD /W3 /O2 /D WIN32 /D WINDOWS /D NDEBUG -I&amp;quot;C:&#92;path&#92;to&#92;apache&#92;include&amp;quot; aprprint.c kernel32.lib ws2_32.lib &amp;quot;C:&#92;path&#92;to&#92;apache&#92;lib&#92;libaprutil-1.lib&amp;quot; &amp;quot;C:&#92;path&#92;to&#92;apache&#92;lib&#92;libapr-1.lib&amp;quot; /nologo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you should get back silence from the compiler. Have a look at the error messages if your don&#39;t.&lt;/p&gt;
&lt;p&gt;The last thing to do is to &lt;a href=&quot;https://helpdeskgeek.com/windows-10/add-windows-path-environment-variable/&quot;&gt;add the &lt;code&gt;bin&lt;/code&gt; folder in apache to your environment variables path&lt;/a&gt;. This will mean the DLL&#39;s that Apache provides will be in scope for your program.&lt;/p&gt;
&lt;p&gt;After all this, you can run the program with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;aprprint.exe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&#39;re an APR programmer now! 😈&lt;/p&gt;
&lt;h2 id=&quot;more!&quot; tabindex=&quot;-1&quot;&gt;More! &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/printing-a-file-with-the-apache-portable-runtime/#more!&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Try adding the file name as a command line argument to print off different functions. Or you could combine this program with my last post about Apache modules to make a static file server.&lt;/p&gt;
&lt;p&gt;Here&#39;s a &lt;a href=&quot;https://dev.ariel-networks.com/apr/apr-tutorial/html/apr-tutorial.html&quot;&gt;tutorial from Inoue Seiichiro about the APR which goes more in depth than I can&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Hosting small websites on EC2</title>
    <link href="https://www.elliotclyde.nz/blog/Hosting-small-websites-on-EC2/"/>
    <updated>2022-10-02T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/Hosting-small-websites-on-EC2/</id>
    <content type="html">&lt;p&gt;You have a small site where you don’t expect many visitors, but you want dynamic server rendering and you want it cheap. My new favourite hosting for this use-case is Amazon Web Services’ EC2.&lt;/p&gt;
&lt;p&gt;You might struggle with the fact you&#39;re giving the richest people in an unequal world more money. And maybe your time is worth too much to waste on server configuration. But if you have a small passion project, a cheap EC2 instance provides the most freedom for the lowest price.&lt;/p&gt;
&lt;p&gt;AWS don’t heavily market &lt;em&gt;how&lt;/em&gt; to do things cheaply. At the end of the day, it’s in their interest to push higher margin platform-as-a-service over dumb infrastructure.&lt;/p&gt;
&lt;p&gt;AWS won’t give you detailed &lt;em&gt;help&lt;/em&gt; with setting up your stack. There&#39;s too much to document for a rented computer which can do anything you want it to. Although, they make a valiant effort to document the most heavily trodden cow paths.&lt;/p&gt;
&lt;p&gt;The secret is that you can install all your stuff on EC2. And on the cheapest type of instance. You can save a lot of money and complexity. Basic building blocks like EC2 help us bring back &lt;a href=&quot;https://www.devever.net/~hl/mildlydynamic&quot;&gt;mildly dynamic websites&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It doesn’t even seem like it’s in AWS’s interest to bother to cater to cheapskates like me. And yet they serve my use-case better than any other hosting provider. I guess 15 years of running infra-as-a-service means they’re able to automate the sales, admin and maintenance away. And they benefit from an economy of scale of biblical proportions.&lt;/p&gt;
&lt;h2 id=&quot;what-type-of-website-are-we-talking-about%3F&quot; tabindex=&quot;-1&quot;&gt;What type of website are we talking about? &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/Hosting-small-websites-on-EC2/#what-type-of-website-are-we-talking-about%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What is the use-case I’m talking about? If you want the &lt;em&gt;absolute&lt;/em&gt; cheapest possible website, you’re going to want a static site. There’s a smorgasbord of cheap and free places to host static HTML, CSS, and JavaScript (github pages and neocities come to mind). The site you’re reading from now is all static files in azure storage, and &lt;a href=&quot;https://www.elliotclyde.nz/blog/going-static/&quot;&gt;I wrote a blog post about moving here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But only running static files is limiting. I want to make websites that do things. You might yell at the screen: “use JavaScript” and “use a service”. But we can’t write everything in JavaScript: There’s always going to be a point where we have privileged data, or when we risk a spinnaggedon request waterfall. I’ve enjoyed making a lot of apps that only run on the client. But there are a lot of apps where this just doesn’t make any sense — &lt;a href=&quot;https://jasonformat.com/application-holotypes/&quot;&gt;see Jason Miller’s Application Holotypes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And services? We’re trying to be cheap here! If I can pay $5.00 a month for a server, why would I want to pay another $5.00 a month for comments, another $5.00 a month for authentication, and another $5.00 a month for analytics. There’s a point where this is worth it, but it isn’t your side project. EC2 gives you a virtual machine that you can use for whatever you want.&lt;/p&gt;
&lt;h2 id=&quot;spinning-up-an-instance&quot; tabindex=&quot;-1&quot;&gt;Spinning up an instance &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/Hosting-small-websites-on-EC2/#spinning-up-an-instance&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I wanted to spin up a quick PHP website which didn’t need a database - just a JSON file for data. I created a &lt;a href=&quot;https://aws.amazon.com/about-aws/whats-new/2015/12/introducing-t2-nano-the-smallest-lowest-cost-amazon-ec2-instance/&quot;&gt;T2.nano&lt;/a&gt; instance, using a &lt;a href=&quot;https://aws.amazon.com/ebs/general-purpose/&quot;&gt;gp3 volume for storage&lt;/a&gt; (this is like the hard drive for the VM). I went with &lt;a href=&quot;https://aws.amazon.com/amazon-linux-2/?amazon-linux-whats-new.sort-by=item.additionalFields.postDateTime&amp;amp;amazon-linux-whats-new.sort-order=desc&quot;&gt;Amazon Linux 2&lt;/a&gt; for an operating system - I assume this OS has been optimized for the cloud. The marketing certainly says that it has been.&lt;/p&gt;
&lt;p&gt;Now to make our instance do something, we need to connect to it. There are ways to connect to the machine graphically, but we’ll use SSL because I like to cosplay as someone good at computers.&lt;/p&gt;
&lt;p&gt;After you create an instance, you’ll be prompted to download a &lt;code&gt;.pem&lt;/code&gt; key. Keep this safe. If you lose it you won’t ever be able to connect to your EC2 instance. By default, the username on Amazon Linux will be &lt;code&gt;ec2-user&lt;/code&gt;. And you need the Public IPv4 DNS. You can find it in the instance details.&lt;/p&gt;
&lt;p&gt;Then, to connect to a fresh Amazon EC2 instance via SSL from windows PowerShell:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ssh -i C:&#92;path&#92;to&#92;mykey.pem ec2-user@my-instance-public-IPv4-address.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might be prompted for some trust warning or something which I said yes to without understanding what I was agreeing to.&lt;/p&gt;
&lt;p&gt;Now you should get some ASCII art courtesy of Adam Selipsky.&lt;/p&gt;
&lt;h2 id=&quot;installing-stuff&quot; tabindex=&quot;-1&quot;&gt;Installing stuff &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/Hosting-small-websites-on-EC2/#installing-stuff&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You’re in. Let’s get Apache:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    sudo yum install -y httpd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will use a Linux package manager called yum to download and install Apache. After this has finished, you can start Apache with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    sudo systemctl start httpd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And test it with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    curl http://localhost:80
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And stop it with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    sudo systemctl stop httpd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great. Now you can &lt;a href=&quot;https://arstech.net/install-php-7-on-amazon-linux-2/&quot;&gt;grab and install PHP&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you’re like me, you probably want nice URLs. You can do this with Apache’s mod_rewrite module, and redirect all traffic to index.php by editing &lt;code&gt;/etc/httpd/conf/httpd.conf&lt;/code&gt;. Edit it over SSL by using a command-line text editor like nano. Add the following to the end of the config file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    #This did not work for me
    &amp;lt;IfModule mod_rewrite.c&amp;gt;
        RewriteEngine on
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule . index.php [L]
    &amp;lt;/IfModule&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wait that didn’t work for me (even though it worked for everyone else on stack overflow). For me, (and maybe you), this was what I needed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    #Redirecting all input to index.php
    &amp;lt;IfModule mod_rewrite.c&amp;gt;
        RewriteEngine on
        RewriteCond /path/to/document/root/%{REQUEST_FILENAME} !-f
        RewriteCond /path/to/document/root/%{REQUEST_FILENAME} !-d
        RewriteRule ^(.*)$ /index.php [QSA,L]
     &amp;lt;/IfModule&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can make your own little PHP router like &lt;a href=&quot;https://medium.com/the-andela-way/how-to-build-a-basic-server-side-routing-system-in-php-e52e613cf241&quot;&gt;this one from John O. Paul.&lt;/a&gt; And you’ll need some way to get your code onto the machine (I’ve just been pulling down from github using git but I’m sure there’s a better way . . . &lt;a href=&quot;https://lightrains.com/blogs/deploy-aws-ec2-using-github-actions/&quot;&gt;github action?&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;You might run into another problem. Apache won’t have the rights to write files! There are some &lt;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/install-LAMP.html#prepare-lamp-server-alami&quot;&gt;instructions on the setting-up-LAMP page on the AWS site&lt;/a&gt; for how to sort this out. It involves a command called &lt;code&gt;chown&lt;/code&gt; which might be my favourite name for a command.&lt;/p&gt;
&lt;p&gt;And we all love locks to the left of our URL bar. We better buy a domain, set up Amazon name servers, and also &lt;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/SSL-on-amazon-linux-2.html&quot;&gt;get a TLS certificate from let’s encrypt and install it into Apache&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before you can connect with a site hosted on your EC2 VM, you’ll also need enable traffic from all IP addresses using &lt;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/authorizing-access-to-an-instance.html&quot;&gt;Inbound rules&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-counterargument&quot; tabindex=&quot;-1&quot;&gt;The counterargument &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/Hosting-small-websites-on-EC2/#the-counterargument&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Wait. Those were a lot of steps. And for a site that might not scale very well? Was this all worth the time and effort? Was the cheapness really worth it? How much maintenance is this all going to require? What about security? Is everything configured properly in a way where no one’s going to steal my data?&lt;/p&gt;
&lt;p&gt;These are all extremely valid questions. And they are the reason you might just want to run away and host a site on Netlify, or try AWS amplify if you’re still desperate to give Bezos more bucks.&lt;/p&gt;
&lt;p&gt;However, the nerdy side of me enjoyed getting this all working, and (I hope) I’d be faster at setting it all up the second and third times I do this. I really like the idea that I can have a deployment environment which isn’t limited by what the powers-that-be decide I’m allowed.&lt;/p&gt;
&lt;p&gt;And I love that it’s cheap . . . 🤷‍♂️&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Gizmo Girls</title>
    <link href="https://www.elliotclyde.nz/blog/gizmo-girls/"/>
    <updated>2022-12-01T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/gizmo-girls/</id>
    <content type="html">&lt;p&gt;I&#39;ve started a new blog! &lt;a href=&quot;https://0e595431.astro-gilmore-blog.pages.dev/&quot;&gt;Gizmo Girls&lt;/a&gt; baby!&lt;/p&gt;
&lt;p&gt;I wanted to &lt;a href=&quot;https://mcfunley.com/choose-boring-technology&quot;&gt;Spend all my innovation tokens&lt;/a&gt; and really try out a whole bunch of cool new tech. I also wanted a set up where someone non-technical would be able to make changes. Another thing I wanted was to be able to update the blog from my phone. This meant that the workflow for this blog you&#39;re reading (writing markdown files and pushing them up to github, then letting a github action deploy to azure blob storage) wouldn&#39;t work.&lt;/p&gt;
&lt;p&gt;I also really wanted to try out &lt;a href=&quot;https://astro.build/&quot;&gt;astro.js&lt;/a&gt;. I love that it&#39;s component driven, HTML-first, and it&#39;s super fast. Astro templates are what JSX always &lt;em&gt;should&lt;/em&gt; have been. And the model of only opting into client-side javascript is one that is going to save on load times and complexity.&lt;/p&gt;
&lt;p&gt;Like all my side projects, I also didn&#39;t want to invest a whole bunch of cash in something that I don&#39;t expect to make any cash FROM. Here&#39;s what these requirements looked like:&lt;/p&gt;
&lt;h2 id=&quot;requirements&quot; tabindex=&quot;-1&quot;&gt;Requirements &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/gizmo-girls/#requirements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Astro.js&lt;/li&gt;
&lt;li&gt;Able to be edited and updated by someone non-technical&lt;/li&gt;
&lt;li&gt;Able to be edited and updated from a mobile device (eg. one that doesn&#39;t need node.js installed)&lt;/li&gt;
&lt;li&gt;Cheap $$$&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;airtable&quot; tabindex=&quot;-1&quot;&gt;Airtable &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/gizmo-girls/#airtable&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As I planned to run this all as a static site, I knew immediately that I wouldn&#39;t be updating the content from within the site - as you might do with non-headless (head-full?) WordPress. So I knew that I&#39;d have to go to an outside service. So I went with airtable.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://airtable.com/&quot;&gt;Airtable&lt;/a&gt; is sort of like google sheets, but you can much more clearly define what data should go in each cell, and also have options for allowing rich text and images.&lt;/p&gt;
&lt;p&gt;They&#39;ve got great API documentation, where code snippets will be dynamically generated based on your data - absolutely killer. And the API is allowed in the free tier. I can&#39;t recommend them enough. They also provide a JavaScript package you can use to hit their API to make things super smooth.&lt;/p&gt;
&lt;h2 id=&quot;github-actions&quot; tabindex=&quot;-1&quot;&gt;Github actions &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/gizmo-girls/#github-actions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the blog you&#39;re reading now, the only way I can change the site is to push up some code to the main branch on github. This will fire up the github action and the static files will get pushed up to their new home.&lt;/p&gt;
&lt;p&gt;However it is also possible to &lt;a href=&quot;https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow&quot;&gt;manually run a github action&lt;/a&gt;. I did this for Gizmo girls. What the github action does is downloads all the data and images from airtable, runs the astro build process, then pushes it up to an Azure blob storage static website. This way if I need to edit the site, I can go to airtable, make my edits, then go to github and press &amp;quot;build&amp;quot;. All from the comfort of my phone. Nice.&lt;/p&gt;
&lt;h2 id=&quot;todo&quot; tabindex=&quot;-1&quot;&gt;Todo &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/gizmo-girls/#todo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are a few limitions and things that could be better:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Images&lt;/strong&gt; - At the moment there&#39;s only one image per post on there. Airtable&#39;s rich text doesn&#39;t allow you to inline images so I might need to create anothe table to hold the images and my own custom sytax to convert them&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Caching&lt;/strong&gt; - The blog will download ALL posts and ALL images on every build. That isn&#39;t gonna scale. Eventually I might add some logic in to only grab what has changed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RSS&lt;/strong&gt; - The RSS feed on the site is just links to posts at the moment. I want to figure out how this could be converted to a feed which includes the full post.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Goodbye Heroku free plan</title>
    <link href="https://www.elliotclyde.nz/blog/goodbye-heroku-free-plan/"/>
    <updated>2023-01-08T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/goodbye-heroku-free-plan/</id>
    <content type="html">&lt;p&gt;A few of my posts on here have been about coding on the cheap. &lt;a href=&quot;https://www.elliotclyde.nz/blog/going-static/&quot;&gt;Static sites&lt;/a&gt;, &lt;a href=&quot;https://www.elliotclyde.nz/blog/Hosting-small-websites-on-EC2/&quot;&gt;EC2 instances with everything installed on them&lt;/a&gt;, &lt;a href=&quot;https://www.elliotclyde.nz/blog/gizmo-girls/&quot;&gt;Github actions&lt;/a&gt;, free plans. Sometimes it’s to my detriment as I sacrifice a whole lot of my time for a few less dollars per month. Buuuuuut my side-projects haven’t been aimed at making money, so I don’t want them to suck up money. Maybe after a few more years on a developer salary my priorities will change and I’ll shoot for options that save time over cash. But I also enjoy the limits I set myself. They narrow down the infinite options we have for building and hosting sites.&lt;/p&gt;
&lt;p&gt;These last few months have been a sad time for the web app cheapskates out there. We’re all slaves to capitalism — especially companies with “sales” in their name. And on 25 August 2022 Heroku (now a subsidiary of Salesforce) &lt;a href=&quot;https://blog.heroku.com/next-chapter&quot;&gt;announced that they would be ending their free plan&lt;/a&gt;. In their announcement they advised that starting 28 November 2022 they would start shutting down free-plan dynos. The app I wrote to learn Laravel, &lt;a href=&quot;https://www.elliotclyde.nz/blog/dropping-plantr-2/&quot;&gt;Plantr&lt;/a&gt;, was hosted on a free Heroku dyno. I started revisiting the site throughout the end of 2022. It continued serving content into December, but a couple of weeks ago I hit the site and got an error message from Heroku.&lt;/p&gt;
&lt;p&gt;Building that app over the last quarter of 2020 introduced me to a whole bunch of concepts; I learnt about &lt;a href=&quot;https://www.elliotclyde.nz/blog/eloquent-models-first-impressions/&quot;&gt;ORMs&lt;/a&gt;, &lt;a href=&quot;https://www.elliotclyde.nz/blog/tailwind-for-interactions-alpine-js/&quot;&gt;Alpine.JS&lt;/a&gt; (even got a &lt;a href=&quot;https://css-tricks.com/lightweight-form-validation-with-alpine-js-and-iodine-js/&quot;&gt;CSS tricks article showing off my form validation&lt;/a&gt;), dependency injection with controllers, and &lt;a href=&quot;https://getcomposer.org/&quot;&gt;Composer&lt;/a&gt;. Using a Rails-style model-view-controller framework back in 2020 has come in quite useful now that I have a job at a C#/ASP.NET shop (I haven’t blogged about the .NET ecosystem, however I might start out of pure frustration).&lt;/p&gt;
&lt;p&gt;I didn’t have a whole lot of proof that I knew how to code before I uploaded Plantr - maybe a couple of JavaScript games and this website. Now I’ve had a job in web development for almost a year, and I’ve written a few &lt;a href=&quot;https://css-tricks.com/author/hughhaworth/&quot;&gt;CSS Tricks Articles&lt;/a&gt; there’s slightly less pressure for me to run side projects to prove I can code. I will still do side projects but they’ll be for fun now.&lt;/p&gt;
&lt;p&gt;I understand that expecting for-profit companies to continue hosting server-rendering applications is a bit of a tall order — even in this world where the cost of compute increasingly approaches zero. In &lt;a href=&quot;https://changelog.com/podcast/513&quot;&gt;Adam Wiggins’ interview on the ChangeLog&lt;/a&gt; he talked about how there were a whole bunch of hobby apps on Heroku and it was difficult to tell which free apps were actually important to people. Servers need to grind away to intermittently fire them up, and it takes storage to keep them. If there’s a whole bunch of one-off apps ready to run at any moment, that costs Heroku cash, possibly with zero benefit.&lt;/p&gt;
&lt;p&gt;At the same time, it’s a little sad that my side-project is no longer out in the wild to show off, even if it might be to laugh about how much (or how little) I’ve learnt. Maybe one day I’ll give it a spruce-up and a few more features and upload it somewhere else again. If so, it would be my third attempt at making it. I don’t know if that’d be the best use of my time, so for now it&#39;s going off to application heaven. &lt;a href=&quot;https://github.com/Elliotclyde/Plantr&quot;&gt;Plantr&lt;/a&gt; served its purpose for me as a self-teaching tool. I don’t think I want to pay $60.00 a year to keep it running somewhere else.&lt;/p&gt;
&lt;p&gt;The web is such a democratic platform and I hope that the death of free plans doesn’t lock too many people out. Coming from New Zealand I’m far more economically privileged than a lot of the world. It’s hackers poorer than me we should worry about. But I reckon it&#39;s going to be okay: whichever platform provides the free plans and creates the least friction is the one that will going to get adoption.&lt;/p&gt;
&lt;p&gt;That&#39;s the platform that’s going to be sustainable.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Hill Times, Vim and Delayed Gratificication</title>
    <link href="https://www.elliotclyde.nz/blog/hill-times-vim-and-delayed-gratificication/"/>
    <updated>2023-01-31T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/hill-times-vim-and-delayed-gratificication/</id>
    <content type="html">&lt;p&gt;There&#39;s a hill near my house, about ten minutes walk away. A steep windy road goes up for five more minutes of (hot) walking, then a gate takes you out of suburbia and into the bush. From there on it&#39;s a steep firebreak. The soil is a light brown clay.&lt;/p&gt;
&lt;p&gt;It gets slippery on rainy days, and it&#39;s broken and uneven. Going up the hill you have to duck through mānuka in some sections or tip-toe around mud. Eventually you make it to the top and you can look out over Lower Hutt, Wainutomata, Wellington harbour.&lt;/p&gt;
&lt;p&gt;On a clear day you can look out into the distance and even see the Kaikoura ranges to the South. The hill is about 250m high according to the &lt;a href=&quot;https://en-nz.topographic-map.com/map-sbr1h/Lower-Hutt/?zoom=14&amp;amp;center=-41.21892%2C174.9394&amp;amp;popup=-41.21865%2C174.94401&quot;&gt;topological map&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I ran up that hill 48 times in 2022. I timed myself each time. Here are the times on the different days:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time (minutes)&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;21.08&lt;/td&gt;
&lt;td&gt;14/1/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19.42&lt;/td&gt;
&lt;td&gt;17/1/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:26&lt;/td&gt;
&lt;td&gt;19/1/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:51&lt;/td&gt;
&lt;td&gt;26/1/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:38&lt;/td&gt;
&lt;td&gt;3/2/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:18&lt;/td&gt;
&lt;td&gt;15/2/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:00&lt;/td&gt;
&lt;td&gt;3/3/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:05&lt;/td&gt;
&lt;td&gt;7/3/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18:41&lt;/td&gt;
&lt;td&gt;22/3/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18:08&lt;/td&gt;
&lt;td&gt;28/3/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19.31&lt;/td&gt;
&lt;td&gt;10/4/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18:20&lt;/td&gt;
&lt;td&gt;13/4/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19.29&lt;/td&gt;
&lt;td&gt;25/4/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:54&lt;/td&gt;
&lt;td&gt;28/4/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18:39&lt;/td&gt;
&lt;td&gt;1/5/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18:17&lt;/td&gt;
&lt;td&gt;3/5/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:49&lt;/td&gt;
&lt;td&gt;22/5/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:24&lt;/td&gt;
&lt;td&gt;23/5/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:06&lt;/td&gt;
&lt;td&gt;28/5/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18:10&lt;/td&gt;
&lt;td&gt;29/5/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:46&lt;/td&gt;
&lt;td&gt;8/6/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:52&lt;/td&gt;
&lt;td&gt;3/7/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23:01&lt;/td&gt;
&lt;td&gt;28/7/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:43&lt;/td&gt;
&lt;td&gt;31/7/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:45&lt;/td&gt;
&lt;td&gt;7/8/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:00&lt;/td&gt;
&lt;td&gt;22/8/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:07&lt;/td&gt;
&lt;td&gt;23/8/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:17&lt;/td&gt;
&lt;td&gt;25/8/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:28&lt;/td&gt;
&lt;td&gt;28/8/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:42&lt;/td&gt;
&lt;td&gt;30/8/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:10&lt;/td&gt;
&lt;td&gt;31/8/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:23&lt;/td&gt;
&lt;td&gt;2/9/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:53&lt;/td&gt;
&lt;td&gt;19/9/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:20&lt;/td&gt;
&lt;td&gt;21/9/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:21&lt;/td&gt;
&lt;td&gt;25/9/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:00&lt;/td&gt;
&lt;td&gt;28/9/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:46&lt;/td&gt;
&lt;td&gt;2/10/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18:38&lt;/td&gt;
&lt;td&gt;3/10/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18:34&lt;/td&gt;
&lt;td&gt;10/10/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26:46&lt;/td&gt;
&lt;td&gt;15/10/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17:57&lt;/td&gt;
&lt;td&gt;16/10/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:09&lt;/td&gt;
&lt;td&gt;22/10/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18:58&lt;/td&gt;
&lt;td&gt;30/10/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18:02&lt;/td&gt;
&lt;td&gt;1/11/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17:38&lt;/td&gt;
&lt;td&gt;8/11/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22:11&lt;/td&gt;
&lt;td&gt;11/12/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:25&lt;/td&gt;
&lt;td&gt;12/12/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:57&lt;/td&gt;
&lt;td&gt;15/12/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:46&lt;/td&gt;
&lt;td&gt;23/12/22&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is the way I get myself to do excercise. The timing means I have something to work towards - making that number go down. I also love get to see the views, and how things change as the weather and seasons change. And so many different times of the day (I&#39;m very inconsistent with what time I run). It&#39;s also good time for listening to podcasts - although I probably don&#39;t pick up much information, wheezing away as I try to ascend up the clay.&lt;/p&gt;
&lt;p&gt;The main reason I do this is for my mental health. It helps a &lt;em&gt;lot&lt;/em&gt; with that.&lt;/p&gt;
&lt;p&gt;Unfortunately, I can&#39;t lie and tell you there isn&#39;t a part of me that says I&#39;m running up that hill to look good. But I avoid thinking about it like that. I don&#39;t want to hate my body or have a negative relationship with excercise or food.&lt;/p&gt;
&lt;p&gt;Regardless, I&#39;m proud of making it up the hill that many times in 2022. I&#39;m looking at my calendar now and I haven&#39;t made it up once this year and January&#39;s almost already over. It might be time to get started again.&lt;/p&gt;
&lt;p&gt;Running uphill is agony, but afterwards I only feel the endorphins. And when I&#39;m 68 I&#39;ll feel that much better from the excercise I&#39;m doing today. It reminds me of another piece of delayed gratification I&#39;m dealing with:&lt;/p&gt;
&lt;h2 id=&quot;vim&quot; tabindex=&quot;-1&quot;&gt;Vim &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/hill-times-vim-and-delayed-gratificication/#vim&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Vim is a text editor. But not a normal one by any means. Notepad is to vim as a spade is to the &lt;a href=&quot;https://en.wikipedia.org/wiki/Kola_Superdeep_Borehole&quot;&gt;Kola Superdeep Borehole&lt;/a&gt; drill. It&#39;s a difficult tool to drive, but it holds a seismic power. Yes there&#39;s jokes about quitting (&lt;code&gt;:qw&lt;/code&gt; to quit and save, &lt;code&gt;:q!&lt;/code&gt; to quit and throwaway your work). Yes there&#39;s a stereotype that this is the tool of the neckbeards and sysadmins.&lt;/p&gt;
&lt;p&gt;But I really want to look after my hands as I code. Someone who&#39;s good at vim has the most terse way of getting letters into the computer. They most certainly don&#39;t need to bother with their mouse like us peasants. Letter keys are used for different commands and naturally live where your hands fall on the keyboard. Using short commands to update repetitive changes makes a lot of sense.&lt;/p&gt;
&lt;p&gt;You can hear a great podcast on why people like vim on &lt;a href=&quot;https://changelog.com/podcast/450&quot;&gt;the changelog&lt;/a&gt;. A good place to start learning vim is with vimtutor. It&#39;s where I started and takes you through the basics of how vim works. Then I tried to start using vim consistently and googling things that I needed to learn.&lt;/p&gt;
&lt;p&gt;The problem with the gratification of vim is that like running up a hill, the gratification is delayed. Some things chafe, especially when you&#39;re looking for your file explorer to the left of your screen, and when you&#39;re used to windows and you control+v to paste (WRONG!). And the vimscript syntax has so many hidden secrets. I still seem to need stack overflow for every one!&lt;/p&gt;
&lt;p&gt;Finally (and I know this is my fault), by default vim is ugly. It has stark brutalist colours and terminal fonts. Listening to &lt;a href=&quot;https://syntax.fm/show/568/supper-club-caleb-porzio&quot;&gt;Caleb Porzio on syntax FM talking about how obsessed the laravel community are on code beauty and developer happiness&lt;/a&gt; reminds me that we deserve for things to look &lt;em&gt;nice&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I guess the main thing is that I could configure all this and learn all that but it takes time and effort. At the moment it&#39;s a case of me still liking VSCode because it&#39;s &lt;a href=&quot;https://chriscoyier.net/2023/01/24/you-like-it-because-you-know-it/&quot;&gt;what I know&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>DotNetCore, Azure AD, and SAML</title>
    <link href="https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/"/>
    <updated>2023-04-20T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/</id>
    <content type="html">&lt;p&gt;DotNetCore, Azure AD, and SAML are an odd mix of old and new tech I found myself needing to combine. You’ve got the newer cloudiness of Azure, the fresh C# runtime DotNetCore, but a crusty old authentication protocol with SAML.&lt;/p&gt;
&lt;p&gt;If you’re like me and have been working on a (mostly) greenfield C# project and find that you need to authenticate to Azure AD using SAML, this guide might come in useful for you. There is &lt;em&gt;nothing&lt;/em&gt; on the internet about this specific mix of application runtime, identity provider and protocol. Although there is &lt;a href=&quot;https://developer.okta.com/blog/2020/10/23/how-to-authenticate-with-saml-in-aspnet-core-and-csharp&quot;&gt;this guide from Okta on using DotNetCore with their own SAML authentication&lt;/a&gt;, which I am partly basing this article off.&lt;/p&gt;
&lt;p&gt;First, a little explanation of these three technologies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DotNetCore (also spelled .NET Core). This is a runtime Microsoft provide, usually for C#. Like all things Microsoft provide, it’s confusingly named. This is a &lt;em&gt;different&lt;/em&gt; product from .NET Framework. We’re specifically going to be looking at a web application built with DotNetCore. If you need advice on using SAML authentication with .NET Framework, you’re going to find a lot more articles on this (because they’re both old).&lt;/li&gt;
&lt;li&gt;Azure AD (Azure Active Directory). This is Microsoft’s way of giving you the ability to know who someone is when they hit your application. It gets used by schools and businesses to keep a list of people who should be able to log into internal apps. It means we don’t have to keep any passwords or usernames. Microsoft is going to do all that dirty work for us.&lt;/li&gt;
&lt;li&gt;SAML (Security Assertion Markup Language) is &lt;em&gt;not&lt;/em&gt; a Microsoft product. This is an open standard for authentication. It was created in the early 2000s when XML was very cool. Most of us have fortunately moved on from XML and now use JSON because we want to enjoy our lives. Unfortunately, the enterprise ecosystem hasn’t. There is a lot of jargon that gets used to SAML protocol. I’m going to avoid using it as much as possible in this article because it makes my eyes glaze over. Basically it’s a way for you to be able to redirect the user to a 3rd party who holds their credentials, then allow that 3rd party to come back and tell us who the user is. In this case, the 3rd party is Microsoft.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;scaffolding-a-dotnetcore-web-app&quot; tabindex=&quot;-1&quot;&gt;Scaffolding A DotNetCore Web App &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/#scaffolding-a-dotnetcore-web-app&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can skip this part if you have an existing DotNetCore app you’re trying to get authenticated, although it’s probably good to take a look at the architecture I’ll be using for the example.&lt;/p&gt;
&lt;p&gt;To keep things simple, we’re going to use a server-rendered razor pages app (although it is easily possible to use these technologies in a single page application with React or Angular). You’ll need to have DotNetCore installed, and the &lt;code&gt;dotnet&lt;/code&gt; CLI added to your path. To scaffold a server-rendered app with DotNetCore, navigate to a new directory and use this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    dotnet new razor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you should be able to immediately start your app with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    # Generates a developer https certificate
    dotnet dev-certs https
    # Starts app
    dotnet run
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should be able to hit the localhost URL it spits out (mine defaults to &lt;a href=&quot;https://localhost:7269/&quot;&gt;https://localhost:7269&lt;/a&gt;) and see a welcome page.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-azure-ad&quot; tabindex=&quot;-1&quot;&gt;Setting Up Azure AD &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/#setting-up-azure-ad&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can skip this part if your company has an infrastructure department who are going to do this for you.&lt;/p&gt;
&lt;p&gt;In this part, we’re setting up the Microsoft Active Directory service which will know that we are going to be writing an application and we want to authenticate with SAML.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Step one: get an Azure account if you don’t have one.&lt;/li&gt;
&lt;li&gt;Step two: head to &lt;a href=&quot;https://portal.azure.com/&quot;&gt;https://portal.azure.com&lt;/a&gt; and searching “Azure AD” in the search bar and click into “Azure Active Directory”.&lt;/li&gt;
&lt;li&gt;Step three: click into “Enterprise Applications” on the left. Now search for “azure ad SAML toolkit” and click this type of app to create your new application registration.&lt;/li&gt;
&lt;li&gt;Step four: Click into your new application, then into “Single Sign On” on the left. Finally select “SAML”.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now you need to click “Edit” on the first block of items - the “Basic SAML Configuration”. Here we need to enter three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Identifier - This is an arbitrary string which identifies which of your applications you are trying to authenticate (in case you have more than 1 application). It can be literally anything, but it’s pretty normal to make it the URL of your site. I’m going to make it “myidentifier”&lt;/li&gt;
&lt;li&gt;Reply URL - this is where Microsoft is going to send the user back to after they’ve authenticated by using a POST redirect when the user signs in with Microsoft. More on this later. I’m going to use the URL: &lt;a href=&quot;https://localhost:7269/Auth/SamlResponse&quot;&gt;https://localhost:7269/Auth/SamlResponse&lt;/a&gt;. You would usually give it a few — one for dev and one for production&lt;/li&gt;
&lt;li&gt;Sign On URL - this is the URL for our application - so that Microsoft knows that the request is coming from the right place. I’m going to use the URL: &lt;a href=&quot;https://localhost:7269/&quot;&gt;https://localhost:7269&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;getting-your-certificate&quot; tabindex=&quot;-1&quot;&gt;Getting Your Certificate &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/#getting-your-certificate&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now you can scroll down and download the “Certificate (Raw)”. This certificate is something our app needs to tell that the user has been authenticated by Microsoft. It’s important to keep this safe, as if somebody steals it, they can pretend that they have been logged in by Microsoft when they haven’t been.&lt;/p&gt;
&lt;p&gt;Save the certificate in the root folder of your application as &lt;code&gt;cert.cer&lt;/code&gt; and add it to &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;getting-your-metadata-url&quot; tabindex=&quot;-1&quot;&gt;Getting Your Metadata URL &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/#getting-your-metadata-url&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There’ll be another item on the SAML toolkit page where the name is “App Federation Metadata Url”. Copy this URL and keep it somewhere. Basically what this is is a link to an XML description of the SAML API Microsoft is providing, with a whole bunch of data that our app needs to understand and verify what we get back from Microsoft.&lt;/p&gt;
&lt;h2 id=&quot;adding-yourself-as-a-user&quot; tabindex=&quot;-1&quot;&gt;Adding Yourself As A User &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/#adding-yourself-as-a-user&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now you’ll need to add yourself to say you can get into the app. From the Azure AD SAML Toolkit enterprise application, click “Users and Groups” on the left. Then click “Add user/group”, then click “None selected” under users. Now you should be able to add yourself to the app.&lt;/p&gt;
&lt;h2 id=&quot;itfoxtec-saml&quot; tabindex=&quot;-1&quot;&gt;ITfoxtec SAML &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/#itfoxtec-saml&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We’re going to go ahead and use a nuget package from the folks over at ITfoxtec: &lt;a href=&quot;https://www.itfoxtec.com/identitysaml2&quot;&gt;ITfoxtec - ITfoxtec Identity SAML 2.0&lt;/a&gt;. This is because implementing your own SAML client would be a horrible, time consuming experience. You can install the package with these commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    dotnet add package ITfoxtec.Identity.Saml2
    dotnet add package ITfoxtec.Identity.Saml2.MvcCore
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can actually start writing some code. Edit your &lt;code&gt;program.cs&lt;/code&gt; file. It should be on the root level of your application folder. First add the imports at the top:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    using ITfoxtec.Identity.Saml2;
    using ITfoxtec.Identity.Saml2.Schemas.Metadata;
    using ITfoxtec.Identity.Saml2.MvcCore.Configuration;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then lower down, just &lt;em&gt;before&lt;/em&gt; it says &lt;code&gt;app.UseAuthorization();&lt;/code&gt; add in:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    app.UseAuthentication();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then scroll back up to &lt;code&gt;builder.Services.AddRazorPages();&lt;/code&gt; and add the following config to register that you want to use ITfoxtec for auth:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    builder.Services.AddRazorPages();
    ConfigurationManager configuration = builder.Configuration;
    builder.Services.Configure&amp;lt;Saml2Configuration&amp;gt;(configuration.GetSection(&amp;quot;Saml2&amp;quot;));
    builder.Services.Configure&amp;lt;Saml2Configuration&amp;gt;(saml2Configuration =&amp;gt;
    {
        saml2Configuration.AllowedAudienceUris.Add(saml2Configuration.Issuer);
        string rootDirectory = configuration.GetValue&amp;lt;string&amp;gt;(WebHostDefaults.ContentRootKey);
        var cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(rootDirectory + &amp;quot;&#92;&#92;cert.cer&amp;quot;);
        saml2Configuration.SignatureValidationCertificates.Add(cert);
        var entityDescriptor = new EntityDescriptor();
        entityDescriptor.ReadIdPSsoDescriptorFromUrl(new Uri(configuration[&amp;quot;Saml2:IdPMetadata&amp;quot;]));
        if (entityDescriptor.IdPSsoDescriptor != null)
        {
            saml2Configuration.SingleSignOnDestination = entityDescriptor.IdPSsoDescriptor.SingleSignOnServices.First().Location;
        }
        else
        {
            throw new Exception(&amp;quot;IdPSsoDescriptor not loaded from metadata.&amp;quot;);
        }
    });
    builder.Services.AddSaml2();
    var app = builder.Build();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;appsettings.json-configuration&quot; tabindex=&quot;-1&quot;&gt;Appsettings.json Configuration &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/#appsettings.json-configuration&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What the code above is doing is grabbing a bunch of stuff from &lt;code&gt;appsettings.json&lt;/code&gt; and giving it to the ITFoxtec library. We need to fill &lt;code&gt;appsettings.json&lt;/code&gt; with data from Azure AD so that ITFoxtec knows how to connect to Microsoft’s SAML service. Create a new key in the top level JSON called “saml2” pointing to a JSON object with 3 keys: “IdPMetadata”, &amp;quot;Issuer&amp;quot;, and &amp;quot;CertificateValidationMode”. In “IdPMetadata”, add the metadata URL we grabbed earlier. In “Issuer”, add the Identifier from earlier (yes it bothers me these have different names). Finally in “CertificateValidationMode” add “None”.&lt;/p&gt;
&lt;p&gt;This is what mine looks like (with the actual GUIDs replaced with “a-random-GUID”):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    {
      &amp;quot;Saml2&amp;quot;: {
        &amp;quot;IdPMetadata&amp;quot;: &amp;quot;https://login.microsoftonline.com/a-random-GUID/federationmetadata/2007-06/federationmetadata.xml?appid=a-random-GUID&amp;quot;,
        &amp;quot;Issuer&amp;quot;: &amp;quot;myidentifier&amp;quot;,
        &amp;quot;CertificateValidationMode&amp;quot;: &amp;quot;None&amp;quot;
      }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why are we putting CertificateValidationMode as none? That sounds a bit gung-ho with security? I can assure you that it’s not. Basically, what is going on here is that there’s a cartel of companies and organisations who create certificates for SSL and have programmed a large amount of software (including your browser) to get angry if certificates aren’t made by them. CertificateValidationMode is a way to tell ITFoxTec to get mad at &lt;em&gt;you&lt;/em&gt; for using a non-supported certificate. The thing is, that the Azure AD certs aren’t part of the certificate club, so we need to set this to false to not throw an error.&lt;/p&gt;
&lt;h2 id=&quot;adding-controller-routing&quot; tabindex=&quot;-1&quot;&gt;Adding Controller Routing &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/#adding-controller-routing&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now we’re going to need a controller. This means we need to change some of the routing from the default for server-rendered razor apps. Replace this part of the config:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    app.MapRazorPages();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    app.UseEndpoints(endpoints =&amp;gt;
    {
        endpoints.MapRazorPages();
        endpoints.MapControllerRoute(
            name: &amp;quot;default&amp;quot;,
            pattern: &amp;quot;{controller=Home}/{action=Index}/{id?}&amp;quot;);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is going to tell it to try and find a razor page to match the route, then if it can’t find it, it will look for a controller using the format &lt;code&gt;controller/action/id&lt;/code&gt; where ID will be passed to the action method if it asks for it. This is a pretty standard way to do controller routing these days.&lt;/p&gt;
&lt;p&gt;Now our app should still be work and won’t error out when we hit the home page.&lt;/p&gt;
&lt;h2 id=&quot;writing-an-auth-controller&quot; tabindex=&quot;-1&quot;&gt;Writing An Auth Controller &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/#writing-an-auth-controller&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Create a new file called &lt;code&gt;AuthController.cs&lt;/code&gt; and fill it with this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    using ITfoxtec.Identity.Saml2;
    using ITfoxtec.Identity.Saml2.Schemas;
    using ITfoxtec.Identity.Saml2.MvcCore;
    using System.Security.Authentication;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Options;
    namespace AzureAd
    {
        [AllowAnonymous]
        [Route(&amp;quot;Auth&amp;quot;)]
        public class AuthController : Controller
        {
            const string relayStateReturnUrl = &amp;quot;ReturnUrl&amp;quot;;
            private readonly Saml2Configuration config;
            public AuthController(IOptions&amp;lt;Saml2Configuration&amp;gt; configAccessor)
            {
                config = configAccessor.Value;
            }
            [Route(&amp;quot;Login&amp;quot;)]
            public IActionResult Login()
            {
                var binding = new Saml2RedirectBinding();
                return binding.Bind(new Saml2AuthnRequest(config)).ToActionResult();
            }
            [Route(&amp;quot;SamlResponse&amp;quot;)]
            public async Task&amp;lt;IActionResult&amp;gt; SamlResponse()
            {
                var binding = new Saml2PostBinding();
                var saml2AuthnResponse = new Saml2AuthnResponse(config);
                binding.ReadSamlResponse(Request.ToGenericHttpRequest(), saml2AuthnResponse);
                if (saml2AuthnResponse.Status != Saml2StatusCodes.Success)
                {
                    throw new AuthenticationException($&amp;quot;SAML Response status: {saml2AuthnResponse.Status}&amp;quot;);
                }
                binding.Unbind(Request.ToGenericHttpRequest(), saml2AuthnResponse);
                await saml2AuthnResponse.CreateSession(HttpContext);
               return Redirect(&amp;quot;https://localhost:7269&amp;quot;);
            }
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There’s a lot going on here. There are two endpoints here; the login endpoint, and the SamlResponse endpoint.&lt;/p&gt;
&lt;h2 id=&quot;the-login-endpoint&quot; tabindex=&quot;-1&quot;&gt;The Login Endpoint &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/#the-login-endpoint&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The login endpoint is where the unauthenticated user sends a request to when they want to log in:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;            [Route(&amp;quot;Login&amp;quot;)]
            public IActionResult Login()
            {
                var binding = new Saml2RedirectBinding();
                return binding.Bind(new Saml2AuthnRequest(config)).ToActionResult();
            }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What will happen here is that the SAML library will redirect the user away from our app and over to Microsoft. As it does this, it adds a large amount of data in URL parameter “saml2” which will contain a bunch of information Microsoft asks for in its metadata (including the identifier). It deflates it (applying a kind of compression), base64 encodes it, then URL encodes, then sends it. You can see what the contents contains using &lt;a href=&quot;https://www.samltool.com/decode.php&quot;&gt;this handy tool&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thankfully this is all abstracted away from us with the &lt;code&gt;Saml2RedirectBinding&lt;/code&gt; class, and we just give it the config it needs to go ahead. Here we can also tell Microsoft to loop back to a different domain (eg. between development and production) by setting the &lt;code&gt;AssertionConsumerServiceUrl&lt;/code&gt;property on the &lt;code&gt;Saml2AuthnRequest&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-samlresponse-endpoint&quot; tabindex=&quot;-1&quot;&gt;The SamlResponse Endpoint &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/#the-samlresponse-endpoint&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The SamlResponse endpoint is where the user is going to get redirected back to after Microsoft has said they’ve entered in the correct username and password and they’re logged in:&#92;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;           [Route(&amp;quot;SamlResponse&amp;quot;)]
            public async Task&amp;lt;IActionResult&amp;gt; SamlResponse()
            {
                var binding = new Saml2PostBinding();
                var saml2AuthnResponse = new Saml2AuthnResponse(config);
                binding.ReadSamlResponse(Request.ToGenericHttpRequest(), saml2AuthnResponse);
                if (saml2AuthnResponse.Status != Saml2StatusCodes.Success)
                {
                    throw new AuthenticationException($&amp;quot;SAML Response status: {saml2AuthnResponse.Status}&amp;quot;);
                }
                binding.Unbind(Request.ToGenericHttpRequest(), saml2AuthnResponse);
                await saml2AuthnResponse.CreateSession(HttpContext);
                return Redirect(&amp;quot;https://localhost:7269&amp;quot;);
            }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;binding.ReadSamlResponse&lt;/code&gt; will get all the information from the SAML data, and if there’s no error here we should fully have all the data we need about the user. If you printed the raw POST body as a string then check it with &lt;a href=&quot;https://www.samltool.com/decode.php&quot;&gt;the SAML defalting tool&lt;/a&gt; you can see what Microsoft is sending back.&lt;/p&gt;
&lt;p&gt;However, even with all this data it’s important to run the &lt;code&gt;unbind&lt;/code&gt; method. This checks whether hashing the response with our certificate will get the same result as what Microsoft adds to the “certificate” field in the SAML response. It’s a way to make sure that the information in the response is actually coming from Azure AD. Then if all has gone well we can create the user session, and from here we’ll know who is logged into the app.&lt;/p&gt;
&lt;h2 id=&quot;user-claims&quot; tabindex=&quot;-1&quot;&gt;User Claims &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/#user-claims&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The way information is stored about the logged in user is in the &lt;code&gt;User&lt;/code&gt; class, which is made accessible to all pages and controllers in DotNetCore apps. What we get is a list of “claims” which are basically key-value pairs of information about the user, which might be their name, email address, or whether they’re a super user. Azure AD will send a bunch of default ones which we can use. You can grab the user claims like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    var nameClaim = User.Claims.FirstOrDefault(c=&amp;gt;c.Type==&amp;quot;http://schemas.microsoft.com/identity/claims/displayname&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s use this claim to display the user’s name in our app:&lt;/p&gt;
&lt;h2 id=&quot;updating-the-homepage&quot; tabindex=&quot;-1&quot;&gt;Updating The Homepage &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/#updating-the-homepage&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Edit the &lt;code&gt;Pages/Index.cshtml&lt;/code&gt; page so the contents of it looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    @page
    @model IndexModel
    @{
        ViewData[&amp;quot;Title&amp;quot;] = &amp;quot;Home page&amp;quot;;
    }
    @if (User.Identity.IsAuthenticated)
    {
        var nameClaim = User.Claims.FirstOrDefault(c=&amp;gt;c.Type==&amp;quot;http://schemas.microsoft.com/identity/claims/displayname&amp;quot;);
        var name = nameClaim == null ? null : nameClaim.Value;
        &amp;lt;p&amp;gt;Hello, @name&amp;lt;/p&amp;gt;
    }
    else
    {
        &amp;lt;a asp-controller=&amp;quot;Auth&amp;quot; asp-action=&amp;quot;Login&amp;quot;&amp;gt;Login&amp;lt;/a&amp;gt;
     }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have a functional page which will only display a login link for an unauthenticated user, and will display the users name after they’ve logged in.&lt;/p&gt;
&lt;p&gt;Now you should be able to start your application with &lt;code&gt;dotnet run&lt;/code&gt; then head to the home page, then click the “login” link.&lt;/p&gt;
&lt;h2 id=&quot;moving-on-from-here&quot; tabindex=&quot;-1&quot;&gt;Moving on from here &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/#moving-on-from-here&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There’s a bunch of stuff that I haven’t done which would be pretty normal in an app:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creating some middleware that will redirect the user to the home page if they aren’t logged in&lt;/li&gt;
&lt;li&gt;Adding some custom claims to the user to give them different access rights to different parts of the application&lt;/li&gt;
&lt;li&gt;Make it configurable from &lt;code&gt;appsettings.json&lt;/code&gt; which URL out from dev and prod you want Microsoft to loop back to using using the &lt;code&gt;AssertionConsumerServiceUrl&lt;/code&gt; property of the &lt;code&gt;Saml2AuthRequest&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Sticker Charts</title>
    <link href="https://www.elliotclyde.nz/blog/sticker-charts/"/>
    <updated>2023-08-13T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/sticker-charts/</id>
    <content type="html">&lt;h2 id=&quot;reporting&quot; tabindex=&quot;-1&quot;&gt;Reporting &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/sticker-charts/#reporting&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For a large amount of my life I didn&#39;t really measure much. I&#39;m bad at arts and crafts because I don&#39;t properly cut at the right angle. When I volunteered on farms I&#39;d plant in weird lines. I like to cook a lot more than I like to bake. I’ve always considered myself an “artistic” person more than a “science” person.&lt;/p&gt;
&lt;p&gt;Now measurement, numbers and reporting are literally my job at a data-obsessed company. I&#39;ve been building reports for HR data around recruitment, headcount and attrition, and training data.&lt;/p&gt;
&lt;p&gt;It turns out people find data really useful. And by people I mean business people. You need data to see what&#39;s working, where things are going, how people are doing at their job. You need data to make the number go up - usually the number is &amp;quot;profits&amp;quot;.&lt;/p&gt;
&lt;h2 id=&quot;power-bi&quot; tabindex=&quot;-1&quot;&gt;Power BI &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/sticker-charts/#power-bi&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are a number of reporting tools out there. There&#39;s &lt;a href=&quot;https://superset.apache.org/&quot;&gt;Apache Superset&lt;/a&gt;, there&#39;s &lt;a href=&quot;https://www.tableau.com/&quot;&gt;Tableau&lt;/a&gt;, but the most ubiquitous is &lt;a href=&quot;https://powerbi.microsoft.com/&quot;&gt;Power BI&lt;/a&gt;. Power BI is a reporting offering from a certain little company in Seattle. It&#39;s is available with a product they sell called Microsoft 365; a SAAS product most businesses &lt;em&gt;already&lt;/em&gt; have a subscription with. So it&#39;s a no brainer for these businesses to use Power BI. They&#39;re already paying for it.&lt;/p&gt;
&lt;p&gt;Power BI is a pretty classic Microsoft product: absolutely packed with features thrown together in the most haphazard way  you can imagine. It&#39;s easy to get started, with but once you get off the beaten path you start to feel bounds of the walled garden.&lt;/p&gt;
&lt;h2 id=&quot;my-3-big-gripes-with-power-bi&quot; tabindex=&quot;-1&quot;&gt;My 3 big Gripes with Power BI &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/sticker-charts/#my-3-big-gripes-with-power-bi&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As a developer coming to Power BI there&#39;s a few big things that annoy me:&lt;/p&gt;
&lt;h3 id=&quot;bad-code-editing-experience&quot; tabindex=&quot;-1&quot;&gt;Bad Code Editing Experience &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/sticker-charts/#bad-code-editing-experience&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When you want to write a DAX measure or do something custom with M Query on the desktop app you&#39;ll find yourself in Power BI&#39;s text editor. It has some basic autocomplete, and some small amount of syntax highlighting but that&#39;s it. There&#39;s no Control-F, there&#39;s no way to look up definitions of functions, there&#39;s no formatter, there&#39;s no way to customise the editor theme in any way.&lt;/p&gt;
&lt;p&gt;I’ve had to literally copy and paste an M Query snippet to notepad so I can control-F throught to check whether I&#39;m including a column. You know something is wrong when notepad has a feature your text editor doesn&#39;t have.&lt;/p&gt;
&lt;h3 id=&quot;2-different-proprietary-languages&quot; tabindex=&quot;-1&quot;&gt;2 Different Proprietary Languages &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/sticker-charts/#2-different-proprietary-languages&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There&#39;s 2 different languages Power BI uses: M query and DAX. It&#39;s important to have a deep understanding of both if you&#39;re doing anything nontrivial in Power BI reports. I&#39;ll accept Microsoft will be Microsoft and is allowed to create new proprietary languages, knowing full well that my knowledge of JavaScript, C# and SQL becomes useless in this environment. But what &lt;em&gt;really&lt;/em&gt; annoys me is they created &lt;em&gt;2&lt;/em&gt; new proprietary languages for a &lt;em&gt;single&lt;/em&gt; product, and both have completely differing syntax.&lt;/p&gt;
&lt;p&gt;For instance in DAX, we have the magical operator “IN” to check if something is in a list:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    NewColumn = IF(
        &#39;Table&#39;[OriginalColumn] IN {&amp;quot;String1&amp;quot;, &amp;quot;String2&amp;quot;, &amp;quot;String3&amp;quot;},
        TRUE(),
        FALSE()
    )

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whereas in M query we have to use a “List.Contains” function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     NewColumnTable = Table.AddColumn(
        Source, 
        &amp;quot;NewColumn&amp;quot;, 
        each List.Contains(
            {&amp;quot;String1&amp;quot;, &amp;quot;String2&amp;quot;, &amp;quot;String3&amp;quot;}, 
            [OriginalColumn])
        )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In DAX we have the logical operators &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt; for “and” and “or”. Whereas in M Query we use the words &lt;code&gt;and&lt;/code&gt; and &lt;code&gt;or&lt;/code&gt; for the same task. Wouldn’t it be better for the syntax to be consistent when it’s part of the exact same platform?&lt;/p&gt;
&lt;h3 id=&quot;lack-of-smart-caching-behaviour&quot; tabindex=&quot;-1&quot;&gt;Lack of Smart Caching Behaviour &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/sticker-charts/#lack-of-smart-caching-behaviour&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When you&#39;re working on a Power BI dataset, you write your query then it will get pulled it down from whatever source you use (whether that be an SQL server, a Power BI dataflow, a web service or any number of other sources). Then you can add steps to process the data - removing and renaming columns, adding types to them, merging with other queries etc.&lt;/p&gt;
&lt;p&gt;The problem seems to be that there&#39;s no separation between the grabbing of the data and the processing of the data. And this will mean that every time you change your M Query it will need to redownload your whole data source. This can take up to half an hour with the dataset sizes I&#39;ve been working with. There&#39;s no way to opt out of redownloading all the data to say &amp;quot;no, I don&#39;t care that this isn&#39;t fresh&amp;quot;. No, it will be a half an hour iteration to check whether your code works.&lt;/p&gt;
&lt;h2 id=&quot;good-things-about-power-bi&quot; tabindex=&quot;-1&quot;&gt;Good Things About Power BI &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/sticker-charts/#good-things-about-power-bi&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I could go on about things that annoy me about Power BI, but some of the good things:&lt;/p&gt;
&lt;p&gt;When you&#39;re following the happy path, Power BI is a super quick way to throw a report together. It comes with a lot of default visuals and there’s also an ecosystem of third party visuals. Microsoft forums have a lot of advice on different problems. Also, sometimes the low-code features &lt;em&gt;can&lt;/em&gt; save you a lot of time writing code. There’s also permissioning tools, usage analytics tools, and a bunch of different integrations with other Microsoft products of course.&lt;/p&gt;
&lt;h2 id=&quot;sticker-charts&quot; tabindex=&quot;-1&quot;&gt;Sticker Charts &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/sticker-charts/#sticker-charts&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Working on these reports also make me wonder whether I should start measuring more things in my life, and presenting these measurements in a visual way. Having a visual way to see your progress in something makes that progress feel more real. This is the reason senior leadership teams and business people are so into reports.&lt;/p&gt;
&lt;p&gt;It’s like a kid’s sticker chart: a kid does something right and they get a sticker on their chart. They feel the endorphins of seeing what they achieved, and it makes it a lot easier to do the next time because they had a visual symbol that they did well. I feel like I could use reporting to encourage me to get out of my default behaviour (endlessly scrolling down a social media app). And maybe do something better, like &lt;a href=&quot;https://www.elliotclyde.nz/blog/hill-times-vim-and-delayed-gratificication/&quot;&gt;learn vim or run up a hill&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Ways of getting data from Excel files into a Database</title>
    <link href="https://www.elliotclyde.nz/blog/ways-of-getting-data-from-excel-files-into-a-database/"/>
    <updated>2024-01-18T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/ways-of-getting-data-from-excel-files-into-a-database/</id>
    <content type="html">&lt;p&gt;The lingua franca of the government/enterprise world for communicating data is the excel file. SQL doesn’t have the same level of adoption for non-technical people. This means that if you’re an SQL person (&lt;a href=&quot;https://www.elliotclyde.nz/blog/why-is-sql-weird/&quot;&gt;As I&#39;m attempting to be&lt;/a&gt;), you’re bound to get asked to translate between these two forms of tabular storage - from excel to an SQL db and vice versa. Often a big part of my job is that I get sent an excel file from a non-technical person, then I need to make bulk updates in a database based on this data.&lt;/p&gt;
&lt;p&gt;I find that my preferred method to do bulk updates is to smash the data into a table (you can use a temporary table, but I prefer using a regular table, then delete it after a few days) as opposed to writing a single script and running it as a one-off,. This allows me to make some assertions on what I expect the updates to look like (eg. how many updates I’m about to do) then I can execute when I’m confident about what the result will be.&lt;/p&gt;
&lt;p&gt;Before I go on: I should note that the flavour of SQL I’ll be looking at here is that of Microsoft SQL Server. If you work in government or enterprise, this is likely the flavour you’ll be dealing with too.&lt;/p&gt;
&lt;p&gt;So without further ado, here are some ways to get data into a database from an excel file:&lt;/p&gt;
&lt;h2 id=&quot;ssms-flat-file-import&quot; tabindex=&quot;-1&quot;&gt;SSMS flat file import &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/ways-of-getting-data-from-excel-files-into-a-database/#ssms-flat-file-import&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This method is quite convenient and requires the least typing as you get to use a graphical user interface to get the data into your database.&lt;/p&gt;
&lt;p&gt;The first step is to save the excel sheet you want to import as a CSV file. Just the regular default save settings for saving as a CSV work for me.&lt;/p&gt;
&lt;p&gt;Now open up &lt;a href=&quot;https://learn.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms?view=sql-server-ver16&quot;&gt;SSIS&lt;/a&gt;, then navigate to your database. Right click your database, click into “Tasks” then “Import Flat File…”. Get the path to the import file, configure the table name (it will have to be a new table; this method doesn’t seem to allow appending to tables). Then in “Modify columns” tab you can configure the types for each of the columns.&lt;/p&gt;
&lt;p&gt;Click through to “Summary”, then “Finish”, then refresh your database and you should be good to go.&lt;/p&gt;
&lt;h2 id=&quot;using-the-import%2Fexport-wizard---microsoft-access&quot; tabindex=&quot;-1&quot;&gt;Using the import/export wizard - Microsoft Access &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/ways-of-getting-data-from-excel-files-into-a-database/#using-the-import%2Fexport-wizard---microsoft-access&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is another way to insert from an excel into a database where you don’t need to convert your excel file to a CSV first. It also means you can do multiple excel sheets at once, and with this method you can append to a table instead of only being able to create a new table. There do seem to be issues with this method: I’ve found that you have to download some tools to get this going, and even then, it seems to periodically not work for reasons that aren’t entirely clear to me.&lt;/p&gt;
&lt;p&gt;The first step you’ll need to take is to download the &lt;a href=&quot;https://www.microsoft.com/en-nz/download/details.aspx?id=13255&quot;&gt;Microsoft Access Database Engine Redistributable&lt;/a&gt;. Also you need to get the &lt;a href=&quot;https://learn.microsoft.com/en-us/answers/questions/634023/sql-server-excel-import-the-microsoft-ace-oledb-12&quot;&gt;32 bit version - not the 64 bit version&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After you have Microsoft Access installed, we’re going to do something similar to the flat file import: open up &lt;a href=&quot;https://learn.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms?view=sql-server-ver16&quot;&gt;SSIS&lt;/a&gt;, right click your database, click into “Tasks” then “Import data…”. If it’s your first time here, you should see the title “Welcome to SQL Server Import and Export wizard”.Click “Next” (or alt N if you’re too cool to use a mouse).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Choosing a data source&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the dropdown, click into “Microsoft Excel”, it should have an ancient Excel logo (one which immediately transported me back to my childhood in the 90s). Then add the path to your excel file and select the excel version you have on your computer. Click “Next” (or alt + N).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Choosing a destination&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I choose “.Net Framework Data Provider for SqlServer” for a destination. Unfortunately, this doesn’t prefill any connection string to your database. That would make far too much sense. So if you don’t have the connection string ready here’s a quick way to do it:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;An easy (ish) way to get SQL Server Connection string&lt;/strong&gt;&lt;br /&gt;
First open up Windows Powershell. Let’s create a blank “.udl” file with this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     ni test.udl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then open Windows Explorer in whatever folder you’re in:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    explorer .  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Right click you’re new test.udl file and open Properties. Click into “Connection”.&lt;/p&gt;
&lt;p&gt;Keep powershell open, but go back to SSMS and click “connect”, then if you’ve already connected to your database, it should pre-fill with the server name. Copy this and paste it in “server name” in the test.udl properties. Then enter your credentials/Windows NT integration for how you’ll authenticate to the db. Now select your database from the dropdown (this should prefill if the authentication and server name are correct). Finally click “Apply” and close. Back in PowerShell, you can now print it out and you should see the connection string:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    cat test.udl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you can use this command to copy the second line (the connection string itself):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    Get-Content test.udl -TotalCount 3 | Select-Object -Last 1 | clip;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can delete the .udl file if you want to keep things clean:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    rm test.udl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, for our import, you can paste the connection string you’ve generated into the “Connection string” value in the wizard on the “Destination” page where we’re importing data.&lt;/p&gt;
&lt;h3 id=&quot;specify-table-copy-or-query&quot; tabindex=&quot;-1&quot;&gt;Specify Table Copy or Query &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/ways-of-getting-data-from-excel-files-into-a-database/#specify-table-copy-or-query&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In this page the default “Copy data from one or more tables or views” will work for us if don’t need to modify the data.&lt;/p&gt;
&lt;h3 id=&quot;select-source-tables-and-views&quot; tabindex=&quot;-1&quot;&gt;Select Source Tables and Views &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/ways-of-getting-data-from-excel-files-into-a-database/#select-source-tables-and-views&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now we get to choose sources and destinations. On the left you’ll see the list of sheets in the excel file, on the right, you’ll see the list of tables in your database that the wizard will create/append to. You can edit the names of the tables. It will also try and guess the types from the excel file, but you can configure types with the “Edit mappings” button. It will default to appending to a table if the table exists and creating table if it doesn’t. In “Edit mappings” you can configure it to it to delete the existing rows.&lt;/p&gt;
&lt;h3 id=&quot;save-and-run-package&quot; tabindex=&quot;-1&quot;&gt;Save and Run Package &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/ways-of-getting-data-from-excel-files-into-a-database/#save-and-run-package&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here you can configure whether you want to run it immediately, save it as a &lt;code&gt;.DTXS&lt;/code&gt; Integration Services Package file, or both. We want to run immediately so hit that then watch it run. If you do save it there are a &lt;a href=&quot;https://learn.microsoft.com/en-us/sql/integration-services/packages/run-integration-services-ssis-packages?view=sql-server-ver16#execute_package_dialog&quot;&gt;many ways to run&lt;/a&gt; the &lt;code&gt;.DTXS&lt;/code&gt; file for later (I tried to use the &lt;code&gt;dtexec&lt;/code&gt; utility but even that wanted the 64 bit version of the Microsoft Access database engine 😣).&lt;/p&gt;
&lt;h2 id=&quot;bulk-insert-command&quot; tabindex=&quot;-1&quot;&gt;BULK INSERT Command &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/ways-of-getting-data-from-excel-files-into-a-database/#bulk-insert-command&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The SQL &lt;code&gt;BULK INSERT&lt;/code&gt; command is similar to the flat file insert, except that this time you’ll have to define your table before you create it. For this method, you will have to save your excel file as a CSV first. This is nice if you don’t want to use a GUI tool and want to just use SQL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    -- Create the table first
    CREATE TABLE Countries(Id bigint,Country varchar(100),Population bigint ,President varchar(100));
    -- Then you can bulk insert
    BULK INSERT Countries
    FROM &#39;C:&#92;Path&#92;To&#92;File&#92;Countries.csv&#39;
    WITH (
      FIELDTERMINATOR = &#39;,&#39;,
      ROWTERMINATOR = &#39;&#92;n&#39;,
      FIRSTROW = 2
    );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty nice that you can do it without having to click through all the steps of the “Insert Data…” method.&lt;/p&gt;
&lt;h2 id=&quot;using-excel-formulas-to-generate-an-sql-script&quot; tabindex=&quot;-1&quot;&gt;Using Excel Formulas to generate an SQL script &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/ways-of-getting-data-from-excel-files-into-a-database/#using-excel-formulas-to-generate-an-sql-script&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now we’re starting to get into more manual methods. These methods rely on generating an SQL script. This means the data upload time might be slower, as SQL server will have to parse all the commands you give it. If you have a lot of data this might take a long time. Creating a script does give you a bit more control to edit data as you go.&lt;/p&gt;
&lt;p&gt;Lets say we have an excel table that looks like this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;country&lt;/th&gt;
&lt;th&gt;population&lt;/th&gt;
&lt;th&gt;leader&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New Zealand&lt;/td&gt;
&lt;td&gt;5000000&lt;/td&gt;
&lt;td&gt;Jacinda Ardern&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;USA&lt;/td&gt;
&lt;td&gt;300000000&lt;/td&gt;
&lt;td&gt;Joe Biden&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;UK&lt;/td&gt;
&lt;td&gt;30000000&lt;/td&gt;
&lt;td&gt;Rishi Sunak&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;And we want to get this data into the database, we could first create our table:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    -- Create the table
    CREATE TABLE Countries(Id bigint,Country varchar(100),Population bigint ,President varchar(100));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then use an excel formula to generate a string.&lt;/p&gt;
&lt;p&gt;So we would start with the cell to the right of “Jacinda Ardern”, then type this formula:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    =CONCAT(&amp;quot;INSERT INTO Countries (Id,Country,Population,Leader) VALUES (&amp;quot;,A2,&amp;quot;,&#39;&amp;quot;,B2,&amp;quot;&#39;,&amp;quot;,C2,&amp;quot;,&#39;&amp;quot;,D2,&amp;quot;&#39;);&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The string that this formula will generate should look like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    INSERT INTO Countries (Id,Country,Population,Leader) VALUES (1,&#39;New Zealand&#39;,5000000,&#39;Jacinda Ardern&#39;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we can use the auto-formula function to pull down the formula to create each cell and make the script which will run through every line.&lt;/p&gt;
&lt;p&gt;I’ve created a few of these in my time, and there are some gotchas to watch out for. It can be quite difficult to balance all the different quotes (the double quotes for the excel strings and the single quotes for the SQL strings). Also, if there are any single quotes in strings, this will cause issues (I would complain about Irish names here, but the Irish have been through enough). You can use the &lt;code&gt;SUBSTITUTE&lt;/code&gt; function of excel to escape single quotes. So, if we were worried about our “leader” column having single quotes in the string, the formula would look like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    =CONCAT(&amp;quot;INSERT INTO Countries (Id,Country,Population,Leader) VALUES (&amp;quot;,A2,&amp;quot;,&#39;&amp;quot;,B2,&amp;quot;&#39;,&amp;quot;,C2,&amp;quot;,&#39;&amp;quot;,SUBSTITUTE(D2,&amp;quot;&#39;&amp;quot;,&amp;quot;&#39;&#39;&amp;quot;),&amp;quot;&#39;);&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;using-awk-to-generate-an-sql-script&quot; tabindex=&quot;-1&quot;&gt;Using awk to generate an SQL script &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/ways-of-getting-data-from-excel-files-into-a-database/#using-awk-to-generate-an-sql-script&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I‘ve had a fascination for the old-school tool &lt;a href=&quot;https://en.wikipedia.org/wiki/AWK&quot;&gt;awk&lt;/a&gt; ever since listening to the &lt;a href=&quot;https://changelog.com/podcast/484&quot;&gt;Changelog interview with Brian Kernighan&lt;/a&gt;. Awk is a simple programming language for data processing which has been around since the 70s and gets included in bash scripting environments.&lt;/p&gt;
&lt;p&gt;If you have linux or mac you will have awk ready to go by default. If you’re on windows, there are a myriad of ways to get a bash-like environment. I’d argue the simplest is &lt;a href=&quot;https://gitforwindows.org/&quot;&gt;git bash&lt;/a&gt;. If you’re using &lt;a href=&quot;https://www.git-scm.com/&quot;&gt;git&lt;/a&gt;, you might already have it installed on your machine.&lt;/p&gt;
&lt;p&gt;A great way to use awk is to first save your file as a tab delimited text file. Let’s continue with the countries file  above. First save that as &lt;code&gt;countries.txt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then open git bash and navigate to the directory and type this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    awk -F&#39;&#92;t&#39; $&#39;{print}&#39; countries.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should print all the lines of the text file. An explanation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;awk&lt;/code&gt; is the bash command we’re using&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-F&#39;&#92;t&lt;/code&gt;&#39;  is configuring it to use tabs as the delimiter&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$``&#39;``{print}``&#39;&lt;/code&gt; is the awk script we’re running (in this case it will print every line)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;countries.txt&lt;/code&gt; is the name of the file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now if we were looking at our countries file we could use this script to generate the sql script we wanted:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    awk -F&#39;&#92;t&#39; $&#39;NR&amp;gt;1 {print &amp;quot;INSERT INTO countries (Id,Country,Population,Leader) VALUES (&amp;quot;$1&amp;quot;,&#92;&#39;&amp;quot;$2&amp;quot;&#92;&#39;,&amp;quot;$3&amp;quot;,&#92;&#39;&amp;quot;$4&amp;quot;&#92;&#39;)&amp;quot;}&#39; countries.txt &amp;gt;&amp;gt; countries.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, what is this new code doing?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NR&amp;gt;1&lt;/code&gt; is skipping the header (only run this code if the row number is over 1)&lt;/li&gt;
&lt;li&gt;Awk will automatically concatenate strings for you so we just throw them in a line.&lt;/li&gt;
&lt;li&gt;We use &lt;code&gt;&#92;``&#39;&lt;/code&gt; to escape the single quotes inside the awk command (this will only work in bash if you use a dollar sign at the start of the string)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;gt;&amp;gt; countries.sql&lt;/code&gt; will command it to store the output in a new SQL file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, we’re going to have the same issue with this as we did with generating scripts in excel, that we don’t want single quotes to destroy our strings. We can use the sub function to prevent this from happening:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    awk -F&#39;&#92;t&#39; $&#39;NR&amp;gt;1 {sub(/&#92;&#39;/,&amp;quot;&#92;&#39;&#92;&#39;&amp;quot;,$4); print &amp;quot;INSERT INTO countries (Id,Country,Population,Leader) VALUES (&amp;quot;$1&amp;quot;,&#92;&#39;&amp;quot;$2&amp;quot;&#92;&#39;,&amp;quot;$3&amp;quot;,&#92;&#39;&amp;quot;$4&amp;quot;&#92;&#39;);&amp;quot;}&#39; countries.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;using-node.js-to-generate-an-sql-script&quot; tabindex=&quot;-1&quot;&gt;Using node.js to generate an SQL script &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/ways-of-getting-data-from-excel-files-into-a-database/#using-node.js-to-generate-an-sql-script&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you’re a normal person, you might want to use a more modern programming language than awk to generate a script. And we should never bet against JavaScript. We can use an npm package called &lt;code&gt;[csv-parser](https://www.npmjs.com/package/csv-parser)&lt;/code&gt; to parse a CSV file and create an SQL script.&lt;/p&gt;
&lt;p&gt;First, navigate to a folder, save your excel file as a .csv in it and use these commands to initialise npm and install &lt;code&gt;csv-parser&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    npm init -y 
    npm install csv-parser
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can write a node script to create your SQL file. First, import &lt;code&gt;fs&lt;/code&gt; and &lt;code&gt;csv-parser&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    const csv = require(&#39;csv-parser&#39;)
    const fs = require(&#39;fs&#39;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can declare an array to store the data in, pipe your file to the csv parser and loop trhough the array to print off a script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // script.js
    const data = [];
    fs.createReadStream(&#39;countries.csv&#39;)
      .pipe(csv())
      .on(&#39;data&#39;,(data)=&amp;gt;{results.push(&#39;data&#39;)})
      .on(&#39;end&#39;,()=&amp;gt;{
        let string = &#39;&#39;;
        results.forEach(r=&amp;gt;{
          string += `INSERT INTO countries (Id,Country,Population,Leader) VALUES (${r[&#39;id&#39;]},&#39;${r[&#39;country&#39;]}&#39;,${r[&#39;population&#39;]},&#39;${r[&#39;leader&#39;]}&#39;);`
        })
    })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then in git bash you can run your script and print it into an SQL file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    node.exe script.js &amp;gt;&amp;gt; script.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our JS script is less ugly compared to the awk script, but it’s still pretty ugly and took more set up. Still, Javascript is so ubiquitous that any developer would be able to do this.&lt;/p&gt;
&lt;h2 id=&quot;other-methods&quot; tabindex=&quot;-1&quot;&gt;Other methods &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/ways-of-getting-data-from-excel-files-into-a-database/#other-methods&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Do you know quicker/better ways than these to get data from excel files into excel?&lt;/p&gt;
&lt;p&gt;Get in touch on &lt;a href=&quot;https://bsky.app/profile/elliotclyde.bsky.social&quot;&gt;Bluesky&lt;/a&gt; or &lt;a href=&quot;https://mastodon.nz/@Elliotclyde&quot;&gt;Mastodon&lt;/a&gt; and I’ll update this post with your method and give you a shout out.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>What&#39;s in a PDF</title>
    <link href="https://www.elliotclyde.nz/blog/whats-in-a-pdf/"/>
    <updated>2024-03-11T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/whats-in-a-pdf/</id>
    <content type="html">&lt;p&gt;If you’ve been a computer person long enough, then you’ll eventually come to terms with the fact that all the files on a computer are made up of the same ingredients: lists of bytes. Whether you save a text file or a movie or an executable on your PC, the circuitry in the storage drive has no idea what it is. Even the operating system is just guessing based on context. If you give a file the correct extension, and it hasn’t been corrupted, the operating system knows what to do with it. &lt;a href=&quot;https://blog.jim-nielsen.com/2024/more-files-plz/&quot;&gt;Jim Nielsen has a good write up about the reusability of files on his blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Technically you could create every file you’ve ever used by writing out raw hexidecimals and using the bash  &lt;code&gt;xxd&lt;/code&gt; command to convert it into binary. If you’re on windows like me, you can use git bash. Let’s make an ASCII text file by writing some hexidecimal by smashing this into a terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    echo &#39;4865 6c6c 6f20 776f 726c 6421&#39; | xxd -r -p &amp;gt; test.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you open the new &lt;code&gt;test.txt&lt;/code&gt; file with a text editor, you should see a text file saying “hello world!”. You can use &lt;a href=&quot;https://en.wikipedia.org/wiki/ASCII#Character_set&quot;&gt;this chart&lt;/a&gt; to write out any ASCII letter you want. Congratulations, you now have an extremely tedious way to create a text file. &lt;a href=&quot;https://en.wikipedia.org/wiki/UTF-8&quot;&gt;UTF-8&lt;/a&gt; is a bit more complicated as it has almost every written language and every emoji.&lt;/p&gt;
&lt;p&gt;However you can print a skull emoji like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     echo &#39;f0 9f 92 80&#39; | xxd -r -p &amp;gt; test10.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are ways to create text files. But binary files are more of a mystery. They are files whose secrets are known by the programs that read them, spec-writers who define them, and those who are willing to go through the pain-staking work of reverse-engineering them.&lt;/p&gt;
&lt;p&gt;We’re going to take a look at a type of binary file: the pdf. Parts of the pdf format are actually understandable as ASCII text, and we’re going to write a PDF from scratch to see what&#39;s in there. Beware though, writing a pdf from scratch is a tough task and certainly isn&#39;t like writing a programming language designed to be easy to edit with a text editor.&lt;/p&gt;
&lt;p&gt;Let’s take a look at the sections of a PDF:&lt;/p&gt;
&lt;h2 id=&quot;sections-of-a-pdf&quot; tabindex=&quot;-1&quot;&gt;Sections of a PDF &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/whats-in-a-pdf/#sections-of-a-pdf&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are always 4 sections to every pdf: first the header, then the body, then the cross reference table and finally the footer. We’re going to go in a weird order: The body first, then the header, then the cross reference table, and finally the footer. This is because the body is where the most interesting and important stuff is.&lt;/p&gt;
&lt;h2 id=&quot;body&quot; tabindex=&quot;-1&quot;&gt;Body &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/whats-in-a-pdf/#body&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The body of a PDF holds the parts that will actually be visible in the PDF, as opposed to boilerplate and metadata. The PDF body holds the text, pages, images, fonts, annotations and form controls. There’s a whole smorgasbord of other stuff you can include too.&lt;/p&gt;
&lt;p&gt;Different elements are structured as a hierarchical tree of “PDF objects”. PDF objects are written in a syntax like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    1 0 obj
    &amp;lt;&amp;lt;
      /Type /Catalog
      /Pages 2 0 R
    &amp;gt;&amp;gt;
    endobj
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s have a look at what we’ve written: First, we have a couple of numbers. The first number is a unique identifier for the object (in this case 1), the second is the version of the object (in this case 0). The version number is used for change tracking. We’re going to leave the version as 0 for all objects in this article. After these numbers we have &lt;code&gt;obj&lt;/code&gt;. This tells the PDF reader we’re about to write a PDF object definition.&lt;/p&gt;
&lt;p&gt;The next section is wrapped in a pair of less than &lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt; and greater than &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; signs. This denotes that we are about to write a dictionary of key/value pairs. This is a bit like a &lt;code&gt;json&lt;/code&gt; object or a &lt;code&gt;C#&lt;/code&gt; dictionary or a &lt;code&gt;java&lt;/code&gt; hashmap. The order does not matter here, just keys and values.&lt;/p&gt;
&lt;p&gt;In the above example we have two keys: &lt;code&gt;Type&lt;/code&gt; and &lt;code&gt;Pages&lt;/code&gt;. In this case the value of type is &lt;code&gt;Catalog&lt;/code&gt;. This is the type we use for the &lt;a href=&quot;https://www.oreilly.com/library/view/pdf-explained/9781449321581/ch04.html#I_sect14_d1e2865&quot;&gt;root object of a PDF document&lt;/a&gt;, as explained in the &lt;a href=&quot;https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf&quot;&gt;PDF standard&lt;/a&gt;. The value of the pages key is a reference to another object. It points to another object just like the one we’ve written. The two numbers after the &lt;code&gt;/Pages&lt;/code&gt; key, &lt;code&gt;2&lt;/code&gt; and &lt;code&gt;0&lt;/code&gt; will match the unique identifier and version respectively. Then a capital R confirms that this is referencing another object.&lt;/p&gt;
&lt;p&gt;Does any of the whitespace matter? The newlines and spaces? No. They will make a difference to the cross reference table later but that’s getting ahead of ourselves.&lt;/p&gt;
&lt;p&gt;We finally close off our dictionary with two greater-than signs then complete our object with &lt;code&gt;endobj&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pages&lt;/strong&gt;&lt;br /&gt;
We’ve actually written a great start to a pdf document already. But that &lt;code&gt;/Pages&lt;/code&gt; key is referencing a PDF object that doesn’t exist. So now let’s write a “pages” object for it to point to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    2 0 obj
    &amp;lt;&amp;lt;
     /Type /Pages
     /Count 1
     /Kids [
      3 0 R
     ]
    &amp;gt;&amp;gt;
    endobj
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, we start with the unique identifier, version and &lt;code&gt;obj&lt;/code&gt; keyword. The identifier and version have to match the values we wrote in the &lt;code&gt;pages&lt;/code&gt; key in the catalog object.&lt;/p&gt;
&lt;p&gt;Just like the catalog object, we say it’s of &lt;code&gt;/Type&lt;/code&gt;  &lt;code&gt;/Pages&lt;/code&gt;. Then we have a new concept: an array! A list of things! In this case the &lt;code&gt;/Kids&lt;/code&gt; key holds a list of references to page objects. We also include a count to say how many pages there will be (because there’s no way software would be able to figure that out from the number of references 🤔).&lt;/p&gt;
&lt;p&gt;We’re only going to make one page but for demonstration purposes if we wanted three pages then we’d have something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /Count 3
    /Kids [
      3 0 R
      4 0 R
      5 0 R
     ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The array is actually the 4th data type we’ve seen.  These are the data types we’ve seen so far:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;name objects (these are the keys with a forward slash before them)&lt;/li&gt;
&lt;li&gt;dictionaries&lt;/li&gt;
&lt;li&gt;numbers&lt;/li&gt;
&lt;li&gt;arrays&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The other pdf data types are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;booleans&lt;/li&gt;
&lt;li&gt;strings&lt;/li&gt;
&lt;li&gt;null&lt;/li&gt;
&lt;li&gt;streams (this is where we’re going to put all our text and where you’d put your images)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These make up everything you can put into the body section of a pdf.&lt;br /&gt;
&lt;strong&gt;Page object&lt;/strong&gt;&lt;br /&gt;
This is what our page object will look like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    3 0 obj
    &amp;lt;&amp;lt;
     /Type /Page
     /Parent 2 0 R
     /MediaBox [
      0
      0
      612
      792
     ]
      /Resources &amp;lt;&amp;lt;
        /Font &amp;lt;&amp;lt;
         /Helv &amp;lt;&amp;lt;
          /Type /Font
          /Subtype /Type1
          /BaseFont /Helvetica
         &amp;gt;&amp;gt;
        &amp;gt;&amp;gt;
       &amp;gt;&amp;gt;
     /Contents [
      4 0 R
     ]
    &amp;gt;&amp;gt;
    endobj
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again we start with our object an ID and version, then inside the dictionary we give it a type. We also hold a reference to the parent (the pages object from earlier).&lt;/p&gt;
&lt;p&gt;Now we actually have a definition of something visual: the size of the page with the &lt;code&gt;/MediaBox&lt;/code&gt; array! First with the x and y coordinates of the lower left corner with 0, 0, then the coordinates of the upper right corner with 612, 792. PDF measures size from left to right, which makes sense to me being a native English reader, however it also measures from bottom to top.&lt;/p&gt;
&lt;p&gt;We could stop here and we’d have a blank page (after adding the header, cross-reference table and trailer), but let’s put something on the page. To do this we’re first going to add a resources object which will describe the helvetica font as above.&lt;/p&gt;
&lt;p&gt;Then we’ll continue down our chain of references, this time pointing to an object reference in the &lt;code&gt;/Contents&lt;/code&gt; key. Let’s define this object now:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Contents object&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here’s what our next object looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    4 0 obj
    &amp;lt;&amp;lt;
     /Length 53
    &amp;gt;&amp;gt;
    stream
    1 0 0 1 72 708 cm BT /Helv 12 Tf (Hello world!) Tj ET
    endstream
    endobj
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It starts pretty normal, with the identifier and dictionary. Here we define a length. This is the length in bytes of the stream, which, if we keep it simple and use ASCII, will be one byte per character. This will count all the characters in between the newline character after the &lt;code&gt;stream&lt;/code&gt; keyword and the newline character before the endstream keyword. These newline characters are not counted, but any spaces or tabs or &lt;em&gt;other&lt;/em&gt; newline characters between these keywords will be counted in the length. The length does include any spaces or other whitespace.&lt;/p&gt;
&lt;p&gt;As you can see, we throw these stream keywords after the dictionary.  What is in this stream? First we write the scale and skew of the text. &lt;code&gt;1 0 0 1&lt;/code&gt; is the section defining this and tells it not to rotate, skew, or scale the text in any way. Have a play with these numbers if you want to see the effect each of them on screwing around with text. I want normal text so I’m gonna leave it as &lt;code&gt;1 0 0 1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then we place the text: 72 cm left and 708 cm up (that’s centimetres if you’re an imperial measurement person). The &lt;code&gt;BT&lt;/code&gt; keyword means we’re starting to define some text content. We specify the font, the font size and then use &lt;code&gt;TF&lt;/code&gt; to finish of the font declaration (this is reverse Polish notation, where you write your two arguments, then name the function, we’re doing the same thing with the object identifier syntax).&lt;/p&gt;
&lt;p&gt;Inside some parentheses we write the actual text we want on the page. Then writing &lt;code&gt;Tj&lt;/code&gt; will pass this text to the &lt;code&gt;Tj&lt;/code&gt; function, which will take the string and paint the glyphs. We write &lt;code&gt;ET&lt;/code&gt; to end the text and we’re done!&lt;/p&gt;
&lt;p&gt;This is the body of our pdf completed!&lt;/p&gt;
&lt;h2 id=&quot;header&quot; tabindex=&quot;-1&quot;&gt;Header &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/whats-in-a-pdf/#header&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The header of the pdf is really easy. It basically always looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    %PDF-1.7
    %âãÏÓ
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First we define that it’s a pdf, define the version of the spec, then there’s some gibberish letters to try and convince people not to do exactly what we’re doing right now and dig into the pdf. As far as I have read, you don’t actually have to put this exact set of gibberish in there, but these characters are standard so it’s what we’ll do.&lt;/p&gt;
&lt;h2 id=&quot;cross-reference-table&quot; tabindex=&quot;-1&quot;&gt;Cross-reference table &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/whats-in-a-pdf/#cross-reference-table&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The cross-reference table is quite interesting. Now we’re really getting into something that looks like a binary file.&lt;/p&gt;
&lt;p&gt;The cross reference table goes after the body, and is a table that software will use to hold the byte address of different objects inside the PDF. Assuming a PDF is all ASCII, it’s basically a count of how many characters (including newlines) before the start of each object.&lt;/p&gt;
&lt;p&gt;This is how ours is going to look:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    xref
    0 5
    0000000000 65535 f 
    0000000015 00000 n 
    0000000068 00000 n 
    0000000133 00000 n
    0000000374 00000 n
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we’re getting into some quite hardcore binary looking stuff.&lt;/p&gt;
&lt;p&gt;First we start with the xref keyword to announce that we’re starting the cross-reference table. Then we have two numbers. The first is the “generation number” - used for versioning, we’re just going to leave it as zero. The second is the number of objects in our cross reference table. We only made 4 pdf objects but I wrote a 5. What gives?&lt;/p&gt;
&lt;p&gt;We always add one extra placeholder object to the start of the xref table. It’s a way to indicate the start of the table.&lt;/p&gt;
&lt;p&gt;After declaring the generation and number of objects in the table we start the table. Each row of the table has 3 columns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The byte-offset from the start of the file to the object&lt;/li&gt;
&lt;li&gt;The generation number of the object&lt;/li&gt;
&lt;li&gt;Either an &lt;code&gt;n&lt;/code&gt; to say the object is in use or a &lt;code&gt;f&lt;/code&gt; to say the object is free and doesn’t actually matter. I’m not sure what the &lt;code&gt;n&lt;/code&gt; actually stands for, but let’s pretend it stands for “now-in-use” or “not-retired”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s important that you left-pad the numbers with zeroes to keep all the items in the table the exact length above.&lt;/p&gt;
&lt;p&gt;Here’s our first placeholder object, which will always look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    0000000000 65535 f  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second object points to the object in our pdf with an ID of 1:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    0000000015 00000 n 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The start of the first pdf object (our catalog object, specifically at the point we gave it an ID of 1) is 17 characters from the start of the file, hence the first number in the xref table is 15. This is the byte-offset of the object. We aren’t bothering with generations, so the second number is 0. Finally, this object &lt;em&gt;is&lt;/em&gt; in use, so we add an &lt;code&gt;n&lt;/code&gt; to indicate it’s in-use. We need to do this with all the objects in the pdf.&lt;/p&gt;
&lt;h2 id=&quot;getting-the-byte-offset-of-a-pdf-section&quot; tabindex=&quot;-1&quot;&gt;Getting the byte offset of a PDF section &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/whats-in-a-pdf/#getting-the-byte-offset-of-a-pdf-section&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can use grep to get the byte offset of an object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    grep --byte-offset --text $&#39;1 0 obj&#39; mypdf.pdf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where you replace the number “1” in the code above with the ID of the object you want the offset for.&lt;/p&gt;
&lt;h2 id=&quot;trailer&quot; tabindex=&quot;-1&quot;&gt;Trailer &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/whats-in-a-pdf/#trailer&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We’re almost there! We’re at the last section of our PDF file.&lt;/p&gt;
&lt;p&gt;This is what our trailer will look like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    trailer
    &amp;lt;&amp;lt;
     /Root 1 0 R
     /Size 5
    &amp;gt;&amp;gt;
    startxref
    491
    %%EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We announce it’s a trailer, then we enter another dictionary with a paid of less-than signs.&lt;/p&gt;
&lt;p&gt;This dictionary holds 2 keys:&lt;code&gt;/Root&lt;/code&gt; and &lt;code&gt;/Size&lt;/code&gt;. The root is a reference to the catalog object. The value of size is the number of rows in the cross-reference table.&lt;/p&gt;
&lt;p&gt;Finally, after we’ve closed off the dictionary, we describe the byte-offset of the start of the cross-reference table. We don’t actually want the “xref” keyword but the offset of the first object of the table so using the grep script from before to grab byte-offsets we can use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    grep --byte-offset --text &#39;0000000000&#39; mypdf.pdf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, finally to confirm we’re done we add an end of file marker with a couple of percent signs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    %%EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-whole-file&quot; tabindex=&quot;-1&quot;&gt;The whole file &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/whats-in-a-pdf/#the-whole-file&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is what the entire pdf should look like. Save this in a text file as a pdf, and if nothing’s gone weird with encoding, you should have a fully functional pdf:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    %PDF-1.7
    %âãÏÓ
    1 0 obj
    &amp;lt;&amp;lt;
      /Type /Catalog
      /Pages 2 0 R
    &amp;gt;&amp;gt;
    endobj
    2 0 obj
    &amp;lt;&amp;lt;
     /Type /Pages
     /Count 1
     /Kids [
      3 0 R
     ]
    &amp;gt;&amp;gt;
    endobj
    3 0 obj
    &amp;lt;&amp;lt;
     /Type /Page
     /Parent 2 0 R
     /MediaBox [
      0
      0
      612
      792
     ]
      /Resources &amp;lt;&amp;lt;
        /Font &amp;lt;&amp;lt;
         /Helv &amp;lt;&amp;lt;
          /Type /Font
          /Subtype /Type1
          /BaseFont /Helvetica
         &amp;gt;&amp;gt;
        &amp;gt;&amp;gt;
       &amp;gt;&amp;gt;
     /Contents [
      4 0 R
     ]
    
    &amp;gt;&amp;gt;
    endobj
    4 0 obj
    &amp;lt;&amp;lt;
     /Length 53
    &amp;gt;&amp;gt;
    stream
    1 0 0 1 72 708 cm BT /Helv 12 Tf (Hello world!) Tj ET
    endstream
    endobj
    xref
    0 5
    0000000000 65535 f 
    0000000015 00000 n 
    0000000068 00000 n 
    0000000133 00000 n
    0000000374 00000 n
    trailer
    &amp;lt;&amp;lt;
     /Root 1 0 R
     /Size 5
    &amp;gt;&amp;gt;
    startxref
    491
    %%EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I don’t know about other editors, but in vim you can force it to treat the text as ASCII to make sure this saves with correct byte offsets by using these settings in vim:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    set encoding=latin1
    set isprint=
    set display+=uhex
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So after you set these settings in vim, you can paste the above file then save, then you should have a working PDF file. Hooray!&lt;/p&gt;
&lt;p&gt;A good way to absolutely verify things are correct is to use the &lt;a href=&quot;https://github.com/pdfcpu/pdfcpu&quot;&gt;GitHub - pdfcpu/pdfcpu: A PDF processor written in Go&lt;/a&gt; to verify your PDF with the “pdfcpu validate mypdf.pdf” command.&lt;/p&gt;
&lt;h2 id=&quot;where-to-go-from-here&quot; tabindex=&quot;-1&quot;&gt;Where to go from here &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/whats-in-a-pdf/#where-to-go-from-here&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Daniel Warren wrote a &lt;a href=&quot;https://blog.idrsolutions.com/make-your-own-pdf-file-part-1-pdf-objects-and-data-types/&quot;&gt;similar blog post to this&lt;/a&gt; back in 2010 (14 years ago 👴). You should check out.&lt;/li&gt;
&lt;li&gt;You can get the &lt;a href=&quot;https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf&quot;&gt;full PDF standard from adobe&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>OIDC Authentication in server-side Blazor apps</title>
    <link href="https://www.elliotclyde.nz/blog/oidc-authentication-in-server-side-blazor-apps/"/>
    <updated>2024-07-17T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/oidc-authentication-in-server-side-blazor-apps/</id>
    <content type="html">&lt;p&gt;Today, we&#39;re going to create a server-side Blazor app that uses Azure OIDC to authenticate. You’ll need the &lt;a href=&quot;https://dotnet.microsoft.com/en-us/download/dotnet?cid=getdotnetcorecli&quot;&gt;DotNetCore SDK&lt;/a&gt;. I’m going to be using DotNetCore 6 - the oldest possible version that&#39;s still supported.&lt;/p&gt;
&lt;p&gt;First I’m going to set things up. I’m not going to go into a whole lot of detail until we get into authorization section, so if this is your first time creating a DotNetCore app, you might want to find a more introductory post. I&#39;ll also be using the command line and VSCode, so this may not be for Visual Studio fans.&lt;/p&gt;
&lt;p&gt;Let’s start by creating a new server-side blazor app:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet new blazorserver -o BlazorServerApp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;code&gt;cd&lt;/code&gt; into the app, then there’s a command you might need to run to get the nuget install going:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    cd BlazorServerApp
    dotnet nuget add source --name nuget.org https://api.nuget.org/v3/index.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s run the app. I like to use the watch command so the server will reload when I make a change:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    dotnet watch -- run
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Head to &lt;code&gt;https://localhost:7092/&lt;/code&gt; (or whichever port it has decided to host from) and you should be looking at a brand new server-side blazor app.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-azure-as-an-oidc-provider&quot; tabindex=&quot;-1&quot;&gt;Setting up Azure as an OIDC provider &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-server-side-blazor-apps/#setting-up-azure-as-an-oidc-provider&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now let’s create an OIDC server with Azure. Basically we’re going to follow &lt;a href=&quot;https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings&quot;&gt;this blogpost’s instructions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We’re going to create an app registration. You’ll need to have some kind of elevated permissions to do this.&lt;/p&gt;
&lt;p&gt;Head to the Azure portal, then we’re going to go to &lt;a href=&quot;https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/RegisteredApps&quot;&gt;Microsoft Entra app registration&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Give your app a name - I’m going to call it “blazorserverapp”. After it’s created, head to “clients and secrets” and create a new client secret. Grab the secret and the client ID and keep these in a text file for now.&lt;/p&gt;
&lt;p&gt;You’ll also need to add a redirect URL so that Azure knows where your app is based and isn’t going to send a load of private information to someone else’s domain. Click into “Authentication” then under “redirect URIs”, add &lt;code&gt;https://localhost:7102/signin-oidc&lt;/code&gt;. We want our site’s address with &lt;code&gt;/signin-oidc&lt;/code&gt; at the end. You can add multiple URLs for development and production if you need.&lt;/p&gt;
&lt;h2 id=&quot;adding-openidconnect-to-your-blazor-app&quot; tabindex=&quot;-1&quot;&gt;Adding OpenIDConnect to your Blazor app &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-server-side-blazor-apps/#adding-openidconnect-to-your-blazor-app&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now, server-side blazor doesn&#39;t link super well with the OpenIDConnect package. This package expects that client and server will be communicating using HTTP, which isn&#39;t the protocol that server-side blazor uses. Instead, &lt;a href=&quot;https://devblogs.microsoft.com/dotnet/blazor-0-5-0-experimental-release-now-available/#what-is-server-side-blazor-&quot;&gt;Server-side Blazor uses a protocol called SignalR&lt;/a&gt;. Don&#39;t fear though! We&#39;re going to (partly) follow the methods of &lt;a href=&quot;https://stackoverflow.com/a/59672926&quot;&gt;this stack overflow answer&lt;/a&gt; to get around these issues.&lt;/p&gt;
&lt;p&gt;First let’s install the OpenIdConnect package:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running DotNet 6 means I needed to add this to install an older version:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect --version 6.0.2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s start by updating our &lt;code&gt;Startup.cs&lt;/code&gt; file to configure our app to use the OpenIdConnect authentication package. First add the using statement at the top:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    using Microsoft.AspNetCore.Authentication.OpenIdConnect;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then anywhere before the &lt;code&gt;app = builder.Build();&lt;/code&gt; statement add in this to register the authentication:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    builder.Services.AddAuthentication(options =&amp;gt;
    {
        options.DefaultScheme = &amp;quot;Cookies&amp;quot;;
        options.DefaultChallengeScheme = &amp;quot;oidc&amp;quot;;
    })
    .AddCookie(&amp;quot;Cookies&amp;quot;)
    .AddOpenIdConnect(&amp;quot;oidc&amp;quot;, options =&amp;gt;
    {
        options.Authority = &amp;quot;https://login.microsoftonline.com/mytenant/v2.0/&amp;quot;;
        options.ClientId = &amp;quot;my client id&amp;quot;; 
        options.ClientSecret = &amp;quot;my client secret&amp;quot;;
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then &lt;em&gt;after&lt;/em&gt; the &lt;code&gt;app = builder.Build();&lt;/code&gt; statement, and before &lt;code&gt;app.UseRouting();&lt;/code&gt;, we tell it to use authentication in the middleware pipeline:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    app.UseAuthentication();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This sets up the authentication. Now when we we start the app the OpenIdConnect middleware will be listening on the &lt;code&gt;/signin-oidc&lt;/code&gt; endpoint for when Azure sends back user information.&lt;/p&gt;
&lt;p&gt;There will still be no difference to the app until we start to configure which pages are public and which pages only authenticated users can see.&lt;/p&gt;
&lt;h2 id=&quot;hiding-pages-behind-authentication&quot; tabindex=&quot;-1&quot;&gt;Hiding pages behind authentication &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-server-side-blazor-apps/#hiding-pages-behind-authentication&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;re going to update the &lt;code&gt;App.razor&lt;/code&gt;  file to check whether the user is logged in and redirect them to a login page if not:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;!-- App.razor --&amp;gt;
    @inject NavigationManager NavigationManager
    &amp;lt;CascadingAuthenticationState&amp;gt;
    &amp;lt;Router AppAssembly=&amp;quot;@typeof(Program).Assembly&amp;quot;&amp;gt;
        &amp;lt;Found Context=&amp;quot;routeData&amp;quot;&amp;gt;
            &amp;lt;AuthorizeRouteView RouteData=&amp;quot;@routeData&amp;quot; DefaultLayout=&amp;quot;@typeof(MainLayout)&amp;quot;&amp;gt;
                &amp;lt;NotAuthorized&amp;gt;
                    @{
                        
                        var returnUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
                        NavigationManager.NavigateTo($&amp;quot;login?redirectUri={returnUrl}&amp;quot;, forceLoad: true);
                    }
                &amp;lt;/NotAuthorized&amp;gt;
                &amp;lt;Authorizing&amp;gt;
                    Wait...
                &amp;lt;/Authorizing&amp;gt;
            &amp;lt;/AuthorizeRouteView&amp;gt;
        &amp;lt;/Found&amp;gt;
        &amp;lt;NotFound&amp;gt;
            &amp;lt;LayoutView Layout=&amp;quot;@typeof(MainLayout)&amp;quot;&amp;gt;
                &amp;lt;p&amp;gt;Sorry, there&#39;s nothing at this address.&amp;lt;/p&amp;gt;
            &amp;lt;/LayoutView&amp;gt;
        &amp;lt;/NotFound&amp;gt;
    &amp;lt;/Router&amp;gt;
    &amp;lt;/CascadingAuthenticationState&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will only redirect for pages where there is an &lt;code&gt;@attribute [Authorize]&lt;/code&gt; on the page. All pages without this attribute will remain open to the public. Let’s update our &lt;code&gt;index.razor&lt;/code&gt; page to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    @page &amp;quot;/&amp;quot;
    @using Microsoft.AspNetCore.Components.Authorization
    @attribute [Authorize]
    
    &amp;lt;PageTitle&amp;gt;Index&amp;lt;/PageTitle&amp;gt;
    
    &amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;
    Welcome to your new app.
    &amp;lt;SurveyPrompt Title=&amp;quot;How is Blazor working for you?&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run the app and head to the home page now, you’ll get redirected to a login URL in our site which does not exist.&lt;/p&gt;
&lt;h2 id=&quot;redirecting-users-to-the-oidc-provider&quot; tabindex=&quot;-1&quot;&gt;Redirecting users to the OIDC provider &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-server-side-blazor-apps/#redirecting-users-to-the-oidc-provider&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let’s create the endpoint the unauthenticated user will get redirected to. We use this endpoint to forward the user to our OIDC provider. Write a &lt;code&gt;login.cshtml&lt;/code&gt; file in the &lt;code&gt;Pages&lt;/code&gt;  folder of the app (&lt;code&gt;cshtml&lt;/code&gt;  files get server-rendered and aren’t part of the blazor app):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    @page
    &amp;lt;!-- Login.cshtml --&amp;gt;
    @model LoginModel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s all we need in the &lt;code&gt;login.cshtml&lt;/code&gt; file, but now we add the model file (&lt;code&gt;Login.cshtml.cs&lt;/code&gt;) in the same directory to define the logic after a request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // Login.cshtml.cs
    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Mvc.RazorPages;
    
    public class LoginModel : PageModel
    {
        public async Task OnGet(string redirectUri)
        {
            if (!User.Identity.IsAuthenticated){
                await HttpContext.ChallengeAsync(&amp;quot;oidc&amp;quot;, new 
                    AuthenticationProperties { RedirectUri = redirectUri } );
            }
            else{
                // redirects user to redirectUri if they are already logged in
                if (redirectUri == null)
                {
                    redirectUri = &amp;quot;/&amp;quot;;
                }
                Response.Redirect(redirectUri);
            }
        }  
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;HttpContext.ChallengeAsync&lt;/code&gt; function is the part which does the work, redirecting the user to Azure if they are not logged on.&lt;/p&gt;
&lt;p&gt;Now after adding this and restarting your app, you should get redirected to Azure to login, then get redirected back to your application again. If you go through this process then look in the “Application” section of your browser’s dev tools you should notice that there are new cookies associated with your app. This is where your session is being kept track of.&lt;/p&gt;
&lt;h2 id=&quot;using-claims-from-oidc-in-your-app&quot; tabindex=&quot;-1&quot;&gt;Using claims from OIDC in your app &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-server-side-blazor-apps/#using-claims-from-oidc-in-your-app&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;How do we get access to user data we’ve received from Azure?&lt;/p&gt;
&lt;p&gt;We can use blazor&#39;s AuthenticationStateProvider class. This can be injected into our blazor components to get accessnto the logged in user. This class will allow us to get a handle on all the claims we’re getting back from Azure. To demostrate this, update your &lt;code&gt;index.razor&lt;/code&gt; file to look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    @page &amp;quot;/&amp;quot;
    @using Microsoft.AspNetCore.Components.Authorization
    @using System.Security.Claims
    @attribute [Authorize]
    
    &amp;lt;PageTitle&amp;gt;Index&amp;lt;/PageTitle&amp;gt;
    &amp;lt;h1&amp;gt;Hello, @User.Identity.Name!&amp;lt;/h1&amp;gt;
    &amp;lt;ul&amp;gt;
        @foreach (var claim in User.Claims)
        {
            &amp;lt;li&amp;gt;
                &amp;lt;strong&amp;gt;@claim.Type:&amp;lt;/strong&amp;gt; @claim.Value
            &amp;lt;/li&amp;gt;
        }
    &amp;lt;/ul&amp;gt;
    
    Welcome to your new app.
    &amp;lt;SurveyPrompt Title=&amp;quot;How is Blazor working for you?&amp;quot; /&amp;gt;
    @code{
      @inject AuthenticationStateProvider AuthenticationStateProvider
      
      @code {
          protected ClaimsPrincipal User;
          protected override async Task OnInitializedAsync()
          {
              var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
              User = authState.User;
          }
      }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will say hello to the user, pulling their name from the user identity data we get back from Azure, and will also list all of the other claims it receives. An important claim is &lt;code&gt;preferred_username&lt;/code&gt;, which in my case was my email address - this could be used to link the logged in user to data in your database.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot; tabindex=&quot;-1&quot;&gt;Wrapping up &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-server-side-blazor-apps/#wrapping-up&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is a small start, but from here the sky is the limit. You don’t have to manage authentication yourself. Your users don’t have to create another password. But after they log in, you know who they are and you can create logic around what they can see and do.&lt;/p&gt;
&lt;p&gt;Where to from here? You can look into configuring Azure to send different claims. These can be used to decide what the user is allowed to see; You can implement claims-based authorization on your pages. Or if there is data in your application that should remain hidden, you can write logic to hide it from users without certain claims.&lt;/p&gt;
&lt;p&gt;If you’re a blazor expert and you have more tips about OIDC authentication to server-side blazor apps, let me know! I’m on &lt;a href=&quot;https://mastodon.nz/@Elliotclyde&quot;&gt;Mastodon&lt;/a&gt; and &lt;a href=&quot;https://bsky.app/profile/elliotclyde.bsky.social&quot;&gt;Bluesky&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;other-resources&quot; tabindex=&quot;-1&quot;&gt;Other resources &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-server-side-blazor-apps/#other-resources&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-8.0&quot;&gt;Authentication in DotNetCore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.microsoft.com/en-us/security/business/security-101/what-is-openid-connect-oidc&quot;&gt;What is OpenID Connect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.microsoft.com/en-us/security/business/security-101/what-is-oauth&quot;&gt;What is OAuth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/signalr/overview/getting-started/introduction-to-signalr&quot;&gt;Introduction to SignalR&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>OIDC Authentication in ASP.NET Core apps explained</title>
    <link href="https://www.elliotclyde.nz/blog/oidc-authentication-in-asp-net-core-apps-explained/"/>
    <updated>2024-10-14T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/oidc-authentication-in-asp-net-core-apps-explained/</id>
    <content type="html">&lt;p&gt;In the times that I’ve had to implement Single Sign On authentication for &lt;a href=&quot;http://asp.net/&quot;&gt;ASP.NET&lt;/a&gt; Core apps, I’m always trying to do it as quickly as possible. I usually get it done in a single-minded period filled with anger at documentation and bewilderment around what I’ve configured incorrectly, then eventually I’ll achieve a signed in user (yay). The information online about OpenIDConnect in &lt;a href=&quot;http://asp.net/&quot;&gt;ASP.NET&lt;/a&gt; Core is either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2 lines telling you to call a couple of methods with no context or explanation; or&lt;/li&gt;
&lt;li&gt;A meandering novel filled with technical jargon&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This post is here to help you avoid what I’ve been through. It’s here to help you reason about what’s going on when you use OIDC authentication in &lt;a href=&quot;http://asp.net/&quot;&gt;ASP.NET&lt;/a&gt; core.&lt;/p&gt;
&lt;h2 id=&quot;what-is-oidc%3F&quot; tabindex=&quot;-1&quot;&gt;What is OIDC? &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-asp-net-core-apps-explained/#what-is-oidc%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OIDC, or OpenIDConnect is a protocol for Single Sign On. It allows everyone in your business to have a username and password with the OIDC provider, and they don’t need to remember a separate username and password for your special snowflake app (and the other 40 special snowflake apps they have to use every day at the business).&lt;/p&gt;
&lt;p&gt;Some definitions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The OIDC provider is a 3rd party - eg. Microsoft Azure, Google, Okta etc&lt;/li&gt;
&lt;li&gt;The OIDC client is your app&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OIDC is built on top of another protocol called OAuth. OAuth is also for communication between apps, but it’s for a user to be able to give your app permissions to access something in another app, whereas OIDC is designed to tell your app who the user is. OAuth is about authorization, OIDC is about authentication. You can find more about the difference on &lt;a href=&quot;https://nat.sakimura.org/2011/05/15/dummys-guide-for-the-difference-between-oauth-authentication-and-openid/&quot;&gt;Nat Sakimura’s blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The OIDC provider tells our app who the user is by sending a post request to our app with url-encoded form data. This data will include an &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference&quot;&gt;ID token, which is a JWT: a Base64 encoded string of JSON providing information about the user&lt;/a&gt;. You can read more on the &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc#successful-response&quot;&gt;Microsoft documentation about what the ID token format looks like.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Let’s create a web app that uses OIDC Single Sign On. I’m going to first provide instructions to create the app, then I’ll go deeper into what we’ve done:&lt;/p&gt;
&lt;h2 id=&quot;setting-up-azure-as-an-oidc-provider&quot; tabindex=&quot;-1&quot;&gt;Setting up Azure as an OIDC provider &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-asp-net-core-apps-explained/#setting-up-azure-as-an-oidc-provider&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First set up an OIDC provider. This isn’t the focus of this post but in my last post, I wrote a bit about &lt;a href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-server-side-blazor-apps/#adding-openidconnect-to-your-blazor-app&quot;&gt;how to set up azure as your OIDC provider&lt;/a&gt;. We basically just follow &lt;a href=&quot;https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings&quot;&gt;this handy tutorial from the Microsoft docs&lt;/a&gt;. After you’ve set it up, you’ll need your tenant ID, client ID, and client secret.&lt;/p&gt;
&lt;h2 id=&quot;scaffolding-a-web-app-with-oidc&quot; tabindex=&quot;-1&quot;&gt;Scaffolding a web app with OIDC &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-asp-net-core-apps-explained/#scaffolding-a-web-app-with-oidc&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let’s scaffold an &lt;a href=&quot;http://asp.net/&quot;&gt;ASP.NET&lt;/a&gt; core web app. We’ll create an MVC application. To do this, you’ll need the &lt;a href=&quot;https://dotnet.microsoft.com/en-us/download&quot;&gt;DotNetCore SDK&lt;/a&gt; installed. Navigate to a new project folder and run this in the command line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    dotnet new mvc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you should be able to start your app:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    dotnet run
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will print off a URL where you will be able to see your app in action. You’ll need to update whatever URL you see here with your OIDC provider - with &lt;code&gt;signin-oidc&lt;/code&gt; as the relative path. This is so the provider knows where to redirect back to on sign in. Now let’s install the OpenIDConnect package:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;ll add a &lt;code&gt;using&lt;/code&gt; statement to the top of our &lt;code&gt;Program.cs&lt;/code&gt; file to import this package. We’re also going to import the cookies authentication namespace (you don’t need to install any more packages to get this):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // Program.cs
    using Microsoft.AspNetCore.Authentication.OpenIdConnect;
    using Microsoft.AspNetCore.Authentication.Cookies;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then anywhere before the &lt;code&gt;app = builder.Build();&lt;/code&gt;statement in &lt;code&gt;Program.cs&lt;/code&gt;, add the following lines of code to register our authentication. If you’re using Azure Active Directory, this where you add your tenant ID, your client ID and your client secret:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // Program.cs
        builder.Services.AddAuthentication(options =&amp;gt;
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddCookie()
        .AddOpenIdConnect(options =&amp;gt;
        {    
            // Replace mytenantid with your tenant ID
            options.Authority = &amp;quot;https://login.microsoftonline.com/mytenantid/v2.0/&amp;quot;;
    
            // Replace my client id with your client ID
            options.ClientId = &amp;quot;my client id&amp;quot;; 
    
            // Replace my client secret with your client secret
            // This should not be committed to source and would be better living in an
            // appsettings.json file
            options.ClientSecret = &amp;quot;my client secret&amp;quot;;
        });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, after the &lt;code&gt;app.UseStaticFiles();&lt;/code&gt; statement, and before &lt;code&gt;app.UseRouting();&lt;/code&gt;, add the following line to add authentication to the middleware pipeline. The position of this method call matters:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // Program.cs
    app.UseAuthentication();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we’re mostly set up, let’s hide a page from non-authenticated users. Head to your &lt;code&gt;HomeController.cs&lt;/code&gt; file. First we’ll add a using statement at the top to pull in the &lt;code&gt;Authorization&lt;/code&gt; namespace:&lt;/p&gt;
&lt;p&gt;using Microsoft.AspNetCore.Authorization;&lt;/p&gt;
&lt;p&gt;Now add an &lt;code&gt;[authorize]&lt;/code&gt; attribute above the &lt;code&gt;Index&lt;/code&gt; method:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // HomeController.cs
       [Authorize]
        public IActionResult Index()
        {
            return View();
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you hit the index page of your application, you should now be redirected to a Single Sign On page from Microsoft. Hooray!&lt;/p&gt;
&lt;p&gt;We can update the controller and the index page to show data we’ve received back from Azure about who the user is. Here’s how we change our HomeController’s &lt;code&gt;Index&lt;/code&gt; method:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    //HomeController.cs
    
        [Authorize]
        public IActionResult Index()
        {
            ViewData[&amp;quot;name&amp;quot;] = HttpContext.User.Claims.First(c=&amp;gt;c.Type==&amp;quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name&amp;quot;).Value;
            return View();
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here’s how we change our &lt;code&gt;Index.cshtml&lt;/code&gt; page:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    //Index.cshtml
    @{
        ViewData[&amp;quot;Title&amp;quot;] = &amp;quot;Home Page&amp;quot;;
    }
    &amp;lt;div class=&amp;quot;text-center&amp;quot;&amp;gt;
        &amp;lt;h1 class=&amp;quot;display-4&amp;quot;&amp;gt;Welcome @(ViewData[&amp;quot;name&amp;quot;] )&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;Learn about &amp;lt;a href=&amp;quot;https://learn.microsoft.com/aspnet/core&amp;quot;&amp;gt;building Web apps with ASP.NET Core&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
    
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now when you hit your index page and sign in, you should see your name in big letters. We’re going to dive into what is happening under the hood to make this work.&lt;/p&gt;
&lt;h2 id=&quot;what-have-we-done%3F&quot; tabindex=&quot;-1&quot;&gt;What have we done? &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-asp-net-core-apps-explained/#what-have-we-done%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We haven’t added many new lines to our application, but we’ve gone from an app that has no idea who the useris, to one that integrates with Microsoft to get information about the user and can lock unauthenticated usersout on a per-controller-method basis. That’s a lot of heavy lifting we’re getting from the framework and theOIDC library, so let’s get into how this actually works:&lt;/p&gt;
&lt;h2 id=&quot;the-openidconnect-library-and-asp.net-core-cookies-authentication&quot; tabindex=&quot;-1&quot;&gt;The OpenIDConnect library and &lt;a href=&quot;http://asp.net/&quot;&gt;ASP.NET&lt;/a&gt; Core Cookies Authentication &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-asp-net-core-apps-explained/#the-openidconnect-library-and-asp.net-core-cookies-authentication&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of our first steps was to install the &lt;code&gt;Microsoft.AspNetCore.Authentication.OpenIdConnect&lt;/code&gt; package. This package is doing the heavy lifting to get OIDC working. You can find the code for this package &lt;a href=&quot;https://github.com/dotnet/aspnetcore/tree/main/src/Security/Authentication/OpenIdConnect/src&quot;&gt;deep within the AspNetCore source code on github&lt;/a&gt;. We’re also using Microsoft’s cookie authentication package, which you can find in the &lt;a href=&quot;https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Cookies/src/&quot;&gt;AspNetCore github repository&lt;/a&gt;. The cookies package comes included in with the &lt;a href=&quot;http://asp.net/&quot;&gt;ASP.NET&lt;/a&gt; core framework so we don’t have to install anything from nuget to use it. What are these libraries doing?&lt;/p&gt;
&lt;h2 id=&quot;registering-authentication&quot; tabindex=&quot;-1&quot;&gt;Registering Authentication &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-asp-net-core-apps-explained/#registering-authentication&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First we registered our authentication schemes by calling &lt;code&gt;AddAuthentication&lt;/code&gt;. Authentication schemes are different sets of logic for authenticating the user. &lt;a href=&quot;http://asp.net/&quot;&gt;ASP.NET&lt;/a&gt; Core uses a string-based registration system for keeping track of authentication schemes, where it stores a &lt;a href=&quot;https://github.com/dotnet/dotnet/blob/f49747b121ddc20ff6e1a2197ac410601d00196a/src/aspnetcore/src/Http/Authentication.Core/src/AuthenticationSchemeProvider.cs#L48&quot;&gt;dictionary of string-to-AuthetnicationScheme mappings&lt;/a&gt;. There’s a good writeup about Authentication Schemes on &lt;a href=&quot;https://matteosonoio.it/aspnet-core-authentication-schemes/&quot;&gt;Matteo Contrini’s blog&lt;/a&gt;. In our app above we passed it strings for our authentication schemes by setting properties of the &lt;code&gt;[AuthenticationOptions](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationoptions?view=aspnetcore-8.0)&lt;/code&gt; class. We set the default scheme to be cookies and our default challenge scheme to be OpenIDConnect.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;AddAuthentication&lt;/code&gt; when we set the schemes, we were actually just passing in strings, so we could change the &lt;code&gt;addAuthentication&lt;/code&gt; lines in our code above to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;        builder.Services.AddAuthentication(options =&amp;gt;
        {
            options.DefaultScheme = &amp;quot;Cookies&amp;quot;;
            options.DefaultChallengeScheme = &amp;quot;OpenIdConnect&amp;quot;;
        })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this would do the same thing as the earlier code we wrote. The issue might be that the two strings &lt;code&gt;&amp;quot;``Cookies``&amp;quot;&lt;/code&gt; and &lt;code&gt;&amp;quot;``OpenIdConnect``&amp;quot;&lt;/code&gt; for the packages default scheme names might change, so it’s better practice to import the default names from the package defaults.&lt;/p&gt;
&lt;p&gt;Why are there two schemes? One for cookies and one for OIDC? The reason is that most of the work of handling authentication can be done with cookies authentication, but we want the challenge (the point we ask the user to login with username/email and password) to be done by OpenIDConnect. The OIDC package is a specific type of scheme called a &lt;code&gt;RemoteAuthenticationScheme&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This type of scheme is for third-party single sign on, but only handles the challenge, and requires another scheme for persisting the user after they’ve logged in. We can keep information about the logged in user with cookie authentication, which will attach a cookie to the user’s browser to store who they are. This cookie API will also handle making sure that the user isn’t tampering with their cookie to pretend to be someone else.&lt;/p&gt;
&lt;p&gt;Passing a couple of strings into the app builder isn’t enough to fully register our authentication. We also have to execute a couple of extension methods provided by the libraries. First we run the &lt;code&gt;AddCookie&lt;/code&gt; method, then the &lt;code&gt;AddOpenIdConnect&lt;/code&gt; method on the returned authentication builder (passing &lt;code&gt;OpenIdConnect&lt;/code&gt; the details for our open ID connect setup).&lt;/p&gt;
&lt;p&gt;Under the hood these extension methods will set up their configuration, and call methods to register the app logic for their authentication schemes. This is done when the &lt;a href=&quot;https://github.com/dotnet/dotnet/blob/f49747b121ddc20ff6e1a2197ac410601d00196a/src/aspnetcore/src/Security/Authentication/Core/src/AuthenticationBuilder.cs#L65&quot;&gt;AddScheme&lt;/a&gt; and &lt;a href=&quot;https://github.com/dotnet/dotnet/blob/f49747b121ddc20ff6e1a2197ac410601d00196a/src/aspnetcore/src/Security/Authentication/Core/src/AuthenticationBuilder.cs#L93&quot;&gt;AddRemoteScheme&lt;/a&gt; methods get called on the &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationbuilder?view=aspnetcore-8.0&quot;&gt;AuthenticationBuilder Class&lt;/a&gt;. These methods each register an authentication handler class.&lt;/p&gt;
&lt;h2 id=&quot;authentication-handlers&quot; tabindex=&quot;-1&quot;&gt;Authentication Handlers &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-asp-net-core-apps-explained/#authentication-handlers&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Authentication handlers are where the actual logic of the authentication happens. You can find a good post about them on &lt;a href=&quot;https://joonasw.net/view/creating-auth-scheme-in-aspnet-core-2&quot;&gt;joints westlins blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Authentication handlers are used to check whether the user is logged in. They need to implement one method: &lt;code&gt;HandleAuthenticateAsync&lt;/code&gt; This method will get called for &lt;em&gt;every&lt;/em&gt; request - we triggered it by adding the &lt;code&gt;[authorize]&lt;/code&gt; attribute to our controller. Authentication handlers have access to all the HTTP request information, which is how the OIDC library can access the JWT data sent by our OIDC provider.&lt;/p&gt;
&lt;p&gt;When the middleware pipeline gets to a page/endpoint that requires authentication, the endpoint will check if the user is logged in based off the result &lt;code&gt;HandleAuthenticateAsync&lt;/code&gt; returns. This method needs to either return &lt;code&gt;Success&lt;/code&gt;, &lt;code&gt;Failure&lt;/code&gt; or &lt;code&gt;NoResult&lt;/code&gt; based on the HTTP request. What is the difference between failure and no result? If there are multiple types of authentication registered to your app, then passing &lt;code&gt;NoResult&lt;/code&gt; will pass it to another one of the authentication handlers. &lt;code&gt;Failure&lt;/code&gt; will mean that our handler &lt;em&gt;should&lt;/em&gt; handle the request, but the check &lt;em&gt;fails&lt;/em&gt;. If every authentication scheme returns &lt;code&gt;NoResult&lt;/code&gt; then the user also won’t be able to access the page.&lt;/p&gt;
&lt;p&gt;Inside the &lt;a href=&quot;https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs&quot;&gt;OpenIDConnectHandler&lt;/a&gt;, the &lt;a href=&quot;https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs#L633&quot;&gt;HandleRemoteAuthenticateAsync&lt;/a&gt; method will listen for POST requests and will check whether they look like an incoming OIDC message. If so, it will parse the JWT, verify that it’s from the OIDC provider, and verify it’s responding to a request our app sent. The &lt;code&gt;OpenIDConnectOptions&lt;/code&gt; class inherits from the &lt;a href=&quot;https://github.com/dotnet/aspnetcore/blob/f14f99eb634dc5a3ce8d5e11c7522577e5473e90/src/Security/Authentication/Core/src/RemoteAuthenticationOptions.cs#L112&quot;&gt;RemoteAuthenticateOptions class, which has a property called&lt;/a&gt; &lt;code&gt;[SignInScheme](https://github.com/dotnet/aspnetcore/blob/f14f99eb634dc5a3ce8d5e11c7522577e5473e90/src/Security/Authentication/Core/src/RemoteAuthenticationOptions.cs#L112)&lt;/code&gt;. This &lt;code&gt;SignInScheme&lt;/code&gt; is the scheme that will &lt;em&gt;persist&lt;/em&gt; the user (it will default to the default authentication scheme). In our case: the &lt;code&gt;Cookies&lt;/code&gt; scheme. This property is what allows our two schemes to connect. After the OpenIDConnectHandler finds the user has successfully logged in, the &lt;a href=&quot;https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Core/src/RemoteAuthenticationHandler.cs#L172&quot;&gt;RemoteAuthenticationHandler&lt;/a&gt; parent class will call &lt;code&gt;Context.SignInAsync&lt;/code&gt;, passing in the &lt;code&gt;SignInScheme&lt;/code&gt; to tell the Cookies authentication to store the user.&lt;/p&gt;
&lt;h2 id=&quot;forwarding-the-challenge-to-the-oidc-provider&quot; tabindex=&quot;-1&quot;&gt;Forwarding the challenge to the OIDC provider &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-asp-net-core-apps-explained/#forwarding-the-challenge-to-the-oidc-provider&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We know how the user gets authenticated after the OIDC provider gets back to us, but how does the OIDC provider know we have a user who needs to log in?&lt;/p&gt;
&lt;p&gt;To ask for this, we send a &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc#send-the-sign-in-request&quot;&gt;Sign-In Request&lt;/a&gt; to our authentication provider. To do this, we need to send a &lt;code&gt;302 redirect&lt;/code&gt; to the user sending them to the provider’s authorization endpoint. In our case with Azure, the authorization endpoint is the &lt;code&gt;https://login.microsoftonline.com/mytenantid/v2.0&lt;/code&gt; URL we specified as the &lt;code&gt;Authority&lt;/code&gt; property in our &lt;code&gt;Program.cs&lt;/code&gt; file. If you’re using another provider, the OIDC spec defines that there should be &lt;a href=&quot;https://openid.net/specs/openid-connect-discovery-1_0.html&quot;&gt;a metadata file describing what the authorization endpoint is&lt;/a&gt;. When we send the request to the authority, we also provide a redirect URL for the provider to send the user back to us, and our client ID in the &lt;code&gt;GET&lt;/code&gt; request parameters (among a few other things).&lt;/p&gt;
&lt;p&gt;I say “we” do this, but it’s all handled by the &lt;code&gt;OpenIdConnectHandler&lt;/code&gt; class. &lt;code&gt;AuthenticationHandler&lt;/code&gt; classes have a method you can override called &lt;code&gt;HandleChallengeAsync&lt;/code&gt;, which gets called when there is a &lt;code&gt;401 Unauthorized&lt;/code&gt; HTTP response code later in the middleware pipeline. Our &lt;code&gt;[Authorize]&lt;/code&gt; attribute on our controller method will trigger this when the user isn’t logged in. The &lt;code&gt;HandleChallengeAsync&lt;/code&gt; method of the  &lt;code&gt;OpenIdConnectHandler&lt;/code&gt; will change the response to a &lt;code&gt;302 Redirect&lt;/code&gt; and send the user to the OIDC provider. The default redirect URL to send the user back to is &lt;code&gt;signin-oidc&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;middleware-pipeline&quot; tabindex=&quot;-1&quot;&gt;Middleware pipeline &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-asp-net-core-apps-explained/#middleware-pipeline&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We’ve registered what authentication we’re using. However, we need to put our authentication into our &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0&quot;&gt;middleware pipeline&lt;/a&gt; to tell &lt;a href=&quot;http://asp.net/&quot;&gt;ASP.NET&lt;/a&gt; core what stage in the pipeline the authentication should go. In the example above, we registered the middleware after &lt;code&gt;app.UseStaticFiles();&lt;/code&gt; statement, and before &lt;code&gt;app.UseRouting();&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The order matters. This is because the order of the methods you call in this part of the &lt;code&gt;program.cs&lt;/code&gt; file decide the order that each piece of middleware gets run If we wanted to prevent unauthenticated users from accessing our static files we could move the &lt;code&gt;useAuthenticate&lt;/code&gt; call to before &lt;code&gt;UseStaticFiles&lt;/code&gt; call (we’d also need to make an update to the &lt;code&gt;UseStaticFiles&lt;/code&gt; options):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    app.UseAuthentication();
    app.UseStaticFiles(
        new StaticFileOptions
        {
            OnPrepareResponse = ctx =&amp;gt;
            {
                if (!ctx.Context.User.Identity.IsAuthenticated)
                {
                    ctx.Context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                }
            }
        }
    );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this would mean only authenticated users would be able to access the static files (CSS, JS, images etc…) in our site.&lt;/p&gt;
&lt;p&gt;This also means that any middleware which might have tampered with HTTP requests before they get to the authentication handlers may affect how our authorization handlers respond to the request.&lt;/p&gt;
&lt;h2 id=&quot;claims&quot; tabindex=&quot;-1&quot;&gt;Claims &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-asp-net-core-apps-explained/#claims&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once a user is logged in, we can access their information in controllers and middleware. The user&#39;s details are available through the &lt;code&gt;HttpContext.User&lt;/code&gt; object, which is of type &lt;code&gt;ClaimsPrincipal&lt;/code&gt;. This is a class which holds a list of claims. Claims in &lt;a href=&quot;http://asp.net/&quot;&gt;ASP.NET&lt;/a&gt; Core are string key-value pairs that store information about the user. Integrating with Azure, you’ll get back something like this (among other claims):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    Claim Name: name
    Claim Value: Fred Flinstone
    
    Claim Name: http://schemas.microsoft.com/identity/claims/objectidentifier
    Claim Value: A-GUID-Here-asdf
    
    Claim Name: preferred_username
    Claim Value: FredFlinstone@gmail.com
    
    Claim Name:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
    Claim Value: randomstring
    
    Claim Name:ttp://schemas.microsoft.com/identity/claims/tenantid
    Claim Value: A-GUID-Here-asdf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, you can get the name of a user, and their preferred username.&lt;/p&gt;
&lt;h2 id=&quot;matching-user-with-database-data&quot; tabindex=&quot;-1&quot;&gt;Matching user with database data &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-asp-net-core-apps-explained/#matching-user-with-database-data&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Often you’ll have a bunch of data associated with your single-sign-on user stored in your application’s database. You don’t want to hit your database every time the user makes a request, so it’d be good to have your important database info in the &lt;code&gt;ClaimsPrincipal&lt;/code&gt;. The ideal way to do this is using the &lt;code&gt;OnTicketReceived&lt;/code&gt; event. This is an event which will fire after the user has successfully logged in. We can add the claims that we want here, which will cache information about the user.  Here’s an example of how to do this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;      .AddOpenIdConnect(
            &amp;quot;oidc&amp;quot;,
            options =&amp;gt;
            {
            /* 
             * Insert Tenant id, client id, ClientSecret code here 
             */
                options.Events = new OpenIdConnectEvents
                {
                    OnTicketReceived = e =&amp;gt;
                    {
                        ClaimsIdentity claimsIdentity = new ClaimsIdentity();
                        /* Hit database and grab information about user here */
                        claimsIdentity.AddClaim(new Claim(&amp;quot;myNewClaim&amp;quot;, &amp;quot;myClaimValue&amp;quot;));
                        e.Principal.AddIdentity(claimsIdentity);
                        return Task.CompletedTask;
                    }
                };
            }
        );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;code&gt;OnTicketReceived&lt;/code&gt; event, you could match the user with the &lt;code&gt;preferred_username&lt;/code&gt;, or email address, or user ID, with a table in your database, then alter their claims based on what you get back.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot; tabindex=&quot;-1&quot;&gt;Wrapping up &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-asp-net-core-apps-explained/#wrapping-up&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hopefully this helps you with adding OIDC to your &lt;a href=&quot;http://asp.net/&quot;&gt;ASP.NET&lt;/a&gt; Core apps. We looked at configuration, adding middleware, authentication schemes, user claims, and authentication events. OIDC can save you and your users from the frustration of managing multiple logins, but the amount of magic in &lt;a href=&quot;http://asp.net/&quot;&gt;ASP.NET&lt;/a&gt; Core can mean some of that frustration gets passed to you. Maybe a little less after this guide! At the very least, if you are able to set up OIDC authentication with your app, you’ll thank yourself for not having to deal with the stress of storing passwords and dealing with forgotten passwords. Anything is better than that!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Speed up Power BI merges with record lookups</title>
    <link href="https://www.elliotclyde.nz/blog/speed-up-power-bi-merges-with-record-lookups/"/>
    <updated>2024-11-14T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/speed-up-power-bi-merges-with-record-lookups/</id>
    <content type="html">&lt;p&gt;You’re in Power BI. You’re in the Power Query editor getting data. Imagine you’ve got a CSV like this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;salary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;EMP-1&lt;/td&gt;
&lt;td&gt;Johnny Bravo&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EMP-2&lt;/td&gt;
&lt;td&gt;Donald Duck&lt;/td&gt;
&lt;td&gt;20000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EMP-3&lt;/td&gt;
&lt;td&gt;Finn the Human&lt;/td&gt;
&lt;td&gt;15000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EMP-4&lt;/td&gt;
&lt;td&gt;Jake the Dog&lt;/td&gt;
&lt;td&gt;5000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;And another CSV like this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;country&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;EMP-1&lt;/td&gt;
&lt;td&gt;New Zealand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EMP-2&lt;/td&gt;
&lt;td&gt;China&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EMP-3&lt;/td&gt;
&lt;td&gt;United Kingdom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EMP-4&lt;/td&gt;
&lt;td&gt;Australia&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;And you want to combine the two to get:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;salary&lt;/th&gt;
&lt;th&gt;country&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;EMP-1&lt;/td&gt;
&lt;td&gt;Johnny Bravo&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;New Zealand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EMP-2&lt;/td&gt;
&lt;td&gt;Donald Duck&lt;/td&gt;
&lt;td&gt;20000&lt;/td&gt;
&lt;td&gt;China&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EMP-3&lt;/td&gt;
&lt;td&gt;Finn the Human&lt;/td&gt;
&lt;td&gt;15000&lt;/td&gt;
&lt;td&gt;United Kingdom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EMP-4&lt;/td&gt;
&lt;td&gt;Jake the Dog&lt;/td&gt;
&lt;td&gt;5000&lt;/td&gt;
&lt;td&gt;Australia&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The first thing I’d usually do is jump to is using the “Combine” function and merge the two tables together on the “id” column. After this, I could expand the resulting merged table to get the country value in my employees table.&lt;/p&gt;
&lt;p&gt;This is how this might look:&lt;/p&gt;
&lt;p&gt;Getting the employees table:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /* table name: employees */
    let
        Source = Csv.Document(File.Contents(&amp;quot;C:&#92;path&#92;to&#92;employees.csv&amp;quot;),[Delimiter=&amp;quot;,&amp;quot;, Columns=3, QuoteStyle=QuoteStyle.None]),
        #&amp;quot;Promoted Headers&amp;quot; = Table.PromoteHeaders(Source, [PromoteAllScalars=true]),
        #&amp;quot;Changed Type&amp;quot; = Table.TransformColumnTypes(#&amp;quot;Promoted Headers&amp;quot;,{ {&amp;quot;id&amp;quot;, type text}, {&amp;quot;name&amp;quot;, type text}, {&amp;quot;salary&amp;quot;, Int64.Type}})
    in
        #&amp;quot;Changed Type&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Getting the countries table:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /* table name: employeecountries */
    let
        Source = Csv.Document(File.Contents(&amp;quot;C:&#92;path&#92;to&#92;employeecountries.csv&amp;quot;),[Delimiter=&amp;quot;,&amp;quot;, Columns=2, QuoteStyle=QuoteStyle.None]),
        #&amp;quot;Promoted Headers&amp;quot; = Table.PromoteHeaders(Source, [PromoteAllScalars=true]),
        #&amp;quot;Changed Type&amp;quot; = Table.TransformColumnTypes(#&amp;quot;Promoted Headers&amp;quot;,{ {&amp;quot;id&amp;quot;, type text}, {&amp;quot;country&amp;quot;, type text}})
    in
        #&amp;quot;Changed Type&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And I’m going to make a third table which combines these with a merge:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /* table name: merge */
    let
        Source = #&amp;quot;employees&amp;quot;,
        #&amp;quot;Merged Queries&amp;quot; = Table.NestedJoin(Source, {&amp;quot;id&amp;quot;}, employeecountries, {&amp;quot;id&amp;quot;}, &amp;quot;employeecountries&amp;quot;, JoinKind.LeftOuter),
        #&amp;quot;Expanded employeecountries&amp;quot; = Table.ExpandTableColumn(#&amp;quot;Merged Queries&amp;quot;, &amp;quot;employeecountries&amp;quot;, {&amp;quot;country&amp;quot;}, {&amp;quot;country&amp;quot;})
    
        
    in
        #&amp;quot;Expanded employeecountries&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works great for some small CSV files on your local machine, but if you’ve got tables with hundreds of thousands of rows, and your sources are from across the internet, you’re going to hit some performance problems.&lt;/p&gt;
&lt;h2 id=&quot;a-faster-way&quot; tabindex=&quot;-1&quot;&gt;A faster way &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/speed-up-power-bi-merges-with-record-lookups/#a-faster-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First we want to convert the countries into a record. As far as I can tell there’s no way to do this in the GUI, so you’re going to have to jump into the “advanced query” section to smash in this code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /* table name: employeecountriesrecord */
    let
        Source = #&amp;quot;employeecountries&amp;quot;,
        #&amp;quot;Renamed Columns&amp;quot; = Table.RenameColumns(Source,{ {&amp;quot;id&amp;quot;, &amp;quot;Name&amp;quot;}, {&amp;quot;country&amp;quot;, &amp;quot;Value&amp;quot;}}),
        ToRecord = Record.FromTable(#&amp;quot;Renamed Columns&amp;quot; )
    in
       ToRecord
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After you’ve added this in, it’s important to right-click this query and switch off “enable load” otherwise Power BI will automatically try to turn this query back into a table.&lt;/p&gt;
&lt;p&gt;Now to grab the data. You can use the &lt;code&gt;Record.FieldOrDefault&lt;/code&gt; function to do a lookup on the related table. This will default to null if it can’t find the key in the lookup table. If you want it to fail when there is no matching key, you can use ‘Record.Field’ instead.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /* table name: lookup */
    let
        Source = #&amp;quot;employees&amp;quot;,
        #&amp;quot;Added Custom&amp;quot; = Table.AddColumn(Source, &amp;quot;country&amp;quot;, each Record.FieldOrDefault(employeecountriesrecord,[id]))
    in
        #&amp;quot;Added Custom&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The bigger your data is, the bigger the difference this will make, as a merge will be processed in O^n2 time (it will exponentially increase with the number of rows), whereas a lookup will not increase with the size of the data.&lt;/p&gt;
&lt;p&gt;For demonstration, you can use git bash to automatically build larger tables with these scripts (okay these were written by chat GPT, but I used my &lt;em&gt;extensive&lt;/em&gt; bash knowledge to audit them 👀).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    #!/bin/bash
    
    # Creates employees table
    
    # Set the output file and the number of rows
    OUTPUT_FILE=&amp;quot;employees2.csv&amp;quot;
    NUM_ROWS=100000  # Adjust this for the number of rows you want
    
    # Define some sample names
    NAMES=(&amp;quot;Johnny Bravo&amp;quot; &amp;quot;Donald Duck&amp;quot; &amp;quot;Finn the Human&amp;quot; &amp;quot;Jake the Dog&amp;quot; &amp;quot;Mickey Mouse&amp;quot; &amp;quot;SpongeBob SquarePants&amp;quot; &amp;quot;Rick Sanchez&amp;quot; &amp;quot;Morty Smith&amp;quot; &amp;quot;Homer Simpson&amp;quot; &amp;quot;Peter Griffin&amp;quot;)
    
    # Create the CSV header
    echo &amp;quot;id,name,salary&amp;quot; &amp;gt; &amp;quot;$OUTPUT_FILE&amp;quot;
    
    # Generate random data for each row
    for i in $(seq 1 $NUM_ROWS); do
        # Unique employee ID
        ID=&amp;quot;EMP-$i&amp;quot;
        # Random name from the list
        NAME=&amp;quot;${NAMES[$RANDOM % ${#NAMES[@]}]}&amp;quot;
        # Random salary between 5000 and 30000
        SALARY=$(( (RANDOM % 25000) + 5000 ))
    
        # Write row to file
        echo &amp;quot;$ID,$NAME,$SALARY&amp;quot; &amp;gt;&amp;gt; &amp;quot;$OUTPUT_FILE&amp;quot;
    done
    
    echo &amp;quot;Generated $NUM_ROWS rows in $OUTPUT_FILE&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this one:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    #!/bin/bash
    
    #Creates countries lookup table
    
    # Set the output file and the number of rows
    OUTPUT_FILE=&amp;quot;employeecountries2.csv&amp;quot;
    NUM_ROWS=100000  # Adjust this for the number of rows you want
    
    # Define a list of sample countries
    COUNTRIES=(&amp;quot;New Zealand&amp;quot; &amp;quot;China&amp;quot; &amp;quot;United Kingdom&amp;quot; &amp;quot;Australia&amp;quot; &amp;quot;United States&amp;quot; &amp;quot;Canada&amp;quot; &amp;quot;Germany&amp;quot; &amp;quot;France&amp;quot; &amp;quot;Japan&amp;quot; &amp;quot;India&amp;quot;)
    
    # Create the CSV header
    echo &amp;quot;id,country&amp;quot; &amp;gt; &amp;quot;$OUTPUT_FILE&amp;quot;
    
    # Generate random data for each row
    for i in $(seq 1 $NUM_ROWS); do
        # Unique employee ID
        ID=&amp;quot;EMP-$i&amp;quot;
        # Random country from the list
        COUNTRY=&amp;quot;${COUNTRIES[$RANDOM % ${#COUNTRIES[@]}]}&amp;quot;
    
        # Write row to file
        echo &amp;quot;$ID,$COUNTRY&amp;quot; &amp;gt;&amp;gt; &amp;quot;$OUTPUT_FILE&amp;quot;
    done
    
    echo &amp;quot;Generated $NUM_ROWS rows in $OUTPUT_FILE&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can add some more queries to check how long these are taking using &lt;a href=&quot;https://blog.crossjoin.co.uk/2014/11/17/timing-power-query-queries/&quot;&gt;Chris Webb’s check for how long our queries&lt;/a&gt; take:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let
    StartTime = DateTime.LocalNow(),
    Source = if StartTime&amp;lt;&amp;gt;null
    then
     #&amp;quot;merge&amp;quot;
    else
    null,
    //insert all other steps here
    NumberOfRows = Number.ToText(Table.RowCount(Source)),
    EndTime = DateTime.LocalNow(),
    Output = &amp;quot;Query returned &amp;quot; &amp;amp; NumberOfRows &amp;amp; &amp;quot; rows and took &amp;quot; &amp;amp; Duration.ToText(EndTime - StartTime),
    end = Table.FromList({Output})
    in
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    let
    StartTime = DateTime.LocalNow(),
    Source = if StartTime&amp;lt;&amp;gt;null
    then
     #&amp;quot;lookup&amp;quot;
    else
    null,
    //insert all other steps here
    NumberOfRows = Number.ToText(Table.RowCount(Source)),
    EndTime = DateTime.LocalNow(),
    Output = &amp;quot;Query returned &amp;quot; &amp;amp; NumberOfRows &amp;amp; &amp;quot; rows and took &amp;quot; &amp;amp; Duration.ToText(EndTime - StartTime),
    end = Table.FromList({Output})
    in
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the end of this, my lookup takes 40% of the time of the merge with 10,000 row files. This gap will only increase as the number of rows increase and as network latency slows your query down. Keep this method in your toolbox, because it’s an important feature to speed up queries.&lt;/p&gt;
&lt;h2 id=&quot;is-that-really-a-merge%3F&quot; tabindex=&quot;-1&quot;&gt;Is that really a merge? &lt;a class=&quot;direct-link&quot; href=&quot;https://www.elliotclyde.nz/blog/speed-up-power-bi-merges-with-record-lookups/#is-that-really-a-merge%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now if you’ve been scrutinising this, you’ll notice that this isn’t a real merge! If you have a one-to-many or a many-to-many relationship between your two tables, the code above won’t help you.&lt;/p&gt;
&lt;p&gt;What I mean by this, is that if you had employees who worked in more than one country and you had 2 rows in the country table for a single employee, and wanted to end up with 2 rows after your merge in your output. Using record lookups won’t do this for you and you should go back to using the merge queries function.&lt;/p&gt;
&lt;p&gt;The thing is, most of the time I find myself reaching for merge function, I don’t want to end up with more rows. In fact, I usually want to actively avoid this as it might cause bugs with the resulting duplicates. Using a record lookup will actively enforce that you end up with the same number of rows you started with, with no duplicate IDs.&lt;/p&gt;
&lt;p&gt;If you have more power bi performance tips, or you want to complain about this post, hit me up on &lt;a href=&quot;https://bsky.app/profile/elliotclyde.bsky.social&quot;&gt;Blue Sky&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Snippets everywhere on Windows with autohotkey</title>
    <link href="https://www.elliotclyde.nz/blog/snippets-everywhere-on-windows-with-autohotkey/"/>
    <updated>2024-12-31T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/snippets-everywhere-on-windows-with-autohotkey/</id>
    <content type="html">&lt;p&gt;One of the big problems with doing anything on computers is getting the information from your brain into the computer. This is opposed to the other (bigger) problem of solving/understanding the problem in your brain. As developers, the biggest thing that separates us from non-developers is that we communicate to the machine through text. Like Steve Yegge said: &lt;a href=&quot;https://changelog.com/podcast/549#transcript-31&quot;&gt;Text is king&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Getting text into the computer is an important mechanical process and we want it to happen as quickly as possible.  There are a lot of methods to speed this up; learning to touch type, &lt;a href=&quot;https://www.elliotclyde.nz/blog/hill-times-vim-and-delayed-gratificication/&quot;&gt;learning vim&lt;/a&gt; (or any editor tooling), using large-language-model autocomplete (which can save a lot of typing regardless of the quality it produces). I‘ve been looking at an underrated productivity boon: having a bunch of text snippets at your disposal. There are so many cases where we are typing the same thing; for loops, React components, common SQL queries. The list goes on.&lt;/p&gt;
&lt;p&gt;Any code editor worth its salt will have a snippet feature, but I found that I needed some of the same text snippets in programs outside my text editor. For instance; what if I need the same snippet in SSMS to query a database and in VSCode for a migration script? What if I need to use a JavaScript snippet in the browser devtools console as well as in my source code?&lt;/p&gt;
&lt;p&gt;If you’re using a Mac, you can laugh your way over to Raycast and use their &lt;a href=&quot;https://manual.raycast.com/snippets&quot;&gt;Snippets feature&lt;/a&gt;. Unfortunately for us Windows users, we don’t have Raycast (&lt;a href=&quot;https://www.raycast.com/windows&quot;&gt;yet&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;However, what we do have on Windows is &lt;a href=&quot;https://www.autohotkey.com/&quot;&gt;autohotkey&lt;/a&gt;. This is a magical piece of software. I am very annoyed that I hadn’t heard of it before 2024, because it’s basically a way to make Windows programmable and useful. It’s an entire programming environment where you can create GUIs, mimic user typing/clicking, and open programs.&lt;/p&gt;
&lt;p&gt;I have barely scratched the surface of what you can do with autohotkey. To use it, you create script files with the &lt;code&gt;ahk&lt;/code&gt; extension, run them with the autohotkey program, then it will hijack certain hotkeys to do whatever you want. If you mess up, and want the old hotkeys back, you can just click into “show hidden icons” in the bottom right of your Windows dock and exit the script.&lt;/p&gt;
&lt;p&gt;There are lots of ways to set up snippets with autohotkey. For example, you can program it so every time you time “hmf” it will get converted to “Hello, my friend. I hope this email finds you well.” with the following script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ; autohotkey v2
    
    ::hmf::Hello, my friend. I hope this email finds you well.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’ve started with a comment at the top specifying that it’s the V2 version of the scripting language. This is in because autohotkey recently went through a huge overhaul (a little like Python’s 3.0 change). Unfortunately, this makes for a lot of churn; lots of old scripts that no longer work. However, it has drastically improved the language; see &lt;a href=&quot;https://www.hillelwayne.com/post/ahk-v2/&quot;&gt;Somehow AutoHotKey is kinda good now&lt;/a&gt;. It’s a little annoying for me, as my work signed off the 1.1 version of autohotkey, but I’m using 2.0 at home.&lt;/p&gt;
&lt;p&gt;Anyway, back to snippets. The code above isn’t how I prefer to create snippets. I don’t really want text that I might accidentally type suddenly be hijacked. Instead, I like to take a windows hotkey that I barely ever use and use it to open a prompt with the list of snippets I can choose from. For me, the best hotkey is &lt;code&gt;shift&lt;/code&gt; + &lt;code&gt;windows&lt;/code&gt; + &lt;code&gt;p&lt;/code&gt;. By default this hotkey configures display settings for multiple monitors, but I usually get to this by right clicking the desktop and opening display settings. I’m configuring monitors &lt;em&gt;at the most&lt;/em&gt; once at the start of the day. Text snippets are more important, so I’m happy to lose the normal hotkey.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ; autohotkey v2
    
    #SingleInstance force
    
    MyMenu := Menu()
    MyMenuBar := MenuBar()
    MyMenu := Menu.Call()
    MyMenuBar := MenuBar.Call()
    
    MyMenu.Add(&amp;quot;1 For loop&amp;quot;,PrintForLoop)
    MyMenu.Add(&amp;quot;2 JS Function&amp;quot;,PrintFunction)
    MyMenu.Add(&amp;quot;3 Get users&amp;quot;,PrintSelectUsers)
    
    PrintForLoop(ItemName, ItemPos, MyMenu){
            PrintSnipp(&amp;quot;for (let i = 0; i &amp;lt; array.length; i++){`n`n}&amp;quot;)
    }
    
    PrintFunction(ItemName, ItemPos, MyMenu){
            PrintSnipp(&amp;quot;function myFunc(){`n`n}&amp;quot;)
    }
    
    PrintSelectUsers(ItemName, ItemPos, MyMenu){
            PrintSnipp(&amp;quot;SELECT TOP 10 * FROM tblUsers&amp;quot;)
    }
    
    PrintSnipp(text){
            ClipSaved := ClipboardAll()
            A_Clipboard := &amp;quot;&amp;quot;
            A_Clipboard := text 
            ClipWait(2)
            send(&amp;quot;^v&amp;quot;)
            Sleep(100)
            A_Clipboard := ClipSaved
    }
    
    #+p::MyMenu.Show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just in case you have to use autohotkey 1.1 sometimes like me, here’s the script to do this in V1.1:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ; autohotkey v1.1
    
    #SingleInstance force
    
    Menu SnippetMenu, Add, 1 For loop, PrintSnippet1
    Menu SnippetMenu, Add, 2 JS Function, PrintSnippet2
    Menu SnippetMenu, Add, 3 Get users, PrintSnippet3
    
    PrintSnippet1:
            PrintSnipp(&amp;quot;for (let i = 0; i &amp;lt; array.length; i++){`n`n}&amp;quot;)
    return
    
    PrintSnippet2:
            PrintSnipp(&amp;quot;function myFunc(){`n`n}&amp;quot;)
    return
    
    PrintSnippet3:
            PrintSnipp(&amp;quot;SELECT TOP 10 * FROM tblUsers&amp;quot;)
    return
    
    PrintSnipp(text){
            ClipSaved := ClipboardAll
            clipBoard := &amp;quot;&amp;quot;
            clipBoard = %text% 
            ClipWait,2
            if (!ErrorLevel) 
                    send, ^v
            Sleep, 100
            clipboard := ClipSaved
    }
    
    #+p::Menu, SnippetMenu, Show
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After you run one of these scripts with your preferred version of autohotkey, it will mean &lt;code&gt;shift&lt;/code&gt; + &lt;code&gt;windows&lt;/code&gt; + &lt;code&gt;p&lt;/code&gt; will open up a little menu where you can press 1, 2, or 3 and it will paste in the snippet you need.&lt;/p&gt;
&lt;p&gt;I previously had some text files deep in my documents folder where I’d store important and useful text snippets that I’d always forget. Now, I just add them onto my autohotkey snippets, so I can pull them out immediately whenever I need.&lt;/p&gt;
&lt;p&gt;From here you can go a lot of places. You could add submenus: This would mean you could split the scripts up by different languages or projects. You could also add a text input search to search through your snippets. Like I said, I’m scratching the surface. If you have any improvements or any better ways of getting snippets on your Windows machine, hit me up on &lt;a href=&quot;https://bsky.app/profile/elliotclyde.bsky.social&quot;&gt;Bluesky&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Scripting Audacity with JavaScript</title>
    <link href="https://www.elliotclyde.nz/blog/scripting-audacity-with-javaScript/"/>
    <updated>2025-02-22T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/scripting-audacity-with-javaScript/</id>
    <content type="html">&lt;p&gt;It&#39;s always great to get back to the programmer‘s favourite hobby: Spending more time automating something than it would take to just manually do the thing. I’ve been looking into audio recently and going into deep dives on the open source tools for removing noise from audio such as RNNoise: a machine learning model developed by Mozilla foundation for removing everything but voice from audio. When I looked at some of the open source packages which use RNNoise I couldn’t find one that was easy to use over the command line but I found a way to use a audacity plug-in to run RNNoise. I started using it and the changes it could make were pretty awesome for something open source.&lt;/p&gt;
&lt;p&gt;Before I go on, I should also specify that this is going to be a mainly Windows post. I haven’t tested the scripts with Mac or Linux, but if you are ready and willing to add config for Linux or Mac, hit me up on &lt;a href=&quot;https://bsky.app/profile/elliotclyde.bsky.social&quot;&gt;bluesky!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Unfortunately audacity doesn’t have a command line interface. However, audacity does provide a way to interact with it using named pipes. This can be an insecure way to interact between processes, so they warn you not to use this on a webserver.&lt;/p&gt;
&lt;p&gt;Before you’re able to use audacity’s scripting interface you’ll have to enable audacity’s mod-script-pipe module. to do this you need to click into edit then preferences then modules then make sure mod-script-pipe is enabled, and finally, restart audacity. Audacity provides two different pipes: one for sending commands to audacity and another one for receiving responses after commands have run. When the scripting module is enabled, then as soon as audacity starts, both pipes are opened and available to write to/read from. Windows named pipes are also quite frustrating in that after one is closed, you’re reliant on the server to restart the pipe, which Audacity doesn’t seem to reliably do.&lt;/p&gt;
&lt;p&gt;This makes it slightly annoying to create a nice function that will run a command then wait for the response to be printed off. However, here’s some code that will let you do that. As you can see we’ve sent a command to audacity to list commands and audacity has returned all the commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // Function to send commands to audacity:
    
    async function sendCommandToAudacity(command) {
        return new Promise((resolve, reject) =&amp;gt; {
            // Open the command pipe for writing
            fs.open(commandPipePath, &#39;w&#39;, (err, commandFd) =&amp;gt; {
                if (err) {
                    return reject(`Error opening command pipe: ${err}`);
                }
                // Write the command to the command pipe
                fs.write(commandFd, command, (err) =&amp;gt; {
                    if (err) {
                        fs.close(commandFd, () =&amp;gt; { }); // Close the file descriptor on error
                        return reject(`Error writing to command pipe: ${err}`);
                    }
                    // Close the command pipe after writing
                    fs.close(commandFd, async (err) =&amp;gt; {
                        if (err) {
                            return reject(`Error closing command pipe: ${err}`);
                        }
                        const response = await readFromResponsePipe();
                        console.log(response)
                        resolve(response);
                    });
                });
            });
        });
    }
    function readFromResponsePipe() {
        return new Promise((resolve, reject) =&amp;gt; {
            // Open the response pipe for reading
            let responseString = &#39;&#39;
            fs.open(responsePipePath, &#39;r&#39;, (err, responseFd) =&amp;gt; {
                if (err) {
                    return reject(`Error opening response pipe: ${err}`);
                }
                // Read the response from the response pipe
                let currentResponseTime = 0;
                let lastTime = performance.now();
                function readMoreRecursive() {
                    if (responseString.trim() !== &#39;&#39; || currentResponseTime &amp;gt;= responsePipeTimeOut) {
                        // If the response we get back is empty, close the pipe and resolve:
                        fs.close(responseFd, (err) =&amp;gt; {
                            if (err) {
                                return reject(`Error closing response pipe: ${err}`);
                            }
                            // Resolve with the response data
                            resolve(responseString);
                        });
                        return;
                    }
                    const buffer = Buffer.alloc(4096);
                    fs.read(responseFd, buffer, 0, buffer.length, null, (err, bytesRead) =&amp;gt; {
                        if (err) {
                            fs.close(responseFd, () =&amp;gt; { });
                            return reject(`Error reading from response pipe: ${err}`);
                        }
                        responseString = buffer.toString(&#39;utf8&#39;, 0, bytesRead);
                        readMoreRecursive()
                        currentResponseTime = currentResponseTime + (performance.now() - lastTime);
                        lastTime = performance.now();
                    });
                }
                readMoreRecursive();
            })
        })
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great. How do we find more commands? To get a list of commands and the syntax to send them, you can find them in audacity macros. In Audacity, you can click into “Tools” then “Macro Manager” to look at macros. You can then create a new Macro and hit “New” then “Insert”.&lt;/p&gt;
&lt;p&gt;From here you’ll have a list of different commands that audacity will expose. Many of them are options in the menus in audacity, along with a few other commands. After you’ve selected a few commands, you can save the macro, then export it. It will get exported as a text file which will show you the format you can use to send commands through the scripting pipe.&lt;/p&gt;
&lt;p&gt;Macros are another way to automate editing audio files with audacity. Without the pipe module, you have to be in the audacity program to use macros, but this could make sense for your workflow: There are commands which will work for a generic use cases eg. noise reduction and truncating silence, but there will usually be custom edits you’ll have to make to specific parts of the file, so it can make sense to stay in the app.&lt;/p&gt;
&lt;p&gt;Looking back at writing the JS scripts: I found that if you leave audacity open and rerun commands again, you’ll get errors because either audacity or node JS might not properly close the pipes (this also could be a skill issue on my part). Because of this, I wrote the script in a way that it will open an audio file, apply the commands, then exit audacity.&lt;/p&gt;
&lt;p&gt;I’ve wrapped up the script into a class you can import and be able to use to start up audacity here - you can also find it &lt;a href=&quot;https://github.com/Elliotclyde/node-audacity-connector&quot;&gt;on GitHub.&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // AudacityConnector.mjs
    import { spawn, exec } from &#39;child_process&#39;;
    import * as fs from &#39;fs&#39;;
    // Paths to the named pipes
    const commandPipePath = &#39;&#92;&#92;&#92;&#92;.&#92;&#92;pipe&#92;&#92;ToSrvPipe&#39;;
    const responsePipePath = &#39;&#92;&#92;&#92;&#92;.&#92;&#92;pipe&#92;&#92;FromSrvPipe&#39;
    const openAudacityTimeOut = 10_000;
    const audacityStartUpTime = 5_000;
    const leaveOpenAfterCommands = false;
    var child;
    const defaultOptions = {
        audacityLocation: &#39;C:&#92;&#92;Program Files&#92;&#92;Audacity&#92;&#92;Audacity.exe&#39;,
        commandTimeOut: 30_000
    }
    export class AudacityConnector {
        constructor(options) {
            this.options = { ...defaultOptions, ...options };
        }
        openAudacity() {
            return new Promise((resolve, reject) =&amp;gt; {
                child = spawn(this.options.audacityLocation, [], {
                    detached: false,
                    stdio: [&#39;ignore&#39;, &#39;ignore&#39;, &#39;ignore&#39;]
                });
                let currentResponseTime = 0;
                let lastTime = performance.now();
                let isFound = false
                function pollForAudacity() {
                    setTimeout(() =&amp;gt; {
                        if (isFound) {
                            return;
                        }
                        if (currentResponseTime &amp;gt; openAudacityTimeOut) {
                            console.log(&#39;Audacity did not open&#39;)
                            reject(&#39;Audacity did not open&#39;)
                        }
    
                        exec(&#39;tasklist&#39;, (err, stdout, stderr) =&amp;gt; {
                            if (err) {
                                return reject(`Error executing tasklist: ${err}`);
                            }
                            if (stderr) {
                                return reject(`Error: ${stderr}`);
                            }
                            // Check if the output contains &amp;quot;audacity.exe&amp;quot;
                            if (stdout.toLowerCase().includes(&#39;audacity.exe&#39;)) {
                                isFound = true;
                                setTimeout(() =&amp;gt; {
                                    resolve()
                                }, audacityStartUpTime)
                            }
                            currentResponseTime = currentResponseTime + (performance.now() - lastTime);
                            lastTime = performance.now();
                        })
                    }, 500)
                }
                pollForAudacity();
            })
        }
        closeAudacity() {
            child.kill()
        }
        sendCommandToAudacity(command) {
            return new Promise((resolve, reject) =&amp;gt; {
                // Open the command pipe for writing
                fs.open(commandPipePath, &#39;w&#39;, (err, commandFd) =&amp;gt; {
                    if (err) {
                        return reject(`Error opening command pipe: ${err}`);
                    }
                    // Write the command to the command pipe
                    fs.write(commandFd, command, (err) =&amp;gt; {
                        if (err) {
                            fs.close(commandFd, () =&amp;gt; { }); // Close the file descriptor on error
                            return reject(`Error writing to command pipe: ${err}`);
                        }
                        // Close the command pipe after writing
                        fs.close(commandFd, async (err) =&amp;gt; {
                            if (err) {
                                return reject(`Error closing command pipe: ${err}`);
                            }
                            const response = await this.readFromResponsePipe();
                            console.log(response)
                            resolve(response);
                        });
                    });
                });
            });
        }
        readFromResponsePipe() {
            return new Promise((resolve, reject) =&amp;gt; {
                // Open the response pipe for reading
                let responseString = &#39;&#39;
                fs.open(responsePipePath, &#39;r&#39;, (err, responseFd) =&amp;gt; {
                    if (err) {
                        return reject(`Error opening response pipe: ${err}`);
                    }
                    function readMoreRecursive() {
                        if (responseString.trim() !== &#39;&#39;) {
                            // If the response we get back is empty, close the pipe and resolve:
                            fs.close(responseFd, (err) =&amp;gt; {
                                if (err) {
                                    return reject(`Error closing response pipe: ${err}`);
                                }
                                // Resolve with the response data
                                resolve(responseString);
                            });
                            return;
                        }
                        const buffer = Buffer.alloc(4096);
                        fs.read(responseFd, buffer, 0, buffer.length, null, (err, bytesRead) =&amp;gt; {
                            if (err) {
                                fs.close(responseFd, () =&amp;gt; { });
                                return reject(`Error reading from response pipe: ${err}`);
                            }
                            responseString = buffer.toString(&#39;utf8&#39;, 0, bytesRead);
                            readMoreRecursive()
                        });
                    }
                    setTimeout(() =&amp;gt; {
                        fs.close(responseFd, () =&amp;gt; { });
                        resolve()
                    }, this.options.commandTimeOut)
                    readMoreRecursive();
                })
            })
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can consume the audacity connector in your code like this (this script takes in a command line argument ):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    //script.js 
    
    const connector = new AudacityConnector();
    await connector.openAudacity();
    await connector.sendCommandToAudacity(`Import2: FileName=${import.meta.dirname}&#92;&#92;${process.argv[2]}`)
    await connector.sendCommandToAudacity(&amp;quot;Select: Track=0&amp;quot;)
    await connector.sendCommandToAudacity(&amp;quot;TruncateSilence: Action=&#92;&amp;quot;Truncate Detected Silence&#92;&amp;quot; Compress=&#92;&amp;quot;50&#92;&amp;quot; Independent=&#92;&amp;quot;0&#92;&amp;quot; Minimum=&#92;&amp;quot;1.0&#92;&amp;quot; Threshold=&#92;&amp;quot;-40&#92;&amp;quot; Truncate=&#92;&amp;quot;0.25&#92;&amp;quot;&amp;quot;)
    await connector.sendCommandToAudacity(&amp;quot;ClickRemoval:Threshold=&#92;&amp;quot;200&#92;&amp;quot; Width=&#92;&amp;quot;20&#92;&amp;quot;&amp;quot;)
    await connector.sendCommandToAudacity(&amp;quot;LoudnessNormalization:DualMono=&#92;&amp;quot;1&#92;&amp;quot; LUFSLevel=&#92;&amp;quot;-20&#92;&amp;quot; NormalizeTo=&#92;&amp;quot;0&#92;&amp;quot; RMSLevel=&#92;&amp;quot;-20&#92;&amp;quot; StereoIndependent=&#92;&amp;quot;0&#92;&amp;quot;&amp;quot;)
    await connector.sendCommandToAudacity(`Export2:Filename=&amp;quot;${import.meta.dirname}&#92;&#92;${process.argv[3]}&amp;quot; NumChannels=&amp;quot;1&amp;quot;`)
    connector.closeAudacity();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run it with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    node ./script.js myaudiofile.wav out.wav
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to try the RNNoise filter, then you can use the &lt;a href=&quot;https://github.com/werman/noise-suppression-for-voice/releases&quot;&gt;werman plugin for audacity&lt;/a&gt;. The results you can get are pretty amazing. You can make an iPhone recording sound like it was from a professional studio. To use it with the above script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    await connector.sendCommandToAudacity(&amp;quot;RnnoiseSuppressionForVoice: Use_Preset=&#92;&amp;quot;Default&#92;&amp;quot;&amp;quot;) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here are other open source command line tools for audio processing you can check out if you want other ways to process your audio:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/&quot;&gt;FFMPEG&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ardour.org/&quot;&gt;ardour.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sourceforge.net/projects/sox/&quot;&gt;SoX - Sound eXchange download | SourceForge.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Yuan-ManX/Audio-Development-Tools?tab=readme-ov-file#asp&quot;&gt;Yuan-ManX/audio-development-tools&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>A tale of two static site hosts</title>
    <link href="https://www.elliotclyde.nz/blog/a-tale-of-two-static-site-hosts/"/>
    <updated>2025-11-01T00:00:00Z</updated>
    <id>https://www.elliotclyde.nz/blog/a-tale-of-two-static-site-hosts/</id>
    <content type="html">&lt;p&gt;This site has been moved to Cloudflare pages. It used to be hosted on Azure blob storage behind Azure CDN. So what happened? Why move?&lt;/p&gt;
&lt;p&gt;One day I got an email from Azure &amp;quot;Hey, you need to migrate away from Azure CDN classic, and move to either Azure Front Door standard or premium&amp;quot;. Okay fine, this is a personal site that gets very little traffic, so I&#39;ll use Front Door standard I guess. Not the premium one. I upgraded to it. Usually my hosting costs have been about $2.00 per month. After migrating to Front Door: a $22.00 bill . Okay fine, I&#39;ll remove Azure Front Door and just point my DNS straight to the static site.&lt;/p&gt;
&lt;p&gt;Uh oh - now I&#39;ve got certificate errors. HTTPS is broken. The site is insecure. I didn&#39;t have time to fix this for about a month, so my site was only available after clicking through a bunch of security notices. I&#39;ve always struggled with the user experience of Azure. Using blob storage to host a static site has always felt awkward, even though it&#39;s a common use case. So, finally I decided enough is enough and to migrate the site to Cloudflare pages.&lt;/p&gt;
&lt;p&gt;What was the experience like moving to Cloudflare pages? I went to Cloudflare and:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Registered using Google single sign-on&lt;/li&gt;
&lt;li&gt;Clicked &amp;quot;new application&amp;quot;, &amp;quot;Pages&amp;quot;&lt;/li&gt;
&lt;li&gt;I authenticated to my GitHub and pointed Cloudflare to the repo&lt;/li&gt;
&lt;li&gt;Chose the 11ty option for the type of site.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then the build started. My developer brain kicked in: &amp;quot;How is this going to fail? What configuration changes am I going to need to make? What version of 11ty and node.js are cloudflare running? Are they going to point to the correct directory for the site root?&amp;quot;. Then the log said &amp;quot;build successful&amp;quot;. They gave me a URL and my site was live at a Cloudflare URL. I updated my custom domain in Cloudflare and my domain host. Then I was finished.&lt;/p&gt;
&lt;p&gt;And the site is FAST. Like, crazy fast. At no point in this process had I given them a credit card or anything. Cloudflare pages are basically free until you become famous. It&#39;s almost annoying how good this experience is.&lt;/p&gt;
&lt;p&gt;Have other people come across the same issue with Azure blob storage static sites? YES! Eakan Gopalakrishnan has &lt;a href=&quot;https://www.softwarecraftsperson.com/posts/2025-04-25-azure-cdn-front-door/&quot;&gt;basically written the same blogpost as this&lt;/a&gt;. Also on reddit, &lt;a href=&quot;https://www.reddit.com/r/AZURE/comments/1forzv5/microsoft_cdn_retirement/&quot;&gt;there are a bunch of posts from people with static sites on Azure mad about these changes&lt;/a&gt;. This seems to be a pretty common problem for a lot of people hosting their sites! In Microsoft&#39;s defence, people using blob storage to host their static sites for $2.00 a month are not customers that get Azure profitable. We probably cost more in administration costs than we give to Azure. We aren&#39;t worth worrying about from a money-making perspective. Azure make their money from big enterprises burning millions on Cloud services. I don&#39;t matter in the scheme of things.&lt;/p&gt;
&lt;p&gt;But how much goodwill can you burn? A lot of the people hosting these static sites will be developers like myself. If we get the rug pulled under us by a cloud provider in our fun personal sites, how are we going to trust the same provider at work where there&#39;s real money on the line?&lt;/p&gt;
&lt;p&gt;It reminds me a little bit of Facebook. In 2009, we used Facebook to connect and see posts from friends, and I loved writing posts. Now after years of repeated rug-pulls and breaches of trust, I don&#39;t want to invest anything into Facebook. It&#39;s 45% slop, 45% ads and 10% things related to my friends and family. I&#39;m only there because I have to be. The cultural relevance of Facebook is not what it once was. I certainly wouldn&#39;t develop any apps or games on top of Facebook&#39;s platform because I couldn&#39;t trust them not to change the rules underneath me.&lt;/p&gt;
&lt;p&gt;Now Azure have burned a bunch of goodwill on these CDN changes, and Cloudflare have earned my goodwill by making such a smooth onboarding process and giving away low-traffic sites for free. The question is, does this goodwill actually matter? To the immediate bottom line of these companies, probably not. However, I think there&#39;s only so many rug pulls you can do before you become the Facebook of Cloud platforms. Before you lose your relevance.&lt;/p&gt;
&lt;p&gt;Now here come the caveats. Who remembers Kiwifarms? It was (is?) a site to doxx trans people and make their lives hell. Any respectable company should have refused that website service and told it to take a hike. &lt;a href=&quot;https://www.theguardian.com/technology/2022/sep/01/cloudflare-defends-providing-security-services-to-trans-trolling-website-kiwi-farms&quot;&gt;Cloudflare were proudly defending it against DDOS attacks&lt;/a&gt;. They made the wrong call on that.&lt;/p&gt;
&lt;p&gt;On a more selfish note: it has been good for me career-wise to practice a little bit of Azure configuration. It&#39;s not something that I enjoy, but it&#39;s something that I have had to do for work. It&#39;s good to eat your vegetables. A little practice using Azure for my personal site made me slightly less lost configuring Azure websites and databases at work.&lt;/p&gt;
&lt;p&gt;Stay safe out there, and don&#39;t get rug-pulled.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Hugh Haworth - Developer Blog</title>
    <link href="https://www.elliotclyde.nz/blog/"/>
    <updated>2025-11-01T09:28:48Z</updated>
    <id>https://www.elliotclyde.nz/blog/</id>
    <content type="html">
&lt;main&gt;
  &lt;h1&gt;
    Hugh Haworth - Developer Blog
    &lt;a class=&quot;rss&quot; href=&quot;https://www.elliotclyde.nz/feed.xml&quot;&gt;
      &lt;image alt=&quot;twitter logo&quot; class=&quot;profile-link-image&quot; src=&quot;/images/rss.svg&quot; width=&quot;30&quot; height=&quot;30&quot;&gt;&lt;/image&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;p class=&quot;subtitle&quot;&gt;
    Writing about the cool things we do with connected computers
  &lt;/p&gt;
  &lt;div class=&quot;list-wrapper&quot;&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/a-tale-of-two-static-site-hosts/&quot;&gt;A tale of two static site hosts&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-36.svg&quot; alt=&quot;The letters JS with an arrow flying around&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;A tale of two static site hosts&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt; 1 November 2025&lt;/div&gt;
          &lt;div&gt;Azure to Cloudflare pages&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/scripting-audacity-with-javaScript/&quot;&gt;Scripting Audacity with JavaScript&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-35.svg&quot; alt=&quot;Cartoon green headphones with a spiky audio wave jumping between the ear phone part.&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Scripting Audacity with JavaScript&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;22 February 2025&lt;/div&gt;
          &lt;div&gt;More ear happiness with more automation&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/snippets-everywhere-on-windows-with-autohotkey/&quot;&gt;Snippets everywhere on Windows with autohotkey&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-34.svg&quot; alt=&quot;3 squares with green gradients. The have the letters A H and K respectively on them.&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Snippets everywhere on Windows with autohotkey&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;31 December 2024&lt;/div&gt;
          &lt;div&gt;Text at your fingertips&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/speed-up-power-bi-merges-with-record-lookups/&quot;&gt;Speed up Power BI merges with record lookups&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-29.svg&quot; alt=&quot;Power BI logo in a gradienty green, with another gradient circle behind it&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Speed up Power BI merges with record lookups&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;14 November 2024&lt;/div&gt;
          &lt;div&gt;Lookup lightning&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-asp-net-core-apps-explained/&quot;&gt;OIDC Authentication in ASP.NET Core apps explained&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-33.svg&quot; alt=&quot;The OpenID logo - a trapezoid with a circle going around it. All in desaturated green.&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;OIDC Authentication in ASP.NET Core apps explained&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;14 October 2024&lt;/div&gt;
          &lt;div&gt;Leave the login forms to the professionals&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/oidc-authentication-in-server-side-blazor-apps/&quot;&gt;OIDC Authentication in server-side Blazor apps&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-32.svg&quot; alt=&quot;The blazor logo, but instead of straight lines and good proportions, it&#39;s slightly badly drawn and green&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;OIDC Authentication in server-side Blazor apps&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;17 July 2024&lt;/div&gt;
          &lt;div&gt;Microsoft, can you tell me who this is? (again)&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/whats-in-a-pdf/&quot;&gt;What&#39;s in a PDF&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-31.svg&quot; alt=&quot;Adobe logo but green-i-fied&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;What&#39;s in a PDF&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;11 March 2024&lt;/div&gt;
          &lt;div&gt;Come with me ... and you&#39;ll be ... in a world of pure documentation&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/ways-of-getting-data-from-excel-files-into-a-database/&quot;&gt;Ways of getting data from Excel files into a Database&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-30.svg&quot; alt=&quot;Database with arrows pointing into it from Xs&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Ways of getting data from Excel files into a Database&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;18 January 2024&lt;/div&gt;
          &lt;div&gt;Gettin&#39; data where it needs to go&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/sticker-charts/&quot;&gt;Sticker Charts&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-29.svg&quot; alt=&quot;Power BI logo in a gradienty green, with another gradient circle behind it&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Sticker Charts&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;13 August 2023&lt;/div&gt;
          &lt;div&gt;My 3 Gripes with Power BI&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/dotnetcore-azure-ad-and-saml/&quot;&gt;DotNetCore, Azure AD, and SAML&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-28.svg&quot; alt=&quot;Green version of the Azure Logo&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;DotNetCore, Azure AD, and SAML&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;20 April 2023&lt;/div&gt;
          &lt;div&gt;Microsoft, can you tell me who this is?&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/hill-times-vim-and-delayed-gratificication/&quot;&gt;Hill Times, Vim and Delayed Gratificication&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-27.svg&quot; alt=&quot;A stylised green cartoon hill&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Hill Times, Vim and Delayed Gratificication&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;31 January 2023&lt;/div&gt;
          &lt;div&gt;Running up that hill&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/goodbye-heroku-free-plan/&quot;&gt;Goodbye Heroku free plan&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-26.svg&quot; alt=&quot;Heroku logo but light green and with a greenish gradient behind it&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Goodbye Heroku free plan&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt; 8 January 2023&lt;/div&gt;
          &lt;div&gt;Death of a Salesforce&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/gizmo-girls/&quot;&gt;Gizmo Girls&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-25.svg&quot; alt=&quot;stylised Emily Gilmore from Gilmore girls&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Gizmo Girls&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt; 1 December 2022&lt;/div&gt;
          &lt;div&gt;A new blog about an old TV show&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/Hosting-small-websites-on-EC2/&quot;&gt;Hosting small websites on EC2&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-24.svg&quot; alt=&quot;Greenish central processing unit of a computer with some flames behind to try and make it cool&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Hosting small websites on EC2&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt; 2 October 2022&lt;/div&gt;
          &lt;div&gt;Embrace the Bezos&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/printing-a-file-with-the-apache-portable-runtime/&quot;&gt;Printing a file with the Apache Portable Runtime&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-23.svg&quot; alt=&quot;Green feathers falling down&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Printing a file with the Apache Portable Runtime&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;31 July 2022&lt;/div&gt;
          &lt;div&gt;Getting portable with C&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/compile-your-own-apache-module-in-c/&quot;&gt;Compile your own Apache Module in C&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-23.svg&quot; alt=&quot;Green feathers falling down&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Compile your own Apache Module in C&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;16 June 2022&lt;/div&gt;
          &lt;div&gt;Extending servers&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/why-is-sql-weird/&quot;&gt;Why is SQL weird?&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-22.svg&quot; alt=&quot;SQL tables getting joined together&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Why is SQL weird?&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;10 May 2022&lt;/div&gt;
          &lt;div&gt;Fear and loathing in RDBMS&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/The-callbacks-are-out-to-get-you/&quot;&gt;The callbacks are out to get you&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-21.svg&quot; alt=&quot;The letters JS with an arrow flying around&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;The callbacks are out to get you&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt; 3 March 2022&lt;/div&gt;
          &lt;div&gt;The part of JavaScript that beginners struggle with&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/My-JavaFX-ListViews-are-doubling-up/&quot;&gt;My JavaFX ListViews are doubling up&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-20.svg&quot; alt=&quot;A cup of steaming hot java with the letters &quot; FX&quot;=&quot;&quot; shooting=&quot;&quot; off=&quot;&quot; to=&quot;&quot; the=&quot;&quot; side&quot;=&quot;&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;My JavaFX ListViews are doubling up&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;18 January 2022&lt;/div&gt;
          &lt;div&gt;Getting lists in JavaFX to behave&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/explorations-in-c-cplusplus-win32-and-sdl/&quot;&gt;Explorations in C, C++, Win32, and SDL&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-19.svg&quot; alt=&quot;An old-school computer with a big C on it.&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Explorations in C, C++, Win32, and SDL&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;10 January 2022&lt;/div&gt;
          &lt;div&gt;Allocatin&#39; and commemoratin&#39;&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/Intro-to-requestAnimationFrame/&quot;&gt;Intro to RequestAnimationFrame&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-18.svg&quot; alt=&quot;A film role of showing a blobby person being animated running&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Intro to RequestAnimationFrame&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;22 December 2021&lt;/div&gt;
          &lt;div&gt;An example-first guide to getting things moving&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/what-should-create-do/&quot;&gt;What should “create” do?&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-17.svg&quot; alt=&quot;Paintbrush drawing a big plus&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;What should “create” do?&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;29 September 2021&lt;/div&gt;
          &lt;div&gt;Creating content in a cloud-first world&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/react-and-electron/&quot;&gt;React and Electron&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-16.svg&quot; alt=&quot;Electron Logo with react logos swirling around it&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;React and Electron&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt; 2 September 2021&lt;/div&gt;
          &lt;div&gt;Of chromium instances and create-react-apps&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/rollup-plugins-for-beginners/&quot;&gt;Rollup Plugins for Absolute Beginners&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-13.svg&quot; alt=&quot;Koru pattern, a big R and a big U&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Rollup Plugins for Absolute Beginners&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;26 July 2021&lt;/div&gt;
          &lt;div&gt;Start getting your hands dirty in the build step&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/Reviving-an-old-laptop-with-linux/&quot;&gt;Reviving an old laptop with linux&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-15.svg&quot; alt=&quot;Linux penguin looking washed up with a cigarette and a beer&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Reviving an old laptop with linux&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;21 July 2021&lt;/div&gt;
          &lt;div&gt;Bringing an old machine back from death&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/http-caching-is-a-superpower/&quot;&gt;HTTP Caching is a Superpower&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-14.svg&quot; alt=&quot;Rack of servers with clocks around them&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;HTTP Caching is a Superpower&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;11 May 2021&lt;/div&gt;
          &lt;div&gt;Make the web faster the old way&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/going-static/&quot;&gt;Going static&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-12.svg&quot; alt=&quot;Ugly picture of the eleventy possum&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Going static&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;25 January 2021&lt;/div&gt;
          &lt;div&gt;Migrating from PHP land to Eleventy&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/easy-node-live-server/&quot;&gt;Easy Node Live Server&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-11.svg&quot; alt=&quot;Node logo re-styled&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Easy Node Live Server&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;17 January 2021&lt;/div&gt;
          &lt;div&gt;Tired of hitting Ctrl R then Ctrl F5 all the time?&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/dropping-plantr-2/&quot;&gt;Dropping Plantr 2.0&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-4.svg&quot; alt=&quot;Four little plants&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Dropping Plantr 2.0&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;12 January 2021&lt;/div&gt;
          &lt;div&gt;Get gardening like it&#39;s 2021&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/2020-to-2021/&quot;&gt;2020 to 2021&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-9.svg&quot; alt=&quot;Covid mask&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;2020 to 2021&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;31 December 2020&lt;/div&gt;
          &lt;div&gt;Learning web development in the year from hell and plans for 2021&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/tailwind-for-interactions-alpine-js/&quot;&gt;Tailwind for interactions - Alpine.JS&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-8.svg&quot; alt=&quot;Snowy mountain&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Tailwind for interactions - Alpine.JS&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt; 7 December 2020&lt;/div&gt;
          &lt;div&gt;Who put JavaScript in my HTML?!&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/react-on-a-budget-with-lit-html-and-haunted/&quot;&gt;React on a Budget with Lit-html and Haunted&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-7.svg&quot; alt=&quot;Haunted ghost with React logo and dollar sign&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;React on a Budget with Lit-html and Haunted&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;17 November 2020&lt;/div&gt;
          &lt;div&gt;Into a build-step free future&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/batteries-included-react/&quot;&gt;Batteries-included React&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-6.svg&quot; alt=&quot;React logo with lightening bolts&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Batteries-included React&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;16 October 2020&lt;/div&gt;
          &lt;div&gt;The myriad of options we have to supercharge Jordan Walke&#39;s framework.&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/eloquent-models-first-impressions/&quot;&gt;Eloquent Models - First Impressions&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-5.svg&quot; alt=&quot;Re-styled Laravel logo&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Eloquent Models - First Impressions&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt; 5 October 2020&lt;/div&gt;
          &lt;div&gt;My First impressions with Laravel&#39;s solution to data management&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/plantr-2-upcoming/&quot;&gt;Upcoming project Plantr 2.0&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-4.svg&quot; alt=&quot;Four little plants&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Upcoming project Plantr 2.0&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;11 September 2020&lt;/div&gt;
          &lt;div&gt;Some things I learnt building a gardening app and how I&#39;m going to make a new one.&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/web-colour-deep-dive/&quot;&gt;Web Colour Deep Dive&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-3.svg&quot; alt=&quot;Paint can being poured out&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Web Colour Deep Dive&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt; 6 September 2020&lt;/div&gt;
          &lt;div&gt;A look into the world of RGB, hue, saturation, lightness, hex codes and colours on the web and ways to process them.&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/php-data-objects/&quot;&gt;PHP Data Objects&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-2.svg&quot; alt=&quot;The PHP elephant&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;PHP Data Objects&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;27 August 2020&lt;/div&gt;
          &lt;div&gt;A cowboy&#39;s guide to prepared PDO Statements&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;block-link&quot;&gt;
      &lt;a class=&quot;block-link__overlay block-link__overlay--transparent&quot; href=&quot;https://www.elliotclyde.nz/blog/building-this-blog/&quot;&gt;Building this Blog&lt;/a&gt;
      &lt;div class=&quot;block-link__content block-link__content--transparent&quot;&gt;
        &lt;img src=&quot;https://www.elliotclyde.nz/images/blog/blog-1.svg&quot; alt=&quot;Bricks coming together to form a pyramid&quot; width=&quot;1400&quot; height=&quot;900&quot; /&gt;
        &lt;div&gt;
          &lt;h2&gt;Building this Blog&lt;/h2&gt;
          &lt;div class=&quot;publish-date&quot;&gt;20 August 2020&lt;/div&gt;
          &lt;div&gt;The joys of reinventing the wheel - building this blog with Edoras.&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
  &lt;/div&gt;
  &lt;a href=&quot;https://www.elliotclyde.nz/&quot;&gt;Home&lt;/a&gt;
&lt;/main&gt;
</content>
  </entry>
</feed>