feat: use goldmark and minify

main
sudo pacman -Syu 2022-07-11 11:26:24 +07:00 committed by sudo pacman -Syu
parent 3b34119da5
commit b811761c00
11 changed files with 163 additions and 510 deletions

View File

@ -1,20 +0,0 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap"
rel="stylesheet"
/>
<style>
body {
font-family: "Recursive", sans-serif;
font-variation-settings: "MONO" 0, "CASL" 1;
}
code {
font-family: "Recursive", monospace;
font-variation-settings: "MONO" 1, "CASL" 1;
}
</style>
<a href="index">Back to index</a>

View File

@ -1,138 +1,6 @@
<!DOCTYPE html>
<html>
<head>
<title>2022-06-08-backup.md</title>
<meta name="GENERATOR" content="github.com/gomarkdown/markdown markdown processor for Go">
<meta charset="utf-8">
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap"
rel="stylesheet"
/>
<style>
body {
font-family: "Recursive", sans-serif;
font-variation-settings: "MONO" 0, "CASL" 1;
}
code {
font-family: "Recursive", monospace;
font-variation-settings: "MONO" 1, "CASL" 1;
}
</style>
<a href="index">Back to index</a>
</head>
<body>
<nav>
<ul>
<li><a href="#toc_0">Backup my way</a>
<ul>
<li><a href="#toc_1">How to backup</a></li>
<li><a href="#toc_2">Recovery strategy</a></li>
<li><a href="#toc_3">The end</a></li>
</ul></li>
</ul>
</nav>
<h1 id="toc_0">Backup my way</h1>
<p>First thing first, I want to list my own devices, which I have through the years:</p>
<ul>
<li>Laptop Samsung NP300E4Z-S06VN (Old laptop which I give to my mom)</li>
<li><a href="https://www.dell.com/support/home/en-vn/product-support/product/inspiron-15-3567-laptop/drivers">Laptop Dell Inspiron 15 3567</a> (My mom bought it for me when I go to college, I give it to my sister afterward)</li>
<li><a href="https://www.acer.com/ac/en/US/content/support-product/8841">Laptop Acer Nitro AN515-45</a> (Gaming laptop which I buy for gaming, of course)</li>
<li>MacBook Pro M1 2020 (My company laptop)</li>
<li>Phone Xiaomi Poco X3 NFC (Primary phone which I use daily)</li>
</ul>
<p>App/Service I use daily:</p>
<ul>
<li><a href="https://bitwarden.com/">Bitwarden</a></li>
<li><a href="https://getaegis.app/">Aegis Authenticator</a></li>
<li><a href="https://rclone.org/">Rclone</a></li>
<li><a href="https://tailscale.com/">Tailscale</a></li>
<li>GitHub / GitLab</li>
<li>Google Keep / Notion</li>
<li>Google Drive (I use 200GB plan)</li>
</ul>
<p>The purpose is that I want my data to be safe, secure, and can be easily recovered if I lost some devices;
or in the worst situation, I lost all.
Because you know, it is hard to guess what is waiting for us in the future.</p>
<p>There are 2 sections which I want to share, the first is <strong>How to backup</strong>, the second is <strong>Recover strategy</strong>.</p>
<h2 id="toc_1">How to backup</h2>
<p>Before I talk about backup, I want to talk about data.
In specifically, which data should I backup?</p>
<p>I use Arch Linux and macOS, primarily work in the terminal so I have too many dotfiles, for example, <code>~/.config/nvim/init.lua</code>.
Each time I reinstall Arch Linux (I like it a lot), I need to reconfigure all the settings, and it is time-consuming.</p>
<p>So for the DE and UI settings, I keep it as default as possible, unless it&rsquo;s getting in my way, I leave the default setting there and forget about it.
The others are dotfiles, which I write my own <a href="https://github.com/haunt98/dotfiles">dotfiles tool</a> to backup and reconfigure easily and quickly.
Also, I know that installing Arch Linux is not easy, despite I install it too many times (Like thousand times since I was in high school).
Not because it is hard, but as life goes on, the <a href="https://wiki.archlinux.org/title/installation_guide">official install guide</a> keeps getting new update and covering too many cases for my own personal use, so I write my own <a href="https://github.com/haunt98/til/blob/main/install-archlinux.md">guide</a> to quickly capture what I need to do.
I back up all my dotfiles inside my dotfiles tool in GitHub and GitLab as I trust them both.
Also as I travel the Internet, I discover <a href="https://codeberg.org/">Codeberg</a> and <a href="https://gitea.treehouse.systems/">Treehouse</a> and use them as another backup for git repo.</p>
<p>So that is my dotfiles, for my regular data, like Wallpaper or Books, Images, I use Google Drive (Actually I pay for it).
But the step: open the webpage, click the upload button and choose files seems boring and time-consuming.
So I use Rclone, it supports Google Drive, One Drive and many providers but I only use Google Drive for now.
The commands are simple:</p>
<pre><code class="language-sh"># Sync from local to remote
<!doctype html><meta charset=utf-8><link rel=preconnect href=https://fonts.googleapis.com><link rel=preconnect href=https://fonts.gstatic.com crossorigin><link href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap" rel=stylesheet><link rel=stylesheet href=styles.css><h1>Backup my way</h1><p>First thing first, I want to list my own devices, which I have through the years:<ul><li>Laptop Samsung NP300E4Z-S06VN (Old laptop which I give to my mom)<li><a href=https://www.dell.com/support/home/en-vn/product-support/product/inspiron-15-3567-laptop/drivers>Laptop Dell Inspiron 15 3567</a> (My mom bought it for me when I go to college, I give it to my sister afterward)<li><a href=https://www.acer.com/ac/en/US/content/support-product/8841>Laptop Acer Nitro AN515-45</a> (Gaming laptop which I buy for gaming, of course)<li>MacBook Pro M1 2020 (My company laptop)<li>Phone Xiaomi Poco X3 NFC (Primary phone which I use daily)</ul><p>App/Service I use daily:<ul><li><a href=https://bitwarden.com/>Bitwarden</a><li><a href=https://getaegis.app/>Aegis Authenticator</a><li><a href=https://rclone.org/>Rclone</a><li><a href=https://tailscale.com/>Tailscale</a><li>GitHub / GitLab<li>Google Keep / Notion<li>Google Drive (I use 200GB plan)</ul><p>The purpose is that I want my data to be safe, secure, and can be easily recovered if I lost some devices;<br>or in the worst situation, I lost all.<br>Because you know, it is hard to guess what is waiting for us in the future.<p>There are 2 sections which I want to share, the first is <strong>How to backup</strong>, the second is <strong>Recover strategy</strong>.<h2>How to backup</h2><p>Before I talk about backup, I want to talk about data.<br>In specifically, which data should I backup?<p>I use Arch Linux and macOS, primarily work in the terminal so I have too many dotfiles, for example, <code>~/.config/nvim/init.lua</code>.<br>Each time I reinstall Arch Linux (I like it a lot), I need to reconfigure all the settings, and it is time-consuming.<p>So for the DE and UI settings, I keep it as default as possible, unless it's getting in my way, I leave the default setting there and forget about it.<br>The others are dotfiles, which I write my own <a href=https://github.com/haunt98/dotfiles>dotfiles tool</a> to backup and reconfigure easily and quickly.<br>Also, I know that installing Arch Linux is not easy, despite I install it too many times (Like thousand times since I was in high school).<br>Not because it is hard, but as life goes on, the <a href=https://wiki.archlinux.org/title/installation_guide>official install guide</a> keeps getting new update and covering too many cases for my own personal use, so I write my own <a href=https://github.com/haunt98/til/blob/main/install-archlinux.md>guide</a> to quickly capture what I need to do.<br>I back up all my dotfiles inside my dotfiles tool in GitHub and GitLab as I trust them both.<br>Also as I travel the Internet, I discover <a href=https://codeberg.org/>Codeberg</a> and <a href=https://gitea.treehouse.systems/>Treehouse</a> and use them as another backup for git repo.<p>So that is my dotfiles, for my regular data, like Wallpaper or Books, Images, I use Google Drive (Actually I pay for it).<br>But the step: open the webpage, click the upload button and choose files seems boring and time-consuming.<br>So I use Rclone, it supports Google Drive, One Drive and many providers but I only use Google Drive for now.<br>The commands are simple:<pre><code class=language-sh># Sync from local to remote
rclone sync MyBooks remote:MyBooks -P --exclude .DS_Store
# Sync from remote to local
rclone sync remote:MyBooks MyBooks -P --exclude .DS_Store
</code></pre>
<p>Before you use Rclone to sync to Google Drive, you should read <a href="https://rclone.org/drive/">Google Drive rclone configuration</a> first.</p>
<p>The next data is my passwords and my OTPs.
These are the things which I&rsquo;m scare to lose the most.
First thing first, I enable 2-Step Verification for all of my important accounts, should use both OTP and phone method.</p>
<p>I use Bitwarden for passwords (That is a long story, coming from Google Password manager to Firefox Lockwise and then settle down with Bitwarden) and Aegis for OTPs.
The reason I choose Aegis, not Authy (I use Authy for so long but Aegis is definitely better) is because Aegis allows me to extract all the OTPs to a single file (Can be encrypted), which I use to transfer or backup easily.</p>
<p>As long as Bitwarden provides free passwords stored, I use all of its apps, extensions so that I can easily sync passwords between laptops and phones.
The thing I need to remember is the master password of Bitwarden in my head.</p>
<p>With Aegis, I export the data, then sync it to Google Drive, also store it locally in my phone.
For safety, I also store Aegis data locally on all of my laptops (Encrypted of course).</p>
<p>The main problem here is the OTP, I can not store all of my OTPs in the cloud completely.
Because if I want to access my OTPs in the cloud, I should log in, and then input my OTP, this is a circle, my friends.</p>
<h2 id="toc_2">Recovery strategy</h2>
<p>There are many strategies that I process to react as if something strange is happening to my devices.</p>
<p>If I lost my laptops, single laptop or all, do not panic as long as I have my phones.
The OTPs are in there, the passwords are in Bitwarden cloud, other data is in Google Drive so nothing is lost here.</p>
<p>If I lost my phone, but not my laptops, I use the OTPs which are stored locally in my laptops.</p>
<p>In the worst situation, I lost everything, my laptops, my phone.
The first step is to recover my SIM, then log in to Google account using the password and SMS OTP.
After that, log in to Bitwarden account using the master password and OTP from Gmail, which I open previously.</p>
<h2 id="toc_3">The end</h2>
<p>This guide will be updated regularly I promise.</p>
</body>
</html>
</code></pre><p>Before you use Rclone to sync to Google Drive, you should read <a href=https://rclone.org/drive/>Google Drive rclone configuration</a> first.<p>The next data is my passwords and my OTPs.<br>These are the things which I'm scare to lose the most.<br>First thing first, I enable 2-Step Verification for all of my important accounts, should use both OTP and phone method.<p>I use Bitwarden for passwords (That is a long story, coming from Google Password manager to Firefox Lockwise and then settle down with Bitwarden) and Aegis for OTPs.<br>The reason I choose Aegis, not Authy (I use Authy for so long but Aegis is definitely better) is because Aegis allows me to extract all the OTPs to a single file (Can be encrypted), which I use to transfer or backup easily.<p>As long as Bitwarden provides free passwords stored, I use all of its apps, extensions so that I can easily sync passwords between laptops and phones.<br>The thing I need to remember is the master password of Bitwarden in my head.<p>With Aegis, I export the data, then sync it to Google Drive, also store it locally in my phone.<br>For safety, I also store Aegis data locally on all of my laptops (Encrypted of course).<p>The main problem here is the OTP, I can not store all of my OTPs in the cloud completely.<br>Because if I want to access my OTPs in the cloud, I should log in, and then input my OTP, this is a circle, my friends.<h2>Recovery strategy</h2><p>There are many strategies that I process to react as if something strange is happening to my devices.<p>If I lost my laptops, single laptop or all, do not panic as long as I have my phones.<br>The OTPs are in there, the passwords are in Bitwarden cloud, other data is in Google Drive so nothing is lost here.<p>If I lost my phone, but not my laptops, I use the OTPs which are stored locally in my laptops.<p>In the worst situation, I lost everything, my laptops, my phone.<br>The first step is to recover my SIM, then log in to Google account using the password and SMS OTP.<br>After that, log in to Bitwarden account using the master password and OTP from Gmail, which I open previously.<h2>The end</h2><p>This guide will be updated regularly I promise.

