<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="https://www.jamesmcnee.com/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>James McNee - Blog</title>
        <link>https://www.jamesmcnee.com/</link>
        <atom:link href="https://www.jamesmcnee.com/rss.xml" rel="self" type="application/rss+xml" />
        <description>Random thoughts and interesting discoveries from a software engineer..</description>
        <language>en</language>
            <item>
                <title>Fascinating Faceting with Postgres</title>
                <link>https://www.jamesmcnee.com/blog/posts/2024/may/05/fascinating-faceting-with-postgres/</link>
                <description>&lt;h3 id=&quot;what-on-earth-is-a-facet%3F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2024/may/05/fascinating-faceting-with-postgres/#what-on-earth-is-a-facet%3F&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; What on earth is a facet?&lt;/h3&gt;
&lt;p&gt;A facet is a piece of information about an entity that can be used to categorise it. Take for example a piece of fruit, it might have facets relating to its colour, size, weight, etc. Facets are a way of describing an entity in a structured way and are often used in search engines to allow users to filter results.&lt;/p&gt;
&lt;p&gt;Let&#39;s continue with the fruit theme, imagine that you are a fruit vendor and you have created a website to sell your fruit. Customers love the quality of your produce, but they are often overwhelmed by the sheer variety of fruit that you offer. You think of other websites that sell products and how they allow users to filter their results by various attributes, such as price, colour, etc. You decide to implement this feature on your website too, and you want customers to be able to drill down to the type of fruit they wish to consume easily, you also want your users to see at each stage how many fruits will match the criteria they are applying.&lt;/p&gt;
&lt;p&gt;You&#39;re a wiz with UI, so you squirrel away and create all the UX elements you need, this also helps you to visualise the data your server will need to respond with.&lt;/p&gt;
&lt;div class=&quot;w-full&quot;&gt;
    &lt;img class=&quot;mx-auto w-full md:w-2/3&quot; src=&quot;https://www.jamesmcnee.com/img/blog/posts/post-content/facinating-faceting/facets.webp&quot; alt=&quot;Screenshot of a facet user interface showing various categories with their values and the number of matching products&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;As you can hopefully see from the image above, the user can filter the fruit by colour, size and origin. When a user selects a colour, the other categories update to show the number of fruits that match the criteria they have selected, the numbers in the category to which the filter has been applied do not change, this shows the user how many additional results will be added if they select one of these. It&#39;s obvious that your customers will celebrate that they no longer have to scroll through pages of fruit to find the perfect apple.&lt;/p&gt;
&lt;h3 id=&quot;the-rules-of-the-facets&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2024/may/05/fascinating-faceting-with-postgres/#the-rules-of-the-facets&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; The rules of the facets&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Each facet can have multiple values, e.g. the colour facet could have values of red, green, yellow, etc.&lt;/li&gt;
&lt;li&gt;A filter applied to a facet should only affect the counts of the other facets and not the facet to which the filter pertains. E.g. if a user selects red, the counts of the other colours should not change.&lt;/li&gt;
&lt;li&gt;In a system with pagination, the counts should be calculated for the entire dataset, not just the current page. This means any global filters/search should be applied to the counts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;faceting-in-postgres&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2024/may/05/fascinating-faceting-with-postgres/#faceting-in-postgres&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Faceting in Postgres&lt;/h3&gt;
&lt;p&gt;Let&#39;s have a look at a mechanism for implementing faceting in Postgres.&lt;/p&gt;
&lt;p&gt;It&#39;s important to note here that this is intended to be implemented with the aid of a high level language such as Java, TypeScript, etc. and not just in SQL. This is because the query will need to be dynamic based on the user&#39;s selections.&lt;/p&gt;
&lt;custom-element&gt;
    &lt;banner type=&quot;warning&quot;&gt;
        The mechanism described below is not the most optimal way to implement faceting in Postgres, it should work great for moderate datasets, but for large datasets you may want to seek out a more performant solution.
  &lt;/banner&gt;
&lt;/custom-element&gt;
&lt;p&gt;Let&#39;s look at a sample of our dataset, we have a table called &lt;code&gt;fruit&lt;/code&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Colour&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Origin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Apple&lt;/td&gt;
&lt;td&gt;red&lt;/td&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;td&gt;domestic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Banana&lt;/td&gt;
&lt;td&gt;yellow&lt;/td&gt;
&lt;td&gt;large&lt;/td&gt;
&lt;td&gt;american&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blueberry&lt;/td&gt;
&lt;td&gt;blue&lt;/td&gt;
&lt;td&gt;small&lt;/td&gt;
&lt;td&gt;domestic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cherry&lt;/td&gt;
&lt;td&gt;red&lt;/td&gt;
&lt;td&gt;small&lt;/td&gt;
&lt;td&gt;european&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Durian&lt;/td&gt;
&lt;td&gt;green&lt;/td&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;td&gt;american&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;It has columns for the &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;colour&lt;/code&gt;, &lt;code&gt;size&lt;/code&gt; and &lt;code&gt;origin&lt;/code&gt; of the fruit. From this list of columns, we wish to facet on the latter three.&lt;/p&gt;
&lt;p&gt;The first step for creating the facets is simply counting each of the values for each of the columns. We can do this with a query like the following:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; facet_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jsonb_object_agg&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;facet_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;null&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; count&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; facet_values
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; facet_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; facet_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; count
        &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fruit&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        LATERAL &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;colour&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;colour&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;size&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;origin&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;origin&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; facets&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;facet_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; facet_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; facet_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; facet_value
     &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; facets
&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; facet_name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This yields an output of:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;facet_name&lt;/th&gt;
&lt;th&gt;facet_values&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;colour&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ &amp;quot;red&amp;quot;: 2, &amp;quot;yellow&amp;quot;: 1, &amp;quot;green&amp;quot;: 1, &amp;quot;blue&amp;quot;: 1 }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;size&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ &amp;quot;medium&amp;quot;: 2, &amp;quot;large&amp;quot;: 1, &amp;quot;small&amp;quot;: 2 }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;origin&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ &amp;quot;domestic&amp;quot;: 2, &amp;quot;american&amp;quot;: 2, &amp;quot;european&amp;quot;: 1 }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;custom-element&gt;
    &lt;banner type=&quot;info&quot;&gt;
        You can of course choose a different type of list aggregation other than `jsonb_object_agg` such as `string_agg` or `array_agg`. JSON is usually easier to parse in a high level language such as Java or TypeScript.
  &lt;/banner&gt;
&lt;/custom-element&gt;
&lt;p&gt;What we have above is great, it provides the counts for each of the facetable columns. However, we need to be able to filter these counts based on the user&#39;s selections. Let&#39;s change the query as if a user has selected &lt;code&gt;colour: red&lt;/code&gt; and &lt;code&gt;size: medium&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; facet_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jsonb_object_agg&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;facet_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;null&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; count&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; facet_values
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; facet_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; facet_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; count
        &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fruit&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        LATERAL &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;colour&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;colour&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;size&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;origin&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;origin&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; facets&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;facet_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; facet_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;colour&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;red&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;size&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;medium&quot;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; facet_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; facet_value
        &lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt; 
            &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;colour&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; facet_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;colour&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; facet_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; count
            &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fruit&quot;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;size&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;medium&quot;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; facet_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; facet_value 
        &lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt; 
            &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;size&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; facet_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;size&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; facet_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; count
            &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fruit&quot;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;colour&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;red&quot;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; facet_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; facet_value
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; facets
&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; facet_name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This yields an output of:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;facet_name&lt;/th&gt;
&lt;th&gt;facet_values&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;colour&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ &amp;quot;red&amp;quot;: 1, &amp;quot;green&amp;quot;: 1 }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;size&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ &amp;quot;medium&amp;quot;: 1, &amp;quot;small&amp;quot;: 1 }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;origin&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ &amp;quot;domestic&amp;quot;: 1 }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Notice that we can still see colours and sizes that were not selected, but that have matching fruit from other applied filters. I.e. even though red was selected, there is still another green fruit that is also medium in size.&lt;/p&gt;
&lt;p&gt;The query needs to be dynamically created based on the user&#39;s selections with various pieces of it being added and altered such as the where clauses. This is fairly trivial to do in a high level language but remember to ensure that user input is sanitised to prevent SQL injection attacks.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2024/may/05/fascinating-faceting-with-postgres/#conclusion&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Conclusion&lt;/h3&gt;
&lt;p&gt;In this post, we explored the concept of faceting and how it can be implemented in Postgres. We also looked at how to filter the facets based on user selections. This is a great way to provide a flexible and powerful search mechanism to your users.&lt;/p&gt;
&lt;p&gt;Further reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A useful &lt;a href=&quot;https://www.cybertec-postgresql.com/en/faceting-large-result-sets/#:~:text=Conceptually%2C%20faceting%20has%20two%20steps,also%20known%20as%20the%20facets.&quot;&gt;Cybertec post&lt;/a&gt; on faceting by Ants Aasma. This also explores a more optimal way to implement faceting in Postgres.&lt;/li&gt;
&lt;li&gt;Information on the &lt;code&gt;LATERAL&lt;/code&gt; keyword in Postgres can be found &lt;a href=&quot;https://www.postgresql.org/docs/14/queries-table-expressions.html#QUERIES-LATERAL&quot;&gt;on the official documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
                <pubDate>Sun, 05 May 2024 00:00:00 +0000</pubDate>
                <dc:creator>James McNee</dc:creator>
                <guid>https://www.jamesmcnee.com/blog/posts/2024/may/05/fascinating-faceting-with-postgres/</guid>
                    <tag>Postgres</tag>
                    <tag>Facets</tag>
                <image>
                    <link>"https://www.jamesmcnee.com/img/blog/posts/blog15.webp"</link>
                    <alt>Photo of a fruit stand containing various fruit of varying colour.</alt>
                    <caption xml:lang="en" type="html">Photo by &lt;a href=&quot;https://unsplash.com/ja/@alschim?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Alexander Schimmeck&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/red-and-green-apples-on-red-plastic-crate-2zJhA9RSkys?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;</caption>
                </image>
            </item>
            <item>
                <title>Managing permissions with Postgres event triggers</title>
                <link>https://www.jamesmcnee.com/blog/posts/2024/feb/13/permission-management-with-postgres-event-triggers/</link>
                <description>&lt;h3 id=&quot;setting-the-scene&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2024/feb/13/permission-management-with-postgres-event-triggers/#setting-the-scene&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Setting the scene&lt;/h3&gt;
&lt;p&gt;We (Auto Trader UK, where I work in the infrastructure team) use &lt;a href=&quot;https://cloud.google.com/kubernetes-engine&quot;&gt;GKE (Google Kubernetes Engine)&lt;/a&gt; to run our application workloads. We use &lt;a href=&quot;https://cloud.google.com/sql&quot;&gt;Google&#39;s CloudSQL offering&lt;/a&gt; to provide developers with relational data stores, such as MySQL and Postgres for their applications to integrate with.&lt;/p&gt;
&lt;p&gt;Our in-house abstraction layer atop tools such as Helm, Kubernetes and Isito provides developers with a self-service interface to get an application spun up and deployed to production in minutes without the need to ask for servers to be provisioned, DNS configured and firewalls poked. Equally, our developers don&#39;t need to learn all the nuances of the aforementioned cloud technologies and can focus on delivering value in their language of choice.&lt;/p&gt;
&lt;p&gt;Applications running within our clusters use &lt;a href=&quot;https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity&quot;&gt;Workload Identity&lt;/a&gt; to participate in Identity Access and Management (IAM), this grants the application access to Cloud Storage Buckets and CloudSQL without credentials, meaning no secrets, key rotation and overall improves security. In regards to Postgres, this means that a login role (a role which has the login attribute set) is bound to a &lt;a href=&quot;https://cloud.google.com/iam/docs/service-account-overview&quot;&gt;Google Service Account&lt;/a&gt;, this provides authentication and we can then use Postgres&#39; standard authorisation/permissions model for the rest.&lt;/p&gt;
&lt;h3 id=&quot;provisioning-and-management&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2024/feb/13/permission-management-with-postgres-event-triggers/#provisioning-and-management&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Provisioning and management&lt;/h3&gt;
&lt;p&gt;We make heavy use of &lt;a href=&quot;https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/&quot;&gt;CRDs (Custom Resource Definitions)&lt;/a&gt; to model processes and infrastructure that is not natively provided by k8s core (or one of the third parties we integrate with, such as Istio) and we recently extended this to include the full provisioning and management of Postgres as a platform feature. Our users can now, from within their application repositories describe:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Postgres instance they want, the major version it should be, its size, in terms of CPU and RAM and any flags that need enabling.&lt;/li&gt;
&lt;li&gt;Their database name and any custom roles that should be defined along with the access these roles should ensure (more on this later).&lt;/li&gt;
&lt;li&gt;Users and applications that should have access to the database besides the current workload and what role they should assume.&lt;/li&gt;
&lt;/ul&gt;
&lt;custom-element&gt;
    &lt;banner type=&quot;error&quot;&gt;
        We strongly discourage applications sharing a datastore as a general rule, doing so (generally) creates tight coupling and leads to unexpected interaction, however, we have workloads such as &lt;a href=&quot;https://docs.confluent.io/platform/current/connect/index.html#what-is-kafka-connect&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Kafka Connect&lt;/a&gt; that fundamentally require it. Some older (pre-cloud) applications were written in a way that requires this access.
  &lt;/banner&gt;
&lt;/custom-element&gt;
&lt;p&gt;When our custom Kubernetes controller sees that one of these resources has been created, it will act to apply the desired configuration, be this provision a new instance, perform an in-place upgrade or create and configure a database.&lt;/p&gt;
&lt;p&gt;We have an opinionated way in which our databases are configured and we allow configuration from users only within the bounds we have set, for example, our controller will create an &#39;owner&#39; role that the application assumes when it connects, this role has &lt;code&gt;CREATE&lt;/code&gt; permissions on the database and is expected to be the owner of all objects within, no other role is granted this access. It is the responsibility of the application to create relations (tables, views) etc either in code or by using a technology such as &lt;a href=&quot;https://flywaydb.org/&quot;&gt;Flyway&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We also opt to never grant permissions directly to login roles themselves, but instead create higher order roles that we dub &#39;permissions roles&#39;, due to them being granted permissions. We then set the &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-set-role.html&quot;&gt;ROLE configuration parameter&lt;/a&gt; of the login role to this permissions roles name, meaning that on login they will assume that role. This gives us the flexibility of managing users separately from the database (in terms of K8s resources) and also allows us to configure the permissions of groups of users by altering a single role.&lt;/p&gt;
&lt;h3 id=&quot;custom-roles&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2024/feb/13/permission-management-with-postgres-event-triggers/#custom-roles&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Custom roles&lt;/h3&gt;
&lt;p&gt;As briefly mentioned, we allow developers to define custom roles that should be created on their database. These roles can then be shared with other workloads (such as Kafka Connectors, Data Importers etc) to allow them restricted access to the database.&lt;/p&gt;
&lt;p&gt;A custom role definition that a developer may define for a Kafka Connector, might look like this:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;databases&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;myDatabase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; my_awesome_database
      &lt;span class=&quot;token key atrule&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; some_instance
      &lt;span class=&quot;token key atrule&quot;&gt;roles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;kafka-connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;permissions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;perSchema&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;financial_reporting&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;token key atrule&quot;&gt;specific&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                  &lt;span class=&quot;token key atrule&quot;&gt;tables&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;token key atrule&quot;&gt;outbox&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; SELECT
        &lt;span class=&quot;token key atrule&quot;&gt;sharedWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;&#39;my-kafka-connector&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;roles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;kafka-connect&#39;&lt;/span&gt;
          &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above may seem a little verbose, with which I would tend to agree, however, it provides application developers with the power to configure as broad or fine granularity as they desire. In the above example, a role has been defined called &lt;code&gt;kafka-connect&lt;/code&gt; and it has been given &lt;code&gt;SELECT&lt;/code&gt; permission on a single table named &lt;code&gt;outbox&lt;/code&gt; in the &lt;code&gt;financial_reporting&lt;/code&gt; schema. This role will not be able to access any other database objects or modify the data in the one table it can access.&lt;/p&gt;
