<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Brendan's Blog]]></title><description><![CDATA[Brendan's Blog]]></description><link>https://blog.brendanscullion.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 08 Sep 2026 00:45:38 GMT</lastBuildDate><atom:link href="https://blog.brendanscullion.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Frontend-only contact form (React (nextJS)+ Google Forms)]]></title><description><![CDATA[I recently created a simple NextJS landing page for my contracting business and decided to host it using GitHub pages, Which is great because it's free and easy to implement. However, it doesn't come with any backend. Keeping with the themes of "free...]]></description><link>https://blog.brendanscullion.com/frontend-only-contact-form-react-nextjs-google-forms</link><guid isPermaLink="true">https://blog.brendanscullion.com/frontend-only-contact-form-react-nextjs-google-forms</guid><category><![CDATA[React]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[Google]]></category><category><![CDATA[forms]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Brendan Scullion]]></dc:creator><pubDate>Thu, 15 Sep 2022 12:41:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1663106253339/XoEuZpPvv.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently created a simple NextJS landing page for my contracting business and decided to host it using GitHub pages, Which is great because it's free and easy to implement. However, it doesn't come with any backend. Keeping with the themes of "free" and "simple" I was looking for some way to add a contact form to the site that doesn't require any backend. </p>
<p>My initial thought was to go super simple and just add a link to a google form but this seemed a bit too clunky for a professional site. 
The next thought was can I embed the form, and It was through googling this that I came across this fantastic package (<a target="_blank" href="https://www.npmjs.com/package/react-google-forms-hooks">react-google-forms-hooks</a>). This is exactly what I wanted. I can use my own custom components and layout to give the form a more cohesive look.</p>
<h2 id="heading-creating-the-google-form">Creating the Google Form</h2>
<p>The first thing I do is create the actual form. In my case, it's just a simple form with Email, Subject, and Message fields</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663098704226/MOfs-3MZR.png" alt="image.png" /></p>
<p>To make sure I'm alerted when someone uses the form I've enabled email notifications</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>1. Click the three dots</td><td>2. Enable email notifications</td></tr>
</thead>
<tbody>
<tr>
<td><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663098940286/iTkAY7l-8.png" alt="image.png" /></td><td><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663098959262/NU5UfTiGs.png" alt="image.png" /></td></tr>
</tbody>
</table>
</div><p>I also need to make sure the form doesn't require sign in</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663099306975/RGdVW8SJ4.png" alt="image.png" /></p>
<h2 id="heading-adding-the-form-to-nextjs-project">Adding the Form to NextJS Project</h2>
<p>Now that the form is created, there are a couple of things I need to do before creating the components</p>
<h4 id="heading-firstly-i-need-to-install-the-package">Firstly I need to install the Package</h4>
<pre><code class="lang-bash">npm install --save react-google-forms-hooks
</code></pre>
<h4 id="heading-now-we-need-to-save-the-details-of-the-google-form-to-a-json-file">Now we need to save the details of the google form to a JSON file</h4>
<p>I'm using the function provided by the package. I found running it like this from the terminal to be the easiest as I don't run into any cors errors.</p>
<pre><code class="lang-bash">node -e <span class="hljs-string">'require("react-google-forms-hooks").googleFormsToJson("https://docs.google.com/forms/d/e/[formid]/viewform").then((data) =&gt;  console.log(data))'</span> &gt; src/data/GoogleForm.json
</code></pre>
<p><strong>Note</strong> : You'll have to go to the file after it's created and make sure the JSON is correctly quoted etc.</p>
<h4 id="heading-thats-all-the-setup-done-the-next-thing-to-do-is-put-together-the-components">That's all the setup done. The next thing to do is put together the components.</h4>
<p>My form is just text fields so all I need is input components for a short answer field and a long answer field. I already have a custom component created for TextInput and TextAreaInput so I'll be using these.</p>
<p>You'll see below that I went for the simpler method of just copying the field ids into the components. If you want to be fancy you could map over the fields array and create the form dynamically. Or why not add some input validation? Go wild</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> {
  useShortAnswerInput,
  useLongAnswerInput,
  useGoogleForm,
  GoogleFormProvider,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'react-google-forms-hooks'</span>;

<span class="hljs-keyword">import</span> { Button } <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/Button'</span>;
<span class="hljs-keyword">import</span> { TextAreaInput } <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/TextAreaInput'</span>;
<span class="hljs-keyword">import</span> { TextInput } <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/TextInput'</span>;
<span class="hljs-keyword">import</span> { Section } <span class="hljs-keyword">from</span> <span class="hljs-string">'../layout/Section'</span>;
<span class="hljs-keyword">import</span> form <span class="hljs-keyword">from</span> <span class="hljs-string">'../data/GoogleForm.json'</span>;

<span class="hljs-keyword">type</span> IFormInputs = {
  id: <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">type</span>: <span class="hljs-string">'text'</span> | <span class="hljs-string">'email'</span> | <span class="hljs-string">'tel'</span>;
};

<span class="hljs-keyword">const</span> ShortAnswerInput = <span class="hljs-function">(<span class="hljs-params">{ id, <span class="hljs-keyword">type</span> }: IFormInputs</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { register, label } = useShortAnswerInput(id);

  <span class="hljs-keyword">return</span> &lt;TextInput <span class="hljs-keyword">type</span>={<span class="hljs-keyword">type</span>} label={label} {...register()} /&gt;;
};

<span class="hljs-keyword">const</span> LongAnswerInput = <span class="hljs-function">(<span class="hljs-params">{ id, <span class="hljs-keyword">type</span> }: IFormInputs</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { register, label } = useLongAnswerInput(id);

  <span class="hljs-keyword">return</span> &lt;TextAreaInput <span class="hljs-keyword">type</span>={<span class="hljs-keyword">type</span>} label={label} {...register()} /&gt;;
};

