Sam Clulow
Yet Another Developer Blog
2021-09-17T00:00:00Z
https://smcllw.me/
Sam (he/they)
sam.g.clulow@gmail.com
Webmentions on 11ty
2021-09-17T00:00:00Z
https://smcllw.me/posts/webmentions/
<h2>is this thing on?</h2>
<p>well fuck me, it's kind of working!</p>
Getting Data from Notion
2021-09-09T00:00:00Z
https://smcllw.me/posts/bookshelf-notion/
<h2>I Really Like Notion</h2>
<p>I've been using Notion on a semi regular basis for the past 3 years. I mostly use it to create various lists, plan holidays, and track progress.</p>
<p>I get the most benefit from progress tracking. I mostly track how many books I have read in order to meet a goal I set. I wrote about this last year.</p>
<p>I've wanted to add this to the blog for a while but have it update automatically.</p>
<h2>Notion made their API public</h2>
<p>I can finally share my "bookshelf" without too much copy pasting! hurray!</p>
<p>I went to look at the <a href="https://developers.notion.com/docs/getting-started">notion API docs</a> and just skimmed the content because there's no way on earth I'm reading all that.</p>
<p>I've noticed that this is something I do all the time. It's a terrible habit, but it's just not how I learn. I cannot sit down and read through some docs, I'll get bored and do something else.</p>
<p>What I normally do is look for code examples, copy paste, and then mess about. I'll tweak things, look up a specific method in the api docs. Rince & Repeat until I have something that "works".</p>
<h2>The Code</h2>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> Client <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"@notionhq/client"</span><span class="token punctuation">)</span><br /><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'dotenv'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">config</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /><span class="token keyword">const</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs/promises'</span><span class="token punctuation">)</span><br /><span class="token comment">//have these keys in your .env file</span><br /><span class="token keyword">const</span> notion <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Client<span class="token punctuation">.</span>Client</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">auth</span><span class="token operator">:</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NOTION_KEY</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /><span class="token keyword">const</span> BookshelfId <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">BOOKSHELF_ID</span><br /><br /><br /><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">try</span> <span class="token punctuation">{</span><br /> <span class="token keyword">await</span> notion<span class="token punctuation">.</span>databases<span class="token punctuation">.</span><span class="token function">query</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">database_id</span><span class="token operator">:</span> BookshelfId<span class="token punctuation">,</span><br /> <span class="token literal-property property">filter</span><span class="token operator">:</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">or</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">property</span><span class="token operator">:</span> <span class="token string">'Name'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">text</span><span class="token operator">:</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">is_not_empty</span><span class="token operator">:</span><span class="token boolean">true</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">]</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">response</span> <span class="token operator">=></span><span class="token punctuation">{</span><br /> <span class="token keyword">const</span> titles<span class="token operator">=</span><span class="token punctuation">[</span><span class="token punctuation">]</span><br /> response<span class="token punctuation">.</span>results<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">result</span> <span class="token operator">=></span><span class="token punctuation">{</span> <br /> <span class="token keyword">const</span> finished <span class="token operator">=</span> result<span class="token punctuation">.</span>properties<span class="token punctuation">.</span>finished<span class="token punctuation">.</span>checkbox <span class="token operator">?</span> <span class="token string">"finished"</span> <span class="token operator">:</span> <span class="token string">"started"</span><br /> titles<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">[</span>result<span class="token punctuation">.</span>properties<span class="token punctuation">.</span>Name<span class="token punctuation">.</span>title<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'plain_text'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>result<span class="token punctuation">.</span>properties<span class="token punctuation">.</span>author<span class="token punctuation">.</span>rich_text<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'plain_text'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>finished <span class="token punctuation">]</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">)</span><br /> fs<span class="token punctuation">.</span><span class="token function">writeFile</span><span class="token punctuation">(</span><span class="token string">"src/_data/bookshelf.json"</span><span class="token punctuation">,</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>titles<span class="token punctuation">,</span><span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>error<span class="token punctuation">.</span>body<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br />module<span class="token punctuation">.</span>exports <span class="token operator">=</span> init</code></pre>
<p>the bookshelf ID is the last part of the URL in the web page of the database you want. It's not very clear how to find this, and there's no way I know in notion's UI to get this ID.</p>
<p>We query notion for a specific database. The Filter here is completly useless. With or without it you'll get all your entries.
But If I wanted all entries that were checked as finished, this wouldn't be too complicated to add.</p>
<p>Once we have our entries, we map over the array and keep just the data points we're interested with (name, author, finished) and we write this to a file.</p>
<p>This file is in a special 11ty folder. Data in <code>_data</code> is globally accessible, So it can be used anywhere.</p>
<p><a href="https://smcllw.me/bookshelf/">And here's it is being used!</a></p>
A Few Hours Of Python
2021-09-03T00:00:00Z
https://smcllw.me/posts/half-hack/
<h2>Today I really wanted to write some Python.</h2>
<p>I can't explain why but coding in Python always brings me a bit of joy.</p>
<p>I was talking with a colleague and the topic of RSS feeds came up. I'd thought about coding a very basic RSS reader for a bit, but today I finally managed to get started.</p>
<p>For now, the <a href="https://github.com/m1rp/feed-me-py">repo is public</a> , and I hope I'll be able to find both time and motivation to dig into it a bit more. I'm not entirely sure what I want to do with this.</p>
<p>it's basic at the moment but what the script does is:</p>
<ul>
<li>read some urls from a JSON file,</li>
<li>fetch the RSS/Atom feed for each url</li>
<li>for each feed only keep <code>title, content, url, updated date</code>,</li>
<li>sort each feed by date</li>
<li>create a list of these feeds</li>
<li>save everything to a JSON file</li>
</ul>
<p>Essentially I'm mapping a list of lists to a list of simpler lists. 'simple'.</p>
<p>I like that I could get something bare bones up and running swiftly without much python knowledge at all. There was one tricky bit that I <s>stole</s> took inspiration from <a href="https://waylonwalker.com/parsing-rss-python/#combining-feeds">this blog post</a> :</p>
<pre class="language-javascript"><code class="language-javascript">simplified_feeds<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>key<span class="token operator">=</span>lambda x<span class="token operator">:</span> <span class="token function">parse</span><span class="token punctuation">(</span>x<span class="token punctuation">[</span><span class="token string">'updated'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> reverse <span class="token operator">=</span> True<span class="token punctuation">)</span></code></pre>
<p>I can understand what's happening but I wouldn't have found this out without a ton of googling. What I'm doing here is sorting a feeds entries in chronological order. I'm not 100% sure about the key=lambda stuff.</p>
<p>What I found slightly unpleasant is that not all RSS feeds us the same data model. that's why I added a conditional statement to get the content, they aren't all called the same thing.</p>
<p>Anyway, I wanted to write some Python. I did. It was cool. I highly recommend it!</p>
Books of 2020
2020-12-18T00:00:00Z
https://smcllw.me/posts/books-2020/
<p>I Started this year by making a short list of things I wanted to accomplish in the following 12 months. I did this in the before times. Simpler times when no one was expecting a pandemic and a year's worth of lockdown.</p>
<p>One of my goals was to read more. I originally set a goal that wouldn't be too hard to acheive but that would still force me to change my reading habits: finish reading 12 books in the year, at least 50% of them not written by men. I had 2 books carried over from the previous year, but reading one book a month seemed like a nice pace. The real difficulty would be adding some diversity. A couple of years ago, my wife decided that for a set amount of time she'd make an effort to read books exclusively written by women and she got a lot out of the experience. It's about supporting women authors, changing perspective and reading different types of books. So thanks to her experience I decided to do something similar, albeit not to the same extent. My excuse is that I already had several books by blokes in my queue. In reality, if you'd have asked me to name 5 non-male authors I don't think I'd have managed, so the idea of completly discarding my references was unsettling.</p>
<hr />
<p>As Winter was coming to an end, our lives came to a stand-still. The corona virus took over our lives for a time. For some of us things stopped, for others they continued but within an unknown and terrifying new reality.</p>
<p>On the 25th of May, George Floyd was killed in broad daylight while pedestrians witnessed and filmed his murder. A wave of protests took over the USA and other parts of the world. Systemic and Institutional racism became, once again, a matter that couldn't be ignored closely followed by police brutality. This sparked a lot of discussion and debate in public and private life. For many these discussions weren't anything new, people have been calling out white supremacy for a long time. This was the first time I took a moment to think about these topics more (systemic racism, white supremacy, color-blindness to name just a few), to pay attention to what was being said and what was happening both abroad and at home and realising that this also concerns me and I cannot ignore this despite it making me mildly uncomfortable.</p>
<p>Wild fires tore through the pacific coast of the States; The melting of Siberian permafrost released more green-house gases than expected and 2020 was, once again, one of the hotest and driest years since time immemorial. The pandemic is an urgent matter that needs to be resolved, but its effects will pale in comparison to the effects of climate change.</p>
<p>This was all in the first half of the year.</p>
<p>The Second half wasn't much better.</p>
<p>In the USA, a white 17 year old proud boy took an assault rifle to a protest and shot several people, killing 2 of them, before being peacefully escorted by the police. His 2 million dollar bail was paid through crowdfunding.</p>
<p>Several terrorist incidents occured in a couple of european countries. Samuel Paty was beheaded in front of the school he worked at by a radicalised young man. This happened after a students father posted a video demanding the teacher be fired for showing one of his classes the famous caricatures printed in Charlie Hebdo. There were three fatal stabings in a church in Nice several days after by another radicalised individual. The predicatable happened, Islamophobia and racism in France had once again been cranked up to 11, closely followed by restriction of civil liberties and handing over more power to an already "over zealous" police force.</p>
<p>Several Islamophobic incidents struggled to make headlines whilst the french government ordered several anti-racist associations to stop their activities (CCIF). The french government decided to also tighten regulations around home schooling and faith schools, with a strong emphasis on muslim faith schools (because financing comes from abroad, "political islam" etc). Several weeks later, as Europe goes into a second lockdown, the french government started discussing a new set of "Global Security" laws with a particularly problematic article: filming and publishing videos of french police officers, that are identifiable, "with the intent to cause harm" would be punishable by law. During public debates aroud these laws, a black music producer was assaulted in his studio by three police officers. The assault was caught on the victim's cctv, and later published online. The victim was released as he was facing charges of assaulting the police officers.</p>
<p>A couple of days before sitting down to write this, Fouad, a 17 year old transgender girl living in the north of France, died by suicide. The day before her death she had been humiliated by school staff for choosing to wear a skirt to school. I still don't fully understand why this particular incident upsets me as much as it does.</p>
<p>Apparently, freedom of expression is only allowed to those in a priviledge position on the condition that they support the status quo.</p>
<hr />
<p>Half way through the year, I decided to expand the scope of my readings. I took the decision to also include non-white authors into my reading list. It's quite embarassing that not only could I not name 5 women authors, I couldn't easily name 5 non-white authors. I know that life is a journey and you learn along the way, that's good and all but it's really fucking embarrassing to get to your thirties and just start to realize that a) your existence and life is built on white supremacy and b) that point 'a' is a problem.</p>
<p>2020 has been, to put it mildly, pretty shit. I originally just wanted to share what I read and give a bit of context about what I chose, then things got out of hand. I personally had a good year, got into my new job, managed to go to Austria for a short break, got into birding. I'm also in a quite secure and priviledged position, I was able to work from home quite easily, I didn't suffer form loneliness as much as most people have.</p>
<p>I feel inadequate writting about having read some books as if it's some kind of accomplishement. I actually learned some great stuff from all of this and I'm really happy to have kept at it and to have read more than planned. But it's honestly risible to be treating this as if it is in any way important considering that the death count for COVID is over 1.5 million.</p>
<p>Anyway here's the fuckin list (chronological order):</p>
<table>
<thead>
<tr>
<th style="text-align:left">title</th>
<th style="text-align:left">Author</th>
<th style="text-align:center">emoji review</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">Crime and Punishement</td>
<td style="text-align:left">Dostoyevsky</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">Thinking Fast and Slow</td>
<td style="text-align:left">Daniel Kahneman</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">Dernières nouvelles de la science</td>
<td style="text-align:left">Mathieu Vidard</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">How Did We Get into This Mess?</td>
<td style="text-align:left">George Monbiot</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">Getting Real</td>
<td style="text-align:left">Jason Fried</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">Brotopia ; breaking up the boys club of silicon valley</td>
<td style="text-align:left">Emily Chang</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">Men Explain Things to Men</td>
<td style="text-align:left">Rebecca Solnit</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">Moan; anonymous essays on female orgasm</td>
<td style="text-align:left">Emma koenig</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">Dans les forΓͺts de SibΓ©rie</td>
<td style="text-align:left">Sylvain Tesson</td>
<td style="text-align:center">π (he can go in the bin)</td>
</tr>
<tr>
<td style="text-align:left">The Hidden Life of Trees</td>
<td style="text-align:left">Peter Wohlleben</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">The End of Nature</td>
<td style="text-align:left">Bill McKibben</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">White Fragility</td>
<td style="text-align:left">Robin Diangelo</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">Go tell it on the Mountain</td>
<td style="text-align:left">James Baldwin</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">An Indigenous Poeples' History of the United States</td>
<td style="text-align:left">Roxanne Dunbar-Oritz</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">How to be an Antiracist</td>
<td style="text-align:left">Ibram X Kendi</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">Shinrin-Yoku</td>
<td style="text-align:left">Dr Qing Li</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">The Secret Network of Nature</td>
<td style="text-align:left">Peter Wohlleben</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">Bientot Papa</td>
<td style="text-align:left">Lionel Pailles & Benoit le Goedec</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">So You Want to Talk About Race</td>
<td style="text-align:left">Ijeoma Olou</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">The Fire Next Time</td>
<td style="text-align:left">James Baldwin</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">The New Jim Crow; Mass Incarceration in the Age of Colorblindness</td>
<td style="text-align:left">Michelle Alexander</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">Headscarves and Hymens; Why the Middle East needs a Sexual Revolution</td>
<td style="text-align:left">Mona Eltahawy</td>
<td style="text-align:center">π</td>
</tr>
<tr>
<td style="text-align:left">The Age of Surveillance Capitalism</td>
<td style="text-align:left">Shoshanna Zuboff</td>
<td style="text-align:center">π</td>
</tr>
</tbody>
</table>
How to make a URL shortener with Express and TypeScript
2020-05-25T00:00:00Z
https://smcllw.me/posts/url-shortener/
<h2>I've always struggled with side projects.</h2>
<p>I find it really complicated to commit to an idea or a project until completion. They're either too small and not engaging (todo lists come to mind), or too intimidating (trying to contributing to open source projects fills me with self-doubt and imposter syndrome). Finding something that's just right is a real challenge.</p>
<p>What I've found works for me is making small tool that I need or want, scratching my own itch so to speak, with the intention of actually using it. That second part is quite important to me, I can't stay motivated if I'm making something for no real reason. For me, actually making small things and launching them is the best way to learn something.</p>
<p>So I decided to make a URL shortener! It ticks all the boxes: it can be as simple or as over-engineered as I want, I get the opportunity to get familiar with a stack I don't use that often, and I can actually use it!</p>
<p>There are 2 parts to this project:</p>
<ul>
<li>the <a href="https://smcllw.me/posts/url-shortener/#code">code</a></li>
<li>the <a href="https://smcllw.me/posts/url-shortener/#ops">deployment</a></li>
</ul>
<p>I'm going to walk through what I've done with code examples and how I deployed everything. It's worth mentioning that all the services I have used are free, with the exception of of my domain name.</p>
<p>This API is made with <a href="https://expressjs.com/">Express</a>, <a href="https://www.typescriptlang.org/">TypeScript</a> and <a href="https://www.mongodb.com/">MongoDB</a>, the API is hosted on <a href="https://www.heroku.com/">heroku</a>, the database is hosted on <a href="https://www.mongodb.com/cloud/atlas">MongoDB Atlas</a>, I got a domain name on <a href="https://www.namecheap.com/">namecheap</a> and <a href="https://netlify.com/">Netlify</a> provides some DNS magic.</p>
<p>Anyway, let's get started!</p>
<h2>Quick Technical Introduction</h2>
<p>What I decided to start off with was a minimal, feature free URL shortener. One way to achieve this is assign a unique ID to a submitted URL and store that information somewhere. Then when someone requests that unique ID, redirect them to the original URL.</p>
<p>We'll store the URLs in a MongoDB instance, but this could also be achieved with different types of database, this could even be achieved using a service like <a href="https://docs.google.com/spreadsheets">google sheets</a> or <a href="https://airtable.com/">airtable</a>!</p>
<p>For creating a Unique ID, we can a node package called <code>shortid</code> as we don't need anything fancy.</p>
<p>This API is an express app running on a Heroku machine, but it could also be adapted to run as a cloud function (or lambda function) or using a different framework.</p>
<h2 id="code">The Code </h2>
<p><em><em>you can find all the code to follow along <a href="https://github.com/m1rp/url-shortener-example/tree/master">here</a></em></em></p>
<p>The Code is approximately structured as follows:</p>
<pre class="language-json"><code class="language-json">|<br />|---- controllers<br />| |--- linksController.ts <span class="token comment">/* all functions related to links */</span><br />| \--- checker.ts <span class="token comment">/* check that request authorised */</span><br />|<br />|---- models<br />| \--- link.ts <span class="token comment">/* data model of link objects */</span><br />|<br />|---- routes<br />| \--- index.ts <span class="token comment">/* routes and associated controllers */</span><br />|<br />|---- index.ts <span class="token comment">/* server and db init*/</span></code></pre>
<p>We won't be using views as we're only going to be interacting with the Backend. Adding a Frontend would require adding some form of authentication (to limit who can add and remove links) and that's out of scope.</p>
<p>In the index file, we connect to our Mongodb instance, initialise our app and routes.</p>
<p>For sensitive data, you can create a <code>.env</code> file in the root of your project and use the <code>dotenv</code> module to access those variables globally.</p>
<p>Here we're using a remote instance of MongoDB that I'll explain how to setup later.</p>
<pre class="language-ts"><code class="language-ts"><span class="token comment">// index.ts</span><br /><span class="token keyword">require</span><span class="token punctuation">(</span> <span class="token string">'./model/link'</span> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> express <span class="token keyword">from</span> <span class="token string">"express"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> mongoose <span class="token keyword">from</span> <span class="token string">"mongoose"</span><br /><span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> bodyParser <span class="token keyword">from</span> <span class="token string">"body-parser"</span><br /><span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> routes <span class="token keyword">from</span> <span class="token string">'./routes/index'</span><br /><span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> dotenv <span class="token keyword">from</span> <span class="token string">'dotenv'</span><br /><span class="token keyword">import</span> morgan <span class="token keyword">from</span> <span class="token string">"morgan"</span><br /><span class="token keyword">import</span> helmet <span class="token keyword">from</span> <span class="token string">"helmet"</span><br /><br /><span class="token comment">// env variables</span><br />dotenv<span class="token punctuation">.</span><span class="token function">config</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /><span class="token keyword">const</span> user <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">USER</span><br /><span class="token keyword">const</span> pass <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">PASSWORD</span><br /><span class="token keyword">const</span> mongodbURL <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">DB_URL</span><br /><br /><span class="token comment">//initialise connection to DB</span><br /><span class="token keyword">const</span> uri <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">mongodb+srv://</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>user<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>pass<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">@</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>mongodbURL<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br /><br /><span class="token comment">// avoid deprecation warnings</span><br /><span class="token comment">// https://mongoosejs.com/docs/deprecations.html</span><br />mongoose<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span> <span class="token string">'useFindAndModify'</span><span class="token punctuation">,</span> <span class="token boolean">false</span> <span class="token punctuation">)</span><span class="token punctuation">;</span><br />mongoose<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span> <span class="token string">'useCreateIndex'</span><span class="token punctuation">,</span> <span class="token boolean">true</span> <span class="token punctuation">)</span><span class="token punctuation">;</span><br />mongoose<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span> <span class="token string">'useUnifiedTopology'</span><span class="token punctuation">,</span> <span class="token boolean">true</span> <span class="token punctuation">)</span><span class="token punctuation">;</span><br />mongoose<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span> <span class="token string">'useNewUrlParser'</span><span class="token punctuation">,</span> <span class="token boolean">true</span> <span class="token punctuation">)</span><br />mongoose<span class="token punctuation">.</span><span class="token function">connect</span><span class="token punctuation">(</span> uri <span class="token punctuation">)</span><br /><span class="token keyword">const</span> db <span class="token operator">=</span> mongoose<span class="token punctuation">.</span>connection<br /><br />db<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span> <span class="token string">'error'</span><span class="token punctuation">,</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span> <span class="token builtin">console</span><span class="token punctuation">,</span> <span class="token string">'connection error:'</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span><br />db<span class="token punctuation">.</span><span class="token function">once</span><span class="token punctuation">(</span> <span class="token string">'open'</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span> <span class="token string">'Database connected:'</span><span class="token punctuation">,</span> uri <span class="token punctuation">)</span> <span class="token punctuation">)</span><br /><br /><span class="token comment">// initialise app</span><br /><span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token function">express</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br />app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span> <span class="token function">helmet</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><br />app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span> bodyParser<span class="token punctuation">.</span><span class="token function">urlencoded</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> extended<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><br />app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span> <span class="token string">'/api/*'</span><span class="token punctuation">,</span> bodyParser<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><br />app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span> <span class="token function">morgan</span><span class="token punctuation">(</span> <span class="token string">'combined'</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><br />app<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span> <span class="token string">'port'</span><span class="token punctuation">,</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">PORT</span> <span class="token operator">||</span> <span class="token number">3000</span> <span class="token punctuation">)</span><br /><br />routes<span class="token punctuation">.</span><span class="token function">routes</span><span class="token punctuation">(</span> app <span class="token punctuation">)</span><br /><br />app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span> app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span> <span class="token string">"port"</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span> <span class="token string">'App is running at %d'</span><span class="token punctuation">,</span> app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span> <span class="token string">'port'</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><br /><span class="token punctuation">}</span><br /><span class="token punctuation">)</span></code></pre>
<p>Let's define the data model for our Links! We're also going to create an <code>Interface</code> for our links. An <code>Interface</code> is a typescript thing, it's an explicit way to define an object's shape. You can read more about that <a href="https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html">in the Typescript documentation</a></p>
<p>For describing and using our data, we create a <code>Schema</code>. According to the Mongoose website, a <code>Schema</code> describes the shape of our <code>Documents</code> in a <code>Collection</code>. For a more in depth explanation please check out the <a href="https://mongoosejs.com/docs/guide.html">mongoose guide</a></p>
<p>It really sounds like we're doing the same thing twice, and we kind of are. The <code>Interface</code> is the description of the object used by typescript and it is completely optional. On the other hand, the <code>Schema</code> is the description of the object that will be stored in our database and this is not optional.</p>
<pre class="language-ts"><code class="language-ts"><span class="token comment">// models/links.ts</span><br /><span class="token keyword">import</span> mongoose<span class="token punctuation">,</span> <span class="token punctuation">{</span> Schema<span class="token punctuation">,</span> Document <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"mongoose"</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">ILink</span> <span class="token punctuation">{</span><br /> originalLink<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span><br /> generatedLink<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span><br /> <span class="token constant">GID</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span><br /> createdAt<span class="token operator">?</span><span class="token operator">:</span> Date<span class="token punctuation">,</span><br /> updatedAt<span class="token operator">?</span><span class="token operator">:</span> Date<span class="token punctuation">,</span><br /> popularity<span class="token operator">:</span> <span class="token builtin">number</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">LinkType</span> <span class="token operator">=</span> ILink <span class="token operator">&</span> Document<br /><br /><span class="token keyword">const</span> linkSchema <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Schema</span><span class="token punctuation">(</span> <span class="token punctuation">{</span><br /> originalLink<span class="token operator">:</span> <span class="token punctuation">{</span><br /> type<span class="token operator">:</span> String<span class="token punctuation">,</span><br /> unique<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> required<span class="token operator">:</span> <span class="token boolean">true</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> generatedLink<span class="token operator">:</span> String<span class="token punctuation">,</span><br /> <span class="token constant">GID</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> type<span class="token operator">:</span> String<span class="token punctuation">,</span><br /> unique<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> required<span class="token operator">:</span> <span class="token boolean">true</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> createdAt<span class="token operator">:</span> <span class="token punctuation">{</span><br /> type<span class="token operator">:</span> Date<span class="token punctuation">,</span><br /> <span class="token keyword">default</span><span class="token operator">:</span> Date<span class="token punctuation">.</span>now<br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> updatedAt<span class="token operator">:</span> <span class="token punctuation">{</span><br /> type<span class="token operator">:</span> Date<span class="token punctuation">,</span><br /> <span class="token keyword">default</span><span class="token operator">:</span> Date<span class="token punctuation">.</span>now<br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> popularity<span class="token operator">:</span> <span class="token punctuation">{</span><br /> type<span class="token operator">:</span> Number<span class="token punctuation">,</span><br /> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token number">0</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">const</span> Link <span class="token operator">=</span> mongoose<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">model</span><span class="token generic class-name"><span class="token operator"><</span>LinkType<span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token string">'Link'</span><span class="token punctuation">,</span> linkSchema <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Lets look into our controllers. This is where most of the functionality is implemented, where we query our database, parse requests and where we model our response.</p>
<p>We can start by implementing some core functionalities, creating a Link, deleting a link, and finding a link. We'll be making use of the <code>Interface</code> and <code>Type</code> we defined previously. Here's a quick implementation of these functions:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Request<span class="token punctuation">,</span> Response<span class="token punctuation">,</span> NextFunction <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"express"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> Link<span class="token punctuation">,</span> ILink<span class="token punctuation">,</span> LinkType <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../model/link'</span><br /><span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> shortid <span class="token keyword">from</span> <span class="token string">'shortid'</span><br /><span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> dotenv <span class="token keyword">from</span> <span class="token string">'dotenv'</span><br />dotenv<span class="token punctuation">.</span><span class="token function">config</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /><br /><span class="token keyword">const</span> baseUrl <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">BASE_URL</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">const</span> createLink <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> req<span class="token operator">:</span> Request<span class="token punctuation">,</span> res<span class="token operator">:</span> Response <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator"><</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">try</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> gid<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">=</span> shortid<span class="token punctuation">.</span><span class="token function">generate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token keyword">const</span> originalLink <span class="token operator">=</span> req<span class="token punctuation">.</span>body<span class="token punctuation">.</span>originalLink<br /> <span class="token keyword">const</span> generatedLink<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">=</span> baseUrl <span class="token operator">?</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>baseUrl<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>gid<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span> <span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>req<span class="token punctuation">.</span>headers<span class="token punctuation">.</span>host<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>gid<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><br /> <span class="token keyword">const</span> linkObject<span class="token operator">:</span> ILink <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token string-property property">'originalLink'</span><span class="token operator">:</span> originalLink<span class="token punctuation">,</span><br /> <span class="token string-property property">'generatedLink'</span><span class="token operator">:</span> generatedLink<span class="token punctuation">,</span><br /> <span class="token string-property property">'GID'</span><span class="token operator">:</span> gid<span class="token punctuation">,</span><br /> <span class="token string-property property">'popularity'</span><span class="token operator">:</span> <span class="token number">0</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">const</span> newLink<span class="token operator">:</span> LinkType <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Link</span><span class="token punctuation">(</span> linkObject <span class="token punctuation">)</span><br /> <span class="token keyword">await</span> Link<span class="token punctuation">.</span><span class="token function">findOneAndUpdate</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> originalLink<span class="token operator">:</span> originalLink <span class="token punctuation">}</span><span class="token punctuation">,</span> newLink <span class="token punctuation">)</span><br /> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span> <span class="token number">201</span> <span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span> newLink <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span> error <span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span> <span class="token number">404</span> <span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> error<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>error<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><br /><span class="token keyword">export</span> <span class="token keyword">const</span> getLinkById <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> req<span class="token operator">:</span> Request<span class="token punctuation">,</span> res<span class="token operator">:</span> Response<span class="token punctuation">,</span> next<span class="token operator">:</span> NextFunction<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator"><</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> gid <span class="token operator">=</span> req<span class="token punctuation">.</span>params<span class="token punctuation">.</span>gid<br /> <span class="token keyword">try</span> <span class="token punctuation">{</span><br /> <span class="token comment">// increment popularity of link</span><br /> <span class="token keyword">const</span> url <span class="token operator">=</span> <span class="token keyword">await</span> Link<span class="token punctuation">.</span><span class="token function">findOneAndUpdate</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> <span class="token constant">GID</span><span class="token operator">:</span> gid <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $inc<span class="token operator">:</span> <span class="token punctuation">{</span> popularity<span class="token operator">:</span> <span class="token number">1</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><br /> url <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token operator">?</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span> <span class="token number">301</span> <span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">redirect</span><span class="token punctuation">(</span> <span class="token string">"/api"</span> <span class="token punctuation">)</span> <span class="token operator">:</span> res<span class="token punctuation">.</span><span class="token function">redirect</span><span class="token punctuation">(</span> <span class="token number">301</span><span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>url<span class="token punctuation">.</span>originalLink<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">)</span><br /> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span> error <span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span> <span class="token number">301</span> <span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">redirect</span><span class="token punctuation">(</span> <span class="token string">"/api"</span> <span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><br /><br /><br /><span class="token keyword">export</span> <span class="token keyword">const</span> deleteLink <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> req<span class="token operator">:</span> Request<span class="token punctuation">,</span> res<span class="token operator">:</span> Response <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator"><</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> gid <span class="token operator">=</span> req<span class="token punctuation">.</span>params<span class="token punctuation">.</span>gid<br /> <span class="token keyword">try</span><span class="token punctuation">{</span><br /> <span class="token keyword">await</span> Link<span class="token punctuation">.</span><span class="token function">findOneAndDelete</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> <span class="token constant">GID</span><span class="token operator">:</span> gid <span class="token punctuation">}</span> <span class="token punctuation">)</span><br /> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span> <span class="token number">204</span> <span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><span class="token keyword">catch</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">{</span><br /> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span> <span class="token number">404</span> <span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> error<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>error<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /></code></pre>
<p>A couple of things worth noting:</p>
<ul>
<li>the error handling is nearly none existent</li>
<li>in our <code>createLink</code> function, we don't check if the GID already exists.</li>
<li><code>getLinkById</code> will redirect us directly to our original link, but will also increment a links popularity. This could be extended to include other data to give you some feedback on how many hits your blog post gets coming from one specific source without needing to implement any user tracking.</li>
</ul>
<p>Ok we're halfaway there! Let's get our routing sorted:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> linksController <span class="token keyword">from</span> <span class="token string">'../controllers/linksController'</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> validator <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../controllers/validator'</span><br /><span class="token keyword">import</span> express<span class="token punctuation">,</span> <span class="token punctuation">{</span> Application <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"express"</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">routes</span> <span class="token operator">=</span> <span class="token punctuation">(</span> app<span class="token operator">:</span> Application <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span> <span class="token string">'/api/:gid'</span><span class="token punctuation">,</span> linksController<span class="token punctuation">.</span>getLinkById <span class="token punctuation">)</span><br /> app<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span> <span class="token string">'/api/shorten'</span><span class="token punctuation">,</span> validator <span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span> <span class="token string">'/api/shorten'</span><span class="token punctuation">,</span> linksController<span class="token punctuation">.</span>createLink <span class="token punctuation">)</span><br /> app<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span> <span class="token string">'/api/delete/:gid'</span><span class="token punctuation">,</span> validator <span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span> <span class="token string">'/api/delete/:gid'</span><span class="token punctuation">,</span> linksController<span class="token punctuation">.</span>deleteLink <span class="token punctuation">)</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>We have our 3 routes using our 3 functions. There are a few ways to test these endpoints, we could use a tool like <a href="https://www.postman.com/">postman</a> or <a href="https://insomnia.rest/">insomnia</a> to query our API and save those queries, or we can use the <code>curl</code> tool in our terminal. Lets ignore (or remove) the <code>validator</code> function for the moment and try to create a link with the following <code>curl</code> command:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">curl</span> --header <span class="token string">"Content-Type: application/json"</span> <span class="token punctuation">\</span><br /> --request POST <span class="token punctuation">\</span><br /> --data <span class="token string">'{"originalLink":"my-cool-site.com"}'</span> <span class="token punctuation">\</span><br /> localhost:3000/api/shorten</code></pre>
<p>Now if we check our database, we should see that we have an entry. I would advise using a tool like Insomnia as it allows you to save your queries as you might need to do some testing and debugging.</p>
<p>I added a <code>validator</code> function to my post and delete routes as I don't want anyone to be able to do whatever they want. Here you could use an authentication library or check for a token, or leave it as is if you're testing.</p>
<p>That's pretty much it for the code.</p>
<p>You can try it out for yourself by <a href="https://github.com/m1rp/url-shortener-example/tree/master">cloning the repo on Github</a></p>
<h2 id="ops">The Deployment </h2>
<p>Let's set up or database, to do that we're going to go to <a href="https://www.mongodb.com/cloud/atlas">https://www.mongodb.com/cloud/atlas</a> and set up a free account.</p>
<p>Once that is done, we need to create a user to read from and write to our database. We can give a username and password. Then we go back to our cluster dashboard and setup a connection. We'll chose the option to connect our application, this will provide us with a code snippet to add to our application. We've already added the snippet so we need to add our user, password and endpoint to our ENV variables.</p>
<p>Now to deploy our service to <a href="https://www.heroku.com/">Heroku</a>.</p>
<p>We can start by creating a free account on their homepage. Once that is done, I'd advise either using <a href="https://devcenter.heroku.com/articles/heroku-cli">Heroku's CLI</a>, or going to the "Deploy" page and selection the deployment method that allows you to connect to Github (this will allow you to automate your deployment process).</p>
<p>Nearly there, not much left to configure! We need to add some Config Vars in the settings page. There are at least 4 that you'll need to provide, we defined them earlier in our app. 3 variables for connecting to the database, and one to specify the base URL of our shortened link</p>
<pre class="language-json"><code class="language-json">BASE_URL=mysite.com/short-links/<br />DB_PASS=my-database-password<br />DB_USER=username<br />DB_ENDPOINT=mongo.endpoint</code></pre>
<p>You might want something catchier, but you'll need to add this URL as a custom domain to you Heroku application. you might have already purchased a domain that you can add here. I had to be a bit more "creative", I have a domain already registered to my blog that is hosted with <a href="https://www.netlify.com/">Netlify</a>, I needed to add a new DNS record entry linked to my Heroku app and also add that domain in Heroku. I'm not an expert on this stuff, but <a href="https://devcenter.heroku.com/articles/custom-domains">Heroku's Documentation</a> is pretty solid!</p>
<p>One issue you'll run into is with SSL certificates, I have not yet figured out a free way of getting these generated and applied to Heroku.</p>
<h2>Wrapping up</h2>
<p>I spent as much time writing this app as I did writing ABOUT it. But I've really enjoyed the whole process. Being able to mess about with something like this has been fun, I've learnt quite a bit, and being able to create and launch a service is really rewarding. The whole process has also prevented some burnout which is the biggest benefit.</p>
<p>If I were doing this again, I'd ditch TypeScript. For such a small app, in my opinion, there's nearly no benefit. I'd have much quicker to get something up and running if I hadn't wasted half a day remembering to install types and figuring out that a response in express has a <code>express.Application.Response</code> type. I felt like i was spending a lot of time just fighting the TypeScript compiler when I could have been writing code.</p>
<p>I also re-discovered that Express is very minimal and un-opinionated, which is fine for my use case, but it does leave me feeling a bit lost when starting something from scratch (like: where do I put my routes? should I have controllers? what's a controller? Do I actually know what I'm doing? help).</p>
<p>Anyway, I hope you've enjoyed reading it and hopefully you learned something too!</p>
Moving to 11ty
2020-04-08T00:00:00Z
https://smcllw.me/posts/11ty/
<p>I've been tinkering with this blog for the past three years, and have never actually gotten around to doing anything significant with it.</p>
<p>The main reason for that is lazyness. It's also fair to say that for at least the same amount of time the last thing I want to do after work is... more work.</p>
<p>That's kind of what this site was, just a bunch of things to deal with, whether it be actually coming up with a design, or finding a sensible way of dealing with images.</p>
<p>The site was made with <a href="https://smcllw.me/posts/11ty/gohugo.io">Hugo</a>, a static site generator written in <a href="https://golang.org/">go</a>. Back in the day, I was working on <a href="https://kreuzwerker.de/en/case/audible-cms">one of my first porjects</a> and got quite familliar with <a href="https://smcllw.me/posts/11ty/gohugo.io">Hugo</a>. It's around that time that I started this blog and decided to use the same stack, which made perfect sense as it was one I was familliar with.</p>
<p>But <a href="https://smcllw.me/posts/11ty/gohugo.io">Hugo</a> didn't quite fit my use case. It really shines if you need to render thousands of pages and collections, taxonomies or whatever you want to call them.</p>
<p>I had like 5 pages, and everytime I wanted to write something I could only see problems that need fixing. Each time I would need to go through a load of documentation to figure out how to do something "trivial".</p>
<p>The bigest issue that I had was not being able to easily add build time functionalities, image processing for example. There are ways around this issue, I could have used an external service like <a href="https://cloudinary.com/">cloudinary</a> (this would be a sensible solution), I could have made an API for my images and have hosted it on <a href="https://www.heroku.com/">heroku</a> (I really should have gone down this route), but what I wanted is to be able to resize my images at build time. I have done this before in Nodejs, and I could have done this with my Hugo setup. But I didn't want to have multiple command in several languages for something like this.</p>
<p>I also really wanted to try out the static site generators all the cool kids are using!
I really missed the experience migrating <a href="https://kreuzwerker.de/en/post/relaunch-with-nuxt">a corporate site</a> over to <a href="https://nuxtjs.org/">Nuxt</a>, I also tried <a href="https://www.gatsbyjs.org/">Gatsby</a> ages ago. But they can be really JavaScript heavy for users, and this is something I want to avoid.</p>
<p>I'd heard some really good stuff about <a href="https://www.11ty.dev/">11ty</a> and it seems to tick all the boxes. It was time to move.</p>
<p>The move was pretty painless, swaping go templates for nunjucks took minutes. There were a couple of things that weren't easy to find in the <a href="https://www.11ty.dev/docs/">Documentation</a>, I still need to figure out a bunch of details but I got the whole thing to work exactly like it did in hugo in a couple of evenings. I really like how simple it feels, and how flexible it can be.</p>
<p>One thing that I really love, if I can't figure out somehting in the docs, there are a bunch of <a href="https://www.11ty.dev/docs/starter/">starter projects</a> to look at and see how people smarter than me have dealt with the issue!</p>
<p>I'm really looking forward to messing around with it some more!</p>
Our Green Planet
2019-04-15T00:00:00Z
https://smcllw.me/posts/eco-conscious/
<h2><em>Sustainable lifestyle, Small steps we can take, and why relying on tech and capitalism won't save us.</em></h2>
<p>I care a lot about our planet, but every day I'm reminded how fragile it is and that we are destroying it at an unprecedented speed. We are all contributing in some way or another to global warming, and it is our collective responsibility to deal with this problem.</p>
<h3>Trying to be a bit more sustainable</h3>
<p>Over the past several years, my partner and I have drastically changed our lifestyle to match our ethical convictions. We've made two changes that are simultaneously quite straight forward to implement, but not that easy to stick to. Keep in mind, these changes are possible mainly because we're in a rather privileged position (both working full time, no kids, no debt and living in a European capital), and I'm fully aware that this is not the case for the majority of people.</p>
<h3>We've stopped consuming animal based products altogether.</h3>
<p>For me, that meant completely changing my diet, cutting out all meat, cheese, butter, milk, honey... any animal food or by-product. I'd really love to be able to say: "It's really no big deal, it's super easy and requires literally no effort".</p>
<p>But that would be a lie. For me, as for many Europeans, my meals' centerpiece was always meat. Vegetables were a side, complementary.</p>
<p>Dropping animal products meant redefining my concept of a meal, learning how to cook completely different foods and meals (I've always enjoyed and felt comfortable cooking, but this was something else).</p>
<p>It also forced me to reconsider my relation to food, each of steps in the "farm to plate" process, and what each of those steps implies. It made me consider the effects of industrial scale farming on our ecosystem, on the impoverishment of farmers while agricultural corporations' profits soar.
Or how over decades we have forgotten, or refuse to see, the link between the meat that is on our plate, the animal it belongs to, how we take this meat from the animal and the conditions that the animal lived and died in.
Or where in the world the vegetables on our plate were cultivated, what kind of products had been applied to them and the effect that they have on us and on the planet.
I could (and might) write more about this, as there's a lot to be said about our relation to food, how food can be sexist and patriarchal, or elitist and so on.</p>
<h3>We've also radically changed our purchasing habits.</h3>
<p>For food, we mainly buy organic, and try to stick to regional and seasonal produce when reasonable. This is quite restrictive and expensive, but no animal products and limiting exotic produce helps keep the price down.</p>
<p>For nearly everything else, we've reconsidered our needs and overall reduces the amount we consume. This mainly involves reconsidering impulse purchases, but can go as far as checking in what condition certain products are made, reducing packaging, or good old fashioned boycotting</p>
<p>At some point tho, there's no such thing as purely ethical consumption in a capitalistic system, so we have to be aware of that and know where to draw the line.</p>
<h3>So why am I saying all this?</h3>
<p>I'm not writing this to boast, or take some kind of moral high-ground.</p>
<p>As I mentioned at the start, we're facing catastrophic consequences of climate change that will transform the world as we know it. Regardless of our actual impact and influence we all benefit from efforts to reduce the impact of global warming.</p>
<p>There's a great deal of inequality regarding an individual's carbon footprint and how that same individual will be affected by climate change. Those who have the highest carbon footprint will be the least affected by climate change.
But we're kind of "all in this together", regardless of privilege and impact, or lack of.</p>
<p>So what can you do?</p>
<p>What do you want to do?</p>
<p>What can you realistically do that you feel happy with?</p>
<p>Consider what you can effect, and to what extent.</p>
<p>Checkout the industries that have the most negative impact of climate, and think about your relation/involvement regarding that industry.</p>
<p>A few easy ones we all interact with:</p>
<ul>
<li><strong>transport</strong>: do you drive regularly? could you use public transport more? What about air travel? have you considered offsetting the carbon on your next flights?</li>
<li><strong>fashion</strong>: Are the clothes you purchase made in un-ethical conditions? have you considered second hand clothes? Vegan clothes (not leather or silk)? Fair Trade, organic?</li>
<li><strong>agriculture</strong>: Where does your food come from? does it contribute to deforestation, or droughts? What's in it? Have you considered doing one meat-free day a week?</li>
<li><strong>waste management</strong>: can you reduce the amount of packaging you purchase? Can you reduce single use plastic? What about recycling, composting?</li>
</ul>
<p>There's a ton of things you can do! Some things are realistic, others require substantial effort or are not always possible.</p>
<p>What I want is for you to think about how everything we consume and everything we do has an impact. And that you influence that.
As an individual what we chose to do has a limited impact, collectively we have more influence. But we can't really consider ourselves the main culprits, as we can't all realistically opt-out of the capitalistic status-quo.</p>
<h3>The root cause of the issue <strong>cannot</strong> be the solution</h3>
<p>Ultimately, the burden of combating climate change shouldn't be carried only by consumers. At the end of the day, the consumer cannot be fully responsible for a company's production methods, the consumer doesn't have a say in this process.</p>
<p>This is where the biggest impact is to be made.</p>
<p>Unfortunately, companies cannot be trusted to self regulate or to do the right thing. At the highest level, companies are a group of stakeholders that have two main objectives that need to be met, no matter the cost: <strong>Growth</strong> and <strong>Income</strong>.</p>
<p>These two goals are not sustainable. Maximizing income and growth are the source of the problem, companies need to either sell constantly more, or make better margins by lowering production costs. Both these option negatively impact climate.</p>
<p>The global climate crisis has enabled various forms of green industries to thrive. There are some real solutions to actual problems. There's an overwhelming amount of either post-colonial micro-credit band-aid solutions, disaster capitalism, green-washing or just parallel revenue streams. <em>Yes the planet and life on earth is important, but is it really as important as Growth and GDP?</em></p>
<p>It is unrealistic to believe that we can keep our current standard of living, keeps consuming at this rate, and keep on this "Business as usual" and "solve" climate change. Our excessive and intensive consumption are the problem. The belief that Growth is the end goal is what got us into this mess. It is vital, necessary, that we reconsider and change our society drastically if we want to have a chance of fighting climate change.</p>
Future
2019-01-03T00:00:00Z
https://smcllw.me/posts/future/
<h2><em>I have a blog, I should probably use it</em></h2>
<p>For the past couple of years I've owned this domain, and have done absolutely nothing with it. Off the top of my head there are 2 reasons for this:</p>
<ul>
<li>I don't believe I have much to write about.</li>
<li>I don't write because there's always something to fix.</li>
</ul>
<p>And put like that, those aren't really valid reasons at all. There are a bunch of things I could write about where it be tech related, code snippets or other things. Those things might not deserve any attention, but they might be worth writing.</p>
<p>One of the benefits of writing is that it helps remember and document information. Stefan mentioned his <a href="https://www.stefanjudis.com/today-i-learned/" title="Stefan's TIL">TIL section</a> a couple of year's ago and I thought it was genius. I still think it's an amazing idea, not the easiest to stick to, but a really good way of documenting knowledge and also progression.
Also it's good for structuring thoughts and knowledge and communicating that knowledge.</p>
<p>Regarding the second point, <em>there'll always be something to fix</em>. While writing this post, I've fixed an image display issue and tried to set up preview branch on <a href="https://www.netlify.com/" title="Netlify">Netlify</a>. At some point I'll want to add Lazy-loading, image compression and other stuff. But that should give me something to write about, I just need to dedicate some time to it.</p>
<p>So For this year I want to:</p>
<ul>
<li>write more,</li>
<li>do more Open Source, it doesn't need to just be code,</li>
<li>learn some more <a href="https://www.python.org/.com/" title="Python">Python</a> and <a href="https://www.rust-lang.org/" title="Rust">Rust</a>,</li>
<li>learn much more <a href="https://www.typescriptlang.org/" title="Typescript">Typescript</a>.</li>
<li>write more about what i'm learning</li>
</ul>
Static Sites Berlin
2018-01-22T00:00:00Z
https://smcllw.me/posts/static-sites-berlin/
<h3>talk 1 : Charlie Owen</h3>
<h4>jekyll to hugo</h4>
<p>talking points:</p>
<ul>
<li>intro to static site generators</li>
<li>pros and cons of jekyll</li>
<li>build systems</li>
<li>pros and cons of Hugo</li>
<li>Linode</li>
<li>Netlify</li>
<li>next steps</li>
<li>indie web</li>
</ul>
<p><br /><br /></p>
<h3>talk 2 : Oleksandr Tryshchenko</h3>
<h4>Kiss with Hexo</h4>
<p>talking points:</p>
<ul>
<li>migration process</li>
<li>rendering</li>
<li>deployment</li>
<li>api</li>
<li><a href="https://smcllw.me/posts/static-sites-berlin/www.otry.eu">otry.eu</a></li>
</ul>
<p><br /><br /></p>
<h3>talk 3 : Benedikt RΓΆtsch</h3>
<h4>Metalsmith.js</h4>
<p>talking points:</p>
<ul>
<li>nodejs.org is written in metalsmith</li>
<li>file to object conversion</li>
<li>layout engines</li>
<li>PUG</li>
<li>very easy to get set up</li>
<li>nodemon and browser-sync</li>
<li>works with webpack</li>
<li>write your own plugins</li>
<li>vast ecosystem</li>
</ul>
Hello From IWC
2017-11-05T00:00:00Z
https://smcllw.me/posts/hello-from-IWC/
<p>I created and deployed this Blog during the Indie Web Camp Berlin 2017.</p>
<p>I learnt about <a href="http://microformats.org/wiki/Main_Page">Microformats</a> , <a href="https://indieauth.com/">indieAuth</a>.</p>
<p>I met a bunch of friendly people.</p>
<p>So far, to make this blog I'm using <a href="https://gohugo.io/">Hugo</a> to generate the site, <a href="http://tachyons.io/">Tachyons</a> for styling, and <a href="https://www.netlify.com/">Netlify</a> for deploying.</p>
<p>For now there's not much to show at all.</p>
<p>All the code is hosted on github!</p>
Kill processes running on specific ports
2021-11-19T00:00:00Z
https://smcllw.me/til/kill-on-port/
<h2>The Problem</h2>
<p>you had a couple of node.js apps running from you terminal and you closed the tabs. You opened a new terminal tan, tried to <code>npm run dev</code> and got hit with a <code>EADDRINUSE</code> error. You could run <code>ps</code> and try to figure out which jobs are your node apps.</p>
<h2>The solution</h2>
<p>One thing you do know is what ports these apps are listening/running on.</p>
<p><code>lsof</code> Lists open files and the corresponding processes. There are 2 options werer interested in here:</p>
<ul>
<li><code>-t</code> to only get the process id</li>
<li><code>-i:{port||3000||8080}</code> to get the process that opened a specific port (you can "daisy chain" the -i option)</li>
</ul>
<p>So when we execute <code>lsof -t -i:3000 -i:8080</code> in our shell we should get 2 pids. Cool.</p>
<p>We can pipe (<code>|</code>) the output of this command to <code>xargs</code> and the <code>kill</code> command like so:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">lsof</span> -t -i:8080 -i:3000 <span class="token operator">|</span> <span class="token function">xargs</span> -n1 <span class="token function">sh</span> -c <span class="token string">'kill -9 $0'</span></code></pre>
<p>A quick breakdown:</p>
<ul>
<li><code>xargs -n1</code>: number of argument (1 in this case)</li>
<li><code>sh -c 'kill -9 $0'</code> : instruct sh to run the following command and replace $0 with value from xargs</li>
</ul>
<p>this also works</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">lsof</span> -t -i:8080 -i:3000 <span class="token operator">|</span> <span class="token function">xargs</span> -i % <span class="token function">sh</span> -c <span class="token string">'kill -9 %'</span></code></pre>
Trigger a Netlify build from an Iphone
2021-11-05T00:00:00Z
https://smcllw.me/til/netlify-shortcut/
<h2>Problem</h2>
<p>I set up <a href="https://zapier.com/">Zapier job</a> to listen for changes on a <a href="https://www.notion.so/">Notion</a> database in order to trigger a rebuild of my blog on <a href="https://www.netlify.com/">Netlify</a>.</p>
<p>This didn't quite work. Well it worked once, and then kind of stopped. This in without a shadow of a doubt something I misconfigured. I didn't want to spend too much time trying to sort this out.</p>
<p>And then I remembered that ios now comes with a <a href="https://support.apple.com/guide/shortcuts/welcome/ios">Shortcuts</a> app. This seemed like the perfect oportunity to have a quick play with something new and shiny (to me at least)</p>
<h2>Solution</h2>
<h3>Generate an endpoint in Netlify</h3>
<p>In the <code>Build & Deploy</code> page in Netlify's settings page there's a section of the page for generating <code>Build hooks</code>.</p>
<p>You can create a hook for a specific branch of you site (you could trigger builds exclusively for a development branch for example), and give that hook a name. Once you click on <code>Save</code>, you should get a new entry that looks like <code>https://api.netlify.com/build_hooks/someRandomGibberish</code> .</p>
<p>There's a little downwards facing chevron that you can click that will show you an example <a href="https://curl.se/">cURL</a> request to trigger the build. At this point you can copy the url for the hook that was created.</p>
<h3>Creating a Shortcut</h3>
<p>The Shortcut is composed of 2 parts but only does 1 task: send a POST request to the Netlify hook url.</p>
<p>To start, you need to create a new shortcut. You can give it a name, and you can add actions.</p>
<p>The first action you want to add is called <code>URL</code>. There's a search bar to find the actions you want. in this action you add the url of the Netlify hook.</p>
<p>The second action to add is called <code>Get contents of URL</code>. By default the <code>GET</code> method is selected and you can change that to a <code>POST</code> method. you you want or need you can also add headers and a body (the body can be used to provide a message to the build I think).</p>
<p>And that's it, you can add this action to your home screen and trigger a build from you phone!</p>
<p><a href="https://docs.netlify.com/configure-builds/build-hooks/">Netlify hooks doumentation</a></p>
Base 64 encoded fonts
2021-09-14T00:00:00Z
https://smcllw.me/til/base64-fonts/
<h2>The Problem</h2>
<p>I struggle referencing fonts in a CSS file. I struggle with getting fonts setup in general.</p>
<p>Sometimes your referencing the path to where your font is locally, but that doesn't quite match where it would be on your server (for example if your CSS file is being used on multiple projects hosted in different places, or just on different domains).</p>
<p>I'm generally quite lazy about this stuff and just reach for google fonts and do what they tell me to do. This isn't always an option, you'll also dependent on a external ressource that might not be available somewhere (google stuff isnt easily available in China for example).</p>
<p>This is the problem I faced at work. Multiple projects referencing the same CSS files and the fonts got lost somewhere for 2 out of 3 projects.</p>
<h2>The Solution</h2>
<p>It turns out you can change your font file into a Data URLs by base 64 encoding it.</p>
<p>That's a lot of words. Let's try to break it down a bit.</p>
<p>Here's what <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs?ref=morsewall.com">MDN</a> has to say:</p>
<blockquote>
<p>Data URLs, URLs prefixed with the data: scheme, allow content creators to embed small files inline in documents.</p>
</blockquote>
<p>So that means we're actually embedding the font in the CSS file. We can do that by encoding the file. we can do this by using the <code>base64</code> command line tool (on unix systems). Browsers can encode and decode base 64, that's why we're using this format.</p>
<pre class="language-bash"><code class="language-bash">base64 -w <span class="token number">0</span> font.woff2 <span class="token operator">></span> font.base64 </code></pre>
<p>The <code>-w 0</code> argument is to remove whitespace and linebreaks.</p>
<p>In the <code>font.base64</code> file there'll be a massive string of gibberish. We can copy/paste that gibberish into our css file where we define our font:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@font-face</span></span> <span class="token punctuation">{</span><br /> <span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token string">'My Cool Font'</span><span class="token punctuation">;</span><br /> <span class="token property">src</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span>data:application/font-woff2;base64,<this is where the gibberish lives><span class="token punctuation">)</span></span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">'woff2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">font-weight</span><span class="token punctuation">:</span> 300<span class="token punctuation">;</span><br /> <span class="token property">font-display</span><span class="token punctuation">:</span> swap<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>And that's it!</p>
<h2>Further Reading</h2>
<p><a href="https://www.zachleat.com/web/comprehensive-webfonts/#critical-foft-with-data-uri">Zach's deep dive and demos</a></p>
<p><a href="https://css-tricks.com/data-uris/">CSS tricks article</a></p>
<p><a href="https://css-tricks.com/the-best-font-loading-strategies-and-how-to-execute-them/">Zell Liew and his thoughts</a></p>
Orca on Arch
2021-05-05T00:00:00Z
https://smcllw.me/til/screen-reader-arch/
<p><a href="https://help.gnome.org/users/orca/stable/">Orca</a> seems to be the go-to screen reader on linux systems.</p>
<p>On Ubuntu, the setup is straight forward</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> orca</code></pre>
<p>similar thing for Arch</p>
<pre class="language-bash"><code class="language-bash">yay orca<br /><span class="token comment">#or</span><br /><span class="token function">sudo</span> pacman -S orca</code></pre>
<p>It gets really tricky when you have a bizarre mix of window manager or GUI or sound system. <a href="https://mail.gnome.org/archives/orca-list/2014-March/msg00033.html">this message from a mail archive set me on the right track to solving my issue</a></p>
<p>This can be configured through <code>spd-conf</code> in the terminal.</p>
<p>I had to change my output from <code>pulse-audio</code> to <code>alsa</code> (I don't know why, but it worked).</p>
<p>The screen reader can be started by pressing <code>Super</code> + <code>Alt</code> + <code>S</code></p>
<p>It can also be accessed through Settings.</p>
remove IR camera from lenovo on Ubuntu 20
2020-09-22T00:00:00Z
https://smcllw.me/til/disable-IR-Camera-ubuntu/
<h2>Problem</h2>
<p>my lenovo Thinkpad T480 has a built in IR camera that does some basic movement
tracking and detection with some windows program, but on ubuntu it's mostly
useless. Worse than that! it's set as the default camera which isn't helpful as
it only renders a bright green output.</p>
<h2>Solution</h2>
<p>I only found one solution
<a href="https://askubuntu.com/questions/1119743/how-do-i-change-the-default-webcam/1119832#1119832">here</a>.
The idea is to create a config file specifically for the device to "detach" it
and this config to the <code>usb_modeswitch</code> udev rules</p>
<h2>Step by Step</h2>
<ul>
<li>find vendor and product id of camera<pre class="language-bash"><code class="language-bash">birb@box: <span class="token function">dmesg</span> <span class="token operator">|</span> <span class="token function">grep</span> <span class="token string">'Camera'</span><br /><span class="token punctuation">[</span> <span class="token number">2.093051</span><span class="token punctuation">]</span> usb <span class="token number">1</span>-5: Product: Integrated IR Camera<br /><span class="token punctuation">[</span> <span class="token number">3.104388</span><span class="token punctuation">]</span> usb <span class="token number">1</span>-8: Product: Integrated Camera<br /><span class="token punctuation">[</span> <span class="token number">4.617796</span><span class="token punctuation">]</span> uvcvideo: Found UVC <span class="token number">1.10</span> device Integrated IR Camera <span class="token punctuation">(</span><span class="token number">5986</span>:1141<span class="token punctuation">)</span><br /><span class="token punctuation">[</span> <span class="token number">4.621236</span><span class="token punctuation">]</span> uvcvideo <span class="token number">1</span>-5:1.0: Entity <span class="token builtin class-name">type</span> <span class="token keyword">for</span> entity Camera <span class="token number">1</span> was not initialized<span class="token operator">!</span><br /><span class="token punctuation">[</span> <span class="token number">4.621300</span><span class="token punctuation">]</span> input: Integrated IR Camera: Integrate as /devices/pci0000:00/0000:00:14.0/usb1/1-5/1-5:1.0/input/input11<br /><span class="token punctuation">[</span> <span class="token number">4.622344</span><span class="token punctuation">]</span> uvcvideo: Found UVC <span class="token number">1.00</span> device Integrated Camera <span class="token punctuation">(</span><span class="token number">5986</span>:2113<span class="token punctuation">)</span><br /><span class="token punctuation">[</span> <span class="token number">4.656120</span><span class="token punctuation">]</span> uvcvideo <span class="token number">1</span>-8:1.0: Entity <span class="token builtin class-name">type</span> <span class="token keyword">for</span> entity Camera <span class="token number">1</span> was not initialized<span class="token operator">!</span><br /><span class="token punctuation">[</span> <span class="token number">4.656206</span><span class="token punctuation">]</span> input: Integrated Camera: Integrated C as /devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/input/input12</code></pre>
</li>
<li>create config file for this device that will detach driver from device<pre class="language-bash"><code class="language-bash">birb@box: <span class="token builtin class-name">echo</span> <span class="token string">"echo DetachStorageOnly=1 > /etc/usb_modeswitch.d/5986:1141"</span> <span class="token operator">|</span> <span class="token function">sudo</span> <span class="token function">zsh</span></code></pre>
</li>
<li>open <code>/lib/udev/rules.d/40-usb_modeswitch.rules</code> and add these lines before
the end to apply config on startup<pre class="language-bash"><code class="language-bash"><span class="token comment"># IR Camera</span><br />ATTR<span class="token punctuation">{</span>idVendor<span class="token punctuation">}</span><span class="token operator">==</span><span class="token string">"5986"</span>, ATTR<span class="token punctuation">{</span>idProduct<span class="token punctuation">}</span><span class="token operator">==</span><span class="token string">"1141"</span>, <span class="token assign-left variable">RUN</span><span class="token operator">+=</span><span class="token string">"usb_modeswitch '%k'"</span></code></pre>
</li>
</ul>
<h2>Important</h2>
<p>make sure that you add <code>==</code> not <code>=</code>, an error in the rules will break the
whole system and require looking for a usb key with and ubuntu image, booting
from usb and looking everywhere to find where to fix your mistake (... ask me
how I found that out)</p>
install Tar packages on Arch
2020-05-22T00:00:00Z
https://smcllw.me/til/tar-install-arch/
<p>you can install local tar files on Arch with <code>pacman</code>:</p>
<pre class="language-bash"><code class="language-bash">pacman -U myPackage.tar.gz</code></pre>
Git isn't Github
2020-05-22T00:00:00Z
https://smcllw.me/til/git-isnt-github/
<p>A lot of developpers use Git everyday, a high percentage of them probably use Github.</p>
<p>I use Github every work day. I use it to the extent that I use the words <code>Git</code> and <code>Github</code> interchangeably, but this is technically wrong. Github is a hosting service for your Git projects, like Gitlab. these services offer a bunch of bells and whistles, but you can also self host your Git projects. You probably already do if you have a cloned a Git repo before. But you can also host your repos on a remote server that you or someone else owns.</p>
<p>This might seem obvious, but somehow it never really occured to me that I could pay for a server or data storage somewhere (like AWS, GCP, Azure to name a few) and host my Git repos on there.</p>
<p>I realized this by cloning a repo, hosted on a virtual machine in GCP, at work that hadn't been commited to Github.</p>
<p>ANYWAY, the procedure is similar to the one used for cloning a repo from Github.</p>
<p>We need to have SSH access to the remote server. For that we need to generate a ssh key like so:</p>
<pre class="language-bash"><code class="language-bash">$ <span class="token builtin class-name">cd</span> ~/.ssh <span class="token operator">&&</span> ssh-keygen -t rsa -b <span class="token number">4096</span> -C <span class="token string">"email@example.com"</span></code></pre>
<p>lets break that down a bit:</p>
<ul>
<li><code>ssh</code> is a short-hand for Secure SHell protocol, <code>ssh</code> is a network protocol to securely communicate between computer <sup><a href="https://smcllw.me/til/git-isnt-github/#foot-1">1</a></sup>,</li>
<li><code>ssh-keygen</code> is a CLI tool used to generate key pairs for <code>ssh</code>, this tool can take several options (or flags?)</li>
<li>the <code> -t</code> in our command is to specify the type of algorithm used to encrypt our key, in our command we're using the <code>rsa</code> algorithm,</li>
<li>the <code> -b</code> option specifies the number of bits in the key to create, in our command we're using <code>4096</code> bits,</li>
<li>finally, the <code> -C</code> command is a comment, this will add our <code>email@example.com</code> to our public key (I believe that this is used on Github for authentication reasons, but i could be wrong.)</li>
</ul>
<p>Great, we have a <code>ssh</code> key pair, but we need to add the generated public key in the <code>authorized_keys</code> folder on the remote server.</p>
<p>There are a couple of ways to do this. For each method you need to know the <code>ip</code> address of the server, and the <code>user</code>. You can define the user when setting up your server or virtual machine. Figuring out the <code>ip</code> might be a bit trickier if you don't have a static <code>ip</code> (in GCP you can request a static <code>ip</code>, or use their CLI that gives you access to all you servers).</p>
<pre class="language-bash"><code class="language-bash">ssh-copy-id -i ~/.ssh/my-new-sshkey user@host</code></pre>
<p>The <code>ssh-copy-id</code> does what it says. It copies your ssh id to your remote host.</p>
<p>If your interacting with servers, you'll be ssh'ing into them regularly like so:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">ssh</span> user@host -i ~/.ssh/my-new-sshkey</code></pre>
<p>This quickly gets old, and for several ssh type commandes you'll need to supply this info. We can add this information into our <code>~/.ssh/config</code>:</p>
<pre class="language-bash"><code class="language-bash">ForwardAgent <span class="token function">yes</span><br /><br />Host my-remote-server<br /> Hostname <span class="token number">192.168</span>.1.20<br /> User user<br /> IdentityFile ~/.ssh/my-new-sshkey.key</code></pre>
<p>Now you can <code>ssh</code> into your server using this config</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">ssh</span> my-remote-server</code></pre>
<p>same for <code>ssh-copy-id</code></p>
<pre class="language-bash"><code class="language-bash">ssh-copy-id my-remote-server</code></pre>
<p>Now after having spent quite some time setting up ssh access to our server, we can finally clone/push the repo!</p>
<pre class="language-bash"><code class="language-bash"><span class="token comment"># normal cloning</span><br /><span class="token function">git</span> clone user@host:my-repo.git<br /><span class="token comment">#cloning, but with ssh config set</span><br /><span class="token function">git</span> clone my-remote-server:my-repo.git<br /><br /><span class="token comment">#pushing your local repo to your remote server</span><br /><span class="token function">git</span> remote <span class="token function">add</span> origin user@host:my-repo.git <br /><span class="token comment">#with ssh config</span><br /><span class="token function">git</span> remote <span class="token function">add</span> origin my-remote-server:my-repo.git</code></pre>
<p><a name="foot-1"></a>checkout more about ssh at <a href="https://www.ssh.com/">https://www.ssh.com/</a></p>