<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Gyri Infotech | Tech Memoir]]></title><description><![CDATA[Thoughts, stories and ideas.]]></description><link>https://blog.gyri.tech/</link><image><url>https://blog.gyri.tech/favicon.png</url><title>Gyri Infotech | Tech Memoir</title><link>https://blog.gyri.tech/</link></image><generator>Ghost 5.82</generator><lastBuildDate>Thu, 09 Apr 2026 13:01:24 GMT</lastBuildDate><atom:link href="https://blog.gyri.tech/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[The Complete Guide to Setting Up SSH Keys on Ubuntu, Windows, and macOS (With GitLab Integration)]]></title><description><![CDATA[<h1 id="step-1-understand-what-ssh-is">Step 1: Understand What SSH Is</h1><p>SSH stands for <strong>Secure Shell</strong>.<br>It is a secure protocol used to:</p><ul><li>Connect to remote servers</li><li>Clone Git repositories</li><li>Push and pull code securely</li><li>Deploy applications</li></ul><p>Instead of using passwords, SSH uses <strong>cryptographic key pairs</strong>.</p><hr><h1 id="step-2-understand-ssh-key-pair">Step 2: Understand SSH Key Pair</h1><p>When you create</p>]]></description><link>https://blog.gyri.tech/the-complete-guide-to-setting-up-ssh-keys-on-ubuntu-windows-and-macos-with-git-lab-integration/</link><guid isPermaLink="false">6996ab4de63ce6040c22526c</guid><dc:creator><![CDATA[AsifDesai]]></dc:creator><pubDate>Thu, 19 Feb 2026 06:51:15 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2026/02/git-lab-ssh-key.webp" medium="image"/><content:encoded><![CDATA[<h1 id="step-1-understand-what-ssh-is">Step 1: Understand What SSH Is</h1><img src="https://blog.gyri.tech/content/images/2026/02/git-lab-ssh-key.webp" alt="The Complete Guide to Setting Up SSH Keys on Ubuntu, Windows, and macOS (With GitLab Integration)"><p>SSH stands for <strong>Secure Shell</strong>.<br>It is a secure protocol used to:</p><ul><li>Connect to remote servers</li><li>Clone Git repositories</li><li>Push and pull code securely</li><li>Deploy applications</li></ul><p>Instead of using passwords, SSH uses <strong>cryptographic key pairs</strong>.</p><hr><h1 id="step-2-understand-ssh-key-pair">Step 2: Understand SSH Key Pair</h1><p>When you create an SSH key, two files are generated:</p>
<!--kg-card-begin: html-->
<table data-start="876" data-end="1025" class="w-fit min-w-(--thread-content-width)"><thead data-start="876" data-end="894"><tr data-start="876" data-end="894"><th data-start="876" data-end="883" data-col-size="sm" class>File</th><th data-start="883" data-end="894" data-col-size="sm" class>Purpose</th></tr></thead><tbody data-start="915" data-end="1025"><tr data-start="915" data-end="973"><td data-start="915" data-end="930" data-col-size="sm"><code data-start="917" data-end="929">id_ed25519</code></td><td data-col-size="sm" data-start="930" data-end="973">Private key (Keep secret &#x274C; Never share)</td></tr><tr data-start="974" data-end="1025"><td data-start="974" data-end="993" data-col-size="sm"><code data-start="976" data-end="992">id_ed25519.pub</code></td><td data-col-size="sm" data-start="993" data-end="1025">Public key (Safe to share &#x2705;)</td></tr></tbody></table>
<!--kg-card-end: html-->
<p>How it works:</p><ol><li>You give your <strong>public key</strong> to GitLab.</li><li>GitLab stores it.</li><li>When you connect, GitLab verifies you using your <strong>private key</strong>.</li><li>If matched &#x2192; Access granted.<br><br>No password needed.</li></ol><hr><h1 id="step-3-why-we-use-ssh-in-gitlab">Step 3: Why We Use SSH in GitLab</h1><p>GitLab is used for:</p><ul><li>Hosting repositories</li><li>CI/CD</li><li>Version control</li><li>Collaboration</li></ul><p>Without SSH (using HTTPS):</p><p><code>git clone</code> https://gitlab.com/username/project.git<br>Every push asks for username &amp; password.</p><p>With SSH:<code>git clone</code> git@gitlab.com:username/project.git<br>Push &#x2192; Done instantly &#x1F680;<br>That&#x2019;s why developers prefer SSH.<br></p><hr><h1 id="step-4-generate-ssh-key-on-ubuntu">Step 4: Generate SSH Key on Ubuntu</h1><h3 id="1%EF%B8%8F%E2%83%A3-open-terminal">1&#xFE0F;&#x20E3; Open Terminal</h3><p>Press:<code>Ctrl + Alt + T</code><br><br>2&#xFE0F;&#x20E3; Check If SSH Already Exists<br><code>ls</code> -al ~/.ssh<br>If folder doesn&#x2019;t exist &#x2192; no problem.</p><h3 id="3%EF%B8%8F%E2%83%A3-generate-new-ssh-key">3&#xFE0F;&#x20E3; Generate New SSH Key</h3><p>Recommended modern algorithm:<br><code>ssh-keygen -t ed25519 -C &quot;your_email@example.com&quot;</code><br>If not supported:<br><code>ssh-keygen -t rsa -b 4096 -C &quot;your_email@example.com&quot;</code><br>Press Enter to accept default location:<code>/home/username/.ssh/</code>id_ed25519<br>Set passphrase (optional but recommended).<br><br>4&#xFE0F;&#x20E3; Start SSH Agent<br><code>eval &quot;$(ssh-agent -s)</code>&quot;<br><br>5&#xFE0F;&#x20E3; Add Key to SSH Agent<br>ssh-add ~/.ssh/id_ed25519</p><p>6&#xFE0F;&#x20E3; Copy Public Key<br><code>cat</code> ~/.ssh/id_ed25519.pub<br>Copy the entire output.</p><hr><h1 id="step-5-generate-ssh-key-on-windows">Step 5: Generate SSH Key on Windows</h1><h2 id="option-1-using-git-bash-recommended">Option 1: Using Git Bash (Recommended)</h2><ol><li>Install Git for Windows</li><li>Open Git Bash</li></ol><p>Run:<code>ssh-keygen -t ed25519 -C &quot;your_email@example.com&quot;</code><br>Keys are stored in:<code>C:\Users\YourUsername\.ssh\</code><br><br>View public key:<code>cat</code> ~/.ssh/id_ed25519.pub<br></p><h2 id="option-2-using-powershell">Option 2: Using PowerShell</h2><p>Open PowerShell and run:</p><p><code>ssh-keygen -t ed25519 -C &quot;your_email@example.com&quot;</code><br>Same process..</p><hr><h1 id="step-6-generate-ssh-key-on-macos">Step 6: Generate SSH Key on macOS</h1><h3 id="1%EF%B8%8F%E2%83%A3-open-terminal-1">1&#xFE0F;&#x20E3; Open Terminal</h3><p><code>Cmd + Space &#x2192; Terminal</code></p><h3 id="2%EF%B8%8F%E2%83%A3-generate-key">2&#xFE0F;&#x20E3; Generate Key</h3><p><code>ssh-keygen -t ed25519 -C &quot;your_email@example.com&quot;</code></p><h3 id="3%EF%B8%8F%E2%83%A3-start-ssh-agent">3&#xFE0F;&#x20E3; Start SSH Agent</h3><p><code>eval &quot;$(ssh-agent -s)</code>&quot;</p><h3 id="4%EF%B8%8F%E2%83%A3-add-key">4&#xFE0F;&#x20E3; Add Key</h3><p>ssh-add ~/.ssh/id_ed25519</p><h3 id="5%EF%B8%8F%E2%83%A3-copy-public-key">5&#xFE0F;&#x20E3; Copy Public Key</h3><p><code>pbcopy &lt; ~/.ssh/id_ed25519.pub</code></p><hr><h1 id="step-7-add-ssh-key-to-gitlab">Step 7: Add SSH Key to GitLab</h1><ol><li>Login to GitLab</li><li>Click Profile &#x2192; Preferences</li><li>Click <strong>SSH Keys</strong></li><li>Paste copied public key</li><li>Click <strong>Add Key</strong></li></ol><p>Now GitLab trusts your machine.</p><hr><h1 id="step-8-test-ssh-connection">Step 8: Test SSH Connection</h1><p>Run: ssh -T git@gitlab.com<br>If successful, you&#x2019;ll see:<code>Welcome to GitLab!</code></p><hr><h1 id="step-9-clone-using-ssh">Step 9: Clone Using SSH</h1><p>Instead of HTTPS:<br><code>git clone</code> git@gitlab.com:username/project.git<br>Now: git push<br>No password required.</p><hr><h1 id="step-10-security-best-practices"><strong>Ste</strong>p 10: Security Best Practices</h1><p>&#x2714; Use <code>ed25519</code><br>&#x2714; Protect private key<br>&#x2714; Add passphrase<br>&#x2714; Never share private key<br>&#x2714; Set correct permissions (Linux/macOS):</p><p><code>chmod</code> 700 ~/.ssh<br><code>chmod 600 ~/.ssh/id_ed25519</code></p><hr><h1 id="bonus-how-ssh-works-internally">Bonus: How SSH Works Internally</h1><ol><li>Client connects to GitLab</li><li>GitLab checks public key</li><li>GitLab sends encrypted challenge</li><li>Your private key decrypts it</li><li>Verified &#x2192; Access granted</li></ol><p>This uses asymmetric cryptography &#x2014; extremely secure.</p><hr><h1 id="final-conclusion">Final Conclusion</h1><p>Setting up SSH keys:</p><ul><li>Improves security</li><li>Removes password dependency</li><li>Enables automation</li><li>Essential for DevOps</li><li>Industry best practice</li></ul><p>If you are serious about professional development, SSH is not optional &#x2014; it&#x2019;s foundational</p>]]></content:encoded></item><item><title><![CDATA[A Complete Guide to Migrating to Spring Boot 4.0.1]]></title><description><![CDATA[<h2 id="technical-guide-upgrading-to-spring-boot-401">Technical Guide: Upgrading to Spring Boot 4.0.1</h2>
<p>This guide outlines the technical requirements, dependency changes, and configuration updates necessary to migrate a Spring Boot application to version 4.0.1.</p>
<p><strong>1. Build Environment Update</strong><br>
Before updating application dependencies, ensure your build tool is compatible with Spring Boot 4.</p>]]></description><link>https://blog.gyri.tech/spring-boot/</link><guid isPermaLink="false">697c526cec9cb1040bfad28c</guid><dc:creator><![CDATA[Aditya Methe]]></dc:creator><pubDate>Fri, 30 Jan 2026 07:27:01 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2026/01/spring_boot_4.jpg" medium="image"/><content:encoded><![CDATA[<h2 id="technical-guide-upgrading-to-spring-boot-401">Technical Guide: Upgrading to Spring Boot 4.0.1</h2>
<img src="https://blog.gyri.tech/content/images/2026/01/spring_boot_4.jpg" alt="A Complete Guide to Migrating to Spring Boot 4.0.1"><p>This guide outlines the technical requirements, dependency changes, and configuration updates necessary to migrate a Spring Boot application to version 4.0.1.</p>
<p><strong>1. Build Environment Update</strong><br>
Before updating application dependencies, ensure your build tool is compatible with Spring Boot 4.0.1.<br>
<strong>Gradle Version</strong>: Update to Gradle 9.2.1. This version is required to support the updated Spring Boot plugins and dependencies.</p>
<p><strong>2. Dependency Management</strong><br>
Update your build.gradle.kts dependencies to their compatible versions.<br>
<strong>Spring Boot Starters</strong>: Ensure all standard starters are updated to the managed versions provided by the Spring Boot 4.0.1 BOM:</p>
<ul>
<li>spring-boot-starter-actuator</li>
<li>spring-boot-starter-data-mongodb-reactive</li>
<li>spring-boot-starter-security</li>
<li>spring-boot-starter-webflux</li>
<li>spring-boot-starter-oauth2-client</li>
</ul>
<p><strong>Third-Party Library Updates</strong>: You must manually update several key libraries to versions compatible with Spring Boot 4:</p>
<ul>
<li>Redisson: Update redisson-spring-boot-starter to 4.1.0.</li>
<li>SpringDoc OpenAPI (Swagger): Update to springdoc-openapi-starter-webflux-ui:3.0.1 to maintain API documentation support.</li>
</ul>
<p><strong>3. Breaking Change: Jackson 3 Migration</strong><br>
Spring Boot 4.0.1 moves from Jackson 2 to Jackson 3, which introduces significant namespace changes. This will cause compilation errors until references are updated.</p>
<p><strong>Dependency Changes</strong>: Replace the standard Kotlin module dependency:</p>
<p>// Remove or Update<br>
<code>implementation(&quot;com.fasterxml.jackson.module:jackson-module-kotlin&quot;)</code><br>
// Add New Tools Module<br>
<code>implementation(&quot;tools.jackson.module:jackson-module-kotlin&quot;)</code><br>
// Maintain backward compatibility if needed for specific libs<br>
<code>implementation(&quot;com.fasterxml.jackson.module:jackson-module-kotlin:2.20.1&quot;)</code></p>
<p><strong>Code Refactoring Required</strong>:</p>
<ul>
<li>
<p>Imports: Update your import statements for the object mapper.<br>
From: <code>com.fasterxml.jackson.module.kotlin.jacksonObjectMapper</code><br>
To: <code>tools.jackson.module.kotlin.jacksonObjectMapper</code></p>
</li>
<li>
<p>Codecs: If you are using Redisson or custom codecs, update <code>TypedJsonJacksonCodec</code> to <code>TypedJsonJackson3Codec</code>.</p>
</li>
</ul>
<p><strong>4. Configuration Properties Updates</strong><br>
Several properties in application.properties have been renamed or require explicit enablement.<br>
<strong>MongoDB Configuration:</strong> The <strong>spring.data.mongodb</strong> prefix has been simplified. You must remove the <strong>.data</strong> segment from your keys.</p>
<p>Full Mapping:</p>
<pre><code>spring.mongodb.host=192.168.1.122
spring.mongodb.port=27017
spring.mongodb.database=AppDBTest
spring.mongodb.username=admin
spring.mongodb.password=secret
spring.mongodb.authentication-database=admin
</code></pre>
<p><strong>Swagger UI</strong>: SpringDoc now requires explicit properties to enable the UI and API docs endpoints:</p>
<pre><code>springdoc.api-docs.enabled=true
springdoc.swagger-ui.enabled=true
</code></pre>
<p><strong>5. Tooling: Replacing Ktlint with Spotless</strong><br>
Older versions of ktlint may not be supported in this environment. It is recommended to switch to the Spotless Gradle plugin for code formatting.</p>
<p><strong>Implementation</strong>: Add the plugin to your build.gradle.kts:</p>
<pre><code>plugins {
    id(&quot;com.diffplug.spotless&quot;) version &quot;8.1.0&quot;
}
</code></pre>
<p><strong>Configuration</strong>: Configure Spotless to handle both Kotlin and Java formatting:</p>
<pre><code>spotless {
  kotlin {
    target(&quot;**/*.kt&quot;)
    ktfmt(&quot;0.53&quot;)
    trimTrailingWhitespace()
    endWithNewline()
  }

  java {
    target(&quot;**/*.java&quot;)
    googleJavaFormat(&quot;1.19.2&quot;)
    removeUnusedImports()
    trimTrailingWhitespace()
    endWithNewline()
  }

  // Bind to compilation tasks
  tasks.named(&quot;spotlessCheck&quot;) { dependsOn(&quot;compileKotlin&quot;, &quot;compileJava&quot;) }
  tasks.named(&quot;spotlessApply&quot;) { dependsOn(&quot;compileKotlin&quot;, &quot;compileJava&quot;) }
}
</code></pre>
<hr>]]></content:encoded></item><item><title><![CDATA[Nextcloud: The Master-Slave Guide]]></title><description><![CDATA[<h2 id="nextcloud-manual-setup-steps"><strong>Nextcloud manual setup steps</strong></h2><ul>
<li><strong>Update and Install Dependencies</strong></li>
</ul>
<p>We need the web server and all PHP modules required by Nextcloud.<br>
Run this on Server 1 AND Server 2:</p>
<pre><code>sudo apt update

sudo apt install apache2 php libapache2-mod-php php-mysql php-common php-gd php-curl php-imagick php-mbstring php-xml php-zip php-intl php-bcmath php-gmp unzip -y</code></pre>]]></description><link>https://blog.gyri.tech/nextcloud/</link><guid isPermaLink="false">69520346ec9cb1040bfad198</guid><dc:creator><![CDATA[Aditya Methe]]></dc:creator><pubDate>Mon, 29 Dec 2025 06:05:43 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2025/12/nextcloud-scaled.jpg" medium="image"/><content:encoded><![CDATA[<h2 id="nextcloud-manual-setup-steps"><strong>Nextcloud manual setup steps</strong></h2><ul>
<li><strong>Update and Install Dependencies</strong></li>
</ul>
<img src="https://blog.gyri.tech/content/images/2025/12/nextcloud-scaled.jpg" alt="Nextcloud: The Master-Slave Guide"><p>We need the web server and all PHP modules required by Nextcloud.<br>
Run this on Server 1 AND Server 2:</p>
<pre><code>sudo apt update

