<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kevin Woblick</title>
    <description>The latest articles on DEV Community by Kevin Woblick (@kovah).</description>
    <link>https://dev.to/kovah</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F80610%2F0bd90666-7c8c-42ba-ba78-ec6d6b41bfd1.jpg</url>
      <title>DEV Community: Kevin Woblick</title>
      <link>https://dev.to/kovah</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kovah"/>
    <language>en</language>
    <item>
      <title>Easy date and time localization with the time HTML element</title>
      <dc:creator>Kevin Woblick</dc:creator>
      <pubDate>Wed, 03 May 2023 11:56:46 +0000</pubDate>
      <link>https://dev.to/kovah/easy-date-and-time-localization-with-the-time-html-element-3a69</link>
      <guid>https://dev.to/kovah/easy-date-and-time-localization-with-the-time-html-element-3a69</guid>
      <description>&lt;p&gt;Did you know there is quite easy way to show localized dates and times on your website? You can use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time"&gt;HTML time element&lt;/a&gt; to display dates and times in the user's locale and timezone without having to guess the user's locale, rely on profiles or settings, or doing any backend work.&lt;/p&gt;

&lt;p&gt;Let's start with the basics. The &lt;code&gt;time&lt;/code&gt; element is used to represent either a time on a 24-hour clock or a precise date in the Gregorian calendar (with optional time and timezone information). The time element can be used with the datetime attribute to represent a machine-readable date/time/period which improves accessibility for both bots and impaired users. The time element is supported by all modern browsers and even Internet Explorer 11.&lt;/p&gt;

&lt;p&gt;We use it like this, in this example with a database model from Laravel. Of course, any input can be used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;time&lt;/span&gt; &lt;span class="na"&gt;datetime=&lt;/span&gt;&lt;span class="s"&gt;"{{ $post-&amp;gt;created_at-&amp;gt;toIso8601String() }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {{ $post-&amp;gt;created_at-&amp;gt;toDateTimeLocalString('minute') }}
&lt;span class="nt"&gt;&amp;lt;/time&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output will be something like this before doing any magic with JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;time&lt;/span&gt; &lt;span class="na"&gt;datetime=&lt;/span&gt;&lt;span class="s"&gt;"2023-04-16T08:07:28+00:00"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    2023-04-16T08:07
&lt;span class="nt"&gt;&amp;lt;/time&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though we will overwrite the content of the time element (2023-04-16T08:07) with JavaScript, it is still important to have some content as a fallback. In case the user has JavaScript disabled, the original date and time will be displayed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Localizing the date and time
&lt;/h2&gt;

&lt;p&gt;JavaScript offers a convenient method to localize dates and times with the &lt;code&gt;toLocaleDateString()&lt;/code&gt; and &lt;code&gt;toLocaleTimeString()&lt;/code&gt; methods of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date"&gt;the Date object&lt;/a&gt;. We can use them to localize the date and time output from above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dateTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// output the localized date and time&lt;/span&gt;
  &lt;span class="nx"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// or output the localized date only&lt;/span&gt;
  &lt;span class="nx"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLocaleDateString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// or output the localized time only&lt;/span&gt;
  &lt;span class="nx"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLocaleTimeString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The displayed text will automatically be converted into the user's locale format and timezone, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;time&lt;/span&gt; &lt;span class="na"&gt;datetime=&lt;/span&gt;&lt;span class="s"&gt;"2023-04-16T08:07:28+00:00"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    16.04.2023, 10:07:28
&lt;span class="nt"&gt;&amp;lt;/time&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. You just enhanced your website with localized dates and times.&lt;/p&gt;




&lt;p&gt;This article was first posted on &lt;a href="https://blog.kovah.de/en/2023/easy-date-time-localization-with-html-time-element/"&gt;blog.kovah.de&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>html</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Versionfeeds: Custom RSS feeds for releases of your favorite software</title>
      <dc:creator>Kevin Woblick</dc:creator>
      <pubDate>Sat, 18 Mar 2023 10:55:09 +0000</pubDate>
      <link>https://dev.to/kovah/versionfeeds-custom-rss-feeds-for-releases-of-your-favorite-software-1idb</link>
      <guid>https://dev.to/kovah/versionfeeds-custom-rss-feeds-for-releases-of-your-favorite-software-1idb</guid>
      <description>&lt;p&gt;This is my second product launching, and I am quite excited. As with so many useful tools and services, they are born out of a problem that needs a solution. &lt;a href="https://versionfeeds.com/"&gt;Versionfeeds&lt;/a&gt; is exactly that.&lt;/p&gt;

&lt;p&gt;I was quite annoyed by the fact, that I had no central place where I could see all the software releases. I use software a lot, be it at my job, to build my side projects, or tools hosted on my NAS at home. It's literally dozens, if not hundreds of tools, packages or even programming languages. And most of them receive updates regularly.&lt;br&gt;&lt;br&gt;
That is why I build Versionfeeds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Versionfeeds
&lt;/h2&gt;

&lt;p&gt;On Versionfeeds, you can create custom RSS feeds, add your favorite software to it and then stay up-to-date with all new releases right in your feed reader. &lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a custom RSS Feed
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--foKJCblA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yrq03eu1cp254jaf227q.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--foKJCblA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yrq03eu1cp254jaf227q.jpg" alt="Preview of the feed creation" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create your own RSS feeds with a custom title and description. Both the title and the description are used for the actual RSS feed and displayed in your feed reader.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Add your favorite Software
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Pzj2Ays4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ncptbp668abyoid1cbr0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Pzj2Ays4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ncptbp668abyoid1cbr0.jpg" alt="Preview of adding Go programming language to a feed" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add software to your feed, search for it across multiple providers. All public Github, Gitlab, NPM and Packagist projects are supported. Releases are fetched twice a day. If a project doesn't use releases, tags are fetched if possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Access your Feed
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XBB-z0BC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/el3q3q9xghmcu0x7y4kg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XBB-z0BC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/el3q3q9xghmcu0x7y4kg.jpg" alt="Preview of NetNewsWire with Versionfeeds feeds" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each feed has its own, unique RSS URL. The feeds adhere to the official Atom Feed standard and were tested with the W3C Validator. They can be used with any tool that is capable of reading RSS feeds, no matter if it's a commercial service, a desktop app or a self-hosted web application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versionfeeds Professional
&lt;/h2&gt;

&lt;p&gt;Versionfeeds offers a free plan, that will stay free forever. The number of feeds and software you can add to them is limited. To add unlimited feeds and software, you can upgrade to the Professional plan at any time. The Professional plan costs $4.90 per month or $49 per year.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://versionfeeds.com"&gt;&lt;strong&gt;Create your first feed on Versionfeeds&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Please let me know what your think!&lt;/p&gt;