<span class="hljs-keyword">const</span> Contactform = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// @ts-ignore</span>
  <span class="hljs-keyword">const</span> methods = useGoogleForm({ form });

  <span class="hljs-keyword">const</span> onSubmit = <span class="hljs-keyword">async</span> (data: <span class="hljs-built_in">any</span>) =&gt; {
    <span class="hljs-keyword">await</span> methods.submitToGoogleForms(data).then(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// eslint-disable-next-line no-alert</span>
      alert(<span class="hljs-string">'Form submitted with success!'</span>);
    });
  };
  <span class="hljs-keyword">return</span> (
    &lt;Section&gt;
      &lt;GoogleFormProvider {...methods}&gt;
        &lt;form id=<span class="hljs-string">"ContactForm"</span> onSubmit={methods.handleSubmit(onSubmit)}&gt;
          &lt;ShortAnswerInput id=<span class="hljs-string">"1234567890"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"email"</span> /&gt;
          &lt;ShortAnswerInput id=<span class="hljs-string">"1234567891"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span> /&gt;
          &lt;LongAnswerInput id=<span class="hljs-string">"1234567892"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span> /&gt;

          &lt;Button <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span> className=<span class="hljs-string">"mt-2"</span>&gt;
            Submit
          &lt;/Button&gt;
        &lt;/form&gt;
      &lt;/GoogleFormProvider&gt;
    &lt;/Section&gt;
  );
};

<span class="hljs-keyword">export</span> { Contactform };
</code></pre>
<h4 id="heading-great-now-i-have-a-contact-form-that-i-can-easily-add-to-my-site-hopefully-it-gets-used">Great now I have a Contact form that I can easily add to my site. Hopefully, it gets used 😁</h4>
<p>You've made it this far might as well check it out
<a target="_blank" href="https://www.blackshoretech.com/">https://www.blackshoretech.com/</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663103948377/zB84AlL6L.png" alt="image.png" /></p>
]]></content:encoded></item><item><title><![CDATA[PostgreSQL Fuzzy Text Search: Not so fuzzy to fuzziest]]></title><description><![CDATA[So you have a bunch of data that comes from some human source (Free text form fields, reviews, blogs, classified ads, social media) and you want to do some analysis on it. but with people being the way they are, you're going to have some problems:

A...]]></description><link>https://blog.brendanscullion.com/postgresql-text-search</link><guid isPermaLink="true">https://blog.brendanscullion.com/postgresql-text-search</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Databases]]></category><category><![CDATA[Data Science]]></category><dc:creator><![CDATA[Brendan Scullion]]></dc:creator><pubDate>Tue, 07 Jun 2022 07:16:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1654533752867/bDBd4SD-S.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So you have a bunch of data that comes from some human source (Free text form fields, reviews, blogs, classified ads, social media) and you want to do some analysis on it. but with people being the way they are, you're going to have some problems:</p>
<ol>
<li>A lot of words are commonly miss-spelled (definitely-&gt; definitly etc).</li>
<li>Regional differences. e.g American and British English (color/colour, analyse/analyze)</li>
<li>Creative ways of spelling to add dramatic effect. (heyyy, whaaaaat!, noooo!)</li>
</ol>
<p>All of this, plus more will affect your results and make it difficult to do any accurate analysis of the data (such as grouping similar topics together, etc). Luckily PostgreSQL comes packaged with a number of really useful tools that make life a lot easier for us. This is a complex topic and I'm only going to touch on the basics. But there is enough here to cover most basic and possibly some more complex use-cases.  </p>
<h2 id="heading-simple-pattern-matching-a-little-fuzzy">Simple Pattern matching (A little Fuzzy)</h2>
<h4 id="heading-like">LIKE</h4>
<p>Useful when you have a good idea of what the data and queries look like but it's difficult to create something generic enough to be useful in a general text dataset. With this, you only have two ways to match the text. <code>%</code> is used as a wildcard for 0 or more characters of any value, and <code>_</code> matches a single character of any value.</p>
<ul>
<li><code>LIKE</code>: use wildcards and character substitution, Case sensitive<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-string">'Hello world'</span> <span class="hljs-keyword">LIKE</span> <span class="hljs-string">'He__o %'</span>;  <span class="hljs-comment">-- TRUE</span>
</code></pre>
</li>
<li><code>ILIKE</code>: use wildcards and character substitution, Case insensitive<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-string">'Hello world'</span> <span class="hljs-keyword">ILIKE</span> <span class="hljs-string">'h_llo _%'</span>; <span class="hljs-comment">-- TRUE</span>
</code></pre>
</li>
<li><code>NOT LIKE</code>/<code>NOT ILIKE</code>: inverse of LIKE or ILIKE<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-string">'Hello world'</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">ILIKE</span> <span class="hljs-string">'%_llo world%'</span>; <span class="hljs-comment">-- FALSE</span>
</code></pre>
</li>
</ul>
<h4 id="heading-regex">Regex</h4>
<p>A little more advanced than the "LIKE" operator. With regex, you have a lot more control over the pattern matching. PostgreSQL comes with two standard ways to do this. </p>
<ul>
<li><code>[NOT] SIMILAR TO</code>: Uses a simpler SQL standard expression syntax which is kind of like a mix between the LIKE syntax and POSIX regular expressions. You can prepend <code>NOT</code> to negate the expression.<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-string">'Hello world'</span> SIMILAR <span class="hljs-keyword">TO</span> <span class="hljs-string">'H(e|a)l+o %'</span>; <span class="hljs-comment">--TRUE</span>
</code></pre>
</li>
<li><code>~</code>/<code>!~</code>/<code>~*</code>/<code>!~*</code>: More powerful POSIX syntax that you may already be familiar with in other languages. <code>*</code> makes the expressions case-insensitive. <code>!</code> negates the expression<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-string">'Hello world'</span> ~ <span class="hljs-string">'^H(e|a)l{1,2}o [a-zA-Z]{5}$'</span>; <span class="hljs-comment">-- TRUE</span>
</code></pre>
</li>
</ul>
<h3 id="heading-improving-performance">Improving performance</h3>
<p>In certain scenarios it's possible to speed up your queries using a special operator class for a BTREE index. <code>text_pattern_ops</code> and <code>varchar_pattern_ops</code> allow you to index a text or varchar field. However, this is only effective if your queries are <em>left-anchored</em> (No leading wildcard) e.g <code>WHERE text_fields LIKE 'hell_ %'</code></p>
<p>I've only covered the basics of what you can do with regex and PostgreSQL, so I suggest looking at the docs below to learn more. </p>
<p><strong>See:</strong> <a target="_blank" href="https://www.postgresql.org/docs/current/functions-matching.html">PostgreSQL Docs -&gt; pattern-matchine</a></p>
<h2 id="heading-text-search-vectors-fuzzyish">Text Search Vectors (Fuzzy(ish))</h2>
<p>This is probably the most efficient option for performing a full-text search. It works by removing all the stop words (it, the, as, by, ...) and duplicates from your text and reducing each word into its main component. For example <em>quick</em>, <em>quickly</em> just becomes <em>quick</em> and <em>product</em>, <em>production</em>, <em>products</em> becomes <em>product</em>. This provides a small bit of fuzziness to results as the query does not need the exact word. One caveat with tsvectors is that to use it effectively you need to know the language of the text. you can use the <code>'simple'</code> config option but you lose a lot of the efficiencies. </p>
<p>The first function you'll need is <code>to_tsvector(config, text)</code>. The result of this is a special datatype <code>tsvector</code> that contains each component along with its index in the original text.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span> to_tsvector(<span class="hljs-string">'english'</span>, <span class="hljs-string">'the quick brown fox ran quickly to the other foxes'</span>);
               to_tsvector                