sudo apt install apache2 php libapache2-mod-php php-mysql php-common php-gd php-curl php-imagick php-mbstring php-xml php-zip php-intl php-bcmath php-gmp unzip -y
</code></pre>
<ul>
<li><strong>Enable Required Apache Modules</strong></li>
</ul>
<p>Run this on Server 1 AND Server 2:</p>
<pre><code>sudo a2enmod rewrite headers env dir mime
sudo systemctl restart apache2
</code></pre>
<ul>
<li><strong>Create the Directory Structure</strong></li>
</ul>
<p>Run this on Server 1 AND Server 2:</p>
<pre><code>sudo mkdir -p /var/www/html/nextcloud
sudo mkdir -p /var/www/html/nextcloud/data
</code></pre>
<ul>
<li><strong>Set Permissions</strong></li>
</ul>
<p>Apache (www-data) must own these folders to write files.<br>
Run this on Server 1 AND Server 2:</p>
<pre><code>sudo chown -R www-data:www-data /var/www/html/nextcloud
sudo chmod -R 755 /var/www/html/nextcloud
</code></pre>
<ul>
<li><strong>Download and Unzip</strong></li>
</ul>
<p>Run this on Server 1 AND Server 2:</p>
<pre><code>cd /var/www/html
sudo wget https://download.nextcloud.com/server/releases/latest.zip
sudo unzip latest.zip
</code></pre>
<p>unzip will extract everything into the nextcloud folder we created</p>
<ul>
<li><strong>Final Permission Fix</strong></li>
</ul>
<p>Ensure the new files are owned by the web server.</p>
<pre><code>sudo chown -R www-data:www-data /var/www/html/nextcloud
</code></pre>
<ul>
<li><strong>Configure Apache</strong></li>
</ul>
<p>Run this on Server 1 AND Server 2 to create the web config:</p>
<pre><code>sudo nano /etc/apache2/sites-available/nextcloud.conf
</code></pre>
<p>Paste this content:</p>
<pre><code>&lt;VirtualHost *:80&gt;
    DocumentRoot /var/www/html/nextcloud
    
    &lt;Directory /var/www/html/nextcloud&gt;
        Require all granted
        AllowOverride All
        Options FollowSymLinks MultiViews
    &lt;/Directory&gt;
&lt;/VirtualHost&gt;
</code></pre>
<p>Enable the site:</p>
<pre><code>sudo a2ensite nextcloud.conf
sudo a2enmod rewrite
sudo systemctl restart apache2
</code></pre>
<hr><h2 id="database-setup-master-slave-replication"><strong>Database setup (Master slave replication)</strong></h2><ul>
<li><strong>Set up the Master (Server 1 Only)</strong></li>
</ul>
<ol>
<li>Secure the installation:</li>
</ol>
<pre><code>sudo mysql\_secure\_installation
</code></pre>
<ol start="2">
<li>Create the Database:</li>
</ol>
<pre><code>sudo mysql -u root -p
</code></pre>
<pre><code>CREATE DATABASE nextcloud;
CREATE USER &apos;nc\_admin&apos;@&apos;localhost&apos; IDENTIFIED BY &apos;12345&apos;;
GRANT ALL PRIVILEGES ON nextcloud.\* TO &apos;nc\_admin&apos;@&apos;localhost&apos;;

-- Create the User for Server 2 to copy data

CREATE USER &apos;replicator&apos;@&apos;%&apos; IDENTIFIED BY &apos;password&apos;;
GRANT REPLICATION SLAVE ON \*.\* TO &apos;replicator&apos;@&apos;%&apos;;
FLUSH PRIVILEGES;
</code></pre>
<ol start="3">
<li>Enable Binary Logging: Edit /etc/mysql/mariadb.conf.d/50-server.cnf:</li>
</ol>
<pre><code>bind-address = 0.0.0.0
server-id    = 1
log\_bin      = /var/log/mysql/mysql-bin.log
binlog\_do\_db = nextcloud
</code></pre>
<p>Restart &amp; Get Coordinates:</p>
<pre><code>sudo systemctl restart mysql
sudo mysql -u root -p -e &quot;SHOW MASTER STATUS;&quot;
</code></pre>
<ul>
<li><strong>Set up the Slave (Server 2 Only)</strong></li>
</ul>
<ol>
<li>
<p>Do NOT create a database. (It will copy Server 1&apos;s DB).</p>
</li>
<li>
<p>Configure Config File: Edit /etc/mysql/mariadb.conf.d/50-server.cnf:</p>
</li>
</ol>
<pre><code>bind-address = 0.0.0.0
server-id    = 2
log\_bin      = /var/log/mysql/mysql-bin.log
binlog\_do\_db = nextcloud
</code></pre>
<ol start="3">
<li>Restart:</li>
</ol>
<pre><code>sudo systemctl restart mysql
</code></pre>
<ul>
<li><strong>Connect Slave to Master</strong></li>
</ul>
<ol>
<li>Run on Server 2: Note: If servers are on different networks (192.168 vs 10.10), open the SSH tunnel first.</li>
</ol>
<pre><code>STOP SLAVE;
CHANGE MASTER TO
MASTER\_HOST=&apos;192.168.0.135&apos;, -- Or &apos;127.0.0.1&apos; if using Tunnel
MASTER\_USER=&apos;replicator&apos;,
MASTER\_PASSWORD=&apos;password&apos;,
MASTER\_LOG\_FILE=&apos;mysql-bin.000001&apos;, -- From Phase 2, Step 1
MASTER\_LOG\_POS=123;                 -- From Phase 2, Step 1
START SLAVE;
</code></pre>
<ol start="2">
<li>Copy Config to Server 2:</li>
</ol>
<p>On Server 1: Open /var/www/html/nextcloud/config/config.php and copy the text.</p>
<pre><code>sudo nano /var/www/html/nextcloud/config/config.php
</code></pre>
<p>On Server 2: Create the file:<br>
Paste the text.</p>
<pre><code>sudo nano /var/www/html/nextcloud/config/config.php
</code></pre>
<p>Important Change: Find the line &apos;dbhost&apos; =&gt; &apos;localhost&apos;, and ensure it stays localhost (so Server 2 reads its own replica DB).</p>
<hr><h2 id="file-synchronization-unison"><strong>File Synchronization (Unison)</strong></h2><ul>
<li><strong>Install Unison (Both Servers)</strong></li>
</ul>
<pre><code>sudo apt install unison -y
</code></pre>
<ul>
<li><strong>Configure SSH Key (Server 2 -&gt; Server 1)</strong></li>
</ul>
<pre><code>ssh-keygen -t ed25519
ssh-copy-id gyriexp@192.168.0.135
</code></pre>
<ul>
<li><strong>Create Sync Profile (Server 2)</strong><br>
Create ~/.unison/default.prf:</li>
</ul>
<pre><code>root = /var/www/html/nextcloud/data
root = ssh://gyriexp@192.168.0.135//var/www/html/nextcloud/data
auto = true
batch = true
prefer = newer
perms = 0     # Ignore permissions
</code></pre>
<ul>
<li><strong>Running the Sync</strong></li>
</ul>
<p>Run this sequence whenever you want to backup files:</p>
<ol>
<li>On Server 1 (Unlock):</li>
</ol>
<pre><code>sudo chown -R gyriexp:gyriexp /var/www/html/nextcloud/data
</code></pre>
<ol start="2">
<li>On Server 2 (Sync):</li>
</ol>
<pre><code>unison default
</code></pre>
<ol start="3">
<li>On Server 1 (Lock):</li>
</ol>
<pre><code>sudo chown -R www-data:www-data /var/www/html/nextcloud/data
</code></pre>
<h2 id="special-conditions"><strong>Special conditions</strong></h2><h3 id="server-1-fails-%E2%86%92-make-server-2-master-%E2%86%92-restore-server-1-as-slave">Server 1 Fails &#x2192; Make Server 2 Master &#x2192; Restore Server 1 as Slave</h3>
<ul>
<li><strong>Promote Server 2</strong></li>
</ul>
<p>Run these on Server 2 to make it the independent Master.</p>
<p>1.Stop it from looking for the dead master:</p>
<pre><code>sudo mysql -u root -p -e &quot;STOP SLAVE; RESET SLAVE ALL;&quot;
</code></pre>
<p>2.Enable Writes (if Read-Only was on)</p>
<pre><code>sudo mysql -u root -p -e &quot;SET GLOBAL read\_only = OFF;&quot;
</code></pre>
<ol start="3">
<li>Update Nextcloud Config: Ensure config.php points to localhost.</li>
</ol>
<pre><code>sudo nano /var/www/html/nextcloud/config/config.php
</code></pre>
<p>Ensure: &apos;dbhost&apos; =&gt; &apos;localhost&apos;,</p>
<ul>
<li><strong>Server 1 is Back (Make it the Slave)</strong></li>
</ul>
<ol>
<li>Create Backup on Server 2:</li>
</ol>
<p>Run on Server 2</p>
<pre><code>sudo mysqldump -u root -p --all-databases --master-data &gt; production\_backup.sql
</code></pre>
<ol start="2">
<li>Send Backup to Server 1:</li>
</ol>
<p>Run on Server 2</p>
<pre><code>scp production\_backup.sql user@server1\_ip:/home/user/
</code></pre>
<ol start="3">
<li>Import &amp; Configure Slave on Server 1:</li>
</ol>
<p>Run on Server 1</p>
<pre><code>sudo mysql -u root -p &lt; /home/user/production\_backup.sql
</code></pre>
<ol start="4">
<li>Start Replication on Server 1:</li>
</ol>
<p>Run on server 2</p>
<pre><code>sudo mysql -u root -p -e &quot;SHOW MASTER STATUS;&quot;
</code></pre>
<p>Run on server 1</p>
<pre><code>sudo mysql -u root -p
STOP SLAVE;
CHANGE MASTER TO
MASTER\_HOST=&apos;Server2\_IP&apos;,
MASTER\_USER=&apos;replicator&apos;,
MASTER\_PASSWORD=&apos;password&apos;,
MASTER\_LOG\_FILE=&apos;mysql-bin.00000X&apos;,  -- Value from Server 2
MASTER\_LOG\_POS=1234;                 -- Value from Server 2
START SLAVE;
</code></pre>
<ul>
<li><strong>Correct Unison File Sync</strong></li>
</ul>
<p>Since Unison is bidirectional, you usually don&apos;t need to change the command if the config is on Server 2. It will detect Server 2 has &quot;New files&quot; and Server 1 is empty/old, and it will push them automatically.</p>
<pre><code>\# Unlock Server 1
ssh user@server1\_ip &quot;sudo chown -R user:user /var/www/html/nextcloud/data&quot;

\# Run Sync (Pushes S2 data to S1)
unison default

\# Lock Server 1
ssh user@server1\_ip &quot;sudo chown -R www-data:www-data /var/www/html/nextcloud/data&quot;
</code></pre>
<p></p><h3 id="normal-chain-is-s1-%E2%86%92-s2-%E2%86%92-s3-server-2-dies-you-need-s1-to-sync-directly-to-s3-when-s2-returns-s3-updates-s2">Normal chain is S1 &#x2192; S2 &#x2192; S3. Server 2 dies. You need S1 to sync directly to S3. When S2 returns, S3 updates S2.</h3>
<ul>
<li><strong>Bypass Server 2 (S2 is Down)</strong></li>
</ul>
<ol>
<li>Create a &quot;Bypass Profile&quot; on Server 3:</li>
</ol>
<pre><code>nano ~/.unison/bypass.prf
</code></pre>
<p>Paste this:</p>
<pre><code>\# Sync Local (S3) with Server 1 (Skipping S2)
root = /var/www/html/nextcloud/data
root = ssh://user@server1\_ip//var/www/html/nextcloud/data
auto = true
batch = true
prefer = newer
perms = 0
</code></pre>
<ol start="2">
<li>Run the Bypass Sync: Run this on Server 3 whenever S2 is down.</li>
</ol>
<pre><code>\# 1. Unlock Server 1
ssh user@server1\_ip &quot;sudo chown -R user:user /var/www/html/nextcloud/data&quot;

\# 2. Sync S3 &lt;-&gt; S1
unison bypass

\# 3. Lock Server 1
ssh user@server1\_ip &quot;sudo chown -R www-data:www-data /var/www/html/nextcloud/data&quot;
</code></pre>
<ul>
<li><strong>Server 2 is Back (Restore &amp; Fill Gaps)</strong></li>
</ul>
<ol>
<li>Sync S3 &#x2192; S2 (Run on Server 3): We create a temporary profile to push data back to the recovered Server 2.</li>
</ol>
<pre><code>nano ~/.unison/restore\_s2.prf
</code></pre>
<pre><code>\# Sync Local (S3) to Server 2
root = /var/www/html/nextcloud/data
root = ssh://user@server2\_ip//var/www/html/nextcloud/data
auto = true
batch = true
prefer = newer
perms = 0
</code></pre>
<ol start="2">
<li>Run the Restore Sync:</li>
</ol>
<pre><code>\# 1. Unlock Server 2 (The recovered server)
ssh user@server2\_ip &quot;sudo chown -R user:user /var/www/html/nextcloud/data&quot;

\# 2. Sync S3 -&gt; S2
unison restore\_s2