&lt;p&gt;This article was first published on &lt;a href="https://blog.kovah.de/en/2023/versionfeeds-custom-rss-feeds-for-releases-of-your-favorite-software/"&gt;my blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rss</category>
      <category>feeds</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to get all user sessions in Laravel from Redis</title>
      <dc:creator>Kevin Woblick</dc:creator>
      <pubDate>Fri, 01 Oct 2021 07:48:11 +0000</pubDate>
      <link>https://dev.to/kovah/how-to-get-all-user-sessions-in-laravel-from-redis-444d</link>
      <guid>https://dev.to/kovah/how-to-get-all-user-sessions-in-laravel-from-redis-444d</guid>
      <description>&lt;p&gt;I wanted to access all session data for all users in Laravel while using the Redis cache driver. If you have all session data, you can inspect the data associated to that session, do some calculations based on averages across all session, or whatever else you want to do.&lt;br&gt;
Unfortunately, Laravel does not offer any way to just get &lt;strong&gt;all&lt;/strong&gt; user sessions at once. I searched the web for solutions, only to find out that users recommend switching to the database to store user sessions. Obviously, not a solution for any production system, and really not a good solution if you consider the mediocre performance of any database compared to Redis.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to access all session data in Laravel
&lt;/h2&gt;

&lt;p&gt;So, here's how to access &lt;strong&gt;all&lt;/strong&gt; user sessions stored in Redis. I assume that you use Laravel 8 with PHP 8. You may use this with older PHP versions if you replace the function shorthand with a full function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Illuminate\Support\Facades\Redis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'laravel_database_'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;unserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;mget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$keys&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you changed your &lt;code&gt;APP_NAME&lt;/code&gt;, replace &lt;code&gt;laravel&lt;/code&gt; in the &lt;code&gt;laravel_database_&lt;/code&gt; string with your value.&lt;br&gt;&lt;br&gt;
Example: If your application name is &lt;code&gt;MyApp&lt;/code&gt; you should use &lt;code&gt;myapp_database_&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's really just two lines of code, which should get you something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;array:2 [▼
  0 =&amp;gt; array:3 [▼
    "_token" =&amp;gt; "1jMe1...oWSJe"
    "_previous" =&amp;gt; array:1 [▶]
    "_flash" =&amp;gt; array:2 [▶]
  ]
  1 =&amp;gt; array:9 [▼
    "_token" =&amp;gt; "z29O...W3lT"
    "url" =&amp;gt; []
    "_previous" =&amp;gt; array:1 [▶]
    "_flash" =&amp;gt; array:2 [▶]
    "login" =&amp;gt; []
    "login_web_59ba...989d" =&amp;gt; 1
    "password_hash_web" =&amp;gt; "$2y$10$6H...bnS"
    "password_hash_sanctum" =&amp;gt; "$2y$10$6H...bnS"
  ]
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Explanation of the Code
&lt;/h2&gt;

&lt;p&gt;Let us walk through the two lines to get a better understanding of the code.&lt;/p&gt;

&lt;p&gt;Redis has no feature to get all data at once. First, we need to get all keys from Redis. Laravel automatically builds the Redis keys based the application name and other parts. To work with the "real keys" later, we have to remove this Laravel-internal string from the key. We do this in the str_replace part, where we remove &lt;code&gt;laravel_database_&lt;/code&gt; from all returned keys. The &lt;code&gt;$keys&lt;/code&gt; array then holds the keys for all user sessions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'laravel_database_'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'laravel_database_*'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is to get the data for all those keys. Thankfully, Redis has the &lt;code&gt;MGET&lt;/code&gt; command, which lets you get the data for an array of keys. For whatever reason, the data returned is serialized twice. I guess it's one serialization for storing data in Redis and one for actually serializing the session data. Whatever the reason might be, we get the data via &lt;code&gt;Redis::mget($keys)&lt;/code&gt; and then loop through all returned datasets and unserialize the string we got from Redis twice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;unserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;mget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$keys&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is an array stored in &lt;code&gt;$data&lt;/code&gt; that holds all user sessions as arrays with their corresponding data.&lt;/p&gt;




&lt;p&gt;This article was first published on &lt;a href="https://blog.kovah.de/en/2021/get-all-user-sessions-in-laravel-with-redis/"&gt;my blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>redis</category>
      <category>webdev</category>
      <category>php</category>
    </item>
    <item>
      <title>The Relaunch of my Portfolio for 2021</title>
      <dc:creator>Kevin Woblick</dc:creator>
      <pubDate>Thu, 15 Apr 2021 14:20:16 +0000</pubDate>
      <link>https://dev.to/kovah/the-relaunch-of-my-portfolio-for-2021-3445</link>
      <guid>https://dev.to/kovah/the-relaunch-of-my-portfolio-for-2021-3445</guid>
      <description>&lt;p&gt;My old &lt;a href="https://kovah.de/"&gt;portfolio at Kovah.de&lt;/a&gt; has not received any big updates for years. The design was basically the same. I had fancy browser mockups which contained full screenshots of the websites I made. A few weeks ago I decided that it was about time to relaunch my website together with a new logo.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new Logo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mOosc6Y3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/feg8z4hori74p6w51gaz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mOosc6Y3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/feg8z4hori74p6w51gaz.jpg" alt="Logo of Kevin Woblick" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The logo represents my primary profession as being a Web Developer: the lower/greater signs (&lt;code&gt;&amp;lt;&lt;/code&gt; and &lt;code&gt;&amp;gt;&lt;/code&gt;) are used in HTML, so I wanted them to be the main part of my logo. I tried various other shapes, variations and also colors. But in the end, I found this new logo the most beautiful thing I've created.&lt;br&gt;&lt;br&gt;
The font used for the logotype and the website is called Nexa, which I bought years ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new Website
&lt;/h2&gt;

&lt;p&gt;The new website was built many years ago and the basic layout, the colors and the overall design didn't change much in the past years. The new version comes with fresher colors, modern soft gradients and bold typography.&lt;/p&gt;

&lt;p&gt;While the previous version was built with Hugo as the static site generator, I decided to give &lt;a href="https://www.11ty.dev/"&gt;Eleventy&lt;/a&gt; a chance as I am not that satisfied with Hugo. Instead of writing CSS on my own, 99% of the site is built with Tailwind CSS. Only a few effects are custom styles.&lt;/p&gt;

&lt;p&gt;There is not a single line of Javascript.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kovah.de/"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hs_6H3_v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n1j6s0juy5rv0yss6gtx.png" alt="Screenshot of kovah.de" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Performance-wise, I am really satisfied with the results, which is not a miracle if you keep in mind that the site is HTML and CSS with some images and a custom font. In fact, images and the fonts are responsible for 90% of the size of the site. However, fonts contain only a small subset of all available characters in the Latin charset. Images are delivered as jpg, webp and avif so the browser can pick the most suitable format.&lt;/p&gt;