View File

@ -1,48 +1,4 @@
<!DOCTYPE html>
<html>
<head>
<title>2022-06-08-dockerfile-go.md</title>
<meta name="GENERATOR" content="github.com/gomarkdown/markdown markdown processor for Go">
<meta charset="utf-8">
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap"
rel="stylesheet"
/>
<style>
body {
font-family: "Recursive", sans-serif;
font-variation-settings: "MONO" 0, "CASL" 1;
}
code {
font-family: "Recursive", monospace;
font-variation-settings: "MONO" 1, "CASL" 1;
}
</style>
<a href="index">Back to index</a>
</head>
<body>
<nav>
<ul>
<li><a href="#toc_0">Dockerfile for Go</a></li>
</ul>
</nav>
<h1 id="toc_0">Dockerfile for Go</h1>
<p>Each time I start a new Go project, I repeat many steps.
Like set up <code>.gitignore</code>, CI configs, Dockerfile, &hellip;</p>
<p>So I decide to have a baseline Dockerfile like this:</p>
<pre><code class="language-Dockerfile">FROM golang:1.18-bullseye as builder
<!doctype html><meta charset=utf-8><link rel=preconnect href=https://fonts.googleapis.com><link rel=preconnect href=https://fonts.gstatic.com crossorigin><link href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap" rel=stylesheet><link rel=stylesheet href=styles.css><h1>Dockerfile for Go</h1><p>Each time I start a new Go project, I repeat many steps.<br>Like set up <code>.gitignore</code>, CI configs, Dockerfile, ...<p>So I decide to have a baseline Dockerfile like this:<pre><code class=language-Dockerfile>FROM golang:1.18-bullseye as builder
RUN go install golang.org/dl/go1.18@latest \
&amp;&amp; go1.18 download
@ -61,67 +17,19 @@ FROM gcr.io/distroless/base-debian11
COPY --from=builder /build/app /app
ENTRYPOINT [&quot;/app&quot;]
</code></pre>
<p>I use <a href="https://docs.docker.com/develop/develop-images/multistage-build/">multi-stage build</a> to keep my image size small.
First stage is <a href="https://hub.docker.com/_/golang">Go official image</a>,
second stage is <a href="https://github.com/GoogleContainerTools/distroless">Distroless</a>.</p>
<p>Before Distroless, I use <a href="https://hub.docker.com/_/alpine">Alpine official image</a>,
There is a whole discussion on the Internet to choose which is the best base image for Go.
After reading some blogs, I discover Distroless as a small and secure base image.
So I stick with it for a while.</p>
<p>Also, remember to match Distroless Debian version with Go official image Debian version.</p>
<pre><code class="language-Dockerfile">FROM golang:1.18-bullseye as builder
</code></pre>
<p>This is Go image I use as a build stage.
This can be official Go image or custom image is required in some companies.</p>
<pre><code class="language-Dockerfile">RUN go install golang.org/dl/go1.18@latest \
</code></pre><p>I use <a href=https://docs.docker.com/develop/develop-images/multistage-build/>multi-stage build</a> to keep my image size small.<br>First stage is <a href=https://hub.docker.com/_/golang>Go official image</a>,<br>second stage is <a href=https://github.com/GoogleContainerTools/distroless>Distroless</a>.<p>Before Distroless, I use <a href=https://hub.docker.com/_/alpine>Alpine official image</a>,<br>There is a whole discussion on the Internet to choose which is the best base image for Go.<br>After reading some blogs, I discover Distroless as a small and secure base image.<br>So I stick with it for a while.<p>Also, remember to match Distroless Debian version with Go official image Debian version.<pre><code class=language-Dockerfile>FROM golang:1.18-bullseye as builder
</code></pre><p>This is Go image I use as a build stage.<br>This can be official Go image or custom image is required in some companies.<pre><code class=language-Dockerfile>RUN go install golang.org/dl/go1.18@latest \
&amp;&amp; go1.18 download
</code></pre>
<p>This is optional.
In my case, my company is slow to update Go image so I use this trick to install latest Go version.</p>
<pre><code class="language-Dockerfile">WORKDIR /build
</code></pre><p>This is optional.<br>In my case, my company is slow to update Go image so I use this trick to install latest Go version.<pre><code class=language-Dockerfile>WORKDIR /build
COPY go.mod .
COPY go.sum .
COPY vendor .
COPY . .
</code></pre>
<p>I use <code>/build</code> to emphasize that I am building something in that directory.</p>
<p>The 4 <code>COPY</code> lines are familiar if you use Go enough.
First is <code>go.mod</code> and <code>go.sum</code> because it defines Go modules.
The second is <code>vendor</code>, this is optional but I use it because I don&rsquo;t want each time I build Dockerfile, I need to redownload Go modules.</p>
<pre><code class="language-Dockerfile">RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOAMD64=v3 go build -o ./app -tags timetzdata -trimpath .
</code></pre>
<p>This is where I build Go program.</p>
<p><code>CGO_ENABLED=0</code> because I don&rsquo;t want to mess with C libraries.
<code>GOOS=linux GOARCH=amd64</code> is easy to explain, Linux with x86-64.
<code>GOAMD64=v3</code> is new since <a href="https://go.dev/doc/go1.18#amd64">Go 1.18</a>,
I use v3 because I read about AMD64 version in <a href="https://gitlab.archlinux.org/archlinux/rfcs/-/blob/master/rfcs/0002-march.rst">Arch Linux rfcs</a>. TLDR&rsquo;s newer computers are already x86-64-v3.</p>
<p><code>-tags timetzdata</code> to embed timezone database incase base image does not have.
<code>-trimpath</code> to support reproduce build.</p>
<pre><code class="language-Dockerfile">FROM gcr.io/distroless/base-debian11
</code></pre><p>I use <code>/build</code> to emphasize that I am building something in that directory.<p>The 4 <code>COPY</code> lines are familiar if you use Go enough.<br>First is <code>go.mod</code> and <code>go.sum</code> because it defines Go modules.<br>The second is <code>vendor</code>, this is optional but I use it because I don't want each time I build Dockerfile, I need to redownload Go modules.<pre><code class=language-Dockerfile>RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOAMD64=v3 go build -o ./app -tags timetzdata -trimpath .
</code></pre><p>This is where I build Go program.<p><code>CGO_ENABLED=0</code> because I don't want to mess with C libraries.<br><code>GOOS=linux GOARCH=amd64</code> is easy to explain, Linux with x86-64.<br><code>GOAMD64=v3</code> is new since <a href=https://go.dev/doc/go1.18#amd64>Go 1.18</a>,<br>I use v3 because I read about AMD64 version in <a href=https://gitlab.archlinux.org/archlinux/rfcs/-/blob/master/rfcs/0002-march.rst>Arch Linux rfcs</a>. TLDR's newer computers are already x86-64-v3.<p><code>-tags timetzdata</code> to embed timezone database incase base image does not have.<br><code>-trimpath</code> to support reproduce build.<pre><code class=language-Dockerfile>FROM gcr.io/distroless/base-debian11
COPY --from=builder /build/app /app
ENTRYPOINT [&quot;/app&quot;]
</code></pre>
<p>Finally, I copy <code>app</code> to Distroless base image.</p>
</body>
</html>
</code></pre><p>Finally, I copy <code>app</code> to Distroless base image.

