<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Swagg::Blogg</title>
    <link>https://blog.swagg.net/</link>
    <description>or: How I Learned to Stop Worrying and Love the WWW&lt;br&gt;&lt;a href=&#34;https://www.swagg.net&#34;&gt;Home&lt;/a&gt;&amp;nbsp;&lt;a href=&#34;https://blog.swagg.net/feed/&#34;&gt;RSS&lt;/a&gt;</description>
    <pubDate>Fri, 03 Apr 2026 21:33:01 +0000</pubDate>
    <image>
      <url>https://i.snap.as/gopN8vBC.png</url>
      <title>Swagg::Blogg</title>
      <link>https://blog.swagg.net/</link>
    </image>
    <item>
      <title>Exploring DBIish</title>
      <link>https://blog.swagg.net/exploring-dbiish?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Previously I wrote that I need to plumb my web app to a DB and was told that DBIish was the tool for the job. I was glad to see it has a migrations module to go along with it. I&#39;ll go ahead and add my first migration which will create the table for a pastebin. These simply consist of SQL statements in a file I creatively named &#39;migrations&#39;:&#xA;&#xA;-- 1 up&#xA;CREATE TABLE IF NOT EXISTS pastes (&#xA;         pasteid SERIAL PRIMARY KEY,&#xA;       pastebody TEXT&#xA;       );&#xA;&#xA;-- 1 down&#xA;DROP TABLE pastes;&#xA;&#xA;Now I&#39;ll need to connect to my DB. I honestly didn&#39;t have time to figure out how I&#39;ll pass this object to my model module(s) so for now I&#39;m using a dynamic variable and rather than reading the credentials from a file I&#39;ll just use prompt() for now:&#xA;&#xA;my $dbh = DBIish.connect:&#xA;    &#39;Pg&#39;,&#xA;    :hostdevbussy.swagg.net,&#xA;    :databasepastesbin,&#xA;    :userpastesbin,&#xA;    password =  prompt &#39;enter DB password: &#39;;&#xA;&#xA;my $m = DB::Migration::Simple.new:&#xA;    :$dbh,&#xA;    :migration-filemigrations;&#xA;&#xA;$m.migrate: :version1;&#xA;&#xA;Now if we run the web app we get prompted for the DB password and then we can see the migration be applied:&#xA;&#xA;$ ./bin/pastes-bin&#xA;enter DB password: hogrider69&#xA;line: -- 1 up&#xA;version: 1, direction: up&#xA;line: CREATE TABLE IF NOT EXISTS pastes (&#xA;line:          pasteid SERIAL PRIMARY KEY,&#xA;line:        pastebody TEXT&#xA;line:        );&#xA;line: -- 1 down&#xA;version: 1, direction: down&#xA;line: DROP TABLE pastes;&#xA;initializing db-migrations-simple-meta&#xA;set initial version to 0&#xA;{1 =  {down =  DROP TABLE pastes;&#xA;, up =  CREATE TABLE IF NOT EXISTS pastes (&#xA;         pasteid SERIAL PRIMARY KEY,&#xA;       pastebody TEXT&#xA;       );&#xA;}}&#xA;migrating from version &#39;0&#39; to version &#39;1&#39;&#xA;True migrating &#39;up&#39; from version &#39;0&#39; to version &#39;1&#39;&#xA;doing &#39;up&#39; migrations for 1&#xA;executing CREATE TABLE IF NOT EXISTS pastes (&#xA;         pasteid SERIAL PRIMARY KEY,&#xA;       pastebody TEXT&#xA;       )&#xA;Humming-Bird listening on port http://localhost:3000&#xA;&#xA;We can check the DB now and see our new table applied along with the metadata for the migrations module itself:&#xA;&#xA;$ psql -h devbussy.swagg.net -U pastesbin&#xA;Password for user pastesbin: &#xA;psql (15.3 (Debian 15.3-0+deb12u1))&#xA;SSL connection (protocol: TLSv1.3, cipher: TLSAES256GCMSHA384, compression: off)&#xA;Type &#34;help&#34; for help.&#xA;&#xA;pastesbin=  \d&#xA;                     List of relations&#xA; Schema |           Name            |   Type   |   Owner    &#xA;--------+---------------------------+----------+------------&#xA; public | db-migrations-simple-meta | table    | pastesbin&#xA; public | pastes                    | table    | pastesbin&#xA; public | pastespasteidseq       | sequence | pastesbin&#xA;(3 rows)&#xA;&#xA;pastesbin=  \d pastes;&#xA;                                 Table &#34;public.pastes&#34;&#xA;   Column   |  Type   | Collation | Nullable |                 Default                  &#xA;------------+---------+-----------+----------+------------------------------------------&#xA; pasteid   | integer |           | not null | nextval(&#39;pastespasteidseq&#39;::regclass)&#xA; pastebody | text    |           |          | &#xA;Indexes:&#xA;    &#34;pastespkey&#34; PRIMARY KEY, btree (pasteid)&#xA;&#xA;Now I wanted to have separate model and controller modules but I&#39;m still working on how to pass $dbh around sanely so for now I&#39;m going to just add this to test:&#xA;&#xA;use Pastes-Bin::Model::Paste;&#xA;&#xA;No routes yet just prompt to &#39;fake it&#39;&#xA;my $new-paste = prompt &#39;enter a new paste: &#39;;&#xA;Pastes-Bin::Model::Paste.create: $new-paste;&#xA;&#xA;Here&#39;s the model code:&#xA;&#xA;unit class Pastes-Bin::Model::Paste;&#xA;&#xA;submethod create(Str $new-paste) {&#xA;    $dbh.execute(q:to/ENDSQL/, $new-paste)&#xA;        INSERT INTO pastes (pastebody)&#xA;        VALUES (?);&#xA;       ENDSQL&#xA;}&#xA;&#xA;And now we run it again:&#xA;&#xA;$ ./bin/pastes-bin &#xA;enter DB password: hogrider69&#xA;line: -- 1 up&#xA;version: 1, direction: up&#xA;line: CREATE TABLE IF NOT EXISTS pastes (&#xA;line:          pasteid SERIAL PRIMARY KEY,&#xA;line:        pastebody TEXT&#xA;line:        );&#xA;line: -- 1 down&#xA;version: 1, direction: down&#xA;line: DROP TABLE pastes;&#xA;current-version: allrows: [[1]]&#xA;{1 =  {down =  DROP TABLE pastes;&#xA;, up =  CREATE TABLE IF NOT EXISTS pastes (&#xA;         pasteid SERIAL PRIMARY KEY,&#xA;       pastebody TEXT&#xA;       );&#xA;}}&#xA;migrating from version &#39;1&#39; to version &#39;1&#39;&#xA;DB already at version 1&#xA;enter a new paste: testing 123...&#xA;Humming-Bird listening on port http://localhost:3000&#xA;^C&#xA;&#xA;And now in the DB:&#xA;&#xA;pastesbin=  SELECT  FROM pastes;&#xA; pasteid | pastebody &#xA;----------+------------&#xA;(0 rows)&#xA;&#xA;pastesbin=  SELECT  FROM pastes;&#xA; pasteid |   paste_body   &#xA;----------+----------------&#xA;        1 | testing 123...&#xA;(1 row)&#xA;&#xA;It&#39;s nice to see I can talk to a DB with ease with DBIish and it handles migrations. I need to next dig into how I can organize my code in Raku where $dbh can be &#39;passed around&#39;. I tried creating a separate controller module that calls the model class but in that configuration it did not see the $*dbh dynamic variable... If/when I figure that out then that&#39;ll be the next blog entry on my journey into Raku.&#xA;&#xA;webDev&#xA;noScript&#xA;RakuLang&#xA;&#xA;Homepage&#xD;&#xA;Code&#xD;&#xA;Mail&#xD;&#xA;Social&#xD;&#xA;Chat&#xD;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>Previously I wrote that I need to plumb my web app to a DB and was told that <a href="https://raku.land/zef:raku-community-modules/DBIish">DBIish</a> was the tool for the job. I was glad to see it has a <a href="https://raku.land/zef:andinus/DB::Migration::Simple">migrations module</a> to go along with it. I&#39;ll go ahead and add my first migration which will create the table for a pastebin. These simply consist of SQL statements in a file I creatively named &#39;migrations&#39;:</p>

<pre><code>-- 1 up
CREATE TABLE IF NOT EXISTS pastes (
         paste_id SERIAL PRIMARY KEY,
       paste_body TEXT
       );

-- 1 down
DROP TABLE pastes;
</code></pre>

<p>Now I&#39;ll need to connect to my DB. I honestly didn&#39;t have time to figure out how I&#39;ll pass this object to my model module(s) so for now I&#39;m using a <a href="https://docs.raku.org/language/variables#The_*_twigil">dynamic variable</a> and rather than reading the credentials from a file I&#39;ll just use <code>prompt()</code> for now:</p>

<pre><code>my $*dbh = DBIish.connect:
    &#39;Pg&#39;,
    :host&lt;devbussy.swagg.net&gt;,
    :database&lt;pastes_bin&gt;,
    :user&lt;pastes_bin&gt;,
    password =&gt; prompt &#39;enter DB password: &#39;;

my $m = DB::Migration::Simple.new:
    :$*dbh,
    :migration-file&lt;migrations&gt;;

$m.migrate: :version&lt;1&gt;;
</code></pre>

<p>Now if we run the web app we get prompted for the DB password and then we can see the migration be applied:</p>

<pre><code>$ ./bin/pastes-bin
enter DB password: hogrider69
line: -- 1 up
version: 1, direction: up
line: CREATE TABLE IF NOT EXISTS pastes (
line:          paste_id SERIAL PRIMARY KEY,
line:        paste_body TEXT
line:        );
line: -- 1 down
version: 1, direction: down
line: DROP TABLE pastes;
initializing db-migrations-simple-meta
set initial version to 0
{1 =&gt; {down =&gt; DROP TABLE pastes;
, up =&gt; CREATE TABLE IF NOT EXISTS pastes (
         paste_id SERIAL PRIMARY KEY,
       paste_body TEXT
       );
}}
migrating from version &#39;0&#39; to version &#39;1&#39;
True migrating &#39;up&#39; from version &#39;0&#39; to version &#39;1&#39;
doing &#39;up&#39; migrations for 1
executing CREATE TABLE IF NOT EXISTS pastes (
         paste_id SERIAL PRIMARY KEY,
       paste_body TEXT
       )
Humming-Bird listening on port http://localhost:3000
</code></pre>

<p>We can check the DB now and see our new table applied along with the metadata for the migrations module itself:</p>