&lt;p&gt;Googles Lighthouse testifies the performance with a solid 100. 🥳&lt;/p&gt;




&lt;p&gt;If you have any questions, feel free to ask!&lt;/p&gt;

</description>
      <category>portfolio</category>
      <category>webdev</category>
      <category>tailwindcss</category>
      <category>css</category>
    </item>
    <item>
      <title>Why I have chosen to rewrite one of my Projects from Scratch</title>
      <dc:creator>Kevin Woblick</dc:creator>
      <pubDate>Wed, 14 Apr 2021 19:10:34 +0000</pubDate>
      <link>https://dev.to/kovah/refactor-or-rewrite-the-story-of-game-quotes-com-3k55</link>
      <guid>https://dev.to/kovah/refactor-or-rewrite-the-story-of-game-quotes-com-3k55</guid>
      <description>&lt;p&gt;&lt;a href="https://game-quotes.com/en"&gt;&lt;strong&gt;Game Quotes&lt;/strong&gt;&lt;/a&gt; is one of my oldest projects. It was launched back in 2013 as a small side project. At that time there was a site similar to what Game Quotes is now: an archive of funny, inspiring or awfully weird quotes from video games. I loved browsing through the archives, but the site seemed abandoned, as no new quotes were added over months. Today, the old archive is gone and with it a handful of great content. Besides that archive, there was no other place on the internet where you could find curated quotes from video games. Game Quotes was born.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drowning in tech debt
&lt;/h2&gt;

&lt;p&gt;After switching from Wordpress to a custom Laravel application in 2013, the site was basically in a maintenance-only mode. I rarely added new games and only a couple of quotes through the year. The system was first built with Laravel 4.2, which ran out of support and maintenance years ago. Development of the site took quite a while because I had to do so much stuff entirely on my own. But I learned a lot and then the site launched successfully. The following is a screenshot of the site displaying some video game quotes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l6xW-M1j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ohiyazhkjtzzgtz0zq3h.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l6xW-M1j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ohiyazhkjtzzgtz0zq3h.jpg" alt="old Game Quotes website" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I started rebuilding the website with a newer version of Laravel, because the framework made some significant changes, so upgrading was not that easy. Around 2016 I started working on Game Quotes 2 and invested a lot of time into it. Unfortunately, I was &lt;em&gt;too motivated&lt;/em&gt; and wanted to build a very sophisticated new site, with some community features like friend requests, teams and chat. Video game quotes should be translatable, and I built a large admin dashboard.&lt;br&gt;
The relaunch was too sophisticated, I spent too much time with details and was just exhausted after some time. Burnout. Game Quotes had to wait, I abandoned the new version, and the old site became a pile of old technical debt. Laravel 4.2 had literally no supported packages anymore and adding new features was risky as I had no tests at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refactoring or Rewrite from scratch?
&lt;/h2&gt;

&lt;p&gt;At the end of 2020 I thought a lot about the future of the project. The old version was unmaintainable, difficult to deploy, and I wasn't motivated to work on it any further. I asked myself a very essential question: does it make sense to refactor the application and slowly move to a newer version of Laravel, or would it make more sense to just start from scratch and port the existing content to the new system?&lt;/p&gt;

&lt;p&gt;Refactoring is a long and sometimes painful process. If you are not careful enough, you end in a deep hole of code chaos. I published an article with &lt;a href="https://blog.kovah.de/en/2021/7-things-to-consider-before-refactoring-code/"&gt;tips for refactoring code&lt;/a&gt; last week. Reading over the list of tips I collected, it became clear pretty fast that it would be too much work to refactor Game Quotes and move to a fresh foundation.&lt;/p&gt;

&lt;p&gt;Rewriting, on the other side, was a lot of work too, because you would have to build everything from scratch and then port the old content. However, I am used to the latest versions of Laravel as well as the frontend stack with Tailwind CSS. I am able to build a custom application in a matter of days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rewriting Game Quotes from scratch
&lt;/h2&gt;

&lt;p&gt;In February 2021 I made the final decision to throw the old Game Quotes into the trash and start from scratch. I set up Laravel 8 based on my &lt;a href="https://github.com/Kovah/Docker-Stack"&gt;Docker stack&lt;/a&gt; together with a minimal Tailwind configuration for the frontend, and then started porting the content.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first step was to rebuild the current structure of the database together with all models. The current structure was absolutely fine, as it would allow me to migrate the database without any changes in the database fields.&lt;/li&gt;
&lt;li&gt;Recreating the models was one of the largest tasks. Laravel 8 makes a lot of things easier than the old Laravel 4. Relations and all database fields were defined in a couple of hours.&lt;/li&gt;
&lt;li&gt;After importing the old database, I was able to access the old content without larger issues. I had to change some field types. In just a few hours of work I was able to use the old content within the new foundation.&lt;/li&gt;
&lt;li&gt;Fast-forward a few days of work: I crafted the base layout, and most of the existing pages with a first version of the new design.&lt;/li&gt;
&lt;li&gt;Next step: admin panel and moderation stuff. This was the largest task, because the old version only had a very minimal, custom-built admin dashboard that allowed me to edit all content. I went with &lt;a href="https://voyager.devdojo.com/"&gt;Voyager&lt;/a&gt;, because it had all features I needed, and I didn't want to pay a ton of money for a project that has no income yet.&lt;/li&gt;
&lt;li&gt;Translations are actually handled via a middleware. I moved from a locale based on the subdomain to a path-based approach. Currently, Game Quotes is available in German and English.&lt;/li&gt;
&lt;li&gt;Another big task was to migrate the old URL structure to the new one, which is based on the standard Laravel resource controllers. Quotes, for example, are located under &lt;code&gt;game-quotes.com/en/quote/quote-id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Last but not least: &lt;strong&gt;tests&lt;/strong&gt;. I wrote a lot of tests for numerous parts of the site. Not everything is tested, but the most important parts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After two weeks of work, I had the first fully working version of Game Quotes based on the completely new foundation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O3I_Rmu2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lb6uduvztt41m13hn7lt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O3I_Rmu2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lb6uduvztt41m13hn7lt.jpg" alt="Game Quotes from 2021" width="800" height="629"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The new Game-Quotes.com
&lt;/h2&gt;

&lt;p&gt;Just three weeks after the initial decision I published the new version of Game Quotes. I had done additional stuff to clean up the content, implement proper Schema.org markup and optimize the design. I got a lot of positive feedback and the first submissions for quotes from random strangers on the internet. It was the right decision to rewrite Game Quotes from scratch. I had quickly moved from a pile of tech debt to a modern application that is also more performant.&lt;br&gt;
A week later I released a new feature that was planned for years already: marking quotes as spoilers or NSFW content. The feature is already available and can be used.&lt;/p&gt;