<span class="hljs-comment">-------------------------------------</span>
 'brown':3 'fox':4,10 'quick':2,6 'ran':5
(1 row)
</code></pre>
<p>The second thing you need is the query generator, which comes in a few different flavors</p>
<ul>
<li><p><code>to_tsquery(config, text) -&gt; tsquery</code>: Creates a basic query from a single token or multiple if
used with boolean operators.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> to_tsquery(<span class="hljs-string">'english'</span>, <span class="hljs-string">'hello'</span>);  <span class="hljs-comment">--&gt; 'hello'</span>
<span class="hljs-comment">-- OR</span>
<span class="hljs-keyword">SELECT</span> to_tsquery(<span class="hljs-string">'english'</span>, <span class="hljs-string">'hello &amp; worlds'</span>);  <span class="hljs-comment">--&gt; 'hello' &amp; 'world'</span>
</code></pre>
</li>
<li><p><code>plainto_tsquery(config, text) -&gt; tsquery</code>: Accepts a more generic search term. by default each word in the query is an "&amp;" operation</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> plainto_tsquery(<span class="hljs-string">'english'</span>, <span class="hljs-string">'hello world'</span>); <span class="hljs-comment">--&gt; 'hello' &amp; 'world'</span>
</code></pre>
</li>
<li><code>websearch_to_tsquery(config, text) -&gt; tsquery</code>: This one is a bit more sophisticated and my favourite. It uses a google type syntax for searching.<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> websearch_to_tsquery(<span class="hljs-string">'simple'</span>, <span class="hljs-string">'"hello there" -world'</span>);  <span class="hljs-comment">--&gt;  'hello' &lt;-&gt; 'there' &amp; !'world'</span>
</code></pre>
</li>
</ul>
<h3 id="heading-example-usage">Example usage</h3>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> message <span class="hljs-keyword">FROM</span> mock_data
<span class="hljs-keyword">WHERE</span> 
    to_tsvector(<span class="hljs-string">'english'</span>, message) 
    @@ 
    websearch_to_tsquery(<span class="hljs-string">'english'</span>, <span class="hljs-string">'product killer -content'</span>)
<span class="hljs-keyword">LIMIT</span> <span class="hljs-number">5</span>; 
             message             
<span class="hljs-comment">---------------------------------</span>
 productize killer architectures
 productize killer synergies
(2 rows)
</code></pre>
<h3 id="heading-improving-performance-1">Improving performance</h3>
<p>To get some really good performance on your queries. Create a generated column with the tsvector data and then add a GIN index to that column. </p>
<pre><code class="lang-sql"><span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> mock_data 
    <span class="hljs-keyword">ADD</span> <span class="hljs-keyword">COLUMN</span> ts_message_col tsvector 
    <span class="hljs-keyword">GENERATED</span> <span class="hljs-keyword">ALWAYS</span> <span class="hljs-keyword">AS</span> (to_tsvector(<span class="hljs-string">'english'</span>, message)) 
    <span class="hljs-keyword">STORED</span>;

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> idx_tsvector_message <span class="hljs-keyword">ON</span> mock_data <span class="hljs-keyword">USING</span> GIN(ts_message_col);
</code></pre>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> message <span class="hljs-keyword">FROM</span> mock_data
<span class="hljs-keyword">WHERE</span> 
    ts_message_col @@ websearch_to_tsquery(<span class="hljs-string">'english'</span>, <span class="hljs-string">'product or content'</span>)
<span class="hljs-keyword">LIMIT</span> <span class="hljs-number">5</span>; 
              message              
<span class="hljs-comment">-----------------------------------</span>
 productize extensible initiatives
 target value-added content
 productize visionary content
 monetize proactive content
 synthesize cross-media content