&lt;p&gt;Not only does providing this level of granularity protect from unexpected actions by the user of the role, either intentional or not, it also provides for clear auditing (in both git and the K8s cluster) of what access has been extended to other workloads. In the above example we can see the role &lt;code&gt;kafka-connect&lt;/code&gt; has been shared with the workload named &lt;code&gt;my-kafka-connector&lt;/code&gt; (workloads are one-to-one with namespaces in our clusters and are therefore unique).&lt;/p&gt;
&lt;h3 id=&quot;two-problems-to-overcome&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2024/feb/13/permission-management-with-postgres-event-triggers/#two-problems-to-overcome&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Two problems to overcome&lt;/h3&gt;
&lt;p&gt;Having such a flexible schema for expressing permissions brings two main challenges that need to be overcome&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If a role is described that grants to &#39;all&#39; objects within a schema, how do we ensure the role accrues permissions to new objects created after the granting code has been run?&lt;/li&gt;
&lt;li&gt;If a role is described that grants to a specific object, how do we account for the fact that it may not yet exist?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&#39;s explore these two issues...&lt;/p&gt;
&lt;h4 id=&quot;perpetual-permissions&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2024/feb/13/permission-management-with-postgres-event-triggers/#perpetual-permissions&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Perpetual permissions&lt;/h4&gt;
&lt;p&gt;When a developer defines their role and deploys it via their CI/CD pipeline as part of their application release, it is fairly trivial for our code to apply the grants to &#39;every object&#39; that exists in the specified schema, assuming they have defined a role that should do this. The harder part is keeping this statement true as new objects are created after the fact.&lt;/p&gt;
&lt;p&gt;Postgres has a system called &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-alterdefaultprivileges.html&quot;&gt;default privileges&lt;/a&gt; which essentially solves the issue above, it allows for describing the permissions that should be granted when new objects are created at either a database or schema level. In terms of this requirement, default privilege is a valid option.&lt;/p&gt;
&lt;h4 id=&quot;an-ordering-problem&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2024/feb/13/permission-management-with-postgres-event-triggers/#an-ordering-problem&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; An ordering problem&lt;/h4&gt;
&lt;p&gt;Described in the previous section is a mechanism for defining and sharing custom roles with other workloads, you will note that the example role gave specific access to a single object (table) in a single schema. This is all well and good, assuming that the subject of the grant already exists, if not we would not be able to set up our permissions role.&lt;/p&gt;
&lt;p&gt;This posed more of an issue for us than might be immediately obvious, as it seems only logical that users would create tables etc before adding them to a custom role. Ultimately, yes it is logical for this to happen and as a series of events, very may well happen, but one needs to remember the way we are managing the datastore, using a K8s CRD ultimately deployed via helm. This means that as soon as an application&#39;s CI/CD pipeline runs it (helm) will create both the applications deployment and the resource representing the database, these will and must be able to handle being applied in any order.&lt;/p&gt;
&lt;p&gt;If a developer has a bunch of commits stacked up such as (ordered as a git log, top = latest):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;da06bf8&lt;/code&gt; Grant access to the orders table to kafka-connect&lt;/li&gt;
&lt;li&gt;&lt;code&gt;c7662cd&lt;/code&gt; Implement API for handling orders and persisting to the database&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0f9c839&lt;/code&gt; Add SQL migration script to create new orders table&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;br /&gt;It&#39;s possible and most likely that the database resource will be applied and handled before the application rolls out and creates the table, meaning that when the code creating the role tried to run &lt;code&gt;GRANT SELECT ON orders TO some_role;&lt;/code&gt; it would have received an error.&lt;/p&gt;
&lt;p&gt;There are a few ways to potentially tackle this, some of which might be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Document it as a &#39;known gotcha&#39; and hope that developers will learn to roll out changes in isolation, e.g. add the script to create the table, roll it to prod, then add the grant.&lt;/li&gt;
&lt;li&gt;Make the controller essentially swallow the error and ignore it if the object doesn&#39;t exist.&lt;/li&gt;
&lt;li&gt;Teach Postgres how to dynamically assign the permissions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;br /&gt;Spoiler alert, it&#39;s the third one that we went with and to which this article pertains! But let&#39;s explore those other two.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Document it as a &#39;known gotcha&#39; and hope that developers will learn to roll out changes in isolation, e.g. add the script to create the table, roll it to prod, and then add the grant.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Adding documentation for quirks and calling it a day is a last resort option really, at best it&#39;s annoying for users to have to remember this gotcha and realistically people will understandably forget.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Make the controller essentially swallow the error and ignore it if the object doesn&#39;t exist.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are a few issues with this one, but the main one is that unless the spec of the resource changes the controller will not run. Therefore, if the above scenario plays out as described, the grant will not be applied and then will also not try again until another unrelated change to the resource spec is made.&lt;/p&gt;
&lt;h3 id=&quot;event-triggers-to-the-rescue&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2024/feb/13/permission-management-with-postgres-event-triggers/#event-triggers-to-the-rescue&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Event Triggers to the rescue&lt;/h3&gt;
&lt;p&gt;So how can you grant permissions on an object that doesn&#39;t (yet) exist? Well, as far as I know, you cannot. But all hope is not lost, because what if we can somehow hook into the lifecycle (create, update, delete) of objects in the database and run our grants at this point? Luckily Postgres has a mechanism for this, &lt;a href=&quot;https://www.postgresql.org/docs/current/event-trigger-definition.html&quot;&gt;Event Triggers&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;md:ml-6 bg-base-300 md:bg-transparent&quot;&gt;
&lt;h4 id=&quot;what-is-an-event-trigger%3F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2024/feb/13/permission-management-with-postgres-event-triggers/#what-is-an-event-trigger%3F&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; What is an event trigger?&lt;/h4&gt;
&lt;p&gt;Triggers have long existed on relational databases as a way to hook into DML (Data Manipulation Language) actions such as &lt;code&gt;INSERT&lt;/code&gt; and &lt;code&gt;DELETE&lt;/code&gt; on rows in tables for example, however, standard triggers are not able to observe DDL (Data Definition Language) events such as &lt;code&gt;CREATE&lt;/code&gt; and &lt;code&gt;DROP&lt;/code&gt;. Postgres&#39; Event Trigger mechanism though is built to facilitate this and allows for the execution of code at either the beginning or end of one of these commands. A function that hooks into these events can observe and even reject the action, making them a powerful tool for restricting actions.&lt;/p&gt;
&lt;p&gt;The most common use case for event triggers seems to be for centralised auditing of DDL tracking when, and by whom was a table, view, sequence, etc) created, dropped or modified. Another use case discussed online in various places is around restricting the types of objects that can be created, for example blocking the creation of functions, materialised views etc.&lt;/p&gt;
&lt;p&gt;The event trigger function, whether it is invoked at the start or end of the DDL command, is fully involved in the transaction. This means that if an exception is raised at any point, the whole transaction will be aborted and rolled back if required.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Hopefully, it&#39;s becoming clear how we can leverage this to achieve the functionality we desire, granting permissions across the board not only on objects that currently exist but also on ones that do not yet. To quote a popular maxim, it allows us to &amp;quot;kill two birds with one stone&amp;quot; (only metaphorical birds were harmed, don&#39;t worry!).&lt;/p&gt;
&lt;h4 id=&quot;defining-the-event-trigger-and-function&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2024/feb/13/permission-management-with-postgres-event-triggers/#defining-the-event-trigger-and-function&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Defining the event trigger and function&lt;/h4&gt;
&lt;custom-element&gt;
    &lt;banner type=&quot;warning&quot;&gt;
        The functions and procedures that are described in this section will be generated and applied on the fly when the k8s resource is observed. For a single database or manual management, this may be quite hard to maintain and simply be overkill.
  &lt;/banner&gt;