<pre><code>$ psql -h devbussy.swagg.net -U pastes_bin
Password for user pastes_bin: 
psql (15.3 (Debian 15.3-0+deb12u1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)
Type &#34;help&#34; for help.

pastes_bin=&gt; \d
                     List of relations
 Schema |           Name            |   Type   |   Owner    
--------+---------------------------+----------+------------
 public | db-migrations-simple-meta | table    | pastes_bin
 public | pastes                    | table    | pastes_bin
 public | pastes_paste_id_seq       | sequence | pastes_bin
(3 rows)

pastes_bin=&gt; \d pastes;
                                 Table &#34;public.pastes&#34;
   Column   |  Type   | Collation | Nullable |                 Default                  
------------+---------+-----------+----------+------------------------------------------
 paste_id   | integer |           | not null | nextval(&#39;pastes_paste_id_seq&#39;::regclass)
 paste_body | text    |           |          | 
Indexes:
    &#34;pastes_pkey&#34; PRIMARY KEY, btree (paste_id)
</code></pre>

<p>Now I wanted to have separate model and controller modules but I&#39;m still working on how to pass <code>$dbh</code> around sanely so for now I&#39;m going to just add this to test:</p>

<pre><code>use Pastes-Bin::Model::Paste;

# No routes yet just prompt to &#39;fake it&#39;
my $new-paste = prompt &#39;enter a new paste: &#39;;
Pastes-Bin::Model::Paste.create: $new-paste;
</code></pre>

<p>Here&#39;s the model code:</p>

<pre><code>unit class Pastes-Bin::Model::Paste;

submethod create(Str $new-paste) {
    $*dbh.execute(q:to/END_SQL/, $new-paste)
        INSERT INTO pastes (paste_body)
        VALUES (?);
       END_SQL
}
</code></pre>

<p>And now we run it again:</p>

<pre><code>$ ./bin/pastes-bin 
enter DB password: hogrider69
line: -- 1 up
version: 1, direction: up
line: CREATE TABLE IF NOT EXISTS pastes (
line:          paste_id SERIAL PRIMARY KEY,
line:        paste_body TEXT
line:        );
line: -- 1 down
version: 1, direction: down
line: DROP TABLE pastes;
current-version: allrows: [[1]]
{1 =&gt; {down =&gt; DROP TABLE pastes;
, up =&gt; CREATE TABLE IF NOT EXISTS pastes (
         paste_id SERIAL PRIMARY KEY,
       paste_body TEXT
       );
}}
migrating from version &#39;1&#39; to version &#39;1&#39;
DB already at version 1
enter a new paste: testing 123...
Humming-Bird listening on port http://localhost:3000
^C
</code></pre>

<p>And now in the DB:</p>

<pre><code>pastes_bin=&gt; SELECT * FROM pastes;
 paste_id | paste_body 
----------+------------
(0 rows)

pastes_bin=&gt; SELECT * FROM pastes;
 paste_id |   paste_body   
----------+----------------
        1 | testing 123...
(1 row)
</code></pre>

<p>It&#39;s nice to see I can talk to a DB with ease with DBIish and it handles migrations. I need to next dig into how I can organize my code in Raku where <code>$dbh</code> can be &#39;passed around&#39;. I tried creating a separate controller module that calls the model class but in that configuration it did not see the <code>$*dbh</code> dynamic variable... If/when I figure that out then that&#39;ll be the next blog entry on my journey into Raku.</p>

<p><a href="https://blog.swagg.net/tag:webDev" class="hashtag"><span>#</span><span class="p-category">webDev</span></a>
<a href="https://blog.swagg.net/tag:noScript" class="hashtag"><span>#</span><span class="p-category">noScript</span></a>
<a href="https://blog.swagg.net/tag:RakuLang" class="hashtag"><span>#</span><span class="p-category">RakuLang</span></a></p>
<ul><li><a href="https://www.swagg.net">Homepage</a></li>
<li><a href="https://codeberg.org/swaggboi">Code</a></li>
<li><a href="mailto:swaggboi@slackware.uk">Mail</a></li>
<li><a href="https://eattherich.club/@swaggboi">Social</a></li>
<li><a href="https://discord.gg/6MXVZKU">Chat</a></li></ul>
]]></content:encoded>
      <guid>https://blog.swagg.net/exploring-dbiish</guid>
      <pubDate>Sun, 03 Dec 2023 04:28:38 +0000</pubDate>
    </item>
    <item>
      <title>Getting Started with Raku</title>
      <link>https://blog.swagg.net/getting-started-with-raku?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Now that my little textboard project is in a decent state I&#39;ve begun thinking about what I wanted to try next. I knew it&#39;ll be some sort of webbed site and there&#39;d be a database but other than that it was up in the air. Mojo.js appealed to me for obvious reasons but honestly I found the Node/JavaScript conventions and ecosystem pretty intimidating. I&#39;ve never been any good at JS and the whole front-end thing is an enigma to me, as is probably obvious if you&#39;ve seen my prior art.&#xA;&#xA;Thankfully I discovered Humming-Bird which I like because it reminds me of Mojo or Sinatra. It&#39;s also a great excuse to use Raku, a language I also find intimidating but at least feels more &#39;fun&#39; and less &#39;enterprise&#39; than JS or even Perl/Ruby. If you already have a nice little CPAN/Perl set-up then getting Raku is easy enough:&#xA;&#xA;cpanm App::Rakubrew &amp;&amp; rakubrew download&#xA;&#xA;If not then Debian ships it:&#xA;&#xA;apt install rakudo&#xA;&#xA;Finally we can use zef to install Humming-Bird:&#xA;&#xA;zef install Humming-Bird&#xA;&#xA;Humming-Bird is quite minimal like Sinatra is so I want to install something for templates. Template::Mustache was recommended to me after I had trouble getting others to work:&#xA;&#xA;zef install Template::Mustache&#xA;&#xA;To get started let&#39;s import Humming-Bird::Core and our template module; I&#39;m going to keep my template in a directory called templates:&#xA;&#xA;!/usr/bin/env raku&#xA;&#xA;use v6.d;&#xA;use Humming-Bird::Core;&#xA;use Template::Mustache;&#xA;&#xA;my $template = Template::Mustache.new: :from./templates;&#xA;&#xA;I&#39;ll go ahead and throw in a basic template named index.mustache:&#xA;&#xA;!DOCTYPE html&#xA;html&#xA;head&#xA;  title{{title}}/title&#xA;/head&#xA;body&#xA;h1{{title}}/h1&#xA;pWe will we will... RAKU!!/p&#xA;/body&#xA;/html&#xA;&#xA;To render this template we only need to add the following to our script, I&#39;ve named mine basic-site.raku if you&#39;re playing along at home:&#xA;&#xA;get &#39;/&#39;, -  $request, $response {&#xA;    my Str %stash = title =  &#39;Hello, web!&#39;;&#xA;&#xA;    $response.html($template.render: &#39;index&#39;, %stash);&#xA;};&#xA;&#xA;listen 3000;&#xA;&#xA;That is literally all you need to take flight:&#xA;&#xA;$ curl -i http://localhost:3000&#xA;HTTP/1.1 200 OK&#xA;Content-Length: 144&#xA;Date: Fri, 10 Nov 2023 02:29:44 +0000&#xA;X-Server: Humming-Bird (Raku)&#xA;content-type: text/html; charset=utf8&#xA;&#xA;!DOCTYPE html&#xA;html&#xA;head&#xA;  titleHello, web!/title&#xA;/head&#xA;body&#xA;h1Hello, web!/h1&#xA;pWe will we will... RAKU!!/p&#xA;/body&#xA;/html&#xA;&#xA;This is pretty decent as-is but if there&#39;s anything I create more than silly websites, it&#39;s compiler errors and warnings. I&#39;d like them to be more verbose so I can also add this:&#xA;&#xA;Add the &#39;middleware&#39; and &#39;advice&#39; parts&#xA;use Humming-Bird::Middleware;&#xA;use Humming-Bird::Advice;&#xA;&#xA;All you need to use &#39;em out-of-the-box&#xA;middleware &amp;middleware-logger;&#xA;advice     &amp;advice-logger;&#xA;&#xA;Our entire script now looks like this:&#xA;&#xA;!/usr/bin/env raku&#xA;&#xA;use v6.d;&#xA;use Humming-Bird::Core;&#xA;use Humming-Bird::Middleware;&#xA;use Humming-Bird::Advice;&#xA;use Template::Mustache;&#xA;&#xA;middleware &amp;middleware-logger;&#xA;advice     &amp;advice-logger;&#xA;&#xA;my $template = Template::Mustache.new: :from./templates;&#xA;&#xA;get &#39;/&#39;, -  $request, $response {&#xA;    my Str %stash = title =  &#39;Hello, web!&#39;;&#xA;&#xA;    $response.html($template.render: &#39;index&#39;, %stash);&#xA;};&#xA;&#xA;listen 3000;&#xA;&#xA;You can take a better look at it in my git repo along with my other Raku experiments so far. I&#39;m glad Humming-Bird arrived on the scene as it&#39;s left me with no more excuses to not at least try Raku and so far I&#39;m really diggin it. I&#39;ve found the best sources of Raku halp to be Reddit and their Discord (which is bridged to IRC as well); you can get your hyperlinks on their website.&#xA;&#xA;webDev&#xA;noScript&#xA;RakuLang&#xA;&#xA;Homepage&#xD;&#xA;Code&#xD;&#xA;Mail&#xD;&#xA;Social&#xD;&#xA;Chat&#xD;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>Now that my little <a href="https://posttext.pl/about">textboard project</a> is in a decent state I&#39;ve begun thinking about what I wanted to try next. I knew it&#39;ll be some sort of webbed site and there&#39;d be a database but other than that it was up in the air. <a href="https://mojojs.org/">Mojo.js</a> appealed to me for obvious reasons but honestly I found the Node/JavaScript conventions and ecosystem pretty intimidating. I&#39;ve never been any good at JS and the whole front-end thing is an enigma to me, as is probably obvious if you&#39;ve seen my <a href="https://www.swagg.net/die">prior art</a>.</p>

<p>Thankfully I discovered <a href="https://github.com/rawleyfowler/Humming-Bird">Humming-Bird</a> which I like because it reminds me of <a href="https://mojolicious.org/">Mojo</a> or <a href="https://sinatrarb.com/">Sinatra</a>. It&#39;s also a great excuse to use <a href="https://raku.org/">Raku</a>, a language I also find intimidating but at least feels more &#39;fun&#39; and less &#39;enterprise&#39; than JS or even Perl/Ruby. If you already have a nice little CPAN/Perl set-up then getting Raku is easy enough:</p>

<pre><code>cpanm App::Rakubrew &amp;&amp; rakubrew download
</code></pre>

<p>If not then Debian ships it:</p>

<pre><code>apt install rakudo
</code></pre>

<p>Finally we can use <code>zef</code> to install Humming-Bird:</p>

<pre><code>zef install Humming-Bird
</code></pre>

<p>Humming-Bird is quite minimal like Sinatra is so I want to install something for templates. <a href="https://github.com/softmoth/raku-Template-Mustache">Template::Mustache</a> was recommended to me after I had trouble getting others to work:</p>

<pre><code>zef install Template::Mustache
</code></pre>

<p>To get started let&#39;s import <code>Humming-Bird::Core</code> and our template module; I&#39;m going to keep my template in a directory called <code>templates</code>:</p>

<pre><code>#!/usr/bin/env raku

use v6.d;
use Humming-Bird::Core;
use Template::Mustache;

my $template = Template::Mustache.new: :from&lt;./templates&gt;;
</code></pre>

<p>I&#39;ll go ahead and throw in a basic template named <code>index.mustache</code>:</p>

<pre><code>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;title&gt;{{title}}&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;{{title}}&lt;/h1&gt;
&lt;p&gt;We will we will... RAKU!!&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>To render this template we only need to add the following to our script, I&#39;ve named mine <code>basic-site.raku</code> if you&#39;re playing along at home:</p>

<pre><code>get &#39;/&#39;, -&gt; $request, $response {
    my Str %stash = title =&gt; &#39;Hello, web!&#39;;

    $response.html($template.render: &#39;index&#39;, %stash);
};

listen 3000;
</code></pre>

<p>That is literally all you need to take flight:</p>

<pre><code>$ curl -i http://localhost:3000
HTTP/1.1 200 OK
Content-Length: 144
Date: Fri, 10 Nov 2023 02:29:44 +0000
X-Server: Humming-Bird (Raku)
content-type: text/html; charset=utf8

&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;title&gt;Hello, web!&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Hello, web!&lt;/h1&gt;
&lt;p&gt;We will we will... RAKU!!&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>This is pretty decent as-is but if there&#39;s anything I create more than silly websites, it&#39;s compiler errors and warnings. I&#39;d like them to be more verbose so I can also add this:</p>

<pre><code># Add the &#39;middleware&#39; and &#39;advice&#39; parts
use Humming-Bird::Middleware;
use Humming-Bird::Advice;

# All you need to use &#39;em out-of-the-box
middleware &amp;middleware-logger;
advice     &amp;advice-logger;
</code></pre>

<p>Our entire script now looks like this:</p>

<pre><code>#!/usr/bin/env raku

use v6.d;
use Humming-Bird::Core;
use Humming-Bird::Middleware;
use Humming-Bird::Advice;
use Template::Mustache;

middleware &amp;middleware-logger;
advice     &amp;advice-logger;