&lt;p&gt;In the next weeks I will add more features and finally enable public profiles, so anyone can register. Stay tuned.&lt;/p&gt;




&lt;p&gt;This article was first published on &lt;a href="https://blog.kovah.de/en/2021/game-quotes-refactoring-story/"&gt;my blog&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>gaming</category>
      <category>codequality</category>
      <category>webdev</category>
    </item>
    <item>
      <title>7 things to consider before starting to refactor Code</title>
      <dc:creator>Kevin Woblick</dc:creator>
      <pubDate>Sat, 03 Apr 2021 11:32:15 +0000</pubDate>
      <link>https://dev.to/kovah/7-things-to-consider-before-starting-to-refactor-code-1h90</link>
      <guid>https://dev.to/kovah/7-things-to-consider-before-starting-to-refactor-code-1h90</guid>
      <description>&lt;p&gt;Refactoring code isn't easy. It is probably one of the hardest things to do in programming. Here are 7 things to consider before starting to refactor any code.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Get to know your Code
&lt;/h2&gt;

&lt;p&gt;Take a look at the whole code first and find out its strengths and weaknesses. Make notes about what problems are actually solved very well and where the original writers took shortcuts or hacks to make the thing work and which solutions are actually holding your further development back. Only if you get the whole picture you are able to make reasonable decisions where to start first. You probably want to fix the hacky solutions first before doing "cosmetic" changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. If it's not broken, don't fix it.
&lt;/h2&gt;

&lt;p&gt;Code that works properly without decreasing performance or impacting the application in a highly negative way can probably left as it is. Hacks mentioned in point 1 can result in a poor performance or bugs nobody understands, so fix those. If a solution just looks not nice to you but works, leave it as it is for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Leave out personal preferences
&lt;/h2&gt;

&lt;p&gt;Personal preferences are not a reason to refactor code! Just because you don't like the structure it doesn't mean it is wrong and needs to be changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. You don't need that new fancy library / framework!
&lt;/h2&gt;

&lt;p&gt;New technology isn't a reason to refactor code, too. Just because there's something new it doesn't mean it is better. A new technology should come with a highly positive impact on the application to be justified. Don't ride the hype train. also consider that new things may not be as battle proven as they should be to be used in production environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Resist the urge to &lt;em&gt;just&lt;/em&gt; rewrite everything.
&lt;/h2&gt;

&lt;p&gt;Existing code, especially if it was shipped to production, may contain sensible workarounds and fixed bugs you don't know. Rewriting everything from scratch can lead to serious issues you can't even imagine yet. Only fix something if it's really broken or results in a poor performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Take small steps
&lt;/h2&gt;

&lt;p&gt;Many small and easy changes will push the refactoring forward faster than doing one big change. Make incremental changes to the codebase. One big change might consume too much work and time to be completed properly, so you quickly loose the motivation to continue working on it. Collect the "low-hanging fruits" first.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Make sur all tests pass
&lt;/h2&gt;

&lt;p&gt;Before committing and shipping any refactored code, make sure &lt;strong&gt;all&lt;/strong&gt; tests pass. Resist the urge to find workarounds and cheat on tests. If tests fail, find out why. Old tests might have some clues on obscure bugs.&lt;/p&gt;




&lt;p&gt;This article was first published on &lt;a href="https://blog.kovah.de/en/2021/7-things-to-consider-before-refactoring-code/"&gt;my Blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>refactoring</category>
      <category>codenewbie</category>
      <category>codequality</category>
    </item>
    <item>
      <title>Using Laravel Scout with global query scopes</title>
      <dc:creator>Kevin Woblick</dc:creator>
      <pubDate>Thu, 11 Feb 2021 08:53:08 +0000</pubDate>
      <link>https://dev.to/kovah/using-laravel-scout-with-global-query-scopes-467c</link>
      <guid>https://dev.to/kovah/using-laravel-scout-with-global-query-scopes-467c</guid>
      <description>&lt;p&gt;I recently implemented Laravel Scout, Laravel's own full text search package, into one of my projects. One of the models which Scout was enabled for had &lt;a href="https://laravel.com/docs/8.x/eloquent#query-scopes"&gt;global query scopes&lt;/a&gt; implemented. They are used to defining specific query constraints for all queries run on this model. You must be careful with defining them, as they affect everything done with the model.&lt;/p&gt;

&lt;p&gt;Let's say, you have an Article model, which has some database fields that should be indexed, and a language field which is used to query articles per language, because our site is multilingual.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nv"&gt;$fillable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'body'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'language'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A global query scope is defined as a separate class and then added to the model. The following scope will add a &lt;code&gt;where()&lt;/code&gt; clause to the query, in which the language field is specified, and the current application locale is used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LanguageScope&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Scope&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Builder&lt;/span&gt; &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Model&lt;/span&gt; &lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'.language'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getLocale&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make the scope usable for every model, I decided to make it depend on the Model class itself, instead of a specific model. To prevent any issues with relations in other queries, the scope uses a table prefix, which we can get by using &lt;code&gt;$model-&amp;gt;getTable()&lt;/code&gt;. In our case with the Article model, the where() clause would look like this: &lt;code&gt;$query-&amp;gt;where('article.language', 'en')&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To enable this global scope, add it to the models &lt;code&gt;booted()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nv"&gt;$fillable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'body'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'language'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;booted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;addGlobalScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LanguageScope&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run a query like &lt;code&gt;Article::all()&lt;/code&gt; now, you will get all articles which match the current language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Importing models with Scout
&lt;/h2&gt;

&lt;p&gt;After installing Scout and preparing the model for it, you must import all models into the index to be able to search them. While testing the import of my models, I noticed that only a fraction of models has been imported. Actually, global scopes are the reason. Because they affect &lt;strong&gt;all&lt;/strong&gt; queries made to the model, Scout is not able to retrieve all entries for the import.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disabling global query scopes for the Scout import
&lt;/h3&gt;

&lt;p&gt;The Searchable trait which is added to models to enable Scout, ships with a function called &lt;code&gt;makeAllSearchableUsing()&lt;/code&gt;. This method is used to get all model entries for the import. By default, the method is protected and can't be accessed in any way while running the import. However, you can override it inside your model. If you do this, you can add the &lt;code&gt;withoutGlobalScopes()&lt;/code&gt; method to the query to disable all global scopes for the import query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Searchable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;makeAllSearchableUsing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Builder&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Builder&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withoutGlobalScopes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After adding this to the model, I was able to import all entries, and not just a fraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Global scopes while searching with Scout
&lt;/h2&gt;

