Two practical ORACLE procedures for adding/dropping fields

DECLARE
    -- Procedure to add a column if it does not exist
    PROCEDURE add_column_if_not_exists (
        p_owner       VARCHAR2,
        p_table_name  VARCHAR2,
        p_column_name VARCHAR2,
        p_column_type VARCHAR2
    ) IS
        l_sql    VARCHAR2(1024);
        l_count  NUMBER;
    BEGIN
        -- Check if column exists
        SELECT COUNT(*)
        INTO l_count
        FROM ALL_TAB_COLUMNS
        WHERE OWNER = p_owner
          AND TABLE_NAME = p_table_name
          AND COLUMN_NAME = p_column_name;

        -- Add column if it does not exist
        IF l_count = 0 THEN
            l_sql := 'ALTER TABLE ' || p_owner || '.' || p_table_name || ' ADD (' || p_column_name || ' ' || p_column_type || ')';
            DBMS_OUTPUT.PUT_LINE(l_sql);
            EXECUTE IMMEDIATE l_sql;
        END IF;
    END;

    -- Procedure to drop a column if it does exist
    PROCEDURE drop_column_if_exists (
        p_owner       VARCHAR2,
        p_table_name  VARCHAR2,
        p_column_name VARCHAR2
    ) IS
        l_sql    VARCHAR2(1024);
        l_count  NUMBER;
    BEGIN
        -- Check if column exists
        SELECT COUNT(*)
        INTO l_count
        FROM ALL_TAB_COLUMNS
        WHERE OWNER = p_owner
          AND TABLE_NAME = p_table_name
          AND COLUMN_NAME = p_column_name;

        -- drop column if it does exist
        IF l_count = 1 THEN
            l_sql := 'ALTER TABLE ' || p_owner || '.' || p_table_name || ' DROP COLUMN ' || p_column_name;
            DBMS_OUTPUT.PUT_LINE(l_sql);
            EXECUTE IMMEDIATE l_sql;
        END IF;
    END;
BEGIN
...
END;
/

A tip to ease the process creating new feature branch

We use feature/ticket-number-team-abbr as feature branch name, such as feature/mt-1234-axo at work.

If you mainly work with a huge repository. Everytime, you switch to the integration/main branch and run git pull before you create a feature branch, it would take a while as it needs to update your work directory and normally there are a lot of files changed.

I use a different way which is a little bit speedy. Whatever which branch you are now in, run

git fetch && git checkout -b feature/mt-xxxx-axo origin/integration 

It helps but I still need to type the above, which is still a lot of letters! So, I create a bash script to help me more,

see the following command nbi 38546

nbi 38546
remote: Enumerating objects: 22, done.
remote: Counting objects: 100% (22/22), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 22 (delta 17), reused 22 (delta 17), pack-reused 0
Unpacking objects: 100% (22/22), 4.43 KiB | 5.00 KiB/s, done.
From github.com:Euronet-XE/DFX
   941796a8a..11f76bff7  feature/mt-38183-fugu -> origin/feature/mt-38183-fugu
 * [new branch]          feature/mt-39492-ice  -> origin/feature/mt-39492-ice
 * [new branch]          feature/mt-39744-ice-card -> origin/feature/mt-39744-ice-card
branch 'feature/mt-38546-axo' set up to track 'origin/integration'.
David.Wei @ /d/git/xemt-core/dfx - [feature/mt-38546-axo] $

what’s in this nbi script? here it is

cat ~/bin/nbi
#!/bin/bash

# Check if the current directory is a git repository
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
    echo "Error: Current directory is not a git repository."
    exit 1
fi

# Run git fetch to get the latest changes from the remote repository
git fetch