(5 rows)
</code></pre>
<h2 id="heading-trigrams-fuzzier">Trigrams (Fuzzier)</h2>
<blockquote>
<p><a target="_blank" href="https://www.postgresql.org/docs/current/pgtrgm.html">pg_trgm</a> module required: <code>CREATE extension pg_trgm;</code></p>
</blockquote>
<p>As the name suggests, a trigram is a series of three consecutive characters from a string. For example, take the string <em>"Hello world".</em></p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> show_trgm(<span class="hljs-string">'Hello world'</span>);
                           show_trgm                           
<span class="hljs-comment">---------------------------------------------------------------</span>
 {"  h","  w"," he"," wo",ell,hel,"ld ",llo,"lo ",orl,rld,wor}
</code></pre>
<p>In PostgreSQL, trigrams are used to generate a similarity score between two strings. 
<a target="_blank" href="https://www.postgresql.org/docs/current/pgtrgm.html">pg_trgm</a> provides us with three functions for this:</p>
<ul>
<li><code>similarity(string, string)</code>: Similarity between the whole first and second string<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> similarity(<span class="hljs-string">'hello'</span>, <span class="hljs-string">'Helo world'</span>); <span class="hljs-comment">--&gt; 0.30769232</span>
<span class="hljs-comment">-- OR</span>
<span class="hljs-keyword">SELECT</span> <span class="hljs-number">1</span> - (<span class="hljs-string">'hello'</span> &lt;-&gt; <span class="hljs-string">'Helo world'</span>);  <span class="hljs-comment">--&gt; 0.307692289352417</span>
</code></pre>
</li>
<li><code>word_similarity(string, string)</code>: The greatest similarity between the first string and any substring of the second string<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> word_similarity(<span class="hljs-string">'hello'</span>, <span class="hljs-string">'Helo world'</span>); <span class="hljs-comment">--&gt; 0.5714286</span>
<span class="hljs-comment">-- OR</span>
<span class="hljs-keyword">SELECT</span> <span class="hljs-number">1</span> - (<span class="hljs-string">'hello'</span> &lt;&lt;-&gt; <span class="hljs-string">'Helo world'</span>); <span class="hljs-comment">--&gt; 0.5714285969734192</span>
</code></pre>
</li>
<li><code>strict_word_similarity(string, string)</code>: The greatest similarity between the first string and any whole word in the second string<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> strict_word_similarity(<span class="hljs-string">'hello'</span>, <span class="hljs-string">'Helo world'</span>); <span class="hljs-comment">--&gt; 0.5714286</span>
<span class="hljs-comment">-- OR</span>
<span class="hljs-keyword">SELECT</span> <span class="hljs-number">1</span> - (<span class="hljs-string">'hello'</span> &lt;&lt;&lt;-&gt; <span class="hljs-string">'Helo world'</span>); <span class="hljs-comment">--&gt; 0.5714285969734192</span>
</code></pre>
</li>
</ul>
<h4 id="heading-or-if-you-want-the-boolean-results">Or if you want the boolean results.</h4>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> (<span class="hljs-string">'hello'</span> % <span class="hljs-string">'Helo world'</span>); <span class="hljs-comment">--&gt; similarity TRUE</span>
<span class="hljs-keyword">SELECT</span> (<span class="hljs-string">'hello'</span> &lt;% <span class="hljs-string">'Helo world'</span>); <span class="hljs-comment">--&gt; word_similarity FALSE</span>
<span class="hljs-keyword">SELECT</span> (<span class="hljs-string">'hello'</span> &lt;&lt;% <span class="hljs-string">'Helo world'</span>); <span class="hljs-comment">--&gt; strict_word_similarity TRUE</span>
</code></pre>
<p>The result of this depends on the following GUC parameters respectively</p>
<ul>
<li><code>pg_trgm.similarity_threshold</code> (default 0.3)</li>
<li><code>pg_trgm.word_similarity_threshold</code> (default 0.6)</li>
<li><code>pg_trgm.strict_word_similarity_threshold</code> (default 0.5)</li>
</ul>
<h3 id="heading-improving-performance-2">Improving performance</h3>
<p>Conveniently pg_trgm module provides GiST and GIN index operator classes that allow you to create an index over a text column. I haven't tested this out fully but apparently, the GIST index provides better performance. </p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> trgm_idx_text_column <span class="hljs-keyword">ON</span> test_table <span class="hljs-keyword">USING</span> GIST (text_column gist_trgm_ops);
<span class="hljs-comment">-- OR</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> trgm_idx_text_column <span class="hljs-keyword">ON</span> test_table <span class="hljs-keyword">USING</span> GIN (text_column gin_trgm_ops);
</code></pre>
<p><strong>See:</strong> <a target="_blank" href="https://www.postgresql.org/docs/current/pgtrgm.html">PostgreSQL Docs: pg_trgm</a></p>
<h2 id="heading-levenshtein-distance-fuzzier">Levenshtein distance (Fuzzier)</h2>
<blockquote>
<p><a target="_blank" href="https://www.postgresql.org/docs/current/fuzzystrmatch.html">fuzzystrmatch</a> module required: <code>CREATE extension fuzzystrmatch;</code></p>
</blockquote>
<p>Levenshtein distance is a measure of the similarity between two strings, measured in terms of the number of characters that need to be changed in order to turn one string into the other. </p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> 
    first_name, 
    levenshtein(first_name, <span class="hljs-string">'Bobby'</span>) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">difference</span> <span class="hljs-keyword">FROM</span> mock_data
<span class="hljs-keyword">WHERE</span> levenshtein(first_name, <span class="hljs-string">'Bobby'</span>) &lt; <span class="hljs-number">3</span>
<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> <span class="hljs-number">2</span>
<span class="hljs-keyword">LIMIT</span> <span class="hljs-number">5</span>;
 first_name | difference 
<span class="hljs-comment">------------+------------</span>
 Bobby      |          0
 Bobbi      |          1
 Bobbi      |          1
 Bibby      |          1
 Toby       |          2