&lt;p&gt;Searching for entries with Scout is a bit special, because you can't apply any regular database queries to the search by default and also don't prepend any constraints to the search. However, Scout allows us to add simple &lt;code&gt;where()&lt;/code&gt; clauses to the search. See &lt;a href="https://laravel.com/docs/8.x/scout#searching"&gt;the documentation of Scout&lt;/a&gt; for reference.&lt;br&gt;
While testing the search, I was wondering why I got no results for my model despite having entered a string correctly. Of course my global scope was applied to the search query. After digging through the code for a while, I found a way to access the underlying database query builder.&lt;/p&gt;
&lt;h3&gt;
  
  
  Disable global scopes while searching
&lt;/h3&gt;

&lt;p&gt;It is indeed possible to disable global scopes while running a search with scout. Let's say you want to search an article for something.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$searchQuery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Right now our global scope would affect the search and only return entries for like English, if you are on the English version of your site. If you want to search across all languages, modify the code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$searchQuery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withoutGlobalScopes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query method allows us to modify the query builder and disable all global scopes while searching for our articles.&lt;/p&gt;




&lt;p&gt;This article was first published on &lt;a href="https://blog.kovah.de/en/2021/laravel-scout-with-global-scopes/"&gt;my blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>php</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>laravel</category>
    </item>
    <item>
      <title>LinkAce - Your self-hosted bookmark archive</title>
      <dc:creator>Kevin Woblick</dc:creator>
      <pubDate>Wed, 16 Dec 2020 20:38:16 +0000</pubDate>
      <link>https://dev.to/kovah/linkace-your-self-hosted-bookmark-archive-2n9e</link>
      <guid>https://dev.to/kovah/linkace-your-self-hosted-bookmark-archive-2n9e</guid>
      <description>&lt;p&gt;About one and a half year ago I was deeply dissatisfied with my personal bookmarks collections. The links saved in my browser piled up to a bloated mess of outdated websites with only little structure. I tested Shaarli for a few weeks but it never "clicked", so I turned it off. After some thinking, I decided to build my own bookmarks archive. And today is the big day: the release of LinkAce 1.0.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3ot6sgsibjcgpwgal7ao.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3ot6sgsibjcgpwgal7ao.png" alt="LinkAce preview"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is LinkAce?
&lt;/h2&gt;

&lt;p&gt;LinkAce is a bookmark archive. It wasn't built to manage the bookmarks of your browser but has its very own philosophy. My browser bookmarks contain only websites I regularly use and access. LinkAce, in contrast, is meant to provide a &lt;em&gt;long-term&lt;/em&gt; archive of links to websites, media files or anything else which has a valid URL. I store interesting articles, neat web tools or libraries I &lt;em&gt;may&lt;/em&gt; use sometime in the future. All saved links are added together with proper tags and some are added to lists which contain links across multiple smaller topics. Every now and then I come back to search for something specific I know I saved in the past.&lt;/p&gt;

&lt;p&gt;This workflow allows me to keep my browser bookmarks clean, but also store interesting stuff in a way so they can be found easily when I need them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature Spotlight
&lt;/h3&gt;

&lt;p&gt;Because I created LinkAce from scratch, I was able to include many features not present in other bookmark managers, that are really useful.&lt;/p&gt;

&lt;p&gt;The most important feature is the &lt;strong&gt;automated backup&lt;/strong&gt; of all sites and regular checks if the websites are still available. Instead of making a backup on your own server, LinkAce relies on the Internet Archive, which already saved billions of websites across the internet. After you saved a link in LinkAce, it will automatically tell the Internet Archive to do a backup of this site.&lt;br&gt;&lt;br&gt;
Links are also checked on a regular base. LinkAce tries to connect to the website. If something went wrong, you will be notified, so you can check what happened to the site.&lt;/p&gt;

&lt;p&gt;If you want to share your link collection with the world, you can enable the &lt;strong&gt;guest mode&lt;/strong&gt;. All links, tags and lists that are not marked as private will be accessible by guests visiting your site.&lt;/p&gt;

&lt;p&gt;Links can be added with the help of a &lt;strong&gt;browser bookmarklet&lt;/strong&gt;. It sits in your bookmarks bar and with a click, the current website will be added to LinkAce.&lt;/p&gt;

&lt;p&gt;LinkAce also features a &lt;strong&gt;full API&lt;/strong&gt;, so you can connect other tools to LinkAce if you want. The API is accessible with a simple API key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try LinkAce today
&lt;/h2&gt;

&lt;p&gt;Of course, the application is completely free and the whole code is available on &lt;a href="https://github.com/Kovah/LinkAce" rel="noopener noreferrer"&gt;Github&lt;/a&gt;. Before you install LinkAce on your own server, you can fiddle around with the &lt;a href="https://demo.linkace.org" rel="noopener noreferrer"&gt;Demo&lt;/a&gt;. If you like the app, you can install it by using Docker, or like many other PHP apps on your web server.&lt;/p&gt;

&lt;p&gt;If you don’t want to host LinkAce on your own, sign up for to be notified when our hosting service launches. I currently have no schedule for the release but I’m working on it.&lt;/p&gt;

&lt;p&gt;If you need help, you can talk to other users in the &lt;a href="https://community.linkace.org/" rel="noopener noreferrer"&gt;Community forum&lt;/a&gt;. I offer prioritised support if you become a &lt;a href="https://www.patreon.com/Kovah" rel="noopener noreferrer"&gt;Patreon&lt;/a&gt; or &lt;a href="https://github.com/sponsors/Kovah" rel="noopener noreferrer"&gt;Github Sponsor&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was first published on &lt;a href="https://blog.kovah.de/en/2020/linkace-1-release/" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>news</category>
      <category>php</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Transactional Emails for Developers: A comparison of 5 services</title>
      <dc:creator>Kevin Woblick</dc:creator>
      <pubDate>Tue, 20 Oct 2020 20:05:10 +0000</pubDate>
      <link>https://dev.to/kovah/transactional-emails-for-developers-a-comparison-of-5-services-3ok8</link>
      <guid>https://dev.to/kovah/transactional-emails-for-developers-a-comparison-of-5-services-3ok8</guid>
      <description>&lt;p&gt;Email support is one of the most basic things almost any webservice need. It should work, be reliable and doesn't cost a ton of money. In this comparison I take a look at the 5 most popular providers and what services they offer.&lt;/p&gt;

&lt;p&gt;Please notice that this guide focuses on transactional and regular marketing emails you send directly from your service, like password reset emails. Providers like Mailchimp which are specialized on email campaigns and advanced marketing are out of scope right now. As you are more likely to just get started with sending emails, I focus on the smallest plans too. Besides a free plan I share details about the first pricing tier with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transactional Email Providers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Mailgun&lt;/li&gt;
&lt;li&gt;Amazon SES&lt;/li&gt;
&lt;li&gt;Sendgrid&lt;/li&gt;
&lt;li&gt;Postmark&lt;/li&gt;
&lt;li&gt;Sparkpost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FPvY6qCF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/ovcpdin8szgm0lld8hiz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FPvY6qCF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/ovcpdin8szgm0lld8hiz.png" alt="Alt Text" width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Mailgun
&lt;/h3&gt;