View File

@ -1,76 +1,4 @@
<!DOCTYPE html>
<html>
<head>
<title>2022-07-10-bootstrap-go.md</title>
<meta name="GENERATOR" content="github.com/gomarkdown/markdown markdown processor for Go">
<meta charset="utf-8">
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap"
rel="stylesheet"
/>
<style>
body {
font-family: "Recursive", sans-serif;
font-variation-settings: "MONO" 0, "CASL" 1;
}
code {
font-family: "Recursive", monospace;
font-variation-settings: "MONO" 1, "CASL" 1;
}
</style>
<a href="index">Back to index</a>
</head>
<body>
<nav>
<ul>
<li><a href="#toc_0">Bootstrap Go</a>
<ul>
<li><a href="#toc_1">Structure</a></li>
<li><a href="#toc_2">Do not repeat!</a></li>
<li><a href="#toc_3">External libs</a>
<ul>
<li><a href="#toc_4">Don&rsquo;t use cli libs (<a href="https://github.com/spf13/cobra">spf13/cobra</a>, <a href="https://github.com/urfave/cli">urfave/cli</a>) just for Go service</a></li>
<li><a href="#toc_5">Don&rsquo;t use <a href="https://github.com/grpc-ecosystem/grpc-gateway">grpc-ecosystem/grpc-gateway</a></a></li>
<li><a href="#toc_6">Don&rsquo;t use <a href="https://github.com/uber/prototool">uber/prototool</a>, use <a href="https://github.com/bufbuild/buf">bufbuild/buf</a></a></li>
<li><a href="#toc_7">Use <a href="https://github.com/gin-gonic/gin">gin-gonic/gin</a> for REST.</a></li>
<li><a href="#toc_8">If you want log, just use <a href="https://github.com/uber-go/zap">uber-go/zap</a></a></li>
<li><a href="#toc_9">Don&rsquo;t overuse ORM libs, no need to handle another layer above SQL.</a></li>
<li><a href="#toc_10">If you want test, just use <a href="https://github.com/stretchr/testify">stretchr/testify</a>.</a></li>
<li><a href="#toc_11">Replace <code>go fmt</code>, <code>goimports</code> with <a href="https://github.com/mvdan/gofumpt">mvdan/gofumpt</a>.</a></li>
<li><a href="#toc_12">Use <a href="https://github.com/golangci/golangci-lint">golangci/golangci-lint</a>.</a></li>
</ul></li>
</ul></li>
</ul>
</nav>
<h1 id="toc_0">Bootstrap Go</h1>
<p>It is hard to write bootstrap tool to quickly create Go service.
So I write this guide instead.
This is a quick checklist for me every damn time I need to write a Go service from scratch.
Also, this is my personal opinion, so feel free to comment.</p>
<h2 id="toc_1">Structure</h2>
<pre><code class="language-txt">main.go
<!doctype html><meta charset=utf-8><link rel=preconnect href=https://fonts.googleapis.com><link rel=preconnect href=https://fonts.gstatic.com crossorigin><link href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap" rel=stylesheet><link rel=stylesheet href=styles.css><h1>Bootstrap Go</h1><p>It is hard to write bootstrap tool to quickly create Go service.<br>So I write this guide instead.<br>This is a quick checklist for me every damn time I need to write a Go service from scratch.<br>Also, this is my personal opinion, so feel free to comment.<h2>Structure</h2><pre><code class=language-txt>main.go
internal
| business_1
| | http
@ -92,103 +20,4 @@ internal
| | | service.go
| | | repository.go
| | | models.go
</code></pre>
<p>All business codes are inside <code>internal</code>.
Each business has a different directory (<code>business_1</code>, <code>business_2</code>).</p>
<p>Inside each business, there are 2 handlers: <code>http</code>, <code>grpc</code>:</p>
<ul>
<li><code>http</code> is for public APIs (Android, iOS,&hellip; are clients).</li>
<li><code>grpc</code> is for internal APIs (other services are clients).</li>
</ul>
<p>Inside each handler, there are usually 3 layers: <code>handler</code>, <code>service</code>, <code>repository</code>:</p>
<ul>
<li><code>handler</code> interacts directly with gRPC or REST using specific codes (cookies,&hellip;)</li>
<li><code>service</code> is where we write business/logic codes, and only business/logic codes is written here.</li>
<li><code>repository</code> is where we write codes which interacts with database/cache like MySQL, Redis, &hellip;</li>
</ul>
<p><code>handler</code> must exist inside <code>grpc</code>, <code>http</code>.
But <code>service</code>, <code>repository</code>, <code>models</code> can exist directly inside <code>business</code> if both <code>grpc</code>, <code>http</code> has same business/logic.</p>
<h2 id="toc_2">Do not repeat!</h2>
<p>If we have too many services, some of the logic will be overlapped.</p>
<p>For example, service A and service B both need to make POST call API to service C.
If service A and service B both have libs to call service C to do that API, we need to move the libs to some common pkg libs.
So in the future, service D which needs to call C will not need to copy libs to handle service C api but only need to import from common pkg libs.</p>
<p>Another bad practice is adapter service.
No need to write a new service if what we need is just common pkg libs.</p>
<h2 id="toc_3">External libs</h2>
<h3 id="toc_4">Don&rsquo;t use cli libs (<a href="https://github.com/spf13/cobra">spf13/cobra</a>, <a href="https://github.com/urfave/cli">urfave/cli</a>) just for Go service</h3>
<p>What is the point to pass many params (<code>--abc</code>, <code>--xyz</code>) when what we only need is start service?</p>
<p>In my case, service starts with only config, and config should be read from file or environment like <a href="https://12factor.net/">The Twelve Factors</a> guide.</p>
<h3 id="toc_5">Don&rsquo;t use <a href="https://github.com/grpc-ecosystem/grpc-gateway">grpc-ecosystem/grpc-gateway</a></h3>
<p>Just don&rsquo;t.</p>
<p>Use <a href="https://github.com/protocolbuffers/protobuf-go">protocolbuffers/protobuf-go</a>, <a href="https://github.com/grpc/grpc-go">grpc/grpc-go</a> for gRPC.</p>
<p>Write 1 for both gRPC, REST sounds good, but in the end, it is not worth it.</p>
<h3 id="toc_6">Don&rsquo;t use <a href="https://github.com/uber/prototool">uber/prototool</a>, use <a href="https://github.com/bufbuild/buf">bufbuild/buf</a></h3>
<p>prototool is deprecated, and buf can generate, lint, format as good as prototool.</p>
<h3 id="toc_7">Use <a href="https://github.com/gin-gonic/gin">gin-gonic/gin</a> for REST.</h3>
<p>Don&rsquo;t use <code>gin.Context</code> when pass context from handler layer to service layer, use <code>gin.Context.Request.Context()</code> instead.</p>
<h3 id="toc_8">If you want log, just use <a href="https://github.com/uber-go/zap">uber-go/zap</a></h3>
<p>It is fast!</p>
<ul>
<li><p>Don&rsquo;t overuse <code>func (*Logger) With</code>. Because if log line is too long, there is a possibility that we can lost it.</p></li>
<li><p>Use <code>MarshalLogObject</code> when we need to hide some field of object when log (field has long or sensitive value)</p></li>
<li><p>Don&rsquo;t use <code>Panic</code>. Use <code>Fatal</code> for errors when start service to check dependencies. If you really need panic level, use <code>DPanic</code>.</p></li>
<li><p>Use <code>contextID</code> or <code>traceID</code> in every log lines for easily debug.</p></li>
</ul>
<h3 id="toc_9">Don&rsquo;t overuse ORM libs, no need to handle another layer above SQL.</h3>
<p>Each ORM libs has each different syntax.
To learn and use those libs correctly is time consuming.
So just stick to plain SQL.
It is easier to debug when something is wrong.</p>
<p>But <code>database/sql</code> has its own limit.
For example, it is hard to get primary key after insert/update.
So may be you want to use ORM for those cases.</p>
<h3 id="toc_10">If you want test, just use <a href="https://github.com/stretchr/testify">stretchr/testify</a>.</h3>
<p>It is easy to write a suite test, thanks to testify.
Also, for mocking, there are many options out there.
Pick 1 then sleep peacefully.</p>
<h3 id="toc_11">Replace <code>go fmt</code>, <code>goimports</code> with <a href="https://github.com/mvdan/gofumpt">mvdan/gofumpt</a>.</h3>
<p><code>gofumpt</code> provides more rules when format Go codes.</p>
<h3 id="toc_12">Use <a href="https://github.com/golangci/golangci-lint">golangci/golangci-lint</a>.</h3>
<p>No need to say more.
Lint or get the f out!</p>
</body>
</html>
</code></pre><p>All business codes are inside <code>internal</code>.<br>Each business has a different directory (<code>business_1</code>, <code>business_2</code>).<p>Inside each business, there are 2 handlers: <code>http</code>, <code>grpc</code>:<ul><li><code>http</code> is for public APIs (Android, iOS,... are clients).<li><code>grpc</code> is for internal APIs (other services are clients).</ul><p>Inside each handler, there are usually 3 layers: <code>handler</code>, <code>service</code>, <code>repository</code>:<ul><li><code>handler</code> interacts directly with gRPC or REST using specific codes (cookies,...)<li><code>service</code> is where we write business/logic codes, and only business/logic codes is written here.<li><code>repository</code> is where we write codes which interacts with database/cache like MySQL, Redis, ...</ul><p><code>handler</code> must exist inside <code>grpc</code>, <code>http</code>.<br>But <code>service</code>, <code>repository</code>, <code>models</code> can exist directly inside <code>business</code> if both <code>grpc</code>, <code>http</code> has same business/logic.<h2>Do not repeat!</h2><p>If we have too many services, some of the logic will be overlapped.<p>For example, service A and service B both need to make POST call API to service C.<br>If service A and service B both have libs to call service C to do that API, we need to move the libs to some common pkg libs.<br>So in the future, service D which needs to call C will not need to copy libs to handle service C api but only need to import from common pkg libs.<p>Another bad practice is adapter service.<br>No need to write a new service if what we need is just common pkg libs.<h2>External libs</h2><h3>Don't use cli libs (<a href=https://github.com/spf13/cobra>spf13/cobra</a>, <a href=https://github.com/urfave/cli>urfave/cli</a>) just for Go service</h3><p>What is the point to pass many params (<code>--abc</code>, <code>--xyz</code>) when what we only need is start service?<p>In my case, service starts with only config, and config should be read from file or environment like <a href=https://12factor.net/>The Twelve Factors</a> guide.<h3>Don't use <a href=https://github.com/grpc-ecosystem/grpc-gateway>grpc-ecosystem/grpc-gateway</a></h3><p>Just don't.<p>Use <a href=https://github.com/protocolbuffers/protobuf-go>protocolbuffers/protobuf-go</a>, <a href=https://github.com/grpc/grpc-go>grpc/grpc-go</a> for gRPC.<p>Write 1 for both gRPC, REST sounds good, but in the end, it is not worth it.<h3>Don't use <a href=https://github.com/uber/prototool>uber/prototool</a>, use <a href=https://github.com/bufbuild/buf>bufbuild/buf</a></h3><p>prototool is deprecated, and buf can generate, lint, format as good as prototool.<h3>Use <a href=https://github.com/gin-gonic/gin>gin-gonic/gin</a> for REST.</h3><p>Don't use <code>gin.Context</code> when pass context from handler layer to service layer, use <code>gin.Context.Request.Context()</code> instead.<h3>If you want log, just use <a href=https://github.com/uber-go/zap>uber-go/zap</a></h3><p>It is fast!<ul><li><p>Don't overuse <code>func (*Logger) With</code>. Because if log line is too long, there is a possibility that we can lost it.<li><p>Use <code>MarshalLogObject</code> when we need to hide some field of object when log (field has long or sensitive value)<li><p>Don't use <code>Panic</code>. Use <code>Fatal</code> for errors when start service to check dependencies. If you really need panic level, use <code>DPanic</code>.<li><p>Use <code>contextID</code> or <code>traceID</code> in every log lines for easily debug.</ul><h3>Don't overuse ORM libs, no need to handle another layer above SQL.</h3><p>Each ORM libs has each different syntax.<br>To learn and use those libs correctly is time consuming.<br>So just stick to plain SQL.<br>It is easier to debug when something is wrong.<p>But <code>database/sql</code> has its own limit.<br>For example, it is hard to get primary key after insert/update.<br>So may be you want to use ORM for those cases.<h3>If you want test, just use <a href=https://github.com/stretchr/testify>stretchr/testify</a>.</h3><p>It is easy to write a suite test, thanks to testify.<br>Also, for mocking, there are many options out there.<br>Pick 1 then sleep peacefully.<h3>Replace <code>go fmt</code>, <code>goimports</code> with <a href=https://github.com/mvdan/gofumpt>mvdan/gofumpt</a>.</h3><p><code>gofumpt</code> provides more rules when format Go codes.<h3>Use <a href=https://github.com/golangci/golangci-lint>golangci/golangci-lint</a>.</h3><p>No need to say more.<br>Lint or get the f out!

