1. Keep learning forward

    To be updated ...
  2. #TIL : Cronjob monitoring web endpoint and alert to Telegram

    This is shell script using curl and grep to send request then check if the keyword (FIND) include in the response text body. If failed, try to alert

    URL="https://news.ycombinator.com/"
    FIND="Hacker News"
    
    response=$(curl -m 10 -s "$URL")
    content=$(echo $response | grep "$FIND")
    
    if [ $? -eq 0 ]; then
    	echo "OK!"
    	exit 0
    fi
    
    # Do watever you want to alert
    CHAT_ID="telegram_chat_id"
    TEXT="The web is down !"
    BASIC_AUTH_BOT_PARAM="bot*****:*********"
    
    curl -s -m 10 --get --data-urlencode "chat_id=$CHAT_ID" --data-urlencode "text=$TEXT" "https://api.telegram.org/$BASIC_AUTH_BOT_PARAM/sendMessage"
    exit 1
  3. #TIL : XCode automatic format code on build time

    XCode (the main IDE of Apple), but lacks much common features like auto formating code.
    So we have to install a external tool (lol it's Apple tool itself) by brew install swift-format

    Then I have to use a trick in Build Phases settings in XCode, we create a bash script run format tool on every build time (before source code has been built, because sometimes your code buidling will failed)

    Here is the bash script, make sure the Run script below "Run Build Tool Plug-ins" step

    run script

    echo "Formating all source codes"
    cd $SOURCE_ROOT
    /opt/homebrew/bin/swift-format --ignore-unparsable-files -i -p -r .
    echo "Formated all source codes."

    Important : You have to turn off Build Options named User Script Sandboxing (this diffenrence of App Sandboxing), to allow your shell scripts can affected real filesystem instead of shadowing sandbox files, below is the screenshot

    Turn of User Script Sandboxing

  4. #TIL : Generate routing stubs for IDEs

    Some IDEs has good LSP or Intellisense can show link of class methods, so we can using this mechanism to listing all the routes to faster searching route and click method link to navigate actual code.

    I make this Laravel command to generate a stub file into routes directory so the IDE can index

    So sometimes when you need to re-generate stubs, run this command

    $ php artisan routing_stubs
    <?php
    namespace App\Console\Commands;
    
    use Illuminate\Console\Command;
    use Illuminate\Support\Facades\Route;
    
    class RoutingStubs extends Command
    {
        /**
         * The name and signature of the console command.
         *
         * @var string
         */
        protected $signature = 'routing_stubs';
    
        /**
         * The console command description.
         *
         * @var string
         */
        protected $description = 'Generate routing stubs';
    
        public function handle()
        {
            $routes = collect(Route::getRoutes())
                ->map(fn (\Illuminate\Routing\Route $route) => explode('@', $route->getActionName()))
                ->mapToGroups(fn ($params) => [$params[0] => $params[1] ?? ''])
                ->filter(fn ($methods, $class) => class_exists($class))
                ->map(fn ($methods, $class) => $methods->map(
                    fn ($method) => sprintf("* @uses %s::%s", $class, $method)
                )->join("\n"))
                ->flatten()
                ->join("\n");
    
            file_put_contents(base_path('routes/stubs.php'), "<?php\n/**\n" . $routes . "\n*/\n");
    
            $this->info('Done');
        }
    }
    
  5. #TIL : Smallest inline dummy image in HTML

    Learned from https://www.phpied.com/minimum-viable-no-image-image-src/

    You can use this for lazy load images in pages

    <img 
      src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>" 
      ...
    >

    Hmm, when I inspected his demo image, I saw new img attribute named decoding="async", so interesting! So we can tell browser not to wait process this dummy image for later and priority the rest higher. (Good for LCP vital metric)

  6. #TIL : Transform array to object shorthand

    To transform a array to stdClass in PHP, try to cast type using (object) before the array variable.

    $arr = ['a' => 1, 'b' => 5];
    $obj = (object) $arr;
    echo $obj->b;
  7. #TIL : Be careful when using built-in btoa from window to decode base64

    window.btoa is built-in function to encode string to base64 string, but it can't process any Unicode characters (each char must be fit in 1 byte, but Unicode is multi-byte char)

    So solution is transform the value to Latin character using some pre-processing function like

    const new_btoa = (val) => btoa(unescape(encodeURIComponent(val)))
    const result = new_btoa('Hế lô xin chào')

    So you have to pair the atob too

    const new_atob = (val) => decodeURIComponent(escape(atob(val)))
    console.log(new_atob(result))

    Or using 3rd-lib like https://www.npmjs.com/package/js-base64 (I recommend use this way)

  8. #TIL : Split a Collection items into 2 groups using 1 boolean function

    Old fashion way

    $groupTrue = $groupFalse = [];
    foreach ($items as $item) {
    	if (some_logic_function($item)) {
    		$groupTrue[] = $item;
    	} else {
    		$groupFalse[] = $item;
    	}
    }

    New way

    list($groupTrue, $groupFalse) = collect($items)
    	->partition(fn ($item) => some_logic_function($item))
    	->map(fn ($group) => $group->values());

    tip: Using the last map then values to reset indexing of 2 groups after spliting.

  9. #TIL : Reactive statement explicit dependencies like React useEffect

    Svelte uses it compiler to detect the reactive dependencies, to trigger the reactive updater function. So our work need is make sure all dependent variables appear in the line.

    I use the below logic trick like this (!0 always true so it won't run init array [x,z,y]), but compiler always see the x,z,y in dependencies. :D

    result = (!0 || [x,y,z]) ? calc() : null;

    OK, here is sample Svelve 4 code (sure Svelte 5 Runes is the saver)

    <script>
    	let x = 1;
    	let y = 1;	
    	let z = 1;	
    	let result = 0;
    
    	function calc() {
    			return x * y * z;
    	}
    
    	$: result = (!0 || [x,y,z]) ? calc() : null;
    
    	function handleClickX() {
    		x += 1;
    	}
    	
    	function handleClickY() {
    		y += 1;
    	}
    
    	function handleClickZ() {
    		z += 1;
    	}
    </script>
    
    <button on:click={handleClickX}>
    	x = {x}
    </button>
    <button on:click={handleClickY}>
    	y = {y}
    </button>
    <button on:click={handleClickZ}>
    	z = {z}
    </button>
    
    <p>Result = {result}</p>
  10. #TIL : Warning on MacOS upgrade major system

    Never ever upgrade OS without :

    • Backup important files (better sync it to cloud every work hour)
    • Unplug all devices or accessories from all USB ports
    • Plugged in power cable

    Yesterday, I upgraded macOS to another version when plugged to the USB-C hub (HDMI external screen, USB keyboard, USB for LED light). Then it restarted few times and hang with black screen. After waiting 10 mins nothing changes, I power off the macbook hard way (by hold power button). Then I never can boot it again (even to Recovery mode).

    After researching whole Internet (Google, MacForum, Youtube) and trying 42 times, I failed to revive it! :(

    But, keep searching I found my life saver's blog post , now I can restore the Macbook and reinstall clean MacOS Sonoma.

    Thanks GOD :D

    Btw, never stupid like me ! And if the thing works fine, keep it works ! If not, carefully upgrade it and always have a plan B.

  11. #TIL : Cache storage utils using redis server

    I created a small util Cache storage using redis server to cache seriable data, it stores serialzied data string in text then unserialize to wake the data up.

    <?php
    
    namespace App\Library;
    
    use Predis\Client;
    use Predis\PredisException;
    
    class CacheStorage
    {
        public function __construct(private Client $redis)
        {
        }
    
        public function remember($key, $ttl, $dataFn)
        {
            try {
                $cached_str = $this->redis->get($key);
                $cached = is_null($cached_str) ? false : unserialize($cached_str);
    
                if ($cached) {
                    return $cached;
                }
    
                $freshData = $dataFn();
                $this->redis->set($key, serialize($freshData), "ex", $ttl);
    
                return $freshData;
            } catch (PredisException $e) {
                return $dataFn();
            }
        }
    }
    
  12. #TIL : Getting database from data range to array of enum objects

    function getData(range, columnMap) {
      const dataValues = range.getValues();
      const mapEntries = Object.entries(columnMap)
    
      return dataValues.map((row) => Object.fromEntries(mapEntries.map((colMap) => [colMap[0], row[colMap[1]]])))
    }

    Example:

    You have a sheet with data from A1:D*** (*** is last row), with the field mappings :

    • first col is product_code
    • second col is total
    • fourth col is rank1

    So the code of main function is

    function main(){
      // Get the sheet you want
      var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
      var lastRow = sheet.getLastRow();
      var dataRange = sheet.getRange("A1:D" + (lastRow));
      
      var data = getData(dataRange, {
        "product_code": 0,
        "total": 1,
        // skip third column
        "rank1": 3,
      })
    
      console.log(data)
    }
  13. #TIL : JS DOM event deepdive using SvelteJS demo

    The DOM event handling go through 2 phrases : Capture and Bubble (with support of preventDefault, stopPropagation, stopImmediatePropagation methods to modify behavior)

    JS DOM event

    Try the demo here : https://svelte.dev/repl/99d1edbf7bd9426384be2c7763d2e872?version=4.2.0

    <script>
    	const onClickEle = (e) => {
    		console.log((new Date()).toISOString() + " : Clicked " + e.currentTarget.tagName)
    	}
    </script>
    
    <h5>Default (bubble)</h5>
    <div on:click={onClickEle}>
    	<button on:click={onClickEle}>Click here!</button>
    </div>
    
    <h5>Default (bubble with stopPropagation)</h5>
    <div on:click={onClickEle}>
    	<button on:click|stopPropagation={onClickEle}>Click here!</button>
    </div>
    
    <h5>Parent capture</h5>
    <div on:click|capture={onClickEle}>
    	<button on:click={onClickEle}>Click here!</button>
    </div>
    
    <h5>Parent capture stopPropagation</h5>
    <div on:click|capture|stopPropagation={onClickEle}>
    	<button on:click={onClickEle}>Click here!</button>
    </div>
    
    <h5>Self (ele = event target)</h5>
    <div on:click|self={onClickEle}>
    	<button on:click={onClickEle}>Click here!</button>
    </div>
    
    <style>
    		div { padding: 10px 30px; background: gray; cursor: pointer; display: inline }
    </style>
  14. #TIL : ES6 Module import in client browsers

    moduleA.js

    export const message = "Hello from moduleA!";

    moduleB.js

    import { message } from './moduleA.js';
    console.log(message);

    app.html

    <script type="module" src="moduleB.js"></script>
    
    // Print console log "Hello from moduleA!"

    So moduleB.js acts like a module entry-point, it loads all dependencies deeply to run it-self, so you only need add script tag point to it.

  15. #TIL : SvelteJS : Reactivity via variable assignment

    Reactivity in Svelte works via assignment of variable, so we have this as example

    let todos = [];
    
    // Don't work
    todos.push({id: 1, name: 'cleaning'})
    todos.push({id: 2, name: 'coding'})
    
    // Work
    todos = [...todos, {id: 1, name: 'cleaning'}]
    
    // or this even work as tricky way
    todos.push({id: 1, name: 'cleaning'})
    todos = todos

    Why ? I don't know in internal system how it implement, but I think because Svelte works as a compiler, so it triggers the update when it see the assignment variable line.

    Different with another frameworks, they work based on proxy object

  16. #TIL : Array group by function in JS

    Object.prototype.groupBy = function(cb) {
      const groupByCategory = this.reduce((group, item) => {
        const category = cb(item);
        group[category] = group[category] ?? [];
        group[category].push(item);
        return group;
      }, {});
    
      return groupByCategory;
    }
    
    // Example
    const people = [{"name":"John","age":28},{"name":"Alice","age":24},{"name":"Michael","age":32},{"name":"Emily","age":29},{"name":"David","age":27},{"name":"Sophia","age":38}];
    
    const by_first_age_period = people.groupBy((person) => Math.floor(person.age / 10) * 10 + '+');
    
    console.log(by_first_age_period);
  17. #TIL : CSS Selector element has an attribute which contains some string

    [abc*="something"] means select element has attribute abc, and its value contains "something" string

    Example :

    /* Warning all admin link is red text color */
    a[href*="/admin/"] {
    	color: red;
    }
  18. #TIL : HTTP Status Codes : 301+302 vs 307+308

    • HTTP 307 : Temporary redirect, keep METHOD and BODY
    • HTTP 308 : Permanent redirect, keep METHOD and BODY

    vs

    • HTTP 301 : Permanent redirect, fallback to GET method
    • HTTP 302 : Temporary redirect, fallback to GET method
  19. #TIL : Becareful on using port mapping of Docker in Development

    Many examples, articles and repository use insecure config in port mapping Docker.

    This is example in official document of Docker

    https://docs.docker.com/engine/reference/run/#detached--d

    $ docker run -d -p 80:80 my_image nginx -g 'daemon off;'

    or

    from 12.2k stars repo in Github

    https://github.com/chatwoot/chatwoot/blob/develop/docker-compose.yaml#L89

        ports:
          - '5432:5432'

    This mapping config means you will proxy all traffic from the container port to the docker host (your dev machine) with binding 0.0.0.0. It means if you don't have a firewall or accidently open all ports, your contaier data will be public in the Local Network (so danger if you use in public places like coffee shops)

    So remember to add the IP, which you wanted to bind in docker host (or have a network firewall ON)

        ports:
          - '127.0.0.1:5432:5432'
          - '172.17.0.1:5432:5432'
  20. #TIL : Using extra hosts to add custom ip of hostnames to Docker container

    By default, all docker container using DNS server from docker host, so if you want to overwrite the specific hostnames ip address, try the flag --add-host [hostname]:[ip]

    $ docker run -it --add-host db1:1.2.3.4 --add-host db2:2.3.4.5 alpine cat /etc/hosts
    127.0.0.1       localhost
    ::1     localhost ip6-localhost ip6-loopback
    fe00::0 ip6-localnet
    ff00::0 ip6-mcastprefix
    ff02::1 ip6-allnodes
    ff02::2 ip6-allrouters
    1.2.3.4 db1
    2.3.4.5 db2
    172.17.0.2      0bdcf2fb2216

    If you use docker-compose, then add the extra-hosts param below the service you want

        extra_hosts:
          - "crm.dev:host-gateway"
          - "db1:1.2.3.4"

    Bonus : host-gateway mean the ip address of the gateway of network container in, often be the docker host!

  21. #TIL : Laravel run scheduled command within parent environment

    Last day I updated my Laravel app from version 8 to 9, all my scheduled command in Console Kernel has run in another environment like "production". Which causes my system didn't work as expected.

    Example, I have 2-3 enviroment which I run cronjob like

    * * * * * php artisan schedule:run --env=hello
    * * * * * php artisan schedule:run --env=world

    But all sub scheduled commands has been run with "production" instead of "hello" or "world"

    So this is my fix, I created a lambda function which pass current environment to the child command.

    class Kernel extends ConsoleKernel
    {
        protected function schedule(Schedule $schedule)
        {
            // Added 2 lines to get lamda function
            $env = app()->environment();
            $schedule_command = fn ($cmd) => $schedule->command($cmd, ['--env' => $env]);
    
            // Then schedule using new lambda function
            $schedule_command('check_product_stock')->everyTenMinutes();
            $schedule_command('run_job_queue')->everyMinute();
            $schedule_command('clean_old_data')->dailyAt('00:25');
        }
    
        protected function commands()
        {
            $this->load(__DIR__ . '/Commands');
    
            require base_path('routes/console.php');
        }
    }

    Enjoy ! :) Hope it works for you !

  22. #TIL : Minimize Google PHP SDK before deploying on production

    In case you use google/apiclient package to connect to Google API services, remember to cleaning unused services after deploying to production.

    So let me show you why

    First, lets counting number of classes of Google API services inside composer autoload classmap file (Composer use this file as a mapping to lazyload your needed class)

    $ cat vendor/composer/autoload_classmap.php | grep 'Google' | wc -l
    15436
    $ du -h vendor/composer/autoload_classmap.php
    3.5M    vendor/composer/autoload_classmap.php

    As you can see, it has almost 15k classes having Google in class namespace. And your autoload_classmap.php has 3.5Mb in size (so large)

    So, let cleaning unused services (by keeping only services you need)

    Editing your composer.json file by adding "pre-autoload-dump" in scripts and "google/apiclient-services" in extra, like below (replace services you need like Drive, Youtube, Sheets, etc..)

    {
        "scripts": {
            "pre-autoload-dump": "Google\\Task\\Composer::cleanup"
        },
        "extra": {
            "google/apiclient-services": [
                "Drive",
                "YouTube",
    	    "Sheets"
            ]
        }
    }

    Finally, run composer dump-autoload again and see the result

    $ cat vendor/composer/autoload_classmap.php | grep 'Google' | wc -l
    383
    $ du -m vendor/composer/autoload_classmap.php
    1.3M    vendor/composer/autoload_classmap.php

    Class map reduce from 15k to 400 lines, and size from 3.5M to 1.3M ! Such an optimize !

    Beware : If you add services after cleaning, try to run rm -r vendor/google/apiclient-services && composer update to redownloading all services classes.

  23. #TIL : Getting back Whoops error pages in Laravel 9

    Laravel 9 uses "Spatie/Ignition" as default error handling pages , which is beatiful but bloated within add-on service like Flare.

    So if you want to get back the good-old "Whoops" error pages, this tutorial is for you :D

    First, we remove "spatie/ignition" from composer packages

    $ composer remove "spatie/laravel-ignition"

    Then, add 3 lines in the boot function of AppServiceProvider

    use Illuminate\Contracts\Foundation\ExceptionRenderer;
    use Illuminate\Foundation\Exceptions\Whoops\WhoopsExceptionRenderer;
    
    class AppServiceProvider extends ServiceProvider {
        public function boot()
        {
            // Add 3 lines below between boot function
            if (config('app.debug')) {
                $this->app->bind(ExceptionRenderer::class, WhoopsExceptionRenderer::class);
            }
        }
    }

    And last but not least, open the config/app.php then add to this line to array config (try to replace the name of Editor you use in this list )

        ...
        'editor' => 'vscode'
        ...

    Now try to add this line to second line in web.php route file to test

    $x = 1 / 0;
  24. #TIL : DirectAdmin change document root to public directory

    By default, the document root of a domain in DirectAdmin (DA) has the format

    /home/[your_username]/domains/[your_domain]/public_html

    But in case you have to run modern PHP app like Laravel or Symfony framework (they use child public directory as their docroot). In some case, using .htaccess file to rewrite is a hacky way to solve problem (or this is the option when you don't have Admin account of DA)

    So, if you have an Admin account, try this

    Open menu Server Manager > Custom HTTPD Configurations, then click on your domain httpd config file, then click CUSTOMIZE

    In the large textbox, add this line (remind to replace username and domain) to

    |?DOCROOT=/home/[your_username]/domains/[your_domain]/public_html/public|

    Click SAVE to save modification. Then go to Service Monitor and reload your webservers (Nginx, Apache or OpenLiteSpeed)

  25. #TIL : Insert fastly current date time to cell in Google Spreadsheets

    Yo! Back to my #TIL

    Today, I find out a way to insert fastly current date or current time to a cell. It makes you faster 3-5 seconds and reduce confusing about what the time is?

    • To insert current date : press Ctrl + :
    • To insert current time : press Ctrl + Shift + :

    Excel (Spreadsheets) never dies !

  26. #TIL : JSON Parse is faster than Javascript Object declaration

    TLDR;

    Use JSON.parse("[your data in json string]") if your data is big (>10KB for instance)

    Short Explaination :

    JSON.parse parses a string to object, so it has many strict requirements than Javascript parses the source code (more syntax, more context)

    Long Explaination :

    https://www.youtube.com/watch?v=ff4fgQxPaO0

  27. #TIL : Use NGINX as a TCP,UDP load balancer

    NGINX is well known as a simple and good web server right now, but not everyone knows that NGINX can act like a TCP-UDP loadbalancer. So you won't need to install HAProxy when you need a LoadBalancer.

    This feature is released on NGINX 1.9+. So you can setup it by this rule

    stream {
    	upstream backend1 {
    		server s1.backend1.com:12345;
    		server s2.backend1.com:12345;
    	}
    	server {
    		listen 54321;
    		proxy_pass backend1;
    	}
    	upstream backend2 {
    		server s1.backend2.com:7777;
    		server s2.backend2.com:7777;
    		server s3.backend2.com:7777;
    	}
    	server {
    		listen 8888 udp; # add udp keyword if you want UDP server
    		proxy_pass backend2;
    	}
    }
    

    To learn more, click here : https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-udp-load-balancer/

  28. #TIL : Improve apt package manager speed by changing source list mirrors

    Sometimes, you ran into issues that apt update package list so slow or even can not connect to the destination server.

    You can change the default list into new one near your country. Get example : if you live in ASIA, choose the Singapore or Japan mirror instead of main mirror. you just change the "archive.ubuntu.com" and "security.ubuntu.com" to "[country_code].archive.ubuntu.com" in file list /etc/apt/sources.list

    Then run sudo apt update to test your new mirror :)

    TIP : you can generate the new source list file based on this online tool : https://repogen.simplylinux.ch/generate.php

  29. #TIL : Run container processes as non-root user

    As default, docker runs your container as root user (uid=0). Although docker isolates your filesystem to protect docker host, but running processes as root is redundant and increasing attacking surface. Even it can make its written files having root owner, which can mess your docker-host filesystem permission.

    This is example that show docker runs as root

    $ docker run --rm alpine sleep 30

    and open another terminal to check this

    $ ps xau | grep 'sleep'
    khanhic+ 15552  0.5  0.4 1264452 49280 pts/1   Sl+  17:37   0:00 docker run --rm alpine:3.9 sleep 30
    root     15610  0.6  0.0   1520     4 ?        Ss   17:37   0:00 sleep 30
    khanhic+ 15876  0.0  0.0  23076  1024 pts/2    S+   17:37   0:00 grep --color=auto sleep

    You can see that the process sleep 30 is running as root with pid = 15610


    To control which user docker container runs as, you can use the --user [userid]:[groupid] argument

    Example

    $ docker run --rm --user 1000:1000 alpine sleep 30

    Then you will get this result

    $ ps xau | grep 'sleep'
    khanhic+ 16275  2.0  0.4 1411916 50124 pts/1   Sl+  17:41   0:00 docker run --rm --user 1000:1000 alpine:3.9 sleep 30
    khanhic+ 16336  1.5  0.0   1520     4 ?        Ss   17:41   0:00 sleep 30
    khanhic+ 16403  0.0  0.0  23076   984 pts/2    S+   17:41   0:00 grep --color=auto sleep

    TIP : you can set a environment variable by add this line to ~/.bash_profile or ~/.bashrc

    export DOCKER_UID="$(id -u ${USER}):$(id -g ${USER})"`

    then use docker command like docker run --user $DOCKER_UID ....

  30. #TIL : Can not get real IP address from Load Balancer SSL Passthrough

    When you use a load balancer stay in front of your app, and use SSL Passthrough mode. You will never get real IP of client, because Load balancer works like a TCP load balancer, which means it can not add extra HTTP headers into encrypted traffic from client when it doesn't handle SSL termination.

    So if you use 1 domain or wildcard subdomains, it's better if you use SSL Termination mode.

  31. #TIL : Export MySQL data by specified query to CSV file

    To export data from MySQL by specified query to CSV file, you can use this command

    $ mysql -B -u username -p database_name -h dbhost -e "SELECT * FROM table_name;" | sed "s/'/\'/;s/\t/\",\"/g;s/^/\"/;s/$/\"/;s/\n//g"

    Tip from : https://stackoverflow.com/a/25427665