&lt;p&gt;I am using &lt;a href="https://www.mailgun.com/"&gt;Mailgun&lt;/a&gt; for years now and still use it for many services, as it simply works and provides all needed tools to send all the emails I need. If you are dealing with EU citizens, you should set up your domains in the EU zone, which is fully GDPR compliant.&lt;/p&gt;

&lt;p&gt;Mailgun provides a basic transactional email service. There are no fancy marketing features present, you send just your emails through them. In their trial plan you get 5000 emails per month for the first 3 months. After that you pay for what you send.&lt;/p&gt;

&lt;h4&gt;
  
  
  Technical Details
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;SMTP sending: yes&lt;/li&gt;
&lt;li&gt;API available: yes&lt;/li&gt;
&lt;li&gt;Laravel/PHP support: yes&lt;/li&gt;
&lt;li&gt;Other programming languages: Java, Kotlin, Node, Python, Go, Ruby, C#, Perl, Curl&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Pricing
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Plan: Flex&lt;/th&gt;
&lt;th&gt;Plan: Foundation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Included emails&lt;/td&gt;
&lt;td&gt;5000&lt;/td&gt;
&lt;td&gt;50000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per month&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1000 emails&lt;/td&gt;
&lt;td&gt;$0.8&lt;/td&gt;
&lt;td&gt;$0.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1 email&lt;/td&gt;
&lt;td&gt;$0.0008&lt;/td&gt;
&lt;td&gt;$0.0007&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1000 emails after limit&lt;/td&gt;
&lt;td&gt;$0.80&lt;/td&gt;
&lt;td&gt;$0.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1 email after limit&lt;/td&gt;
&lt;td&gt;$0.0008&lt;/td&gt;
&lt;td&gt;$0.0008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notable restrictions&lt;/td&gt;
&lt;td&gt;No inbound routing, no dedicated IPs, restricted support&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Amazon SES (Simple Email Service)
&lt;/h3&gt;

&lt;p&gt;If you just need email sending and incoming mails, without &lt;em&gt;any&lt;/em&gt; other features like statistics or email tracking, Amazon SES might be the best solution for you. Instead of offering plans, the &lt;a href=""&gt;Amazon Simple Email Service&lt;/a&gt; is entirely based on the pay per use model.&lt;/p&gt;

&lt;p&gt;SES is probably the most valuable service to use, if you are already using AWS services or plan to use them. If you send any emails from Amazon EC2 servers, you get a whopping 62000 emails per month for free.&lt;br&gt;&lt;br&gt;
Attention: while other email providers do not charge for outgoing traffic, Amazon SES does! Based on the region, you have to calculate the traffic for your outgoing emails.&lt;/p&gt;

&lt;p&gt;Receiving emails also costs extra, but you get 1000 incoming emails for free each month.&lt;/p&gt;

&lt;p&gt;Last but not least: there are basically no detailed logs of what emails you send and receive. If you need deep insights, you would have to configure additional logging with other Amazon products.&lt;/p&gt;

&lt;h4&gt;
  
  
  Technical Details
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;SMTP sending: yes&lt;/li&gt;
&lt;li&gt;API available: yes&lt;/li&gt;
&lt;li&gt;Laravel/PHP support: yes&lt;/li&gt;
&lt;li&gt;Other programming languages: via Amazon SDK (C++, Go, Java, Javascript, .NET, Python, Ruby)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Pricing
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Pay per use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Price per month&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1000 emails&lt;/td&gt;
&lt;td&gt;$0.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1 email&lt;/td&gt;
&lt;td&gt;$0.0001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per outgoing GB&lt;/td&gt;
&lt;td&gt;$0.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1000 incoming emails&lt;/td&gt;
&lt;td&gt;$0.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1 incoming email&lt;/td&gt;
&lt;td&gt;$0.0001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Sendgrid
&lt;/h3&gt;

&lt;p&gt;As a part of the Twilio universe, &lt;a href="https://sendgrid.com/"&gt;Sendgrid&lt;/a&gt; offers transactional email support via their Email API service. In their free plan you get 100 emails per day free of charge, forever. The first paid plan starts after reaching the free limit, and offers up to 40 thousand emails per month.&lt;/p&gt;

&lt;p&gt;If you decide to use advanced marketing features, their Marketing Email service got you covered. It allows you to build campaigns with a visual builder.&lt;/p&gt;

&lt;h4&gt;
  
  
  Technical Details
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;SMTP sending: yes&lt;/li&gt;
&lt;li&gt;API available: yes&lt;/li&gt;
&lt;li&gt;Laravel/PHP support: yes (via SMTP)&lt;/li&gt;
&lt;li&gt;Other programming languages: Java, Node, Python, Go, Ruby, C#&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Pricing
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Plan: Free&lt;/th&gt;
&lt;th&gt;Plan: Essentials&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Included emails&lt;/td&gt;
&lt;td&gt;3000&lt;/td&gt;
&lt;td&gt;40000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per month&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$14.95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1000 emails&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.374&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1 email&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.000374&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1000 emails after limit&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1 email after limit&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notable restrictions&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Postmark
&lt;/h3&gt;

&lt;p&gt;Similar to Mailgun, &lt;a href="https://postmarkapp.com/"&gt;Postmark&lt;/a&gt; offers basic transactional emails without all the marketing features or campaigns, however it does have an email template builder available. They offer analytics for their emails and advertise a secure service which is fully GDPR compliant.&lt;/p&gt;

&lt;h4&gt;
  
  
  Technical Details
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;SMTP sending: yes&lt;/li&gt;
&lt;li&gt;API available: yes&lt;/li&gt;
&lt;li&gt;Laravel/PHP support: yes&lt;/li&gt;
&lt;li&gt;Other programming languages: Ruby, .NET, Java, Node.js&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Pricing
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Plan: Free&lt;/th&gt;
&lt;th&gt;Plan: 10k Emails&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Included emails&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per month&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1000 emails&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1 email&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1000 emails after limit&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$1.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1 email after limit&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.00125&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notable restrictions&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Sparkpost
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.sparkpost.com/"&gt;Sparkpost&lt;/a&gt; aligns with Mailgun and Postmark. Their simple transactional email service offers all features you need to get started with sending emails to your users.&lt;/p&gt;

&lt;p&gt;It's notable, that overuse of the email sending limits more than double the price per email. Make sure to select the right plan for you.&lt;/p&gt;