View File

@ -1,49 +1 @@
<!DOCTYPE html>
<html>
<head>
<title>index.md</title>
<meta name="GENERATOR" content="github.com/gomarkdown/markdown markdown processor for Go">
<meta charset="utf-8">
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap"
rel="stylesheet"
/>
<style>
body {
font-family: "Recursive", sans-serif;
font-variation-settings: "MONO" 0, "CASL" 1;
}
code {
font-family: "Recursive", monospace;
font-variation-settings: "MONO" 1, "CASL" 1;
}
</style>
<a href="index">Back to index</a>
</head>
<body>
<nav>
<ul>
<li><a href="#toc_0">Index</a></li>
</ul>
</nav>
<h1 id="toc_0">Index</h1>
<p>This is where I dump my thoughts.</p>
<ul>
<li><a href="2022-06-08-backup">Backup my way</a></li>
<li><a href="2022-06-08-dockerfile-go">Dockerfile for Go</a></li>
<li><a href="2022-07-10-bootstrap-go">Bootstrap Go</a></li>
</ul>
</body>
</html>
<!doctype html><meta charset=utf-8><link rel=preconnect href=https://fonts.googleapis.com><link rel=preconnect href=https://fonts.gstatic.com crossorigin><link href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap" rel=stylesheet><link rel=stylesheet href=styles.css><h1>Index</h1><p>This is where I dump my thoughts.<ul><li><a href=2022-06-08-backup>Backup my way</a><li><a href=2022-06-08-dockerfile-go>Dockerfile for Go</a><li><a href=2022-07-10-bootstrap-go>Bootstrap Go</a></ul>