&lt;/custom-element&gt;
&lt;p&gt;Without further ado, let&#39;s have a look at an event trigger based, on the configuration example above:&lt;/p&gt;
&lt;p&gt;First, we will define a procedure (a function that doesn&#39;t return a value) that will contain all our granting logic, it will take the following arguments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;object_id&lt;/code&gt;: The oid of the object that is currently being handled.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;object_identity&lt;/code&gt;: The identity (schema-qualified name) of the object.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;object_type&lt;/code&gt;: An enumerable mapping for the type of the object is &lt;code&gt;RELATION&lt;/code&gt; (Table, View), &lt;code&gt;SEQUENCE&lt;/code&gt; etc.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;schema_name&lt;/code&gt;: The name of the schema to which the object belongs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We will then template our function with the logic that checks if the current object is one we are interested in and if so, applies the grants.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Function to inspect the current object and apply grants upon it if desired.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;REPLACE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PROCEDURE&lt;/span&gt; admin_schema&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle_grants&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;object_id oid&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; object_identity &lt;span class=&quot;token keyword&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; object_type &lt;span class=&quot;token keyword&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; schema_name &lt;span class=&quot;token keyword&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;LANGUAGE&lt;/span&gt; plpgsql
 &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; search_path &lt;span class=&quot;token keyword&quot;&gt;TO&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;admin_schema&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;pg_temp&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; $&lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt;$
    &lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; object_type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SCHEMA&#39;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; nspname
                  &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; pg_catalog&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pg_namespace
                  &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; nspname &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; object_identity
                  &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; nspowner::regrole::&lt;span class=&quot;token keyword&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;owner_role&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;EXECUTE&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;GRANT USAGE ON SCHEMA %s TO &quot;kafka-connect&quot;;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; object_identity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      
      
      &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; object_type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;TABLE&#39;&lt;/span&gt; object_identity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;financial_reporting.outbox&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;EXECUTE&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;GRANT SELECT ON %s TO &quot;connect&quot;;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; object_identity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    $&lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt;$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next thing we need is a function that the event trigger will invoke directly, the shape of this function is rigid in that it must return an &lt;code&gt;event_trigger&lt;/code&gt; type and also has access to retrieve data about the current invocation of the trigger. The above function could be folded into this, however, to foreshadow slightly, it will be useful to have it separate later...&lt;/p&gt;
&lt;p&gt;There are a few interesting things about this function:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It uses &lt;code&gt;SECURITY DEFINER&lt;/code&gt; - When a function is invoked in SQL, usually it will be done so acting as the &lt;code&gt;CURRENT_USER&lt;/code&gt; i.e. the current role that is set for the session. However, it&#39;s often necessary to use a function to allow a user to perform elevated actions they could not otherwise perform, without giving them too much power. By creating a function as &lt;code&gt;SECURITY DEFINER&lt;/code&gt; it means that the function should be executed as the role that owns it, rather than the invoker&#39;s role.&lt;/li&gt;
&lt;li&gt;It calls a mystery &lt;code&gt;pg_event_trigger_ddl_commands()&lt;/code&gt; function and loops through the returned rows. This function returns the &#39;DDL commands&#39; that caused the event trigger to fire, usually, this will only contain a single row, but certain statements can result in multiple.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;br /&gt; Hopefully the rest is fairly self-explanatory, we loop through all the DDL commands that have happened in the current invocation and call our grant procedure after grouping the DDL commands by their target. I.e. tables, views and materialized views should all be treated as &lt;code&gt;RELATION&lt;/code&gt;s.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Function to be invoked directly by the event trigger&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;REPLACE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FUNCTION&lt;/span&gt; admin_schema&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle_role_grants_event_trigger&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;RETURNS&lt;/span&gt; event_trigger
 &lt;span class=&quot;token keyword&quot;&gt;LANGUAGE&lt;/span&gt; plpgsql
 SECURITY &lt;span class=&quot;token keyword&quot;&gt;DEFINER&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; search_path &lt;span class=&quot;token keyword&quot;&gt;TO&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;admin_schema&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;pg_temp&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; $&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;$
    &lt;span class=&quot;token keyword&quot;&gt;DECLARE&lt;/span&gt;
        obj RECORD&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; obj &lt;span class=&quot;token operator&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; pg_event_trigger_ddl_commands&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;LOOP&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;command_tag &lt;span class=&quot;token operator&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;CREATE TABLE&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;CREATE VIEW&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;CREATE MATERIALIZED VIEW&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;CREATE TABLE AS&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;CALL&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;admin_schema&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle_grants&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objid&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;object_identity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;RELATION&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;schema_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

                &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;command_tag &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;CREATE SEQUENCE&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;CALL&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;admin_schema&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle_grants&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objid&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;object_identity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SEQUENCE&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;schema_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

                &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;command_tag &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;CREATE SCHEMA&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;CALL&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;admin_schema&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle_grants&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objid&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;object_identity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SCHEMA&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;object_identity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LOOP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    $&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally for all of this to work, we need to define the event trigger and bind it to our function, which thankfully is simple!&lt;/p&gt;
&lt;p&gt;Notice in the following snippet that we specify &lt;code&gt;ddl_command_end&lt;/code&gt;, there are two main points one can hook into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ddl_command_start&lt;/code&gt; - Execute (and wait for) the function &lt;strong&gt;BEFORE&lt;/strong&gt; running the DDL itself, inspecting catalogues at this point &lt;strong&gt;will not&lt;/strong&gt; show the change as being reflected. This is a good point to perform validation/security checks.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ddl_command_end&lt;/code&gt; - Execute (and wait for before committing) &lt;strong&gt;AFTER&lt;/strong&gt; running the DDL itself, inspecting catalogues at this point &lt;strong&gt;will&lt;/strong&gt; show the change. This is what we want as we need the object to exist to apply grants upon it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As mentioned earlier, both of these happen within the transactional boundary of the DDL event, so if the function throws (raises an exception) then the whole transaction will be rolled back.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; EVENT &lt;span class=&quot;token keyword&quot;&gt;TRIGGER&lt;/span&gt; role_grants_event_trigger &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; ddl_command_end
    &lt;span class=&quot;token keyword&quot;&gt;EXECUTE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FUNCTION&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;admin_schema&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle_role_grants_event_trigger&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;reconciliation&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2024/feb/13/permission-management-with-postgres-event-triggers/#reconciliation&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Reconciliation&lt;/h4&gt;
&lt;p&gt;The above section describes how we can set up an event trigger to respond to new objects being created on the database and dynamically grant the appropriate permissions to roles that should have access, however, it&#39;s important to also be able to &#39;reconcile&#39; existing objects on the database and have permissions applied retrospectively.&lt;/p&gt;
&lt;p&gt;A few use cases where this is needed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The first time the event trigger is added to an existing database.&lt;/li&gt;
&lt;li&gt;If a new object is defined as requiring roles to have access that previously did not.&lt;/li&gt;
&lt;li&gt;Each time the access list is altered either allowing more roles access to an object or changing the permissions an existing one has.&lt;/li&gt;
&lt;li&gt;In the event that something unexpected happened and the grants were not applied.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;br /&gt;Essentially, it&#39;s going to be important to run a full reconcile each time the spec for the Kubernetes resource changes, this catches all of the above and also will allow for applying changes to the granting logic and having it retrospectively applied.&lt;/p&gt;
&lt;p&gt;Rather than maintaining two completely disparate procedures, one for responding to DDL events and one for reconciling existing objects, we can utilise the slightly abstracted procedure (&lt;code&gt;admin_schema.handle_grants&lt;/code&gt;) that we created above. This ensures that the granting logic is the same no matter which code path triggers it.&lt;/p&gt;
&lt;p&gt;This is the final function/procedure, I promise! Here&#39;s what it&#39;s doing:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Applying grants&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Finding all objects by querying &lt;code&gt;pg_class&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;For each object, invoke (call) the &lt;code&gt;handle_grants&lt;/code&gt; procedure, exactly the same as the event trigger does&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Revoking old grants&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Finding all privileges on objects that are in schemas that are owned by the &lt;code&gt;owner_role&lt;/code&gt; (described in a section above).&lt;/li&gt;
&lt;li&gt;Loop over each of these privileges (grants)&lt;/li&gt;
&lt;li&gt;If the grant is still valid, move on, and leave it alone.&lt;/li&gt;
&lt;li&gt;If the grant is no longer valid, run a &lt;code&gt;REVOKE&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;REPLACE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PROCEDURE&lt;/span&gt; admin_schema&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;reconcile_role_grants&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;token keyword&quot;&gt;LANGUAGE&lt;/span&gt; plpgsql
 &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; search_path &lt;span class=&quot;token keyword&quot;&gt;TO&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;admin_schema&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;pg_temp&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; $&lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt;$
    &lt;span class=&quot;token keyword&quot;&gt;DECLARE&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt; RECORD&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;-- Logic for reconciling role grants&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;IN&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; oid&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                   concat&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;quote_ident&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;relnamespace::regnamespace::&lt;span class=&quot;token keyword&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                          &lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                          quote_ident&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;relname&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; object_identifier&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                   relnamespace::regnamespace::&lt;span class=&quot;token keyword&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                   relkind
            &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; pg_catalog&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pg_class
        &lt;span class=&quot;token keyword&quot;&gt;LOOP&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;-- relkind - r: Table (Relation), v: View, m: Materialized View&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;relkind &lt;span class=&quot;token operator&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;r&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;v&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;m&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt;
              &lt;span class=&quot;token keyword&quot;&gt;CALL&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;admin_schema&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle_grants&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;oid&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;object_identifier&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;TABLE&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token comment&quot;&gt;-- relkind - S: Sequence&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;relkind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;S&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt;
              &lt;span class=&quot;token keyword&quot;&gt;CALL&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;admin_schema&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle_grants&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;oid&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;object_identifier&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SEQUENCE&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LOOP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; nspname &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;schema&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; pg_catalog&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pg_namespace&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;LOOP&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;CALL&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;admin_schema&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle_grants&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SCHEMA&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LOOP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;-- Revoke grants that are no longer valid&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;IN&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; relnamespace::regnamespace::&lt;span class=&quot;token keyword&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                   relname &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; object_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                   &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;CASE&lt;/span&gt;
                      &lt;span class=&quot;token keyword&quot;&gt;WHEN&lt;/span&gt; relkind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;r&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;TABLE&#39;&lt;/span&gt;
                      &lt;span class=&quot;token keyword&quot;&gt;WHEN&lt;/span&gt; relkind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;v&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;TABLE&#39;&lt;/span&gt;
                      &lt;span class=&quot;token keyword&quot;&gt;WHEN&lt;/span&gt; relkind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;m&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;TABLE&#39;&lt;/span&gt;
                      &lt;span class=&quot;token keyword&quot;&gt;WHEN&lt;/span&gt; relkind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;S&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SEQUENCE&#39;&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; object_type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                   &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;aclexplode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;relacl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;grantee::regrole::&lt;span class=&quot;token keyword&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; grantee&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                   &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;aclexplode&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;relacl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;privilege_type::&lt;span class=&quot;token keyword&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; privilege
            &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; pg_class
            &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; relacl &lt;span class=&quot;token operator&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; relkind &lt;span class=&quot;token operator&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;r&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;v&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;m&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;S&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;privileges&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; nspname
                        &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; pg_catalog&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pg_namespace
                        &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; nspname &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;schema&lt;/span&gt;
                        &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; nspowner::regrole::&lt;span class=&quot;token keyword&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;owner_role&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;LOOP&lt;/span&gt;

          &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;object_type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;TABLE&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;schema&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;financial_reporting&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;object_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;outbox&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;grantee &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;kafka-connect&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;privilege &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SELECT&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;CONTINUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

          &lt;span class=&quot;token keyword&quot;&gt;EXECUTE&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;REVOKE %s ON %s FROM %s;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                          &lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;privilege&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                          concat&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;quote_ident&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; quote_ident&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;object_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                          quote_ident&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;grantee&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LOOP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    $&lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt;$&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2024/feb/13/permission-management-with-postgres-event-triggers/#conclusion&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Conclusion&lt;/h3&gt;
&lt;p&gt;The functions and procedures that have been shown in this post are very much an MVP of what gets applied to the databases that are being managed by this mechanism, but hopefully, this has shown how it all fits together and provided a base for anyone wishing to do similar.&lt;/p&gt;
&lt;p&gt;As mentioned earlier in the post, we have an opinionated way that our databases should broadly look and therefore can do things like scope to things owned by the &#39;owner role&#39;, not everyone will be in this position.&lt;/p&gt;
&lt;p&gt;It&#39;d be interesting to hear any thoughts on what we are doing here and how others have solved similar issues, especially when trying to fully automate database provisioning and management.&lt;/p&gt;
&lt;p&gt;Further reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&quot;https://www.postgresql.org/docs/current/functions-event-triggers.html&quot;&gt;official Postgres documentation on event triggers&lt;/a&gt;, is both useful and also infuriatingly vague in parts.&lt;/li&gt;
&lt;li&gt;An &lt;a href=&quot;https://www.cybertec-postgresql.com/en/abusing-security-definer-functions/&quot;&gt;interesting and informative post&lt;/a&gt; by Laurenz Albe on how &lt;code&gt;SECURITY DEFINER&lt;/code&gt; functions can be abused and the steps to take to secure them. Some of which you will see reflected in the above examples (e.g. setting the &lt;code&gt;search_path&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
</description>
                <pubDate>Tue, 13 Feb 2024 00:00:00 +0000</pubDate>
                <dc:creator>James McNee</dc:creator>
                <guid>https://www.jamesmcnee.com/blog/posts/2024/feb/13/permission-management-with-postgres-event-triggers/</guid>
                    <tag>Postgres</tag>
                    <tag>Event Triggers</tag>
                    <tag>Kubernetes</tag>
                    <tag>Self Service</tag>
                    <tag>CloudSQL</tag>
                <image>
                    <link>"https://www.jamesmcnee.com/img/blog/posts/blog14.webp"</link>
                    <alt>Photo depicting an elephant squirting water from its trunk.</alt>
                    <caption xml:lang="en" type="html">Photo by &lt;a href=&quot;https://unsplash.com/@geraninmo?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Geranimo&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/brown-elephant-standing-on-brown-field-during-daytime-AX9sJ-mPoL4?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;</caption>
                </image>
            </item>
            <item>
                <title>Waiting is hard... but just Persevere(JS)</title>
                <link>https://www.jamesmcnee.com/blog/posts/2023/oct/15/waiting-is-hard-but-just-perseverejs/</link>
                <description>&lt;h3 id=&quot;preface&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/15/waiting-is-hard-but-just-perseverejs/#preface&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Preface&lt;/h3&gt;
&lt;p&gt;Working with technologies that are asynchronous in nature, such as messaging queues like Apache Kafka and RabbitMQ, brings not only some new interesting mechanisms for communication but also a few challenges to deal with in terms of verifying they are working as expected.&lt;/p&gt;
&lt;p&gt;I&#39;m a big fan of what I like to call &#39;shallow integration tests&#39;, these are tests that cover the integration around a single slice of your application, without testing the whole thing (a full integration test). Let&#39;s explore this a little further as it&#39;ll help with context.&lt;/p&gt;
&lt;h3 id=&quot;shallow-integration-testing&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/15/waiting-is-hard-but-just-perseverejs/#shallow-integration-testing&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Shallow Integration Testing&lt;/h3&gt;
&lt;p&gt;In order to properly explain what I mean by &#39;shallow integration tests&#39;, it&#39;s somewhat important to touch on those either side of them, namely unit and integration tests.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unit Tests&lt;/strong&gt; are those that are focused on the testing of a single unit of code, e.g. a function/method. They are isolated in what they test, meaning that they do not rely on external factors such as the network or a database. The aim of unit testing is to prove that given a set of inputs/conditions the isolated unit responds/behaves as expected.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration Tests&lt;/strong&gt; differ from unit tests in that rather than testing an isolated piece of code such as a function or method, they test the integration between all the units of your application. Integration tests tend to be higher level than a unit test, not exercising every edge case, but rather testing core flows through your application.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;p&gt;When I refer to a &#39;shallow integration test&#39; what I am referring to is a test that sits at a boundary of your application and tests the integration between your code that interacts with an external system (or is interacted with). Most commonly this will either be at the &lt;code&gt;repository&lt;/code&gt; or &lt;code&gt;controller&lt;/code&gt; layer.&lt;br /&gt;&lt;/p&gt;
&lt;custom-element&gt;
    &lt;banner type=&quot;info&quot;&gt;
        &lt;ul&gt;
            &lt;li&gt;A &lt;b&gt;repository&lt;/b&gt; is a class that is responsible for handling data exchange between your application and an external system, this could be a database, HTTP service, message queue or even something like a printer.&lt;/li&gt;
            &lt;li&gt;A &lt;b&gt;controller&lt;/b&gt; is a class that handles the interaction into your application, this could be a set of HTTP endpoints, a console/terminal prompt a consumer of a messaging queue or even a physical button that someone might press.&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/banner&gt;
&lt;/custom-element&gt;
&lt;br /&gt;
&lt;p&gt;Imagine that we have an application that serves a HTTP API for a library, one of the functions that this API performs is allowing patrons to check if books are available to borrow. The API is backed by a &lt;code&gt;MongoDB&lt;/code&gt; database which it can use to track and update the availability of books.&lt;/p&gt;
</description>
                <pubDate>Sun, 15 Oct 2023 00:00:00 +0000</pubDate>
                <dc:creator>James McNee</dc:creator>
                <guid>https://www.jamesmcnee.com/blog/posts/2023/oct/15/waiting-is-hard-but-just-perseverejs/</guid>
                    <tag>Await</tag>
                    <tag>Persevere</tag>
                    <tag>Kafka</tag>
                    <tag>Testing</tag>
                <image>
                    <link>"https://www.jamesmcnee.com/img/blog/posts/blog13.webp"</link>
                    <alt>Wooden clocks of various shapes and sizes piled with their faces showing</alt>
                    <caption xml:lang="en" type="html">Photo by &lt;a href=&quot;https://unsplash.com/@jontyson?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Jon Tyson&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/FlHdnPO6dlw?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;</caption>
                </image>
            </item>
            <item>
                <title>Preventing Catastrophe using Gum</title>
                <link>https://www.jamesmcnee.com/blog/posts/2023/oct/07/preventing-catastrophe-using-gum/</link>
                <description>&lt;h3 id=&quot;it&#39;s-all-about-context&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/07/preventing-catastrophe-using-gum/#it&#39;s-all-about-context&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; It&#39;s all about context&lt;/h3&gt;
&lt;p&gt;There are a variety of binaries that I use daily that utilise a static &#39;context&#39; so that the commands run against a specified environment. These tools have some state, most commonly a file on disk somewhere that references the context they are currently set to.&lt;/p&gt;
&lt;p&gt;It&#39;s too easy when using tools like &lt;a href=&quot;https://kubernetes.io/docs/reference/kubectl/&quot;&gt;kubectl&lt;/a&gt;, &lt;a href=&quot;https://helm.sh/&quot;&gt;helm&lt;/a&gt; and &lt;a href=&quot;https://cloud.google.com/&quot;&gt;gcloud&lt;/a&gt; to inadvertently run a command against the wrong cluster or project because you have forgotten that you have switched to production. Hopefully, the command is ultimately harmless, but it could easily not be.&lt;/p&gt;
&lt;p&gt;This post is to share how I help to mitigate the risk of accidentally running destructive commands against production infrastructure using &lt;a href=&quot;https://github.com/charmbracelet/gum&quot;&gt;gum&lt;/a&gt;, which is an awesome tool that allows developers to build powerful TUI (terminal user interface) based scripts.&lt;/p&gt;
&lt;p&gt;Of course, an argument can be made around not having the level of privilege to do anything dangerous in production contexts in the first place, but it&#39;s like all things a trade-off between security and practicality. Having access to production clusters is necessary at some level to be able to respond to incidents and investigate issues, how this access is managed is not the topic of this post, but is in itself an interesting topic.&lt;/p&gt;
&lt;h3 id=&quot;visual-signals&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/07/preventing-catastrophe-using-gum/#visual-signals&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Visual signals&lt;/h3&gt;
&lt;p&gt;Before I get onto the meat of the post, around how I use gum to help prevent accidents, it&#39;s good to note some of the other techniques that both myself and others use to aid with this goal, because as with most things in life, more is often better.&lt;/p&gt;
&lt;h4 id=&quot;custom-prompts&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/07/preventing-catastrophe-using-gum/#custom-prompts&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Custom Prompts&lt;/h4&gt;
&lt;div class=&quot;w-full&quot;&gt;
    &lt;img class=&quot;mx-auto w-full md:w-2/3&quot; src=&quot;https://www.jamesmcnee.com/img/blog/posts/post-content/preventing-catastrophe/shell-prompt-kube-context.webp&quot; alt=&quot;Screenshot of a terminal window showing a prompt containing a kube context indicator&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;Most shells provide a mechanism for customising the &#39;prompt&#39; i.e. where user input is taken. Customisations range from showing the current status of a project in version control, how long a command took to execute, the exit code of a command and so on.&lt;/p&gt;
&lt;p&gt;A very useful customisation that one can add to their shell is to show the current Kubernetes cluster that they are pointing at and if desired, the namespace they have active too. You can see this in practice in the above screenshot.&lt;/p&gt;
&lt;h4 id=&quot;colour-coded-shells&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/07/preventing-catastrophe-using-gum/#colour-coded-shells&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Colour Coded Shells&lt;/h4&gt;
&lt;p&gt;Another approach, although not one that I use, is to have the title bar of the terminal window change colour depending on the current context. Many people choose to have their shells getting more and more red as they step through environments closer to production.&lt;/p&gt;
&lt;h3 id=&quot;gumming-it-up&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/07/preventing-catastrophe-using-gum/#gumming-it-up&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Gumming it up&lt;/h3&gt;
&lt;p&gt;The above two approaches are good and do help to prevent mishaps, but they still rely on the user noticing a visual cue. What I wanted was something that would pull the emergency break for me and prevent me from slamming into the wall that is, a production incident.&lt;/p&gt;
&lt;h4 id=&quot;what-is-gum%3F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/07/preventing-catastrophe-using-gum/#what-is-gum%3F&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; What is Gum?&lt;/h4&gt;
&lt;p&gt;Gum is a binary that can be used to build powerful TUI (terminal UI) based scripts quickly and easily, check out the &lt;a href=&quot;https://github.com/charmbracelet/gum&quot;&gt;readme on their GitHub&lt;/a&gt; for some examples and how to use it.&lt;/p&gt;
&lt;p&gt;Essentially though, gum allows you to add dynamic user input into an otherwise boring shell script, notable features are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Input&lt;/strong&gt; requires the user to enter some input, this can be used inline or assigned to a variable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write&lt;/strong&gt; allows the user to provide a block of multi-line text.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filter&lt;/strong&gt; and &lt;strong&gt;Choose&lt;/strong&gt; will show a list of choices, the former allowing the user to type to filter. Supports multi-selection etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Confirm&lt;/strong&gt; is what we will be focusing on. This shows a prompt to the user seeking confirmation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;creating-a-kubectl-wrapper&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/07/preventing-catastrophe-using-gum/#creating-a-kubectl-wrapper&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Creating a kubectl wrapper&lt;/h4&gt;
&lt;div class=&quot;w-full&quot;&gt;
    &lt;img class=&quot;mx-auto w-full md:w-2/3&quot; src=&quot;https://www.jamesmcnee.com/img/blog/posts/post-content/preventing-catastrophe/confirm-prompt.webp&quot; alt=&quot;Screenshot of a terminal window showing a prompt containing a kube context indicator&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;I am going to focus on &lt;code&gt;kubectl&lt;/code&gt; for the remainder of this post, but this technique can be applied to basically any binary. I use it for both &lt;code&gt;kubectl&lt;/code&gt; and &lt;code&gt;helm&lt;/code&gt; currently.&lt;/p&gt;
&lt;p&gt;The first thing I needed to do was to create a wrapper script that I would invoke whenever I typed &lt;code&gt;kubectl&lt;/code&gt; (or my alias &lt;code&gt;k&lt;/code&gt;). I could then capture the command and perform the sniff test to see if it looks dubious. You can see what the result of this looks like in the screenshot above, just so you know what we are building.&lt;/p&gt;
&lt;p&gt;I have a pattern for creating my shell scripts and an automated way of sharing dependencies between them, which I may write about at some point, but for now, I will keep it simple. I tend to have a &lt;code&gt;main&lt;/code&gt; function to contain any logic, this is mainly for variable scoping, but it also aids with reading. First, I create two variables, one that will point at the real &lt;code&gt;kubectl&lt;/code&gt; binary and another that will get the currently active kube context.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-name function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;KUBECTL_PATH&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${&lt;span class=&quot;token environment constant&quot;&gt;HOME&lt;/span&gt;}&lt;/span&gt;/kubernetes/kubectl&quot;&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;CURRENT_CLUSTER&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;KUBECTL_PATH&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; config current-context&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

main &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$@&lt;/span&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, let&#39;s take an iterative approach. We will get something working quickly and then enhance it, so firstly let&#39;s just only allow actions to take place if the context is `testing&#39;.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-name function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;KUBECTL_PATH&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${&lt;span class=&quot;token environment constant&quot;&gt;HOME&lt;/span&gt;}&lt;/span&gt;/kubernetes/kubectl&quot;&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;CURRENT_CLUSTER&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;KUBECTL_PATH&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; config current-context&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
  
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$CURRENT_CLUSTER&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;testing&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
  
  &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${KUBECTL_PATH}&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$@&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

main &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$@&lt;/span&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the above, we are aborting if the current kube context is not &lt;code&gt;testing&lt;/code&gt; and if it is, we continue, passing through the original arguments to the real &lt;code&gt;kubectl&lt;/code&gt; binary.&lt;/p&gt;
&lt;p&gt;Now, rather than just exiting early, let&#39;s make the script ask us to confirm if we intend to perform the action.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-name function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;KUBECTL_PATH&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${&lt;span class=&quot;token environment constant&quot;&gt;HOME&lt;/span&gt;}&lt;/span&gt;/kubernetes/kubectl&quot;&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;CURRENT_CLUSTER&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;KUBECTL_PATH&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; config current-context&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
  
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$CURRENT_CLUSTER&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;testing&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
     &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; gum confirm &lt;span class=&quot;token string&quot;&gt;&quot;WARNING: This will run against &#39;&lt;span class=&quot;token variable&quot;&gt;${CURRENT_CLUSTER}&lt;/span&gt;&#39;. Are you sure you want to do this?&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;false&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--timeout&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;10s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
        &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;CANCELLED: Aborting command; that was close!&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;2&lt;/span&gt;
        &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
  
  &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${KUBECTL_PATH}&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$@&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

main &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$@&lt;/span&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Awesome, now, by using gum, we will be asked if we are sure that we wish to execute the command against a production cluster.&lt;/p&gt;
&lt;p&gt;We could stop there, as this fulfils the objective that we set out with, but there are some operations that I don&#39;t need to be asked to confirm, even for production. Things like&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;kubectl get&lt;/strong&gt; when looking at cluster state&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kubectl logs&lt;/strong&gt; when grabbing pod logs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kubectl version&lt;/strong&gt; when looking at what binary version I am running&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So let&#39;s enhance the script to exclude those, and some more &#39;safe&#39; commands. What you deem to be a safe command may differ, and that is fine, edit as appropriate.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-name function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;KUBECTL_PATH&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${&lt;span class=&quot;token environment constant&quot;&gt;HOME&lt;/span&gt;}&lt;/span&gt;/kubernetes/kubectl&quot;&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;CURRENT_CLUSTER&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;KUBECTL_PATH&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; config current-context&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;SAFE_OPERATIONS&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;get&quot;&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;explain&quot;&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;describe&quot;&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;logs&quot;&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;completion&quot;&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;config&quot;&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;api-resources&quot;&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;version&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;SAFE_OPERATIONS_PIPE_DELIMITED&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;&lt;span class=&quot;token environment constant&quot;&gt;IFS&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;|&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${SAFE_OPERATIONS&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;*&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;}&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$CURRENT_CLUSTER&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;testing&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-qvE&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^(&lt;span class=&quot;token variable&quot;&gt;${SAFE_OPERATIONS_PIPE_DELIMITED}&lt;/span&gt;)$&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; gum confirm &lt;span class=&quot;token string&quot;&gt;&quot;WARNING: This will run against &#39;&lt;span class=&quot;token variable&quot;&gt;${CURRENT_CLUSTER}&lt;/span&gt;&#39;. Are you sure you want to do this?&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;false&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--timeout&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;10s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
        &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;CANCELLED: Aborting command; that was close!&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;2&lt;/span&gt;
        &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;

  &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${KUBECTL_PATH}&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$@&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

main &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$@&lt;/span&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Boom! Now we will only be asked to confirm potentially destructive actions (&lt;code&gt;edit&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;scale&lt;/code&gt; and &lt;code&gt;apply&lt;/code&gt; etc).&lt;/p&gt;
&lt;custom-element&gt;
    &lt;banner type=&quot;note&quot;&gt;
        You will notice that I have added `completion` to my list of safe commands, this is to allow for kubectl autocompletion to work still, which by the way it very much does with this mechanism. Snazzy.
  &lt;/banner&gt;
&lt;/custom-element&gt;
&lt;h4 id=&quot;ensuring-that-the-wrapper-is-invoked&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/07/preventing-catastrophe-using-gum/#ensuring-that-the-wrapper-is-invoked&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Ensuring that the wrapper is invoked&lt;/h4&gt;
&lt;p&gt;Now, we have a script that wraps &lt;code&gt;kubectl&lt;/code&gt;, but now we need to make sure that it actually gets invoked, not only when I type &lt;code&gt;kubectl&lt;/code&gt; or use an alias, but also if any other application tries to, because I don&#39;t want that modifying production either!&lt;/p&gt;
&lt;p&gt;To achieve this, I created another very small script and called it &lt;code&gt;kubectl&lt;/code&gt;, which I then placed in a location which is referenced in my &lt;code&gt;PATH&lt;/code&gt;. This script simply executes the &lt;code&gt;shim&lt;/code&gt; we created above. This looks like the following&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/bin/bash&lt;/span&gt;
/path/to/where/shim/is/located/kube-shim.sh &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$@&lt;/span&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I then created an alias for my preferred &lt;code&gt;k&lt;/code&gt; invocation in my &lt;code&gt;.zshrc&lt;/code&gt; file that invokes this shim script. Now, anything that attempts to use &lt;code&gt;kubectl&lt;/code&gt; will be routed through the &lt;code&gt;shim&lt;/code&gt; script. You could also if you wanted, just use the one script (i.e. place the &lt;code&gt;shim&lt;/code&gt; in a bin folder and call it &lt;code&gt;kubectl&lt;/code&gt;) but for my setup, I separated them.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/07/preventing-catastrophe-using-gum/#summary&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Summary&lt;/h3&gt;
&lt;p&gt;To summarise, having a safety net around &#39;dangerous&#39; operations on production infrastructure is blissful, obviously, even with mechanisms in place to prevent accidents from happening, complacency should be avoided.&lt;/p&gt;
&lt;p&gt;Hopefully, this post will help you create a similar safety net around some of your binaries, or at least introduce you to gum, which is awesome.&lt;/p&gt;
</description>
                <pubDate>Sat, 07 Oct 2023 00:00:00 +0000</pubDate>
                <dc:creator>James McNee</dc:creator>
                <guid>https://www.jamesmcnee.com/blog/posts/2023/oct/07/preventing-catastrophe-using-gum/</guid>
                    <tag>Bash</tag>
                    <tag>Gum</tag>
                    <tag>Script</tag>
                    <tag>Shell</tag>
                <image>
                    <link>"https://www.jamesmcnee.com/img/blog/posts/blog12.webp"</link>
                    <alt>Drinking glass containing colourful bubble gum balls sat atop a desk with scattered papers</alt>
                    <caption xml:lang="en" type="html">Photo by &lt;a href=&quot;https://unsplash.com/@marvelous?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Marvin Meyer&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/pwQ-rAd8gjU?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;</caption>
                </image>
            </item>
            <item>
                <title>Hardening Security: From Zero to Hero</title>
                <link>https://www.jamesmcnee.com/blog/posts/2023/oct/01/hardening-security-zero-to-hero/</link>
                <description>&lt;h4 id=&quot;preface&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/01/hardening-security-zero-to-hero/#preface&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Preface&lt;/h4&gt;
&lt;p&gt;I think it&#39;s important to preface this article with the following disclaimer... I am by no means a security expert! Everything that you read here is just documentation of what I have implemented for my blog at &lt;a href=&quot;https://jamesmcnee.co.uk/&quot;&gt;JamesMcNee.co.uk&lt;/a&gt;. As you will read though, I have not just made this up off the top of my head. I have used the tools that have been written by security experts to evaluate the hardiness of a website.&lt;/p&gt;
&lt;p&gt;With this in mind, this post serves to explain how I took my blog (the one you&#39;re reading now, unless I have rebuilt it again!) from having a weak &lt;code&gt;D-&lt;/code&gt; score over at &lt;a href=&quot;https://observatory.mozilla.org/&quot;&gt;observatory.mozilla.org&lt;/a&gt; to a smug &lt;code&gt;A+&lt;/code&gt; in a couple of hours (and cups of coffee &lt;i class=&quot;ri-cup-line&quot;&gt;&lt;/i&gt;).&lt;/p&gt;
&lt;h4 id=&quot;but-what-makes-a-site-&#39;secure&#39;%3F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/01/hardening-security-zero-to-hero/#but-what-makes-a-site-&#39;secure&#39;%3F&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; But what makes a site &#39;secure&#39;?&lt;/h4&gt;
&lt;p&gt;Good question, and one that until I saw my abysmal score I didn&#39;t know either! Well, that&#39;s not quite true, there are obvious things like not sending passwords in plain text, using TLS/SSL over HTTPS rather than HTTP etc, but this post focuses on the more nuanced ways that a site can be exploited.&lt;/p&gt;
&lt;p&gt;All of this stemmed from running a scan on &lt;a href=&quot;https://observatory.mozilla.org/&quot;&gt;observatory.mozilla.org&lt;/a&gt; after I saw it linked in a post I was reading. I had already been using Google&#39;s &lt;a href=&quot;https://pagespeed.web.dev/&quot;&gt;Lighthouse&lt;/a&gt; to improve the performance of my site, so when I hit run on the Mozilla Observatory, I braced myself to be informed on how brilliant I am (&lt;em&gt;*cough*&lt;/em&gt; Eleventy and Cloudflare are). So when the results came in and I had been awarded a &lt;code&gt;D-&lt;/code&gt; I could only laugh with shame.&lt;/p&gt;
&lt;h5 id=&quot;so-what-was-wrong%3F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/01/hardening-security-zero-to-hero/#so-what-was-wrong%3F&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; So what was wrong?&lt;/h5&gt;
&lt;p&gt;Well, Observatory found the following issues with my site (I will be exploring these in more detail in the coming sections, so below are the descriptions paraphrased from &lt;a href=&quot;https://developer.mozilla.org/&quot;&gt;MDN&lt;/a&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;span class=&quot;text-red-700&quot;&gt;[-25 Points]&lt;/span&gt; No Content Security Policy (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP&quot;&gt;CSP&lt;/a&gt;)&lt;/strong&gt;: An added layer of security that helps to detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft, to site defacement, to malware distribution.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;span class=&quot;text-red-700&quot;&gt;[-20 Points]&lt;/span&gt; No HTTP Strict Transport Security (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/HSTS&quot;&gt;HSTS&lt;/a&gt;)&lt;/strong&gt;: Allows a website to inform the browser that it should never load the site using HTTP and should automatically convert all attempts to access the site using HTTP to HTTPS requests instead. It consists of one HTTP header, &lt;code&gt;Strict-Transport-Security&lt;/code&gt;, sent by the server with the resource.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;span class=&quot;text-red-700&quot;&gt;[-20 Points]&lt;/span&gt; No X-Frame-Options (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options&quot;&gt;XFO&lt;/a&gt;)&lt;/strong&gt;: Used to indicate whether a browser should be allowed to render a page in a &lt;code&gt;&amp;lt;frame&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;embed&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt;. Sites can use this to avoid click-jacking attacks, by ensuring that their content is not embedded into other sites.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;span class=&quot;text-red-700&quot;&gt;[-10 Points]&lt;/span&gt; No X-XSS-Protection (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection&quot;&gt;XXSS&lt;/a&gt;)&lt;/strong&gt;: A feature of Internet Explorer, Chrome and Safari that stops pages from loading when they detect reflected cross-site scripting (XSS) attacks. These protections are largely unnecessary in modern browsers when sites implement a strong &lt;code&gt;CSP&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If one thing is clear, it&#39;s that my team will not be winning whatever game we&#39;re playing with a scorecard like that... There were some positive results in the report too, alas I cannot take credit for those, they were because my host (&lt;a href=&quot;https://pages.cloudflare.com/&quot;&gt;Cloudflare Pages&lt;/a&gt;) had taken care of them for me.&lt;/p&gt;
&lt;p&gt;Let&#39;s work through that list, adding a CSP seems to be the biggest hitter as it will not only wipe out that top negative report (CSP), but it will also remove the last one (XXSS).&lt;/p&gt;
&lt;div class=&quot;divider&quot;&gt;&lt;i class=&quot;ri-lock-line&quot;&gt;&lt;/i&gt;&lt;/div&gt;
&lt;h4 id=&quot;implementing-a-content-security-policy-(csp)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/01/hardening-security-zero-to-hero/#implementing-a-content-security-policy-(csp)&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Implementing a Content Security Policy (CSP)&lt;/h4&gt;
&lt;p&gt;I have an admission to make, although I have heard those three letters banded around both online and in my workplace, I never really understood what a CSP was. It always seemed to be some mythical beast that front-end folks would cheer about once conquered as if they had just slain a dragon. This gave me reason enough to avoid knowing more about it, that is of course until I did not really have a choice!&lt;/p&gt;
&lt;p&gt;So, assuming that you have already read the definition above from MDN, and pulled a face at the vagueness of it, I shall attempt to summarise. A CSP is an HTTP header that instructs clients (browsers) where the server expects content to be loaded from, thus preventing assets from loading from an unknown source. It describes where things like images, video, scripts and styles are allowed to originate from and each of these are individually addressable within the header.&lt;/p&gt;
&lt;div class=&quot;md:ml-6 bg-base-300 md:bg-transparent&quot;&gt;
&lt;h5 id=&quot;a-brief-note-on-the-%3Cmeta%3E-tag&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/01/hardening-security-zero-to-hero/#a-brief-note-on-the-%3Cmeta%3E-tag&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; A brief note on the &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag&lt;/h5&gt;
&lt;p&gt;It&#39;s possible to emulate HTTP response headers (to varying degrees of success) using the &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag, and setting the &lt;code&gt;http-equiv&lt;/code&gt; attribute to the name of the header you&#39;re trying to use. If you have never come across this concept before, you can read more about it over on &lt;a href=&quot;https://www.w3.org/International/questions/qa-http-and-lang#meta_summary&quot;&gt;this page by w3&lt;/a&gt;, the standards authority for the web.&lt;/p&gt;
&lt;p&gt;It is possible to have a CSP implemented using a &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag, but there are some caveats (beyond the fact that using this tag is discouraged), the main one is that CSP reporting (more on this later) is not supported using this approach. Some additional features that are also not supported when using the meta tag approach, you can read more about them over at &lt;a href=&quot;https://content-security-policy.com/examples/meta/&quot;&gt;content-security-policy.com&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;As touched on, a CSP header comprises rules targeting the various pieces of dynamic content that a site can load, here are the ones that were important to me, along with where I wanted to allow content from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;img-src&lt;/code&gt;&lt;/strong&gt;: Allow images to be loaded from any origin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;media-src&lt;/code&gt;&lt;/strong&gt;: Allow audio/video to be loaded from any origin too.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;style-src&lt;/code&gt;&lt;/strong&gt;: Allow styles to only be loadable from my own origins jamesmcnee[.co.uk | .com], along with styles from &lt;a href=&quot;https://giscus.app/&quot;&gt;giscus&lt;/a&gt;, which I use for my post comments section.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;script-src&lt;/code&gt;&lt;/strong&gt;: Allow scripts to be loadable from my own origins and giscus.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;frame-src&lt;/code&gt;&lt;/strong&gt;: Allow &lt;code&gt;i-frames&lt;/code&gt; etc to connect to my own origins and giscus.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;frame-ancestors&lt;/code&gt;&lt;/strong&gt;: Do not allow my site to be i-framed, anywhere. If I need to do this for my own origin in the future, I&#39;ll open it up.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;object-src&lt;/code&gt;&lt;/strong&gt;: Do not allow &lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;embed&amp;gt;&lt;/code&gt; elements.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most of these rules seem simple enough, the tricky part comes from &lt;code&gt;inline-scripts&lt;/code&gt; and &lt;code&gt;inline-styles&lt;/code&gt; i.e. the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags, and also directives like &lt;code&gt;onclick=&amp;quot;doSomething()&amp;quot;&lt;/code&gt; and &lt;code&gt;style=&amp;quot;color: blue;&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;md:ml-6&quot;&gt;
&lt;h5 id=&quot;why-would-inline-scripts-and-styles-be-a-problem%3F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/01/hardening-security-zero-to-hero/#why-would-inline-scripts-and-styles-be-a-problem%3F&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Why would inline scripts and styles be a problem?&lt;/h5&gt;
&lt;p&gt;I am going to focus on the first of these two because the exploit is easier to demonstrate, but you can read more about how styles can be exploited in this insightful and detailed &lt;a href=&quot;https://stackoverflow.com/a/41925838&quot;&gt;&lt;i class=&quot;ri-stack-overflow-line&quot;&gt;&lt;/i&gt; Stack Overflow answer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Imagine the following script that displays what the user searched for:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;script type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; searchTerm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getQueryParam&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;term&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;search-results&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;innerHTML&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;h1&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;searchTerm&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;/h1&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;script&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This looks harmless enough, but what about if I set my url to &lt;code&gt;mydomain.com?term=&amp;lt;script&amp;gt;alert(&amp;quot;EVIL&amp;quot;)&amp;lt;/script&amp;gt;&lt;/code&gt;? All of a sudden I have injected code into the webpage (alert is used as a display that arbitrary code can be executed), this could be exploited to make the browser do anything!&lt;/p&gt;
&lt;p&gt;Obviously, if a bad actor wants to do this to their own browser, then all the power to them, the issue comes when this URL is hidden inside a link somewhere else and an unsuspecting party clicks it.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Okay, I am convinced, inline scripts are bad... But they are so convenient... I had a few small inline scripts on various pages throughout my blog that made sense to live there. This brings me to the workaround/solution for the inline script problem...&lt;/p&gt;
&lt;p&gt;You can do one of four things, listed in order of general preference sentiment:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Move any inline scripts into separate script files and load them into your site as you would for external dependencies.&lt;/li&gt;
&lt;li&gt;Use a &lt;a href=&quot;https://content-security-policy.com/nonce/&quot;&gt;nonce&lt;/a&gt; (number once) to bind the HTTP response from the server and the scripts returned in the body. As demonstrated here.&lt;/li&gt;
&lt;li&gt;Take a &lt;a href=&quot;https://content-security-policy.com/hash/&quot;&gt;hash&lt;/a&gt; of the script&#39;s content and add this to the CSP to mark the script as safe/allowed.&lt;/li&gt;
&lt;li&gt;Last resort, you can allow &lt;code&gt;inline-scripts&lt;/code&gt; in your CSP, but this greatly weakens the protections a CSP provides. Think carefully before doing this.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In my case, I used a combination of &lt;code&gt;1&lt;/code&gt; (shifting scripts to separate files) and &lt;code&gt;3&lt;/code&gt; (Using a hash of inline scripts/styles), I also removed any random &lt;code&gt;style&lt;/code&gt; attributes that I had placed on elements with tailwind classes. I will not detail how to generate the hash, because it&#39;s explained &lt;a href=&quot;https://content-security-policy.com/hash/&quot;&gt;well on the content-security-policy.com&lt;/a&gt; website, as are most CSP concepts, so it&#39;s worth a browse.&lt;/p&gt;
&lt;p&gt;I actually believe that using the nonce technique is a good option, and if it was easily doable for my setup, I may have chosen to do this rather than move scripts out into their own files. Alas, because I have a static site, hosted by Cloudflare Pages, I cannot easily inject a nonce value onto each script tag per request without using something like CF workers. I&#39;d rather avoid the additional complexity and potential cost (you get so much for free), and just use the other two mechanisms.&lt;/p&gt;
&lt;div class=&quot;md:ml-6 bg-base-300 md:bg-transparent&quot;&gt;
&lt;h5 id=&quot;a-brief-note-on-csp-reporting&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/01/hardening-security-zero-to-hero/#a-brief-note-on-csp-reporting&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; A brief note on CSP reporting&lt;/h5&gt;
&lt;p&gt;CSP&#39;s provide the ability to report violations to a given endpoint. I did not end up actually using this feature, but it can be a good way to implement a CSP in a limited fashion, by enabling report only mode, to make sure that nothing is missed.&lt;/p&gt;
&lt;p&gt;You can read more about this feature, including how to utilise it, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to&quot;&gt;over on MDN here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h5 id=&quot;building-the-csp&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/01/hardening-security-zero-to-hero/#building-the-csp&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Building the CSP&lt;/h5&gt;
&lt;p&gt;Right, let&#39;s actually build the CSP header.&lt;/p&gt;
&lt;p&gt;A CSP header looks like the following:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;  &lt;span class=&quot;token key atrule&quot;&gt;Content-Security-Policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; default&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;src &#39;self&#39; domain.tld &lt;span class=&quot;token important&quot;&gt;*.domain.tld;&lt;/span&gt; img&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;src &lt;span class=&quot;token important&quot;&gt;*;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each directive (e.g. &lt;code&gt;default-src&lt;/code&gt;, &lt;code&gt;image-src&lt;/code&gt;, etc) is separated by a semi-colon (&lt;code&gt;;&lt;/code&gt;) and each source (e.g. &lt;code&gt;domain.tld&lt;/code&gt;) by a space.&lt;/p&gt;
&lt;p&gt;My host (&lt;a href=&quot;https://pages.cloudflare.com/&quot;&gt;Cloudflare Pages&lt;/a&gt;) supports the somewhat well-known pattern of accepting a &lt;code&gt;_headers&lt;/code&gt; file that defines additional response headers to serve on content, many static site hosts such as Netlify and GitHub pages also subscribe to this. This file takes the form of a path match pattern and then key-value based headers. e.g.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;/blog/*
  &lt;span class=&quot;token key atrule&quot;&gt;X-MY-CUSTOM-HEADER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Value&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above example, any request to a path that starts with &lt;code&gt;/blog&lt;/code&gt; e.g. &lt;code&gt;/blog/my-page&lt;/code&gt; would have the &lt;code&gt;X-MY-CUSTOM-HEADER&lt;/code&gt; response header attached.&lt;/p&gt;
&lt;p&gt;Initially, I began to implement the CSP header manually by adding to my &lt;code&gt;_headers&lt;/code&gt; file a &lt;code&gt;Content-Security-Policy&lt;/code&gt; header on the &lt;code&gt;/*&lt;/code&gt; root. But this quickly became unwieldy to work with as I ended up needing to duplicate strings (like my domain for example). As I am using Eleventy (which I briefly wrote about my switch to in this &lt;a href=&quot;https://www.jamesmcnee.com/blog/posts/2023/sep/29/eleventy-search/#switching-to-eleventy&quot;&gt;previous post&lt;/a&gt;) I decided that the &lt;code&gt;_headers&lt;/code&gt; could just be a templated file and I could then use Javascript to add a &lt;a href=&quot;https://www.11ty.dev/docs/shortcodes/&quot;&gt;shortcode&lt;/a&gt; to build my CSP.&lt;/p&gt;
&lt;p&gt;So now I had shifted the big unwieldy string from a text file into Javascript, which whilst better, was still pretty grim having to do lots of string interpolation. Before I went off and built a little function to generate the CSP, I googled and found that someone already had created a &lt;a href=&quot;https://www.npmjs.com/package/content-security-policy-builder&quot;&gt;content-security-policy-builder&lt;/a&gt; library! Horray! This library allows you to pass in a structured object representing the CSP and it will do the string templating for you.&lt;/p&gt;
&lt;p&gt;In the end, this is what I ended up with&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addShortcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;csp&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cspBuilder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;content-security-policy-builder&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; myDomains &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;jamesmcnee.com&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;jamesmcnee.co.uk&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; defaultSrc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;self&#39; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;myDomains&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39; &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;myDomains&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;*.&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; d&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39; &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; allowedInlineScriptHashes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&#39;sha256-smKXypSFxzKD9ffC0rSshp292sAzf/X7cquCvQEA8XA=&#39;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// The post search script on index&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cspBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;directives&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string-property property&quot;&gt;&#39;default-src&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; defaultSrc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string-property property&quot;&gt;&#39;img-src&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;*&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string-property property&quot;&gt;&#39;media-src&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;*&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string-property property&quot;&gt;&#39;style-src&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;defaultSrc&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; https://giscus.app&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string-property property&quot;&gt;&#39;script-src&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;defaultSrc&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;allowedInlineScriptHashes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;unsafe-inline&#39;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;allowedInlineScriptHashes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hash&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;hash&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39; &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; https://giscus.app&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string-property property&quot;&gt;&#39;script-src-attr&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;unsafe-hashes&#39; &#39;sha256-1jAmyYXcRq6zFldLe/GCgIDJBiOONdXjTLgEFMDnDSM=&#39;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// This is to allow the preloading of stylesheets&lt;/span&gt;
            &lt;span class=&quot;token string-property property&quot;&gt;&#39;frame-src&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;defaultSrc&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; https://giscus.app&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string-property property&quot;&gt;&#39;frame-ancestors&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;none&#39;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string-property property&quot;&gt;&#39;object-src&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;none&#39;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;custom-element&gt;
    &lt;banner type=&quot;info&quot;&gt;
        Now, let me first address `unsafe-inline` here, before I get comments (I wish) telling me that I said it was bad and then went and used it! Well it&#39;s actually considered best practice to add `unsafe-inline` as a fallback for older browsers that don&#39;t support the hashing of scripts, if the browser does, it will be discounted. Therefore, I conditionally add it based on if I have a script hash or not!
  &lt;/banner&gt;
&lt;/custom-element&gt;
&lt;p&gt;I can then use this shortcode in my &lt;code&gt;_headers&lt;/code&gt; template file which now looks like&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;permalink&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; _headers
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
/*
  &lt;span class=&quot;token key atrule&quot;&gt;Content-Security-Policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;% csp %&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;{% csp %}&lt;/code&gt; here will be expanded during the eleventy build to the actual full CSP. In-case you&#39;re curious, this is what the templated file looks like using the above settings in the builder&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;/*
  &lt;span class=&quot;token key atrule&quot;&gt;Content-Security-Policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; default&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;src &#39;self&#39; jamesmcnee.com jamesmcnee.co.uk &lt;span class=&quot;token important&quot;&gt;*.jamesmcnee.com&lt;/span&gt; &lt;span class=&quot;token important&quot;&gt;*.jamesmcnee.co.uk;&lt;/span&gt; img&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;src &lt;span class=&quot;token important&quot;&gt;*;&lt;/span&gt; media&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;src &lt;span class=&quot;token important&quot;&gt;*;&lt;/span&gt; style&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;src &#39;self&#39; jamesmcnee.com jamesmcnee.co.uk &lt;span class=&quot;token important&quot;&gt;*.jamesmcnee.com&lt;/span&gt; &lt;span class=&quot;token important&quot;&gt;*.jamesmcnee.co.uk&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//giscus.app; script&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;src &#39;self&#39; jamesmcnee.com jamesmcnee.co.uk &lt;span class=&quot;token important&quot;&gt;*.jamesmcnee.com&lt;/span&gt; &lt;span class=&quot;token important&quot;&gt;*.jamesmcnee.co.uk&lt;/span&gt; &#39;unsafe&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;inline&#39; &#39;sha256&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;smKXypSFxzKD9ffC0rSshp292sAzf/X7cquCvQEA8XA=&#39; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//giscus.app; script&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;src&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;attr &#39;unsafe&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;hashes&#39; &#39;sha256&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;1jAmyYXcRq6zFldLe/GCgIDJBiOONdXjTLgEFMDnDSM=&#39;; frame&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;src &#39;self&#39; jamesmcnee.com jamesmcnee.co.uk &lt;span class=&quot;token important&quot;&gt;*.jamesmcnee.com&lt;/span&gt; &lt;span class=&quot;token important&quot;&gt;*.jamesmcnee.co.uk&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//giscus.app; frame&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;ancestors &#39;none&#39;; object&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;src &#39;none&#39;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Right-o CSP done, &lt;code&gt;+25 points&lt;/code&gt;, &lt;code&gt;+10 points&lt;/code&gt; for handing XSS protection (implicit in the CSP) and &lt;code&gt;+5 points&lt;/code&gt; for having a strong CSP! Next...&lt;/p&gt;
&lt;div class=&quot;divider&quot;&gt;&lt;i class=&quot;ri-lock-line&quot;&gt;&lt;/i&gt;&lt;/div&gt;
&lt;h4 id=&quot;implementing-http-strict-transport-security-(hsts)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/01/hardening-security-zero-to-hero/#implementing-http-strict-transport-security-(hsts)&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Implementing HTTP Strict Transport Security (HSTS)&lt;/h4&gt;
&lt;p&gt;Next on the agenda was to tackle &lt;code&gt;HSTS&lt;/code&gt;, which if you remember is essentially not allowing HTTP traffic and enforcing that it be upgraded to HTTPS.&lt;/p&gt;
&lt;p&gt;This one is much simpler than the last (you will be glad to hear), it is essentially just setting a response header, as I described at the end of the previous section, I can do this on my host using a &lt;code&gt;_headers&lt;/code&gt; file. The header consists of three elements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Max Age &lt;sup&gt;(*required)&lt;/sup&gt;: The time, in seconds, that the browser should remember that a site is only to be accessed using HTTPS. Recommended to be set as 2 years.&lt;/li&gt;
&lt;li&gt;Include Sub Domains: If specified the rule will be applied to all subdomains as well as the current domain.&lt;/li&gt;
&lt;li&gt;Preload: An optional parameter than when specified (must also have a max age of &amp;gt; 1 year, and include subdomains set to true) will include your domain in a list that browsers use to determine if your domain should only use HTTPS, without even making a request.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For this one, I just applied all of the settings, I set my max age as &lt;code&gt;2 years&lt;/code&gt;, included sub-domains and opted into the preload register. My &lt;code&gt;_headers&lt;/code&gt; file now looks like:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;permalink&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; _headers
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
/*
    &lt;span class=&quot;token key atrule&quot;&gt;Content-Security-Policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;% csp %&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;Strict-Transport-Security&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; max&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;age=63072000; includeSubDomains; preload&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another one done, &lt;code&gt;+20 points&lt;/code&gt; for &lt;s&gt;gryffindor&lt;/s&gt; me! Next!&lt;/p&gt;
&lt;div class=&quot;divider&quot;&gt;&lt;i class=&quot;ri-lock-line&quot;&gt;&lt;/i&gt;&lt;/div&gt;
&lt;h4 id=&quot;implementing-x-frame-options-(xfo)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/01/hardening-security-zero-to-hero/#implementing-x-frame-options-(xfo)&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Implementing X-Frame-Options (XFO)&lt;/h4&gt;
&lt;p&gt;The final one on the list was to tackle &lt;code&gt;XFO&lt;/code&gt;, you will remember that this is essentially setting whether your site should be allowed to be rendered inside an &lt;code&gt;i-frame&lt;/code&gt; etc. Allowing your site to be embedded can be used to facilitate a &lt;code&gt;click-jacking&lt;/code&gt; attack, whereby buttons on your site, could be hidden behind elements so that the user doesn&#39;t know that they are clicking them.&lt;/p&gt;
&lt;p&gt;This header &lt;code&gt;X-Frame-Options&lt;/code&gt; can be set to either &lt;code&gt;DENY&lt;/code&gt; or &lt;code&gt;SAMEORIGIN&lt;/code&gt;, the former blocks being embedded altogether, while the latter allows for you to i-frame your own site.&lt;/p&gt;
&lt;p&gt;As I do not have a good reason to allow this, even on my own origin, I opted to set this to deny. My final &lt;code&gt;_headers&lt;/code&gt; file looks like this&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;permalink&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; _headers
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
/*
    &lt;span class=&quot;token key atrule&quot;&gt;Content-Security-Policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;% csp %&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;Strict-Transport-Security&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; max&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;age=63072000; includeSubDomains; preload
    &lt;span class=&quot;token key atrule&quot;&gt;X-Frame-Options&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; DENY&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Boom, that&#39;s a wrap, final one done, &lt;code&gt;+20 points&lt;/code&gt; and an additional &lt;code&gt;+5 points&lt;/code&gt; too apparently!&lt;/p&gt;
&lt;h4 id=&quot;summarising&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/oct/01/hardening-security-zero-to-hero/#summarising&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Summarising&lt;/h4&gt;
&lt;p&gt;If you made it this far, thanks, I hope it was useful! In this post, I covered how I took my static Eleventy site from having a security rating of &lt;code&gt;D-&lt;/code&gt; score over at &lt;a href=&quot;https://observatory.mozilla.org/&quot;&gt;observatory.mozilla.org&lt;/a&gt; to &lt;code&gt;A+&lt;/code&gt; in a few hours, and now, hopefully, you can do the same!&lt;/p&gt;
&lt;p&gt;The main things that needed doing on my site, and that I covered were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Implementing a strong CSP to ensure that content is only being loaded from origins I expect&lt;/li&gt;
&lt;li&gt;Adding an HSTS header to ensure that HTTPS is always used when accessing my site...&lt;/li&gt;
&lt;li&gt;Adding an XFO header to ensure that my site is not i-framed anywhere, helping to reduce &lt;code&gt;click-jacking&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
                <pubDate>Sun, 01 Oct 2023 00:00:00 +0000</pubDate>
                <dc:creator>James McNee</dc:creator>
                <guid>https://www.jamesmcnee.com/blog/posts/2023/oct/01/hardening-security-zero-to-hero/</guid>
                    <tag>CSP</tag>
                    <tag>Security</tag>
                    <tag>Javascript</tag>
                    <tag>Eleventy</tag>
                    <tag>HTTP</tag>
                    <tag>Headers</tag>
                <image>
                    <link>"https://www.jamesmcnee.com/img/blog/posts/blog11.webp"</link>
                    <alt>Desk with smart phone laid upon it showing a screen with a padlock and the word secured below</alt>
                    <caption xml:lang="en" type="html">Photo by &lt;a href=&quot;https://unsplash.com/@danny144?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Dan Nelson&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/ah-HeguOe9k?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;</caption>
                </image>
            </item>
            <item>
                <title>Adding dynamic search to a static Eleventy site</title>
                <link>https://www.jamesmcnee.com/blog/posts/2023/sep/29/eleventy-search/</link>
                <description>&lt;h4 id=&quot;switching-to-eleventy&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/sep/29/eleventy-search/#switching-to-eleventy&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Switching to Eleventy&lt;/h4&gt;
&lt;p&gt;Until recently, my blog at &lt;a href=&quot;https://jamesmcnee.co.uk/&quot;&gt;JamesMcNee.co.uk&lt;/a&gt; was a single-page application built using the Angular framework a few years ago. I decided to build the blog using Angular because it was, and still is, the framework that I am most comfortable with. In my day-to-day work, if I need to whip up a quick web app for users to interact with a system that I am building, my go-to will be an Angular-based SPA.&lt;/p&gt;
&lt;p&gt;With time, I started to call into question my decision to build a blog using the Angular framework. The site was using an old version of the framework and upgrading was taking work, it was using a bespoke custom CMS that I built, powered by a Java API and MongoDB store. It also had a lot of custom CSS, which is not my forte. I wanted something lighter, faster to load and easy to host and maintain.&lt;/p&gt;
&lt;p&gt;It was time to explore one of these new-fangled static site generators that everyone has been raving about, promising to solve all the problems I had with my current set up. After a bit of googling, I decided to try out &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt;, and I was immediately impressed by both the minimalism and flexibility it provides. So a few hours later, I had ported the pages and content, mainly keeping in the same style as the previous incarnation, but with the extra goodness of using &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind&lt;/a&gt; to do it.&lt;/p&gt;
&lt;h4 id=&quot;searching-for-a-way-to-search&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/sep/29/eleventy-search/#searching-for-a-way-to-search&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Searching for a way to search&lt;/h4&gt;
&lt;p&gt;The benefits that a static site provides in terms of web performance and the ease of adding new content are great, but it does pose a challenge for certain features that would be trivial with an API, in this case, search... I wanted to add a search component to my blog to allow for quick finding of posts by title and synopsis keywords.&lt;/p&gt;
&lt;p&gt;So, like any good developer, I immediately went to Google in hopes of finding someone who has already done the work for me! Alas, my search did not yield a good example of how to implement what I wanted.&lt;/p&gt;
&lt;h4 id=&quot;building-it-from-lab&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/sep/29/eleventy-search/#building-it-from-lab&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Building it from lab&lt;/h4&gt;
&lt;p&gt;After not having found a good example (there may be good examples, I just did not find one), I set in thinking about how I could implement it myself.&lt;/p&gt;
&lt;p&gt;The back of the napkin requirements were simple, the solution should:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Plug into Eleventy&#39;s collection framework, so that I could use the same collection of blog posts for rendering and searching.&lt;/li&gt;
&lt;li&gt;Have the ability to dynamically show results within a component, and not require linking off to a separate page.&lt;/li&gt;
&lt;li&gt;Be entirely totally self-contained within the repository, i.e. not using something like Google Site Search.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;creating-a-search-payload&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/sep/29/eleventy-search/#creating-a-search-payload&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Creating a search payload&lt;/h5&gt;
&lt;p&gt;The first step to implement this was to get a data source in place, a simple JSON page served on a static route should do it. Here is an example of a nunjucks template that gives the search information for each post on my blog:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;search.json.njk&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;---json
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;permalink&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;search.json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;eleventyExcludeFromCollections&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://www.jamesmcnee.com/&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
---
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;results&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;%- for post in collections.posts | reverse %&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;%- set absolutePostUrl = post.url | absoluteUrl(metadata.url) %&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;{{ absolutePostUrl }}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;{{ post.url }}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;{{ post.data.title }}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;synopsis&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;{{ post.data.synopsis }}&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;% if not loop.last %&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;% endif %&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;%- endfor %&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This template yields an endpoint at &lt;code&gt;/search.json&lt;/code&gt; with the following:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;results&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    ...
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://www.jamesmcnee.com/blog/posts/2023/sep/29/eleventy-search/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/blog/posts/2023/sep/29/eleventy-search/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Adding dynamic search to a static Eleventy site&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;synopsis&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;This post covers how I went about adding a dynamic search element to a static Eleventy based blog, without compromising on the benefits of SSG.&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    ...
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should be enough information to facilitate a search by keyword in the posts title or synopsis text and allow for linking off to the full article.&lt;/p&gt;
&lt;h5 id=&quot;creating-the-markup&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/sep/29/eleventy-search/#creating-the-markup&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Creating the markup&lt;/h5&gt;
&lt;p&gt;Next, we need a search component, essentially a text input which can show search results below it.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;flex&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;flex-1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;form-control w-full max-w-xs&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
                &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;search&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
                    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;label-text&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Search Posts:&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
                    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;label-text-alt&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;e.g. &quot;Patch&quot;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
                &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
                &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;search&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;autocomplete&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;off&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;search&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;placeholder&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Type here&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;input input-bordered w-full max-w-xs&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;search-results-container&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;card shadow-xl bg-base-100 absolute border-gray-600 border-solid border-2 translate-y-2 left-0 ml-[2%] w-[94%] z-50 md:left-auto md:w-96 md:-translate-x-1/2 lg:-translate-x-1/4 invisible&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
                &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;search-results&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;card-body p-4 text-sm max-h-96 overflow-y-scroll&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;flex justify-center mb-2&quot;&gt;
    &lt;div class=&quot;relative&quot;&gt;
        &lt;div class=&quot;form-control mx-auto sm:w-full md:w-2/3&quot;&gt;
            &lt;label class=&quot;label&quot; for=&quot;search&quot;&gt;
                &lt;span class=&quot;label-text&quot;&gt;Search Posts:&lt;/span&gt;
                &lt;span class=&quot;label-text-alt&quot;&gt;e.g. &quot;Patch&quot;&lt;/span&gt;
            &lt;/label&gt;
            &lt;input id=&quot;search&quot; autocomplete=&quot;off&quot; type=&quot;search&quot; placeholder=&quot;Type here&quot; class=&quot;input input-bordered w-full&quot; /&gt;
        &lt;/div&gt;
        &lt;div id=&quot;search-results-container&quot; class=&quot;card shadow-xl bg-base-100 border-gray-600 border-solid border-2 -translate-x-[50%] left-[50%] mt-2&quot;&gt;
            &lt;div id=&quot;search-results&quot; class=&quot;card-body p-4 text-sm max-h-96 overflow-y-scroll&quot;&gt;
                &lt;div class=&quot;cursor-pointer&quot;&gt;
                    &lt;a class=&quot;font-bold mb-0&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/sep/29/eleventy-search/#&quot;&gt;Post One&lt;/a&gt;&lt;p class=&quot;mb-0&quot;&gt;This is the synopsis for the first post, it gives a brief description as to its content.&lt;/p&gt;
                &lt;/div&gt;
                &lt;span class=&quot;divider mt-0.5 mb-0.5&quot;&gt;&lt;/span&gt;
                &lt;div class=&quot;cursor-pointer&quot;&gt;
                    &lt;a class=&quot;font-bold mb-0&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/sep/29/eleventy-search/#&quot;&gt;Post Two&lt;/a&gt;&lt;p class=&quot;mb-0&quot;&gt;This is the synopsis for the second post, it gives a brief description as to its content.&lt;/p&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The above is what I came up with, if you want to use it for inspiration, you can find the full &lt;a href=&quot;https://github.com/JamesMcNee/static-blog-site/blob/8d110bcdd810d507ad4d7255436e4ab3f5980c16/src/index.njk#L64-L78&quot;&gt;markup for it over on my GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h5 id=&quot;implementing-the-dynamic-results&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/sep/29/eleventy-search/#implementing-the-dynamic-results&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Implementing the dynamic results&lt;/h5&gt;
&lt;p&gt;Now that I had the markup for searching and displaying the results, I just needed to write a bit of Javascript to wire it up to the search payload we created earlier... For this, I just used an inline &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; block as it seemed the simplest solution.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text/javascript&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
    &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;searchShouldBeVisible&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;search-results-container&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;classList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;visible &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;invisible&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;visible&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;search-results-container&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;classList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;visible &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;visible&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;invisible&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;term&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Set search results to invisible if the term is falsy (empty string, or null/undefined)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;term&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;searchShouldBeVisible&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Set search results to visible&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;searchShouldBeVisible&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Fetch the full search payload&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; searchResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/search.json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; responseBody &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; searchResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Filter the results array for items that contain the term (ignoring case)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; filtered &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; responseBody&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;results&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;synopsis&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;term&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Get the DOM element to populate with results&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; resultsDiv &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;search-results&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Special handling if nothing found&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filtered&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            resultsDiv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Nothing found...&#39;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Build up the inner HTML for the search results div&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; compiledString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; filtered&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; post &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; filtered&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; card &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;div class=&quot;cursor-pointer&quot; onclick=&quot;window.location = &#39;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;;&quot;&gt;&amp;lt;a class=&quot;font-bold mb-0&quot; href=&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;/a&gt;&amp;lt;p class=&quot;mb-0&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;synopsis&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;/p&gt;&amp;lt;/div&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
            compiledString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;compiledString&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;card&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; filtered&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                compiledString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;compiledString&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;span class=&quot;divider mt-0.5 mb-0.5&quot;&gt;&amp;lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Assign the compiled string to the innerHTML for the div&lt;/span&gt;
        resultsDiv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; compiledString
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above is a slightly simplified version of what I ended up with as I also wanted the following features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click outside detection - When the user clicks or taps outside the search results, it should dismiss/hide them.&lt;/li&gt;
&lt;li&gt;Search debounce - As I will be binding to the &lt;code&gt;keyup&lt;/code&gt; event, to request the search payload for every user keystroke would be inefficient. Therefore, it would be beneficial to wait for the user to finish typing before running the search.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can, if desired, have a gander at the &lt;a href=&quot;https://github.com/JamesMcNee/static-blog-site/blob/8d110bcdd810d507ad4d7255436e4ab3f5980c16/src/index.njk#L13-L62&quot;&gt;full implementation&lt;/a&gt; of this on the GitHub repository for this blog. Do note that some of the required functions are off in other files though.&lt;/p&gt;
&lt;h5 id=&quot;tying-it-all-together&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/sep/29/eleventy-search/#tying-it-all-together&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Tying it all together&lt;/h5&gt;
&lt;p&gt;All that is left now is to wire up the search input to the function we have created above.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;search&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;autocomplete&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;off&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;search&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;placeholder&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Type here&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;input input-bordered w-full max-w-xs&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; 
       &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;onkeyup&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value javascript language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;w-4/5 md:w-3/5 mx-auto&quot;&gt;
  &lt;img alt=&quot;Image showing a search box with some dummy results under it&quot; class=&quot;mx-auto dark:hidden&quot; width=&quot;100%&quot; src=&quot;https://www.jamesmcnee.com/img/blog/posts/post-content/eleventy-search/search-component-finished.png&quot; /&gt;
  &lt;img alt=&quot;Image showing a search box with some dummy results under it&quot; class=&quot;mx-auto hidden dark:block&quot; width=&quot;100%&quot; src=&quot;https://www.jamesmcnee.com/img/blog/posts/post-content/eleventy-search/search-component-finished-dark.png&quot; /&gt;
&lt;/div&gt;
&lt;h4 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2023/sep/29/eleventy-search/#summary&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Summary&lt;/h4&gt;
&lt;p&gt;In this post we have explored how we can add a bit of dynamic flare to an otherwise static site and implement a useful and fully customisable search, whilst not having to leave the framework!&lt;/p&gt;
&lt;p&gt;If desired, you could extend this to also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Show a thumbnail for each search result&lt;/li&gt;
&lt;li&gt;Dismiss the results when clicking outside (see above)&lt;/li&gt;
&lt;li&gt;Debounce the search element (see above)&lt;/li&gt;
&lt;/ul&gt;
</description>
                <pubDate>Fri, 29 Sep 2023 00:00:00 +0000</pubDate>
                <dc:creator>James McNee</dc:creator>
                <guid>https://www.jamesmcnee.com/blog/posts/2023/sep/29/eleventy-search/</guid>
                    <tag>Eleventy</tag>
                    <tag>Javascript</tag>
                    <tag>Blog</tag>
                    <tag>Search</tag>
                <image>
                    <link>"https://www.jamesmcnee.com/img/blog/posts/blog10.webp"</link>
                    <alt>Autumn forest with a magnifying glass, blurred background but clarity though the glass</alt>
                    <caption xml:lang="en" type="html">Photo by &lt;a href=&quot;https://unsplash.com/@stevenwright?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Steven Wright&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/mq8QogEBy00?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;</caption>
                </image>
            </item>
            <item>
                <title>Gradle Plugin: Adding an external dependency</title>
                <link>https://www.jamesmcnee.com/blog/posts/2022/jan/01/gradle-plugin/</link>
                <description>&lt;p&gt;I recently took on a bit of a side project to create a grade plugin to consolidate some shared build time concerns across a number of projects at my workplace.&lt;/p&gt;
&lt;p&gt;While I thoroughly enjoyed writing the plugin and learned a lot about how Gradle works (I would recommend the exercise to all as a learning experience), I did hit some roadblocks along the way.&lt;/p&gt;
&lt;p&gt;One of these problems is the focus of this post which should hopefully provide some help to others who are trying to solve the same problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Problem statement:&lt;/strong&gt;&lt;br /&gt;
I would like my plugin to add an external dependency to the project it is applied to.&lt;/p&gt;
&lt;custom-element&gt;
    &lt;banner type=&quot;info&quot;&gt;
&lt;p&gt;&lt;strong&gt;Jargon&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Let&#39;s just start by defining some terms that will be used that may not be obvious as to what they refer to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Configuration&lt;/strong&gt;: In Gradle, a configuration in the context of dependencies is the scope that it will be applied within. For example, in a Java project, the common scopes that we see are &lt;code&gt;implementation&lt;/code&gt; and &lt;code&gt;testImplementation&lt;/code&gt;. These two scopes define if the dependency will be available for use in the main application code or only in tests.&lt;/p&gt;
 &lt;/banner&gt;
&lt;/custom-element&gt;
&lt;p&gt;Diving into the code, let&#39;s imagine that we have a brand new plugin (this post will not cover how to create this, but official documentation can be found &lt;a href=&quot;https://docs.gradle.org/current/userguide/custom_plugins.html&quot;&gt;over on the Gradle docs site&lt;/a&gt;). Out main plugin class looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyAwesomePlugin&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Plugin&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Project&lt;/span&gt; project&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adding an external dependency is in theory not actually that much of an issue, we can simply use the &lt;code&gt;project.getDependencies().add()&lt;/code&gt; method. The main issue comes when determining what to pass into this method. Let&#39;s take a look at the signature of the method: &lt;code&gt;Dependency add(String configurationName, Object dependencyNotation)&lt;/code&gt;. We know what &lt;code&gt;configurationName&lt;/code&gt; is (see jargon section above), but what on earth are we supposed to pass as &lt;code&gt;dependencyNotation&lt;/code&gt;?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;String Notation: Simply a String written using Gradle dependency notation e.g. &lt;code&gt;com.mycompany:my-awesome-dependency:1.2.3&lt;/code&gt;. There are also &lt;a href=&quot;https://docs.gradle.org/current/userguide/single_versions.html#simple_version_declaration_semantics&quot;&gt;ways to specify things like strictness&lt;/a&gt; when using these &#39;simple&#39; declarations.&lt;/li&gt;
&lt;li&gt;Map Notation: This is where you pass a &lt;code&gt;Map&amp;lt;String, String&amp;gt;&lt;/code&gt; containing key-value pairs representing the dependency. The documentation on this is either non existent or elusive, but for example: &lt;code&gt;&amp;quot;group&amp;quot;: &amp;quot;com.mycompany&amp;quot;, &amp;quot;name&amp;quot;: &amp;quot;my-awesome-dependency&amp;quot;, &amp;quot;version&amp;quot;: &amp;quot;1.2.3&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is also possible that you pass in an object that implements one of the &lt;code&gt;Dependency&lt;/code&gt; interfaces that the Gradle API provides. I would recommend against this though as there are no default implementations provided and you would therefore need to implement it yourself. This may not be such an issue but there are some methods on the interface that may require somewhat complicated logic to satisfy.&lt;/p&gt;
&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyAwesomePlugin&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Plugin&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Project&lt;/span&gt; project&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        project&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDependencies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;implementation&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;com.mycompany:my-awesome-dependency:1.2.3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that is it! It&#39;s not terribly complicated but it&#39;s also not immediately obvious, so hopefully, this post helps someone out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Closing Remarks:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Gradle API is very flexible and does a lot of &#39;magic&#39;, I have found myself fighting with this and trying to keep things nice and typed but sometimes you have just got to let Gradle do its thing!&lt;/p&gt;
</description>
                <pubDate>Sat, 01 Jan 2022 00:00:00 +0000</pubDate>
                <dc:creator>James McNee</dc:creator>
                <guid>https://www.jamesmcnee.com/blog/posts/2022/jan/01/gradle-plugin/</guid>
                    <tag>Gradle</tag>
                    <tag>Java</tag>
                    <tag>Plugin</tag>
                    <tag>Dependency</tag>
                    <tag>How to</tag>
                <image>
                    <link>"https://www.jamesmcnee.com/img/blog/posts/blog9.jpg"</link>
                    <alt>Jigsaw Puzzle representing a plugin</alt>
                    <caption xml:lang="en" type="html">Photo by &lt;a href=&quot;https://unsplash.com/@sloppyperfectionist?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Hans-Peter Gauster&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;</caption>
                </image>
            </item>
            <item>
                <title>Reverse Proxies: Take care what is being exposed!</title>
                <link>https://www.jamesmcnee.com/blog/posts/2021/mar/03/reverse-proxy-restricting/</link>
                <description>&lt;p&gt;Having a microservice architecture certainly has advantages; it allows a service to be fully independent meaning that it can be both deployed and scaled separately while also allowing for a smaller code base which often helps developers to keep the codebase relatively clean. Microservices also allow for a trimmed down, domain-specific API which is not often the case when dealing with a monolith as it can be tempting to pull in multiple modules to build a single API response; this makes it hard to decouple and refactor.&lt;/p&gt;
&lt;p&gt;One of the downsides to using this architecture though is that it moves away from the single URL / API&#39; approach that is the preferred / most common way to interact with the services of a company. To illustrate this, imagine that a social network company wishes to allow developers to access some of its information, let&#39;s call this company... TwitBook. There are 3 data points that TwitBook wish to expose: &lt;code&gt;User Data&lt;/code&gt;, &lt;code&gt;Post Data&lt;/code&gt; and &lt;code&gt;Message History&lt;/code&gt;. Each of these three data points are available internally via three separate apps, each with their own URL and REST API. TwitBook does not wish to expose these APIs as three individual, detached services but would like them to all appear as one with an API structure such as this: &lt;code&gt;developer.twitbook.tld/api/user/{userId}&lt;/code&gt;, &lt;code&gt;developer.twitbook.tld/api/post/{postId}&lt;/code&gt; and &lt;code&gt;developer.twitbook.tld/api/message/{messageId}&lt;/code&gt;. Having endpoints exposed as a single API is also the most standard way for a front-end to communicate with its back-end service.&lt;/p&gt;
&lt;p&gt;In order to have our cake and eat it too, have a detached microservice architecture but the benefits of a unified API, we can leverage a piece of technology known as the &lt;strong&gt;reverse proxy&lt;/strong&gt;.&lt;/p&gt;
&lt;custom-element&gt;
    &lt;banner type=&quot;info&quot;&gt;
&lt;p&gt;&lt;strong&gt;What is a reverse proxy?&lt;/strong&gt;
&lt;br /&gt;
A reverse proxy in its simplest form is a service that sits in front of multiple other services in which traffic (usually from the internet) is routed via. This layer can be used to add a layer of security, load balance, cache content and/or act as a facade for a microservice architecture (which this post focuses on).
&lt;br /&gt;&lt;/p&gt;
&lt;div class=&quot;text-centre&quot;&gt;&lt;img src=&quot;https://www.jamesmcnee.com/img/blog/posts/post-content/reverse-proxy/reverse-proxy-dark.svg&quot; width=&quot;70%&quot; /&gt;&lt;/div&gt;
Further reading around what a reverse proxy is and how it compares to a regular proxy can be found online such as 
&lt;p&gt;&lt;a href=&quot;https://www.cloudflare.com/en-gb/learning/cdn/glossary/reverse-proxy&quot;&gt;this&lt;/a&gt; article from Cloudflare.&lt;/p&gt;
&lt;/banner&gt;
&lt;/custom-element&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4 id=&quot;sorry...-it&#39;s-time-for-the-other-shoe-to-drop&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2021/mar/03/reverse-proxy-restricting/#sorry...-it&#39;s-time-for-the-other-shoe-to-drop&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; Sorry... It&#39;s time for the other shoe to drop&lt;/h4&gt;
&lt;p&gt;So far, the reverse proxy sounds like a pretty ideal solution to creating the facade of a unified API within a microservice ecosystem, and it absolutely is. The problem that this post describes is not from the use of the technology itself but rather from misuse of it and a potential oversight when configuring a reverse proxy.&lt;/p&gt;
&lt;p&gt;If we consider the earlier example of TwitBook and the desire to expose a single API that provides data from multiple services, a naive configuration for a reverse proxy might look something like this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;/api/user&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user-service.internal.twitbook.tld&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;/api/post&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;post-service.internal.twitbook.tld&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;/api/message&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;messaging-service.internal.twitbook.tld&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we are forwarding any request to &lt;code&gt;developer.twitbook.tld/api/user&lt;/code&gt; to the internal user service and are doing similar for both post and message endpoints. At first glance, everything seems fine here and unfortunately, this is sometimes where the configuration ends. We are able to access any current endpoint &lt;strong&gt;and&lt;/strong&gt; any endpoint that is added to one of these services in the future; hopefully, now the issue is starting to become clear.&lt;/p&gt;
&lt;p&gt;The intention seldom is to expose every endpoint of the downstream service, this is especially true if the service in question has admin endpoints exposed. One could argue here that the admin endpoints should be exposed via a different port or at the very least have auth that is different from that of the &#39;regular&#39; endpoints but not always!&lt;/p&gt;
&lt;br /&gt;
&lt;h4 id=&quot;i&#39;m-convinced!-how-can-we-solve-this%3F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://www.jamesmcnee.com/blog/posts/2021/mar/03/reverse-proxy-restricting/#i&#39;m-convinced!-how-can-we-solve-this%3F&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;text-sm&quot;&gt;&lt;i class=&quot;ri-hashtag&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; I&#39;m convinced! How can we solve this?&lt;/h4&gt;
&lt;p&gt;It&#39;s pretty simple really, only forward the endpoints that you actually want to expose and to go one step further... restrict the HTTP methods that are forwarded too. Here is a simplified example config illustrating this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;/api/user&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;target&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user-service.internal.twitbook.tld&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;whitelist&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;methods&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/{id}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;methods&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PUT&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PATCH&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;DELETE&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The implementation of this will depend on the framework that is being used for the reverse proxy, but hopefully, the idea has been conveyed. Using this method alone is not the only steps that should be taken to secure the proxy, it is vital to ensure that there are sufficient auth and user-level privileges in most cases too, but is a step up from just blindly forwarding everything.&lt;/p&gt;
</description>
                <pubDate>Sat, 20 Feb 2021 00:00:00 +0000</pubDate>
                <dc:creator>James McNee</dc:creator>
                <guid>https://www.jamesmcnee.com/blog/posts/2021/mar/03/reverse-proxy-restricting/</guid>
                    <tag>Reverse Proxy</tag>
                    <tag>Security</tag>
                    <tag>HTTP</tag>
                <image>
                    <link>"https://www.jamesmcnee.com/img/blog/posts/blog8.jpg"</link>
                    <alt>Major road junction denoting interconnection</alt>
                    <caption xml:lang="en" type="html">Photo by &lt;a href=&quot;https://unsplash.com/@dnevozhai?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Denys Nevozhai&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;</caption>
                </image>
            </item>
            <item>
                <title>To PATCH or not to PATCH; that is not the question</title>
                <link>https://www.jamesmcnee.com/blog/posts/2021/feb/20/to-patch-or-not/</link>
                <description>&lt;p&gt;When creating an API I will usually plan to implement 4 of the HTTP verbs; namely &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt; and &lt;code&gt;PUT&lt;/code&gt;. These four request methods are all that are needed to implement a CRUD (Create, Read, Update and Delete) interface. It is also common to use the &lt;code&gt;GET&lt;/code&gt; twice to implement a search interface, extending the acronym to SCRUD (or CRUDS).&lt;/p&gt;
&lt;p&gt;There is another common HTTP verb that has not been mentioned above though and that happens to be the subject of this post; I of course refer to &lt;code&gt;PATCH&lt;/code&gt; and not &lt;code&gt;HEAD&lt;/code&gt;, &lt;code&gt;CONNECT&lt;/code&gt;, &lt;code&gt;OPTIONS&lt;/code&gt; or &lt;code&gt;TRACE&lt;/code&gt; which are also not mentioned but are a little more on the obscure side.&lt;/p&gt;
&lt;p&gt;Recently I decided to implement a &lt;code&gt;PATCH&lt;/code&gt; method onto an API I was working on as it seemed appropriate. The context around this was to allow a boolean field to be updated and the original state was irrelevant to the client. One such use case would be notifications and marking them as read/acknowledged. With a newfound interest in &lt;code&gt;PATCH&lt;/code&gt; I began researching the standards governing it and was surprised to find that there are in fact two main types of &lt;code&gt;PATCH&lt;/code&gt; functionality which I will explore in this post; one being a &lt;a href=&quot;https://tools.ietf.org/html/rfc7396&quot;&gt;Merge Patch&lt;/a&gt; and the other a &lt;a href=&quot;https://tools.ietf.org/html/rfc6902&quot;&gt;JSON Patch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let&#39;s first assume no knowledge of the &lt;code&gt;PATCH&lt;/code&gt; request method, and for that matter, we can also assume no knowledge of &lt;code&gt;PUT&lt;/code&gt;. Both of these methods allow for a client to instruct the server to update a specific resource. The &lt;code&gt;PUT&lt;/code&gt; verb uses a replacement method whereas the &lt;code&gt;PATCH&lt;/code&gt; verb allows for more surgical alterations. Imagine a simple API that provides methods to interact with a user object and there is a user available at the following location: &lt;code&gt;/api/user/1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The current state of this user is as follows:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;forename&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;John&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;surname&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Doe&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;john.doe@mail.com&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h4&gt;How a PUT request works&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;PUT&lt;/code&gt; verb allows us to replace the entire state of the resource with what is being provided in the request body. For example, if we wanted to alter the email of the user above we could do the following request: &lt;code&gt;PUT: /api/user/1&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;forename&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;John&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;surname&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Doe&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;john.doe@newmail.com&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the face of it, this has done exactly what we wanted, the result is that the email has been updated. The downside of this method is that we need to provide the entire state of the object again meaning not only that our request is bigger than it needs to be but also that we need to know the current state of the resource before updating it.&lt;/p&gt;
&lt;br /&gt;
&lt;h4&gt;How a PATCH request works&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;PATCH&lt;/code&gt; verb allows us to selectively replace a section of the state in a more surgical way than that provided by the &lt;code&gt;PUT&lt;/code&gt; method instead allowing us to alter (or add) individual keys in the JSON document.&lt;/p&gt;
&lt;p&gt;As mentioned in the preamble at the beginning of this post, there are two types of &lt;code&gt;PATCH&lt;/code&gt; implementation. Let&#39;s take a look at both:&lt;/p&gt;
&lt;h5&gt;The &#39;Merge Patch&#39; implementation&lt;/h5&gt;
&lt;p&gt;This implementation is perhaps the most common one and is the one that I have favoured when implementing a &lt;code&gt;PATCH&lt;/code&gt; method. The merge patch works by only acting upon keys that are present in the request. An important thing to note here is that this implementation of the &lt;code&gt;PATCH&lt;/code&gt; verb does not provide a mechanism to treat a &lt;code&gt;null&lt;/code&gt; value separately than a remove operation i.e. if knowing that a value was explicitly set to null is important then this implementation will not allow for it. If we wanted to update the email of the user (just like we did in the &lt;code&gt;PUT&lt;/code&gt; example) then we would execute the following request: &lt;code&gt;PATCH: /api/user/1&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;john.doe@newmail.com&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, we only provided the field that we wished to update; there was no need to provide the full state. The spec for the merge patch can be found: &lt;a href=&quot;https://tools.ietf.org/html/rfc7396&quot;&gt;here (RFC 7396)&lt;/a&gt;.&lt;/p&gt;
&lt;h5&gt;The &#39;JSON Patch&#39; implementation&lt;/h5&gt;
&lt;p&gt;This implementation uses a format called &lt;code&gt;JSON Patch&lt;/code&gt; which is a way to describe updates to a JSON document. This format defines a JSON schema that provides for six &#39;operations&#39;; &lt;code&gt;Add&lt;/code&gt;, &lt;code&gt;Remove&lt;/code&gt;, &lt;code&gt;Replace&lt;/code&gt;, &lt;code&gt;Copy&lt;/code&gt;, &lt;code&gt;Move&lt;/code&gt; and &lt;code&gt;Test&lt;/code&gt;. If we wanted to use the &lt;code&gt;JSON Patch&lt;/code&gt; format to perform our user update then we would execute the following request: &lt;code&gt;PATCH: /api/user/1&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;op&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;replace&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;john.doe@newmail.com&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again we did not need to provide the full state of the object and instead provided instructions on how to update the original document with our desired change.&lt;/p&gt;
&lt;p&gt;Operations are passed inside of a JSON array allowing for multiple operations to be provided at once. There are a few benefits to this implementation of the &lt;code&gt;PATCH&lt;/code&gt; verb. For one it is more explicit by providing the desired operation, it allows for differentiation between the value of &lt;code&gt;null&lt;/code&gt; and the removal of a field if this is desired. The spec for the JSON patch can be found: &lt;a href=&quot;https://tools.ietf.org/html/rfc6902&quot;&gt;here (RFC 6902)&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h4&gt;How does the server know the difference...&lt;/h4&gt;
&lt;p&gt;... and what if I want to accept both types of patch?&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Content-Type&lt;/code&gt; header can be used to instruct the server which type of patch you are using. Providing &lt;code&gt;application/json&lt;/code&gt; as the value can be used to denote a Merge Patch and providing &lt;code&gt;application/json-patch+json&lt;/code&gt; should denote a &lt;code&gt;JSON Patch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another header &lt;code&gt;Accept-Patch&lt;/code&gt; can be used by the server to advertise which types of patch are supported by returning the values mentioned above. See more about this header &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Patch&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h4&gt;Summary&lt;/h4&gt;
&lt;p&gt;After putting in the time to understand and appreciate the &lt;code&gt;PATCH&lt;/code&gt; request method I have come around to the thinking that having it is perhaps more useful than having a &lt;code&gt;PUT&lt;/code&gt; endpoint in many ways. But as the title &amp;quot;To PATCH or not to PATCH; that is not the question&amp;quot; suggests, whether or not we decide to implement &lt;code&gt;PATCH&lt;/code&gt; is not the real question. It is how we might go about it.&lt;/p&gt;
&lt;p&gt;In this post, I explored two different methods of implementing a &lt;code&gt;PATCH&lt;/code&gt; endpoint. I currently prefer the &#39;Merge Patch&#39; method but do find the &#39;JSON Patch&#39; implementation interesting and worth consideration.&lt;/p&gt;
&lt;custom-element&gt;
    &lt;banner type=&quot;info&quot;&gt;
        Below are some resources that I used to help research the content in this post.
&lt;ul&gt;
&lt;li&gt;An interesting StackOverflow question and set of answers exploring &lt;code&gt;PATCH&lt;/code&gt; endpoints in detail. The answers also explore the idempotency of the &lt;code&gt;PATCH&lt;/code&gt; request method which is a very interesting read. This can be found &lt;a href=&quot;https://stackoverflow.com/questions/28459418/use-of-put-vs-patch-methods-in-rest-api-real-life-scenarios&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A great article on the differences between &lt;code&gt;PUT&lt;/code&gt; and the two implementations of the &lt;code&gt;PATCH&lt;/code&gt; endpoint. This can be found &lt;a href=&quot;https://apisyouwonthate.com/blog/put-vs-patch-vs-json-patch&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;RFC 6902 (JSON Patch). This can be found &lt;a href=&quot;https://tools.ietf.org/html/rfc6902&quot;&gt;here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;RFC 7396 (Merge Patch). This can be found &lt;a href=&quot;https://tools.ietf.org/html/rfc7396&quot;&gt;here&lt;/a&gt;
&lt;/li&gt;&lt;/ul&gt;&lt;/banner&gt;
&lt;/custom-element&gt;

</description>
                <pubDate>Sat, 20 Feb 2021 00:00:00 +0000</pubDate>
                <dc:creator>James McNee</dc:creator>
                <guid>https://www.jamesmcnee.com/blog/posts/2021/feb/20/to-patch-or-not/</guid>
                    <tag>HTTP</tag>
                    <tag>REST</tag>
                    <tag>PATCH</tag>
                    <tag>JSON</tag>
                <image>
                    <link>"https://www.jamesmcnee.com/img/blog/posts/blog7.jpg"</link>
                    <alt>Patchwork Quilt</alt>
                    <caption xml:lang="en" type="html">Photo by &lt;a href=&quot;https://unsplash.com/@nathanbang?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Nathan Bang&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/quilt?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;</caption>
                </image>
            </item>
            <item>
                <title>Preventing log spam in Java</title>
                <link>https://www.jamesmcnee.com/blog/posts/2020/aug/15/preventing-log-spam/</link>
                <description>&lt;p&gt;Logging when something unexpected happens in an app is often a useful endeavour and provides valuable information to help diagnose and remedy potential errors. Logging infrastructure such as Elasticsearch (with a UI such as Kibana) allow for the easy storage and retrieval of log messages; ingesting and storing this data however has a cost.&lt;/p&gt;
&lt;p&gt;In the everyday running of an application it might be normal to see a dozen messages in the logs. In this case it is very useful to have as much information as possible about each individual log to make sure that the diagnosis and remedy is as swift as possible. While it is useful to have this level of detail when there are so few logs, it is not very useful if there are thousands all pointing to the same error.&lt;/p&gt;
&lt;p&gt;It is inevitable that an app will at some point run into an issue; this could be a simple issue such as loss of network connection. In this scenario it is important not to flood the logs with messages that could span into the thousands. Doing so puts strain on logging infrastructure but also makes it even more difficult to see other logs that may have happened around the same time.&lt;/p&gt;
&lt;p&gt;This issue will be especially prevalent with asynchronous operations such as Kafka consumers where the number of messages is not always consistent.&lt;/p&gt;
&lt;p&gt;In order to mitigate this issue I have created a &lt;code&gt;CountingWindowBuffer&lt;/code&gt; class which once a defined threshold is hit will act as a circuit breaker to prevent the aforementioned log spam.&lt;/p&gt;
&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ThresholdBuffer&lt;/span&gt; buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CountingWindowedBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ofMinutes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;LOGGER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Something catastrophic has happened %d times... This is a disaster!!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; count&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleSomeAsyncAction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Action&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;increment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;LOGGER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Some more specific text about this particular error + the original exception&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice in the above example we defined a threshold of 5 and a window of 1 minute, this would mean that once the &lt;code&gt;increment()&lt;/code&gt; method is called 5 times the buffer would activate and the supplied function will no longer be called. Instead once the window has elapsed, the function defined within the buffers constructor is executed. The number of executions within the window will also be provided to this function.&lt;/p&gt;
&lt;p&gt;Using the above &lt;code&gt;CountingWindowBuffer&lt;/code&gt; we can maintain the standard logging that we find useful while also ensuring that during an incident that may cause a drastic increase in logs we do not spam our logs.&lt;/p&gt;
&lt;p&gt;You can find the code for this buffer over on my GitHub &lt;a href=&quot;https://github.com/JamesMcNee/CountingWindowedBuffer&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
                <pubDate>Sat, 15 Aug 2020 00:00:00 +0000</pubDate>
                <dc:creator>James McNee</dc:creator>
                <guid>https://www.jamesmcnee.com/blog/posts/2020/aug/15/preventing-log-spam/</guid>
                    <tag>Buffer</tag>
                    <tag>Java</tag>
                    <tag>Logging</tag>
                    <tag>Spam</tag>
                <image>
                    <link>"https://www.jamesmcnee.com/img/blog/posts/blog6.jpg"</link>
                    <alt>Wooden logs stacked to fill frame</alt>
                    <caption xml:lang="en" type="html">Photo by &lt;a href=&quot;https://unsplash.com/@etiennegirardet?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Etienne Girardet&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/logging?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;</caption>
                </image>
            </item>
            <item>
                <title>Propelled by a Tailwind</title>
                <link>https://www.jamesmcnee.com/blog/posts/2020/may/03/propelled-by-tailwind/</link>
                <description>&lt;p&gt;I participate in both front-end and back-end development; focusing mainly on the latter. I do however do a fair amount of front-end development, mostly in Angular, both professionally and for hobby projects. One of the main components of front-end development is the style of the site, defined in &lt;code&gt;CSS (Cascading Style Sheets)&lt;/code&gt;. It is fair to say that I do not excel at design and CSS; I can get by for hobby projects, and understand how CSS works, I simply do not have the passion for it. I believe that the single pain point and that which takes a huge chunk of time when creating personal projects is writing CSS. Each time I start a project, I think &#39;this time I shall do write the CSS in a maintainable way&#39;, yet each time it ends up in this delicate balance where it seems one style change could send the whole site out of whack.&lt;/p&gt;
&lt;p&gt;With this thinking in place; that when starting a new project I need to start again with building this delicate CSS Jenga tower, it was a great surprise when listening to an episode of the &lt;a href=&quot;https://fishandscripts.com/&quot;&gt;Fish and Scripts podcast&lt;/a&gt; one of the hosts mentioned a library called Tailwind. This library was unlike many of the other CSS frameworks out there such as Bootstrap, which aims to provide ready-made components and classes that can be plugged onto elements such as a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; to instantly render a predefined utility like a date picker. Tailwind, on the other hand, is different, it provides classes that add a single CSS property such that &lt;code&gt;m-4&lt;/code&gt; which adds &lt;code&gt;margin: 1rem&lt;/code&gt;, the power comes from combining many of these classes to quickly build a component, without worrying about class names, or targeting the right element.&lt;/p&gt;
&lt;p&gt;Tailwind helps you to use the &lt;code&gt;DRY (don&#39;t repeat yourself)&lt;/code&gt; principal while also helping to prevent this from happening too early. When writing CSS, you will often find yourself writing in a way that means that you can reuse the class, but often it will only be used once, on that specific element. Tailwind supports creating custom classes that combine multiple tailwind classes, so you can create classes such as &lt;code&gt;.btn&lt;/code&gt; and &lt;code&gt;.link&lt;/code&gt; where reuse is required.&lt;/p&gt;
&lt;p&gt;Anyway, that is enough advertising for the framework. I have only used it once in a limited fashion, and I am not sure how easy/nice it would be to integrate with an existing project. I am however really excited about the potential this could provide for me to abstract the CSS creation on my projects and not end up fighting with the implementation provided by the likes of bootstrap.&lt;/p&gt;
&lt;p&gt;A closing note:
I aim to try to build a small/medium project using Tailwind and give it a proper go. I found an article: &lt;a href=&quot;https://dev.to/seankerwin/angular-8-tailwind-css-guide-3m45&quot;&gt;Angular 8 + Tailwind CSS Guide&lt;/a&gt; by Sean Kerwin extremely useful in getting tailwind set up to be used inside of Angular.&lt;/p&gt;
</description>
                <pubDate>Sun, 03 May 2020 00:00:00 +0000</pubDate>
                <dc:creator>James McNee</dc:creator>
                <guid>https://www.jamesmcnee.com/blog/posts/2020/may/03/propelled-by-tailwind/</guid>
                    <tag>Angular</tag>
                    <tag>CSS</tag>
                    <tag>Framework</tag>
                    <tag>Frontend</tag>
                    <tag>Tailwind</tag>
                <image>
                    <link>"https://www.jamesmcnee.com/img/blog/posts/blog5.jpg"</link>
                    <alt>Jet plane takeoff</alt>
                    <caption xml:lang="en" type="html">Photo by &lt;a href=&quot;https://unsplash.com/@blackpoetry?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;pixpoetry&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/jet?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;</caption>
                </image>
            </item>
            <item>
                <title>Spring Shell Application: Purge History</title>
                <link>https://www.jamesmcnee.com/blog/posts/2019/dec/15/spring-shell-application/</link>
                <description>&lt;p&gt;Hello all! It&#39;s been a while. I recently had the desire to create a shell application for a project that I am planning on working on; I wanted to create a small proof of concept application first so as to not complicate things. The first few hours of my forray into writing shell based apps was spent choosing a langage, and after much deliberation I decided to stick to what I know; Java and Spring. I am still yet to decide if this was a good idea; it certainly allowed me to be very productive and get a working prototype up in a reasonable amount of time, but I may have taken the easy road by sticking with Java.&lt;/p&gt;
&lt;p&gt;Anyway, enough of the pre-amble. It turns out that Spring as always had my back when it came to creating a shell application. No need for manually registering user input and maintaining the app&#39;s lifecycle etc, Spring took care of all that, along with allowing me to use Dependency Injection and all the other good stuff spring offers. All of this came bundled up inside of the Spring shell library, or more accurately &lt;code&gt;spring-shell-starter&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Spring shell provides some functionality by default, one of which is history; the ability to retain the commands entered by the user for later recall. This feature is extremely useful, and even persists across sessions, history can be recalled simply by typing &lt;code&gt;history&lt;/code&gt; into the prompt. The issue is, there is no built in way to purge this history, which I deemed a neccessity for the application I was creating (see bottom of this article for a description).&lt;/p&gt;
&lt;p&gt;After a bit of digging I found that the History functionality is provided by the &lt;code&gt;org.jline&lt;/code&gt; package. This is all set up automagically by Spring for us, which is both extremely useful, and also very tedious to change the fucntionality of. There is little documentatin on how Spring is using &lt;code&gt;jline&lt;/code&gt; so it took some trial and error to find the class that I needed to interact with. The class in question is &lt;code&gt;LineReader&lt;/code&gt;:  &lt;code&gt;org.jline.reader.LineReader&lt;/code&gt;. There is an instance method on this class &lt;code&gt;getHistory()&lt;/code&gt; which provides a history object to which the &lt;code&gt;purge()&lt;/code&gt; method can be called to wipe it. Simple...&lt;/p&gt;
&lt;p&gt;&lt;custom-element&gt;&lt;banner type=&quot;error&quot;&gt;
There is an issue when wiring in the &lt;code&gt;LineReader&lt;/code&gt; bean as this creates a cyclical dependency. I got around this by marking the bean as Lazy in my components constructor (&lt;code&gt;@Lazy LineReader lineReader&lt;/code&gt;). This means that it will not be wired in until it is needed, and when it is wired in, a proxy is wired, rather than the real object.&lt;/banner&gt;&lt;/custom-element&gt;&lt;/p&gt;
&lt;p&gt;So why did I write this post... I found it tedious to find out how to implement this functionality, and if I can help someone else out then it was worth it!&lt;/p&gt;
&lt;p&gt;Here is a sample of the code required:&lt;/p&gt;
&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jline&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LineReader&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jline&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;utils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AttributedString&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;beans&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;factory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;annotation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Autowired&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;annotation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Lazy&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shell&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;standard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ShellComponent&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shell&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;standard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ShellMethod&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shell&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;standard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ShellOption&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token import&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@ShellComponent&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OtherCommands&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LineReader&lt;/span&gt; lineReader&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OtherCommands&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Lazy&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LineReader&lt;/span&gt; lineReader&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lineReader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lineReader&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ShellMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Clear all search history&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AttributedString&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;clearHistory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        lineReader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHistory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;purge&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CommandHelper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;standardPrefixed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Info&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Command history has now been purged!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For those interested, the project that I wanted this for was an Urban Dictionary definition CLI. I was looking for a public API to play with while I was learning about Spring Shell and that seemed like a fun project; spoiler alert... it was. You can view the code for this, download the binary or fork it for yourself over on &lt;a href=&quot;https://github.com/JamesMcNee/urban-cli&quot;&gt;GitHub&lt;/a&gt;!&lt;/p&gt;
</description>
                <pubDate>Sun, 15 Dec 2019 00:00:00 +0000</pubDate>
                <dc:creator>James McNee</dc:creator>
                <guid>https://www.jamesmcnee.com/blog/posts/2019/dec/15/spring-shell-application/</guid>
                    <tag>Spring</tag>
                    <tag>Java</tag>
                    <tag>CLI</tag>
                    <tag>Shell</tag>
                    <tag>Terminal</tag>
                <image>
                    <link>"https://www.jamesmcnee.com/img/blog/posts/blog4.jpg"</link>
                    <alt>Wet floor slippage sign</alt>
                    <caption xml:lang="en" type="html">Photo by &lt;a href=&quot;https://unsplash.com/@4themorningshoot?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Oliver Hale&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;</caption>
                </image>
            </item>
            <item>
                <title>Google Analytics click events in Angular 2+ (the easy way)</title>
                <link>https://www.jamesmcnee.com/blog/posts/2019/apr/29/angular-google-analytics/</link>
                <description>&lt;p&gt;I recently wanted to add Google Analytics into my portfolio page and blog to track engagement and interaction. I did not need anything fancy basically just page views and button/link clicks. I started as I usually do by doing a quick google, something similar to &#39;&lt;em&gt;Google Analytics Angular&lt;/em&gt;&#39; and found a good article from Kissa Eric on &lt;a href=&quot;https://scotch.io/&quot;&gt;Scotch.io&lt;/a&gt; (unfortunately the page has now been removed) with a detailed breakdown on how exactly GA can be added to an Angular project.&lt;/p&gt;
&lt;p&gt;It basically boils down to adding the javascript provided by Google to the &lt;em&gt;index.html&lt;/em&gt; file then creating an Angular service to send GA tracking events. As mentioned, this article was really useful in helping me to add Google Analytics to my project. The one area I think that was lacking however was in the way that it is recommended to send events on button clicks etc. The method provided is very programatic, binding to the click event of the element and then passing data to the service like this:&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Template&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;input type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;button&quot;&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Click Me!&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;click&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;trackButton()&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Component&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; analyticsService&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AnalyticsService&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;trackButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;analyticsService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;emitEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;category&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;label&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this method certainly works, it means that you are either requied to create a method for each click, or pass arguments into one method; either option is not ideal. In an ideal circumstance we would not have to edit the component at all. This is why I decided to create a directive that can fulfil this task. See below:&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Directive&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Input&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; HostListener &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@angular/core&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; GoogleAnalyticsService &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./google-analytics.service&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token decorator&quot;&gt;&lt;span class=&quot;token at operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Directive&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  selector&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;[app-analytics]&#39;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AnalyticsDirective&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token decorator&quot;&gt;&lt;span class=&quot;token at operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Input&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;anayticsCategory&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; category&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token decorator&quot;&gt;&lt;span class=&quot;token at operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Input&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;anayticsAction&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; action&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token decorator&quot;&gt;&lt;span class=&quot;token at operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Input&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;anayticsLabel&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; label&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token decorator&quot;&gt;&lt;span class=&quot;token at operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Input&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;anayticsvalue&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; _analyticsService&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GoogleAnalyticsService&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token decorator&quot;&gt;&lt;span class=&quot;token at operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;HostListener&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_analyticsService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;emitEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;category&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This directive allows me to tag an input or any element really and have click tracking applied; all without having to add any code to the componet itself. An example of how to add this in the template:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;li&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;(click)&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;gotoHome()&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;app-analytics&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;anayticsCategory&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;navigation&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; 
    &lt;span class=&quot;token attr-name&quot;&gt;anayticsAction&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;click&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;anayticsLabel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;header-home-link&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Home&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see I have added the directive to the list item &lt;em&gt;app-analytics&lt;/em&gt;, I use &#39;navigation&#39; as my &lt;em&gt;analyticsCategory&lt;/em&gt; to allow grouping of all navigation events in GA. I then set the &lt;em&gt;analyticsAction&lt;/em&gt; and &lt;em&gt;analyticsLabel&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In summary, following the aforementioned tutorial on &lt;a href=&quot;https://scotch.io/&quot;&gt;Scotch.io&lt;/a&gt; (again, unfortunately the page has been removed), along with my directive, you can create a powerful way of tracking click events on any element. With a little tweaking you could also track other events like hovering etc.&lt;/p&gt;
</description>
                <pubDate>Mon, 29 Apr 2019 00:00:00 +0000</pubDate>
                <dc:creator>James McNee</dc:creator>
                <guid>https://www.jamesmcnee.com/blog/posts/2019/apr/29/angular-google-analytics/</guid>
                    <tag>Angular</tag>
                    <tag>Directive</tag>
                    <tag>Google Analytics</tag>
                    <tag>How to</tag>
                    <tag>Typescript</tag>
                <image>
                    <link>"https://www.jamesmcnee.com/img/blog/posts/blog3.jpg"</link>
                    <alt>Webpage with graphs and charts</alt>
                    <caption xml:lang="en" type="html">Photo by &lt;a href=&quot;https://unsplash.com/photos/JKUTrJ4vK00?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Luke Chesser&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/search/photos/analytics?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;</caption>
                </image>
            </item>
            <item>
                <title>Using GitLab CI to build and publish an Angular app [FTP]</title>
                <link>https://www.jamesmcnee.com/blog/posts/2019/apr/26/gitlab-angular-publish/</link>
                <description>&lt;p&gt;After recently deciding to update my portfolio page I thought it was time that I moved (&lt;em&gt;slightly&lt;/em&gt;) towards a more professional automated publishing mechanism; for context my previous method was to directly upload a site via FTP. This called for... a pipeline!&lt;/p&gt;
&lt;p&gt;Having used &lt;a href=&quot;https://www.gocd.org/&quot;&gt;GoCD&lt;/a&gt; at my place of employment, I already had a good understanding of how a CI/CD (Continuous Integration/Delivery) system worked. My requirements for this pipeline were simple, build my angular app (using node) and publish the compiled project (via FTP) to my web host. I decided that as part of my pipeline I would also like a preprod stage, so that I can view my site in a production like environment before it went live.&lt;/p&gt;
&lt;p&gt;While GoCD is a decent piece of software it felt a bit heavy for what I needed. I didn&#39;t want to set up a CI/CD environment for all my projects, I just wanted a single pipeline. I remembered that after I made the move a few months ago from GitHub to GitLab, there was a pipeline feature built in. It was a pleasant surprise when I discovered that it was also &lt;strong&gt;100% free&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The setup process for the pipeline was different from what I have seen before; it uses a config file, rather than a GUI based solution. However this was fine, so I did a bit of Googling and found a good starting point on how to configure a pipeline with a node image. It was suprisingly intuitive to setup, first I setup a cache that I would shove the node_modules directory into and also the dist folder which would cache images and the like. This will save time with subsiquent builds by using cached resources rather than redownloading them from NPM.&lt;/p&gt;
&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;$CI_BUILD_REF_NAME&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;untracked&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; node_modules/
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; dist/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After this I needed to define the stages for my job, as mentioned I wanted to &lt;strong&gt;build&lt;/strong&gt;, publish to a &lt;strong&gt;preprod&lt;/strong&gt; environment and then finally to a &lt;strong&gt;production&lt;/strong&gt; one.&lt;/p&gt;
&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;stages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; build
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; preprod
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; prod&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next was to setup the first stage which is of course the build stage. Here my angular code will be compiled and bundled together.&lt;/p&gt;
&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;runBuild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest &lt;span class=&quot;token comment&quot;&gt;# Use the node image (has node installed)&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;before_script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; npm install
  &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; build
  &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; npm run&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;script build&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;prod&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I needed to copy the compiled source onto my FTP server in the preprod directory. This was where it got slightly more complicated as&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I needed to abstract my FTP login details so to not store them in plain text on git.&lt;/li&gt;
&lt;li&gt;I needed to understand how to use a CLI to copy files via FTP&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After some more Googling and browsing StackOverflow I found a &lt;a href=&quot;https://stackoverflow.com/questions/41633518/automated-gitlab-ci-yml-lftp-configuration/41645006#41645006&quot;&gt;useful post&lt;/a&gt; describing the various options of the CLI; with this and another &lt;a href=&quot;https://forum.gitlab.com/t/deploy-with-ci-on-ftp-server/9437/2&quot;&gt;helpful post&lt;/a&gt; on the GitLab forum I created a preprod job:&lt;/p&gt;
&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;runPublishPreProd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; preprod
  &lt;span class=&quot;token key atrule&quot;&gt;before_script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; apt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;get update
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; apt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;get install &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;y lftp &lt;span class=&quot;token comment&quot;&gt;# Install the FTP library&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;artifacts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;$CI_BUILD_NAME/$CI_BUILD_REF_NAME&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Using the artifact from the previous stage&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; dist/ &lt;span class=&quot;token comment&quot;&gt;# Path inside the artifact bundle&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;expire_in&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 2d &lt;span class=&quot;token comment&quot;&gt;# Keep around for 2 days&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; cd dist/portfolio/ &lt;span class=&quot;token comment&quot;&gt;# Change into the dist/portfolio directory (that came from the build stage)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; lftp &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;u $FTP_USERNAME&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;$FTP_PASSWORD $FTP_HOST &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;e &quot;mirror &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;e &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;R &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;p ./ preprod&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;main&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;site ; quit&quot;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; echo &quot;Deployment Completed&lt;span class=&quot;token tag&quot;&gt;!&lt;/span&gt;&quot;
  &lt;span class=&quot;token key atrule&quot;&gt;dependencies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; runBuild &lt;span class=&quot;token comment&quot;&gt;# This stage cannot be run until the runBuild task has been completed.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was it, very simple. Now it was time to do the prod stage, which was almost identical except that I needed to add a manual trigger; this allows me to verify in preprod before pushing live. It was as simple as adding the following to the end of the task:&lt;/p&gt;
&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;  &lt;span class=&quot;token key atrule&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 
    manual&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Overall a pretty decent experience. I now have a pipeline that is triggered by pushing to the git repository and auto deploys into preprod. Hopefully this post will be of use to anyone who is wanting to do a similar thing!&lt;/p&gt;
</description>
                <pubDate>Mon, 22 Apr 2019 00:00:00 +0000</pubDate>
                <dc:creator>James McNee</dc:creator>
                <guid>https://www.jamesmcnee.com/blog/posts/2019/apr/26/gitlab-angular-publish/</guid>
                    <tag>Gitlab</tag>
                    <tag>Pipeline</tag>
                    <tag>FTP</tag>
                    <tag>CI/CD</tag>
                <image>
                    <link>"https://www.jamesmcnee.com/img/blog/posts/blog2.jpg"</link>
                    <alt>Photo of two pipelines running side by side</alt>
                    <caption xml:lang="en" type="html">Photo by &lt;a href=&quot;https://unsplash.com/photos/L4gN0aeaPY4?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Quinten de Graaf&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/search/photos/pipeline?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;</caption>
                </image>
            </item>
            <item>
                <title>Hello World</title>
                <link>https://www.jamesmcnee.com/blog/posts/2019/apr/22/hello-world/</link>
                <description>&lt;p&gt;It seems only fitting that the first post on this blog should be around the journey that I have taken to create it. Not only create the blog section but the whole portfolio site. It will be useful to provide some context to start off with around why I decided to create the site and what I had before. Around 2 years ago I decided to create a site to showcase my personal projects and also have a feed of what I have been looking at and watching recently. This was my main site until a few days ago when a new incarnation went live. It was clear that a number of issues were present in the old site; both in design and purpose. Responsiveness was completely lacking from the site; this meant that it looked awful on mobile devices. Another issue came from the only feed being sourced from a YouTube playlist, this restricted my ability to share articles and talks that I have been interested in that was outside of YouTube.&lt;/p&gt;
&lt;p&gt;Therefore it was clear, out with the old; in with the new. Learning from my mistakes of the past, it was obvious that I needed to start with a CSS framework that would allow me to build upon a strong foundation. In the past I have tried to build the entire template from lab and realistically CSS is not my strongest skill; not my weakest, but certainly I am more comfortable supplimenting an existing base. I decided Bootstrap would be my new best friend, and started to sketch out a design with the trusty old pen and paper. It was in this phase that I decided to look around for inspiration and came across a great looking bootstrap theme by &lt;a href=&quot;https://github.com/jeromelachaud/freelancer-theme&quot;&gt;Jeromelachaud&lt;/a&gt; and decided to base my design around it. I chose not to use the template itself as this would allow me greater control and flexibility when adding new components; using the template would also not have allowed me to properly structure towards to the Angular framework.&lt;/p&gt;
&lt;p&gt;A few sketches later I set about creating the site, with some frustration here and magic there, it was born; a brand new professional portfolio page. I also decided to integrate with GitLab&#39;s CI to provide continuous integration and automatic deployment with both a preprod and prod stage to boot; more on this in another blog post though! So there it was, all done and dusted; brand new site; automated pipeline, but I felt there was still something missing. Dynamic content. I decided to give blogging a try; after having tried it in the past and gave up after a few posts. This time though I have more experience under my belt and a greater appreciation for the sharing of knowledge. I decided to create my own simple interface and service to provide the posts from a MySQL DB, and even markdown integration!&lt;/p&gt;
&lt;p&gt;Anyway, that is all for now. First post done! I am hoping to write a piece around my setup with the GitLab CI and hopefully provide some useful bits for anyone trying a similar endeavour&lt;/p&gt;
</description>
                <pubDate>Mon, 22 Apr 2019 00:00:00 +0000</pubDate>
                <dc:creator>James McNee</dc:creator>
                <guid>https://www.jamesmcnee.com/blog/posts/2019/apr/22/hello-world/</guid>
                    <tag>Hello</tag>
                    <tag>Introduction</tag>
                <image>
                    <link>"https://www.jamesmcnee.com/img/blog/posts/blog1.jpg"</link>
                    <alt>Photo of a sign reading &#39;Welcome come on in&#39;</alt>
                    <caption xml:lang="en" type="html">Photo by &lt;a href=&quot;https://unsplash.com/photos/AvqpdLRjABs?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Aaron Burden&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/search/photos/welcome?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;</caption>
                </image>
            </item>
    </channel>
</rss>