&lt;h4&gt;
  
  
  Technical Details
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;SMTP sending: yes&lt;/li&gt;
&lt;li&gt;API available: yes&lt;/li&gt;
&lt;li&gt;Laravel/PHP support: yes (via SMTP)&lt;/li&gt;
&lt;li&gt;Other programming languages: Curl, Node.js, Python, Go, Java, Elixir, C#, Ruby&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Pricing
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Plan: Test&lt;/th&gt;
&lt;th&gt;Plan: 50k&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Included emails&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;50000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per month&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1000 emails&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1 email&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.0004&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1000 emails after limit&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1 email after limit&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notable restrictions&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Providers I do not personally recommend
&lt;/h2&gt;

&lt;p&gt;Sadly, there is a provider I personally do not recommend, and it's Mailjet. I had to work with this service while doing a client project, and it was a pain to work with the API, the email templates and the support took ages to respond to any tickets despite having a paid high volume plan for the client. Not to mention the multiple account issues with "lost" passwords. Simply stay away from this service.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(If anyone from Mailjet reads this: work on your customer support.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion for Transactional Email Provider
&lt;/h2&gt;

&lt;p&gt;It's not that easy to choose a service when it comes to transactional emails, or later on marketing emails including template builders and campaigns. It starts with guessing the correct volume and calculate your costs based on that.&lt;/p&gt;

&lt;p&gt;When it comes to money, you can go with the following list as a reference, which has all the above providers sorted by their price per 1000 emails for volumes up to 30k to 50k emails per month. Keep in mind, that Sendgrid, Postmark and Sparkpost charge a fixed amount per month regardless of how many emails you send.&lt;/p&gt;

&lt;p&gt;To calculate the costs for Amazon SES which charges for traffic, I assumed an average email size of 250kb, which leads to additional costs of $0.00003 per email. As this is such a small amount of money, I left it out to keep the numbers small. This probably won't pile up to huge costs anyway if you only send 10k emails per month.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Price per 1000 emails&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.&lt;/td&gt;
&lt;td&gt;Amazon SES&lt;/td&gt;
&lt;td&gt;$0.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.&lt;/td&gt;
&lt;td&gt;Sendgrid&lt;/td&gt;
&lt;td&gt;$0.374&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.&lt;/td&gt;
&lt;td&gt;Sparkpost&lt;/td&gt;
&lt;td&gt;$0.40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4.&lt;/td&gt;
&lt;td&gt;Mailgun&lt;/td&gt;
&lt;td&gt;$0.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5.&lt;/td&gt;
&lt;td&gt;Postmark&lt;/td&gt;
&lt;td&gt;$1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you liked this post, please share it with your friends and followers.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was first published on &lt;a href="https://blog.kovah.de/en/2020/transactional-email-comparison/"&gt;my blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>email</category>
      <category>webdev</category>
      <category>review</category>
    </item>
    <item>
      <title>How I built a web tool for gamers</title>
      <dc:creator>Kevin Woblick</dc:creator>
      <pubDate>Tue, 13 Oct 2020 14:16:53 +0000</pubDate>
      <link>https://dev.to/kovah/how-i-built-a-web-tool-for-gamers-3hp</link>
      <guid>https://dev.to/kovah/how-i-built-a-web-tool-for-gamers-3hp</guid>
      <description>&lt;p&gt;As a passionate gamer I play a lot of different games which involve trading, like No Man's Sky. I often end up with a bunch of papers filled with prices for items the merchants offer, so I could find the best trades between them. I stepped up the trading game by using Excel, filled sheets with the numbers. But it felt just way too difficult.&lt;/p&gt;

&lt;p&gt;Fortunately, I am also a web engineer who builds stuff when he needs something to work with. I did that with a lot of tools, including &lt;a href="https://taboo.kovah.de/" rel="noopener noreferrer"&gt;my web-based Taboo game&lt;/a&gt; and &lt;a href="https://www.linkace.org/" rel="noopener noreferrer"&gt;LinkAce&lt;/a&gt;, my personal bookmarks archive. As I wanted to learn React for quite some time I thought that would be a perfect opportunity.&lt;/p&gt;

&lt;p&gt;Please welcome the &lt;a href="https://tradefinder.kovah.de/" rel="noopener noreferrer"&gt;&lt;strong&gt;Tradefinder&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea behind the Tradefinder
&lt;/h2&gt;

&lt;p&gt;The basic idea behind this tool is to help someone find profitable trades. In my case those trades happen between space stations in No Man's Sky, or cities in Windward. Those video games offer a full-blown trading system that include a ton of different merchants, and a dynamic supply and demand system. Prices are different for all those merchants, they buy or sell different amounts of the goods.&lt;br&gt;&lt;br&gt;
I thought the best way to deal with this system and make trading as efficient as possible, is to track the goods and prices for all merchants and then find matching buy-sell constellations. It started with a bunch of papers filled with the data, then I used Excel to change prices faster and without wasting paper. It was a tedious task, I noticed how I entered the same data again and again, and I finally lost the interest in trading because it became too complicated. I knew that a tool built specifically for this task was needed. So I built one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The development process
&lt;/h2&gt;

&lt;p&gt;The tool needed to have a screen where you enter your merchants and add the items they buy or sell, including available or wanted amounts with the corresponding prices. Then the tool should find and calculate the possible trades. To make this as fast as possible, a single page application (SPA) that works directly in the users' browser sounded just right for this.&lt;br&gt;&lt;br&gt;
I had worked with Vue in the past, but in my recent job search I noticed that positions for Vue were very rare, but the amount of React positions were astonishing. React was on my learning to-do list for quite some time now, but I never felt the urge to learn it. Now was the time to dive into it.&lt;/p&gt;

&lt;h3&gt;
  
  
  React with Redux...
&lt;/h3&gt;