1
docs/styles.css Normal file
View File

@ -0,0 +1 @@
body{font-family:recursive,sans-serif;font-variation-settings:"MONO" 0,"CASL" 1}code{font-family:recursive,monospace;font-variation-settings:"MONO" 1,"CASL" 1}

7
go.mod
View File

@ -2,4 +2,9 @@ module github.com/haunt98/posts-go
go 1.18
require github.com/gomarkdown/markdown v0.0.0-20220627144906-e9a81102ebeb
require (
github.com/tdewolff/minify/v2 v2.12.0
github.com/yuin/goldmark v1.4.13
)
require github.com/tdewolff/parse/v2 v2.6.1 // indirect

18
go.sum
View File

@ -1,2 +1,16 @@
github.com/gomarkdown/markdown v0.0.0-20220627144906-e9a81102ebeb h1:5b/eFaSaKPFG9ygDBaPKkydKU5nFJYk08g9jPIVogMg=
github.com/gomarkdown/markdown v0.0.0-20220627144906-e9a81102ebeb/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/tdewolff/minify/v2 v2.12.0 h1:ZyvMKeciyR3vzJrK/oHyBcSmpttQ/V+ah7qOqTZclaU=
github.com/tdewolff/minify/v2 v2.12.0/go.mod h1:8mvf+KglD7XurfvvFZDUYvVURy6bA/r0oTvmakXMnyg=
github.com/tdewolff/parse/v2 v2.6.1 h1:RIfy1erADkO90ynJWvty8VIkqqKYRzf2iLp8ObG174I=
github.com/tdewolff/parse/v2 v2.6.1/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM=
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