my $template = Template::Mustache.new: :from&lt;./templates&gt;;

get &#39;/&#39;, -&gt; $request, $response {
    my Str %stash = title =&gt; &#39;Hello, web!&#39;;

    $response.html($template.render: &#39;index&#39;, %stash);
};

listen 3000;
</code></pre>

<p>You can take a better look at it in my <a href="https://git.minimally.online/swaggboi/Mind-Fuck/src/branch/main/basic-site">git repo</a> along with my other Raku experiments so far. I&#39;m glad Humming-Bird <a href="https://www.reddit.com/r/rakulang/comments/17qbjj2/introducing_hummingbird_version_3/">arrived on the scene</a> as it&#39;s left me with no more excuses to not at least try Raku and so far I&#39;m really diggin it. I&#39;ve found the best sources of Raku halp to be Reddit and their Discord (which is bridged to IRC as well); you can get your hyperlinks <a href="https://raku.org/community/">on their website</a>.</p>

<p><a href="https://blog.swagg.net/tag:webDev" class="hashtag"><span>#</span><span class="p-category">webDev</span></a>
<a href="https://blog.swagg.net/tag:noScript" class="hashtag"><span>#</span><span class="p-category">noScript</span></a>
<a href="https://blog.swagg.net/tag:RakuLang" class="hashtag"><span>#</span><span class="p-category">RakuLang</span></a></p>
<ul><li><a href="https://www.swagg.net">Homepage</a></li>
<li><a href="https://codeberg.org/swaggboi">Code</a></li>
<li><a href="mailto:swaggboi@slackware.uk">Mail</a></li>
<li><a href="https://eattherich.club/@swaggboi">Social</a></li>
<li><a href="https://discord.gg/6MXVZKU">Chat</a></li></ul>
]]></content:encoded>
      <guid>https://blog.swagg.net/getting-started-with-raku</guid>
      <pubDate>Fri, 10 Nov 2023 02:53:11 +0000</pubDate>
    </item>
    <item>
      <title>Stupid Meta...</title>
      <link>https://blog.swagg.net/stupid-meta?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[So there&#39;s these meta elements by Meta. Well not the meta element itself... But Open Graph. I wanted my links to look like this when I spam them in Discord:&#xA;&#xA;A link preview of an article from web3isgoinggreat.com&#xA;&#xA;Remember that scene from American Psycho where Christian Bale is sweating over a business card?&#xA;&#xA;So anyways, I look at the HTML and see these meta tags. That&#39;s gotta be it, I mean I see these then some tags that say something about Twitter and I don&#39;t want Twitter since I&#39;m really actually not a terrible person once you get to know me so I guess I&#39;ll implement these tags. The image seems easy enough, throw it in a Mojo template but what&#39;s annoying is I have my head entirely in the layout rather than the template itself. I first considered a partial template like I&#39;ve seen in Rails before but Mojo couldn&#39;t find the partial. I think this was due to me just using a single layout for all my templates associated with different controllers. My site is pretty simple so honestly I didn&#39;t want to copy the same layout for every controller.&#xA;&#xA;So I was left to hit the docs looking for some sort of feature or something that could help me out here. Finally I settled on the contentfor() helper. In my layout I put the following:&#xA;&#xA;head&#xA;  !-- Some stuff here... --&#xA;  %= content &#39;opengraph&#39; =%&#xA;  meta property=&#34;og:url&#34; content=&#34;&lt;%= urlfor-toabs %  &#34;  meta property=&#34;og:sitename&#34; content=&#34;Post::Text&#34;&#xA;  &lt;meta property=&#34;og:image&#34;&#xA;        content=&#34;%= urlfor(&#39;/images/logo.png&#39;)-toabs %  &#34;  meta property=&#34;og:image:type&#34; content=&#34;image/png&#34;&#xA;  meta property=&#34;og:image:width&#34; content=&#34;1200&#34;&#xA;  meta property=&#34;og:image:height&#34; content=&#34;1200&#34;&#xA;  &lt;meta property=&#34;og:image:alt&#34;&#xA;        content=&#34;Post::Text logo; a small nerdy anime girl giving a V sign&#34;  /head&#xA;&#xA;Now in my templates I simply do:&#xA;&#xA;% layout &#39;default&#39;;&#xA;% title &#39;New Thread&#39;;&#xA;% contentfor opengraph =  begin&#xA;  meta property=&#34;og:type&#34; content=&#34;website&#34;&#xA;  meta property=&#34;og:title&#34; content=&#34;&lt;%= title %&#34;  meta property=&#34;og:description&#34; content=&#34;Start a new thread.&#34;&#xA;% end&#xA;h2 class=&#34;page-title&#34;%= title %/h2&#xA;!-- Begin the rest of the page here... --&#xA;&#xA;Well that oughta do it! I mean I even made my image 1200 pixels wide, this oughta look breathtaking...&#xA;&#xA;A URL preivew of posttext.pl with a smaller image preview&#xA;&#xA;That&#39;s... A URL preview but why is my image so smol? It should be 4K ultra-HD like Molly White&#39;s site! I mean yeah there&#39;s these Twitter tags but why would it use those? There&#39;s not even a &#39;site name&#39; and I clearly see them in both our previews so it must be using Open Graph. Unless... It&#39;s using both. But there&#39;s no way it&#39;d do that I mean are the Twitter tags even considered a standard? Hell, is Open Graph even a standard? Out of desperation I try...&#xA;&#xA;% contentfor twittercard =  begin&#xA;  meta name=&#34;twitter:title&#34; content=&#34;&lt;%= title %&#34;  meta name=&#34;twitter:description&#34; content=&#34;Start a new thread.&#34;&#xA;% end&#xA;&#xA;And back to the layout:&#xA;&#xA;%= content &#39;twittercard&#39; =%&#xA;meta name=&#34;twitter:card&#34; content=&#34;summarylargeimage&#34;&#xA;meta name=&#34;twitter:creator&#34; content=&#34;@swaggboi@eattherich.club&#34;&#xA;&lt;meta name=&#34;twitter:site&#34;&#xA;      content=&#34;@posttext@fedi.seriousbusiness.international&#34;  &lt;meta name=&#34;twitter:image&#34;&#xA;      content=&#34;%= urlfor(&#39;/images/logo.png&#39;)-to_abs %  &#34;  &lt;meta name=&#34;twitter:image:alt&#34;&#xA;      content=&#34;Post::Text logo; a small nerdy anime girl giving a V sign&#34;  Well, what&#39;s it gunna be Discord? Make my day...&#xA;&#xA;The same link to posttext.pl but now the image is large&#xA;&#xA;😳&#xA;&#xA;Alright then... It&#39;s got the site title from Open Graph... And the image from the stupid Twitter tags. I guess. Cool.&#xA;&#xA;Well anyways that&#39;s done and Post::Text is finally live so I&#39;m happy. On to the next harebrained project! 🫡&#xA;&#xA;P.S. I know it&#39;s been a while since I&#39;ve blogged, my last prediction is partially becoming true already 💀&#xA;&#xA;perl&#xA;mojolicious&#xA;textboard&#xA;&#xA;Homepage&#xD;&#xA;Code&#xD;&#xA;Mail&#xD;&#xA;Social&#xD;&#xA;Chat&#xD;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>So there&#39;s these <code>&lt;meta&gt;</code> elements by Meta. Well not the <code>&lt;meta&gt;</code> element itself... But <a href="https://ogp.me">Open Graph</a>. I wanted my links to look like this when I spam them in <a href="https://discord.gg/6MXVZKU">Discord</a>:</p>

<p><img src="https://i.snap.as/fv9tGhOS.png" alt="A link preview of an article from web3isgoinggreat.com"/></p>

<p>Remember that scene from American Psycho where Christian Bale is sweating over a business card?</p>

<p>So anyways, I look at the HTML and see <a href="https://paste.lgts.xyz/?0f06b976abeb2060#6yuDpinXzC7vHXQmf4HoLKyurd3HaJdNjuNQ6Q3zMw4K">these</a> <code>&lt;meta&gt;</code> tags. That&#39;s gotta be it, I mean I see these then some tags that say something about Twitter and I don&#39;t want Twitter since I&#39;m really actually not a <a href="https://www.theguardian.com/world/2023/jun/03/twitter-conservative-media-elon-musk-ron-desantis">terrible person</a> once you get to know me so I guess I&#39;ll implement these tags. The image seems easy enough, throw it in a <a href="https://docs.mojolicious.org/Mojolicious/Guides/Rendering#Embedded-Perl">Mojo template</a> but what&#39;s annoying is I have my <code>&lt;head&gt;</code> entirely in the <a href="https://docs.mojolicious.org/Mojolicious/Guides/Rendering#Layouts">layout</a> rather than the template itself. I first considered a partial template like I&#39;ve seen in Rails before but Mojo couldn&#39;t find the partial. I think this was due to me just using a single layout for all my templates associated with different controllers. My site is pretty simple so honestly I didn&#39;t want to copy the same layout for every controller.</p>

<p>So I was left to hit the docs looking for some sort of feature or something that could help me out here. Finally I settled on the <a href="https://docs.mojolicious.org/Mojolicious/Plugin/DefaultHelpers#content_for">content_for()</a> helper. In my layout I put the following:</p>

<pre><code>&lt;head&gt;
  &lt;!-- Some stuff here... --&gt;
  &lt;%= content &#39;open_graph&#39; =%&gt;
  &lt;meta property=&#34;og:url&#34; content=&#34;&lt;%= url_for-&gt;to_abs %&gt;&#34;&gt;
  &lt;meta property=&#34;og:site_name&#34; content=&#34;Post::Text&#34;&gt;
  &lt;meta property=&#34;og:image&#34;
        content=&#34;&lt;%= url_for(&#39;/images/logo.png&#39;)-&gt;to_abs %&gt;&#34;&gt;
  &lt;meta property=&#34;og:image:type&#34; content=&#34;image/png&#34;&gt;
  &lt;meta property=&#34;og:image:width&#34; content=&#34;1200&#34;&gt;
  &lt;meta property=&#34;og:image:height&#34; content=&#34;1200&#34;&gt;
  &lt;meta property=&#34;og:image:alt&#34;
        content=&#34;Post::Text logo; a small nerdy anime girl giving a V sign&#34;&gt;
&lt;/head&gt;
</code></pre>

<p>Now in my templates I simply do:</p>

<pre><code>% layout &#39;default&#39;;
% title &#39;New Thread&#39;;
% content_for open_graph =&gt; begin
  &lt;meta property=&#34;og:type&#34; content=&#34;website&#34;&gt;
  &lt;meta property=&#34;og:title&#34; content=&#34;&lt;%= title %&gt;&#34;&gt;
  &lt;meta property=&#34;og:description&#34; content=&#34;Start a new thread.&#34;&gt;
% end
&lt;h2 class=&#34;page-title&#34;&gt;&lt;%= title %&gt;&lt;/h2&gt;
&lt;!-- Begin the rest of the page here... --&gt;
</code></pre>

<p>Well that oughta do it! I mean I even made my image 1200 pixels wide, this oughta look <em>breathtaking</em>...</p>

<p><img src="https://i.snap.as/ttM8gh3E.png" alt="A URL preivew of posttext.pl with a smaller image preview"/></p>

<p>That&#39;s... A URL preview but why is my image so smol? It should be 4K ultra-HD like <a href="https://web3isgoinggreat.com">Molly White&#39;s</a> site! I mean yeah there&#39;s <a href="https://paste.lgts.xyz/?7ee65dddb1a83f6e#AVtRxh5Lvnvk3bZP6ntMSLiMW3h6TkAP5zkjzZhJ1ucG">these Twitter tags</a> but why would it use <a href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards">those</a>? There&#39;s not even a &#39;site name&#39; and I clearly see them in both our previews so it must be using Open Graph. Unless... It&#39;s using <em>both</em>. But there&#39;s no way it&#39;d do that I mean are the Twitter tags even considered a standard? Hell, is Open Graph even a standard? Out of desperation I try...</p>