# Determine the action based on the script name
case "$(basename "$0")" in
    nbi)
        # Check if an argument is provided
        if [[ $# -eq 1 ]]; then
            # Run git checkout to create and switch to a new branch based on the integration branch
            git checkout -b "feature/mt-$1-axo" "origin/integration"
            exit $?
        else
            echo "Usage: $0 <anumber>"
            exit 1
        fi
        ;;
    nbm)
        # Check if an argument is provided
        if [[ $# -eq 1 ]]; then
            # Run git checkout to create and switch to a new branch based on the main branch
            git checkout -b "feature/mt-$1-axo" "origin/main"
            exit $?
        else
            echo "Usage: $0 <anumber>"
            exit 1
        fi
        ;;
    *)
        echo "Error: Unsupported script name."
        exit 1
        ;;
esac

why do I call it nbi ? haha, it is a ABBR from new branch from integration

Wait, why did I mention nbm in the script? Yes, you can copy this nbi script with name nbm , then using the nbm command it will work the same way with the repositories that use main as the integration branch.

Did you see? How lazy I am!

2024-07-22 Update: I also wrote another script sw to ease the switching branches operation:

#!/bin/bash

# Check if the user provided a number
if [ -z "$1" ]; then
  echo "Usage: sw <number>"
  exit 1
fi

# Construct the branch name
branch_name="feature/mt-$1-axo"

# Switch to the branch
git switch "$branch_name"

# Check if the switch was successful
if [ $? -eq 0 ]; then
  echo "Switched to branch '$branch_name'"
else
  echo "Failed to switch to branch '$branch_name'"
fi

Sharing pre-request script among Postman collections

I tried a few ways to write a script that can be reused among a few of postman request collections and finally I got a solution I like.

  1. Using const or let keywords to define internal used objects or utility containers. For example
const stpCommon = {
  randomCharacters: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.split(''),
  nonUsdCurrencies: 'GBP,AUD,NZD,CAD,JPY,EUR'.split(','),
  getCurrencyPair: (currency) => `${currency}/USD`,
  getRandomRate: () => (Math.random() * 0.9 + 0.8).toFixed(5),
  getRandomElement: (array) => {
    if (array.length === 0) {
      throw new Error('Cannot get a random element from an empty array');
    }
    const randomIndex = Math.floor(Math.random() * array.length);
    return array[randomIndex];
  },
  getNextWeekday: function (date) {
    const clonedDate = new Date(date);
    const day = clonedDate.getDay();
    if (day === 6) {
      clonedDate.setDate(clonedDate.getDate() + 2);
    } else if (day === 0) {
      clonedDate.setDate(clonedDate.getDate() + 1);
    }
  },
  getBasicRequest: () => {
    const fixedAmountCurrency = stpCommon.getFixedAmountCurrency()
    return {
      currencyPair: stpCommon.getCurrencyPair(fixedAmountCurrency),
      rate: stpCommon.getRandomRate(),
      side: "2",
      fixedAmount: stpCommon.getFixedAmount(),
      ...
    }
...
  1. Using object name itself in its member methods. don't use this keyword, see the last method in above example.
  2. At the end of your script, using a conditional block to expose the object you want in Postman environment, normally one object is enough. For instance:
// the only way to expose an object in postman, is to define a object without any declaration keywords like var/let/const
// the following is a hack to expose the object in postman without troubling Node.js as in Node.js, we are not allowed to
// define a variable without any declaration keywords like var/let/const
if (typeof(pm) !== 'undefined') {
  stpUtils = _stpUtils
}
  1. At the end of your script, utilize conditional exports the objects that you want to use in Node.js environment, this step enables writing test cases for it. For example
const utilsA = {...}
const utilsB = {...}
const _utilsC = {something using utilsA & utilsB}
// provided you want to use utilsC in postman
if (typeof(pm) !== 'undefined') {
  utilsC = _utilsC
}
// and you want to expose all objects in your test cases
if (typeof (module) !== 'undefined') {
  module.exports = {
    utilsA, utilsB, _utilsC
  }
}

This way, you can paste all the script body into the postman scripts tab without trouble, while in Node.js env, you can freely use Jest or other unit test framework to write test cases for your shard library. What a great solution!

Outputing human friendly JSON string with JSON.stringify

method 1:

JSON.stringify(obj, null, '\t');

This "pretty-prints" your JSON string, using a tab for indentation.

method 2:

If you prefer to use spaces instead of tabs, you could also use a number for the number of spaces you'd like :

JSON.stringify(obj, null, 2);

Reference

Generating Android 5-Compatible HTTPS Certificates on Ubuntu and Automating Renewal

In the realm of modern web development, the HTTPS protocol stands as a cornerstone of security. While Let's Encrypt provides SSL certificates at no cost, their certificate chains don't always play well with older Android versions, such as Android 5. This guide delves into the art of generating Android 5-compatible certificates and automating their renewal.

Step 1: Install Certbot and Nginx

Before embarking on this quest, ensure that your Ubuntu host has Certbot and Nginx installed.

sudo apt-get update
sudo apt-get install certbot nginx

Step 2: Download ISRG Root X1 Certificate

From the Let's Encrypt website, procure the latest ISRG Root X1 certificate and store it in a designated directory.

sudo wget -O /etc/letsencrypt/isrgrootx1.pem https://letsencrypt.org/certs/isrgrootx1.pem

Step 3: Craft the Automation Script

Forge a script named "update-certificates.sh" that automatically generates an Android 5-compatible certificate chain and reloads Nginx's configuration whenever Certbot renews the certificate.

#!/bin/bash

DOMAIN="yourdomain.com" 
CERT_DIR="/etc/letsencrypt/live/$DOMAIN"
FULLCHAIN="$CERT_DIR/fullchain.pem"
PRIVKEY="$CERT_DIR/privkey.pem"
ANDROID_FULLCHAIN="$CERT_DIR/fullchain-android.pem"
ISRG_ROOT="/etc/letsencrypt/isrgrootx1.pem"

# Generate Android5-compatible certificate chain
sudo cat $FULLCHAIN $ISRG_ROOT | sudo tee $ANDROID_FULLCHAIN > /dev/null

# Reload Nginx configuration
sudo systemctl reload nginx

Remember to replace "yourdomain.com" with your actual domain name and verify the paths are correct.

Step 4: Grant Script Execution Privileges

sudo chmod +x /path/to/update-certificates.sh

Step 5: Configure Certbot Renewal Hook

Certbot allows running custom hook scripts upon certificate renewal. Configure the aforementioned script as Certbot's "--deploy-hook" hook.

Edit Certbot's renewal configuration file (usually located at /etc/letsencrypt/renewal/yourdomain.com.conf):

renew_hook = /path/to/update-certificates.sh

Alternatively, configure it using Certbot's command-line option:

sudo certbot renew --deploy-hook /path/to/update-certificates.sh

Step 6: Set Up Automatic Renewal

Certbot by default sets up a cron job or systemd timer to automatically renew certificates. Verify this using the following command:

sudo systemctl list-timers | grep certbot

If no automatic renewal task exists, add a cron job manually:

sudo crontab -e

In the crontab file, add the following line to perform a renewal check daily:

0 2 * * * /usr/bin/certbot renew --deploy-hook /path/to/update-certificates.sh

Conclusion

By following these steps, you can ensure that HTTPS certificates generated using Let's Encrypt are compatible with Android 5 and automate certificate renewal. This eliminates the need to manually renew certificates every three months, significantly streamlining website maintenance.