&lt;p&gt;From my work with Vue I knew some basic requirements, like to use some sort of state to handle all the data. In case of React, I decided to go with Redux because it is the most popular I know of. It took quite some time to get the base of the app ready for further development, because the concept of JSX elements provided by classes or functions is different, than I was used to it with Vue. Especially the integration with Redux, passing data around and working with the state was hard to learn. To be honest, I find the VueX (Vue's state library) documentation far better than the documentation of Redux. Especially the examples given in the docs seem very inconsistent, using different file and folder structures and introducing advanced features while learning the basics. In the end, I managed to get the state working and got a tool to add and edit both merchants and items.&lt;/p&gt;

&lt;h2&gt;
  
  
  More details about the Tradefinder
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyqpsddtka121gd8qsmn7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyqpsddtka121gd8qsmn7.png" alt="Preview screenshot of the Tradefinder tool"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today I released the Tradefinder. This first version has all core features that I need and supports the import and export of data. The foundation is solid and ready to get more features in the future.&lt;br&gt;&lt;br&gt;
Here's a list of what the tool is currently capable of.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add, edit and delete locations as well as items.&lt;/li&gt;
&lt;li&gt;Item handling for all locations: amounts and prices for both buying and selling.&lt;/li&gt;
&lt;li&gt;Automated trade finding and calculation of the amounts you can buy/sell, including profits.&lt;/li&gt;
&lt;li&gt;All data is stored in your browser via Localstorage. No data is sent to any servers.&lt;/li&gt;
&lt;li&gt;Data can be exported and imported.&lt;/li&gt;
&lt;li&gt;The number formatting can be toggled between the US and EU standards.&lt;/li&gt;
&lt;li&gt;The tool has a built-in migration system to update the data once a newer version is released.&lt;/li&gt;
&lt;li&gt;No analytics, no tracking, no social sign ups.&lt;/li&gt;
&lt;li&gt;Reset or completely wipe all data to make a fresh start.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool is completely open source, the repository can be found on &lt;a href="https://github.com/Kovah/Tradefinder" rel="noopener noreferrer"&gt;Github&lt;/a&gt;. I already created some issues with ideas for the future:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keyboard shortcuts for some actions.&lt;/li&gt;
&lt;li&gt;Presets that can be loaded for different games like No Man's Sky.&lt;/li&gt;
&lt;li&gt;Option to load different "saves", to be able to jump between games.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to take the Tradefinder for a test ride and share your ideas in the repository, or via Twitter or &lt;a href="https://news.ycombinator.com/item?id=24753595" rel="noopener noreferrer"&gt;Hacker News&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was first published on &lt;a href="https://blog.kovah.de/en/2020/tradefinder/" rel="noopener noreferrer"&gt;my Blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gaming</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>.nvmrc or .node-version - Which one do you prefer?</title>
      <dc:creator>Kevin Woblick</dc:creator>
      <pubDate>Tue, 18 Aug 2020 18:46:14 +0000</pubDate>
      <link>https://dev.to/kovah/nvmrc-or-node-version-which-one-do-you-prefer-300n</link>
      <guid>https://dev.to/kovah/nvmrc-or-node-version-which-one-do-you-prefer-300n</guid>
      <description>&lt;p&gt;There are many different version managers out there for always getting the right Node version ready for a project. This can be incredibly helpful when working with older projects which do not support a new Node version.&lt;/p&gt;

&lt;p&gt;The most used managers are &lt;a href="https://github.com/nvm-sh/nvm"&gt;nvm&lt;/a&gt;, &lt;a href="https://github.com/tj/n"&gt;n&lt;/a&gt;, or &lt;a href="https://github.com/nodenv/nodenv"&gt;nodenv&lt;/a&gt;. All of them let you specify a specific Node version, but how do you know which one to use for a project? Stage enter: &lt;code&gt;.nvmrc&lt;/code&gt; and &lt;code&gt;.node-version&lt;/code&gt;. Both contain a Node version, but are not equally supported by the different tools.&lt;/p&gt;

&lt;p&gt;How do &lt;strong&gt;you&lt;/strong&gt; define Node versions for your projects? Do you prefer using NVM with &lt;code&gt;.nvmrc&lt;/code&gt; or nodenv with &lt;code&gt;.node-version&lt;/code&gt;? Or do you even use a completely other approach or tool?&lt;/p&gt;

</description>
      <category>node</category>
      <category>npm</category>
      <category>javascript</category>
    </item>
    <item>
      <title>DevLorem: Rewritten from the ground up in Go</title>
      <dc:creator>Kevin Woblick</dc:creator>
      <pubDate>Fri, 24 Jul 2020 08:53:02 +0000</pubDate>
      <link>https://dev.to/kovah/devlorem-rewritten-from-the-ground-up-in-go-4laf</link>
      <guid>https://dev.to/kovah/devlorem-rewritten-from-the-ground-up-in-go-4laf</guid>
      <description>&lt;p&gt;About 3 weeks ago my good old &lt;a href="https://github.com/Kovah/DevLorem"&gt;DevLorem&lt;/a&gt; project was a simple PHP website that generated Lorem Ipsum text made from movie quotes of famous actors and actresses. I decided to rewrite the whole thing with Go to dive into the language and its features.&lt;/p&gt;

&lt;p&gt;Yesterday I released version 2, the complete rewrite with many more features. A demo of the website and API is available on &lt;a href="https://devlorem.kovah.de/"&gt;devlorem.kovah.de&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Go to develop a feature-rich application
&lt;/h2&gt;

&lt;p&gt;The main idea was to have one single binary that could be used as a web server handling the website, as well as a CLI tool to generate quotes right in the terminal. The first steps of getting a simple website up and running with Go were fairly straight forward. I used the &lt;a href="https://github.com/gorilla/mux"&gt;gorilla/mux&lt;/a&gt; package, which provides a set of tools to run a HTTP web server. The examples have given me a good direction to build both the website itself, and the API.&lt;/p&gt;

&lt;p&gt;Adding CLI commands were a bigger task as I also had to implement the generation in the terminal. The &lt;a href="https://github.com/spf13/cobra"&gt;Cobra&lt;/a&gt; package, also used by famous projects like Hugo, has given me the perfect framework to build all command line options.&lt;/p&gt;

&lt;h2&gt;
  
  
  One binary for 7 platforms, and a Docker image
&lt;/h2&gt;

&lt;p&gt;One of the most important features after the rewrite: DevLorem is now available as a single binary for 7 different platforms, including Linux, macOS and Windows. With the help of the &lt;a href="https://github.com/GeertJohan/go.rice"&gt;go.rice&lt;/a&gt; package, I was able to bundle all assets, from the quote sources to the static CSS and JavaScript for the website, into one binary.&lt;/p&gt;

&lt;p&gt;Besides of that, I built a Docker image that is now available as &lt;code&gt;kovah/devlorem&lt;/code&gt; on the Docker Hub. It provides all features of the tool itself.&lt;/p&gt;

&lt;p&gt;You can download the executables from the &lt;a href="https://github.com/Kovah/DevLorem/releases/tag/v2.0.0"&gt;latest release&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first steps with Go
&lt;/h2&gt;

&lt;p&gt;Go itself is an amazing language. After writing the dynamically typed PHP and Python, using a strict type system was a great step forward. I already worked with static types in Rust, which was more a pain than a help because I spent more time fighting with type abstractions than writing productive code. Go has a clear and easy to understand type system and I was able to work with it right from the start. If you want to learn Go and come from another programming language, your best way to start is the &lt;a href="https://gobyexample.com/"&gt;Go by Example&lt;/a&gt; website. It has all the important aspects of Go packed into simple examples.&lt;/p&gt;

&lt;p&gt;I will probably use Go more in the feature, and update the CorporateLorem tool too.&lt;/p&gt;

</description>
      <category>go</category>
      <category>lorem</category>
      <category>news</category>
    </item>
  </channel>
</rss>