(5 rows)
</code></pre>
<h3 id="heading-performance-improvements">Performance Improvements</h3>
<p>One of the issues with the Levenshtein method is that there is no way to index it as the index would need to know the input. However, there is something we can do. We can reduce the number of records it has to process by combining it with one of the more fuzzy options below.</p>
<p><strong>See:</strong> <a target="_blank" href="https://www.postgresql.org/docs/current/fuzzystrmatch.html#id-1.11.7.24.7">PostgreSQL Docs: fuzzystrmatch</a></p>
<h2 id="heading-phonetic-similarity-very-fuzzy">Phonetic similarity (Very Fuzzy)</h2>
<blockquote>
<p><a target="_blank" href="https://www.postgresql.org/docs/current/fuzzystrmatch.html">fuzzystrmatch</a> module required: <code>CREATE extension fuzzystrmatch;</code></p>
</blockquote>
<p>For me, I found these next couple of methods particularly interesting. Instead of measuring how similar words are to each other in terms of individual characters. We can actually compare them by how they sound when they are spoken.
fuzzystrmatch provides three functions out of the box for this.</p>
<ul>
<li><code>soundex(string) -&gt; text</code>: converts a string to its Soundex code.<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">soundex</span>(<span class="hljs-string">'Anne'</span>), <span class="hljs-keyword">soundex</span>(<span class="hljs-string">'Ann'</span>), <span class="hljs-keyword">difference</span>(<span class="hljs-string">'Anne'</span>, <span class="hljs-string">'Ann'</span>);
soundex | soundex | difference 
<span class="hljs-comment">---------+---------+------------</span>
A500    | A500    |          4
(1 row)
</code></pre>
</li>
<li><code>metaphone(string, max_output_length) -&gt; text</code>: like Soundex, is based on the idea of constructing a representative code for an input string.<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> metaphone(<span class="hljs-string">'brendan'</span>, <span class="hljs-number">10</span>), metaphone(<span class="hljs-string">'brandon'</span>, <span class="hljs-number">10</span>);
metaphone | metaphone 
<span class="hljs-comment">-----------+-----------</span>
BRNTN     | BRNTN
(1 row)
</code></pre>
</li>
<li><code>dmetaphone(string) -&gt; text</code>/`dmetaphone_alt(string) -&gt; text: computes two “sounds like” strings for a given input string — a “primary” and an “alternate”. In most cases, they are the same, but for non-English names especially they can be a bit different, depending on pronunciation. These functions compute the primary and alternate codes<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> dmetaphone_alt(<span class="hljs-string">'brendan'</span>), dmetaphone(<span class="hljs-string">'Brandon'</span>);
dmetaphone_alt | dmetaphone 
<span class="hljs-comment">----------------+------------</span>
PRNT           | PRNT
(1 row)
</code></pre>
</li>
</ul>
<h3 id="heading-performance-improvements-1">performance improvements</h3>
<p>Each of these methods can be indexed using a normal function based index</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> idx_sdx_first_name <span class="hljs-keyword">ON</span> mock_data (<span class="hljs-keyword">soundex</span>(first_name));
<span class="hljs-comment">--OR</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> idx_mtf_first_name <span class="hljs-keyword">ON</span> mock_data (metaphone(first_name, <span class="hljs-number">10</span>));
<span class="hljs-comment">--OR</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> idx_dmtf_first_name <span class="hljs-keyword">ON</span> mock_data (dmetaphone(first_name));
</code></pre>
<p> I mentioned above that we can improve the Levenshtein method by combining it with one of these methods. Once you've indexed the column for one of the phonetics functions you can use that to reduce the dataset and use Levenshtein to finish the filtering</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> 
    first_name, 
    levenshtein(first_name, <span class="hljs-string">'Bobby'</span>) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">difference</span> <span class="hljs-keyword">FROM</span> mock_data
<span class="hljs-keyword">WHERE</span> 
    <span class="hljs-keyword">soundex</span>(first_name) = <span class="hljs-keyword">soundex</span>(<span class="hljs-string">'bobby'</span>)
<span class="hljs-keyword">AND</span> 
    levenshtein(first_name, <span class="hljs-string">'Bobby'</span>) &lt; <span class="hljs-number">3</span>
<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> <span class="hljs-number">2</span>
<span class="hljs-keyword">LIMIT</span> <span class="hljs-number">5</span>;
 first_name | difference 
<span class="hljs-comment">------------+------------</span>
 Bobby      |          0
 Bobbi      |          1
 Bobbi      |          1
 Bibby      |          1
 Bobbie     |          2
(5 rows)
</code></pre>
<p><strong>See:</strong> <a target="_blank" href="https://www.postgresql.org/docs/current/fuzzystrmatch.html">PostgreSQL Docs: fuzzystrmatch</a></p>
]]></content:encoded></item><item><title><![CDATA[Creating Trusted Local SSL certs for development]]></title><description><![CDATA[NOTE: This article to assumes you have NGINX and local DNS configure correctly if not check out this article to get started. 
If you're building web applications, then you've probably run into this before. You need to run your local environment over ...]]></description><link>https://blog.brendanscullion.com/creating-trusted-local-ssl-certs-for-development</link><guid isPermaLink="true">https://blog.brendanscullion.com/creating-trusted-local-ssl-certs-for-development</guid><category><![CDATA[nginx]]></category><category><![CDATA[SSL]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Linux]]></category><category><![CDATA[macOS]]></category><dc:creator><![CDATA[Brendan Scullion]]></dc:creator><pubDate>Mon, 09 May 2022 20:28:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1652122318052/s6pEYdBc6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>NOTE</strong>: This article to assumes you have NGINX and local DNS configure correctly if not check out <a target="_blank" href="TBC">this article</a> to get started. </p>
<p>If you're building web applications, then you've probably run into this before. You need to run your local environment over HTTPS because you're connecting to some third party API (maybe Facebook login etc.). You create an SSL cert, add it to your NGINX config, open your browser and bypass the <code>NET::ERR_CERT_AUTHORITY_INVALID</code> warning. </p>
<p>Next you can't connect to the local back-end server instance because it's also not trusted. You can't be arsed finding the commands  and creating a new ssl cert so you use the same one or maybe you're only using the snakeoil cert that comes with the OS. </p>
<p>This works fine but you're still going to be annoyed by the browser warnings, and if you're using websockets then it's an even bigger hassle. Repeat all this for every project you're working on, and every domain it uses. It adds up. It's not the biggest issue in the world but it's enough to be annoying. 
Also, getting rid of the  'NOT Secure' warning in the URL bar is surprisingly refreshing. </p>
<h3 id="heading-so-lets-look-at-what-we-can-do-about-it">So lets look at what we can do about it?</h3>
<ol>
<li>Create a local certificate authority (CA)</li>
<li>Create SSL certs for each project and sign them using this CA</li>
<li>Add this CA to your browsers trusted authorities</li>
<li>Create new certs signed by the same CA for other projects</li>
</ol>
<p>Luckily for you I've put together this handy bash script to add to your arsenal. It's available on my <a target="_blank" href="https://github.com/brsc2909/makecrt">Github</a> but or convenience you can just download it directly</p>
<pre><code class="lang-bash">curl --location -O https://raw.githubusercontent.com/brsc2909/makecrt/main/makecrt &amp;&amp; chmod +x makecrt
</code></pre>
<h3 id="heading-how-do-i-use-it">how do I use it ?</h3>
<p>I've made this as simple as possible. All you need to do is run the script as root and pass the domains as arguments. you need to specify each domain that this cert will be used with. ( I usually create a separate cert per project).</p>
<pre><code class="lang-bash">sudo ./makecert --domains local-app.connectmor.io local-api.connectmor.io
</code></pre>
<pre><code>Creating extfile
Creating certificate <span class="hljs-keyword">for</span> local<span class="hljs-operator">-</span>app.connectmor.io
Signature ok
subject<span class="hljs-operator">=</span>C <span class="hljs-operator">=</span> IE, ST <span class="hljs-operator">=</span> Leinster, L <span class="hljs-operator">=</span> DUBLIN, O <span class="hljs-operator">=</span> Local CA beast, OU <span class="hljs-operator">=</span> IT, CN <span class="hljs-operator">=</span> local<span class="hljs-operator">-</span>app.connectmor.io
Getting CA Private Key
NGINX config:
ssl_certificate_key  <span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>ssl<span class="hljs-operator">/</span><span class="hljs-keyword">private</span><span class="hljs-operator">/</span>local<span class="hljs-operator">-</span>app.connectmor.io.key;
ssl_certificate  <span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>ssl<span class="hljs-operator">/</span>certs<span class="hljs-operator">/</span>local<span class="hljs-operator">-</span>app.connectmor.io.crt;
</code></pre><p>And that's it. You can use the NGINX config at the end of the output to update your config. </p>
<h3 id="heading-adding-the-ca-to-my-browser-youll-only-need-to-do-this-once">Adding the CA to my browser. (You'll only need to do this once)</h3>
<p>Each browser is slightly different follow the same principals. Below is how to do it on Chrome or any other chromium based browser (Vivaldi e.t.c) </p>
<ol>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652125391640/7NQWoiBZ1.png" alt="image.png" /></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652125473843/iupVSej0a.png" alt="image.png" /></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652125503497/t8tyAeiD9.png" alt="image.png" /></p>
</li>
<li><p>Add the CA cert you just created. If you used all the defaults then it will be located at <code>/etc/ssl/cers/myLocalRootCA.crt</code>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652125561567/6FkHCGxPq.png" alt="image.png" /></p>
</li>
<li><p>Tell the browser to trust this for identifying websites
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652125608021/MLdJL3Rcp.png" alt="image.png" /></p>
</li>
<li><p>You can then check your domain in the list of trusted authorities. Mine looks like this
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652125654184/bEFG9J0G_.png" alt="image.png" /></p>
</li>
<li><p>Ah now that is nice
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652126779824/NC56aODb_.png" alt="image.png" /></p>
</li>
</ol>
<h3 id="heading-custom-config">Custom config</h3>
<p>depending on your OS you might need to edit the script slightly. For example if your ssl certs are not stores in <code>/etc/ssl/</code>.
You have two options here:</p>
<ol>
<li>You can specify the options each time you run the command. use the <code>--help</code> option to view the list of commands.</li>
</ol>
<pre><code>Usage: ./makecrt <span class="hljs-operator">-</span>d [domain1, domain2...] [options]

options:
<span class="hljs-operator">-</span>h, <span class="hljs-operator">-</span><span class="hljs-operator">-</span>help           show brief help
<span class="hljs-operator">-</span>d, <span class="hljs-operator">-</span><span class="hljs-operator">-</span>domains <span class="hljs-operator">&lt;</span>DOMAIN1 DOMAIN2...&gt; Specify domain <span class="hljs-keyword">for</span> which the cert will be used on
     <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-literal">days</span>          Specify how long the cert <span class="hljs-keyword">is</span> valid <span class="hljs-keyword">for</span>. default: <span class="hljs-number">3650</span>
     <span class="hljs-operator">-</span><span class="hljs-operator">-</span>CA            Specify a local CA. default: <span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>ssl<span class="hljs-operator">/</span>certs<span class="hljs-operator">/</span>myLocalRootCA.crt
     <span class="hljs-operator">-</span><span class="hljs-operator">-</span>CAkey         Specify a local CA <span class="hljs-keyword">private</span> key. default: <span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>ssl<span class="hljs-operator">/</span><span class="hljs-keyword">private</span><span class="hljs-operator">/</span>myLocalRootCA.key
<span class="hljs-operator">-</span>pd, <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-keyword">private</span><span class="hljs-operator">-</span>dir   Specify <span class="hljs-keyword">private</span> key dir. default: <span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>ssl<span class="hljs-operator">/</span><span class="hljs-keyword">private</span>
<span class="hljs-operator">-</span>cd, <span class="hljs-operator">-</span><span class="hljs-operator">-</span>cert<span class="hljs-operator">-</span>dir      Specify certificate dir. default: <span class="hljs-operator">/</span>etc<span class="hljs-operator">/</span>ssl<span class="hljs-operator">/</span>certs
<span class="hljs-operator">-</span>e, <span class="hljs-operator">-</span><span class="hljs-operator">-</span>eliptic<span class="hljs-operator">-</span>curve  Specify what <span class="hljs-keyword">type</span> of eliptic curve to use. Default: prime256v1

Example:
./makecrt <span class="hljs-operator">-</span>d example.com blog.example.com
</code></pre><ol>
<li>Edit the default Values in the script. (This probably makes the most sense)</li>
</ol>
<pre><code class="lang-bash"><span class="hljs-comment"># You can change these default values to suit</span>
DAYS=3650

CERT_DIR=/etc/ssl/certs
PRIVATE_KEY_DIR=/etc/ssl/private

e_curve=prime256v1
localRootCA=<span class="hljs-variable">$CERT_DIR</span>/myLocalRootCA.crt
localRootCAkey=<span class="hljs-variable">$PRIVATE_KEY_DIR</span>/myLocalRootCA.key

EXTFILE=/tmp/_v3.ext

ORG=<span class="hljs-string">"Local CA <span class="hljs-subst">$(hostname)</span>"</span>
ORG_UNIT=<span class="hljs-string">"IT"</span>
COUNTY=<span class="hljs-string">"DUBLIN"</span>
STATE=<span class="hljs-string">"Leinster"</span>
COUNTRY=<span class="hljs-string">"IE"</span>
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Virtual CAN-BUS simulator (Virtual ECU)]]></title><description><![CDATA[While developing some monitoring applications for a Raspberry-Pi car computer, I found that writing code on my laptop while sitting in the front seat of my car is not the most productive way to do things. What's needed is some sort of virtual ECU, an...]]></description><link>https://blog.brendanscullion.com/virtual-can-bus-simulator-virtual-ecu</link><guid isPermaLink="true">https://blog.brendanscullion.com/virtual-can-bus-simulator-virtual-ecu</guid><category><![CDATA[Bash]]></category><category><![CDATA[Linux]]></category><dc:creator><![CDATA[Brendan Scullion]]></dc:creator><pubDate>Mon, 09 May 2022 15:07:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/KVPRz5JEDbc/upload/v1652106957021/tZQ5ZUVIo.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>While developing some monitoring applications for a Raspberry-Pi car computer, I found that writing code on my laptop while sitting in the front seat of my car is not the most productive way to do things. What's needed is some sort of virtual ECU, and i have just the solution. </p>
<h4 id="heading-in-short-my-solution-is-this">In short my solution is this:</h4>
<ol>
<li>Take a can recording of the car starting up and running for a little while</li>
<li>Create a virtual can network device</li>
<li>replay the recording on an infinite loop</li>
<li>package it up nicely so that I can start the simulation on demand
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652107126520/ODP7DeD6f.gif" alt="cansim-demo-2.gif" /></li>
</ol>
<ol>
<li><h3 id="heading-record-some-data">Record Some Data</h3>
<p>This may be different for other cars but for my disco 3, I need to wait 5 minutes after turning the car off for the ECU to go to sleep. 
Once the ECU is asleep I start the recording. </p>
<pre><code class="lang-bash">candump can0 -l
</code></pre>
<p>I start the car and let it run for 5 minutes or so, later on I’ll redo this and take the car for a spin around the town but for now this is enough for me. so I <code>CTL-C</code> the recording and I get a file named <code>candump-2020-04-14_202239.log</code>. I rename this to something with a little more meaning (“candump-landrover-discovery-3-vcan0.log”)</p>
</li>
<li><h3 id="heading-configure-virtual-can-device">Configure Virtual CAN device</h3>
<p>The vcan device is basically another network device so configuring it is pretty straight forward. </p>
<pre><code class="lang-bash">sudo modprobe vcan
sudo ip link add dev vcan0 <span class="hljs-built_in">type</span> vcan
sudo ip link <span class="hljs-built_in">set</span> up vcan0
</code></pre>
</li>
<li><h3 id="heading-replay-can-recording">Replay CAN Recording</h3>
<p>Now i just need to replay my recording back into vcan0.</p>
<pre><code class="lang-bash">canplayer -I candump-landrover-discovery-3-vcan0.log -l i vcan0=slcan0
</code></pre>
<p>By default canplayer will replay the file via the same device that it was captured on (<code>slcan0</code>). to get around this i need to specify the <code>vcan0</code> as new device, hence <code>vcan0=slcan0</code></p>
</li>
<li><h3 id="heading-create-cansim-service">Create cansim service</h3>
<p>Finally I want to be able to start the simulation on demand. I do this by creating a systemd service config</p>
<pre><code class="lang-bash">sudo vim /lib/systemd/system/cansim.service
</code></pre>
</li>
</ol>
<pre><code class="lang-bash">[Unit]
Description=Canbus simulator
After=network.target

[Service]
Label=cansim
Type=<span class="hljs-built_in">exec</span>
Environment=CANDATA=/mnt/DATA/sharedfolder/candump-landrover-discovery-3-vcan0.log
Environment=LOG_INTERFACE=slcan0

ExecStartPre=modprobe vcan
ExecStartPre=-ip link add dev vcan0 <span class="hljs-built_in">type</span> vcan 
ExecStartPre=ip link <span class="hljs-built_in">set</span> up vcan0
ExecStart=canplayer -I <span class="hljs-variable">$CANDATA</span> -l i vcan0=<span class="hljs-variable">${LOG_INTERFACE}</span>

ExecStop=/bin/<span class="hljs-built_in">kill</span> -s TERM <span class="hljs-variable">$MAINPID</span>
PIDFile=/run/cansim/cansim.pid
TimeoutStopSec=0
</code></pre>
<p>Now I can start the simulation whenever I need it with</p>
<pre><code class="lang-bash">systemctl start cansim
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Mounting Windows Raid Volume on Linux]]></title><description><![CDATA[Estimated time: 5 mins
My current PC setup includes 2 SSD’s, one for my Linux (Manjaro) installation and one for windows 10, I then have 2 spinning disks in raid 0 that I use for mass storage and backups etc. I switch between Linux and Windows regula...]]></description><link>https://blog.brendanscullion.com/mounting-windows-raid-volume-on-linux</link><guid isPermaLink="true">https://blog.brendanscullion.com/mounting-windows-raid-volume-on-linux</guid><category><![CDATA[Linux]]></category><category><![CDATA[Windows]]></category><dc:creator><![CDATA[Brendan Scullion]]></dc:creator><pubDate>Mon, 09 May 2022 14:30:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/GNyjCePVRs8/upload/v1652106549467/mESFQUaRV.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h4 id="heading-estimated-time-5-mins">Estimated time: 5 mins</h4>
<p>My current PC setup includes 2 SSD’s, one for my Linux (Manjaro) installation and one for windows 10, I then have 2 spinning disks in raid 0 that I use for mass storage and backups etc. I switch between Linux and Windows regularly and need access to the drives on each system. </p>
<h3 id="heading-1-map-storage-devices-devices">1. Map Storage devices devices</h3>
<p>The first thing you need to do is get the operating system to recognise the raid pair as one single drive (single UUID)</p>
<pre><code class="lang-bash">sudo pacman -S libldm
<span class="hljs-comment"># On ubuntu</span>
sudo apt-get install ldmtool
</code></pre>
<h3 id="heading-2-create-device-mapper-devices-for-all-volumes">2. Create device-mapper devices for all volumes</h3>
<p>Copy the name if the drive (ldm_vol...) as you'll need it. </p>
<pre><code class="lang-bash">sudo ldmtool create all
[
  <span class="hljs-string">"ldm_vol_DESKTOP-XXXXXX-Dg0_Volume1"</span>
]
</code></pre>
<p>Cool so now I have a storage device that's recognised by the operating system</p>
<h3 id="heading-3-mount-volume">3. Mount Volume</h3>
<pre><code class="lang-bash">sudo mkdir -p /mnt/DATA
sudo mount /dev/mapper/ldm_vol_DESKTOP-XXXXXXX-Dg0_Volume1 /mnt/DATA
</code></pre>
<p>This works, but every time I restart my machine I'm going to have to do this again. so lets make it permanent. </p>
<h3 id="heading-4-create-a-systemd-service-config-for-ldmtool">4. Create a systemd service config for ldmtool</h3>
<pre><code class="lang-bash">sudo vim /etc/systemd/system/ldmtool.service
</code></pre>
<pre><code>[Unit]
Description<span class="hljs-operator">=</span>Windows Dynamic Disk Mount
After<span class="hljs-operator">=</span>local<span class="hljs-operator">-</span>fs<span class="hljs-operator">-</span>pre.target
Before<span class="hljs-operator">=</span>local<span class="hljs-operator">-</span>fs.target
DefaultDependencies<span class="hljs-operator">=</span>no

[Service]
Type<span class="hljs-operator">=</span>simple
User<span class="hljs-operator">=</span>root
ExecStart<span class="hljs-operator">=</span><span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>bin<span class="hljs-operator">/</span>ldmtool create all

[Install]
WantedBy<span class="hljs-operator">=</span>local<span class="hljs-operator">-</span>fs.target
Enable the service
</code></pre><h3 id="heading-5-enable-the-service-so-that-it-runs-after-each-reboot">5. Enable the service so that it runs after each reboot.</h3>
<pre><code class="lang-sh">sudo systemctl <span class="hljs-built_in">enable</span> ldmtool.service
</code></pre>
<h3 id="heading-6-add-file-system-to-fstab">6. Add file system to <code>fstab</code></h3>
<pre><code class="lang-bash">sudo vim /etc/fstab
</code></pre>
<p>Append to the bottom of the file</p>
<pre><code><span class="hljs-comment"># Mount Windows raid Directory</span>
<span class="hljs-string">/dev/mapper/ldm_vol_DESKTOP-XXXXXXX-Dg0_Volume1</span>   <span class="hljs-string">/mnt/DATA</span>   <span class="hljs-string">ntfs</span>  <span class="hljs-string">rw,noatime</span>      <span class="hljs-number">0</span> <span class="hljs-number">0</span>
</code></pre><p>And I’m done. Just reboot the PC to confirm it works.</p>
<p>Alternatively, you can reference the drive using the UUID which is recommended by some people but i couldn't say exactly why that is. to do this you’ll need to find the UUID of the drive.</p>
<pre><code class="lang-bash">lsblk -o NAME,UUID   /dev/mapper/ldm_vol_DESKTOP-XXXXXXX-Dg0_Volume1

NAME                                UUID
ldm_vol_DESKTOP-XXXXXXX-Dg0_Volume1 D26C43A96C438767
</code></pre>
<p>With this information the entry in the /etc/fstab will be.</p>
<pre><code><span class="hljs-comment"># Mount Windows raid Directory</span>
<span class="hljs-attribute">UUID</span>=D<span class="hljs-number">26</span>C<span class="hljs-number">43</span>A<span class="hljs-number">96</span>C<span class="hljs-number">438767</span>           /mnt/DATA       ntfs    rw,noatime      <span class="hljs-number">0</span> <span class="hljs-number">0</span>
</code></pre>]]></content:encoded></item></channel></rss>