<pre><code>% content_for twitter_card =&gt; begin
  &lt;meta name=&#34;twitter:title&#34; content=&#34;&lt;%= title %&gt;&#34;&gt;
  &lt;meta name=&#34;twitter:description&#34; content=&#34;Start a new thread.&#34;&gt;
% end
</code></pre>

<p>And back to the layout:</p>

<pre><code>&lt;%= content &#39;twitter_card&#39; =%&gt;
&lt;meta name=&#34;twitter:card&#34; content=&#34;summary_large_image&#34;&gt;
&lt;meta name=&#34;twitter:creator&#34; content=&#34;<a href="/@/swaggboi@eattherich.club" class="u-url mention">@<span>swaggboi@eattherich.club</span></a>&#34;&gt;
&lt;meta name=&#34;twitter:site&#34;
      content=&#34;<a href="/@/posttext@fedi.seriousbusiness.international" class="u-url mention">@<span>posttext@fedi.seriousbusiness.international</span></a>&#34;&gt;
&lt;meta name=&#34;twitter:image&#34;
      content=&#34;&lt;%= url_for(&#39;/images/logo.png&#39;)-&gt;to_abs %&gt;&#34;&gt;
&lt;meta name=&#34;twitter:image:alt&#34;
      content=&#34;Post::Text logo; a small nerdy anime girl giving a V sign&#34;&gt;
</code></pre>

<p>Well, what&#39;s it gunna be Discord? Make my day...</p>

<p><img src="https://i.snap.as/jGrP0PB5.png" alt="The same link to posttext.pl but now the image is large"/></p>

<h2>😳</h2>

<p>Alright then... It&#39;s got the site title from Open Graph... And the image from the stupid Twitter tags. I guess. Cool.</p>

<p>Well anyways that&#39;s done and <a href="https://posttext.pl">Post::Text</a> is finally live so I&#39;m happy. On to the next harebrained project! 🫡</p>

<p>P.S. I know it&#39;s been a while since I&#39;ve blogged, my last prediction is partially becoming true already 💀</p>

<p><a href="https://blog.swagg.net/tag:perl" class="hashtag"><span>#</span><span class="p-category">perl</span></a>
<a href="https://blog.swagg.net/tag:mojolicious" class="hashtag"><span>#</span><span class="p-category">mojolicious</span></a>
<a href="https://blog.swagg.net/tag:textboard" class="hashtag"><span>#</span><span class="p-category">textboard</span></a></p>
<ul><li><a href="https://www.swagg.net">Homepage</a></li>
<li><a href="https://codeberg.org/swaggboi">Code</a></li>
<li><a href="mailto:swaggboi@slackware.uk">Mail</a></li>
<li><a href="https://eattherich.club/@swaggboi">Social</a></li>
<li><a href="https://discord.gg/6MXVZKU">Chat</a></li></ul>
]]></content:encoded>
      <guid>https://blog.swagg.net/stupid-meta</guid>
      <pubDate>Thu, 08 Jun 2023 02:29:02 +0000</pubDate>
    </item>
    <item>
      <title>New Years Predictions</title>
      <link>https://blog.swagg.net/new-years-predictions?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Anyone got any New Years predictions?&#xA;&#xA;I predict that “human-generated” code will be deprecated as unsafe in favor of AI-generated code. Layoffs and record profits will follow.&#xA;&#xA;Homepage&#xD;&#xA;Code&#xD;&#xA;Mail&#xD;&#xA;Social&#xD;&#xA;Chat&#xD;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>Anyone got any New Years predictions?</p>

<p>I predict that “human-generated” code will be deprecated as unsafe in favor of AI-generated code. Layoffs and record profits will follow.</p>
<ul><li><a href="https://www.swagg.net">Homepage</a></li>
<li><a href="https://codeberg.org/swaggboi">Code</a></li>
<li><a href="mailto:swaggboi@slackware.uk">Mail</a></li>
<li><a href="https://eattherich.club/@swaggboi">Social</a></li>
<li><a href="https://discord.gg/6MXVZKU">Chat</a></li></ul>
]]></content:encoded>
      <guid>https://blog.swagg.net/new-years-predictions</guid>
      <pubDate>Sun, 25 Dec 2022 06:48:22 +0000</pubDate>
    </item>
    <item>
      <title>I Like to Migrate, Migrate</title>
      <link>https://blog.swagg.net/i-like-to-migrate-migrate?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[I actually normally wouldn&#39;t enjoy this but thankfully this Mojo::Pg wrapper makes it easy. I honestly don&#39;t have any experience with the original DBD::Pg as I&#39;m still very new to the database world so I hope this doesn&#39;t read too much like I&#39;m lost in the sauce. First I took a look at my current tables using the psql tool:&#xA;&#xA;posttext=  \d&#xA;                   List of relations&#xA; Schema |         Name          |   Type   |   Owner&#xA;--------+-----------------------+----------+-----------&#xA; public | mojomigrations       | table    | posttext&#xA; public | replies               | table    | posttext&#xA; public | repliesreplyidseq  | sequence | posttext&#xA; public | threads               | table    | posttext&#xA; public | threadsthreadidseq | sequence | posttext&#xA;(5 rows)&#xA;&#xA;posttext=  \d replies;&#xA;                                            Table &#34;public.replies&#34;&#xA;     Column     |           Type           | Collation | Nullable |                  Default&#xA;----------------+--------------------------+-----------+----------+-------------------------------------------&#xA; replyid       | integer                  |           | not null | nextval(&#39;repliesreplyidseq&#39;::regclass)&#xA; threadid      | integer                  |           |          |&#xA; replydate     | timestamp with time zone |           | not null | now()&#xA; replyauthor   | character varying(64)    |           |          |&#xA; replybody     | character varying(4096)  |           |          |&#xA; hiddenstatus  | boolean                  |           | not null |&#xA; flaggedstatus | boolean                  |           | not null |&#xA;Indexes:&#xA;    &#34;repliespkey&#34; PRIMARY KEY, btree (replyid)&#xA;Foreign-key constraints:&#xA;    &#34;repliesthreadidfkey&#34; FOREIGN KEY (threadid) REFERENCES threads(threadid)&#xA;&#xA;These long lines are ugly so here&#39;s a pastebin link. Essentially I need to s/reply/remark/g; including that sequence, index and foreign-key constraint:&#xA;&#xA;-- This file is migrations/5/up.sql btw&#xA;&#xA; ALTER TABLE replies&#xA;RENAME TO remarks;&#xA;&#xA; ALTER TABLE remarks&#xA;RENAME replyid&#xA;    TO remarkid;&#xA;&#xA; ALTER TABLE remarks&#xA;RENAME replydate&#xA;    TO remarkdate;&#xA;&#xA; ALTER TABLE remarks&#xA;RENAME replyauthor&#xA;   TO remarkauthor;&#xA;&#xA; ALTER TABLE remarks&#xA;RENAME replybody&#xA;    TO remarkbody;&#xA;&#xA; ALTER TABLE remarks&#xA;RENAME CONSTRAINT repliesthreadidfkey&#xA;    TO remarksthreadidfkey;&#xA;&#xA; ALTER INDEX repliespkey&#xA;RENAME TO remarkspkey;&#xA;&#xA; ALTER SEQUENCE repliesreplyidseq&#xA;RENAME TO remarksremarkidseq;&#xA;&#xA;I&#39;m also going to do the exact opposite for our rolling-back pleasure:&#xA;&#xA;-- This one is migrations/5/down.sql&#xA;&#xA; ALTER TABLE remarks&#xA;RENAME TO replies;&#xA;&#xA; ALTER TABLE replies&#xA;RENAME remarkid&#xA;    TO replyid;&#xA;&#xA; ALTER TABLE replies&#xA;RENAME remarkdate&#xA;    TO replydate;&#xA;&#xA; ALTER TABLE replies&#xA;RENAME remarkauthor&#xA;    TO replyauthor;&#xA;&#xA; ALTER TABLE replies&#xA;RENAME remarkbody&#xA;    TO replybody;&#xA;&#xA; ALTER TABLE replies&#xA;RENAME CONSTRAINT remarksthreadidfkey&#xA;    TO repliesthreadidfkey;&#xA;&#xA; ALTER INDEX remarkspkey&#xA;RENAME TO repliespkey;&#xA;&#xA; ALTER SEQUENCE remarksremarkidseq&#xA;RENAME TO repliesreplyidseq;&#xA;&#xA;Idk why but I always find this step feels weird to me because it feels like I&#39;m undoing the undo lol. But we will need it later so let&#39;s absolutely include it. Now I&#39;m gunna see if it works...&#xA;&#xA;daniel@netburst:~/git/PostText$ ./PostText.pl eval &#39;app-  pg-  migrations-  fromdir(&#34;migrations&#34;)-  migrate(5);&#39;&#xA;daniel@netburst:~/git/PostText$ echo $?&#xA;0&#xA;&#xA;No news is good news I guess. Let&#39;s whip out psql again and see how it looks:&#xA;&#xA;posttext=  \d&#xA;                   List of relations&#xA; Schema |         Name          |   Type   |   Owner&#xA;--------+-----------------------+----------+-----------&#xA; public | mojomigrations       | table    | posttext&#xA; public | remarks               | table    | posttext&#xA; public | remarksremarkidseq | sequence | posttext&#xA; public | threads               | table    | posttext&#xA; public | threadsthreadidseq | sequence | posttext&#xA;(5 rows)&#xA;&#xA;posttext=  \d remarks;&#xA;                                            Table &#34;public.remarks&#34;&#xA;     Column     |           Type           | Collation | Nullable |                  Default&#xA;----------------+--------------------------+-----------+----------+--------------------------------------------&#xA; remarkid      | integer                  |           | not null | nextval(&#39;remarksremarkidseq&#39;::regclass)&#xA; threadid      | integer                  |           |          |&#xA; remarkdate    | timestamp with time zone |           | not null | now()&#xA; remarkauthor  | character varying(64)    |           |          |&#xA; remarkbody    | character varying(4096)  |           |          |&#xA; hiddenstatus  | boolean                  |           | not null |&#xA; flaggedstatus | boolean                  |           | not null |&#xA;Indexes:&#xA;    &#34;remarkspkey&#34; PRIMARY KEY, btree (remarkid)&#xA;Foreign-key constraints:&#xA;    &#34;remarksthreadidfkey&#34; FOREIGN KEY (threadid) REFERENCES threads(threadid)&#xA;&#xA;Aaaaaand the pastebin. I think we&#39;re in good shape. I&#39;m going to migrate back for now as I still need to make some changes in the controller logic to use the new Remark model instead of my old Reply model.&#xA;&#xA;daniel@netburst:~/git/PostText$ ./PostText.pl eval &#39;app-  pg-  migrations-  fromdir(&#34;migrations&#34;)-  migrate(4);&#39;&#xA;daniel@netburst:~/git/PostText$ echo $?&#xA;0&#xA;&#xA;I keep the migration hard-coded in the method call in the app itself just to save myself from accidentally migrating to the latest before it&#39;s ready:&#xA;&#xA;From PostText.pl&#xA;app-  pg-  migrations-  fromdir(&#39;migrations&#39;)-  migrate(4);&#xA;&#xA;I know there&#39;s some reason I started doing that... I accidentally something but now I can&#39;t remember. Gunna just stick with the cargo cult and leave it be.&#xA;&#xA;Next I gotta work on the aforementioned controller logic. And then moar tests.&#xA;&#xA;database&#xA;mojolicious&#xA;perl&#xA;sql&#xA;webdev&#xA;&#xA;Homepage&#xD;&#xA;Code&#xD;&#xA;Mail&#xD;&#xA;Social&#xD;&#xA;Chat&#xD;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>I actually normally wouldn&#39;t enjoy this but thankfully this <a href="https://metacpan.org/pod/Mojo::Pg">Mojo::Pg</a> wrapper makes it easy. I honestly don&#39;t have any experience with the original <a href="https://metacpan.org/pod/DBD::Pg">DBD::Pg</a> as I&#39;m still very new to the database world so I hope this doesn&#39;t read too much like I&#39;m lost in the sauce. First I took a look at my current tables using the <code>psql</code> tool:</p>