135
main.go
View File

@ -1,22 +1,41 @@
package main
import (
"bytes"
"io"
"log"
"os"
"path/filepath"
"strings"
"text/template"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/tdewolff/minify/v2"
minify_css "github.com/tdewolff/minify/v2/css"
minify_html "github.com/tdewolff/minify/v2/html"
"github.com/yuin/goldmark"
gm_extension "github.com/yuin/goldmark/extension"
gm_html "github.com/yuin/goldmark/renderer/html"
)
const (
postsPath = "posts"
headHTMLPath = "custom/head.html"
postFilesPath = "posts"
templatePostPath = "templates/post.html"
templateCSSPath = "templates/styles.css"
cssFilename = "styles.css"
generatedPath = "docs"
htmlExt = ".html"
extHTML = ".html"
mimeTypeHTML = "text/html"
mimeTypeCSS = "text/css"
)
type templatePostData struct {
Body string
}
func main() {
// Cleanup generated path
if err := os.RemoveAll(generatedPath); err != nil {
@ -27,47 +46,99 @@ func main() {
log.Fatalln("Failed to mkdir all", generatedPath)
}
// Read needed files
headHTML, err := os.ReadFile(headHTMLPath)
// Read post files directory
postFiles, err := os.ReadDir(postFilesPath)
if err != nil {
log.Fatalln("Failed to read file", headHTML)
log.Fatalln("Failed to read dir", postFilesPath)
}
files, err := os.ReadDir(postsPath)
// Prepare template
templatePostBytes, err := os.ReadFile(templatePostPath)
if err != nil {
log.Fatalln("Failed to read dir", postsPath)
log.Fatalln("Failed to read file", templatePostPath, err)
}
for _, file := range files {
if file.IsDir() {
templatePost, err := template.New("post").Parse(string(templatePostBytes))
if err != nil {
log.Fatalln("Failed to parse template", err)
}
// Prepare parse markdown
gm := goldmark.New(
goldmark.WithExtensions(
gm_extension.GFM,
),
goldmark.WithRendererOptions(
gm_html.WithHardWraps(),
),
)
// Prepare minify
m := minify.New()
m.AddFunc(mimeTypeHTML, minify_html.Minify)
m.AddFunc(mimeTypeCSS, minify_css.Minify)
// Generate post files
for _, postFile := range postFiles {
if postFile.IsDir() {
continue
}
// Generate HTML
filePath := filepath.Join(postsPath, file.Name())
md, err := os.ReadFile(filePath)
// Prepare post file
mdFilename := filepath.Join(postFilesPath, postFile.Name())
mdFileBytes, err := os.ReadFile(mdFilename)
if err != nil {
log.Fatalln("Failed to read file", filePath)
log.Fatalln("Failed to read file", mdFilename, err)
}
htmlFlags := html.CommonFlags |
html.CompletePage |
html.TOC |
html.LazyLoadImages
htmlRendererOtps := html.RendererOptions{
Title: file.Name(),
Head: headHTML,
Flags: htmlFlags,
// Prepare html file
htmlFilename := strings.TrimSuffix(postFile.Name(), filepath.Ext(postFile.Name())) + extHTML
htmlFilepath := filepath.Join(generatedPath, htmlFilename)
htmlFile, err := os.OpenFile(htmlFilepath, os.O_RDWR|os.O_CREATE, 0o600)
if err != nil {
log.Fatalln("Failed to open file", htmlFilepath, err)
}
htmlRenderer := html.NewRenderer(htmlRendererOtps)
generatedHTML := markdown.ToHTML(md, nil, htmlRenderer)
generatedFileName := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())) + htmlExt
generatedFilePath := filepath.Join(generatedPath, generatedFileName)
if err := os.WriteFile(generatedFilePath, generatedHTML, 0o666); err != nil {
log.Fatalln("Failed to write file", generatedFilePath, err)
// Parse markdown
var markdownBuf bytes.Buffer
if err := gm.Convert(mdFileBytes, &markdownBuf); err != nil {
log.Fatalln("Failed to convert markdown", err)
}
tmpReader, tmpWriter := io.Pipe()
// Template
go func() {
if err := templatePost.Execute(tmpWriter, templatePostData{
Body: markdownBuf.String(),
}); err != nil {
log.Fatalln("Failed to execute html template", err)
}
tmpWriter.Close()
}()
// Minify
if err := m.Minify(mimeTypeHTML, htmlFile, tmpReader); err != nil {
log.Fatalln("Failed to minify html", err)
}
tmpReader.Close()
htmlFile.Close()
}
// Copy css file
templateCSSFile, err := os.OpenFile(templateCSSPath, os.O_RDONLY, 0o600)
if err != nil {
log.Fatalln("Failed to open file", templateCSSPath, err)
}
cssFilename := filepath.Join(generatedPath, cssFilename)
cssFile, err := os.OpenFile(cssFilename, os.O_RDWR|os.O_CREATE, 0o600)
if err != nil {
log.Fatalln("Failed to open file", cssFilename, err)
}
if err := m.Minify(mimeTypeCSS, cssFile, templateCSSFile); err != nil {
log.Fatalln("Failed to minify css", err)
}
}

16
templates/post.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
{{.Body}}
</body>
</html>

9
templates/styles.css Normal file
View File

@ -0,0 +1,9 @@
body {
font-family: "Recursive", sans-serif;
font-variation-settings: "MONO" 0, "CASL" 1;
}
code {
font-family: "Recursive", monospace;
font-variation-settings: "MONO" 1, "CASL" 1;
}