\# 3. Lock Server 2
ssh user@server2\_ip &quot;sudo chown -R www-data:www-data /var/www/html/nextcloud/data&quot;
</code></pre>
<ol start="3">
<li>Resume Normal Operations: Now that S2 is up-to-date, you can go back to your original setup (S1 syncing to S2, and S2 syncing to S3).</li>
</ol>
<ul>
<li><strong>step 1: Verify Server 2 is &quot;Clean&quot;</strong></li>
</ul>
<p>Run on Server 2:</p>
<pre><code>ls -l /var/www/html/nextcloud/data/
</code></pre>
<ul>
<li><strong>Step 2: Resume Server 1 &#x2192; Server 2 Sync</strong></li>
</ul>
<ol>
<li>Unlock Server 2<br>
Run on Server 1</li>
</ol>
<pre><code>ssh user@server2_ip &quot;sudo chown -R user:user /var/www/html/nextcloud/data&quot;
</code></pre>
<ol start="2">
<li>ssh user@server2_ip &quot;sudo chown -R user:user /var/www/html/nextcloud/data&quot;</li>
</ol>
<pre><code>unison default
</code></pre>
<ol start="3">
<li>Lock Server 2:</li>
</ol>
<pre><code>ssh user@server2_ip &quot;sudo chown -R www-data:www-data /var/www/html/nextcloud/data&quot;
</code></pre>
<ul>
<li><strong>Step 3: Resume Server 2 &#x2192; Server 3 Sync</strong></li>
</ul>
<p>Now that S2 is updated by S1, it passes those updates down to S3.<br>
Run on Server 2: (Assuming you have a profile named sync_to_s3 that points to S3).</p>
<p>Unlock Server 3:</p>
<pre><code>ssh user@server3_ip &quot;sudo chown -R user:user /var/www/html/nextcloud/data&quot;
</code></pre>
<p>Run the Standard Sync:</p>
<pre><code>unison sync_to_s3
</code></pre>
<p>Lock Server 3:</p>
<pre><code>ssh user@server3_ip &quot;sudo chown -R www-data:www-data /var/www/html/nextcloud/data&quot;
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Designing a Reactive, Event-Driven Email OTP Login System with CQRS and Event Sourcing]]></title><description><![CDATA[<p>Modern authentication systems are moving beyond passwords. With the rise of <strong>passwordless authentication</strong>, <strong>One-Time Passwords (OTPs)</strong> delivered over email or SMS are becoming a trusted standard for secure and seamless user logins.</p><p>However, implementing an OTP-based login mechanism that is <strong>scalable, reactive, and audit-compliant</strong> requires more than a simple verification</p>]]></description><link>https://blog.gyri.tech/building-a-reactive-event-driven-otp-login-system-with-spring-boot-cqrs-event-sourcing/</link><guid isPermaLink="false">691303ee7c25ab040d0c2db6</guid><dc:creator><![CDATA[Sangram Ekshinge]]></dc:creator><pubDate>Tue, 11 Nov 2025 11:06:10 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2025/11/Screenshot-2025-11-11-163110-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.gyri.tech/content/images/2025/11/Screenshot-2025-11-11-163110-1.png" alt="Designing a Reactive, Event-Driven Email OTP Login System with CQRS and Event Sourcing"><p>Modern authentication systems are moving beyond passwords. With the rise of <strong>passwordless authentication</strong>, <strong>One-Time Passwords (OTPs)</strong> delivered over email or SMS are becoming a trusted standard for secure and seamless user logins.</p><p>However, implementing an OTP-based login mechanism that is <strong>scalable, reactive, and audit-compliant</strong> requires more than a simple verification service. It demands an architecture that supports <strong>asynchronous workflows</strong>, <strong>event traceability</strong>, and <strong>reactive scalability</strong> &#x2014; all while keeping the system maintainable and extendable.</p><p>In this post, we&#x2019;ll walk through the <strong>conceptual design</strong> of a <strong>Reactive OTP Login System</strong> built using <strong>Spring Boot</strong>, <strong>CQRS (Command Query Responsibility Segregation)</strong>, and <strong>Event Sourcing</strong>.<br>We&#x2019;ll focus purely on the architecture and system flow &#x2014; without diving into code &#x2014; to illustrate how such a system achieves <strong>high throughput</strong>, <strong>clean separation of concerns</strong>, and <strong>full auditability</strong>.</p><h2 id="architectural-overview">Architectural Overview</h2><p>At its core, this system adopts a <strong>Reactive CQRS + Event Sourcing</strong> design:</p><ul><li><strong>Commands</strong> modify the system state (e.g., logging in a user).</li><li><strong>Queries</strong> fetch or compute data without altering state (e.g., generating OTP for a user).</li><li><strong>Events</strong> record every state transition as an immutable fact.</li><li><strong>Reactive Streams (Mono/Flux)</strong> ensure non-blocking, backpressure-aware execution.</li></ul><h2 id="step-1-requesting-an-email-otp">Step 1: Requesting an Email OTP</h2><p>The OTP generation process begins when a user initiates a login using their email.</p><h3 id="conceptual-flow">Conceptual Flow</h3><ol><li>The <strong>client</strong> sends a request to <code>/api/v1/user/request/emailloginotp/{email}</code>.</li><li>The <strong>Query Controller</strong> constructs a <code>RequestEmailLoginOTPQuery</code>, encapsulating the user&#x2019;s email and tenant metadata.</li><li>The <strong>Query Dispatcher</strong> routes the query to the appropriate <strong>Query Handler</strong>.</li><li>The <strong>Query Handler</strong> verifies whether the user exists and is active using a <strong>reactive repository</strong>.</li><li>If valid, it invokes <strong>EmailLoginOTPService</strong> to generate a unique OTP.</li><li>It then emits an <strong>EmailOTPSentEvent</strong>, which is published to <strong>Kafka</strong> for asynchronous notification delivery and audit tracking.</li><li>The API responds with a confirmation that the OTP request was processed successfully.</li></ol><h2 id="step-2-validating-the-email-otp-login-process">Step 2: Validating the Email OTP (Login Process)</h2><p>Once the user receives the OTP, they can authenticate using it.</p><h3 id="conceptual-flow-1">Conceptual Flow</h3><ol><li>The <strong>client</strong> submits a POST request to <code>/api/v1/user/validate/emailloginotp</code> with <code>email</code> and <code>otp</code>.</li><li>The <strong>Command Controller</strong> validates the OTP through the <strong>EmailLoginOTPService</strong>.</li><li>On success, it dispatches a <strong>LogInWithEmailOTPCommand</strong> to the <strong>Command Handler</strong>.</li><li>The <strong>Command Handler</strong> performs several key actions:<ul><li>Rehydrates the user&#x2019;s aggregate state from the <strong>Event Store</strong>.</li><li>Raises a <strong>LoggedInWithEmailOTPEvent</strong> to represent successful login.</li><li>Persists this event back to the <strong>Event Store</strong>.</li></ul></li><li>The <strong>Event Handler</strong> listens for the event, updating the <strong>read projection</strong> (user table) to reflect login status.</li><li>The <strong>JWT Token Provider</strong> issues a new <strong>access token</strong> for the authenticated session.</li><li>The client receives a success response along with the token.</li></ol><h2 id="event-sourcing-lifecycle">Event Sourcing Lifecycle</h2><p>Event Sourcing lies at the heart of this system.<br>Every change in user state &#x2014; from OTP sent to login success &#x2014; is captured as a <strong>domain event</strong> and stored immutably in the <strong>Event Store</strong>.</p><h3 id="conceptual-event-chain">Conceptual Event Chain</h3>
<!--kg-card-begin: html-->
<table data-start="6159" data-end="6635" class="w-fit min-w-(--thread-content-width)"><thead data-start="6159" data-end="6253"><tr data-start="6159" data-end="6253"><th data-start="6159" data-end="6170" data-col-size="sm">Sequence</th><th data-start="6170" data-end="6198" data-col-size="sm">Event Name</th><th data-start="6198" data-end="6253" data-col-size="md">Description</th></tr></thead><tbody data-start="6349" data-end="6635"><tr data-start="6349" data-end="6443"><td data-start="6349" data-end="6360" data-col-size="sm">1</td><td data-start="6360" data-end="6388" data-col-size="sm"><code data-start="6362" data-end="6381">EmailOTPSentEvent</code></td><td data-start="6388" data-end="6443" data-col-size="md">OTP generated and dispatched via Kafka.</td></tr><tr data-start="6444" data-end="6540"><td data-start="6444" data-end="6455" data-col-size="sm">2</td><td data-start="6455" data-end="6485" data-col-size="sm"><code data-start="6457" data-end="6484">LoggedInWithEmailOTPEvent</code></td><td data-start="6485" data-end="6540" data-col-size="md">User successfully logged in with the OTP.</td></tr><tr data-start="6541" data-end="6635"><td data-start="6541" data-end="6552" data-col-size="sm">3</td><td data-start="6552" data-end="6580" data-col-size="sm"><code data-start="6554" data-end="6574">AuditRecordedEvent</code></td><td data-start="6580" data-end="6635" data-col-size="md">Login and OTP actions recorded for compliance.</td></tr></tbody></table>
<!--kg-card-end: html-->
<p>Replaying these events allows the system to <strong>rebuild state from history</strong>, ensuring <strong>full traceability</strong> and <strong>fault recovery</strong> without traditional database state dependencies.</p><h2 id="security-and-compliance-considerations">Security and Compliance Considerations</h2><p>Security is baked into every stage of the pipeline:</p><ul><li><strong>OTP expiration, uniqueness, and rate limiting</strong> are handled by the <code>EmailLoginOTPService</code>.</li><li><strong>JWT tokens</strong> provide stateless, secure session management.</li><li><strong>Audit events</strong> ensure all critical actions are logged for compliance.</li><li><strong>Tenant-aware scoping</strong> enforces strict data isolation in multi-tenant setups.</li></ul><p>By combining event logs with JWT-based access, the system achieves both <strong>security</strong> and <strong>observability</strong>.</p><h2 id="conclusion">Conclusion</h2><p>Building an OTP-based authentication system using <strong>Reactive</strong>, <strong>CQRS</strong>, and <strong>Event Sourcing</strong> principles enables:</p><p><strong>Scalable, non-blocking performance</strong> under heavy traffic<br><strong>Strict auditability</strong> with event logs<br><strong>Fault tolerance</strong> through event replay<br><strong>Extensibility</strong> for future login channels or audit pipelines<br><strong>Seamless user experience</strong> with real-time responsiveness</p><p>In essence, this architecture transforms a simple OTP mechanism into a <strong>production-grade authentication framework</strong> &#x2014; one that is reactive by nature, event-driven by design, and compliant by default.</p>]]></content:encoded></item><item><title><![CDATA[Securely Embedding Documents in Excel: The Short URL Pattern]]></title><description><![CDATA[<h2 id="the-problem">The Problem</h2>
<p>Enterprise applications that export Excel reports with embedded documents face a critical security challenge. The straightforward implementation exposes internal system identifiers:</p>
<pre><code>https://app.example.com/api/document/12345/view?tenantId=67890
</code></pre>
<p>This reveals:</p>
<ul>
<li>Internal document IDs (12345)</li>
<li>Tenant identifiers (67890)</li>
<li>API structure and endpoints</li>
</ul>
<p>Once Excel files are</p>]]></description><link>https://blog.gyri.tech/the-secure-redirect-pattern-the-real-way-to-link-to-secure-documents-in-excel/</link><guid isPermaLink="false">69034b7a35fde4040e7a7ae4</guid><dc:creator><![CDATA[Aditya Methe]]></dc:creator><pubDate>Thu, 30 Oct 2025 13:46:27 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1590859808308-3d2d9c515b1a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHdlYiUyMGxpbmt8ZW58MHx8fHwxNzYxODMxOTMwfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<h2 id="the-problem">The Problem</h2>
<img src="https://images.unsplash.com/photo-1590859808308-3d2d9c515b1a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHdlYiUyMGxpbmt8ZW58MHx8fHwxNzYxODMxOTMwfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Securely Embedding Documents in Excel: The Short URL Pattern"><p>Enterprise applications that export Excel reports with embedded documents face a critical security challenge. The straightforward implementation exposes internal system identifiers:</p>
<pre><code>https://app.example.com/api/document/12345/view?tenantId=67890
</code></pre>
<p>This reveals:</p>
<ul>
<li>Internal document IDs (12345)</li>
<li>Tenant identifiers (67890)</li>
<li>API structure and endpoints</li>
</ul>
<p>Once Excel files are distributed, these identifiers persist indefinitely, creating ongoing security exposure even if the URLs require authentication.</p>
<p>The challenge intensifies when requirements include:</p>
<ul>
<li>Embedding images directly in Excel cells</li>
<li>Providing download links for full-resolution access</li>
<li>Supporting non-image documents (PDFs, DOCX) as clickable links</li>
</ul>
<h2 id="solution-short-url-redirection">Solution: Short URL Redirection</h2>
<p>Replace direct URLs with temporary, opaque redirect tokens:</p>
<p><strong>Before:</strong></p>
<pre><code>https://app.example.com/api/document/12345/view?tenantId=67890
</code></pre>
<p><strong>After:</strong></p>
<pre><code>https://app.example.com/api/shorturl/7k9x-m2p4
</code></pre>
<p>The short URL provides:</p>
<ul>
<li>Complete opacity (no exposed identifiers)</li>
<li>Automatic expiration (configurable timeframe)</li>
<li>Full auditability (access tracking)</li>
<li>Universal compatibility (images and documents)</li>
</ul>
<hr>
<h2 id="architecture">Architecture</h2>
<h3 id="phase-1-report-generation">Phase 1: Report Generation</h3>
<p><strong>Token Generation:</strong></p>
<p>For each document in the export:</p>
<ol>
<li>Generate cryptographically secure random token (e.g., <code>7k9x-m2p4</code>)</li>
<li>Store mapping in database:</li>
</ol>
<pre><code>short_code:    7k9x-m2p4
original_url:  /api/document/12345/view?tenantId=67890
expires_at:    2025-11-06 14:30:00 UTC
created_by:    user@example.com
</code></pre>
<p><strong>Content Handling:</strong></p>
<p><strong>Images:</strong></p>
<ul>
<li>Fetch image via secure internal URL (server-side)</li>
<li>Embed binary data directly into Excel cell</li>
<li>Attach short URL as clickable hyperlink</li>
<li>Result: Thumbnail preview with download link</li>
</ul>
<p><strong>Non-Images:</strong></p>
<ul>
<li>Insert short URL as clickable hyperlink only</li>
<li>Result: Text link for download/viewing</li>
</ul>
<p><strong>Excel Generation:</strong></p>
<p>Use server-side libraries (Apache POI, OpenPyXL, EPPlus) to:</p>
<ul>
<li>Embed image binary data in cells</li>
<li>Configure hyperlinks to short URLs</li>
<li>Ensure zero internal identifiers in output</li>
</ul>
<h3 id="phase-2-document-access">Phase 2: Document Access</h3>
<p><strong>User clicks link in Excel:</strong></p>
<ol>
<li>
<p><strong>Request</strong>: Browser opens <code>GET /api/shorturl/7k9x-m2p4</code></p>
</li>
<li>
<p><strong>Validation</strong>: Public endpoint checks:</p>
<ul>
<li>Token exists in database</li>
<li>Token not expired</li>
<li>Log access attempt</li>
</ul>
</li>
<li>
<p><strong>Redirect</strong>: Return HTTP 302:</p>
<pre><code>Location: https://app.example.com/api/document/12345/view?tenantId=67890
</code></pre>
</li>
<li>
<p><strong>Authentication</strong>: Browser follows redirect with existing session, normal authentication applies</p>
</li>
</ol>
<p><strong>User experience:</strong> Click &#x2192; Document opens</p>
<p><strong>System process:</strong> Validate &#x2192; Redirect &#x2192; Authenticate &#x2192; Deliver</p>
<hr>
<h2 id="technical-implementation">Technical Implementation</h2>
<h3 id="database-schema">Database Schema</h3>
<table>
<thead>
<tr>
<th>Column</th>
<th>Type</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>short_code</code></td>
<td>VARCHAR(32), PRIMARY KEY</td>
<td>Unique token identifier</td>
</tr>
<tr>
<td><code>original_url</code></td>
<td>TEXT</td>
<td>Target document URL</td>
</tr>
<tr>
<td><code>expires_at</code></td>
<td>TIMESTAMP</td>
<td>Expiration timestamp</td>
</tr>
<tr>
<td><code>created_by</code></td>
<td>VARCHAR(255)</td>
<td>Originating user</td>
</tr>
<tr>
<td><code>click_count</code></td>
<td>INTEGER</td>
<td>Access counter</td>
</tr>
</tbody>
</table>
<h3 id="security-configuration">Security Configuration</h3>
<p><strong>Redirect Endpoint:</strong></p>
<ul>
<li>Path: <code>/api/shorturl/**</code></li>
<li>Authentication: None (public access)</li>
<li>Rate limiting: Required (prevents enumeration)</li>
<li>Validation: Token existence and expiration only</li>
</ul>
<p><strong>Token Specification:</strong></p>
<ul>
<li>Length: Minimum 12 alphanumeric characters</li>
<li>Entropy: Cryptographically secure random generation</li>
<li>Character set: URL-safe (a-z, A-Z, 0-9)</li>
<li>Collision handling: Database unique constraint with retry</li>
</ul>
<h3 id="expiration-policy">Expiration Policy</h3>
<table>
<thead>
<tr>
<th>Use Case</th>
<th>Recommended Period</th>
</tr>
</thead>
<tbody>
<tr>
<td>Internal reports</td>
<td>7 days</td>
</tr>
<tr>
<td>External sharing</td>
<td>24-48 hours</td>
</tr>
<tr>
<td>Archive/compliance</td>
<td>30 days</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="security-benefits">Security Benefits</h2>
<h3 id="1-information-protection">1. Information Protection</h3>
<ul>
<li>Internal IDs completely hidden</li>
<li>API structure remains confidential</li>
<li>Zero architectural details exposed</li>
</ul>
<h3 id="2-temporal-controls">2. Temporal Controls</h3>
<ul>
<li>Automatic expiration reduces long-term risk</li>
<li>Distributed files lose access over time</li>
<li>No indefinite credential exposure</li>
</ul>
<h3 id="3-audit-capability">3. Audit Capability</h3>
<ul>
<li>Track every access attempt</li>
<li>Record timestamp, IP, user identity</li>
<li>Enable anomaly detection and compliance reporting</li>
</ul>
<h3 id="4-access-management">4. Access Management</h3>
<ul>
<li>Revoke tokens immediately if needed</li>
<li>Extend expiration programmatically</li>
<li>Tenant-level and user-level controls</li>
</ul>
<hr>
<h2 id="performance-considerations">Performance Considerations</h2>
<p><strong>Token Generation:</strong></p>
<ul>
<li>Batch create during report generation</li>
<li>~1ms per token generation</li>
<li>Minimal database overhead</li>
</ul>
<p><strong>Lookup Performance:</strong></p>
<ul>
<li>O(1) with proper indexing</li>
<li>Cache active tokens (optional)</li>
<li>Typical response time: &lt;10ms</li>
</ul>
<p><strong>Cleanup:</strong></p>
<ul>
<li>Schedule periodic deletion of expired tokens</li>
<li>Run during off-peak hours</li>
<li>Archive audit data before deletion</li>
</ul>
<p><strong>Image Processing:</strong></p>
<ul>
<li>Server-side fetch and embed: ~50-100ms per image</li>
<li>Memory efficient with streaming</li>
<li>No browser limitations</li>
</ul>
<hr>
<h2 id="example-workflow">Example Workflow</h2>
<p><strong>Scenario:</strong> Report with 30 images, 20 PDFs</p>
<p><strong>Generation:</strong></p>
<ol>
<li>Generate 50 short URLs: ~50ms</li>
<li>Fetch and embed 30 images: ~2-3 seconds</li>
<li>Build Excel file: ~500ms</li>
<li>Total: ~3-4 seconds</li>
</ol>
<p><strong>Usage:</strong></p>
<ul>
<li>5 users, 7-day period</li>
<li>150 total accesses across 50 documents</li>
<li>Complete audit trail maintained</li>
<li>Zero ID exposure</li>
<li>Automatic expiration after policy period</li>
</ul>
<hr>
<h2 id="implementation-checklist">Implementation Checklist</h2>
<p><strong>Required Components:</strong></p>
<ul>
<li>Short URL mapping database table with proper indexes</li>
<li>Token generation service (cryptographically secure)</li>
<li>Public redirect endpoint (unauthenticated)</li>
<li>Token validation logic (existence + expiration)</li>
<li>Audit logging mechanism</li>
<li>Scheduled cleanup job for expired tokens</li>
</ul>
<p><strong>Security Requirements:</strong></p>
<ul>
<li>Rate limiting on redirect endpoint</li>
<li>Cryptographic random token generation</li>
<li>Proper expiration policy configuration</li>
<li>HTTP 302 redirect (not 301 permanent)</li>
<li>Error handling (404 for invalid/expired tokens)</li>
</ul>
<p><strong>Server-Side Excel Library:</strong></p>
<ul>
<li>Java: Apache POI</li>
<li>Python: OpenPyXL</li>
<li>.NET: EPPlus</li>
</ul>
<p><strong>Why Server-Side:</strong><br>
Browser-based libraries (xlsx-js-style, etc.) cannot embed images in Excel cells. Server-side libraries provide full binary embedding capability.</p>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>The short URL redirection pattern solves document embedding security through architectural indirection. By replacing direct URLs with temporary tokens, systems achieve:</p>
<ul>
<li><strong>Complete identifier masking</strong> (no IDs exposed)</li>
<li><strong>Automatic expiration</strong> (temporal security)</li>
<li><strong>Full auditability</strong> (compliance ready)</li>
<li><strong>Seamless UX</strong> (transparent to users)</li>
</ul>
<p>This approach is applicable beyond Excel exports&#x2014;anywhere temporary, auditable access to secure resources is required without exposing internal architecture.</p>
<p><strong>Key Takeaway:</strong> Never expose internal identifiers in exported files. Use opaque, expiring tokens as an indirection layer between distributed content and secure resources.</p>
<hr>
<p><strong>Technical Stack Considerations:</strong></p>
<p>This pattern integrates with existing authentication infrastructure. The redirect endpoint bypasses authentication (public), while the target URL enforces normal security. Token validation provides the security boundary, relying on cryptographic randomness and expiration rather than session state.</p>
<p><strong>Broader Applications:</strong> PDF reports, email notifications, third-party integrations, mobile apps, automated distribution systems&#x2014;any context requiring temporary resource access without persistent authentication.</p>
]]></content:encoded></item><item><title><![CDATA[MongoDB Commands]]></title><description><![CDATA[<h2 id="commands">Commands</h2><ul><li><code>mongod</code> - start mongo db server instance</li><li><code>mongo</code> - start mongo db command prompt to execute commands</li><li><code>show dbs</code> - show databases</li><li><code>mongoimport -d [DATABASE_NAME] -c [COLLECTIONS] --file [FILE_NAME]</code> - import into database and collection from file</li><li><code>mongoexport -d [DATABASE_NEM] -c [COLLECTIONS] --out [FILE_NAME]</code> -</li></ul>]]></description><link>https://blog.gyri.tech/mongodb-commands/</link><guid isPermaLink="false">68ec6c3bd1e62b04108b4ceb</guid><dc:creator><![CDATA[Kaustubh Kesarkar]]></dc:creator><pubDate>Mon, 13 Oct 2025 03:06:19 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1658204238967-3a81a063d162?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fG1vbmdvZGJ8ZW58MHx8fHwxNzYwMzI0NzE2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<h2 id="commands">Commands</h2><ul><li><code>mongod</code> - start mongo db server instance</li><li><code>mongo</code> - start mongo db command prompt to execute commands</li><li><code>show dbs</code> - show databases</li><li><code>mongoimport -d [DATABASE_NAME] -c [COLLECTIONS] --file [FILE_NAME]</code> - import into database and collection from file</li><li><code>mongoexport -d [DATABASE_NEM] -c [COLLECTIONS] --out [FILE_NAME]</code> - export db in json</li><li><code>mongodump</code> - binary export</li><li><code>mongorestore</code> - restore binary export</li><li><code>bsondump</code></li><li><code>mongostat</code></li><li><code>use [DB_NAME]</code>- change/switch DB. There is NO command to create DB</li><li><code>db</code> - set variable as DB (after <em>user [DB_NAME]</em>)</li><li><code>db.[COLLECTIONS]</code> - e.g. db.links. Don&apos;t have to create collections (similar to DB)</li><li><code>db.[COLLECTIONS].count()</code> - to check number of documents. e.g. db.links.count()</li><li><code>db.[COLLECTIONS].insert({[JSON_DATA]})</code> - e.g. db.links.insert({title: &quot;google search&quot;, url:&quot;www.google.com&quot;, comment:&quot;top search engine&quot;, tags:[&quot;default&quot;,&quot;main page&quot;], saved_on: new Date()})</li><li><code>db.[COLLECTIONS].save({[JSON_DATA]})</code> - e.g. db.links.save({title: &quot;yahoo search&quot;, url: &quot;www.yahoo.com&quot;, saved_on: new Date()})</li><li><code>db.[COLLECTIONS].find()</code> - e.g. db.links.find(). Query the database to find the documents</li></ul><h3 id="examples">Examples</h3><ul><li><code>show collections</code> - show collections for db</li><li><code>db.users.insert({&quot;name&quot;:&quot;username&quot;})</code> - insert collection user with data</li><li><code>db.users.count()</code> - show count of collections</li><li><code>db.users.find()</code> - find all data from collection</li><li><code>db.users.find().explain()</code> - explain execution planner for query to find data</li><li><code>db.users.find().explain(&quot;executionStats&quot;)</code> - more information on explain</li><li><code>db.users.createIndex({number:1})</code> - to create an index on collection</li><li><code>db.users.update({&quot;name&quot;:&quot;username&quot;}, $set: {&quot;first_name&quot;:&quot;Name&quot;})&quot;</code> - <em>$set</em> update record</li><li><code>db.users.update({&quot;name&quot;:&quot;username&quot;}, {$addToSet: {&quot;hobbies&quot;:&quot;running&quot;}})&quot;</code> - <em>$addToSet</em> update array with new value</li><li><code>db.users.remove({&quot;name&quot;:&quot;username&quot;})</code> - remove record</li><li><code>db.users.drop()</code> - drop entire <em>users</em> collection</li></ul><h3 id="notes">Notes</h3><ul><li><strong>bson</strong>: binary JSON</li><li><strong>Collection</strong>: Like an RDBMS table; but collection has no schemas.</li><li><strong>Document</strong>: Like an RDBMS record or row. There are bson documents.</li><li><strong>Field</strong>: Like an RDBMS column; {key: value}</li></ul><hr>]]></content:encoded></item><item><title><![CDATA[react-native-document-picker]]></title><description><![CDATA[<p>The <code>react-native-document-picker</code> library is a React Native module that allows users to select documents (or files) from their device storage. This can include various types of files, such as images, videos, PDFs, audio files, and other types of documents, depending on the platform (iOS or Android).</p><p>The primary use case</p>]]></description><link>https://blog.gyri.tech/react-native-document-picker/</link><guid isPermaLink="false">67441a29c1ace803d59102c3</guid><dc:creator><![CDATA[Ankita Shelake]]></dc:creator><pubDate>Mon, 13 Oct 2025 02:56:12 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1544396821-4dd40b938ad3?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDd8fGZpbGVzfGVufDB8fHx8MTc2MDMyNDE0Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1544396821-4dd40b938ad3?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDd8fGZpbGVzfGVufDB8fHx8MTc2MDMyNDE0Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="react-native-document-picker"><p>The <code>react-native-document-picker</code> library is a React Native module that allows users to select documents (or files) from their device storage. This can include various types of files, such as images, videos, PDFs, audio files, and other types of documents, depending on the platform (iOS or Android).</p><p>The primary use case for this library is to give users the ability to pick files from their local storage or cloud services (like iCloud, Google Drive, etc.) within a React Native app.</p><h3 id="key-features-of-react-native-document-picker">Key Features of <code>react-native-document-picker</code>:</h3><ol><li><strong>File Selection</strong>:<ul><li>Allows users to pick various types of files (documents, images, audio, etc.) from their device storage.</li><li>Works across iOS and Android, providing a consistent API to access documents.</li></ul></li><li><strong>File Type Filtering</strong>:<ul><li>You can specify the types of files you want users to select (e.g., only PDFs, only images, only audio files, etc.).</li><li>The library provides predefined constants for file types (e.g., <code>DocumentPicker.types.images</code>, <code>DocumentPicker.types.audio</code>, <code>DocumentPicker.types.pdf</code>), but you can also specify custom types.</li></ul></li><li><strong>Multiple File Selection</strong>:<ul><li>You can allow the user to select multiple files at once (in supported platforms).</li></ul></li><li><strong>File Metadata</strong>:<ul><li>After selecting a file, the library provides metadata about the file, such as its URI, name, size, type, and more, which you can use in your app.</li></ul></li><li><strong>Cross-platform</strong>:<ul><li>Works on both <strong>iOS</strong> and <strong>Android</strong> devices, though there are some differences in behavior between the platforms, especially regarding the supported file types and the appearance of the file picker.</li></ul></li></ol><h3 id="basic-usage-example">Basic Usage Example:</h3><p>Here&#x2019;s a simple example of how to use the <code>react-native-document-picker</code> library to allow a user to pick an audio file:</p><p><strong>Import and Use the Library</strong>:Below is an example of how to use <code>react-native-document-picker</code> to allow the user to pick an audio file:</p><pre><code class="language-javascript">import React, { useState } from &apos;react&apos;;
import { View, Button, Text, Alert } from &apos;react-native&apos;;
import DocumentPicker from &apos;react-native-document-picker&apos;;

const FilePickerExample = () =&gt; {
  const [fileInfo, setFileInfo] = useState(null);

  // Function to pick an audio file
  const handlePickFile = async () =&gt; {
    try {
      // Pick an audio file
      const res = await DocumentPicker.pick({
        type: [DocumentPicker.types.audio],  // Filter for audio files
      });

      setFileInfo(res);  // Set the file metadata

      // Alert with file information
      Alert.alert(&apos;File Selected&apos;, `File: ${res.name}, Size: ${res.size} bytes`);

      console.log(res);  // Log the file metadata (URI, type, name, etc.)
    } catch (err) {
      if (DocumentPicker.isCancel(err)) {
        console.log(&apos;User canceled the picker&apos;);
      } else {
        console.error(&apos;Error picking file:&apos;, err);
      }
    }
  };

  return (
    &lt;View style={{ flex: 1, justifyContent: &apos;center&apos;, alignItems: &apos;center&apos; }}&gt;
      &lt;Button title=&quot;Pick an Audio File&quot; onPress={handlePickFile} /&gt;
      {fileInfo &amp;&amp; (
        &lt;Text style={{ marginTop: 20 }}&gt;
          File: {fileInfo.name} (Size: {fileInfo.size} bytes)
        &lt;/Text&gt;
      )}
    &lt;/View&gt;
  );
};

export default FilePickerExample;
</code></pre><p><strong>Install the library</strong>:If you haven&#x2019;t already, install the library by running:</p><pre><code class="language-bash">npm install react-native-document-picker
</code></pre><p>Or if you&apos;re using yarn:</p><pre><code class="language-bash">yarn add react-native-document-picker
</code></pre><h3 id="file-metadata-example">File Metadata Example:</h3><p>When a file is selected, the <code>DocumentPicker.pick()</code> method returns an object containing metadata about the file. Here is an example of the metadata you might get:</p><pre><code class="language-js">{
  uri: &apos;file:///storage/emulated/0/Download/song.mp3&apos;,
  type: &apos;audio/mp3&apos;,  // The MIME type of the file
  name: &apos;song.mp3&apos;,   // The file name
  size: 1234567,      // The size of the file in bytes
  fileCopyUri: &apos;content://com.android.providers.media.documents/document/...&apos;,
}
</code></pre><h3 id="file-types">File Types:</h3><p><code>react-native-document-picker</code> supports many file types, and you can filter what type of files users can pick. Here are some of the predefined types available:</p><p><strong>All Files</strong> (Generic for any file type):</p><pre><code class="language-js">DocumentPicker.types.allFiles
</code></pre><p><strong>Spreadsheets (Excel files)</strong>:</p><pre><code class="language-js">DocumentPicker.types.xls
</code></pre><p><strong>Plain Text</strong>:</p><pre><code class="language-js">DocumentPicker.types.plainText
</code></pre><p><strong>PDF Documents</strong>:</p><pre><code class="language-js">DocumentPicker.types.pdf
</code></pre><p><strong>Audio</strong>:</p><pre><code class="language-js">DocumentPicker.types.audio
</code></pre><p><strong>Images</strong>:</p><pre><code class="language-js">DocumentPicker.types.images
</code></pre><p>You can also combine multiple types. For example, to allow both images and audio:</p><pre><code class="language-js">type: [DocumentPicker.types.images, DocumentPicker.types.audio]
</code></pre><h3 id="key-methods">Key Methods:</h3><ul><li><strong><code>pick()</code></strong>: The main method used to open the file picker and select files. You can pass an object with options to specify file types and whether multiple files can be selected.</li><li><strong><code>pickMultiple()</code></strong>: Allows users to pick multiple files at once (iOS only).</li><li><strong><code>isCancel()</code></strong>: A utility method to check if the user canceled the file picking action.</li></ul><h3 id="platform-differences">Platform Differences:</h3><ul><li><strong>iOS</strong>: The library provides a native file picker. You can pick files from the local file system, including cloud services like iCloud Drive.</li><li><strong>Android</strong>: The library typically uses the device&apos;s file system picker, which may vary depending on the Android version and manufacturer. The file picker might not always show cloud storage options unless explicitly configured by the app.</li></ul><h3 id="use-cases">Use Cases:</h3><ol><li><strong>Allow users to upload documents, images, or audio files</strong> from their device to your app (e.g., for profile pictures, document submissions, etc.).</li><li><strong>Cloud storage integration</strong>: Some Android versions may allow cloud storage access via the file picker, but for full cloud file support, you&apos;ll typically need to integrate with a specific cloud API (e.g., Google Drive, iCloud).</li><li><strong>Handling PDFs or media files</strong>: If you need to open or process PDFs or media files (audio, video, etc.), this library helps by allowing users to select these files from their device.</li></ol><h3 id="summary">Summary:</h3><ul><li><strong>What it does</strong>: <code>react-native-document-picker</code> enables users to select files from their local storage (including documents, images, and audio files) and provides metadata about the selected file.</li><li><strong>Why it&apos;s useful</strong>: It simplifies the process of file selection in React Native apps, providing a consistent interface across iOS and Android.</li></ul><p>Let me know if you need more information or examples!</p>]]></content:encoded></item><item><title><![CDATA[Git Essential Commands]]></title><description><![CDATA[<h2 id="git-commands">Git Commands</h2>
<ul>
<li>which git</li>
<li>git --version</li>
<li>vi /etc/gitconfig</li>
<li>vi ~/.gitconfig : user level git config file</li>
<li>vi [PROJECT_PATH]/.git/config : project level git config file</li>
<li>git config --system : system level configuration</li>
<li>git config --global : user level configuration</li>
<li>git config : project level configuration</li>
<li>git config --global user.name &quot;[NAME]</li></ul>]]></description><link>https://blog.gyri.tech/git-essential-commands/</link><guid isPermaLink="false">682f2e72a37147040ff7651a</guid><dc:creator><![CDATA[Kaustubh Kesarkar]]></dc:creator><pubDate>Thu, 22 May 2025 14:04:57 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1556075798-4825dfaaf498?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGdpdHxlbnwwfHx8fDE3NDc5MjI2NzN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<h2 id="git-commands">Git Commands</h2>
<ul>
<li>which git</li>
<li>git --version</li>
<li>vi /etc/gitconfig</li>
<li>vi ~/.gitconfig : user level git config file</li>
<li>vi [PROJECT_PATH]/.git/config : project level git config file</li>
<li>git config --system : system level configuration</li>
<li>git config --global : user level configuration</li>
<li>git config : project level configuration</li>
<li>git config --global user.name &quot;[NAME]&quot;</li>
<li>git config --list : list of config properties</li>
<li>git config --global core.excludefile ~/.gitignore_global : global config for gitignore</li>
<li>git config --global alias.[shortcut] [git-command] : shortcut for git command</li>
<li>git --help</li>
<li>git init : initialize repository</li>
<li>ls -la .git : shows git config files</li>
<li>git add . : add all uncommitted changes from project. dot (.) is current directory</li>
<li>git commit -m &quot;message&quot; : commit changes with message</li>
<li>git commit -am &quot;message&quot; : shorthand for <code>git add .</code> and <code>git commit -m &quot;message&quot;</code> (grabs everything. not tracked(new) and deleted files are not included)</li>
<li>git commit --amend -m &quot;message&quot; : this command is a convenient way to fix up the most recent commit.</li>
<li>git log : check git commit log</li>
<li>git log -2 : restrict show commit log to last two</li>
<li>git log --since=2016-01-12 : show commits after this date</li>
<li>git log --until=2016-01-12 : show commits till this date</li>
<li>git log --author=&quot;Kaustubh&quot; : show commits for specified author</li>
<li>git log --grep=&quot;st&quot; : show commits having specified text</li>
<li>git log --online : shows all logs in one line</li>
<li>git log --online -[number] : restrict log for given number</li>
<li>git log [SHA]..[SHA] : shows log between two mentioned commits</li>
<li>git log [file] : shows details about file</li>
<li>git log -p : more details about commit. Like code insertion etc</li>
<li>git log --stat --summary</li>
<li>git log --format=oneline : this is similar to `git log --oneline&quot;</li>
<li>git log --format=[oneline / short / medium / fuller / email / raw]</li>
<li>git log --graph</li>
<li>git log --oneline --graph --all --decorate</li>
<li>git status : difference between working directory, the staging index and teh repository</li>
<li>git status -s</li>
<li>git reset HEAD [file] : to unstage file</li>
<li>git reset HEAD</li>
<li>git reset --soft</li>
<li>git reset --mixed : default</li>
<li>git reset --hard</li>
<li>git reset --hard [SHA] : reverted to the specified SHA version</li>
<li>git diff : shows difference between the repository where HEAD is pointed at Vs working directory</li>
<li>git diff [file]</li>
<li>git diff --staged : shows difference between the staging index Vs working directory</li>
<li>git diff --cached : similar to <code>git diff --staged</code></li>
<li>git diff --color-words [file] : shows only different words</li>
<li>git diff [SHA] : difference between working directory and directory at that point of time</li>
<li>git diff [SHA] [file]</li>
<li>git diff [SHA]..[SHA] : difference between two commits</li>
<li>git diff [SHA]..[SHA] [file]</li>
<li>git diff -b : is same as <code>git diff --ignore-space-change</code></li>
<li>git diff -w : is same as <code>git diff --ignore-all-space</code></li>
<li>git diff [branch-1]..[branch-2]</li>
<li>git diff [branch-1]..[branch-2]^ : compare with previous commit of branch-2</li>
<li>git rm [file] : remove uncommitted file. similar to <code>git add</code>. use <code>git commit -m</code> to commit and remove file from repository.</li>
<li>git rm --cached [file] : remove from caching (or staging index)</li>
<li>git mv [orig_file] [newname_file] : rename file</li>
<li>git checkout -- [file] : to discard changes in working directory. &quot;--&quot; indicates from current branch only.</li>
<li>git checkout [SHA-key] -- [file] : revert to that SHA version</li>
<li>git revert [SHA] : revert and commit</li>
<li>git revert -n [SHA] : revert and not committed</li>
<li>git clean -n : test run of <code>git clean</code></li>
<li>git clean -f : final run of <code>git clean</code></li>
<li>git ls-tree HEAD</li>
<li>git ls-tree [branch-name]</li>
<li>git ls-tree [branch-name] [folder-path]</li>
<li>git ls-tree [branch-name]^ [folder-path] : earlier commit</li>
<li>git ls-tree [SHA]</li>
<li>git show [SHA]</li>
<li>git branch : list branch names in working directory</li>
<li>git branch [branch-name] : creates new branch</li>
<li>git branch -d [branch-name] : delete branch (before this make sure you are not working on this branch)</li>
<li>git branch -D [branch-name] : force delete branch</li>
<li>git branch -m [branch-new-name] : rename branch</li>
<li>git branch --merged :To see which branches are already merged into the branch you&#x2019;re on</li>
<li>git branch --no-merged : To see all the branches that contain work you haven&#x2019;t yet merged in</li>
<li>git branch -r : shows remote branches</li>
<li>git branch -a : shows local and remote branches</li>
<li>git branch [local-branch-name] [remote-branch-name] : creates new branch and tracking is set to the remote branch</li>
<li>git checkout [branch-name] : change working directory to specified branch name</li>
<li>git checkout -b [branch-name] : create and checkout at the same time for new branch</li>
<li>git checkout -b [local-branch-name] [remote-branch-name] : creates and checkout new branch and tracking is set to the remote branch</li>
<li>git merge [branch-name] : merge specified branch to current working branch</li>
<li>git merge --no-ff [branch-name] : no fast forward. create new commit</li>
<li>git merge -ff-only [branch-name] : do merge only for fast forward else abort</li>
<li>git merge --abort : abort the current merge</li>
<li>git stash save &quot;message&quot; : stash current changes</li>
<li>git stash list : shows list of items are in stash</li>
<li>git stash show stash@{NUMBER} : shows the details of that number</li>
<li>git stash show -p stash@{NUMBER} : shows the details of that number (more descriptive with -p option)</li>
<li>git stash pop : checkout changes are removes the stash</li>
<li>git stash apply : only checkout changes. keeps in the stash as well</li>
<li>git stash drop stash@{NUMBER} : delete the specified stash</li>
<li>git stash clear : clears the stash list</li>
<li>git remote : alias for remote branch</li>
<li>git remote add [alias] [remote-url] : add alias for remote url</li>
<li>git remote -v : more details about remote branch</li>
<li>git push -u [remote-alias] [branch-name] : push code changes to remote repository. -u option to track remote branch</li>
<li>git push : push current commits of the same branch to the origin (remote) branch</li>
<li>git push origin :[remote-branch-name] : deletes the branch from remote</li>
<li>git push origin --delete [remote-branch-name] : similar to <code>git push origin :[remote-branch-name]</code></li>
<li>git clone [git-remote-url] : creates with default folder</li>
<li>git clone [git-remote-url] [folder-name] : creates folder to clone remote repository</li>
<li>git fetch origin : sync (fetch) remote branch with local branch</li>
<li>git fetch --all : sync (fetch) all remote branches with local branches</li>
<li>git pull === git fetch + git merge</li>
</ul>
<h2 id="git-flow-commands">Git flow Commands</h2>
<ul>
<li>git flow init -d : initialise git flow</li>
<li>git flow feature start [feature-branch-name] : creates new feature branch</li>
<li>git flow feature publish [feature-branch-name] : publish feature branch to remote repository</li>
<li>git flow feature finish [feature-branch-name] : it deletes as well.</li>
</ul>
<h2 id="notes">Notes</h2>
<ul>
<li>SHA or SHA1 : Secure Hash Algorithm (1) <a href="https://en.wikipedia.org/wiki/SHA-1?ref=blog.gyri.tech">https://en.wikipedia.org/wiki/SHA-1</a></li>
<li>toggle between long commits : forward (f) or backward (b). space bar or enter to see more details</li>
<li>toggle fold long lines : minus sign (-) + shift + S + return. wrap it around instead of lines being truncated.</li>
<li>to return to long lines (unwrap) : minus sign (-) + S + return</li>
<li>cat .git/master : points to where HEAD (SHA) is located</li>
<li>cat .git/refs/heads/master : points to where HEAD (SHA) is located</li>
<li>project/.gitignore : file to specify which files or directory to ignore</li>
<li>.gitkeep : use this file to track empty folder</li>
<li>remote url details can be found at <code>.git/config</code> file</li>
</ul>
<h2 id="git-cherry-pick">Git cherry-pick</h2>
<ul>
<li>Cherry picking in git means to choose a commit from one branch and apply it onto another.</li>
<li>This is in contrast with other ways such as merge and rebase which normally apply many commits onto another branch.
<ol>
<li>Make sure you are on the branch you want to apply the commit to.<br>
<code>git checkout master</code></li>
<li>Execute the following:<br>
<code>git cherry-pick &lt;commit-hash&gt;</code></li>
</ol>
</li>
</ul>
<h2 id="git-windows-command">Git Windows command</h2>
<ul>
<li>git add --chmod=+x [FILE] : To mark file executable</li>
</ul>
<h2 id="contents">Contents</h2>
<img src="https://images.unsplash.com/photo-1556075798-4825dfaaf498?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGdpdHxlbnwwfHx8fDE3NDc5MjI2NzN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Git Essential Commands"><p><a href="https://www.linkedin.com/learning/git-essential-training-2023/get-started-with-git?ref=blog.gyri.tech">https://www.linkedin.com/learning/git-essential-training-2023/get-started-with-git</a></p>
<ul>
<li>
<p>Introduction</p>
<ul>
<li>Introduction</li>
<li>How to use the exercise files</li>
</ul>
</li>
<li>
<p>Welcome</p>
<ul>
<li>What you should know before watching this course</li>
<li>Using the exercise files</li>
</ul>
</li>
</ul>
<ol>
<li>
<p>What is Git?</p>
<ul>
<li>Understanding version control</li>
<li>The history of Git</li>
<li>About distributed version control</li>
<li>Who should use Git?</li>
</ul>
</li>
<li>
<p>Installing Git</p>
<ul>
<li>Installing Git on a Mac</li>
<li>Installing Git on Windows</li>
<li>Installing Git on Linux</li>
<li>Configuring Git</li>
<li>Exploring Git auto-completion</li>
<li>Using Git help</li>
</ul>
</li>
<li>
<p>Getting Started</p>
<ul>
<li>Initializing a repository</li>
<li>Understanding where Git files are stored</li>
<li>Performing your first commit</li>
<li>Writing commit messages</li>
<li>Viewing the commit log</li>
</ul>
</li>
<li>
<p>Git Concepts and Architecture</p>
<ul>
<li>Exploring the three-trees architecture</li>
<li>The Git workflow</li>
<li>Using hash values (SHA-1)</li>
<li>Working with the HEAD pointer</li>
</ul>
</li>
<li>
<p>Making Changes to Files</p>
<ul>
<li>Adding files</li>
<li>Editing files</li>
<li>Viewing changes with diff</li>
<li>Viewing only staged changes</li>
<li>Deleting files</li>
<li>Moving and renaming files</li>
</ul>
</li>
<li>
<p>Using Git with a Real Project</p>
<ul>
<li>Introducing the Explore California web site</li>
<li>Initializing Git</li>
<li>Editing the support phone number</li>
<li>Editing the backpack file name and links</li>
</ul>
</li>
<li>
<p>Undoing Changes</p>
<ul>
<li>Undoing working directory changes</li>
<li>Unstaging files</li>
<li>Amending commits</li>
<li>Retrieving old versions</li>
<li>Reverting a commit</li>
<li>Using reset to undo commits</li>
<li>Demonstrating a soft reset</li>
<li>Demonstrating a mixed reset</li>
<li>Demonstrating a hard reset</li>
<li>Removing untracked files</li>
</ul>
</li>
<li>
<p>Ignoring Files</p>
<ul>
<li>Using .gitignore files</li>
<li>Understanding what to ignore</li>
<li>Ignoring files globally</li>
<li>Ignoring tracked files</li>
<li>Tracking empty directories</li>
</ul>
</li>
<li>
<p>Navigating the Commit Tree</p>
<ul>
<li>Referencing commits</li>
<li>Exploring tree listings</li>
<li>Getting more from the commit log</li>
<li>Viewing commits</li>
<li>Comparing commits</li>
</ul>
</li>
<li>
<p>Branching</p>
<ul>
<li>Branching overview</li>
<li>Viewing and creating branches</li>
<li>Switching branches</li>
<li>Creating and switching branches</li>
<li>Switching branches with uncommitted changes</li>
<li>Comparing branches</li>
<li>Renaming branches</li>
<li>Deleting branches</li>
<li>Configuring the command prompt to show the branch</li>
</ul>
</li>
<li>
<p>Merging Branches</p>
<ul>
<li>Merging code</li>
<li>Using fast-forward merge vs. true merge</li>
<li>Merging conflicts</li>
<li>Resolving merge conflicts</li>
<li>Exploring strategies to reduce merge conflicts</li>
</ul>
</li>
<li>
<p>Stashing Changes</p>
<ul>
<li>Saving changes in the stash</li>
<li>Viewing stashed changes</li>
<li>Retrieving stashed changes</li>
<li>Deleting stashed changes</li>
</ul>
</li>
<li>
<p>Remotes</p>
<ul>
<li>Using local and remote repositories</li>
<li>Setting up a GitHub account</li>
<li>Adding a remote repository</li>
<li>Creating a remote branch</li>
<li>Cloning a remote repository</li>
<li>Tracking remote branches</li>
<li>Pushing changes to a remote repository</li>
<li>Fetching changes from a remote repository</li>
<li>Merging in fetched changes</li>
<li>Checking out remote branches</li>
<li>Pushing to an updated remote branch</li>
<li>Deleting a remote branch</li>
<li>Enabling collaboration</li>
<li>A collaboration workflow</li>
</ul>
</li>
<li>
<p>Tools and Next Steps</p>
<ul>
<li>Setting up aliases for common commands</li>
<li>Using SSH keys for remote login</li>
<li>Exploring integrated development environments</li>
<li>Exploring graphical user interfaces</li>
<li>Understanding Git hosting</li>
</ul>
</li>
<li>
<p>Conclusion</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[UNIX commands]]></title><description><![CDATA[<p></p><h2 id="basic-commands">Basic commands</h2>
<ul>
<li><code>su</code>: login as super user (e.g. root)</li>
<li><code>pwd</code>: current directory</li>
<li><code>ls</code>: list files</li>
<li><code>ls -l</code>: list files with detail info</li>
<li><code>bunzip2 [FILE_NAME]</code>: decompress file at same location</li>
<li><code>cp [FILE_1] [FILE_2]</code>: copy file1 to a file called file2</li>
<li><code>mv [FILE_1] [FILE_2]</code>: (move) rename</li></ul>]]></description><link>https://blog.gyri.tech/unix-commands/</link><guid isPermaLink="false">682031bdcb3275078952dad1</guid><dc:creator><![CDATA[Kaustubh Kesarkar]]></dc:creator><pubDate>Sun, 11 May 2025 05:13:24 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1629654297299-c8506221ca97?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGxpbnV4fGVufDB8fHx8MTc0Njk0MDMzNHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1629654297299-c8506221ca97?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGxpbnV4fGVufDB8fHx8MTc0Njk0MDMzNHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="UNIX commands"><p></p><h2 id="basic-commands">Basic commands</h2>
<ul>
<li><code>su</code>: login as super user (e.g. root)</li>
<li><code>pwd</code>: current directory</li>
<li><code>ls</code>: list files</li>
<li><code>ls -l</code>: list files with detail info</li>
<li><code>bunzip2 [FILE_NAME]</code>: decompress file at same location</li>
<li><code>cp [FILE_1] [FILE_2]</code>: copy file1 to a file called file2</li>
<li><code>mv [FILE_1] [FILE_2]</code>: (move) rename file1 to the name file2</li>
<li><code>mv [FILE_1] /[FOLDER_NAME]</code>: move file1 to the [FOLDER_NAME] directory</li>
<li><code>rm *</code>: deletes everything in a subdirectory (<a href="http://cmgm.stanford.edu/classes/unix/rm.html?ref=blog.gyri.tech">http://cmgm.stanford.edu/classes/unix/rm.html</a>)</li>
<li><code>rm -r *</code>: deletes everything in directory</li>
<li><code>rm test.txt</code>: removes only file with a name &quot;test.txt&quot; on the end</li>
<li><code>tail -500 FILE_NAME | less</code>: see last 500 lines of files</li>
<li><code>cat [FILE_NAME]</code>: view file</li>
</ul>
<h2 id="size">Size</h2>
<ul>
<li><code>df -k</code>: disk size</li>
<li><code>df -g</code>: disk size</li>
<li><code>df -h</code>: disk size</li>
<li><code>du -s [FOLDER_NAME]</code>: folder size info</li>
<li><code>du -sh ./</code>: current directory size</li>
<li><code>du -sh -- *(D) | sort -k1n</code>: order parent folder by size in ascending order</li>
<li><code>ls -l | grep ^- | sort -nr -k 5</code>: order files as per size</li>
</ul>
<h2 id="basic-network-commands">Basic network commands</h2>
<ul>
<li><code>ifconfig</code>: similar to ipconfig (display network interface parameters)</li>
<li><code>hostname</code>: get machine&apos;s name</li>
<li><code>/etc/init.d/network restart</code>: restart network</li>
</ul>
<h2 id="install">Install</h2>
<ul>
<li><code>rpm -ivh</code>: install rpm bin file</li>
</ul>
<h2 id="init-file">Init file</h2>
<ul>
<li><code>/etc/init.d/chkconfig &lt;SERVICE_NAME&gt; off</code>: off service at startup with super user (<a href="http://www.aboutlinux.info/2006/04/enabling-and-disabling-services-during_01.html?ref=blog.gyri.tech">http://www.aboutlinux.info/2006/04/enabling-and-disabling-services-during_01.html</a>)</li>
<li><code>/etc/init.d/chkconfig &lt;SERVICE_NAME&gt; on</code>: start service at startup with super user (<a href="http://www.aboutlinux.info/2006/04/enabling-and-disabling-services-during_01.html?ref=blog.gyri.tech">http://www.aboutlinux.info/2006/04/enabling-and-disabling-services-during_01.html</a>)</li>
</ul>
<h2 id="terminal">Terminal</h2>
<ul>
<li><code>tty</code>: to check terminal</li>
<li><code>ps -ef | grep java</code>: check the java processes</li>
<li><code>kill -9 &lt;process_id&gt;</code>: &lt;process_id&gt; is returned by prev command.</li>
</ul>
<h2 id="user">User</h2>
<ul>
<li><code>startx</code>: start GUI</li>
<li><code>useradd &lt;user&gt;</code>: create new user</li>
<li><code>passwd &lt;user&gt;</code>: change password for user</li>
<li><code>groupadd &lt;groupname&gt;</code>: create new group</li>
<li><code>usermod -g &lt;groupname&gt; &lt;user&gt;</code>: Assign a user to a primary group</li>
<li><code>usermod -G &lt;groupname&gt; &lt;user&gt;</code>: Assign a user to secondary groups</li>
<li><code>who am i</code>: current user</li>
</ul>
<h2 id="vi-editor">vi Editor</h2>
<ul>
<li><code>vi [FILE_NAME]</code>: Open or edit a file.</li>
<li><code>Esc</code>: Switch to Command mode.</li>
<li><code>oo / o</code>: insert new line after current line</li>
<li><code>OO / O</code>: insert new line before curren line</li>
<li><code>i</code>: Switch to Insert mode.</li>
<li><code>[ESC] i ENTER</code>: insert into file to location</li>
<li><code>:w</code>: Save and continue editing</li>
<li><code>:wq</code> or <code>ZZ</code>: Save and quit/exit vi</li>
<li><code>:x</code>: to file save</li>
<li><code>:q!</code>: Quit vi and do not save changes</li>
<li><code>yy</code>: Yank (copy) a line of text</li>
<li><code>p</code>: Paste a line of yanked text below the current line</li>
<li><code>o</code>: Open a new line under the current line</li>
<li><code>O</code>: Open a new line above the current line</li>
<li><code>A</code>: Append to the end of the line</li>
<li><code>a</code>: Append after the cursor&#x2019;s current position</li>
<li><code>I</code>: Insert text at the beginning of the current line</li>
<li><code>b</code>: Go to the beginning of the word</li>
<li><code>e</code>: Go to the end of the word</li>
<li><code>x</code>: Delete a single character</li>
<li><code>dd</code>: Delete an entire line</li>
<li><code>[X]dd</code>: Delete X number of lines</li>
<li><code>[X]yy</code>: Yank X number of lines</li>
<li><code>G</code>: Go to the last line in a file</li>
<li><code>[X]G</code>: Go to line X in a file</li>
<li><code>gg</code>: Go to the first line in a file</li>
<li><code>:num</code>: Display the current line&#x2019;s line number</li>
<li><code>h</code>: Move left one character</li>
<li><code>l</code>: Move right one character</li>
<li><code>j</code>: Move down one line</li>
<li><code>k</code>: Move up one line</li>
</ul>
<h2 id="set-env">Set ENV</h2>
<ul>
<li><code>vi ~/.bash_profile</code>: env variable setup file</li>
<li><code>source ~/.bash_profile</code>: export env variables to system</li>
<li><code>export JAVA_HOME=/root/java/jdk1.7.0_45</code></li>
<li><code>export MAVEN_HOME=/root/java/apache-maven-3.1.1</code></li>
<li><code>export ANT_HOME=/root/java/apache-ant-1.9.2</code></li>
<li><code>export PATH=$PATH:${JAVA_HOME}/bin:${MAVEN_HOME}/bin:${ANT_HOME}/bin</code></li>
</ul>
<h2 id="increase-swap-size">Increase swap size</h2>
<ul>
<li><code>dd if=/dev/zero of=/.swapfile bs=1M count=2048</code></li>
<li><code>mkswap -v1 /.swapfile</code></li>
<li><code>swapon /.swapfile</code></li>
</ul>
<h2 id="miscellaneous">Miscellaneous</h2>
<ul>
<li>ubuntu jdk setup: <a href="http://www.mkyong.com/java/how-to-install-java-jdk-on-ubuntu-linux/?ref=blog.gyri.tech">http://www.mkyong.com/java/how-to-install-java-jdk-on-ubuntu-linux/</a></li>
<li>clear temp directory: <a href="http://forums.opensuse.org/english/get-technical-help-here/how-faq-forums/unreviewed-how-faq/412640-clear-temp-files-boot.html?ref=blog.gyri.tech">http://forums.opensuse.org/english/get-technical-help-here/how-faq-forums/unreviewed-how-faq/412640-clear-temp-files-boot.html</a></li>
<li><code>sync; echo 3 &gt; /proc/sys/vm/drop_caches</code>: clear buffer cache</li>
<li><code>/tmp/VMwareDnD</code>: VM ware DragNDrop files - verify and delete</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Migrate Data from MongoDB to PostgreSQL]]></title><description><![CDATA[<blockquote><strong>ETL </strong>(Extract, Transform, Load)</blockquote><p>ETL (Extract, Transform, Load) is a fundamental process in databases and data warehousing used to move and process data from one system to another. It consists of three main steps:</p><ol><li><strong>Extract</strong> &#x2013; Retrieve data from multiple sources (e.g., databases, APIs, files).</li><li><strong>Transform</strong> &#x2013; Clean, filter,</li></ol>]]></description><link>https://blog.gyri.tech/migrate-data-from-mongodb-to-postgresql/</link><guid isPermaLink="false">67d2b2eecf0c31040a1a7e2b</guid><dc:creator><![CDATA[Shubham Kiran Lokhande]]></dc:creator><pubDate>Mon, 17 Mar 2025 07:59:29 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2025/03/Migration.png" medium="image"/><content:encoded><![CDATA[<blockquote><strong>ETL </strong>(Extract, Transform, Load)</blockquote><img src="https://blog.gyri.tech/content/images/2025/03/Migration.png" alt="Migrate Data from MongoDB to PostgreSQL"><p>ETL (Extract, Transform, Load) is a fundamental process in databases and data warehousing used to move and process data from one system to another. It consists of three main steps:</p><ol><li><strong>Extract</strong> &#x2013; Retrieve data from multiple sources (e.g., databases, APIs, files).</li><li><strong>Transform</strong> &#x2013; Clean, filter, and modify the data to fit the target system&#x2019;s structure and requirements.</li><li><strong>Load</strong> &#x2013; Store the transformed data into the target database or data warehouse.</li></ol><p>ETL is widely used for <strong>data migration, integration, and analytics</strong>. For example, moving data from <strong>MongoDB to PostgreSQL</strong> is ETL process that ensures structured storage and efficient querying.</p><blockquote><strong>Change Data Capture(CDC) </strong></blockquote><p>The ETL processor supports real-time <strong>Change Data Capture (CDC)</strong> to keep data synchronized between MongoDB and PostgreSQL With CDC:</p><ul><li><strong>Watchers monitor MongoDB collections</strong> for real-time changes.</li><li><strong>Changes (inserts, updates, replacements, and deletes) are automatically captured</strong> and replicated to PostgreSQL.</li><li><strong>Each ETL job can have its own CDC configuration</strong>, ensuring flexibility.</li><li><strong>CDC complements batch ETL</strong>, providing both real-time and scheduled data synchronization.</li></ul><blockquote><strong><em>Methods for Implementing CDC</em></strong></blockquote><h3 id="1-apache-nifi"><strong>1) Apache NiFi</strong></h3><p><a href="https://nifi.apache.org/?ref=blog.gyri.tech" rel="noopener">Apache NiFi</a> is a <strong>powerful data flow automation tool</strong> designed for ETL processes and real-time data streaming. Originally developed by the NSA and later open-sourced by the Apache Software Foundation, NiFi offers:</p><ul><li><strong>An intuitive drag-and-drop interface</strong> for building complex data pipelines.</li><li><strong>Support for multiple data sources and destinations</strong>, making integration seamless.</li><li><strong>Real-time data ingestion and transformation</strong>, ideal for high-volume workloads.</li></ul><h3 id="2-custom-jobs"><strong>2) Custom Jobs</strong></h3><p><a href="https://qms-etl2.gyri.tech/?ref=blog.gyri.tech" rel="noopener">Custom ETL Jobs</a> automate data transfer between different databases, ensuring seamless integration. These jobs:</p><ul><li><strong>Migrate data from MongoDB to PostgreSQL on a scheduled basis</strong> (e.g., daily).</li><li><strong>Define source and target databases</strong> to structure data efficiently.</li><li><strong>Process data incrementally</strong> based on a specific field (e.g., <code>creationDate</code>).</li><li><strong>Handle data in batches</strong> (e.g., 500 records at a time for optimal performance).</li><li><strong>Leverage CDC</strong> to track and update changes, ensuring data consistency with minimal manual intervention.</li></ul><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.gyri.tech/content/images/2025/03/Migration2-2.png" class="kg-image" alt="Migrate Data from MongoDB to PostgreSQL" loading="lazy" width="546" height="631"><figcaption><span style="white-space: pre-wrap;">Migrate Data</span></figcaption></figure>]]></content:encoded></item><item><title><![CDATA[Firebase Push Notifications Setup in React Native (Android specific)]]></title><description><![CDATA[<p>Firebase Cloud Messaging (FCM) is a service that allows you to send notifications and messages to users across platforms like iOS, Android, and the web.This guide will walk you through how to set up Firebase Push Notifications in a React Native app for Android.</p><h3 id="prerequisites"><strong>Prerequisites</strong></h3><ul><li>A React Native project</li></ul>]]></description><link>https://blog.gyri.tech/how-to-integrate-firebase-push-notifications-in-react-native/</link><guid isPermaLink="false">67a06513cf0c31040a1a7d3b</guid><dc:creator><![CDATA[Ankita Shelake]]></dc:creator><pubDate>Thu, 06 Feb 2025 05:51:00 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2025/02/1_W_GKuVzLWjsKmuwdJ0qWgQ.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.gyri.tech/content/images/2025/02/1_W_GKuVzLWjsKmuwdJ0qWgQ.png" alt="Firebase Push Notifications Setup in React Native (Android specific)"><p>Firebase Cloud Messaging (FCM) is a service that allows you to send notifications and messages to users across platforms like iOS, Android, and the web.This guide will walk you through how to set up Firebase Push Notifications in a React Native app for Android.</p><h3 id="prerequisites"><strong>Prerequisites</strong></h3><ul><li>A React Native project setup (if you don&apos;t have one, you can create it by running <code>npx react-native init MyProject</code>).</li><li>Node.js installed on your machine.</li><li>Firebase account and Firebase project setup in the Firebase Console.</li></ul><h3 id="step-1-create-a-firebase-project">Step 1: Create a Firebase Project:<br></h3><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/02/image-10.png" class="kg-image" alt="Firebase Push Notifications Setup in React Native (Android specific)" loading="lazy" width="2000" height="1300" srcset="https://blog.gyri.tech/content/images/size/w600/2025/02/image-10.png 600w, https://blog.gyri.tech/content/images/size/w1000/2025/02/image-10.png 1000w, https://blog.gyri.tech/content/images/size/w1600/2025/02/image-10.png 1600w, https://blog.gyri.tech/content/images/2025/02/image-10.png 2000w" sizes="(min-width: 720px) 720px"></figure><ol><li>Go to <a href="https://firebase.google.com/?gad_source=1&amp;gclid=CjwKCAiAtYy9BhBcEiwANWQQL_3G1WupNuemiDQxtWOTSSAuArwvVW8VW3DbHY0SBz7Kp5ULSrxvfhoC1cwQAvD_BwE&amp;gclsrc=aw.ds&amp;ref=blog.gyri.tech" rel="noreferrer">Firebase Console</a>.</li><li>Click on <strong>Add Project</strong> and follow the steps to create a new project.</li><li><strong>Go to your Firebase Console</strong> and select your project.</li><li>In the <strong>left-hand sidebar</strong>, navigate to <strong>Project settings</strong> (gear icon at the top).</li><li>In the <strong>Project Settings</strong>, under the <strong>General</strong> tab, you&apos;ll see the <strong>Your apps</strong> section.</li><li>There should be an option to <strong>Download google-services.json</strong> right next to your Android app listing. Just click that, and it&#x2019;ll download the file for you.</li><li>Once downloaded, <strong>move</strong> the <code>google-services.json</code> file into the <strong>app folder</strong> of your Android project (usually located at <code>app/</code> in your project structure).</li></ol><pre><code class="language-MyProject/">MyProject/
  &#x251C;&#x2500;&#x2500; android/
  &#x2502;    &#x251C;&#x2500;&#x2500; app/
  &#x2502;    &#x2502;    &#x251C;&#x2500;&#x2500; google-services.json  &lt;-- place it here
  &#x2502;    &#x2502;    &#x251C;&#x2500;&#x2500; src/
  &#x2502;    &#x2502;    &#x2514;&#x2500;&#x2500; ...
  &#x251C;&#x2500;&#x2500; ios/
  &#x251C;&#x2500;&#x2500; lib/
  &#x2514;&#x2500;&#x2500; ...
</code></pre><h3 id="step-2-install-required-dependencies"><strong>Step 2: Install Required Dependencies:</strong></h3><p>To use Firebase Cloud Messaging in your React Native app, install these packages via ``npm``<br><code>npm install @react-native-firebase/app @react-native-firebase/messaging @notifee/react-native</code></p><ul><li><strong>@react-native-firebase/app</strong>: This is the core library that connects your React Native app with Firebase.</li><li><strong>@react-native-firebase/messaging</strong>: This library allows you to interact with Firebase Cloud Messaging (FCM) for push notifications.</li><li><strong>@notifee/react-native</strong>: Notifee enables developers to rapidly build rich notifications with a simple API interface, whilst taking care of complex problems such as scheduling, background tasks, device API compatibility &amp; more.</li></ul><h3 id="step-3-set-up-and-configure-each-library-according-to-official-documentation">Step 3: Set Up and Configure Each Library According to Official Documentation:</h3><p>a. Setup <code>@react-native-firebase/app</code> (Firebase Core SDK):<br>`npm install @react-native-firebase/app`</p><ol><li>Modify <code>android/build.gradle</code></li></ol><pre><code class="language-groovy">buildscript {
  dependencies {
    // ... other dependencies
    // NOTE: if you are on react-native 0.71 or below, you must not update
    //       the google-services plugin past version 4.3.15 as it requires gradle &gt;= 7.3.0
    classpath &apos;com.google.gms:google-services:4.4.2&apos;
    // Add me --- /\
  }
}</code></pre><ol start="3"><li>Modify <code>android/app/build.gradle</code></li></ol><pre><code class="language-groovy">apply plugin: &apos;com.android.application&apos;
apply plugin: &apos;com.google.gms.google-services&apos; // &lt;- Add this line</code></pre><ol start="4"><li>rebuilding</li></ol><pre><code class="language-bash"># Android apps
npx react-native run-android</code></pre><blockquote><a href="https://rnfirebase.io/?ref=blog.gyri.tech" rel="noreferrer">Document file to learn more about this library.</a></blockquote><p>b. Setup <code>@react-native-firebase/messaging</code>:</p><blockquote><a href="https://rnfirebase.io/?ref=blog.gyri.tech" rel="noreferrer">You can refer to the official documentation for setting up messaging.</a></blockquote><p>c. Setup <code>@notifee/react-native</code>:</p><blockquote><a href="https://notifee.app/react-native/docs/overview?ref=blog.gyri.tech" rel="noreferrer">Document file to learn more about this library.</a></blockquote><h3 id="step-4-get-fcm-device-token">Step 4: Get FCM Device Token:</h3><p><strong>Import the Firebase Messaging Library</strong>: You need to import <code>@react-native-firebase/messaging</code> to retrieve the FCM token.</p><pre><code>import messaging from &apos;@react-native-firebase/messaging&apos;;

async function getDeviceToken() {
  try {
    // Get the FCM token
    const token = await messaging().getToken();
    console.log(&apos;FCM Device Token:&apos;, token);
    return token;
  } catch (error) {
    console.error(&apos;Error fetching FCM token:&apos;, error);
  }
}

// Call the function to retrieve the token
getDeviceToken();
</code></pre><h3 id="step-5-save-the-fcm-token-to-firestore-or-your-backend">Step 5: <strong>Save the FCM Token to Firestore or Your Backend:</strong></h3><p>To send the FCM token to your <strong>backend server</strong>, follow these steps:</p><ol><li><strong>Set up the backend server</strong> (using Node.js or any other backend of your choice).</li><li><strong>Send the token from the React Native app</strong> to your backend, which will save the token in your database.</li></ol><h3 id="step-6-send-a-test-push-notification-from-firebase-console">Step 6: Send a Test Push Notification from Firebase Console:</h3><ol><li><strong>Go to Firebase Console</strong>:</li></ol><ul><li>Navigate to the <a rel="noopener">Firebase Console</a>.</li><li>Select your project.</li></ul><ol start="2"><li><strong>Open the Cloud Messaging Section</strong>:<br></li></ol><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.gyri.tech/content/images/2025/02/image-3.png" class="kg-image" alt="Firebase Push Notifications Setup in React Native (Android specific)" loading="lazy" width="1877" height="573" srcset="https://blog.gyri.tech/content/images/size/w600/2025/02/image-3.png 600w, https://blog.gyri.tech/content/images/size/w1000/2025/02/image-3.png 1000w, https://blog.gyri.tech/content/images/size/w1600/2025/02/image-3.png 1600w, https://blog.gyri.tech/content/images/2025/02/image-3.png 1877w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Click on Create your first campaign</span></figcaption></figure><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/02/image-4.png" class="kg-image" alt="Firebase Push Notifications Setup in React Native (Android specific)" loading="lazy" width="1103" height="983" srcset="https://blog.gyri.tech/content/images/size/w600/2025/02/image-4.png 600w, https://blog.gyri.tech/content/images/size/w1000/2025/02/image-4.png 1000w, https://blog.gyri.tech/content/images/2025/02/image-4.png 1103w" sizes="(min-width: 720px) 720px"></figure><ol start="3"><li><strong>Compose the Message</strong>:</li></ol><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/02/image-6.png" class="kg-image" alt="Firebase Push Notifications Setup in React Native (Android specific)" loading="lazy" width="1099" height="1160" srcset="https://blog.gyri.tech/content/images/size/w600/2025/02/image-6.png 600w, https://blog.gyri.tech/content/images/size/w1000/2025/02/image-6.png 1000w, https://blog.gyri.tech/content/images/2025/02/image-6.png 1099w" sizes="(min-width: 720px) 720px"></figure><ol start="4"><li><strong>Target the Notification</strong>:</li></ol><ul><li>Under <strong>Target</strong>, select <strong>Single Device</strong>.</li><li><strong>Enter the FCM Token</strong>: In the &quot;Token&quot; field, paste the device token that you retrieved in the previous step.<ul><li>If you&#x2019;ve saved the token in Firestore, retrieve it from there, or use the token logged in the console.<br></li></ul></li></ul><ol start="5"><li><strong>Send the Notification</strong>:</li></ol><ul><li>After filling in the details, click on <strong>Send Test Message</strong>.</li><li>You should see a confirmation message stating that the notification was sent successfully.<br></li></ul><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/02/image-7.png" class="kg-image" alt="Firebase Push Notifications Setup in React Native (Android specific)" loading="lazy" width="1019" height="1117" srcset="https://blog.gyri.tech/content/images/size/w600/2025/02/image-7.png 600w, https://blog.gyri.tech/content/images/size/w1000/2025/02/image-7.png 1000w, https://blog.gyri.tech/content/images/2025/02/image-7.png 1019w" sizes="(min-width: 720px) 720px"></figure><blockquote><em>Conclusion</em></blockquote><blockquote><em>Setting up Firebase Push Notifications in a React Native app for Android is a straightforward process if you follow the steps carefully. By integrating the required libraries, configuring Firebase, and obtaining the FCM device token, you can enable seamless push notifications for your users.</em></blockquote>]]></content:encoded></item><item><title><![CDATA[Error-06  React Native and Android Build Updates: Recent Changes to Gradle and Dependencies (Android specific).]]></title><description><![CDATA[<figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/02/image_2025_02_03T13_09_30_063Z.png" class="kg-image" alt loading="lazy" width="1174" height="1340" srcset="https://blog.gyri.tech/content/images/size/w600/2025/02/image_2025_02_03T13_09_30_063Z.png 600w, https://blog.gyri.tech/content/images/size/w1000/2025/02/image_2025_02_03T13_09_30_063Z.png 1000w, https://blog.gyri.tech/content/images/2025/02/image_2025_02_03T13_09_30_063Z.png 1174w" sizes="(min-width: 720px) 720px"></figure><h2 id="steps-to-solve-this-issue">Steps to solve this issue:</h2><p><br>1. <strong>Android Build Configuration Updates</strong></p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/02/image-12.png" class="kg-image" alt loading="lazy" width="1028" height="468" srcset="https://blog.gyri.tech/content/images/size/w600/2025/02/image-12.png 600w, https://blog.gyri.tech/content/images/size/w1000/2025/02/image-12.png 1000w, https://blog.gyri.tech/content/images/2025/02/image-12.png 1028w" sizes="(min-width: 720px) 720px"></figure><ol start="2"><li>Updates to <code>package.json</code> Dependencies</li></ol><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/02/image-13.png" class="kg-image" alt loading="lazy" width="1124" height="732" srcset="https://blog.gyri.tech/content/images/size/w600/2025/02/image-13.png 600w, https://blog.gyri.tech/content/images/size/w1000/2025/02/image-13.png 1000w, https://blog.gyri.tech/content/images/2025/02/image-13.png 1124w" sizes="(min-width: 720px) 720px"></figure><ol start="3"><li>Kotlin Version Update in <code>@react-native/gradle-plugin</code>:<br>Another file that was modified is the patch file for <code>@react-native/gradle-plugin</code>:</li></ol><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/02/image-14.png" class="kg-image" alt loading="lazy" width="2000" height="543" srcset="https://blog.gyri.tech/content/images/size/w600/2025/02/image-14.png 600w, https://blog.gyri.tech/content/images/size/w1000/2025/02/image-14.png 1000w, https://blog.gyri.tech/content/images/size/w1600/2025/02/image-14.png 1600w, https://blog.gyri.tech/content/images/2025/02/image-14.png 2226w" sizes="(min-width: 720px) 720px"></figure><blockquote><a href="https://github.com/riteshshukla04/React-native-zip-header-repro/commit/de19abbfa08e8f802f9f07d2db223315460423f2?ref=blog.gyri.tech" rel="noreferrer">To know more about this issue</a></blockquote>]]></description><link>https://blog.gyri.tech/error-2/</link><guid isPermaLink="false">67a35fc2cf0c31040a1a7dfe</guid><dc:creator><![CDATA[Ankita Shelake]]></dc:creator><pubDate>Wed, 05 Feb 2025 13:38:17 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2025/02/image-11-1.png" medium="image"/><content:encoded><![CDATA[<figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/02/image_2025_02_03T13_09_30_063Z.png" class="kg-image" alt="Error-06  React Native and Android Build Updates: Recent Changes to Gradle and Dependencies (Android specific)." loading="lazy" width="1174" height="1340" srcset="https://blog.gyri.tech/content/images/size/w600/2025/02/image_2025_02_03T13_09_30_063Z.png 600w, https://blog.gyri.tech/content/images/size/w1000/2025/02/image_2025_02_03T13_09_30_063Z.png 1000w, https://blog.gyri.tech/content/images/2025/02/image_2025_02_03T13_09_30_063Z.png 1174w" sizes="(min-width: 720px) 720px"></figure><h2 id="steps-to-solve-this-issue">Steps to solve this issue:</h2><img src="https://blog.gyri.tech/content/images/2025/02/image-11-1.png" alt="Error-06  React Native and Android Build Updates: Recent Changes to Gradle and Dependencies (Android specific)."><p><br>1. <strong>Android Build Configuration Updates</strong></p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/02/image-12.png" class="kg-image" alt="Error-06  React Native and Android Build Updates: Recent Changes to Gradle and Dependencies (Android specific)." loading="lazy" width="1028" height="468" srcset="https://blog.gyri.tech/content/images/size/w600/2025/02/image-12.png 600w, https://blog.gyri.tech/content/images/size/w1000/2025/02/image-12.png 1000w, https://blog.gyri.tech/content/images/2025/02/image-12.png 1028w" sizes="(min-width: 720px) 720px"></figure><ol start="2"><li>Updates to <code>package.json</code> Dependencies</li></ol><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/02/image-13.png" class="kg-image" alt="Error-06  React Native and Android Build Updates: Recent Changes to Gradle and Dependencies (Android specific)." loading="lazy" width="1124" height="732" srcset="https://blog.gyri.tech/content/images/size/w600/2025/02/image-13.png 600w, https://blog.gyri.tech/content/images/size/w1000/2025/02/image-13.png 1000w, https://blog.gyri.tech/content/images/2025/02/image-13.png 1124w" sizes="(min-width: 720px) 720px"></figure><ol start="3"><li>Kotlin Version Update in <code>@react-native/gradle-plugin</code>:<br>Another file that was modified is the patch file for <code>@react-native/gradle-plugin</code>:</li></ol><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/02/image-14.png" class="kg-image" alt="Error-06  React Native and Android Build Updates: Recent Changes to Gradle and Dependencies (Android specific)." loading="lazy" width="2000" height="543" srcset="https://blog.gyri.tech/content/images/size/w600/2025/02/image-14.png 600w, https://blog.gyri.tech/content/images/size/w1000/2025/02/image-14.png 1000w, https://blog.gyri.tech/content/images/size/w1600/2025/02/image-14.png 1600w, https://blog.gyri.tech/content/images/2025/02/image-14.png 2226w" sizes="(min-width: 720px) 720px"></figure><blockquote><a href="https://github.com/riteshshukla04/React-native-zip-header-repro/commit/de19abbfa08e8f802f9f07d2db223315460423f2?ref=blog.gyri.tech" rel="noreferrer">To know more about this issue</a></blockquote>]]></content:encoded></item><item><title><![CDATA[Moving Entities to System and Common]]></title><description><![CDATA[<blockquote><strong>Understanding the Role of System and Common Projects</strong></blockquote><ul><li><strong>Common Project</strong>:<br>For shared things that multiple projects can use (like tools, reusable code, or common settings).<br>Example: Same DTOs, enums, or helper functions used everywhere.</li><li><strong>System Project</strong>:<br>For things specific to one application (like custom logic, workflows, and configurations).<br>Example: APIs,</li></ul>]]></description><link>https://blog.gyri.tech/moving-entities-to-system-and-common/</link><guid isPermaLink="false">6798be02cf0c31040a1a7ce7</guid><dc:creator><![CDATA[Shubham Kiran Lokhande]]></dc:creator><pubDate>Tue, 28 Jan 2025 13:17:18 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2025/01/6-1.png" medium="image"/><content:encoded><![CDATA[<blockquote><strong>Understanding the Role of System and Common Projects</strong></blockquote><ul><li><strong>Common Project</strong>:<br>For shared things that multiple projects can use (like tools, reusable code, or common settings).<br>Example: Same DTOs, enums, or helper functions used everywhere.</li><li><strong>System Project</strong>:<br>For things specific to one application (like custom logic, workflows, and configurations).<br>Example: APIs, security settings, or how the app runs.</li></ul><blockquote><strong>Before Migration (Example Project)</strong></blockquote><img src="https://blog.gyri.tech/content/images/2025/01/6-1.png" alt="Moving Entities to System and Common"><p>Example for &#x201C;Model&#x201D; entity</p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/01/1-1.png" class="kg-image" alt="Moving Entities to System and Common" loading="lazy" width="490" height="742"></figure><p><strong>A)&#xA0;&#xA0;Steps to Move in Common</strong> <strong>Project :</strong></p><p>1) Create a new package: &#x201C;<code>tech.gyri.example.query.model&#x201D;</code>.</p><p>2) Move the &#x201C;<code>dto</code>, <code>entity</code>, and <code>repository&#x201D;</code> objects from the &#x201C;<code>query&#x201D;</code> package of the Example project to the newly created package &#x201C;<code>tech.gyri.example.model&#x201D;</code> in the <strong>Common project</strong>.</p><p>3) Add a new enum to the &#x201C;<code>CDCCollectionEnum&#x201D;</code> file and implement a corresponding function in the &#x201C;<code>CDCStreamWatcher&#x201D;</code> file.</p><p>4) &#xA0;Add a new enum in the &#x201C;<code>application-common-properties&#x201D;</code> file.</p><blockquote><strong>Common Project (After Migration)</strong></blockquote><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/01/2.png" class="kg-image" alt="Moving Entities to System and Common" loading="lazy" width="792" height="534" srcset="https://blog.gyri.tech/content/images/size/w600/2025/01/2.png 600w, https://blog.gyri.tech/content/images/2025/01/2.png 792w" sizes="(min-width: 720px) 720px"></figure><p><strong>B)&#xA0;&#xA0;Steps to Move in System Project :</strong></p><p>1) Create the following packages in the <strong>System project</strong>:</p><ul><li><code>tech.gyri.example.command</code></li><li><code>tech.gyri.example.framework</code></li><li><code>tech.gyri.example.query</code></li><li><code>tech.gyri.example.shared</code></li></ul><p>2) Move the &#x201C;<code>model.command&#x201D;</code> objects from the <strong>Example project</strong> to the &#x201C;<code>tech.gyri.example.command.model&#x201D;</code> package in the <strong>System project</strong>.</p><p>3) In &#x201C;<code>tech.gyri.example.framework.common&#x201D;</code>, create a &#x201C;<code>cache&#x201D;</code> package and move the &#x201C;<code>RedisCacheConstants&#x201D;</code> file there. Additionally, create another package named &#x201C;<code>exception&#x201D;</code> and move the &#x201C;<code>ErrorCode&#x201D;</code> file into it.</p><p>4) Move the &#x201C;<code>model.query&#x201D;</code> objects from the <strong>Example project</strong> to the &#x201C;<code>tech.gyri.example.query.model&#x201D;</code> package in the <strong>System project</strong>. Within this package, move only the &#x201C;<code>api</code>, <code>exceptions</code>, and <code>infrastructure&#x201D;</code> sub-packages. Move the remaining sub-packages to the <strong>Common project</strong>.</p><p>5) Move the &#x201C;<code>shared.model.events&#x201D;</code> objects from the <strong>Example project</strong> to &#x201C;<code>tech.gyri.example.shared&#x201D;</code> in the <strong>System project</strong>.</p><p>6) Set the authority for the API in the &#x201C;<code>securityConfig&#x201D;</code> file within the <strong>System project</strong>. Add all APIs from the <strong>Example project</strong> to the <strong>System project</strong>.</p><p>7) Register <strong>command</strong> and <strong>query</strong> components in the <code>main</code> file of the <strong>System project</strong>.</p><p>8) Configure <strong>Consul</strong> in the <code>application.properties</code> file.</p><blockquote><strong>System Project (After Migration)</strong></blockquote><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/01/3.png" class="kg-image" alt="Moving Entities to System and Common" loading="lazy" width="457" height="724"></figure><blockquote><strong>Updated Example Project (After Migration)</strong></blockquote><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/01/4.png" class="kg-image" alt="Moving Entities to System and Common" loading="lazy" width="643" height="253" srcset="https://blog.gyri.tech/content/images/size/w600/2025/01/4.png 600w, https://blog.gyri.tech/content/images/2025/01/4.png 643w"></figure><blockquote>The process of moving entities from the Example project to the <strong>System</strong> and <strong>Common</strong> projects helps achieve a more organized and modular software architecture. By distinguishing between system-specific logic and shared components, the system becomes more maintainable and scalable.</blockquote><p><a href="https://blog.gyri.tech/cqrs-pattern-in-microservices/" rel="noreferrer">To know more about CQRS pattern</a></p>]]></content:encoded></item><item><title><![CDATA[Transforming Image  into Meaningful Words]]></title><description><![CDATA[<p>In this blog, we will explore how to create a robust system for describe image into meaningful word descriptions using Kotlin and Spring Boot. This process integrates modern tools and frameworks like AI APIs (Gemini, ChatGPT), Redis caching, and reactive programming.</p><h3 id="step-1-the-controller">Step 1: The Controller</h3><p>The controller exposes an API</p>]]></description><link>https://blog.gyri.tech/transform-image-into-words-using-ai/</link><guid isPermaLink="false">67820b55cf0c31040a1a7c1d</guid><dc:creator><![CDATA[Vishal Dilip Patil]]></dc:creator><pubDate>Mon, 27 Jan 2025 06:36:28 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2025/01/DALL-E-2025-01-11-13.12.33---A-visually-engaging-illustration-of-a-futuristic-AI-system-analyzing-an-image-from-a-URL-to-generate-an-accurate-textual-description.-The-AI-is-repres.webp" medium="image"/><content:encoded><![CDATA[<img src="https://blog.gyri.tech/content/images/2025/01/DALL-E-2025-01-11-13.12.33---A-visually-engaging-illustration-of-a-futuristic-AI-system-analyzing-an-image-from-a-URL-to-generate-an-accurate-textual-description.-The-AI-is-repres.webp" alt="Transforming Image  into Meaningful Words"><p>In this blog, we will explore how to create a robust system for describe image into meaningful word descriptions using Kotlin and Spring Boot. This process integrates modern tools and frameworks like AI APIs (Gemini, ChatGPT), Redis caching, and reactive programming.</p><h3 id="step-1-the-controller">Step 1: The Controller</h3><p>The controller exposes an API endpoint to accept requests for image descriptions. Here&#x2019;s how it&#x2019;s implemented:</p><ul><ul><ul><li>The endpoint accepts a <code>POST</code> request containing an  <strong>image </strong>and <strong>account credentials</strong>.</li><li>It validates the input and delegates the request to the query handler for further processing.</li></ul></ul></ul><h3 id="step-2-the-query">Step 2: The Query</h3><p>The query encapsulates the data required to retrieve the image description.</p><ul><ul><ul><li>It ensures the necessary details like <code>imageUrl</code> and <code>accountToken</code> are provided.</li><li>Structuring the data in a manner that is easily processed by the query handler.</li></ul></ul></ul><h3 id="step-3-query-handler">Step 3: Query Handler</h3><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2025/01/flowchart-2.png" class="kg-image" alt="Transforming Image  into Meaningful Words" loading="lazy" width="960" height="720" srcset="https://blog.gyri.tech/content/images/size/w600/2025/01/flowchart-2.png 600w, https://blog.gyri.tech/content/images/2025/01/flowchart-2.png 960w" sizes="(min-width: 720px) 720px"></figure><p>The query handler executes the core business logic. Its responsibilities include:</p><ol><li><strong>Cache Check</strong>:<br>It checks if a cached description exists in <strong>Redis</strong>.</li><li><strong>Database Retrieval</strong>:<br>If no cached description is found, it retrieves the description from the database, if available.</li><li><strong>API Call</strong>:<br>If the description is neither in the cache nor the database, it calls the <strong>AI API</strong> to generate a new description.</li></ol><h3 id="step-4-ai-api-integration">Step 4: AI API Integration</h3><p>The service interacts with the AI API to generate image descriptions.</p><h3 id="summary">Summary</h3><p>This system shows how to use Kotlin, Spring Boot, and AI to turn image into word descriptions. By using caching and reactive programming, it works quickly and can handle a lot of requests. Whether you&apos;re creating an AI app or learning new tools, this setup is a great starting point.</p>]]></content:encoded></item><item><title><![CDATA[Configuring Liquibase in Spring Boot]]></title><description><![CDATA[<p>Liquibase is a powerful database schema evolution tool that integrates excellent with Spring Boot projects. In this guide, we&apos;ll walk through the process of configuring Liquibase in a Spring Boot application using Kotlin and Gradle.</p><blockquote><em>Step 1: Add Dependencies </em></blockquote><pre><code>dependencies {
implementation(&quot;org.springframework.boot:spring-boot-starter-data-jpa&quot;)
implementation(</code></pre>]]></description><link>https://blog.gyri.tech/configuring-liquibase-in-spring-boot-with-kotlin-and-gradle/</link><guid isPermaLink="false">65ab651007863504b8cfe5e0</guid><dc:creator><![CDATA[AsifDesai]]></dc:creator><pubDate>Fri, 13 Dec 2024 01:55:29 GMT</pubDate><media:content url="https://blog.gyri.tech/content/images/2024/01/Untitled-Diagram-3.webp" medium="image"/><content:encoded><![CDATA[<img src="https://blog.gyri.tech/content/images/2024/01/Untitled-Diagram-3.webp" alt="Configuring Liquibase in Spring Boot"><p>Liquibase is a powerful database schema evolution tool that integrates excellent with Spring Boot projects. In this guide, we&apos;ll walk through the process of configuring Liquibase in a Spring Boot application using Kotlin and Gradle.</p><blockquote><em>Step 1: Add Dependencies </em></blockquote><pre><code>dependencies {
implementation(&quot;org.springframework.boot:spring-boot-starter-data-jpa&quot;)
implementation(&quot;org.liquibase:liquibase-core&quot;)

// Add your PostgreSQL dependency
implementation(&quot;org.postgresql:postgresql&quot;)
}
</code></pre><p>Run <em>`./gradlew build`  </em>to fetch the updated dependencies.</p><blockquote><em>Step 2: Create Liquibase Configuration </em></blockquote><p>create a <em>`liquibase.properties` </em>file in<em> `src/main/resources` </em>with your postgreSQL database connection details and Liquibase settings: </p><figure class="kg-card kg-image-card"><img src="https://blog.gyri.tech/content/images/2024/01/db-agnostic-500x239.webp" class="kg-image" alt="Configuring Liquibase in Spring Boot" loading="lazy" width="500" height="239"></figure><blockquote><em>Step 3: Create Liquibase Change Log </em></blockquote><p>In the <em>`src/main/resources/db/changelog`</em> directory, create a <em>`db.changelog-master.xml`</em> file. This file will contain your database changes sets:</p><pre><code>&lt;!-- db.changelog-master.xml --&gt;

&lt;databaseChangeLog
        xmlns=&quot;http://www.liquibase.org/xml/ns/dbchangelog&quot;
        xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
        xsi:schemaLocation=&quot;http://www.liquibase.org/xml/ns/dbchangelog
                            http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd&quot;&gt;

    &lt;!-- Add your change sets here --&gt;

&lt;/databaseChangeLog&gt;
</code></pre><blockquote>Step 4: Configure Liquibase in <strong><em>`application.properties` </em></strong></blockquote><p>Open your `application.properties` file and configure liquibase : </p><pre><code># application.properties

spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.xml
</code></pre><p>Step 5: Run Your Application</p><p>Exceute the following Gradle command to run your Spring Boot application: </p><pre><code>./gradlew bootRun
</code></pre><p>Liquibase will automatically apply the database changes during the application startup.</p><blockquote>Congratulations! You&apos;ve successfully configured Liquibase in your Spring Boot project , with PostgreSQL as the database. Customize the database connection properties and change sets based on your project requirements.<br></blockquote><blockquote><em>NOTE: Adjust the PostgreSQL connection details according to your setup.</em></blockquote>]]></content:encoded></item></channel></rss>