<pre><code>post_text=&gt; \d
                   List of relations
 Schema |         Name          |   Type   |   Owner
--------+-----------------------+----------+-----------
 public | mojo_migrations       | table    | post_text
 public | replies               | table    | post_text
 public | replies_reply_id_seq  | sequence | post_text
 public | threads               | table    | post_text
 public | threads_thread_id_seq | sequence | post_text
(5 rows)

post_text=&gt; \d replies;
                                            Table &#34;public.replies&#34;
     Column     |           Type           | Collation | Nullable |                  Default
----------------+--------------------------+-----------+----------+-------------------------------------------
 reply_id       | integer                  |           | not null | nextval(&#39;replies_reply_id_seq&#39;::regclass)
 thread_id      | integer                  |           |          |
 reply_date     | timestamp with time zone |           | not null | now()
 reply_author   | character varying(64)    |           |          |
 reply_body     | character varying(4096)  |           |          |
 hidden_status  | boolean                  |           | not null |
 flagged_status | boolean                  |           | not null |
Indexes:
    &#34;replies_pkey&#34; PRIMARY KEY, btree (reply_id)
Foreign-key constraints:
    &#34;replies_thread_id_fkey&#34; FOREIGN KEY (thread_id) REFERENCES threads(thread_id)
</code></pre>

<p>These long lines are ugly so here&#39;s a <a href="https://paste.lgts.xyz/?3218cb8891eb58e7#6UADDjAN4bngeJwwiZndmTQyPauFeTwcknHSTkDzbtqJ">pastebin link</a>. Essentially I need to <code>s/reply_/remark_/g;</code> including that sequence, index and foreign-key constraint:</p>

<pre><code>-- This file is migrations/5/up.sql btw

 ALTER TABLE replies
RENAME TO remarks;

 ALTER TABLE remarks
RENAME reply_id
    TO remark_id;

 ALTER TABLE remarks
RENAME reply_date
    TO remark_date;

 ALTER TABLE remarks
RENAME reply_author
   TO remark_author;

 ALTER TABLE remarks
RENAME reply_body
    TO remark_body;

 ALTER TABLE remarks
RENAME CONSTRAINT replies_thread_id_fkey
    TO remarks_thread_id_fkey;

 ALTER INDEX replies_pkey
RENAME TO remarks_pkey;

 ALTER SEQUENCE replies_reply_id_seq
RENAME TO remarks_remark_id_seq;
</code></pre>

<p>I&#39;m also going to do the exact opposite for our rolling-back pleasure:</p>

<pre><code>-- This one is migrations/5/down.sql

 ALTER TABLE remarks
RENAME TO replies;

 ALTER TABLE replies
RENAME remark_id
    TO reply_id;

 ALTER TABLE replies
RENAME remark_date
    TO reply_date;

 ALTER TABLE replies
RENAME remark_author
    TO reply_author;

 ALTER TABLE replies
RENAME remark_body
    TO reply_body;

 ALTER TABLE replies
RENAME CONSTRAINT remarks_thread_id_fkey
    TO replies_thread_id_fkey;

 ALTER INDEX remarks_pkey
RENAME TO replies_pkey;

 ALTER SEQUENCE remarks_remark_id_seq
RENAME TO replies_reply_id_seq;
</code></pre>

<p>Idk why but I always find this step feels weird to me because it feels like I&#39;m undoing the undo lol. But we will need it later so let&#39;s absolutely include it. Now I&#39;m gunna see if it works...</p>

<pre><code>daniel@netburst:~/git/PostText$ ./PostText.pl eval &#39;app-&gt;pg-&gt;migrations-&gt;from_dir(&#34;migrations&#34;)-&gt;migrate(5);&#39;
daniel@netburst:~/git/PostText$ echo $?
0
</code></pre>

<p>No news is good news I guess. Let&#39;s whip out <code>psql</code> again and see how it looks:</p>

<pre><code>post_text=&gt; \d
                   List of relations
 Schema |         Name          |   Type   |   Owner
--------+-----------------------+----------+-----------
 public | mojo_migrations       | table    | post_text
 public | remarks               | table    | post_text
 public | remarks_remark_id_seq | sequence | post_text
 public | threads               | table    | post_text
 public | threads_thread_id_seq | sequence | post_text
(5 rows)

post_text=&gt; \d remarks;
                                            Table &#34;public.remarks&#34;
     Column     |           Type           | Collation | Nullable |                  Default
----------------+--------------------------+-----------+----------+--------------------------------------------
 remark_id      | integer                  |           | not null | nextval(&#39;remarks_remark_id_seq&#39;::regclass)
 thread_id      | integer                  |           |          |
 remark_date    | timestamp with time zone |           | not null | now()
 remark_author  | character varying(64)    |           |          |
 remark_body    | character varying(4096)  |           |          |
 hidden_status  | boolean                  |           | not null |
 flagged_status | boolean                  |           | not null |
Indexes:
    &#34;remarks_pkey&#34; PRIMARY KEY, btree (remark_id)
Foreign-key constraints:
    &#34;remarks_thread_id_fkey&#34; FOREIGN KEY (thread_id) REFERENCES threads(thread_id)
</code></pre>

<p>Aaaaaand the <a href="https://paste.lgts.xyz/?45b1e2168a8bd9c2#6ii86AWYJKtVkTzyjHe2uHyxRntnmPx7knuo4HSo27jK">pastebin</a>. I think we&#39;re in good shape. I&#39;m going to migrate back for now as I still need to make some changes in the controller logic to use the new Remark model instead of my old Reply model.</p>

<pre><code>daniel@netburst:~/git/PostText$ ./PostText.pl eval &#39;app-&gt;pg-&gt;migrations-&gt;from_dir(&#34;migrations&#34;)-&gt;migrate(4);&#39;
daniel@netburst:~/git/PostText$ echo $?
0
</code></pre>

<p>I keep the migration hard-coded in the method call in the app itself just to save myself from accidentally migrating to the latest before it&#39;s ready:</p>

<pre><code># From PostText.pl
app-&gt;pg-&gt;migrations-&gt;from_dir(&#39;migrations&#39;)-&gt;migrate(4);
</code></pre>

<p>I know there&#39;s some reason I started doing that... I accidentally something but now I can&#39;t remember. Gunna just stick with the <a href="https://en.wikipedia.org/wiki/Cargo_cult_programming">cargo cult</a> and leave it be.</p>

<p>Next I gotta work on the aforementioned controller logic. And then moar tests.</p>

<p><a href="https://blog.swagg.net/tag:database" class="hashtag"><span>#</span><span class="p-category">database</span></a>
<a href="https://blog.swagg.net/tag:mojolicious" class="hashtag"><span>#</span><span class="p-category">mojolicious</span></a>
<a href="https://blog.swagg.net/tag:perl" class="hashtag"><span>#</span><span class="p-category">perl</span></a>
<a href="https://blog.swagg.net/tag:sql" class="hashtag"><span>#</span><span class="p-category">sql</span></a>
<a href="https://blog.swagg.net/tag:webdev" class="hashtag"><span>#</span><span class="p-category">webdev</span></a></p>
<ul><li><a href="https://www.swagg.net">Homepage</a></li>
<li><a href="https://codeberg.org/swaggboi">Code</a></li>
<li><a href="mailto:swaggboi@slackware.uk">Mail</a></li>
<li><a href="https://eattherich.club/@swaggboi">Social</a></li>
<li><a href="https://discord.gg/6MXVZKU">Chat</a></li></ul>
]]></content:encoded>
      <guid>https://blog.swagg.net/i-like-to-migrate-migrate</guid>
      <pubDate>Mon, 22 Aug 2022 16:51:40 +0000</pubDate>
    </item>
    <item>
      <title>I Accidentally Reply Model</title>
      <link>https://blog.swagg.net/i-accidentally-reply-model?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[What should I do...&#xA;&#xA;Is this dangerous?&#xA;&#xA;So I ran into a funny little snafu last night. I finally implemented my Reply model for my little textboard project but once I did this I noticed the app no longer shows in the snazzy built-in error pages. That&#39;s bummer because I find them real helpful when running apps in development mode. I kinda gave up on this last night and looked at it again this morning and noticed the following in the console output (requesting a route that does not exist, expect a 404):&#xA;&#xA;[2022-08-20 13:08:43.53717] [27345] [trace] [CgcDACZgwvD4] GET &#34;/swagg&#34;&#xA;Mojo::Reactor::Poll: I/O watcher failed: Can&#39;t locate object method &#34;exception&#34; via package &#34;PostText::Model::Reply&#34; at /home/daniel/perl5/lib/perl5/Mojolicious.pm line 200.&#xA;&#xA;It must be trying to dereference exception from an object named reply... Let&#39;s see...&#xA;&#xA;[daniel@netburst mojo]$ grep -i -r &#39;\-  reply&#39; * | grep -i &#39;lite&#39;&#xA;t/mojolicious/exceptionliteapp.t:  $c-  reply-  exception(undef);&#xA;t/mojolicious/exceptionliteapp.t:  $c-  reply-  exception;&#xA;t/mojolicious/exceptionliteapp.t:  $c-  reply-  exception(Mojo::Exception-  new);&#xA;t/mojolicious/liteapp.t:  $c-  rendermaybe(&#39;thisdoesnoteverexist&#39;) or $c-  reply-  static($file);&#xA;t/mojolicious/liteapp.t:get &#39;/static&#39; =  sub { shift-  reply-  static(&#39;hello.txt&#39;) };&#xA;t/mojolicious/longpollingliteapp.t:  Mojo::IOLoop-  timer(0.25 =  sub { $c-  reply-  static(&#39;hello.txt&#39;) });&#xA;t/mojolicious/staticliteapp.t:get &#39;/hello3.txt&#39; =  sub { shift-  reply-  static(&#39;hello2.txt&#39;) };&#xA;t/mojolicious/staticliteapp.t:  $c-  reply-  static(&#39;hello2.txt&#39;);&#xA;t/mojolicious/staticliteapp.t:  $c-  reply-  asset($mem);&#xA;t/mojolicious/staticliteapp.t:  $c-  reply-  file(curfile-  sibling(&#39;templates2&#39;, &#39;42.html.ep&#39;));&#xA;&#xA;Gah! I think I can work around this I suppose by just renaming the reply helper to something other than reply but for consistency I&#39;m going to go ahead and migrate everything to use the name Remark instead of Reply. &#39;Comment&#39; would remind me too much of social media so I guess these threads are gettin remarked upon.&#xA;&#xA;This, of course, means another night of databases. 😩&#xA;&#xA;perl&#xA;mojolicious&#xA;webdev&#xA;database&#xA;&#xA;Homepage&#xD;&#xA;Code&#xD;&#xA;Mail&#xD;&#xA;Social&#xD;&#xA;Chat&#xD;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<h2 id="what-should-i-do" id="what-should-i-do">What should I do...</h2>

<h3 id="is-this-dangerous" id="is-this-dangerous">Is this dangerous?</h3>

<p>So I ran into a funny little snafu last night. I finally implemented my <a href="https://github.com/swaggboi/PostText/commit/3bec9a71f1c44f7970e4031e2288bc1201c28b89">Reply model</a> for my little textboard project but once I did this I noticed the app no longer shows in the snazzy built-in <a href="https://docs.mojolicious.org/Mojolicious/Guides/Tutorial#Built-in-exception-and-not_found-pages">error pages</a>. That&#39;s bummer because I find them real helpful when running apps in development mode. I kinda gave up on this last night and looked at it again this morning and noticed the following in the console output (requesting a route that does not exist, expect a 404):</p>

<pre><code>[2022-08-20 13:08:43.53717] [27345] [trace] [CgcDACZgwvD4] GET &#34;/swagg&#34;
Mojo::Reactor::Poll: I/O watcher failed: Can&#39;t locate object method &#34;exception&#34; via package &#34;PostText::Model::Reply&#34; at /home/daniel/perl5/lib/perl5/Mojolicious.pm line 200.
</code></pre>

<p>It must be trying to dereference <code>exception</code> from an object named <code>reply</code>... Let&#39;s see...</p>

<pre><code>[daniel@netburst mojo]$ grep -i -r &#39;\-&gt;reply&#39; * | grep -i &#39;lite&#39;
t/mojolicious/exception_lite_app.t:  $c-&gt;reply-&gt;exception(undef);
t/mojolicious/exception_lite_app.t:  $c-&gt;reply-&gt;exception;
t/mojolicious/exception_lite_app.t:  $c-&gt;reply-&gt;exception(Mojo::Exception-&gt;new);
t/mojolicious/lite_app.t:  $c-&gt;render_maybe(&#39;this_does_not_ever_exist&#39;) or $c-&gt;reply-&gt;static($file);
t/mojolicious/lite_app.t:get &#39;/static&#39; =&gt; sub { shift-&gt;reply-&gt;static(&#39;hello.txt&#39;) };
t/mojolicious/longpolling_lite_app.t:  Mojo::IOLoop-&gt;timer(0.25 =&gt; sub { $c-&gt;reply-&gt;static(&#39;hello.txt&#39;) });
t/mojolicious/static_lite_app.t:get &#39;/hello3.txt&#39; =&gt; sub { shift-&gt;reply-&gt;static(&#39;hello2.txt&#39;) };
t/mojolicious/static_lite_app.t:  $c-&gt;reply-&gt;static(&#39;hello2.txt&#39;);
t/mojolicious/static_lite_app.t:  $c-&gt;reply-&gt;asset($mem);
t/mojolicious/static_lite_app.t:  $c-&gt;reply-&gt;file(curfile-&gt;sibling(&#39;templates2&#39;, &#39;42.html.ep&#39;));
</code></pre>

<p>Gah! I think I can work around this I suppose by just renaming the <code>reply</code> <a href="https://docs.mojolicious.org/Mojolicious/Guides/Tutorial#Helpers">helper</a> to something other than <code>reply</code> but for consistency I&#39;m going to go ahead and <a href="https://docs.mojolicious.org/Mojo/Pg/Migrations">migrate</a> everything to use the name Remark instead of Reply. &#39;Comment&#39; would remind me too much of social media so I guess these threads are gettin remarked upon.</p>

<p>This, of course, means another night of databases. 😩</p>

<p><a href="https://blog.swagg.net/tag:perl" class="hashtag"><span>#</span><span class="p-category">perl</span></a>
<a href="https://blog.swagg.net/tag:mojolicious" class="hashtag"><span>#</span><span class="p-category">mojolicious</span></a>
<a href="https://blog.swagg.net/tag:webdev" class="hashtag"><span>#</span><span class="p-category">webdev</span></a>
<a href="https://blog.swagg.net/tag:database" class="hashtag"><span>#</span><span class="p-category">database</span></a></p>
<ul><li><a href="https://www.swagg.net">Homepage</a></li>
<li><a href="https://codeberg.org/swaggboi">Code</a></li>
<li><a href="mailto:swaggboi@slackware.uk">Mail</a></li>
<li><a href="https://eattherich.club/@swaggboi">Social</a></li>
<li><a href="https://discord.gg/6MXVZKU">Chat</a></li></ul>
]]></content:encoded>
      <guid>https://blog.swagg.net/i-accidentally-reply-model</guid>
      <pubDate>Sat, 20 Aug 2022 23:08:13 +0000</pubDate>
    </item>
    <item>
      <title>Input Validation and Errors</title>
      <link>https://blog.swagg.net/input-validation-and-errors?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[I used to use the flash() helper in Mojolicious for reporting errors back to the user but recently discovered I can just use Mojolicious::Validator::Validation in my templates which seems like the right tool for the job. I was already using it in my Mojolicious::Lite app like so:&#xA;&#xA;$v-  required(&#39;name&#39; )-  size(1, 63  );&#xA;$v-  required(&#39;title&#39;)-  size(1, 127 );&#xA;$v-  required(&#39;post&#39; )-  size(2, 4000);&#xA;&#xA;But other than changing the status to 400 I didn&#39;t realize I could use this to also report to the user that their request was invalid. The methods can be used in the templates themselves:&#xA;&#xA;form method=&#34;post&#34;&#xA;  div class=&#34;name field&#34;&#xA;    %= labelfor name = &#39;Author&#39; %  %= textfield name =&#39;Anonymous&#39;, maxlength =  63, minlength =  1 %  % if (my $error = validation-error(&#39;name&#39;)) { =%  p class=&#34;field-with-error&#34;Invalid name: 1 to 63 characters please./p&#xA;    % } =%&#xA;  /div&#xA;  div class=&#34;title field&#34;&#xA;    %= labelfor title = &#39;Title&#39; %  %= textfield &#39;title&#39;, maxlength = 127, minlength =  1 %  % if (my $error = validation-error(&#39;title&#39;)) { =%  p class=&#34;field-with-error&#34;Invalid title: 1 to 127 characters please./p&#xA;    % } =%&#xA;  /div&#xA;  div class=&#34;text field&#34;&#xA;    %= labelfor post = &#39;Text&#39; %  &lt;%= textarea &#39;post&#39;, (&#xA;        maxlength =  4000,&#xA;        minlength =  2,&#xA;        required  =  &#39;true&#39;,&#xA;        rows      =  6&#xA;    ) %  % if (my $error = validation-error(&#39;post&#39;)) { =%  p class=&#34;field-with-error&#34;Invalid post: Up to 4,000 characters only./p&#xA;    % } =%&#xA;  /div&#xA;  %= submit_button &#39;Post&#39;, class = &#39;post button&#39; %  /form&#xA;&#xA;So when the user makes their initial GET request, the following HTML is rendered within the form:&#xA;&#xA;div class=&#34;name field&#34;&#xA;  label for=&#34;name&#34;Author/label&#xA;  input maxlength=&#34;63&#34; minlength=&#34;1&#34; name=&#34;name&#34; type=&#34;text&#34; value=&#34;Anonymous&#34;&#xA;/div&#xA;div class=&#34;title field&#34;&#xA;  label for=&#34;title&#34;Title/label&#xA;  input maxlength=&#34;127&#34; minlength=&#34;1&#34; name=&#34;title&#34; type=&#34;text&#34;&#xA;/div&#xA;div class=&#34;text field&#34;&#xA;  label for=&#34;post&#34;Text/label&#xA;  textarea maxlength=&#34;4000&#34; minlength=&#34;2&#34; name=&#34;post&#34; required=&#34;true&#34; rows=&#34;6&#34;/textarea&#xA;/div&#xA;input class=&#34;post button&#34; type=&#34;submit&#34; value=&#34;Post&#34;&#xA;&#xA;Now let&#39;s say you screw up and submit a null value for the Title in your subsequent POST request, in your response the form is rendered again like this:&#xA;&#xA;div class=&#34;name field&#34;&#xA;  label for=&#34;name&#34;Author/label&#xA;  input maxlength=&#34;63&#34; minlength=&#34;1&#34; name=&#34;name&#34; type=&#34;text&#34; value=&#34;anon&#34;&#xA;/div&#xA;div class=&#34;title field&#34;&#xA;  label class=&#34;field-with-error&#34; for=&#34;title&#34;Title/label&#xA;  input class=&#34;field-with-error&#34; maxlength=&#34;127&#34; minlength=&#34;1&#34; name=&#34;title&#34; type=&#34;text&#34;&#xA;    p class=&#34;field-with-error&#34;Invalid title: 1 to 127 characters please./p&#xA;/div&#xA;div class=&#34;text field&#34;&#xA;  label for=&#34;post&#34;Text/label&#xA;  textarea maxlength=&#34;4000&#34; minlength=&#34;2&#34; name=&#34;post&#34; required=&#34;true&#34; rows=&#34;6&#34;hi/textarea&#xA;/div&#xA;input class=&#34;post button&#34; type=&#34;submit&#34; value=&#34;Post&#34;&#xA;&#xA;The class attribute field-with-error was added to the invalid fields allowing me to decorate this to hint to the user. Then I added another little paragraph (with the same class) to make sure accessibility isn&#39;t an issue. These are definitely scenarios where I choose TagHelpers over vanilla HTML in my templates.&#xA;&#xA;Also while testing all of this, I was resorting to using curl to submit invalid input since my browser won&#39;t let me thanks to attributes such as maxlength and minlength being present. Finally I realized I could do the same with Mojo&#39;s built-in commands which is awesome:&#xA;&#xA;$ ./PostText.pl get -M POST -f &#39;name=anon&#39; -f &#39;title=&#39; -f &#39;post=hi&#39; &#39;/post&#39;&#xA;[2022-08-15 19:28:16.84229] [76893] [trace] [jJBy5DsZGrMq] POST &#34;/post&#34;&#xA;[2022-08-15 19:28:16.84266] [76893] [trace] [jJBy5DsZGrMq] Routing to a callback&#xA;[2022-08-15 19:28:16.84279] [76893] [trace] [jJBy5DsZGrMq] Routing to a callback&#xA;[2022-08-15 19:28:16.84359] [76893] [trace] [jJBy5DsZGrMq] Rendering template &#34;post.html.ep&#34;&#xA;[2022-08-15 19:28:16.84564] [76893] [trace] [jJBy5DsZGrMq] Rendering template &#34;layouts/main.html.ep&#34;&#xA;[2022-08-15 19:28:16.84741] [76893] [trace] [jJBy5DsZGrMq] 400 Bad Request (0.005116s, 195.465/s)&#xA;!DOCTYPE html&#xA;html lang=&#34;en&#34;&#xA;head&#xA;  titlePost::Text - New Thread/title&#xA;  link href=&#34;/asset/942e7be1d2/PostText.css&#34; rel=&#34;stylesheet&#34;&#xA;/head&#xA;body&#xA;h1Post::Text/h1&#xA;nav&#xA;  a href=&#34;/view&#34;View/a&#xA;  a href=&#34;/post&#34;New/a&#xA;/nav&#xA;hr&#xA;h2New Thread/h2&#xA;form method=&#34;post&#34;&#xA;  div class=&#34;name field&#34;&#xA;    label for=&#34;name&#34;Author/label&#xA;    input maxlength=&#34;63&#34; minlength=&#34;1&#34; name=&#34;name&#34; type=&#34;text&#34; value=&#34;anon&#34;&#xA;  /div&#xA;  div class=&#34;title field&#34;&#xA;    label class=&#34;field-with-error&#34; for=&#34;title&#34;Title/label&#xA;    input class=&#34;field-with-error&#34; maxlength=&#34;127&#34; minlength=&#34;1&#34; name=&#34;title&#34; type=&#34;text&#34;&#xA;      p class=&#34;field-with-error&#34;Invalid title: 1 to 127 characters please./p&#xA;  /div&#xA;  div class=&#34;text field&#34;&#xA;    label for=&#34;post&#34;Text/label&#xA;    textarea maxlength=&#34;4000&#34; minlength=&#34;2&#34; name=&#34;post&#34; required=&#34;true&#34; rows=&#34;6&#34;hi/textarea&#xA;  /div&#xA;  input class=&#34;post button&#34; type=&#34;submit&#34; value=&#34;Post&#34;&#xA;/form&#xA;footer&#xA;  pIn UTF-8 we trust./p&#xA;/footer&#xA;/body&#xA;/html&#xA;&#xA;I see two ways forward on this little project. I can maybe pause now and blow this up into a full-structure Mojolicious app or I can implement my next model (involving new-to-me SQL stuff like FOREIGN KEY and JOIN). Really either of these will be new-to-me and will take some time so I&#39;ll probably try to pick the lowest hanging fruit of the two... Once I figure out what the hell that is.&#xA;&#xA;perl&#xA;mojolicious&#xA;&#xA;Homepage&#xD;&#xA;Code&#xD;&#xA;Mail&#xD;&#xA;Social&#xD;&#xA;Chat&#xD;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>I used to use the <code>flash()</code> <a href="https://docs.mojolicious.org/Mojolicious/Plugin/DefaultHelpers#flash">helper</a> in Mojolicious for reporting errors back to the user but recently discovered I can just use <a href="https://docs.mojolicious.org/Mojolicious/Validator/Validation">Mojolicious::Validator::Validation</a> in my templates which seems like the right tool for the job. I was already using it in my <a href="https://docs.mojolicious.org/Mojolicious/Lite">Mojolicious::Lite</a> app like so:</p>

<pre><code>$v-&gt;required(&#39;name&#39; )-&gt;size(1, 63  );
$v-&gt;required(&#39;title&#39;)-&gt;size(1, 127 );
$v-&gt;required(&#39;post&#39; )-&gt;size(2, 4000);
</code></pre>

<p>But other than changing the <a href="https://en.wikipedia.org/wiki/List_of_HTTP_status_codes">status</a> to 400 I didn&#39;t realize I could use this to also report to the user that their request was invalid. The methods can be used in the templates themselves:</p>

<pre><code>&lt;form method=&#34;post&#34;&gt;
  &lt;div class=&#34;name field&#34;&gt;
    &lt;%= label_for name =&gt; &#39;Author&#39; %&gt;
    &lt;%= text_field name =&gt;&#39;Anonymous&#39;, maxlength =&gt; 63, minlength =&gt; 1 %&gt;
    &lt;% if (my $error = validation-&gt;error(&#39;name&#39;)) { =%&gt;
      &lt;p class=&#34;field-with-error&#34;&gt;Invalid name: 1 to 63 characters please.&lt;/p&gt;
    &lt;% } =%&gt;
  &lt;/div&gt;
  &lt;div class=&#34;title field&#34;&gt;
    &lt;%= label_for title =&gt; &#39;Title&#39; %&gt;
    &lt;%= text_field &#39;title&#39;, maxlength =&gt; 127, minlength =&gt; 1 %&gt;
    &lt;% if (my $error = validation-&gt;error(&#39;title&#39;)) { =%&gt;
      &lt;p class=&#34;field-with-error&#34;&gt;Invalid title: 1 to 127 characters please.&lt;/p&gt;
    &lt;% } =%&gt;
  &lt;/div&gt;
  &lt;div class=&#34;text field&#34;&gt;
    &lt;%= label_for post =&gt; &#39;Text&#39; %&gt;
    &lt;%= text_area &#39;post&#39;, (
        maxlength =&gt; 4000,
        minlength =&gt; 2,
        required  =&gt; &#39;true&#39;,
        rows      =&gt; 6
    ) %&gt;
    &lt;% if (my $error = validation-&gt;error(&#39;post&#39;)) { =%&gt;
      &lt;p class=&#34;field-with-error&#34;&gt;Invalid post: Up to 4,000 characters only.&lt;/p&gt;
    &lt;% } =%&gt;
  &lt;/div&gt;
  &lt;%= submit_button &#39;Post&#39;, class =&gt; &#39;post button&#39; %&gt;
&lt;/form&gt;
</code></pre>

<p>So when the user makes their initial <code>GET</code> request, the following HTML is rendered within the form:</p>

<pre><code>&lt;div class=&#34;name field&#34;&gt;
  &lt;label for=&#34;name&#34;&gt;Author&lt;/label&gt;
  &lt;input maxlength=&#34;63&#34; minlength=&#34;1&#34; name=&#34;name&#34; type=&#34;text&#34; value=&#34;Anonymous&#34;&gt;
&lt;/div&gt;
&lt;div class=&#34;title field&#34;&gt;
  &lt;label for=&#34;title&#34;&gt;Title&lt;/label&gt;
  &lt;input maxlength=&#34;127&#34; minlength=&#34;1&#34; name=&#34;title&#34; type=&#34;text&#34;&gt;
&lt;/div&gt;
&lt;div class=&#34;text field&#34;&gt;
  &lt;label for=&#34;post&#34;&gt;Text&lt;/label&gt;
  &lt;textarea maxlength=&#34;4000&#34; minlength=&#34;2&#34; name=&#34;post&#34; required=&#34;true&#34; rows=&#34;6&#34;&gt;&lt;/textarea&gt;
&lt;/div&gt;
&lt;input class=&#34;post button&#34; type=&#34;submit&#34; value=&#34;Post&#34;&gt;
</code></pre>

<p>Now let&#39;s say you screw up and submit a null value for the Title in your subsequent <code>POST</code> request, in your response the form is rendered again like this:</p>

<pre><code>&lt;div class=&#34;name field&#34;&gt;
  &lt;label for=&#34;name&#34;&gt;Author&lt;/label&gt;
  &lt;input maxlength=&#34;63&#34; minlength=&#34;1&#34; name=&#34;name&#34; type=&#34;text&#34; value=&#34;anon&#34;&gt;
&lt;/div&gt;
&lt;div class=&#34;title field&#34;&gt;
  &lt;label class=&#34;field-with-error&#34; for=&#34;title&#34;&gt;Title&lt;/label&gt;
  &lt;input class=&#34;field-with-error&#34; maxlength=&#34;127&#34; minlength=&#34;1&#34; name=&#34;title&#34; type=&#34;text&#34;&gt;
    &lt;p class=&#34;field-with-error&#34;&gt;Invalid title: 1 to 127 characters please.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;text field&#34;&gt;
  &lt;label for=&#34;post&#34;&gt;Text&lt;/label&gt;
  &lt;textarea maxlength=&#34;4000&#34; minlength=&#34;2&#34; name=&#34;post&#34; required=&#34;true&#34; rows=&#34;6&#34;&gt;hi&lt;/textarea&gt;
&lt;/div&gt;
&lt;input class=&#34;post button&#34; type=&#34;submit&#34; value=&#34;Post&#34;&gt;
</code></pre>

<p>The <code>class</code> attribute <code>field-with-error</code> was added to the invalid fields allowing me to decorate this to hint to the user. Then I added another little paragraph (with the same <code>class</code>) to make sure accessibility isn&#39;t an issue. These are definitely scenarios where I choose <a href="https://docs.mojolicious.org/Mojolicious/Plugin/TagHelpers">TagHelpers</a> over vanilla HTML in my templates.</p>

<p>Also while testing all of this, I was resorting to using <code>curl</code> to submit invalid input since my browser won&#39;t let me thanks to attributes such as <code>maxlength</code> and <code>minlength</code> being present. Finally I realized I could do the same with Mojo&#39;s <a href="https://docs.mojolicious.org/Mojolicious/Commands">built-in commands</a> which is awesome:</p>

<pre><code>$ ./PostText.pl get -M POST -f &#39;name=anon&#39; -f &#39;title=&#39; -f &#39;post=hi&#39; &#39;/post&#39;
[2022-08-15 19:28:16.84229] [76893] [trace] [jJBy5DsZGrMq] POST &#34;/post&#34;
[2022-08-15 19:28:16.84266] [76893] [trace] [jJBy5DsZGrMq] Routing to a callback
[2022-08-15 19:28:16.84279] [76893] [trace] [jJBy5DsZGrMq] Routing to a callback
[2022-08-15 19:28:16.84359] [76893] [trace] [jJBy5DsZGrMq] Rendering template &#34;post.html.ep&#34;
[2022-08-15 19:28:16.84564] [76893] [trace] [jJBy5DsZGrMq] Rendering template &#34;layouts/main.html.ep&#34;
[2022-08-15 19:28:16.84741] [76893] [trace] [jJBy5DsZGrMq] 400 Bad Request (0.005116s, 195.465/s)
&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
  &lt;title&gt;Post::Text - New Thread&lt;/title&gt;
  &lt;link href=&#34;/asset/942e7be1d2/PostText.css&#34; rel=&#34;stylesheet&#34;&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Post::Text&lt;/h1&gt;
&lt;nav&gt;
  &lt;a href=&#34;/view&#34;&gt;View&lt;/a&gt;
  &lt;a href=&#34;/post&#34;&gt;New&lt;/a&gt;
&lt;/nav&gt;
&lt;hr&gt;
&lt;h2&gt;New Thread&lt;/h2&gt;
&lt;form method=&#34;post&#34;&gt;
  &lt;div class=&#34;name field&#34;&gt;
    &lt;label for=&#34;name&#34;&gt;Author&lt;/label&gt;
    &lt;input maxlength=&#34;63&#34; minlength=&#34;1&#34; name=&#34;name&#34; type=&#34;text&#34; value=&#34;anon&#34;&gt;
  &lt;/div&gt;
  &lt;div class=&#34;title field&#34;&gt;
    &lt;label class=&#34;field-with-error&#34; for=&#34;title&#34;&gt;Title&lt;/label&gt;
    &lt;input class=&#34;field-with-error&#34; maxlength=&#34;127&#34; minlength=&#34;1&#34; name=&#34;title&#34; type=&#34;text&#34;&gt;
      &lt;p class=&#34;field-with-error&#34;&gt;Invalid title: 1 to 127 characters please.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&#34;text field&#34;&gt;
    &lt;label for=&#34;post&#34;&gt;Text&lt;/label&gt;
    &lt;textarea maxlength=&#34;4000&#34; minlength=&#34;2&#34; name=&#34;post&#34; required=&#34;true&#34; rows=&#34;6&#34;&gt;hi&lt;/textarea&gt;
  &lt;/div&gt;
  &lt;input class=&#34;post button&#34; type=&#34;submit&#34; value=&#34;Post&#34;&gt;
&lt;/form&gt;
&lt;footer&gt;
  &lt;p&gt;In UTF-8 we trust.&lt;/p&gt;
&lt;/footer&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>I see two ways forward on this little project. I can maybe pause now and <a href="https://docs.mojolicious.org/Mojolicious/Guides/Growing#WELL-STRUCTURED-APPLICATION">blow this up</a> into a full-structure Mojolicious app or I can implement my next model (involving new-to-me SQL stuff like <code>FOREIGN KEY</code> and <code>JOIN</code>). Really either of these will be new-to-me and will take some time so I&#39;ll probably try to pick the lowest hanging fruit of the two... Once I figure out what the hell that is.</p>

<p><a href="https://blog.swagg.net/tag:perl" class="hashtag"><span>#</span><span class="p-category">perl</span></a>
<a href="https://blog.swagg.net/tag:mojolicious" class="hashtag"><span>#</span><span class="p-category">mojolicious</span></a></p>
<ul><li><a href="https://www.swagg.net">Homepage</a></li>
<li><a href="https://codeberg.org/swaggboi">Code</a></li>
<li><a href="mailto:swaggboi@slackware.uk">Mail</a></li>
<li><a href="https://eattherich.club/@swaggboi">Social</a></li>
<li><a href="https://discord.gg/6MXVZKU">Chat</a></li></ul>
]]></content:encoded>
      <guid>https://blog.swagg.net/input-validation-and-errors</guid>
      <pubDate>Mon, 15 Aug 2022 23:50:03 +0000</pubDate>
    </item>
    <item>
      <title>More Things I Should Have Already Figured Out Years Ago</title>
      <link>https://blog.swagg.net/more-things-i-should-have-already-figured-out-years-ago?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[So I used to read data into an array of arrays like so:&#xA;&#xA;sub getthreads($self) {&#xA;    $self-  pg-  db-  query(&lt;~&#39;ENDSQL&#39;)-arrays()&#xA;        SELECT threadid,&#xA;               TOCHAR(threaddate, &#39;Dy Mon DD HH:MI:SS AM TZ YYYY&#39;),&#xA;               threadauthor,&#xA;               threadtitle,&#xA;               threadbody&#xA;          FROM threads&#xA;         WHERE NOT hiddenstatus&#xA;         ORDER BY threaddate DESC;&#xA;       ENDSQL&#xA;}&#xA;&#xA;Then I&#39;d plop that data into templates like so:&#xA;&#xA;% for my $thread (@$threads) { =%&#xA;  article class=&#34;thread&#34;&#xA;    h3 class=&#34;title&#34;%= @$thread[3] %/h3&#xA;    h4 class=&#34;date&#34;%= @$thread[1] %/h4&#xA;    h5 class=&#34;author&#34;%= @$thread[2] %/h5&#xA;    p class=&#34;body&#34;%= @$thread[0] %/p&#xA;  /article&#xA;% } =%&#xA;&#xA;This is already pretty cool but I kept losing track of what index number went to which data field. Then I saw that there is a hashes() method in Mojo::Pg::Results and thought... What if we used 100% of the brain? (Edit: Formatting turned out bad for this one)&#xA;&#xA;sub getthreads($self) {&#xA;    $self-  pg-  db-  query(&lt;~&#39;ENDSQL&#39;)-hashes()&#xA;        SELECT threadid                                             AS id,&#xA;               TOCHAR(threaddate, &#39;Dy Mon DD HH:MI:SS AM TZ YYYY&#39;) AS date,&#xA;               threadauthor                                         AS author,&#xA;               threadtitle                                          AS title,&#xA;               threadbody                                           AS body&#xA;          FROM threads&#xA;         WHERE NOT hiddenstatus&#xA;         ORDER BY threaddate DESC;&#xA;       ENDSQL&#xA;}&#xA;&#xA;By using SQL to assign an alias to the column names my templates now look much cleaner:&#xA;&#xA;% for my $thread (@$threads) { =%&#xA;  article class=&#34;thread&#34;&#xA;    h3 class=&#34;title&#34;%= %$thread{&#39;title&#39;} %/h3&#xA;    h4 class=&#34;date&#34;%= %$thread{&#39;date&#39;} %/h4&#xA;    h5 class=&#34;author&#34;%= %$thread{&#39;author&#39;} %/h5&#xA;    p class=&#34;body&#34;%= %$thread{&#39;body&#39;} %/p&#xA;  /article&#xA;% } =%&#xA;&#xA;Readability becomes more and more important as my memory gets worse and worse... I need to learn POD so if I&#39;m a boi of my word there may be a blog post on that in the future. Knowing me... Years in the future.&#xA;&#xA;perl&#xA;mojolicious&#xA;sql&#xA;&#xA;Homepage&#xD;&#xA;Code&#xD;&#xA;Mail&#xD;&#xA;Social&#xD;&#xA;Chat&#xD;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>So I used to read data into an array of arrays like so:</p>

<pre><code>sub get_threads($self) {
    $self-&gt;pg-&gt;db-&gt;query(&lt;&lt;~&#39;END_SQL&#39;)-&gt;arrays()
        SELECT thread_id,
               TO_CHAR(thread_date, &#39;Dy Mon DD HH:MI:SS AM TZ YYYY&#39;),
               thread_author,
               thread_title,
               thread_body
          FROM threads
         WHERE NOT hidden_status
         ORDER BY thread_date DESC;
       END_SQL
}
</code></pre>

<p>Then I&#39;d plop that data into <a href="https://docs.mojolicious.org/Mojolicious/Guides/Rendering#Embedded-Perl">templates</a> like so:</p>

<pre><code>&lt;% for my $thread (@$threads) { =%&gt;
  &lt;article class=&#34;thread&#34;&gt;
    &lt;h3 class=&#34;title&#34;&gt;&lt;%= @$thread[3] %&gt;&lt;/h3&gt;
    &lt;h4 class=&#34;date&#34;&gt;&lt;%= @$thread[1] %&gt;&lt;/h4&gt;
    &lt;h5 class=&#34;author&#34;&gt;&lt;%= @$thread[2] %&gt;&lt;/h5&gt;
    &lt;p class=&#34;body&#34;&gt;&lt;%= @$thread[0] %&gt;&lt;/p&gt;
  &lt;/article&gt;
&lt;% } =%&gt;
</code></pre>

<p>This is already pretty cool but I kept losing track of what index number went to which data field. Then I saw that there is a <code>hashes()</code> method in <a href="https://metacpan.org/pod/Mojo::Pg::Results">Mojo::Pg::Results</a> and thought... What if we used <em>100%</em> of the brain? (Edit: Formatting turned out bad for <a href="https://pastebin.com/yTzau4T2">this one</a>)</p>

<pre><code>sub get_threads($self) {
    $self-&gt;pg-&gt;db-&gt;query(&lt;&lt;~&#39;END_SQL&#39;)-&gt;hashes()
        SELECT thread_id                                             AS id,
               TO_CHAR(thread_date, &#39;Dy Mon DD HH:MI:SS AM TZ YYYY&#39;) AS date,
               thread_author                                         AS author,
               thread_title                                          AS title,
               thread_body                                           AS body
          FROM threads
         WHERE NOT hidden_status
         ORDER BY thread_date DESC;
       END_SQL
}
</code></pre>

<p>By using <a href="https://www.sqlstyle.guide">SQL</a> to assign an alias to the column names my templates now look much cleaner:</p>

<pre><code>&lt;% for my $thread (@$threads) { =%&gt;
  &lt;article class=&#34;thread&#34;&gt;
    &lt;h3 class=&#34;title&#34;&gt;&lt;%= %$thread{&#39;title&#39;} %&gt;&lt;/h3&gt;
    &lt;h4 class=&#34;date&#34;&gt;&lt;%= %$thread{&#39;date&#39;} %&gt;&lt;/h4&gt;
    &lt;h5 class=&#34;author&#34;&gt;&lt;%= %$thread{&#39;author&#39;} %&gt;&lt;/h5&gt;
    &lt;p class=&#34;body&#34;&gt;&lt;%= %$thread{&#39;body&#39;} %&gt;&lt;/p&gt;
  &lt;/article&gt;
&lt;% } =%&gt;
</code></pre>

<p>Readability becomes more and more important as my memory gets worse and worse... I need to learn <a href="https://perldoc.perl.org/perlpod">POD</a> so if I&#39;m a boi of my word there may be a blog post on that in the future. Knowing me... Years in the <a href="https://en.wikipedia.org/wiki/Year_2038_problem">future</a>.</p>

<p><a href="https://blog.swagg.net/tag:perl" class="hashtag"><span>#</span><span class="p-category">perl</span></a>
<a href="https://blog.swagg.net/tag:mojolicious" class="hashtag"><span>#</span><span class="p-category">mojolicious</span></a>
<a href="https://blog.swagg.net/tag:sql" class="hashtag"><span>#</span><span class="p-category">sql</span></a></p>
<ul><li><a href="https://www.swagg.net">Homepage</a></li>
<li><a href="https://codeberg.org/swaggboi">Code</a></li>
<li><a href="mailto:swaggboi@slackware.uk">Mail</a></li>
<li><a href="https://eattherich.club/@swaggboi">Social</a></li>
<li><a href="https://discord.gg/6MXVZKU">Chat</a></li></ul>
]]></content:encoded>
      <guid>https://blog.swagg.net/more-things-i-should-have-already-figured-out-years-ago</guid>
      <pubDate>Fri, 05 Aug 2022 04:20:52 +0000</pubDate>
    </item>
    <item>
      <title>I see that Mojolicious was ported to JS</title>
      <link>https://blog.swagg.net/i-see-that-mojolicious-was-ported-to-js?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[I will say if I had to choose either PHP or JS, I&#39;d choose&#xA;JS lol. But I do think the World Wide Web&#xA;without Perl is worse off. I hope the O.G. Perl Mojolicious lives on&#xA;in harmony.&#xA;&#xA;perl&#xA;javascript&#xA;mojolicious&#xA;insomnia&#xA;&#xA;Homepage&#xD;&#xA;Code&#xD;&#xA;Mail&#xD;&#xA;Social&#xD;&#xA;Chat&#xD;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>I will say if I had to choose either PHP or JS, I&#39;d choose
<a href="https://mojojs.org">JS</a> lol. But I do think the World Wide Web
without Perl is worse off. I hope the O.G. Perl Mojolicious lives on
in harmony.</p>

<p><a href="https://blog.swagg.net/tag:perl" class="hashtag"><span>#</span><span class="p-category">perl</span></a>
<a href="https://blog.swagg.net/tag:javascript" class="hashtag"><span>#</span><span class="p-category">javascript</span></a>
<a href="https://blog.swagg.net/tag:mojolicious" class="hashtag"><span>#</span><span class="p-category">mojolicious</span></a>
<a href="https://blog.swagg.net/tag:insomnia" class="hashtag"><span>#</span><span class="p-category">insomnia</span></a></p>
<ul><li><a href="https://www.swagg.net">Homepage</a></li>
<li><a href="https://codeberg.org/swaggboi">Code</a></li>
<li><a href="mailto:swaggboi@slackware.uk">Mail</a></li>
<li><a href="https://eattherich.club/@swaggboi">Social</a></li>
<li><a href="https://discord.gg/6MXVZKU">Chat</a></li></ul>
]]></content:encoded>
      <guid>https://blog.swagg.net/i-see-that-mojolicious-was-ported-to-js</guid>
      <pubDate>Sun, 31 Jul 2022 08:43:35 +0000</pubDate>
    </item>
    <item>
      <title>Warranty Theater</title>
      <link>https://blog.swagg.net/warranty-theater?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[I recently have come to the come to the conclusion that &#39;warranty&#39; as&#xA;we know it in 2022 is a big fat Greek lie because:&#xA;&#xA;Devices aren&#39;t even repairable anymore. What are they gunna do for you?&#xA;Who says they&#39;ll honor the warranty anyways?&#xA;&#xA;I&#39;m swearing them off forever now. My new warranty is the SwaggPlan:&#xA;&#xA;Always decline the warranty. Always.&#xA;Use search tools like StackOverflow, YouTube, etc for fucking&#xA;   everything. Just this week I&#39;ve fixed a fridge and HVAC. I know&#xA;   literally nothing about these things.&#xA;You are probably capable of figuring out much more shit than you&#xA;   give yourself credit for!&#xA;&#xA;Forget &#39;peace of mind&#39;, warranty only provides the illusion of that. Real peace of mind is ensuring you&#39;re never reliant on people/processes that are demonstrably unreliable.&#xA;&#xA;Right to repair now,&#xA;&#xA;Dan&#xA;&#xA;righttorepair&#xA;diy&#xA;&#xA;Homepage&#xD;&#xA;Code&#xD;&#xA;Mail&#xD;&#xA;Social&#xD;&#xA;Chat&#xD;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>I recently have come to the come to the conclusion that &#39;warranty&#39; as
we know it in 2022 is a big fat Greek lie because:</p>
<ol><li>Devices aren&#39;t even repairable anymore. What are they gunna do for you?</li>
<li>Who says they&#39;ll honor the warranty anyways?</li></ol>

<p>I&#39;m swearing them off forever now. My new warranty is the SwaggPlan:</p>
<ol><li>Always decline the warranty. <strong>Always.</strong></li>
<li>Use search tools like StackOverflow, YouTube, etc for fucking
everything. Just this week I&#39;ve fixed a fridge and HVAC. I know
literally nothing about these things.</li>
<li>You are probably capable of figuring out much more shit than you
give yourself credit for!</li></ol>

<p>Forget &#39;peace of mind&#39;, warranty only provides the <em>illusion</em> of that. Real peace of mind is ensuring you&#39;re never reliant on people/processes that are demonstrably unreliable.</p>

<p>Right to repair <strong>now</strong>,</p>

<p>Dan</p>

<p><a href="https://blog.swagg.net/tag:righttorepair" class="hashtag"><span>#</span><span class="p-category">righttorepair</span></a>
<a href="https://blog.swagg.net/tag:diy" class="hashtag"><span>#</span><span class="p-category">diy</span></a></p>
<ul><li><a href="https://www.swagg.net">Homepage</a></li>
<li><a href="https://codeberg.org/swaggboi">Code</a></li>
<li><a href="mailto:swaggboi@slackware.uk">Mail</a></li>
<li><a href="https://eattherich.club/@swaggboi">Social</a></li>
<li><a href="https://discord.gg/6MXVZKU">Chat</a></li></ul>
]]></content:encoded>
      <guid>https://blog.swagg.net/warranty-theater</guid>
      <pubDate>Thu, 02 Jun 2022 22:31:51 +0000</pubDate>
    </item>
  </channel>
</rss>