[{"data":1,"prerenderedAt":5348},["ShallowReactive",2],{"projects-count":3},[4,329,522,878,1419,1708,2170,2593,2931,3343,3656,4125,4456,4999],{"id":5,"title":6,"body":7,"description":307,"duration":308,"extension":309,"featured":310,"github":311,"image":312,"live":313,"meta":314,"navigation":315,"order":227,"path":316,"role":313,"seo":317,"status":318,"stem":319,"team_size":313,"tech":320,"type":326,"year":327,"__hash__":328},"projects\u002Fproject\u002Fauto-discover-vms.md","Azure Auto Discover VMs",{"type":8,"value":9,"toc":299},"minimark",[10,15,19,23,26,69,73,78,86,108,112,117,123,158,162,165,295],[11,12,14],"h2",{"id":13},"the-problem","The Problem",[16,17,18],"p",{},"In standard cloud deployments, scaling backend servers or replacing unhealthy instances requires manually updating load balancer configurations or hardcoding static IPs. This manual intervention introduces configuration drift, increases the risk of human error, and causes latency or dropped connections during proxy configuration reloads. In a dynamic, auto-scaling environment, infrastructure must be self-aware and autonomous.",[11,20,22],{"id":21},"my-solution","My Solution",[16,24,25],{},"I engineered a custom Service Mesh architecture for Azure that eliminates manual IP tracking and static load balancer configuration.",[27,28,29,40,48,61],"ul",{},[30,31,32,36,39],"li",{},[33,34,35],"strong",{},"Custom Control Plane",[37,38],"br",{},"\nBuilt a lightweight, highly concurrent Go service using the Azure SDK to automatically discover backend Virtual Machines based on metadata.",[30,41,42,45,47],{},[33,43,44],{},"Dynamic Configuration (xDS)",[37,46],{},"\nIntegrated Envoy Proxy as the edge gateway. It receives real-time configuration via gRPC and updates routing instantly without requiring restarts.",[30,49,50,53,55,56,60],{},[33,51,52],{},"Infrastructure as Code",[37,54],{},"\nOrchestrated the environment using modular Terraform. Infrastructure is bootstrapped automatically using ",[57,58,59],"code",{},"cloud-init",".",[30,62,63,66,68],{},[33,64,65],{},"Real-Time Observability",[37,67],{},"\nImplemented a REST API in the Control Plane to expose discovery status, health metrics, and system state.",[11,70,72],{"id":71},"technical-deep-dive","Technical Deep Dive",[74,75,77],"h3",{"id":76},"architecture-decisions","Architecture Decisions",[16,79,80,83,85],{},[33,81,82],{},"Why Envoy Proxy over Nginx\u002FHAProxy?",[37,84],{},"\nTraditional reverse proxies require reloads to apply upstream changes, which can drop active connections. Envoy supports dynamic configuration via the xDS API, allowing in-memory updates with zero downtime.",[16,87,88,91,93,94,97,98,97,101,97,104,107],{},[33,89,90],{},"Why Terraform Modularity?",[37,92],{},"\nThe infrastructure is separated into independent modules (",[57,95,96],{},"network",", ",[57,99,100],{},"backend-vms",[57,102,103],{},"control-plane",[57,105,106],{},"envoy-lb","). This ensures backend changes do not impact core networking or load balancing layers.",[74,109,111],{"id":110},"key-features","Key Features",[113,114,116],"h4",{"id":115},"_1-automated-iac-provisioning-and-injection","1. Automated IaC Provisioning and Injection",[16,118,119,120,122],{},"Terraform dynamically injects the Control Plane IP into Envoy’s bootstrap configuration using ",[57,121,59],{},", enabling immediate gRPC connectivity on startup.",[124,125,130],"pre",{"className":126,"code":127,"language":128,"meta":129,"style":129},"language-hcl shiki shiki-themes github-light github-dark","# Inject Control Plane IP into Envoy bootstrap config\ncustom_data = base64encode(templatefile(\"${path.module}\u002Fcloud-init.yml\", {\n  control_plane_host = var.control_plane_host\n}))\n","hcl","",[57,131,132,140,146,152],{"__ignoreMap":129},[133,134,137],"span",{"class":135,"line":136},"line",1,[133,138,139],{},"# Inject Control Plane IP into Envoy bootstrap config\n",[133,141,143],{"class":135,"line":142},2,[133,144,145],{},"custom_data = base64encode(templatefile(\"${path.module}\u002Fcloud-init.yml\", {\n",[133,147,149],{"class":135,"line":148},3,[133,150,151],{},"  control_plane_host = var.control_plane_host\n",[133,153,155],{"class":135,"line":154},4,[133,156,157],{},"}))\n",[113,159,161],{"id":160},"_2-rest-api-for-mesh-observability","2. REST API for Mesh Observability",[16,163,164],{},"A REST API was built alongside the gRPC server to provide real-time visibility into the system state.",[124,166,170],{"className":167,"code":168,"language":169,"meta":129,"style":129},"language-json shiki shiki-themes github-light github-dark","\u002F\u002F GET \u002Fapi\u002Fvms\n{\n  \"last_update\": \"2025-02-04T10:00:00Z\",\n  \"total_vms\": 2,\n  \"snapshot_version\": 15,\n  \"vms\": [\n    {\n      \"name\": \"auto-discover-vms-app-0\",\n      \"public_ip\": \"20.120.10.1\",\n      \"private_ip\": \"10.1.1.4\"\n    }\n  ]\n}\n","json",[57,171,172,178,184,200,212,225,234,240,253,266,277,283,289],{"__ignoreMap":129},[133,173,174],{"class":135,"line":136},[133,175,177],{"class":176},"sJ8bj","\u002F\u002F GET \u002Fapi\u002Fvms\n",[133,179,180],{"class":135,"line":142},[133,181,183],{"class":182},"sVt8B","{\n",[133,185,186,190,193,197],{"class":135,"line":148},[133,187,189],{"class":188},"sj4cs","  \"last_update\"",[133,191,192],{"class":182},": ",[133,194,196],{"class":195},"sZZnC","\"2025-02-04T10:00:00Z\"",[133,198,199],{"class":182},",\n",[133,201,202,205,207,210],{"class":135,"line":154},[133,203,204],{"class":188},"  \"total_vms\"",[133,206,192],{"class":182},[133,208,209],{"class":188},"2",[133,211,199],{"class":182},[133,213,215,218,220,223],{"class":135,"line":214},5,[133,216,217],{"class":188},"  \"snapshot_version\"",[133,219,192],{"class":182},[133,221,222],{"class":188},"15",[133,224,199],{"class":182},[133,226,228,231],{"class":135,"line":227},6,[133,229,230],{"class":188},"  \"vms\"",[133,232,233],{"class":182},": [\n",[133,235,237],{"class":135,"line":236},7,[133,238,239],{"class":182},"    {\n",[133,241,243,246,248,251],{"class":135,"line":242},8,[133,244,245],{"class":188},"      \"name\"",[133,247,192],{"class":182},[133,249,250],{"class":195},"\"auto-discover-vms-app-0\"",[133,252,199],{"class":182},[133,254,256,259,261,264],{"class":135,"line":255},9,[133,257,258],{"class":188},"      \"public_ip\"",[133,260,192],{"class":182},[133,262,263],{"class":195},"\"20.120.10.1\"",[133,265,199],{"class":182},[133,267,269,272,274],{"class":135,"line":268},10,[133,270,271],{"class":188},"      \"private_ip\"",[133,273,192],{"class":182},[133,275,276],{"class":195},"\"10.1.1.4\"\n",[133,278,280],{"class":135,"line":279},11,[133,281,282],{"class":182},"    }\n",[133,284,286],{"class":135,"line":285},12,[133,287,288],{"class":182},"  ]\n",[133,290,292],{"class":135,"line":291},13,[133,293,294],{"class":182},"}\n",[296,297,298],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":129,"searchDepth":142,"depth":142,"links":300},[301,302,303],{"id":13,"depth":142,"text":14},{"id":21,"depth":142,"text":22},{"id":71,"depth":142,"text":72,"children":304},[305,306],{"id":76,"depth":148,"text":77},{"id":110,"depth":148,"text":111},"A dynamic Service Mesh utilizing Envoy Proxy and a custom Go Control Plane to automatically discover and load balance Azure Virtual Machines with zero downtime.","3 weeks","md",false,"https:\u002F\u002Fgithub.com\u002Fszuryuu\u002Fauto-discover-vms","\u002Fimages\u002Fprojects\u002Fterraform.png",null,{},true,"\u002Fproject\u002Fauto-discover-vms",{"title":6,"description":307},"Completed","project\u002Fauto-discover-vms",[321,322,323,324,325],"Go","Envoy Proxy","Terraform","Azure","Cloud-init","Solo Project","2026","Xj35xEp8AGPgIVTcPjHOaTmCzo-JJ6at_tJJsymA7Es",{"id":330,"title":331,"body":332,"description":512,"duration":513,"extension":309,"featured":315,"github":514,"image":312,"live":313,"meta":515,"navigation":315,"order":142,"path":516,"role":313,"seo":517,"status":318,"stem":518,"team_size":313,"tech":519,"type":326,"year":327,"__hash__":521},"projects\u002Fproject\u002Fauto-discover-vmss.md","Dynamic VMSS Auto-Discovery & Mesh",{"type":8,"value":333,"toc":504},[334,336,339,341,344,370,372,374,380,386,390,394,397,501],[11,335,14],{"id":13},[16,337,338],{},"In a modern cloud environment, static infrastructure is a liability. Applications must scale out during traffic spikes and scale in during quiet periods to optimize costs. However, dynamic scaling introduces a massive routing challenge: as Virtual Machine Scale Sets (VMSS) autonomously create and destroy ephemeral instances, traditional load balancers fail to keep up. Updating reverse proxy configurations manually or via slow CI\u002FCD pipelines during an autoscaling event leads to dropped requests, traffic blackholes, and unacceptable downtime.",[11,340,22],{"id":21},[16,342,343],{},"I evolved my previous static discovery architecture into a fully elastic, self-healing Service Mesh designed specifically for Azure VMSS.",[27,345,346,352,358,364],{},[30,347,348,351],{},[33,349,350],{},"Metric-Driven Elasticity:"," Orchestrated Azure Monitor Autoscale settings via Terraform to dynamically scale backend instances based on real-time CPU thresholds.",[30,353,354,357],{},[33,355,356],{},"Autonomous Control Plane:"," Enhanced the Go-based Control Plane to continuously track ephemeral VMSS instances using Azure Managed Identities, instantly detecting scaling events.",[30,359,360,363],{},[33,361,362],{},"Zero-Downtime Routing (xDS):"," Leveraged Envoy Proxy as the edge gateway. When the Control Plane detects a new instance spun up by the autoscaler, it streams the new routing topology to Envoy via gRPC, applying the configuration in-memory without dropping a single active connection.",[30,365,366,369],{},[33,367,368],{},"Immutable Infrastructure:"," Completely codified the network isolation, scale sets, autoscale rules, and security groups using modular Terraform.",[11,371,72],{"id":71},[74,373,77],{"id":76},[16,375,376,379],{},[33,377,378],{},"Why Virtual Machine Scale Sets (VMSS)?","\nUnlike standalone VMs, VMSS provides native high availability, fault domains, and automated elasticity. By decoupling the compute layer into a Scale Set, the architecture shifts from \"pet\" servers to \"cattle\", allowing the system to handle unpredictable traffic loads efficiently.",[16,381,382,385],{},[33,383,384],{},"Why Envoy's xDS over DNS-based Discovery?","\nDNS caching and TTLs (Time to Live) create dangerous propagation delays during scaling events. Envoy's xDS protocol pushes endpoint updates directly to the proxy in real-time, ensuring traffic is only routed to instances that actually exist and are ready to serve requests.",[74,387,389],{"id":388},"key-features-i-built","Key Features I Built",[113,391,393],{"id":392},"_1-metric-driven-autoscaling-logic","1. Metric-Driven Autoscaling Logic",[16,395,396],{},"I wrote Terraform modules to implement strict autoscaling rules. The infrastructure automatically scales out (adds instances) when CPU utilization exceeds 75% for 5 minutes, and scales in (removes instances) when CPU drops below 25%, optimizing cloud computing costs.",[124,398,400],{"className":126,"code":399,"language":128,"meta":129,"style":129},"# modules\u002Fbackend-vmss\u002Fautoscale.tf\nrule {\n  metric_trigger {\n    metric_name        = \"Percentage CPU\"\n    metric_resource_id = azurerm_linux_virtual_machine_scale_set.app_vmss.id\n    time_grain         = \"PT1M\"\n    statistic          = \"Average\"\n    time_window        = \"PT5M\"\n    time_aggregation   = \"Average\"\n    operator           = \"GreaterThan\"\n    threshold          = 75\n  }\n  scale_action {\n    direction = \"Increase\"\n    type      = \"ChangeCount\"\n    value     = \"1\"\n    cooldown  = \"PT1M\"\n  }\n}\n",[57,401,402,407,412,417,422,427,432,437,442,447,452,457,462,467,473,479,485,491,496],{"__ignoreMap":129},[133,403,404],{"class":135,"line":136},[133,405,406],{},"# modules\u002Fbackend-vmss\u002Fautoscale.tf\n",[133,408,409],{"class":135,"line":142},[133,410,411],{},"rule {\n",[133,413,414],{"class":135,"line":148},[133,415,416],{},"  metric_trigger {\n",[133,418,419],{"class":135,"line":154},[133,420,421],{},"    metric_name        = \"Percentage CPU\"\n",[133,423,424],{"class":135,"line":214},[133,425,426],{},"    metric_resource_id = azurerm_linux_virtual_machine_scale_set.app_vmss.id\n",[133,428,429],{"class":135,"line":227},[133,430,431],{},"    time_grain         = \"PT1M\"\n",[133,433,434],{"class":135,"line":236},[133,435,436],{},"    statistic          = \"Average\"\n",[133,438,439],{"class":135,"line":242},[133,440,441],{},"    time_window        = \"PT5M\"\n",[133,443,444],{"class":135,"line":255},[133,445,446],{},"    time_aggregation   = \"Average\"\n",[133,448,449],{"class":135,"line":268},[133,450,451],{},"    operator           = \"GreaterThan\"\n",[133,453,454],{"class":135,"line":279},[133,455,456],{},"    threshold          = 75\n",[133,458,459],{"class":135,"line":285},[133,460,461],{},"  }\n",[133,463,464],{"class":135,"line":291},[133,465,466],{},"  scale_action {\n",[133,468,470],{"class":135,"line":469},14,[133,471,472],{},"    direction = \"Increase\"\n",[133,474,476],{"class":135,"line":475},15,[133,477,478],{},"    type      = \"ChangeCount\"\n",[133,480,482],{"class":135,"line":481},16,[133,483,484],{},"    value     = \"1\"\n",[133,486,488],{"class":135,"line":487},17,[133,489,490],{},"    cooldown  = \"PT1M\"\n",[133,492,494],{"class":135,"line":493},18,[133,495,461],{},[133,497,499],{"class":135,"line":498},19,[133,500,294],{},[296,502,503],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":129,"searchDepth":142,"depth":142,"links":505},[506,507,508],{"id":13,"depth":142,"text":14},{"id":21,"depth":142,"text":22},{"id":71,"depth":142,"text":72,"children":509},[510,511],{"id":76,"depth":148,"text":77},{"id":388,"depth":148,"text":389},"An elastic Service Mesh architecture integrating Envoy Proxy with Azure Virtual Machine Scale Sets (VMSS), featuring metric-driven autoscaling and zero-downtime dynamic routing.","2 weeks","https:\u002F\u002Fgithub.com\u002Fszuryuu\u002Fauto-discover-vmss",{},"\u002Fproject\u002Fauto-discover-vmss",{"title":331,"description":512},"project\u002Fauto-discover-vmss",[321,322,323,520,325],"Azure VMSS","qfh2-iASTOPA1O-xbNeJbwEY7qfZohkAmyaf5u4xfEw",{"id":523,"title":524,"body":525,"description":866,"duration":513,"extension":309,"featured":310,"github":867,"image":868,"live":313,"meta":869,"navigation":315,"order":214,"path":870,"role":313,"seo":871,"status":318,"stem":872,"team_size":313,"tech":873,"type":326,"year":327,"__hash__":877},"projects\u002Fproject\u002Fazure-e2e-devops.md","Azure End-to-End DevOps Pipeline",{"type":8,"value":526,"toc":858},[527,529,532,534,537,571,573,575,589,603,605,609,612,719,723,733,855],[11,528,14],{"id":13},[16,530,531],{},"Manual infrastructure provisioning and application deployment create bottlenecks, configuration drift, and severe security vulnerabilities. Storing hardcoded credentials (like container registry passwords) inside production servers is a massive security risk. Furthermore, pushing code without automated vulnerability scanning often leads to deploying compromised container images to production, while lack of centralized telemetry makes post-deployment troubleshooting nearly impossible.",[11,533,22],{"id":21},[16,535,536],{},"I engineered a fully automated, secure, and observable End-to-End (E2E) DevOps pipeline combining GitHub Actions, Terraform, and Docker on Microsoft Azure.",[27,538,539,545,559,565],{},[30,540,541,544],{},[33,542,543],{},"GitOps Workflows:"," Automated the entire lifecycle from code push to deployment. Terraform plan outputs are automatically generated and commented directly onto Pull Requests for peer review before infrastructure changes are applied.",[30,546,547,550,551,554,555,558],{},[33,548,549],{},"Shift-Left Security:"," Integrated Trivy vulnerability scanning into the CI\u002FCD pipeline, automatically failing the build if ",[57,552,553],{},"CRITICAL"," or ",[57,556,557],{},"HIGH"," vulnerabilities are detected in the Docker image.",[30,560,561,564],{},[33,562,563],{},"Zero Hardcoded Secrets:"," Utilized Azure Managed Identities (System-Assigned). The production Virtual Machine authenticates to the Azure Container Registry (ACR) securely without needing any stored passwords or tokens.",[30,566,567,570],{},[33,568,569],{},"Centralized Observability:"," Configured Application Insights and Log Analytics Workspace via Terraform, capturing both host-level metrics and application telemetry.",[11,572,72],{"id":71},[74,574,77],{"id":76},[16,576,577,580,581,584,585,588],{},[33,578,579],{},"Why Azure Managed Identities?","\nTraditionally, pulling a private Docker image requires running ",[57,582,583],{},"docker login"," with a username and password, storing those secrets in the server's environment. By attaching a System-Assigned Managed Identity to the VM with ",[57,586,587],{},"AcrPull"," RBAC roles, the infrastructure securely authenticates to Azure's API natively. If the server is ever compromised, there are no credential files for an attacker to steal.",[16,590,591,594,595,598,599,602],{},[33,592,593],{},"Why Terraform with GitHub Actions?","\nI decoupled the infrastructure provisioning (",[57,596,597],{},"terraform apply",") from the application build process (",[57,600,601],{},"docker build","). This ensures that compute, network, and monitoring resources are guaranteed to be in the correct state before the latest application artifact is deployed.",[74,604,389],{"id":388},[113,606,608],{"id":607},"_1-shift-left-container-security-scanning","1. Shift-Left Container Security Scanning",[16,610,611],{},"Before any image is pushed to the Azure Container Registry, the GitHub Actions pipeline builds a local test image and scans it using Trivy. If vulnerabilities are found, the deployment is hard-stopped, protecting the production environment.",[124,613,617],{"className":614,"code":615,"language":616,"meta":129,"style":129},"language-yaml shiki shiki-themes github-light github-dark","# .github\u002Fworkflows\u002Fmain.yml\n- name: Run Trivy vulnerability scanner\n  uses: aquasecurity\u002Ftrivy-action@master\n  with:\n    image-ref: \"local\u002Fmy-webapp:test\"\n    format: \"table\"\n    exit-code: \"1\" # Fails the pipeline on detection\n    ignore-unfixed: true\n    vuln-type: \"os,library\"\n    severity: \"CRITICAL,HIGH\"\n","yaml",[57,618,619,624,638,648,656,666,676,689,699,709],{"__ignoreMap":129},[133,620,621],{"class":135,"line":136},[133,622,623],{"class":176},"# .github\u002Fworkflows\u002Fmain.yml\n",[133,625,626,629,633,635],{"class":135,"line":142},[133,627,628],{"class":182},"- ",[133,630,632],{"class":631},"s9eBZ","name",[133,634,192],{"class":182},[133,636,637],{"class":195},"Run Trivy vulnerability scanner\n",[133,639,640,643,645],{"class":135,"line":148},[133,641,642],{"class":631},"  uses",[133,644,192],{"class":182},[133,646,647],{"class":195},"aquasecurity\u002Ftrivy-action@master\n",[133,649,650,653],{"class":135,"line":154},[133,651,652],{"class":631},"  with",[133,654,655],{"class":182},":\n",[133,657,658,661,663],{"class":135,"line":214},[133,659,660],{"class":631},"    image-ref",[133,662,192],{"class":182},[133,664,665],{"class":195},"\"local\u002Fmy-webapp:test\"\n",[133,667,668,671,673],{"class":135,"line":227},[133,669,670],{"class":631},"    format",[133,672,192],{"class":182},[133,674,675],{"class":195},"\"table\"\n",[133,677,678,681,683,686],{"class":135,"line":236},[133,679,680],{"class":631},"    exit-code",[133,682,192],{"class":182},[133,684,685],{"class":195},"\"1\"",[133,687,688],{"class":176}," # Fails the pipeline on detection\n",[133,690,691,694,696],{"class":135,"line":242},[133,692,693],{"class":631},"    ignore-unfixed",[133,695,192],{"class":182},[133,697,698],{"class":188},"true\n",[133,700,701,704,706],{"class":135,"line":255},[133,702,703],{"class":631},"    vuln-type",[133,705,192],{"class":182},[133,707,708],{"class":195},"\"os,library\"\n",[133,710,711,714,716],{"class":135,"line":268},[133,712,713],{"class":631},"    severity",[133,715,192],{"class":182},[133,717,718],{"class":195},"\"CRITICAL,HIGH\"\n",[113,720,722],{"id":721},"_2-zero-touch-bootstrapping-dynamic-telemetry","2. Zero-Touch Bootstrapping & Dynamic Telemetry",[16,724,725,726,728,729,732],{},"I utilized ",[57,727,59],{}," to automate the VM setup upon creation. The script installs Docker, configures Nginx as a reverse proxy, waits for the Managed Identity to initialize, pulls the image, and uses ",[57,730,731],{},"sed"," to dynamically inject the Application Insights connection string into the container at runtime—keeping configuration out of the source code.",[124,734,738],{"className":735,"code":736,"language":737,"meta":129,"style":129},"language-bash shiki shiki-themes github-light github-dark","# terraform\u002Fmodules\u002Fcompute\u002Fcloud-init.yml\necho \"Logging in to Azure with Managed Identity...\"\naz login --identity\naz acr login --name ${acr_name}\n\n# Pull and run the application\ndocker run -d -p 8080:80 --name production-app $IMAGE_TAG\n\n# Inject Telemetry connection string securely at runtime\ndocker exec production-app sed -i 's|CONNECTION_STRING|${app_insights_connection_string}|g' \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml\u002Findex.html\ndocker restart production-app\n","bash",[57,739,740,745,753,765,780,785,790,815,819,824,845],{"__ignoreMap":129},[133,741,742],{"class":135,"line":136},[133,743,744],{"class":176},"# terraform\u002Fmodules\u002Fcompute\u002Fcloud-init.yml\n",[133,746,747,750],{"class":135,"line":142},[133,748,749],{"class":188},"echo",[133,751,752],{"class":195}," \"Logging in to Azure with Managed Identity...\"\n",[133,754,755,759,762],{"class":135,"line":148},[133,756,758],{"class":757},"sScJk","az",[133,760,761],{"class":195}," login",[133,763,764],{"class":188}," --identity\n",[133,766,767,769,772,774,777],{"class":135,"line":154},[133,768,758],{"class":757},[133,770,771],{"class":195}," acr",[133,773,761],{"class":195},[133,775,776],{"class":188}," --name",[133,778,779],{"class":182}," ${acr_name}\n",[133,781,782],{"class":135,"line":214},[133,783,784],{"emptyLinePlaceholder":315},"\n",[133,786,787],{"class":135,"line":227},[133,788,789],{"class":176},"# Pull and run the application\n",[133,791,792,795,798,801,804,807,809,812],{"class":135,"line":236},[133,793,794],{"class":757},"docker",[133,796,797],{"class":195}," run",[133,799,800],{"class":188}," -d",[133,802,803],{"class":188}," -p",[133,805,806],{"class":195}," 8080:80",[133,808,776],{"class":188},[133,810,811],{"class":195}," production-app",[133,813,814],{"class":182}," $IMAGE_TAG\n",[133,816,817],{"class":135,"line":242},[133,818,784],{"emptyLinePlaceholder":315},[133,820,821],{"class":135,"line":255},[133,822,823],{"class":176},"# Inject Telemetry connection string securely at runtime\n",[133,825,826,828,831,833,836,839,842],{"class":135,"line":268},[133,827,794],{"class":757},[133,829,830],{"class":195}," exec",[133,832,811],{"class":195},[133,834,835],{"class":195}," sed",[133,837,838],{"class":188}," -i",[133,840,841],{"class":195}," 's|CONNECTION_STRING|${app_insights_connection_string}|g'",[133,843,844],{"class":195}," \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml\u002Findex.html\n",[133,846,847,849,852],{"class":135,"line":279},[133,848,794],{"class":757},[133,850,851],{"class":195}," restart",[133,853,854],{"class":195}," production-app\n",[296,856,857],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":129,"searchDepth":142,"depth":142,"links":859},[860,861,862],{"id":13,"depth":142,"text":14},{"id":21,"depth":142,"text":22},{"id":71,"depth":142,"text":72,"children":863},[864,865],{"id":76,"depth":148,"text":77},{"id":388,"depth":148,"text":389},"A production-ready CI\u002FCD pipeline and immutable infrastructure setup on Azure, featuring automated security scanning, zero-secret authentication, and dynamic telemetry injection.","https:\u002F\u002Fgithub.com\u002Fszuryuu\u002Fazure-e2e-devops","\u002Fimages\u002Fprojects\u002Fazure.png",{},"\u002Fproject\u002Fazure-e2e-devops",{"title":524,"description":866},"project\u002Fazure-e2e-devops",[874,323,875,324,876],"GitHub Actions","Docker","Bash","kxqFPEJyOQv4AN1fPXFZk2gMi9O4cyIOrq-vEsqje-U",{"id":879,"title":880,"body":881,"description":1404,"duration":1405,"extension":309,"featured":310,"github":1406,"image":1407,"live":313,"meta":1408,"navigation":315,"order":242,"path":1409,"role":313,"seo":1410,"status":318,"stem":1411,"team_size":313,"tech":1412,"type":326,"year":327,"__hash__":1418},"projects\u002Fproject\u002Fbodylog.md","BodyLog: Serverless Fitness Tracker",{"type":8,"value":882,"toc":1396},[883,885,888,890,897,923,925,927,933,939,941,945,956,1154,1158,1161,1393],[11,884,14],{"id":13},[16,886,887],{},"The fitness app market is heavily saturated, yet fundamentally flawed. Most mainstream workout trackers lock users into expensive monthly subscriptions, hide their own workout data behind proprietary walled gardens, and feature bloated, overly complex interfaces that slow down logging mid-workout. Users need a system focused entirely on progressive overload, speed, and absolute data ownership.",[11,889,22],{"id":21},[16,891,892,893,896],{},"I engineered ",[33,894,895],{},"BodyLog",", a high-contrast, brutalist web application that completely bypasses traditional databases in favor of a zero-cost, fully sovereign data architecture.",[27,898,899,905,911,917],{},[30,900,901,904],{},[33,902,903],{},"Zero-DB Architecture:"," Integrated the Google Sheets v4 API via Google Cloud Service Accounts. Every set, rep, and weigh-in is instantly written to a private Google Sheet owned by the user, ensuring data is never trapped in a proprietary ecosystem.",[30,906,907,910],{},[33,908,909],{},"Dynamic Program Modes:"," Engineered a unified interface that seamlessly switches between Gym (barbell\u002Fmachine focus) and Calisthenics (bodyweight progression) tracking states.",[30,912,913,916],{},[33,914,915],{},"AI Coach Integration:"," Implemented a data-export pipeline that compiles weekly workout telemetry into a highly structured prompt, allowing users to leverage LLMs (like Gemini) for personalized progressive overload analysis and nutrition coaching.",[30,918,919,922],{},[33,920,921],{},"Automated Sheet Formatting:"," Wrote low-level API batch updates to automatically style, color-code, and format the raw Google Sheet whenever new weekly modules are generated.",[11,924,72],{"id":71},[74,926,77],{"id":76},[16,928,929,932],{},[33,930,931],{},"Why Google Sheets as a Database?","\nFor a personal tracking application, deploying a managed PostgreSQL instance is architectural overkill and incurs unnecessary cloud costs. Google Sheets natively supports tabular data (which perfectly matches set\u002Frep logs), provides a free visual GUI for manual edits, and costs exactly $0 to run. By communicating securely via server-side Nitro API routes, the Google Service Account credentials remain completely hidden from the client.",[16,934,935,938],{},[33,936,937],{},"Why Nuxt 4 & Serverless?","\nTo achieve an instantaneous, native-app feel on mobile browsers, the application requires heavy hydration and optimized chunking. Nuxt 4 paired with TailwindCSS v4 provides a blazing-fast frontend, while its integrated Nitro engine allows the backend API routes (which execute the Google Sheets payload) to be deployed seamlessly to Vercel as highly efficient Serverless Functions.",[74,940,389],{"id":388},[113,942,944],{"id":943},"_1-automated-sheet-orchestration","1. Automated Sheet Orchestration",[16,946,947,948,951,952,955],{},"Writing raw strings to a spreadsheet is trivial, but keeping the sheet highly readable for the user is complex. I built a backend utility that intercepts the save event and sends a batch of ",[57,949,950],{},"userEnteredFormat"," and ",[57,953,954],{},"addBanding"," requests to the Google Sheets API. This automatically resizes columns, adds borders, and applies alternating row colors dynamically without the user ever opening the sheet application.",[124,957,961],{"className":958,"code":959,"language":960,"meta":129,"style":129},"language-typescript shiki shiki-themes github-light github-dark","\u002F\u002F server\u002Futils\u002Fsheets.ts - Automating spreadsheet UI via API\nrequests.push({\n  addBanding: {\n    bandedRange: {\n      range: {\n        sheetId,\n        startRowIndex: 0,\n        endRowIndex: totalRows,\n        startColumnIndex: 0,\n        endColumnIndex: 10,\n      },\n      rowProperties: {\n        headerColor: { red: 0.13, green: 0.59, blue: 0.6 },\n        firstBandColor: { red: 1, green: 1, blue: 1 },\n        secondBandColor: { red: 0.95, green: 0.98, blue: 0.98 },\n      },\n    },\n  },\n});\nawait sheets.spreadsheets.batchUpdate({\n  spreadsheetId,\n  requestBody: { requests },\n});\n","typescript",[57,962,963,968,979,984,989,994,999,1009,1014,1023,1033,1038,1043,1066,1084,1103,1107,1112,1117,1122,1137,1143,1149],{"__ignoreMap":129},[133,964,965],{"class":135,"line":136},[133,966,967],{"class":176},"\u002F\u002F server\u002Futils\u002Fsheets.ts - Automating spreadsheet UI via API\n",[133,969,970,973,976],{"class":135,"line":142},[133,971,972],{"class":182},"requests.",[133,974,975],{"class":757},"push",[133,977,978],{"class":182},"({\n",[133,980,981],{"class":135,"line":148},[133,982,983],{"class":182},"  addBanding: {\n",[133,985,986],{"class":135,"line":154},[133,987,988],{"class":182},"    bandedRange: {\n",[133,990,991],{"class":135,"line":214},[133,992,993],{"class":182},"      range: {\n",[133,995,996],{"class":135,"line":227},[133,997,998],{"class":182},"        sheetId,\n",[133,1000,1001,1004,1007],{"class":135,"line":236},[133,1002,1003],{"class":182},"        startRowIndex: ",[133,1005,1006],{"class":188},"0",[133,1008,199],{"class":182},[133,1010,1011],{"class":135,"line":242},[133,1012,1013],{"class":182},"        endRowIndex: totalRows,\n",[133,1015,1016,1019,1021],{"class":135,"line":255},[133,1017,1018],{"class":182},"        startColumnIndex: ",[133,1020,1006],{"class":188},[133,1022,199],{"class":182},[133,1024,1025,1028,1031],{"class":135,"line":268},[133,1026,1027],{"class":182},"        endColumnIndex: ",[133,1029,1030],{"class":188},"10",[133,1032,199],{"class":182},[133,1034,1035],{"class":135,"line":279},[133,1036,1037],{"class":182},"      },\n",[133,1039,1040],{"class":135,"line":285},[133,1041,1042],{"class":182},"      rowProperties: {\n",[133,1044,1045,1048,1051,1054,1057,1060,1063],{"class":135,"line":291},[133,1046,1047],{"class":182},"        headerColor: { red: ",[133,1049,1050],{"class":188},"0.13",[133,1052,1053],{"class":182},", green: ",[133,1055,1056],{"class":188},"0.59",[133,1058,1059],{"class":182},", blue: ",[133,1061,1062],{"class":188},"0.6",[133,1064,1065],{"class":182}," },\n",[133,1067,1068,1071,1074,1076,1078,1080,1082],{"class":135,"line":469},[133,1069,1070],{"class":182},"        firstBandColor: { red: ",[133,1072,1073],{"class":188},"1",[133,1075,1053],{"class":182},[133,1077,1073],{"class":188},[133,1079,1059],{"class":182},[133,1081,1073],{"class":188},[133,1083,1065],{"class":182},[133,1085,1086,1089,1092,1094,1097,1099,1101],{"class":135,"line":475},[133,1087,1088],{"class":182},"        secondBandColor: { red: ",[133,1090,1091],{"class":188},"0.95",[133,1093,1053],{"class":182},[133,1095,1096],{"class":188},"0.98",[133,1098,1059],{"class":182},[133,1100,1096],{"class":188},[133,1102,1065],{"class":182},[133,1104,1105],{"class":135,"line":481},[133,1106,1037],{"class":182},[133,1108,1109],{"class":135,"line":487},[133,1110,1111],{"class":182},"    },\n",[133,1113,1114],{"class":135,"line":493},[133,1115,1116],{"class":182},"  },\n",[133,1118,1119],{"class":135,"line":498},[133,1120,1121],{"class":182},"});\n",[133,1123,1125,1129,1132,1135],{"class":135,"line":1124},20,[133,1126,1128],{"class":1127},"szBVR","await",[133,1130,1131],{"class":182}," sheets.spreadsheets.",[133,1133,1134],{"class":757},"batchUpdate",[133,1136,978],{"class":182},[133,1138,1140],{"class":135,"line":1139},21,[133,1141,1142],{"class":182},"  spreadsheetId,\n",[133,1144,1146],{"class":135,"line":1145},22,[133,1147,1148],{"class":182},"  requestBody: { requests },\n",[133,1150,1152],{"class":135,"line":1151},23,[133,1153,1121],{"class":182},[113,1155,1157],{"id":1156},"_2-cross-program-mode-switching","2. Cross-Program Mode Switching",[16,1159,1160],{},"To handle different fitness regimens, I utilized Vue 3 Composables to manage global state dynamically. The UI and the underlying data schema adapt instantly depending on whether the user is tracking heavy barbell lifts or bodyweight static holds.",[124,1162,1164],{"className":958,"code":1163,"language":960,"meta":129,"style":129},"\u002F\u002F app\u002Fcomposables\u002FuseMode.ts - Global state management for workout modalities\nexport const useMode = () => {\n  const currentMode = useState\u003C\"gym\" | \"calist\" | null>(\n    \"workout_mode\",\n    () => null,\n  );\n\n  const setMode = (mode: \"gym\" | \"calist\") => {\n    currentMode.value = mode;\n    localStorage.setItem(\"workout_mode\", mode);\n  };\n\n  return {\n    currentMode,\n    isGym: computed(() => currentMode.value === \"gym\"),\n    isCalist: computed(() => currentMode.value === \"calist\"),\n    setMode,\n  };\n};\n",[57,1165,1166,1171,1194,1227,1234,1245,1250,1254,1287,1298,1315,1320,1324,1331,1336,1360,1379,1384,1388],{"__ignoreMap":129},[133,1167,1168],{"class":135,"line":136},[133,1169,1170],{"class":176},"\u002F\u002F app\u002Fcomposables\u002FuseMode.ts - Global state management for workout modalities\n",[133,1172,1173,1176,1179,1182,1185,1188,1191],{"class":135,"line":142},[133,1174,1175],{"class":1127},"export",[133,1177,1178],{"class":1127}," const",[133,1180,1181],{"class":757}," useMode",[133,1183,1184],{"class":1127}," =",[133,1186,1187],{"class":182}," () ",[133,1189,1190],{"class":1127},"=>",[133,1192,1193],{"class":182}," {\n",[133,1195,1196,1199,1202,1204,1207,1210,1213,1216,1219,1221,1224],{"class":135,"line":148},[133,1197,1198],{"class":1127},"  const",[133,1200,1201],{"class":188}," currentMode",[133,1203,1184],{"class":1127},[133,1205,1206],{"class":757}," useState",[133,1208,1209],{"class":182},"\u003C",[133,1211,1212],{"class":195},"\"gym\"",[133,1214,1215],{"class":1127}," |",[133,1217,1218],{"class":195}," \"calist\"",[133,1220,1215],{"class":1127},[133,1222,1223],{"class":188}," null",[133,1225,1226],{"class":182},">(\n",[133,1228,1229,1232],{"class":135,"line":154},[133,1230,1231],{"class":195},"    \"workout_mode\"",[133,1233,199],{"class":182},[133,1235,1236,1239,1241,1243],{"class":135,"line":214},[133,1237,1238],{"class":182},"    () ",[133,1240,1190],{"class":1127},[133,1242,1223],{"class":188},[133,1244,199],{"class":182},[133,1246,1247],{"class":135,"line":227},[133,1248,1249],{"class":182},"  );\n",[133,1251,1252],{"class":135,"line":236},[133,1253,784],{"emptyLinePlaceholder":315},[133,1255,1256,1258,1261,1263,1266,1270,1273,1276,1278,1280,1283,1285],{"class":135,"line":242},[133,1257,1198],{"class":1127},[133,1259,1260],{"class":757}," setMode",[133,1262,1184],{"class":1127},[133,1264,1265],{"class":182}," (",[133,1267,1269],{"class":1268},"s4XuR","mode",[133,1271,1272],{"class":1127},":",[133,1274,1275],{"class":195}," \"gym\"",[133,1277,1215],{"class":1127},[133,1279,1218],{"class":195},[133,1281,1282],{"class":182},") ",[133,1284,1190],{"class":1127},[133,1286,1193],{"class":182},[133,1288,1289,1292,1295],{"class":135,"line":255},[133,1290,1291],{"class":182},"    currentMode.value ",[133,1293,1294],{"class":1127},"=",[133,1296,1297],{"class":182}," mode;\n",[133,1299,1300,1303,1306,1309,1312],{"class":135,"line":268},[133,1301,1302],{"class":182},"    localStorage.",[133,1304,1305],{"class":757},"setItem",[133,1307,1308],{"class":182},"(",[133,1310,1311],{"class":195},"\"workout_mode\"",[133,1313,1314],{"class":182},", mode);\n",[133,1316,1317],{"class":135,"line":279},[133,1318,1319],{"class":182},"  };\n",[133,1321,1322],{"class":135,"line":285},[133,1323,784],{"emptyLinePlaceholder":315},[133,1325,1326,1329],{"class":135,"line":291},[133,1327,1328],{"class":1127},"  return",[133,1330,1193],{"class":182},[133,1332,1333],{"class":135,"line":469},[133,1334,1335],{"class":182},"    currentMode,\n",[133,1337,1338,1341,1344,1347,1349,1352,1355,1357],{"class":135,"line":475},[133,1339,1340],{"class":182},"    isGym: ",[133,1342,1343],{"class":757},"computed",[133,1345,1346],{"class":182},"(() ",[133,1348,1190],{"class":1127},[133,1350,1351],{"class":182}," currentMode.value ",[133,1353,1354],{"class":1127},"===",[133,1356,1275],{"class":195},[133,1358,1359],{"class":182},"),\n",[133,1361,1362,1365,1367,1369,1371,1373,1375,1377],{"class":135,"line":481},[133,1363,1364],{"class":182},"    isCalist: ",[133,1366,1343],{"class":757},[133,1368,1346],{"class":182},[133,1370,1190],{"class":1127},[133,1372,1351],{"class":182},[133,1374,1354],{"class":1127},[133,1376,1218],{"class":195},[133,1378,1359],{"class":182},[133,1380,1381],{"class":135,"line":487},[133,1382,1383],{"class":182},"    setMode,\n",[133,1385,1386],{"class":135,"line":493},[133,1387,1319],{"class":182},[133,1389,1390],{"class":135,"line":498},[133,1391,1392],{"class":182},"};\n",[296,1394,1395],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":129,"searchDepth":142,"depth":142,"links":1397},[1398,1399,1400],{"id":13,"depth":142,"text":14},{"id":21,"depth":142,"text":22},{"id":71,"depth":142,"text":72,"children":1401},[1402,1403],{"id":76,"depth":148,"text":77},{"id":388,"depth":148,"text":389},"A brutalist, serverless progressive overload tracker built with Nuxt 4, utilizing Google Sheets API as a zero-cost headless database for 100% data ownership.","1 week","https:\u002F\u002Fgithub.com\u002Fszuryuu\u002Fbodylog","\u002Fimages\u002Fprojects\u002Fnuxt.png",{},"\u002Fproject\u002Fbodylog",{"title":880,"description":1404},"project\u002Fbodylog",[1413,1414,1415,1416,1417],"Nuxt 4","Vue 3","Tailwind CSS v4","Google Sheets API","Serverless","ey7cQ1NieXq0ivJFB0SnsjORH_c3LXtgwNNPeoqM8Iw",{"id":1420,"title":1421,"body":1422,"description":1694,"duration":1695,"extension":309,"featured":315,"github":1696,"image":312,"live":313,"meta":1697,"navigation":315,"order":136,"path":1698,"role":313,"seo":1699,"status":318,"stem":1700,"team_size":313,"tech":1701,"type":326,"year":327,"__hash__":1707},"projects\u002Fproject\u002Fcloudops.md","CloudOps: Azure GitOps Platform",{"type":8,"value":1423,"toc":1686},[1424,1426,1429,1431,1434,1464,1466,1468,1486,1495,1497,1501,1512,1599,1603,1610,1683],[11,1425,14],{"id":13},[16,1427,1428],{},"In traditional deployments, infrastructure code, backend logic, and frontend assets are heavily entangled. A minor frontend change might trigger a massive infrastructure validation pipeline, slowing down delivery. Furthermore, \"Day-2 Operations\"—such as scaling, monitoring, and managing registry credentials—are often handled manually. Storing static passwords for container registries inside Kubernetes clusters introduces critical security vulnerabilities and maintenance nightmares when credentials rotate.",[11,1430,22],{"id":21},[16,1432,1433],{},"I engineered a comprehensive GitOps monorepo that strictly decouples the application lifecycle from the infrastructure lifecycle, fully deployed on Microsoft Azure.",[27,1435,1436,1442,1448,1454],{},[30,1437,1438,1441],{},[33,1439,1440],{},"Path-Based CI\u002FCD Pipelines:"," Configured GitHub Actions to dynamically trigger independent workflows (Frontend, Backend, Infrastructure) based exclusively on which directories were modified in the commit.",[30,1443,1444,1447],{},[33,1445,1446],{},"Zero-Credential Infrastructure:"," Provisioned Azure Kubernetes Service (AKS) and Azure Container Registry (ACR) via Terraform, utilizing SystemAssigned Managed Identities for seamless, passwordless image pulling.",[30,1449,1450,1453],{},[33,1451,1452],{},"Internal DNS Routing:"," Designed the Kubernetes topology to eliminate unnecessary public endpoints. The Go (Gin) backend and PostgreSQL database run purely on internal ClusterIPs, protected behind an Nginx reverse proxy.",[30,1455,1456,1459,1460,1463],{},[33,1457,1458],{},"Proactive Observability:"," Deployed the ",[57,1461,1462],{},"kube-prometheus-stack"," via Helm, configuring custom Prometheus alerting rules and Grafana dashboards to proactively monitor node resources and pod health.",[11,1465,72],{"id":71},[74,1467,77],{"id":76},[16,1469,1470,1473,1474,1477,1478,1481,1482,1485],{},[33,1471,1472],{},"Why Path-Based CI\u002FCD in a Monorepo?","\nA monorepo provides a single source of truth, but running ",[57,1475,1476],{},"terraform plan"," every time a developer updates a Go API endpoint wastes compute minutes and delays deployments. By utilizing GitHub Actions ",[57,1479,1480],{},"paths"," filtering, pushing code to ",[57,1483,1484],{},"applications\u002Fbackend\u002F**"," strictly triggers the Go build and AKS deployment process, completely isolating it from the frontend and infrastructure pipelines.",[16,1487,1488,1491,1492,1494],{},[33,1489,1490],{},"Why SystemAssigned Managed Identities?","\nInstead of manually creating Kubernetes Secrets to store ACR passwords (which can be leaked or expire), I configured Terraform to grant the AKS cluster's managed identity the ",[57,1493,587],{}," role. This allows the cluster to authenticate to the container registry natively via Azure's IAM layer, achieving a true zero-secret deployment state.",[74,1496,389],{"id":388},[113,1498,1500],{"id":1499},"_1-secure-internal-routing-nginx-reverse-proxy","1. Secure Internal Routing (Nginx Reverse Proxy)",[16,1502,1503,1504,1507,1508,1511],{},"To minimize the attack surface and reduce cloud load balancer costs, only the frontend Nginx pod is exposed externally. I configured Nginx to act as an API gateway, dynamically routing ",[57,1505,1506],{},"\u002Fapi\u002F*"," traffic to the backend via Kubernetes' internal DNS (",[57,1509,1510],{},"backend-service","), ensuring the Go API remains completely hidden from the public internet.",[124,1513,1517],{"className":1514,"code":1515,"language":1516,"meta":129,"style":129},"language-nginx shiki shiki-themes github-light github-dark","# applications\u002Ffrontend\u002Fnginx.k8s.conf\nserver {\n    listen 80;\n\n    # Serve static frontend files\n    location \u002F {\n        root \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml;\n        index index.html;\n    }\n\n    # Proxy API traffic securely through Kubernetes internal DNS\n    location \u002Fapi\u002F {\n        proxy_pass http:\u002F\u002Fbackend-service:5000\u002F;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n    }\n}\n","nginx",[57,1518,1519,1524,1529,1534,1538,1543,1548,1553,1558,1562,1566,1571,1576,1581,1586,1591,1595],{"__ignoreMap":129},[133,1520,1521],{"class":135,"line":136},[133,1522,1523],{},"# applications\u002Ffrontend\u002Fnginx.k8s.conf\n",[133,1525,1526],{"class":135,"line":142},[133,1527,1528],{},"server {\n",[133,1530,1531],{"class":135,"line":148},[133,1532,1533],{},"    listen 80;\n",[133,1535,1536],{"class":135,"line":154},[133,1537,784],{"emptyLinePlaceholder":315},[133,1539,1540],{"class":135,"line":214},[133,1541,1542],{},"    # Serve static frontend files\n",[133,1544,1545],{"class":135,"line":227},[133,1546,1547],{},"    location \u002F {\n",[133,1549,1550],{"class":135,"line":236},[133,1551,1552],{},"        root \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml;\n",[133,1554,1555],{"class":135,"line":242},[133,1556,1557],{},"        index index.html;\n",[133,1559,1560],{"class":135,"line":255},[133,1561,282],{},[133,1563,1564],{"class":135,"line":268},[133,1565,784],{"emptyLinePlaceholder":315},[133,1567,1568],{"class":135,"line":279},[133,1569,1570],{},"    # Proxy API traffic securely through Kubernetes internal DNS\n",[133,1572,1573],{"class":135,"line":285},[133,1574,1575],{},"    location \u002Fapi\u002F {\n",[133,1577,1578],{"class":135,"line":291},[133,1579,1580],{},"        proxy_pass http:\u002F\u002Fbackend-service:5000\u002F;\n",[133,1582,1583],{"class":135,"line":469},[133,1584,1585],{},"        proxy_set_header Host $host;\n",[133,1587,1588],{"class":135,"line":475},[133,1589,1590],{},"        proxy_set_header X-Real-IP $remote_addr;\n",[133,1592,1593],{"class":135,"line":481},[133,1594,282],{},[133,1596,1597],{"class":135,"line":487},[133,1598,294],{},[113,1600,1602],{"id":1601},"_2-automated-day-2-operational-scripts","2. Automated Day-2 Operational Scripts",[16,1604,1605,1606,1609],{},"To ensure rapid incident response, I wrote abstraction scripts for complex disaster recovery scenarios. Instead of operators memorizing complex ",[57,1607,1608],{},"kubectl"," rollout commands during high-stress outages, they can execute a single automated script to instantly revert deployments to their last known stable state.",[124,1611,1613],{"className":735,"code":1612,"language":737,"meta":129,"style":129},"# scripts\u002Frollback.sh\necho \"Initiating rollback for $COMPONENT...\"\nkubectl rollout undo deployment\u002F$COMPONENT -n $NAMESPACE\n\necho \"Waiting for rollback to complete...\"\nkubectl rollout status deployment\u002F$COMPONENT -n $NAMESPACE\n",[57,1614,1615,1620,1633,1655,1659,1666],{"__ignoreMap":129},[133,1616,1617],{"class":135,"line":136},[133,1618,1619],{"class":176},"# scripts\u002Frollback.sh\n",[133,1621,1622,1624,1627,1630],{"class":135,"line":142},[133,1623,749],{"class":188},[133,1625,1626],{"class":195}," \"Initiating rollback for ",[133,1628,1629],{"class":182},"$COMPONENT",[133,1631,1632],{"class":195},"...\"\n",[133,1634,1635,1637,1640,1643,1646,1649,1652],{"class":135,"line":148},[133,1636,1608],{"class":757},[133,1638,1639],{"class":195}," rollout",[133,1641,1642],{"class":195}," undo",[133,1644,1645],{"class":195}," deployment\u002F",[133,1647,1648],{"class":182},"$COMPONENT ",[133,1650,1651],{"class":188},"-n",[133,1653,1654],{"class":182}," $NAMESPACE\n",[133,1656,1657],{"class":135,"line":154},[133,1658,784],{"emptyLinePlaceholder":315},[133,1660,1661,1663],{"class":135,"line":214},[133,1662,749],{"class":188},[133,1664,1665],{"class":195}," \"Waiting for rollback to complete...\"\n",[133,1667,1668,1670,1672,1675,1677,1679,1681],{"class":135,"line":227},[133,1669,1608],{"class":757},[133,1671,1639],{"class":195},[133,1673,1674],{"class":195}," status",[133,1676,1645],{"class":195},[133,1678,1648],{"class":182},[133,1680,1651],{"class":188},[133,1682,1654],{"class":182},[296,1684,1685],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":129,"searchDepth":142,"depth":142,"links":1687},[1688,1689,1690],{"id":13,"depth":142,"text":14},{"id":21,"depth":142,"text":22},{"id":71,"depth":142,"text":72,"children":1691},[1692,1693],{"id":76,"depth":148,"text":77},{"id":388,"depth":148,"text":389},"A production-grade GitOps monorepo deploying a Go\u002FNginx microservices architecture on a Terraform-provisioned Azure Kubernetes Service (AKS) cluster, featuring decoupled CI\u002FCD and zero-credential security.","4 weeks","https:\u002F\u002Fgithub.com\u002Fszuryuu\u002Fcloudops",{},"\u002Fproject\u002Fcloudops",{"title":1421,"description":1694},"project\u002Fcloudops",[324,1702,323,874,1703,1704,1705,1706],"AKS","Go (Gin)","Nginx","PostgreSQL","Prometheus","EkFyCLGiN8uyjJi8YcVn-m0h2mejVpS5tU18FkPj9lU",{"id":1709,"title":1710,"body":1711,"description":2157,"duration":2158,"extension":309,"featured":310,"github":2159,"image":2160,"live":313,"meta":2161,"navigation":315,"order":236,"path":2162,"role":313,"seo":2163,"status":318,"stem":2164,"team_size":313,"tech":2165,"type":2167,"year":2168,"__hash__":2169},"projects\u002Fproject\u002Fdokku-nginx-path.md","Dokku Nginx Path",{"type":8,"value":1712,"toc":2149},[1713,1715,1722,1742,1744,1758,1792,1794,1796,1801,1826,1831,1858,1863,1879,1881,1885,1888,2051,2055,2058,2146],[11,1714,14],{"id":13},[16,1716,1717,1718,1721],{},"Standard Dokku deployments force a subdomain-based architecture (e.g., ",[57,1719,1720],{},"app.domain.com","), which creates significant friction for specific architectural patterns. Developers faced challenges with:",[27,1723,1724,1730,1736],{},[30,1725,1726,1729],{},[33,1727,1728],{},"SEO Fragmentation:"," Splitting traffic across subdomains dilutes domain authority compared to subdirectories.",[30,1731,1732,1735],{},[33,1733,1734],{},"SSL Complexity:"," Requiring wildcard certificates or managing separate certs for every microservice.",[30,1737,1738,1741],{},[33,1739,1740],{},"Unified Gateway Needs:"," Difficulty in presenting multiple microservices (e.g., frontend, api, admin) as a single monolithic endpoint to the client without an external reverse proxy layer.",[11,1743,22],{"id":21},[16,1745,1746,1747,1750,1751,97,1754,1757],{},"I engineered a custom Dokku plugin that intercepts the Nginx configuration lifecycle to enable ",[33,1748,1749],{},"Path-Based Routing"," (e.g., ",[57,1752,1753],{},"domain.com\u002Fapp1",[57,1755,1756],{},"domain.com\u002Fapi",").",[27,1759,1760,1766,1780,1786],{},[30,1761,1762,1765],{},[33,1763,1764],{},"Unified Domain Root:"," Serves multiple isolated Dokku apps under one domain name.",[30,1767,1768,1771,1772,1775,1776,1779],{},[33,1769,1770],{},"\"Default App\" Authority:"," Implemented a master-slave logic where one \"Default App\" controls the root configuration (",[57,1773,1774],{},"\u002F","), while secondary apps inject their specific ",[57,1777,1778],{},"location"," blocks.",[30,1781,1782,1785],{},[33,1783,1784],{},"Automated Config Merging:"," Hooks into Dokku's deploy cycle to automatically rebuild the Nginx map whenever a linked application is deployed or updated.",[30,1787,1788,1791],{},[33,1789,1790],{},"Hybrid Architecture:"," Utilizes Bash for Dokku lifecycle hooks and Go for high-performance property parsing.",[11,1793,72],{"id":71},[74,1795,77],{"id":76},[16,1797,1798],{},[33,1799,1800],{},"Why Hybrid Bash & Go?",[27,1802,1803,1816],{},[30,1804,1805,1808,1809,97,1812,1815],{},[33,1806,1807],{},"Bash:"," Essential for deep integration with Dokku's plugin trigger system (",[57,1810,1811],{},"pre-deploy",[57,1813,1814],{},"proxy-build-config",") which relies on shell scripts.",[30,1817,1818,1821,1822,1825],{},[33,1819,1820],{},"Go:"," Replaced complex shell string manipulation with Go for the property management system (",[57,1823,1824],{},"nginx-property","). This ensures type safety and faster execution when parsing complex configuration maps for hundreds of potential vhosts.",[16,1827,1828],{},[33,1829,1830],{},"Why Sigil over standard Nginx includes?",[27,1832,1833,1840],{},[30,1834,1835,1836,1839],{},"Standard Nginx ",[57,1837,1838],{},"include"," directives were insufficient for dynamic upstream port mapping.",[30,1841,1842,1843,1846,1847,951,1850,1853,1854,1857],{},"I leveraged ",[33,1844,1845],{},"Glider Labs' Sigil"," templating engine to dynamically loop through ",[57,1848,1849],{},"PROXY_PORT_MAP",[57,1851,1852],{},"DOKKU_APP_LISTENERS"," at runtime, generating a valid, cohesive ",[57,1855,1856],{},"nginx.conf"," that routes traffic based on internal Docker IP changes.",[16,1859,1860],{},[33,1861,1862],{},"Handling the \"Default App\" Concept",[27,1864,1865],{},[30,1866,1867,1868,1871,1872,1875,1876,1878],{},"To prevent routing collisions, I architected a priority system. The plugin identifies a ",[57,1869,1870],{},"default-app"," via a custom property. This app generates the main server block, while all other apps identified by ",[57,1873,1874],{},"app-path"," are rendered as upstream definitions and included via ",[57,1877,1838],{}," directives, preventing \"duplicate location\" errors.",[74,1880,389],{"id":388},[113,1882,1884],{"id":1883},"_1-dynamic-location-block-generation-sigil-template","1. Dynamic Location Block Generation (Sigil Template)",[16,1886,1887],{},"This snippet demonstrates how the plugin iterates through listeners to construct Nginx location blocks dynamically, handling SSL redirects and header forwarding automatically.",[124,1889,1891],{"className":735,"code":1890,"language":737,"meta":129,"style":129},"# nginx\n{{ range $port_map := .PROXY_PORT_MAP | split \" \" }}\n  # ... (upstream logic)\n\n  # Current app routing\n  {{ $current_app_path := $.APP_PATH | default $.APP }}\n  location \u002F{{ $current_app_path }}\u002F {\n    {{ if eq $.STRIP_PATH \"true\" }}\n      rewrite \"^\u002F{{ $current_app_path }}\u002F(.*)\" \"\u002F$1\" break;\n    {{ end }}\n    proxy_pass http:\u002F\u002F{{ $.APP }}-{{ $upstream_port }}\u002F;\n\n    # Automated Header Injection for Context\n    proxy_set_header X-Forwarded-Prefix \u002F{{ $current_app_path }}\u002F;\n    proxy_set_header X-Script-Name \u002F{{ $current_app_path }};\n  }\n{{ end }}\n",[57,1892,1893,1898,1903,1908,1912,1917,1922,1938,1943,1972,1977,2001,2005,2010,2026,2042,2046],{"__ignoreMap":129},[133,1894,1895],{"class":135,"line":136},[133,1896,1897],{"class":176},"# nginx\n",[133,1899,1900],{"class":135,"line":142},[133,1901,1902],{"class":182},"{{ range $port_map := .PROXY_PORT_MAP | split \" \" }}\n",[133,1904,1905],{"class":135,"line":148},[133,1906,1907],{"class":176},"  # ... (upstream logic)\n",[133,1909,1910],{"class":135,"line":154},[133,1911,784],{"emptyLinePlaceholder":315},[133,1913,1914],{"class":135,"line":214},[133,1915,1916],{"class":176},"  # Current app routing\n",[133,1918,1919],{"class":135,"line":227},[133,1920,1921],{"class":182},"  {{ $current_app_path := $.APP_PATH | default $.APP }}\n",[133,1923,1924,1927,1930,1933,1936],{"class":135,"line":236},[133,1925,1926],{"class":757},"  location",[133,1928,1929],{"class":195}," \u002F{{",[133,1931,1932],{"class":182}," $current_app_path ",[133,1934,1935],{"class":195},"}}\u002F",[133,1937,1193],{"class":195},[133,1939,1940],{"class":135,"line":242},[133,1941,1942],{"class":182},"    {{ if eq $.STRIP_PATH \"true\" }}\n",[133,1944,1945,1948,1951,1954,1957,1960,1963,1966,1969],{"class":135,"line":255},[133,1946,1947],{"class":757},"      rewrite",[133,1949,1950],{"class":195}," \"^\u002F{{ ",[133,1952,1953],{"class":182},"$current_app_path",[133,1955,1956],{"class":195}," }}\u002F(.*)\"",[133,1958,1959],{"class":195}," \"\u002F",[133,1961,1962],{"class":188},"$1",[133,1964,1965],{"class":195},"\"",[133,1967,1968],{"class":195}," break",[133,1970,1971],{"class":182},";\n",[133,1973,1974],{"class":135,"line":268},[133,1975,1976],{"class":182},"    {{ end }}\n",[133,1978,1979,1982,1985,1988,1991,1994,1997,1999],{"class":135,"line":279},[133,1980,1981],{"class":757},"    proxy_pass",[133,1983,1984],{"class":195}," http:\u002F\u002F{{",[133,1986,1987],{"class":182}," $",[133,1989,1990],{"class":195},".APP",[133,1992,1993],{"class":195}," }}-{{",[133,1995,1996],{"class":182}," $upstream_port ",[133,1998,1935],{"class":195},[133,2000,1971],{"class":182},[133,2002,2003],{"class":135,"line":285},[133,2004,784],{"emptyLinePlaceholder":315},[133,2006,2007],{"class":135,"line":291},[133,2008,2009],{"class":176},"    # Automated Header Injection for Context\n",[133,2011,2012,2015,2018,2020,2022,2024],{"class":135,"line":469},[133,2013,2014],{"class":757},"    proxy_set_header",[133,2016,2017],{"class":195}," X-Forwarded-Prefix",[133,2019,1929],{"class":195},[133,2021,1932],{"class":182},[133,2023,1935],{"class":195},[133,2025,1971],{"class":182},[133,2027,2028,2030,2033,2035,2037,2040],{"class":135,"line":475},[133,2029,2014],{"class":757},[133,2031,2032],{"class":195}," X-Script-Name",[133,2034,1929],{"class":195},[133,2036,1932],{"class":182},[133,2038,2039],{"class":195},"}}",[133,2041,1971],{"class":182},[133,2043,2044],{"class":135,"line":481},[133,2045,461],{"class":182},[133,2047,2048],{"class":135,"line":487},[133,2049,2050],{"class":182},"{{ end }}\n",[113,2052,2054],{"id":2053},"_2-lifecycle-hook-interception","2. Lifecycle Hook Interception",[16,2056,2057],{},"The plugin doesn't just sit idle; it actively listens to deployment events. If a secondary app is deployed, the plugin forces a rebuild of the primary proxy configuration to ensure the new path is live immediately.",[124,2059,2061],{"className":735,"code":2060,"language":737,"meta":129,"style":129},"# From: noes\u002Fproxy-build-config\ntrigger-nginx-path-vhosts-proxy-build-config() {\n  declare APP=\"$1\"\n  # ... checks ...\n\n  # Trigger network config build ensuring IP tables are updated\n  plugn trigger network-build-config \"$APP\"\n\n  # Rebuild the complex Nginx config map\n  nginx_build_config \"$APP\"\n}\n",[57,2062,2063,2068,2076,2089,2094,2098,2103,2122,2126,2131,2142],{"__ignoreMap":129},[133,2064,2065],{"class":135,"line":136},[133,2066,2067],{"class":176},"# From: noes\u002Fproxy-build-config\n",[133,2069,2070,2073],{"class":135,"line":142},[133,2071,2072],{"class":757},"trigger-nginx-path-vhosts-proxy-build-config",[133,2074,2075],{"class":182},"() {\n",[133,2077,2078,2081,2084,2086],{"class":135,"line":148},[133,2079,2080],{"class":188},"  declare",[133,2082,2083],{"class":195}," APP=\"",[133,2085,1962],{"class":188},[133,2087,2088],{"class":195},"\"\n",[133,2090,2091],{"class":135,"line":154},[133,2092,2093],{"class":176},"  # ... checks ...\n",[133,2095,2096],{"class":135,"line":214},[133,2097,784],{"emptyLinePlaceholder":315},[133,2099,2100],{"class":135,"line":227},[133,2101,2102],{"class":176},"  # Trigger network config build ensuring IP tables are updated\n",[133,2104,2105,2108,2111,2114,2117,2120],{"class":135,"line":236},[133,2106,2107],{"class":757},"  plugn",[133,2109,2110],{"class":195}," trigger",[133,2112,2113],{"class":195}," network-build-config",[133,2115,2116],{"class":195}," \"",[133,2118,2119],{"class":182},"$APP",[133,2121,2088],{"class":195},[133,2123,2124],{"class":135,"line":242},[133,2125,784],{"emptyLinePlaceholder":315},[133,2127,2128],{"class":135,"line":255},[133,2129,2130],{"class":176},"  # Rebuild the complex Nginx config map\n",[133,2132,2133,2136,2138,2140],{"class":135,"line":268},[133,2134,2135],{"class":757},"  nginx_build_config",[133,2137,2116],{"class":195},[133,2139,2119],{"class":182},[133,2141,2088],{"class":195},[133,2143,2144],{"class":135,"line":279},[133,2145,294],{"class":182},[296,2147,2148],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":129,"searchDepth":142,"depth":142,"links":2150},[2151,2152,2153],{"id":13,"depth":142,"text":14},{"id":21,"depth":142,"text":22},{"id":71,"depth":142,"text":72,"children":2154},[2155,2156],{"id":76,"depth":148,"text":77},{"id":388,"depth":148,"text":389},"A Dokku plugin enabling path-based routing for multiple applications under a single domain, featuring automated Nginx configuration merging and unified SSL management.","1 months","https:\u002F\u002Fgithub.com\u002Fszuryuu\u002Fnginx-path-vhost","\u002Fimages\u002Fprojects\u002Fdokku.png",{},"\u002Fproject\u002Fdokku-nginx-path",{"title":1710,"description":2157},"project\u002Fdokku-nginx-path",[876,321,1704,2166],"Dokku","Intern Project","2025","O8Vk3PTV0iumMcyJIG2lFwWmsAYVYs4lZzR-jfCoQoc",{"id":2171,"title":2172,"body":2173,"description":2583,"duration":513,"extension":309,"featured":310,"github":2584,"image":2585,"live":313,"meta":2586,"navigation":315,"order":291,"path":2587,"role":313,"seo":2588,"status":318,"stem":2589,"team_size":313,"tech":2590,"type":326,"year":2168,"__hash__":2592},"projects\u002Fproject\u002Fgha-follow-unfollow.md","GHA Follow Unfollow",{"type":8,"value":2174,"toc":2575},[2175,2177,2180,2182,2189,2215,2217,2219,2224,2246,2251,2269,2271,2275,2278,2399,2403,2406,2572],[11,2176,14],{"id":13},[16,2178,2179],{},"Managing GitHub social connections manually is tedious. Many users employ \"follow-for-follow\" strategies only to quietly unfollow later, skewing follower ratios. Manually cross-referencing thousands of followers to find who isn't following back—and reciprocating valid follows—wastes valuable time that could be spent coding.",[11,2181,22],{"id":21},[16,2183,2184,2185,2188],{},"I built ",[33,2186,2187],{},"GitHub Follow\u002FUnfollow Bot",", a self-contained automation tool that runs entirely within the GitHub ecosystem, requiring zero external infrastructure or VPS costs.",[27,2190,2191,2197,2203,2209],{},[30,2192,2193,2196],{},[33,2194,2195],{},"Serverless Architecture:"," Leverages GitHub Actions Scheduled Workflows (CRON) to run maintenance tasks automatically twice a day.",[30,2198,2199,2202],{},[33,2200,2201],{},"Smart Synchronization:"," Automatically detects and follows back new supporters while cleaning up non-mutual connections.",[30,2204,2205,2208],{},[33,2206,2207],{},"Anti-Abuse Mechanisms:"," Implements random jitter and strict operational limits to mimic human behavior and comply with GitHub's API rate limits.",[30,2210,2211,2214],{},[33,2212,2213],{},"Audit Logging:"," Automatically commits execution logs back to the repository, creating a permanent history of actions without a database.",[11,2216,72],{"id":71},[74,2218,77],{"id":76},[16,2220,2221],{},[33,2222,2223],{},"Why Go over Python\u002FJS?",[27,2225,2226,2236],{},[30,2227,2228,2231,2232,2235],{},[33,2229,2230],{},"Type Safety:"," Leveraging the ",[57,2233,2234],{},"google\u002Fgo-github"," library ensures strict typing for API responses, preventing runtime errors when handling large follower lists.",[30,2237,2238,2241,2242,2245],{},[33,2239,2240],{},"Single Binary Simplicity:"," Although currently run via ",[57,2243,2244],{},"go run",", the project is structured to be easily compiled into a single binary artifact for portability across different CI runners.",[16,2247,2248],{},[33,2249,2250],{},"Why GitHub Actions?",[27,2252,2253,2259],{},[30,2254,2255,2258],{},[33,2256,2257],{},"Cost Efficiency:"," Replaces the need for a 24\u002F7 server. The script only consumes compute minutes when it runs (approx. 2-5 minutes per run).",[30,2260,2261,2264,2265,2268],{},[33,2262,2263],{},"Security:"," Utilizing GitHub Secrets (",[57,2266,2267],{},"MY_PAT",") ensures high-privilege Personal Access Tokens are injected securely at runtime and never exposed in the codebase.",[74,2270,389],{"id":388},[113,2272,2274],{"id":2273},"_1-o1-lookup-strategy-for-difference-calculation","1. O(1) Lookup Strategy for Difference Calculation",[16,2276,2277],{},"To efficiently handle users with thousands of followers, I utilized Go Maps to implement set difference logic. This reduces the complexity of finding non-mutual followers from O(n²) (nested loops) to O(n).",[124,2279,2281],{"className":735,"code":2280,"language":737,"meta":129,"style":129},"# go\n# Create a map for O(1) lookups\nfollowingMap := make(map[string]bool)\nfor _, f := range following {\n    followingMap[f.GetLogin()] = true\n}\n\n# Identify followers who aren't followed back (A - B)\nvar needFollow []string\nfor _, f := range followers {\n    if !followingMap[f.GetLogin()] {\n        needFollow = append(needFollow, f.GetLogin())\n    }\n}\n",[57,2282,2283,2288,2293,2312,2320,2328,2332,2336,2341,2352,2359,2370,2391,2395],{"__ignoreMap":129},[133,2284,2285],{"class":135,"line":136},[133,2286,2287],{"class":176},"# go\n",[133,2289,2290],{"class":135,"line":142},[133,2291,2292],{"class":176},"# Create a map for O(1) lookups\n",[133,2294,2295,2298,2301,2304,2306,2309],{"class":135,"line":148},[133,2296,2297],{"class":757},"followingMap",[133,2299,2300],{"class":195}," :=",[133,2302,2303],{"class":195}," make",[133,2305,1308],{"class":182},[133,2307,2308],{"class":757},"map[string]bool",[133,2310,2311],{"class":182},")\n",[133,2313,2314,2317],{"class":135,"line":154},[133,2315,2316],{"class":1127},"for",[133,2318,2319],{"class":182}," _, f := range following {\n",[133,2321,2322,2325],{"class":135,"line":214},[133,2323,2324],{"class":757},"    followingMap[f.GetLogin",[133,2326,2327],{"class":182},"()] = true\n",[133,2329,2330],{"class":135,"line":227},[133,2331,294],{"class":182},[133,2333,2334],{"class":135,"line":236},[133,2335,784],{"emptyLinePlaceholder":315},[133,2337,2338],{"class":135,"line":242},[133,2339,2340],{"class":176},"# Identify followers who aren't followed back (A - B)\n",[133,2342,2343,2346,2349],{"class":135,"line":255},[133,2344,2345],{"class":757},"var",[133,2347,2348],{"class":195}," needFollow",[133,2350,2351],{"class":182}," []string\n",[133,2353,2354,2356],{"class":135,"line":268},[133,2355,2316],{"class":1127},[133,2357,2358],{"class":182}," _, f := range followers {\n",[133,2360,2361,2364,2367],{"class":135,"line":279},[133,2362,2363],{"class":1127},"    if",[133,2365,2366],{"class":757}," !followingMap[f.GetLogin",[133,2368,2369],{"class":182},"()] {\n",[133,2371,2372,2375,2377,2380,2382,2385,2388],{"class":135,"line":285},[133,2373,2374],{"class":757},"        needFollow",[133,2376,1184],{"class":195},[133,2378,2379],{"class":195}," append",[133,2381,1308],{"class":182},[133,2383,2384],{"class":757},"needFollow,",[133,2386,2387],{"class":195}," f.GetLogin",[133,2389,2390],{"class":182},"())\n",[133,2392,2393],{"class":135,"line":291},[133,2394,282],{"class":182},[133,2396,2397],{"class":135,"line":469},[133,2398,294],{"class":182},[113,2400,2402],{"id":2401},"_2-human-like-execution-throttling","2. Human-Like Execution Throttling",[16,2404,2405],{},"To prevent the account from being flagged as a spam bot, I implemented artificial delays and strict limits. The bot creates a random sleep interval between actions and caps the total operations per run.",[124,2407,2409],{"className":735,"code":2408,"language":737,"meta":129,"style":129},"# go\nconst limit = 25 \u002F\u002F Safety cap per run\n\n\u002F\u002F Random jitter between 2 to 5 seconds\nsleepTime := time.Duration(2+rand.Intn(3)) * time.Second\n\nfor i, user := range needFollow {\n    if i >= limit {\n        log.Printf(\"Reached max follow limit (%d)...\", limit)\n        break\n    }\n    client.FollowPeople(ctx, user)\n    time.Sleep(sleepTime) \u002F\u002F Wait before next action\n}\n",[57,2410,2411,2415,2443,2447,2473,2497,2501,2508,2524,2539,2544,2548,2558,2568],{"__ignoreMap":129},[133,2412,2413],{"class":135,"line":136},[133,2414,2287],{"class":176},[133,2416,2417,2420,2423,2425,2428,2431,2434,2437,2440],{"class":135,"line":142},[133,2418,2419],{"class":757},"const",[133,2421,2422],{"class":195}," limit",[133,2424,1184],{"class":195},[133,2426,2427],{"class":188}," 25",[133,2429,2430],{"class":195}," \u002F\u002F",[133,2432,2433],{"class":195}," Safety",[133,2435,2436],{"class":195}," cap",[133,2438,2439],{"class":195}," per",[133,2441,2442],{"class":195}," run\n",[133,2444,2445],{"class":135,"line":148},[133,2446,784],{"emptyLinePlaceholder":315},[133,2448,2449,2452,2455,2458,2461,2464,2467,2470],{"class":135,"line":154},[133,2450,2451],{"class":757},"\u002F\u002F",[133,2453,2454],{"class":195}," Random",[133,2456,2457],{"class":195}," jitter",[133,2459,2460],{"class":195}," between",[133,2462,2463],{"class":188}," 2",[133,2465,2466],{"class":195}," to",[133,2468,2469],{"class":188}," 5",[133,2471,2472],{"class":195}," seconds\n",[133,2474,2475,2478,2480,2483,2485,2488,2491,2494],{"class":135,"line":214},[133,2476,2477],{"class":757},"sleepTime",[133,2479,2300],{"class":195},[133,2481,2482],{"class":195}," time.Duration",[133,2484,1308],{"class":182},[133,2486,2487],{"class":757},"2+rand.Intn(3",[133,2489,2490],{"class":182},")) ",[133,2492,2493],{"class":1127},"*",[133,2495,2496],{"class":182}," time.Second\n",[133,2498,2499],{"class":135,"line":227},[133,2500,784],{"emptyLinePlaceholder":315},[133,2502,2503,2505],{"class":135,"line":236},[133,2504,2316],{"class":1127},[133,2506,2507],{"class":182}," i, user := range needFollow {\n",[133,2509,2510,2512,2515,2518,2520,2522],{"class":135,"line":242},[133,2511,2363],{"class":1127},[133,2513,2514],{"class":757}," i",[133,2516,2517],{"class":1127}," >",[133,2519,1294],{"class":195},[133,2521,2422],{"class":195},[133,2523,1193],{"class":195},[133,2525,2526,2529,2532,2535,2537],{"class":135,"line":255},[133,2527,2528],{"class":757},"        log.Printf(",[133,2530,2531],{"class":757},"\"Reached max follow limit (%d)...\"",[133,2533,2534],{"class":757},",",[133,2536,2422],{"class":195},[133,2538,2311],{"class":182},[133,2540,2541],{"class":135,"line":268},[133,2542,2543],{"class":1127},"        break\n",[133,2545,2546],{"class":135,"line":279},[133,2547,282],{"class":182},[133,2549,2550,2553,2556],{"class":135,"line":285},[133,2551,2552],{"class":757},"    client.FollowPeople(ctx,",[133,2554,2555],{"class":195}," user",[133,2557,2311],{"class":182},[133,2559,2560,2563,2565],{"class":135,"line":291},[133,2561,2562],{"class":182},"    time.Sleep(",[133,2564,2477],{"class":757},[133,2566,2567],{"class":182},") \u002F\u002F Wait before next action\n",[133,2569,2570],{"class":135,"line":469},[133,2571,294],{"class":182},[296,2573,2574],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":129,"searchDepth":142,"depth":142,"links":2576},[2577,2578,2579],{"id":13,"depth":142,"text":14},{"id":21,"depth":142,"text":22},{"id":71,"depth":142,"text":72,"children":2580},[2581,2582],{"id":76,"depth":148,"text":77},{"id":388,"depth":148,"text":389},"A serverless Go automation tool running on GitHub Actions to manage followers, maintaining a healthy follower ratio with automated sync and activity logging.","https:\u002F\u002Fgithub.com\u002Fszuryuu\u002Fgha-follow-unfollow","\u002Fimages\u002Fprojects\u002Fgithub-actions.png",{},"\u002Fproject\u002Fgha-follow-unfollow",{"title":2172,"description":2583},"project\u002Fgha-follow-unfollow",[321,874,2591],"GitHub API","KiUEKgKChuOBazKwwHZtjOuDreVHZWFLhIt-37ZvK8k",{"id":2594,"title":2595,"body":2596,"description":2923,"duration":513,"extension":309,"featured":310,"github":2924,"image":2585,"live":313,"meta":2925,"navigation":315,"order":285,"path":2926,"role":313,"seo":2927,"status":318,"stem":2928,"team_size":313,"tech":2929,"type":326,"year":2168,"__hash__":2930},"projects\u002Fproject\u002Fgha-sheet-attend.md","GHA Sheet Attendance",{"type":8,"value":2597,"toc":2915},[2598,2600,2603,2605,2612,2638,2640,2642,2647,2665,2670,2727,2729,2733,2736,2905,2909,2912],[11,2599,14],{"id":13},[16,2601,2602],{},"Internships and remote work often require daily attendance logging or \"logbooks.\" Doing this manually in a spreadsheet is repetitive, and maintaining consistent formatting (borders, alignment) across hundreds of rows becomes tedious. Forgetting to log a day can lead to administrative issues at the end of the month.",[11,2604,22],{"id":21},[16,2606,2607,2608,2611],{},"I developed ",[33,2609,2610],{},"GHA Sheet Attend",", a \"set-and-forget\" automation tool that turns a GitHub Actions workflow into a structured data entry interface.",[27,2613,2614,2620,2626,2632],{},[30,2615,2616,2619],{},[33,2617,2618],{},"Serverless Data Entry:"," Users can log attendance via a simple form in the GitHub Actions tab (or let it run on autopilot via CRON).",[30,2621,2622,2625],{},[33,2623,2624],{},"Intelligent Formatting:"," The script doesn't just dump text; it acts as a layout engine, automatically drawing borders and formatting cells for each new entry using the Google Sheets BatchUpdate API.",[30,2627,2628,2631],{},[33,2629,2630],{},"Smart Defaults:"," Automatically detects weekends or \"Libur\" (Holiday) statuses to clear start\u002Fend times, preventing invalid data entry.",[30,2633,2634,2637],{},[33,2635,2636],{},"Secure Integration:"," Uses Google Service Account authentication stored in GitHub Secrets, ensuring no credentials are ever exposed in the client-side code.",[11,2639,72],{"id":71},[74,2641,77],{"id":76},[16,2643,2644],{},[33,2645,2646],{},"Why Go for a Scripting Task?",[27,2648,2649,2659],{},[30,2650,2651,2654,2655,2658],{},[33,2652,2653],{},"Strict Typing for API Payloads:"," The Google Sheets API has nested, complex JSON structures for formatting requests (like ",[57,2656,2657],{},"UpdateBordersRequest","). Go's struct-based typing makes constructing these payloads significantly less error-prone than untyped languages like JavaScript\u002FPython.",[30,2660,2661,2664],{},[33,2662,2663],{},"Execution Speed:"," The compiled binary runs instantly on the CI runner, keeping billable action minutes to a minimum (usually under 30 seconds).",[16,2666,2667],{},[33,2668,2669],{},"Handling Timezones in CI\u002FCD",[27,2671,2672],{},[30,2673,2674,2675],{},"GitHub Actions runners default to UTC. To ensure the \"Today\" date in the spreadsheet matches the user's local context (Indonesia), I implemented explicit timezone loading:\n",[124,2676,2678],{"className":735,"code":2677,"language":737,"meta":129,"style":129},"# go\nloc, _ := time.LoadLocation(\"Asia\u002FJakarta\")\ntoday := time.Now().In(loc)\n",[57,2679,2680,2684,2704],{"__ignoreMap":129},[133,2681,2682],{"class":135,"line":136},[133,2683,2287],{"class":176},[133,2685,2686,2689,2692,2694,2697,2699,2702],{"class":135,"line":142},[133,2687,2688],{"class":757},"loc,",[133,2690,2691],{"class":195}," _",[133,2693,2300],{"class":195},[133,2695,2696],{"class":195}," time.LoadLocation",[133,2698,1308],{"class":182},[133,2700,2701],{"class":757},"\"Asia\u002FJakarta\"",[133,2703,2311],{"class":182},[133,2705,2706,2709,2711,2714,2717,2720,2722,2725],{"class":135,"line":148},[133,2707,2708],{"class":757},"today",[133,2710,2300],{"class":195},[133,2712,2713],{"class":195}," time.Now",[133,2715,2716],{"class":182},"()",[133,2718,2719],{"class":195},".In",[133,2721,1308],{"class":182},[133,2723,2724],{"class":757},"loc",[133,2726,2311],{"class":182},[74,2728,389],{"id":388},[113,2730,2732],{"id":2731},"_1-programmatic-layout-engine","1. Programmatic Layout Engine",[16,2734,2735],{},"Instead of relying on the spreadsheet's conditional formatting (which can break), I engineered the bot to \"draw\" its own table borders after every write operation. It calculates the specific grid range of the newly added row and sends a batch update command.",[124,2737,2739],{"className":735,"code":2738,"language":737,"meta":129,"style":129},"# go\n# Calculate the exact range of the newly added row\nnewRowNumber, _ := strconv.Atoi(matches[1])\nrowIndex := int64(newRowNumber - 1)\n\n$ Send a BatchUpdate request to draw solid borders\nUpdateBorders: &sheets.UpdateBordersRequest{\n    Range: &sheets.GridRange{\n        StartRowIndex: rowIndex,\n        EndRowIndex:   rowIndex + 1,\n        # ...\n    },\n    Top: &sheets.Border{Style: \"SOLID\"},\n    # ...\n}\n",[57,2740,2741,2745,2750,2769,2792,2796,2824,2837,2849,2857,2871,2876,2880,2896,2901],{"__ignoreMap":129},[133,2742,2743],{"class":135,"line":136},[133,2744,2287],{"class":176},[133,2746,2747],{"class":135,"line":142},[133,2748,2749],{"class":176},"# Calculate the exact range of the newly added row\n",[133,2751,2752,2755,2757,2759,2762,2764,2767],{"class":135,"line":148},[133,2753,2754],{"class":757},"newRowNumber,",[133,2756,2691],{"class":195},[133,2758,2300],{"class":195},[133,2760,2761],{"class":195}," strconv.Atoi",[133,2763,1308],{"class":182},[133,2765,2766],{"class":757},"matches[1]",[133,2768,2311],{"class":182},[133,2770,2771,2774,2776,2779,2781,2784,2787,2790],{"class":135,"line":154},[133,2772,2773],{"class":757},"rowIndex",[133,2775,2300],{"class":195},[133,2777,2778],{"class":195}," int64",[133,2780,1308],{"class":182},[133,2782,2783],{"class":757},"newRowNumber",[133,2785,2786],{"class":195}," -",[133,2788,2789],{"class":188}," 1",[133,2791,2311],{"class":182},[133,2793,2794],{"class":135,"line":214},[133,2795,784],{"emptyLinePlaceholder":315},[133,2797,2798,2801,2804,2807,2810,2813,2815,2818,2821],{"class":135,"line":227},[133,2799,2800],{"class":757},"$",[133,2802,2803],{"class":195}," Send",[133,2805,2806],{"class":195}," a",[133,2808,2809],{"class":195}," BatchUpdate",[133,2811,2812],{"class":195}," request",[133,2814,2466],{"class":195},[133,2816,2817],{"class":195}," draw",[133,2819,2820],{"class":195}," solid",[133,2822,2823],{"class":195}," borders\n",[133,2825,2826,2829,2832,2835],{"class":135,"line":236},[133,2827,2828],{"class":757},"UpdateBorders:",[133,2830,2831],{"class":182}," &",[133,2833,2834],{"class":757},"sheets.UpdateBordersRequest",[133,2836,183],{"class":195},[133,2838,2839,2842,2844,2847],{"class":135,"line":242},[133,2840,2841],{"class":757},"    Range:",[133,2843,2831],{"class":182},[133,2845,2846],{"class":757},"sheets.GridRange",[133,2848,183],{"class":195},[133,2850,2851,2854],{"class":135,"line":255},[133,2852,2853],{"class":757},"        StartRowIndex:",[133,2855,2856],{"class":195}," rowIndex,\n",[133,2858,2859,2862,2865,2868],{"class":135,"line":268},[133,2860,2861],{"class":757},"        EndRowIndex:",[133,2863,2864],{"class":195},"   rowIndex",[133,2866,2867],{"class":195}," +",[133,2869,2870],{"class":195}," 1,\n",[133,2872,2873],{"class":135,"line":279},[133,2874,2875],{"class":176},"        # ...\n",[133,2877,2878],{"class":135,"line":285},[133,2879,1111],{"class":182},[133,2881,2882,2885,2887,2890,2893],{"class":135,"line":291},[133,2883,2884],{"class":757},"    Top:",[133,2886,2831],{"class":182},[133,2888,2889],{"class":757},"sheets.Border",[133,2891,2892],{"class":195},"{Style:",[133,2894,2895],{"class":195}," \"SOLID\"},\n",[133,2897,2898],{"class":135,"line":469},[133,2899,2900],{"class":176},"    # ...\n",[133,2902,2903],{"class":135,"line":475},[133,2904,294],{"class":182},[113,2906,2908],{"id":2907},"_2-dynamic-sequence-generation","2. Dynamic Sequence Generation",[16,2910,2911],{},"The system treats the spreadsheet as a database. Before writing, it performs a read operation (Values.Get) to find the last sequence number in Column A, casts it to an integer, and increments it. This ensures the numbering remains sequential (1, 2, 3...) even if rows are manually deleted or modified in between runs.",[296,2913,2914],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":129,"searchDepth":142,"depth":142,"links":2916},[2917,2918,2919],{"id":13,"depth":142,"text":14},{"id":21,"depth":142,"text":22},{"id":71,"depth":142,"text":72,"children":2920},[2921,2922],{"id":76,"depth":148,"text":77},{"id":388,"depth":148,"text":389},"A serverless attendance logging system that leverages GitHub Actions to automate daily reporting into Google Sheets with programmatic formatting and secure credential handling.","https:\u002F\u002Fgithub.com\u002Fszuryuu\u002Fgha-sheet-attend",{},"\u002Fproject\u002Fgha-sheet-attend",{"title":2595,"description":2923},"project\u002Fgha-sheet-attend",[321,874,1416],"yRksOrIhKam8c8OjDwVLYiVvU74ifrxq8D3X4TDpFrw",{"id":2932,"title":2933,"body":2934,"description":3329,"duration":3330,"extension":309,"featured":310,"github":3331,"image":3332,"live":313,"meta":3333,"navigation":315,"order":268,"path":3334,"role":313,"seo":3335,"status":318,"stem":3336,"team_size":313,"tech":3337,"type":3341,"year":2168,"__hash__":3342},"projects\u002Fproject\u002Finternship-monitoring.md","Internship Monitoring",{"type":8,"value":2935,"toc":3321},[2936,2938,2941,2943,2950,2988,2990,2992,2997,3030,3035,3047,3049,3053,3056,3217,3221,3224,3318],[11,2937,14],{"id":13},[16,2939,2940],{},"Vocational schools (SMK) and universities face logistical nightmares when managing hundreds of students scattered across different companies for internships (PKL). Tracking attendance, grading, and administrative approval manually via paper logbooks is inefficient and prone to data loss.",[11,2942,22],{"id":21},[16,2944,2945,2946,2949],{},"I developed the ",[33,2947,2948],{},"Internship Monitoring System",", a web-based solution that digitizes the entire internship lifecycle.",[27,2951,2952,2962,2972,2978],{},[30,2953,2954,2957,2958,2961],{},[33,2955,2956],{},"Rapid Admin Development:"," Leveraged ",[33,2959,2960],{},"Filament PHP"," to build a feature-rich admin panel in record time, allowing teachers to manage Students, Industries, and Internship placements effortlessly.",[30,2963,2964,2967,2968,2971],{},[33,2965,2966],{},"API-Ready Architecture:"," Unlike a traditional monolith, I exposed a comprehensive set of RESTful APIs (",[57,2969,2970],{},"app\u002FHttp\u002FControllers\u002FApi",") to allow future integration with student mobile applications for logbook entry.",[30,2973,2974,2977],{},[33,2975,2976],{},"Automated State Management:"," Implemented database triggers to handle complex status changes automatically, ensuring data consistency without heavy application-level logic.",[30,2979,2980,2983,2984,2987],{},[33,2981,2982],{},"Role-Based Access Control:"," Utilized ",[33,2985,2986],{},"Filament Shield"," to manage granular permissions between Admins, Teachers, and Staff.",[11,2989,72],{"id":71},[74,2991,77],{"id":76},[16,2993,2994],{},[33,2995,2996],{},"Why Filament PHP?",[27,2998,2999,3010],{},[30,3000,3001,3004,3005,3009],{},[33,3002,3003],{},"TALL Stack Efficiency:"," Filament is built on the TALL stack (Tailwind, Alpine, Laravel, Livewire). This allowed me to create reactive, modern UI components for the dashboard (like the ",[3006,3007,3008],"em",{},"Internship Stats Overview"," widget) without writing separate frontend code.",[30,3011,3012,3015,3016,3019,3020,97,3023,3026,3027,60],{},[33,3013,3014],{},"Resource Management:"," Filament's \"Resource\" pattern (",[57,3017,3018],{},"app\u002FFilament\u002FAdmin\u002FResources",") drastically reduced boilerplate for CRUD operations on models like ",[57,3021,3022],{},"BusinessField",[57,3024,3025],{},"Industry",", and ",[57,3028,3029],{},"Student",[16,3031,3032],{},[33,3033,3034],{},"Database-Level Logic (Triggers)",[27,3036,3037,3040],{},[30,3038,3039],{},"To ensure data integrity regardless of whether data is modified via the API or Admin Panel, I moved critical status logic to the database layer.",[30,3041,3042,3043,3046],{},"I wrote a custom migration ",[57,3044,3045],{},"2025_05_20_143053_internship_triggers"," that installs SQL triggers. These triggers automatically update a student's status (e.g., from 'Active' to 'Interning') whenever a new internship record is created or completed.",[74,3048,389],{"id":388},[113,3050,3052],{"id":3051},"_1-hybrid-controller-architecture","1. Hybrid Controller Architecture",[16,3054,3055],{},"The system serves two masters: the web admin panel and mobile clients. I structured the backend to handle both gracefully.",[124,3057,3059],{"className":735,"code":3058,"language":737,"meta":129,"style":129},"# php\n# app\u002FHttp\u002FControllers\u002FApi\u002FInternshipController.php\npublic function index(Request $request)\n{\n    # JSON response for Mobile Apps\n    return InternshipResource::collection(Internship::all());\n}\n\n# app\u002FFilament\u002FAdmin\u002FResources\u002FInternshipResource.php\npublic static function form(Form $form): Form\n{\n    # Reactive Form UI for Admin Panel\n    return $form->schema([\n        Select::make('student_id')->relationship('student', 'name'),\n        # ...\n    ]);\n}\n",[57,3060,3061,3066,3071,3090,3094,3099,3115,3119,3123,3128,3153,3157,3162,3178,3204,3208,3213],{"__ignoreMap":129},[133,3062,3063],{"class":135,"line":136},[133,3064,3065],{"class":176},"# php\n",[133,3067,3068],{"class":135,"line":142},[133,3069,3070],{"class":176},"# app\u002FHttp\u002FControllers\u002FApi\u002FInternshipController.php\n",[133,3072,3073,3076,3079,3082,3084,3087],{"class":135,"line":148},[133,3074,3075],{"class":757},"public",[133,3077,3078],{"class":195}," function",[133,3080,3081],{"class":195}," index",[133,3083,1308],{"class":182},[133,3085,3086],{"class":757},"Request",[133,3088,3089],{"class":182}," $request)\n",[133,3091,3092],{"class":135,"line":154},[133,3093,183],{"class":182},[133,3095,3096],{"class":135,"line":214},[133,3097,3098],{"class":176},"    # JSON response for Mobile Apps\n",[133,3100,3101,3104,3107,3109,3112],{"class":135,"line":227},[133,3102,3103],{"class":1127},"    return",[133,3105,3106],{"class":195}," InternshipResource::collection",[133,3108,1308],{"class":182},[133,3110,3111],{"class":757},"Internship::all",[133,3113,3114],{"class":182},"());\n",[133,3116,3117],{"class":135,"line":236},[133,3118,294],{"class":182},[133,3120,3121],{"class":135,"line":242},[133,3122,784],{"emptyLinePlaceholder":315},[133,3124,3125],{"class":135,"line":255},[133,3126,3127],{"class":176},"# app\u002FFilament\u002FAdmin\u002FResources\u002FInternshipResource.php\n",[133,3129,3130,3132,3135,3137,3140,3142,3145,3148,3150],{"class":135,"line":268},[133,3131,3075],{"class":757},[133,3133,3134],{"class":195}," static",[133,3136,3078],{"class":195},[133,3138,3139],{"class":195}," form",[133,3141,1308],{"class":182},[133,3143,3144],{"class":757},"Form",[133,3146,3147],{"class":182}," $form)",[133,3149,1272],{"class":195},[133,3151,3152],{"class":195}," Form\n",[133,3154,3155],{"class":135,"line":279},[133,3156,183],{"class":182},[133,3158,3159],{"class":135,"line":285},[133,3160,3161],{"class":176},"    # Reactive Form UI for Admin Panel\n",[133,3163,3164,3166,3169,3172,3175],{"class":135,"line":291},[133,3165,3103],{"class":1127},[133,3167,3168],{"class":182}," $form-",[133,3170,3171],{"class":1127},">",[133,3173,3174],{"class":195},"schema",[133,3176,3177],{"class":182},"([\n",[133,3179,3180,3183,3186,3189,3191,3194,3197,3199,3202],{"class":135,"line":469},[133,3181,3182],{"class":182},"        Select::make(",[133,3184,3185],{"class":195},"'student_id'",[133,3187,3188],{"class":182},")-",[133,3190,3171],{"class":1127},[133,3192,3193],{"class":182},"relationship(",[133,3195,3196],{"class":195},"'student'",[133,3198,2534],{"class":1127},[133,3200,3201],{"class":195}," 'name'",[133,3203,1359],{"class":182},[133,3205,3206],{"class":135,"line":475},[133,3207,2875],{"class":176},[133,3209,3210],{"class":135,"line":481},[133,3211,3212],{"class":182},"    ]);\n",[133,3214,3215],{"class":135,"line":487},[133,3216,294],{"class":182},[113,3218,3220],{"id":3219},"_2-complex-relationship-management","2. Complex Relationship Management",[16,3222,3223],{},"The system handles a web of relationships: Students belong to Departments, apply to Industries, and are supervised by Teachers. I modeled this using Laravel's Eloquent relationships and enforced constraints at the schema level to prevent orphaned records.",[124,3225,3227],{"className":735,"code":3226,"language":737,"meta":129,"style":129},"# php\n# app\u002FModels\u002FInternship.php\npublic function industry()\n{\n    return $this->belongsTo(Industry::class);\n}\n\npublic function teacher()\n{\n    return $this->belongsTo(Teacher::class);\n}\n",[57,3228,3229,3233,3238,3250,3254,3274,3278,3282,3293,3297,3314],{"__ignoreMap":129},[133,3230,3231],{"class":135,"line":136},[133,3232,3065],{"class":176},[133,3234,3235],{"class":135,"line":142},[133,3236,3237],{"class":176},"# app\u002FModels\u002FInternship.php\n",[133,3239,3240,3242,3244,3247],{"class":135,"line":148},[133,3241,3075],{"class":757},[133,3243,3078],{"class":195},[133,3245,3246],{"class":195}," industry",[133,3248,3249],{"class":182},"()\n",[133,3251,3252],{"class":135,"line":154},[133,3253,183],{"class":182},[133,3255,3256,3258,3261,3263,3266,3268,3271],{"class":135,"line":214},[133,3257,3103],{"class":1127},[133,3259,3260],{"class":182}," $this-",[133,3262,3171],{"class":1127},[133,3264,3265],{"class":195},"belongsTo",[133,3267,1308],{"class":182},[133,3269,3270],{"class":757},"Industry::class",[133,3272,3273],{"class":182},");\n",[133,3275,3276],{"class":135,"line":227},[133,3277,294],{"class":182},[133,3279,3280],{"class":135,"line":236},[133,3281,784],{"emptyLinePlaceholder":315},[133,3283,3284,3286,3288,3291],{"class":135,"line":242},[133,3285,3075],{"class":757},[133,3287,3078],{"class":195},[133,3289,3290],{"class":195}," teacher",[133,3292,3249],{"class":182},[133,3294,3295],{"class":135,"line":255},[133,3296,183],{"class":182},[133,3298,3299,3301,3303,3305,3307,3309,3312],{"class":135,"line":268},[133,3300,3103],{"class":1127},[133,3302,3260],{"class":182},[133,3304,3171],{"class":1127},[133,3306,3265],{"class":195},[133,3308,1308],{"class":182},[133,3310,3311],{"class":757},"Teacher::class",[133,3313,3273],{"class":182},[133,3315,3316],{"class":135,"line":279},[133,3317,294],{"class":182},[296,3319,3320],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":129,"searchDepth":142,"depth":142,"links":3322},[3323,3324,3325],{"id":13,"depth":142,"text":14},{"id":21,"depth":142,"text":22},{"id":71,"depth":142,"text":72,"children":3326},[3327,3328],{"id":76,"depth":148,"text":77},{"id":388,"depth":148,"text":389},"A centralized platform for managing vocational school internships, featuring a robust Admin Dashboard built with Filament PHP and RESTful APIs for student mobile apps.","2 months","https:\u002F\u002Fgithub.com\u002Fszuryuu\u002Finternship-monitoring-laravel","\u002Fimages\u002Fprojects\u002Flaravel.png",{},"\u002Fproject\u002Finternship-monitoring",{"title":2933,"description":3329},"project\u002Finternship-monitoring",[3338,2960,3339,3340],"Laravel 11","MySQL","Vue.js","Exam Project","HzvV3BZx9MZrwmJEzQ1537blF_fOySMhmLt5S8C_YCc",{"id":3344,"title":3345,"body":3346,"description":3642,"duration":308,"extension":309,"featured":315,"github":3643,"image":1407,"live":3644,"meta":3645,"navigation":315,"order":148,"path":3646,"role":313,"seo":3647,"status":318,"stem":3648,"team_size":313,"tech":3649,"type":326,"year":2168,"__hash__":3655},"projects\u002Fproject\u002Flapor-cepat-bpbd.md","LaporCepat: AI-Powered Disaster Reporting",{"type":8,"value":3347,"toc":3634},[3348,3350,3353,3355,3361,3391,3393,3395,3401,3407,3409,3413,3416,3624,3628,3631],[11,3349,14],{"id":13},[16,3351,3352],{},"Indonesia is highly disaster-prone, yet existing emergency reporting systems heavily rely on manual phone calls to BPBD command centers. During mass casualty events, dispatchers are overwhelmed, communication is vague, and manual data entry is slow. When a citizen is panicked, forcing them to navigate a clunky web form or wait in a call queue costs critical response time and, ultimately, lives.",[11,3354,22],{"id":21},[16,3356,892,3357,3360],{},[33,3358,3359],{},"LaporCepat",", a complete overhaul of the emergency reporting pipeline that uses artificial intelligence to completely eliminate form-filling for victims while providing perfect situational awareness for dispatchers.",[27,3362,3363,3369,3375,3381],{},[30,3364,3365,3368],{},[33,3366,3367],{},"Voice-First Triage Pipeline:"," Citizens simply tap a button and speak. The audio is instantly transcribed using Groq Whisper Large V3.",[30,3370,3371,3374],{},[33,3372,3373],{},"Multimodal Extraction:"," Google Gemini 2.5 Flash analyzes the text transcript alongside uploaded field photos to automatically extract disaster type, victim estimates, severity level (CRITICAL to LOW), and generate survival instructions.",[30,3376,3377,3380],{},[33,3378,3379],{},"Frictionless Geolocation:"," Automatically captures exact GPS coordinates via browser APIs with an IP-based fallback, preventing location miscommunication.",[30,3382,3383,3386,3387,3390],{},[33,3384,3385],{},"Real-Time BPBD Dashboard:"," An operations dashboard for dispatchers powered by Server-Sent Events (SSE) and Firebase ",[57,3388,3389],{},"onSnapshot"," that streams live reports onto a spatial map without requiring page reloads.",[11,3392,72],{"id":71},[74,3394,77],{"id":76},[16,3396,3397,3400],{},[33,3398,3399],{},"Why Voice-First Reporting (Groq Whisper)?","\nUnder severe psychological stress, fine motor skills degrade, making typing difficult. By leveraging Groq's insanely fast inference engine for Whisper Large V3, I allowed victims to simply describe their emergency verbally, converting chaotic audio into perfectly structured text in milliseconds.",[16,3402,3403,3406],{},[33,3404,3405],{},"Why Upgrade to Nuxt 4?","\nThe project utilizes the bleeding-edge Nuxt 4 framework to leverage its improved module resolution and optimized Nitro server engine. This was crucial for handling the custom Server-Sent Events (SSE) endpoints efficiently on Vercel's serverless environment.",[74,3408,389],{"id":388},[113,3410,3412],{"id":3411},"_1-zero-latency-incident-streaming-sse","1. Zero-Latency Incident Streaming (SSE)",[16,3414,3415],{},"Instead of forcing the dispatcher dashboard to constantly poll the database or implementing heavy WebSockets, I built a lightweight Server-Sent Events (SSE) stream. The Nitro backend listens to Firebase Firestore mutations and pushes raw JSON chunks directly to the frontend's reactive state.",[124,3417,3419],{"className":958,"code":3418,"language":960,"meta":129,"style":129},"\u002F\u002F Real-time unidirectional feed using SSE over HTTP\u002F2\nexport default defineEventHandler((event) => {\n  setHeader(event, \"Content-Type\", \"text\u002Fevent-stream\");\n  setHeader(event, \"Cache-Control\", \"no-cache\");\n  setHeader(event, \"Connection\", \"keep-alive\");\n\n  const unsubscribe = db\n    .collection(\"reports\")\n    .where(\"status\", \"==\", \"PENDING\")\n    .onSnapshot((snapshot) => {\n      \u002F\u002F Push live incident updates directly to the dispatcher's map\n      event.node.res.write(`data: ${JSON.stringify(reports)}\\n\\n`);\n    });\n});\n",[57,3420,3421,3426,3448,3466,3482,3498,3502,3514,3529,3553,3570,3575,3615,3620],{"__ignoreMap":129},[133,3422,3423],{"class":135,"line":136},[133,3424,3425],{"class":176},"\u002F\u002F Real-time unidirectional feed using SSE over HTTP\u002F2\n",[133,3427,3428,3430,3433,3436,3439,3442,3444,3446],{"class":135,"line":142},[133,3429,1175],{"class":1127},[133,3431,3432],{"class":1127}," default",[133,3434,3435],{"class":757}," defineEventHandler",[133,3437,3438],{"class":182},"((",[133,3440,3441],{"class":1268},"event",[133,3443,1282],{"class":182},[133,3445,1190],{"class":1127},[133,3447,1193],{"class":182},[133,3449,3450,3453,3456,3459,3461,3464],{"class":135,"line":148},[133,3451,3452],{"class":757},"  setHeader",[133,3454,3455],{"class":182},"(event, ",[133,3457,3458],{"class":195},"\"Content-Type\"",[133,3460,97],{"class":182},[133,3462,3463],{"class":195},"\"text\u002Fevent-stream\"",[133,3465,3273],{"class":182},[133,3467,3468,3470,3472,3475,3477,3480],{"class":135,"line":154},[133,3469,3452],{"class":757},[133,3471,3455],{"class":182},[133,3473,3474],{"class":195},"\"Cache-Control\"",[133,3476,97],{"class":182},[133,3478,3479],{"class":195},"\"no-cache\"",[133,3481,3273],{"class":182},[133,3483,3484,3486,3488,3491,3493,3496],{"class":135,"line":214},[133,3485,3452],{"class":757},[133,3487,3455],{"class":182},[133,3489,3490],{"class":195},"\"Connection\"",[133,3492,97],{"class":182},[133,3494,3495],{"class":195},"\"keep-alive\"",[133,3497,3273],{"class":182},[133,3499,3500],{"class":135,"line":227},[133,3501,784],{"emptyLinePlaceholder":315},[133,3503,3504,3506,3509,3511],{"class":135,"line":236},[133,3505,1198],{"class":1127},[133,3507,3508],{"class":188}," unsubscribe",[133,3510,1184],{"class":1127},[133,3512,3513],{"class":182}," db\n",[133,3515,3516,3519,3522,3524,3527],{"class":135,"line":242},[133,3517,3518],{"class":182},"    .",[133,3520,3521],{"class":757},"collection",[133,3523,1308],{"class":182},[133,3525,3526],{"class":195},"\"reports\"",[133,3528,2311],{"class":182},[133,3530,3531,3533,3536,3538,3541,3543,3546,3548,3551],{"class":135,"line":255},[133,3532,3518],{"class":182},[133,3534,3535],{"class":757},"where",[133,3537,1308],{"class":182},[133,3539,3540],{"class":195},"\"status\"",[133,3542,97],{"class":182},[133,3544,3545],{"class":195},"\"==\"",[133,3547,97],{"class":182},[133,3549,3550],{"class":195},"\"PENDING\"",[133,3552,2311],{"class":182},[133,3554,3555,3557,3559,3561,3564,3566,3568],{"class":135,"line":268},[133,3556,3518],{"class":182},[133,3558,3389],{"class":757},[133,3560,3438],{"class":182},[133,3562,3563],{"class":1268},"snapshot",[133,3565,1282],{"class":182},[133,3567,1190],{"class":1127},[133,3569,1193],{"class":182},[133,3571,3572],{"class":135,"line":279},[133,3573,3574],{"class":176},"      \u002F\u002F Push live incident updates directly to the dispatcher's map\n",[133,3576,3577,3580,3583,3585,3588,3591,3593,3596,3598,3601,3604,3607,3610,3613],{"class":135,"line":285},[133,3578,3579],{"class":182},"      event.node.res.",[133,3581,3582],{"class":757},"write",[133,3584,1308],{"class":182},[133,3586,3587],{"class":195},"`data: ${",[133,3589,3590],{"class":188},"JSON",[133,3592,60],{"class":195},[133,3594,3595],{"class":757},"stringify",[133,3597,1308],{"class":195},[133,3599,3600],{"class":182},"reports",[133,3602,3603],{"class":195},")",[133,3605,3606],{"class":195},"}",[133,3608,3609],{"class":188},"\\n\\n",[133,3611,3612],{"class":195},"`",[133,3614,3273],{"class":182},[133,3616,3617],{"class":135,"line":291},[133,3618,3619],{"class":182},"    });\n",[133,3621,3622],{"class":135,"line":469},[133,3623,1121],{"class":182},[113,3625,3627],{"id":3626},"_2-robust-ai-fallback-mechanism","2. Robust AI Fallback Mechanism",[16,3629,3630],{},"Emergency systems cannot afford downtime. I designed a failover mechanism in the AI pipeline. If the primary Google Gemini 2.5 Flash model hits a rate limit or exhausts its quota, the system autonomously degrades to Gemini 2.0 Flash to ensure triage continuity without interrupting the citizen's submission process.",[296,3632,3633],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":129,"searchDepth":142,"depth":142,"links":3635},[3636,3637,3638],{"id":13,"depth":142,"text":14},{"id":21,"depth":142,"text":22},{"id":71,"depth":142,"text":72,"children":3639},[3640,3641],{"id":76,"depth":148,"text":77},{"id":388,"depth":148,"text":389},"A real-time emergency reporting system that replaces manual call centers with an AI pipeline (Groq Whisper STT + Gemini 2.5 Flash) for instant triage and live dispatch.","https:\u002F\u002Fgithub.com\u002Fszuryuu\u002Flapor-cepat-bpbd","https:\u002F\u002Flapor-cepat.vercel.app",{},"\u002Fproject\u002Flapor-cepat-bpbd",{"title":3345,"description":3642},"project\u002Flapor-cepat-bpbd",[1413,3650,3651,3652,3653,3654],"Groq Whisper AI","Google Gemini 2.5 Flash","Firebase","SSE","Leaflet","WCGEdHBRjCfKQ2UkC_qXkqu5JCxvxqbfscvPN0l7CxM",{"id":3657,"title":3658,"body":3659,"description":4109,"duration":308,"extension":309,"featured":310,"github":4110,"image":4111,"live":313,"meta":4112,"navigation":315,"order":279,"path":4113,"role":313,"seo":4114,"status":318,"stem":4115,"team_size":313,"tech":4116,"type":4122,"year":4123,"__hash__":4124},"projects\u002Fproject\u002Fmikro-manager.md","Mikrotik Manager",{"type":8,"value":3660,"toc":4101},[3661,3663,3666,3668,3674,3708,3710,3712,3721,3736,3744,3759,3761,3765,3768,3880,3884,3887,4098],[11,3662,14],{"id":13},[16,3664,3665],{},"Network administrators often spend hours manually configuring MikroTik routers via WinBox GUI for repetitive tasks like setting up IP addresses, configuring DHCP servers, or managing backups. This manual process is prone to clicks-errors and lacks the speed required for deploying multiple devices efficiently.",[11,3667,22],{"id":21},[16,3669,2184,3670,3673],{},[33,3671,3672],{},"Mikro Manager",", a modular Command Line Interface (CLI) tool that interacts directly with the RouterOS API to automate network provisioning.",[27,3675,3676,3686,3692,3702],{},[30,3677,3678,3681,3682,3685],{},[33,3679,3680],{},"Interactive CLI:"," Leveraged the ",[57,3683,3684],{},"rich"," library to create a beautiful, menu-driven terminal interface that is easy to navigate.",[30,3687,3688,3691],{},[33,3689,3690],{},"Task Automation:"," implemented specific modules for critical tasks such as Backup Management, DHCP Setup, NAT Configuration, and IP Address management.",[30,3693,3694,3697,3698,3701],{},[33,3695,3696],{},"Secure Connection:"," Uses environment variables (",[57,3699,3700],{},".env",") to handle router credentials securely, separating configuration from code.",[30,3703,3704,3707],{},[33,3705,3706],{},"Modular Design:"," Architected with a clear separation of concerns (Views pattern), making it easy to add new configuration modules without breaking the core application.",[11,3709,72],{"id":71},[74,3711,77],{"id":76},[16,3713,3714],{},[33,3715,3716,3717,3720],{},"Why ",[57,3718,3719],{},"routeros_api"," wrapper?",[27,3722,3723],{},[30,3724,3725,3728,3729,3731,3732,3735],{},[33,3726,3727],{},"Abstraction:"," Instead of writing raw socket commands, I used the ",[57,3730,3719],{}," library to interact with MikroTik's resources in a Pythonic way (e.g., ",[57,3733,3734],{},"api.get_resource('\u002Fip\u002Faddress')","). This ensures better error handling and cleaner code.",[16,3737,3738],{},[33,3739,3716,3740,3743],{},[57,3741,3742],{},"Rich"," for the UI?",[27,3745,3746],{},[30,3747,3748,3751,3752,951,3755,3758],{},[33,3749,3750],{},"User Experience:"," CLI tools don't have to be ugly. I used ",[57,3753,3754],{},"rich.console",[57,3756,3757],{},"rich.panel"," to display formatted tables, success\u002Ferror alerts, and banners. This makes the tool approachable for technicians who might not be developers.",[74,3760,389],{"id":388},[113,3762,3764],{"id":3763},"_1-modular-resource-interaction","1. Modular Resource Interaction",[16,3766,3767],{},"Each network function is encapsulated in its own view. For example, adding an IP address isn't just a script; it's a function that validates input and interacts with the specific RouterOS endpoint.",[124,3769,3771],{"className":735,"code":3770,"language":737,"meta":129,"style":129},"# python\n# views\u002Fip_address.py\ndef add_ip(api):\n    # ... input prompts ...\n    try:\n        list_ip = api.get_resource('\u002Fip\u002Faddress')\n        list_ip.add(address=addr, interface=interface)\n        console.print(Panel(f\"Success: IP {addr} added to {interface}\", style=\"green\"))\n    except Exception as e:\n        console.print(f\"[red]Error adding IP: {e}[\u002Fred]\")\n",[57,3772,3773,3778,3783,3800,3805,3810,3827,3840,3856,3870],{"__ignoreMap":129},[133,3774,3775],{"class":135,"line":136},[133,3776,3777],{"class":176},"# python\n",[133,3779,3780],{"class":135,"line":142},[133,3781,3782],{"class":176},"# views\u002Fip_address.py\n",[133,3784,3785,3788,3791,3793,3796,3798],{"class":135,"line":148},[133,3786,3787],{"class":757},"def",[133,3789,3790],{"class":195}," add_ip",[133,3792,1308],{"class":182},[133,3794,3795],{"class":757},"api",[133,3797,3603],{"class":182},[133,3799,655],{"class":195},[133,3801,3802],{"class":135,"line":154},[133,3803,3804],{"class":176},"    # ... input prompts ...\n",[133,3806,3807],{"class":135,"line":214},[133,3808,3809],{"class":757},"    try:\n",[133,3811,3812,3815,3817,3820,3822,3825],{"class":135,"line":227},[133,3813,3814],{"class":757},"        list_ip",[133,3816,1184],{"class":195},[133,3818,3819],{"class":195}," api.get_resource",[133,3821,1308],{"class":182},[133,3823,3824],{"class":757},"'\u002Fip\u002Faddress'",[133,3826,2311],{"class":182},[133,3828,3829,3832,3835,3838],{"class":135,"line":236},[133,3830,3831],{"class":757},"        list_ip.add(address",[133,3833,3834],{"class":195},"=addr,",[133,3836,3837],{"class":195}," interface=interface",[133,3839,2311],{"class":182},[133,3841,3842,3845,3848,3850,3853],{"class":135,"line":242},[133,3843,3844],{"class":757},"        console.print(Panel(f",[133,3846,3847],{"class":757},"\"Success: IP {addr} added to {interface}\"",[133,3849,2534],{"class":757},[133,3851,3852],{"class":195}," style=\"green\"",[133,3854,3855],{"class":182},"))\n",[133,3857,3858,3861,3864,3867],{"class":135,"line":255},[133,3859,3860],{"class":757},"    except",[133,3862,3863],{"class":195}," Exception",[133,3865,3866],{"class":195}," as",[133,3868,3869],{"class":195}," e:\n",[133,3871,3872,3875,3878],{"class":135,"line":268},[133,3873,3874],{"class":757},"        console.print(f",[133,3876,3877],{"class":757},"\"[red]Error adding IP: {e}[\u002Fred]\"",[133,3879,2311],{"class":182},[113,3881,3883],{"id":3882},"_2-centralized-authentication-menu-loop","2. Centralized Authentication & Menu Loop",[16,3885,3886],{},"The main.py acts as the controller, managing the connection state and routing the user to the correct module based on their input.",[124,3888,3890],{"className":735,"code":3889,"language":737,"meta":129,"style":129},"# python\n# main.py\ndef main():\n    connection = Connect()\n    api = connection.connect()\n\n    while True:\n        # Display menu with Rich\n        console.print(Panel.fit(\"1. Add IP Address\\n2. DHCP Setup\\n...\", title=\"Menu\"))\n\n        match choice:\n            case \"1\":\n                ip_address.add_ip(api)\n            case \"2\":\n                dhcp.setup_dhcp(api2. Centralized Authentication & Menu Loop\n\n                The main.py acts as the controller, managing the connection state and routing the user to the correct module based on their input.)\n            # ... handles other modules\n",[57,3891,3892,3896,3901,3912,3924,3936,3940,3948,3953,3968,3972,3980,3989,3996,4005,4025,4029,4093],{"__ignoreMap":129},[133,3893,3894],{"class":135,"line":136},[133,3895,3777],{"class":176},[133,3897,3898],{"class":135,"line":142},[133,3899,3900],{"class":176},"# main.py\n",[133,3902,3903,3905,3908,3910],{"class":135,"line":148},[133,3904,3787],{"class":757},[133,3906,3907],{"class":195}," main",[133,3909,2716],{"class":182},[133,3911,655],{"class":195},[133,3913,3914,3917,3919,3922],{"class":135,"line":154},[133,3915,3916],{"class":757},"    connection",[133,3918,1184],{"class":195},[133,3920,3921],{"class":195}," Connect",[133,3923,3249],{"class":182},[133,3925,3926,3929,3931,3934],{"class":135,"line":214},[133,3927,3928],{"class":757},"    api",[133,3930,1184],{"class":195},[133,3932,3933],{"class":195}," connection.connect",[133,3935,3249],{"class":182},[133,3937,3938],{"class":135,"line":227},[133,3939,784],{"emptyLinePlaceholder":315},[133,3941,3942,3945],{"class":135,"line":236},[133,3943,3944],{"class":1127},"    while",[133,3946,3947],{"class":757}," True:\n",[133,3949,3950],{"class":135,"line":242},[133,3951,3952],{"class":176},"        # Display menu with Rich\n",[133,3954,3955,3958,3961,3963,3966],{"class":135,"line":255},[133,3956,3957],{"class":757},"        console.print(Panel.fit(",[133,3959,3960],{"class":757},"\"1. Add IP Address\\n2. DHCP Setup\\n...\"",[133,3962,2534],{"class":757},[133,3964,3965],{"class":195}," title=\"Menu\"",[133,3967,3855],{"class":182},[133,3969,3970],{"class":135,"line":268},[133,3971,784],{"emptyLinePlaceholder":315},[133,3973,3974,3977],{"class":135,"line":279},[133,3975,3976],{"class":757},"        match",[133,3978,3979],{"class":195}," choice:\n",[133,3981,3982,3985,3987],{"class":135,"line":285},[133,3983,3984],{"class":182},"            case ",[133,3986,685],{"class":195},[133,3988,655],{"class":182},[133,3990,3991,3994],{"class":135,"line":291},[133,3992,3993],{"class":757},"                ip_address.add_ip(api",[133,3995,2311],{"class":182},[133,3997,3998,4000,4003],{"class":135,"line":469},[133,3999,3984],{"class":182},[133,4001,4002],{"class":195},"\"2\"",[133,4004,655],{"class":182},[133,4006,4007,4010,4013,4016,4019,4022],{"class":135,"line":475},[133,4008,4009],{"class":757},"                dhcp.setup_dhcp(api2.",[133,4011,4012],{"class":195}," Centralized",[133,4014,4015],{"class":195}," Authentication",[133,4017,4018],{"class":182}," & ",[133,4020,4021],{"class":757},"Menu",[133,4023,4024],{"class":195}," Loop\n",[133,4026,4027],{"class":135,"line":481},[133,4028,784],{"emptyLinePlaceholder":315},[133,4030,4031,4034,4037,4040,4042,4045,4048,4051,4053,4056,4059,4062,4065,4067,4069,4071,4073,4076,4079,4082,4085,4088,4091],{"class":135,"line":487},[133,4032,4033],{"class":757},"                The",[133,4035,4036],{"class":195}," main.py",[133,4038,4039],{"class":195}," acts",[133,4041,3866],{"class":195},[133,4043,4044],{"class":195}," the",[133,4046,4047],{"class":195}," controller,",[133,4049,4050],{"class":195}," managing",[133,4052,4044],{"class":195},[133,4054,4055],{"class":195}," connection",[133,4057,4058],{"class":195}," state",[133,4060,4061],{"class":195}," and",[133,4063,4064],{"class":195}," routing",[133,4066,4044],{"class":195},[133,4068,2555],{"class":195},[133,4070,2466],{"class":195},[133,4072,4044],{"class":195},[133,4074,4075],{"class":195}," correct",[133,4077,4078],{"class":195}," module",[133,4080,4081],{"class":195}," based",[133,4083,4084],{"class":195}," on",[133,4086,4087],{"class":195}," their",[133,4089,4090],{"class":195}," input.",[133,4092,2311],{"class":182},[133,4094,4095],{"class":135,"line":493},[133,4096,4097],{"class":176},"            # ... handles other modules\n",[296,4099,4100],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":129,"searchDepth":142,"depth":142,"links":4102},[4103,4104,4105],{"id":13,"depth":142,"text":14},{"id":21,"depth":142,"text":22},{"id":71,"depth":142,"text":72,"children":4106},[4107,4108],{"id":76,"depth":148,"text":77},{"id":388,"depth":148,"text":389},"A Python-based CLI utility designed to automate common MikroTik RouterOS configuration tasks, featuring a modular architecture and a rich terminal interface.","https:\u002F\u002Fgithub.com\u002Fszuryuu\u002Fmikro-manager","\u002Fimages\u002Fprojects\u002Fmikrotik.png",{},"\u002Fproject\u002Fmikro-manager",{"title":3658,"description":4109},"project\u002Fmikro-manager",[4117,4118,4119,4120,4121],"Python","MikroTik","Paramiko","Streamlit","SSH","Team Project","2024","XP7ksGjcqHdxs6dJTAE6ut7dWIm_CLeJxyPykXjoiQI",{"id":4126,"title":4127,"body":4128,"description":4446,"duration":2158,"extension":309,"featured":315,"github":4447,"image":4448,"live":313,"meta":4449,"navigation":315,"order":154,"path":4450,"role":313,"seo":4451,"status":318,"stem":4452,"team_size":313,"tech":4453,"type":2167,"year":2168,"__hash__":4455},"projects\u002Fproject\u002Fmysql-load-test.md","MySQL Load Test",{"type":8,"value":4129,"toc":4438},[4130,4132,4139,4141,4146,4180,4182,4184,4189,4205,4210,4222,4224,4228,4231,4276,4280,4283,4435],[11,4131,14],{"id":13},[16,4133,4134,4135,4138],{},"Synthetic benchmarks like Sysbench are great for raw hardware testing but fail to represent ",[33,4136,4137],{},"real-world traffic patterns",". Production databases often face skewed data access (Pareto principle), specific query complexity distributions, and \"thundering herd\" scenarios that synthetic random queries cannot simulate.",[11,4140,22],{"id":21},[16,4142,892,4143,4145],{},[33,4144,4127],{},", a \"Record & Replay\" ecosystem designed to replicate production behavior with high fidelity.",[27,4147,4148,4158,4168,4174],{},[30,4149,4150,4153,4154,4157],{},[33,4151,4152],{},"Traffic Capture:"," Instead of inventing queries, it parses real SQL traffic directly from network packets (",[57,4155,4156],{},".pcap",") or TShark logs.",[30,4159,4160,4163,4164,4167],{},[33,4161,4162],{},"Weighted Replay:"," It doesn't just run queries randomly; it calculates the \"fingerprint\" weight of every query type. If ",[57,4165,4166],{},"SELECT * FROM products"," happens 80% of the time in production, the load tester ensures it happens 80% of the time during the test.",[30,4169,4170,4173],{},[33,4171,4172],{},"High Concurrency:"," Built on Go's goroutines to spawn thousands of concurrent virtual users with minimal memory footprint.",[30,4175,4176,4179],{},[33,4177,4178],{},"Observability:"," Integrated Prometheus exporter to visualize Latency (p99, p95) and QPS in real-time.",[11,4181,72],{"id":71},[74,4183,77],{"id":76},[16,4185,4186],{},[33,4187,4188],{},"Why Packet Capture (Gopacket) over General Logs?",[27,4190,4191],{},[30,4192,4193,4196,4197,4200,4201,4204],{},[33,4194,4195],{},"Zero Overhead:"," Enabling the \"General Query Log\" in MySQL kills performance. By capturing traffic at the network layer (TCP\u002FIP) using ",[57,4198,4199],{},"gopacket",", I could extract SQL queries from a production server without adding ",[3006,4202,4203],{},"any"," load to the database itself.",[16,4206,4207],{},[33,4208,4209],{},"Why Go?",[27,4211,4212],{},[30,4213,4214,4217,4218,4221],{},[33,4215,4216],{},"Channel-Based Pipeline:"," The ",[57,4219,4220],{},"query-collector"," component uses a streaming pipeline pattern. It reads gigabytes of PCAP data, processes distinct query fingerprints, and writes them to the DB in parallel without loading the entire dataset into RAM.",[74,4223,389],{"id":388},[113,4225,4227],{"id":4226},"_1-weighted-random-selection-strategy","1. Weighted Random Selection Strategy",[16,4229,4230],{},"To mimic real traffic, I implemented a logic that selects queries based on their historical frequency. The config allows fetching these weights dynamically from the database.",[124,4232,4234],{"className":614,"code":4233,"language":616,"meta":129,"style":129},"# configuration snippet\nfingerprint_weights_query: |\n  SELECT\n    qf2.Hash AS Hash,\n    CAST(COUNT(*) AS DECIMAL(10,4)) \u002F qft.c * 100 AS Weight\n  FROM QueryFingerprint qf2\n  ...\n",[57,4235,4236,4241,4251,4256,4261,4266,4271],{"__ignoreMap":129},[133,4237,4238],{"class":135,"line":136},[133,4239,4240],{"class":176},"# configuration snippet\n",[133,4242,4243,4246,4248],{"class":135,"line":142},[133,4244,4245],{"class":631},"fingerprint_weights_query",[133,4247,192],{"class":182},[133,4249,4250],{"class":1127},"|\n",[133,4252,4253],{"class":135,"line":148},[133,4254,4255],{"class":195},"  SELECT\n",[133,4257,4258],{"class":135,"line":154},[133,4259,4260],{"class":195},"    qf2.Hash AS Hash,\n",[133,4262,4263],{"class":135,"line":214},[133,4264,4265],{"class":195},"    CAST(COUNT(*) AS DECIMAL(10,4)) \u002F qft.c * 100 AS Weight\n",[133,4267,4268],{"class":135,"line":227},[133,4269,4270],{"class":195},"  FROM QueryFingerprint qf2\n",[133,4272,4273],{"class":135,"line":236},[133,4274,4275],{"class":195},"  ...\n",[113,4277,4279],{"id":4278},"_2-network-layer-extraction-pipeline","2. Network Layer Extraction Pipeline",[16,4281,4282],{},"The tool treats network packets as a stream of raw bytes, reassembles the TCP stream, and extracts the MySQL protocol payload. This allows \"sniffing\" queries even from uninstrumented legacy applications.",[124,4284,4286],{"className":735,"code":4285,"language":737,"meta":129,"style":129},"# From internal\u002Fcmd\u002Fquery-collector\u002Finput_pcap.go\nfunc (i *InputPcap) StartExtractor(ctx context.Context, out chan\u003C- *query.Query) error {\n    handle, _ := pcap.OpenOffline(i.file)\n    packetSource := gopacket.NewPacketSource(handle, handle.LinkType())\n\n    for packet := range packetSource.Packets() {\n        \u002F\u002F ... TCP reassembly and MySQL protocol decoding logic\n        out \u003C- extractedQuery\n    }\n}\n",[57,4287,4288,4293,4335,4354,4374,4378,4386,4414,4427,4431],{"__ignoreMap":129},[133,4289,4290],{"class":135,"line":136},[133,4291,4292],{"class":176},"# From internal\u002Fcmd\u002Fquery-collector\u002Finput_pcap.go\n",[133,4294,4295,4298,4301,4303,4306,4309,4312,4315,4318,4321,4323,4326,4329,4332],{"class":135,"line":142},[133,4296,4297],{"class":757},"func",[133,4299,4300],{"class":182}," (i ",[133,4302,2493],{"class":188},[133,4304,4305],{"class":195},"InputPcap",[133,4307,4308],{"class":182},") StartExtractor(",[133,4310,4311],{"class":757},"ctx",[133,4313,4314],{"class":195}," context.Context,",[133,4316,4317],{"class":195}," out",[133,4319,4320],{"class":195}," chan",[133,4322,1209],{"class":1127},[133,4324,4325],{"class":195},"-",[133,4327,4328],{"class":188}," *",[133,4330,4331],{"class":195},"query.Query",[133,4333,4334],{"class":182},") error {\n",[133,4336,4337,4340,4342,4344,4347,4349,4352],{"class":135,"line":148},[133,4338,4339],{"class":757},"    handle,",[133,4341,2691],{"class":195},[133,4343,2300],{"class":195},[133,4345,4346],{"class":195}," pcap.OpenOffline",[133,4348,1308],{"class":182},[133,4350,4351],{"class":757},"i.file",[133,4353,2311],{"class":182},[133,4355,4356,4359,4361,4364,4366,4369,4372],{"class":135,"line":154},[133,4357,4358],{"class":757},"    packetSource",[133,4360,2300],{"class":195},[133,4362,4363],{"class":195}," gopacket.NewPacketSource",[133,4365,1308],{"class":182},[133,4367,4368],{"class":757},"handle,",[133,4370,4371],{"class":195}," handle.LinkType",[133,4373,2390],{"class":182},[133,4375,4376],{"class":135,"line":214},[133,4377,784],{"emptyLinePlaceholder":315},[133,4379,4380,4383],{"class":135,"line":227},[133,4381,4382],{"class":1127},"    for",[133,4384,4385],{"class":182}," packet := range packetSource.Packets() {\n",[133,4387,4388,4391,4394,4397,4400,4402,4405,4408,4411],{"class":135,"line":236},[133,4389,4390],{"class":757},"        \u002F\u002F",[133,4392,4393],{"class":195}," ...",[133,4395,4396],{"class":195}," TCP",[133,4398,4399],{"class":195}," reassembly",[133,4401,4061],{"class":195},[133,4403,4404],{"class":195}," MySQL",[133,4406,4407],{"class":195}," protocol",[133,4409,4410],{"class":195}," decoding",[133,4412,4413],{"class":195}," logic\n",[133,4415,4416,4419,4422,4424],{"class":135,"line":242},[133,4417,4418],{"class":757},"        out",[133,4420,4421],{"class":1127}," \u003C",[133,4423,4325],{"class":195},[133,4425,4426],{"class":195}," extractedQuery\n",[133,4428,4429],{"class":135,"line":255},[133,4430,282],{"class":182},[133,4432,4433],{"class":135,"line":268},[133,4434,294],{"class":182},[296,4436,4437],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":129,"searchDepth":142,"depth":142,"links":4439},[4440,4441,4442],{"id":13,"depth":142,"text":14},{"id":21,"depth":142,"text":22},{"id":71,"depth":142,"text":72,"children":4443},[4444,4445],{"id":76,"depth":148,"text":77},{"id":388,"depth":148,"text":389},"A high-performance load testing suite that captures real production traffic via PCAP analysis and replays it with weighted concurrency to accurately simulate database load.","https:\u002F\u002Fgithub.com\u002Fszuryuu\u002Fmysql-load-test","\u002Fimages\u002Fprojects\u002Fmysql.png",{},"\u002Fproject\u002Fmysql-load-test",{"title":4127,"description":4446},"project\u002Fmysql-load-test",[321,4454,1706,3339],"Gopacket","swZaS0GgHomHJY6mveSeGAbnlQ6N6qUpI2vilbsKtgI",{"id":4457,"title":4458,"body":4459,"description":4988,"duration":3330,"extension":309,"featured":310,"github":4989,"image":4990,"live":4991,"meta":4992,"navigation":315,"order":469,"path":4993,"role":313,"seo":4994,"status":318,"stem":4995,"team_size":313,"tech":4996,"type":326,"year":2168,"__hash__":4998},"projects\u002Fproject\u002Fnotori.md","Notori - Notes App",{"type":8,"value":4460,"toc":4980},[4461,4463,4466,4468,4481,4526,4528,4530,4535,4560,4565,4573,4575,4579,4582,4776,4780,4783,4978],[11,4462,14],{"id":13},[16,4464,4465],{},"Building complex user interfaces with \"Vanilla\" JavaScript often results in unmaintainable \"spaghetti code\" and global CSS conflicts. Developers need a way to encapsulate logic, templates, and styles strictly without the overhead of heavy frameworks like React or Vue for smaller applications.",[11,4467,22],{"id":21},[16,4469,2607,4470,4473,4474,951,4477,4480],{},[33,4471,4472],{},"Notori",", a Single Page Application (SPA) that leverages the browser's native ",[33,4475,4476],{},"Custom Elements API",[33,4478,4479],{},"Shadow DOM"," to create truly encapsulated UI components.",[27,4482,4483,4496,4506,4520],{},[30,4484,4485,4488,4489,951,4492,4495],{},[33,4486,4487],{},"Component-Based Architecture:"," Broken down into reusable custom elements like ",[57,4490,4491],{},"\u003Cnotes-list>",[57,4493,4494],{},"\u003Cnotes-item>",", ensuring style isolation and reusability.",[30,4497,4498,4501,4502,4505],{},[33,4499,4500],{},"Modern Tooling Pipeline:"," Configured a custom ",[33,4503,4504],{},"Webpack"," environment from scratch to handle asset bundling, CSS extraction, and ES6+ transpilation via Babel.",[30,4507,4508,4511,4512,4515,4516,4519],{},[33,4509,4510],{},"Interactive UX:"," Integrated ",[33,4513,4514],{},"SweetAlert2"," for modal dialogs and ",[33,4517,4518],{},"Anime.js"," for fluid micro-interactions, enhancing the native feel of the app.",[30,4521,4522,4525],{},[33,4523,4524],{},"Robust Data Layer:"," Implemented a dedicated API service layer to handle asynchronous CRUD operations with the backend.",[11,4527,72],{"id":71},[74,4529,77],{"id":76},[16,4531,4532],{},[33,4533,4534],{},"Why Web Components over React?",[27,4536,4537,4550],{},[30,4538,4539,4542,4543,951,4546,4549],{},[33,4540,4541],{},"Native Performance:"," By using ",[57,4544,4545],{},"HTMLElement",[57,4547,4548],{},"attachShadow({ mode: \"open\" })",", the app runs directly on browser standards with zero runtime overhead from a virtual DOM.",[30,4551,4552,4555,4556,4559],{},[33,4553,4554],{},"Scoped Styling:"," Each component, such as the note list, injects its own ",[57,4557,4558],{},"\u003Cstyle>"," tag into its Shadow Root. This guarantees that CSS written for the list grid never leaks out to affect other parts of the application.",[16,4561,4562],{},[33,4563,4564],{},"Why Custom Webpack Config?",[27,4566,4567],{},[30,4568,4569,4572],{},[33,4570,4571],{},"Control:"," Instead of using tools like CRA or Vite, I manually configured Webpack to understand the compilation process deeply—managing loaders for CSS, images, and Javascript modules explicitly.",[74,4574,389],{"id":388},[113,4576,4578],{"id":4577},"_1-encapsulated-grid-layout-shadow-dom","1. Encapsulated Grid Layout (Shadow DOM)",[16,4580,4581],{},"I implemented a responsive grid system that adapts its column count based on attributes, isolated entirely within the Shadow DOM.",[124,4583,4585],{"className":735,"code":4584,"language":737,"meta":129,"style":129},"# javacript\n# src\u002Fscript\u002Fcomponents\u002Fnotes-list.js\nclass NotesList extends HTMLElement {\n  constructor() {\n    super();\n    # Create a shadow root for style isolation\n    this._shadowRoot = this.attachShadow({ mode: \"open\" });\n  }\n\n  render() {\n    # Dynamic grid template based on 'column' property\n    this._style.textContent = `\n      .list {\n        display: grid;\n        grid-template-columns: ${\"1fr \".repeat(this._column).trim()};\n        gap: ${this._gutter}px;\n      }\n    `;\n    # ...\n  }\n}\ncustomElements.define(\"notes-list\", NotesList);\n",[57,4586,4587,4592,4597,4613,4620,4628,4633,4646,4650,4654,4661,4666,4676,4683,4691,4720,4738,4743,4750,4754,4758,4762],{"__ignoreMap":129},[133,4588,4589],{"class":135,"line":136},[133,4590,4591],{"class":176},"# javacript\n",[133,4593,4594],{"class":135,"line":142},[133,4595,4596],{"class":176},"# src\u002Fscript\u002Fcomponents\u002Fnotes-list.js\n",[133,4598,4599,4602,4605,4608,4611],{"class":135,"line":148},[133,4600,4601],{"class":757},"class",[133,4603,4604],{"class":195}," NotesList",[133,4606,4607],{"class":195}," extends",[133,4609,4610],{"class":195}," HTMLElement",[133,4612,1193],{"class":195},[133,4614,4615,4618],{"class":135,"line":154},[133,4616,4617],{"class":757},"  constructor",[133,4619,2075],{"class":182},[133,4621,4622,4625],{"class":135,"line":214},[133,4623,4624],{"class":757},"    super",[133,4626,4627],{"class":182},"();\n",[133,4629,4630],{"class":135,"line":227},[133,4631,4632],{"class":176},"    # Create a shadow root for style isolation\n",[133,4634,4635,4638,4640,4643],{"class":135,"line":236},[133,4636,4637],{"class":757},"    this._shadowRoot",[133,4639,1184],{"class":195},[133,4641,4642],{"class":195}," this.attachShadow",[133,4644,4645],{"class":182},"({ mode: \"open\" });\n",[133,4647,4648],{"class":135,"line":242},[133,4649,461],{"class":182},[133,4651,4652],{"class":135,"line":255},[133,4653,784],{"emptyLinePlaceholder":315},[133,4655,4656,4659],{"class":135,"line":268},[133,4657,4658],{"class":757},"  render",[133,4660,2075],{"class":182},[133,4662,4663],{"class":135,"line":279},[133,4664,4665],{"class":176},"    # Dynamic grid template based on 'column' property\n",[133,4667,4668,4671,4673],{"class":135,"line":285},[133,4669,4670],{"class":757},"    this._style.textContent",[133,4672,1184],{"class":195},[133,4674,4675],{"class":195}," `\n",[133,4677,4678,4681],{"class":135,"line":291},[133,4679,4680],{"class":757},"      .list",[133,4682,1193],{"class":195},[133,4684,4685,4688],{"class":135,"line":469},[133,4686,4687],{"class":757},"        display:",[133,4689,4690],{"class":195}," grid;\n",[133,4692,4693,4696,4699,4702,4704,4707,4709,4712,4714,4717],{"class":135,"line":475},[133,4694,4695],{"class":757},"        grid-template-columns:",[133,4697,4698],{"class":195}," ${\"1fr \".",[133,4700,4701],{"class":182},"repeat",[133,4703,1308],{"class":195},[133,4705,4706],{"class":182},"this",[133,4708,60],{"class":195},[133,4710,4711],{"class":182},"_column",[133,4713,1757],{"class":195},[133,4715,4716],{"class":182},"trim",[133,4718,4719],{"class":195},"()};\n",[133,4721,4722,4725,4728,4730,4732,4735],{"class":135,"line":481},[133,4723,4724],{"class":757},"        gap:",[133,4726,4727],{"class":195}," ${",[133,4729,4706],{"class":182},[133,4731,60],{"class":195},[133,4733,4734],{"class":182},"_gutter",[133,4736,4737],{"class":195},"}px;\n",[133,4739,4740],{"class":135,"line":487},[133,4741,4742],{"class":195},"      }\n",[133,4744,4745,4748],{"class":135,"line":493},[133,4746,4747],{"class":195},"    `",[133,4749,1971],{"class":182},[133,4751,4752],{"class":135,"line":498},[133,4753,2900],{"class":176},[133,4755,4756],{"class":135,"line":1124},[133,4757,461],{"class":182},[133,4759,4760],{"class":135,"line":1139},[133,4761,294],{"class":182},[133,4763,4764,4767,4770,4772,4774],{"class":135,"line":1145},[133,4765,4766],{"class":757},"customElements.define(",[133,4768,4769],{"class":757},"\"notes-list\"",[133,4771,2534],{"class":757},[133,4773,4604],{"class":195},[133,4775,3273],{"class":182},[113,4777,4779],{"id":4778},"_2-service-layer-pattern","2. Service Layer Pattern",[16,4781,4782],{},"To keep the UI components clean, I separated all network logic into a dedicated service module. This module handles error parsing and JSON serialization for the external Notes API.",[124,4784,4786],{"className":735,"code":4785,"language":737,"meta":129,"style":129},"# javascript\n# src\u002Fscript\u002Fdata\u002Fnotes-data-api.js\nconst addNotes = async (note) => {\n  try {\n    const response = await fetch(`${baseUrl}\u002Fnotes`, {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application\u002Fjson\" },\n      body: JSON.stringify(note),\n    });\n    # Centralized error handling\n    if (!response.ok) throw new Error(responseJson.message);\n    return responseJson.data;\n  } catch (error) {\n    console.error(\"Failed to add data:\", error);\n  }\n};\n",[57,4787,4788,4793,4798,4817,4824,4855,4863,4879,4896,4900,4905,4935,4944,4955,4970,4974],{"__ignoreMap":129},[133,4789,4790],{"class":135,"line":136},[133,4791,4792],{"class":176},"# javascript\n",[133,4794,4795],{"class":135,"line":142},[133,4796,4797],{"class":176},"# src\u002Fscript\u002Fdata\u002Fnotes-data-api.js\n",[133,4799,4800,4802,4805,4807,4810,4813,4815],{"class":135,"line":148},[133,4801,2419],{"class":757},[133,4803,4804],{"class":195}," addNotes",[133,4806,1184],{"class":195},[133,4808,4809],{"class":195}," async",[133,4811,4812],{"class":182}," (note) =",[133,4814,3171],{"class":1127},[133,4816,1193],{"class":182},[133,4818,4819,4822],{"class":135,"line":154},[133,4820,4821],{"class":757},"  try",[133,4823,1193],{"class":195},[133,4825,4826,4829,4832,4834,4837,4840,4842,4845,4848,4851,4853],{"class":135,"line":214},[133,4827,4828],{"class":757},"    const",[133,4830,4831],{"class":195}," response",[133,4833,1184],{"class":195},[133,4835,4836],{"class":195}," await",[133,4838,4839],{"class":195}," fetch",[133,4841,1308],{"class":182},[133,4843,4844],{"class":195},"`${",[133,4846,4847],{"class":182},"baseUrl",[133,4849,4850],{"class":195},"}\u002Fnotes`",[133,4852,2534],{"class":757},[133,4854,1193],{"class":195},[133,4856,4857,4860],{"class":135,"line":227},[133,4858,4859],{"class":757},"      method:",[133,4861,4862],{"class":195}," \"POST\",\n",[133,4864,4865,4868,4871,4874,4877],{"class":135,"line":236},[133,4866,4867],{"class":757},"      headers:",[133,4869,4870],{"class":195}," {",[133,4872,4873],{"class":195}," \"Content-Type\":",[133,4875,4876],{"class":195}," \"application\u002Fjson\"",[133,4878,1065],{"class":195},[133,4880,4881,4884,4887,4889,4892,4894],{"class":135,"line":242},[133,4882,4883],{"class":757},"      body:",[133,4885,4886],{"class":195}," JSON.stringify",[133,4888,1308],{"class":182},[133,4890,4891],{"class":757},"note",[133,4893,3603],{"class":182},[133,4895,199],{"class":195},[133,4897,4898],{"class":135,"line":255},[133,4899,3619],{"class":182},[133,4901,4902],{"class":135,"line":268},[133,4903,4904],{"class":176},"    # Centralized error handling\n",[133,4906,4907,4909,4911,4914,4917,4919,4922,4925,4928,4930,4933],{"class":135,"line":279},[133,4908,2363],{"class":1127},[133,4910,1265],{"class":182},[133,4912,4913],{"class":1127},"!",[133,4915,4916],{"class":757},"response.ok",[133,4918,1282],{"class":182},[133,4920,4921],{"class":757},"throw",[133,4923,4924],{"class":195}," new",[133,4926,4927],{"class":195}," Error",[133,4929,1308],{"class":182},[133,4931,4932],{"class":757},"responseJson.message",[133,4934,3273],{"class":182},[133,4936,4937,4939,4942],{"class":135,"line":285},[133,4938,3103],{"class":1127},[133,4940,4941],{"class":195}," responseJson.data",[133,4943,1971],{"class":182},[133,4945,4946,4949,4952],{"class":135,"line":291},[133,4947,4948],{"class":182},"  } catch (",[133,4950,4951],{"class":757},"error",[133,4953,4954],{"class":182},") {\n",[133,4956,4957,4960,4963,4965,4968],{"class":135,"line":469},[133,4958,4959],{"class":757},"    console.error(",[133,4961,4962],{"class":757},"\"Failed to add data:\"",[133,4964,2534],{"class":757},[133,4966,4967],{"class":195}," error",[133,4969,3273],{"class":182},[133,4971,4972],{"class":135,"line":475},[133,4973,461],{"class":182},[133,4975,4976],{"class":135,"line":481},[133,4977,1392],{"class":182},[296,4979,4100],{},{"title":129,"searchDepth":142,"depth":142,"links":4981},[4982,4983,4984],{"id":13,"depth":142,"text":14},{"id":21,"depth":142,"text":22},{"id":71,"depth":142,"text":72,"children":4985},[4986,4987],{"id":76,"depth":148,"text":77},{"id":388,"depth":148,"text":389},"A lightweight, modular note-taking application engineered with Native Web Components and Webpack to demonstrate framework-agnostic component architecture.","https:\u002F\u002Fgithub.com\u002Fszuryuu\u002Fnotori","\u002Fimages\u002Fprojects\u002Fjavascript.png","https:\u002F\u002Fnotori.vercel.app\u002F",{},"\u002Fproject\u002Fnotori",{"title":4458,"description":4988},"project\u002Fnotori",[4997,4504,4518],"JavaScript","eSBtj7BIXVxhcJ9cG6v0pqhKPUkmhQNLO_RM-0l_yK4",{"id":5000,"title":5001,"body":5002,"description":5337,"duration":5338,"extension":309,"featured":310,"github":5339,"image":3332,"live":313,"meta":5340,"navigation":315,"order":255,"path":5341,"role":313,"seo":5342,"status":318,"stem":5343,"team_size":313,"tech":5344,"type":326,"year":2168,"__hash__":5347},"projects\u002Fproject\u002Fsinvent.md","Sinvent - Inventory System",{"type":8,"value":5003,"toc":5329},[5004,5006,5009,5011,5017,5067,5069,5071,5076,5099,5104,5122,5124,5128,5135,5244,5248,5251,5326],[11,5005,14],{"id":13},[16,5007,5008],{},"Small to medium enterprises often struggle with disjointed inventory tracking methods—relying on spreadsheets that are prone to human error and lack real-time synchronization. They need a secure, scalable solution to manage products, monitor stock levels, and handle staff access without the complexity of enterprise ERP software.",[11,5010,22],{"id":21},[16,5012,2184,5013,5016],{},[33,5014,5015],{},"Sinvent",", a robust Full-Stack application designed to bridge the gap between usability and complex data management.",[27,5018,5019,5032,5046,5058],{},[30,5020,5021,5024,5025,5028,5029,5031],{},[33,5022,5023],{},"Decoupled Architecture:"," Built with a ",[33,5026,5027],{},"Laravel 12"," REST API backend and a ",[33,5030,1414],{}," Single Page Application (SPA) frontend, allowing for independent scaling and maintenance.",[30,5033,5034,5037,5038,5041,5042,5045],{},[33,5035,5036],{},"Secure Authentication:"," Implemented stateless authentication using ",[33,5039,5040],{},"JWT (JSON Web Tokens)"," via ",[57,5043,5044],{},"tymon\u002Fjwt-auth",", ensuring secure and scalable session management across devices.",[30,5047,5048,5051,5052,951,5054,5057],{},[33,5049,5050],{},"Modern UI\u002FUX:"," Crafted a responsive interface using ",[33,5053,1415],{},[33,5055,5056],{},"Shadcn Vue"," components, prioritizing accessibility and ease of use.",[30,5059,5060,4511,5063,5066],{},[33,5061,5062],{},"Data Visualization:",[33,5064,5065],{},"Chart.js"," to transform raw inventory data into actionable visual insights for administrators.",[11,5068,72],{"id":71},[74,5070,77],{"id":76},[16,5072,5073],{},[33,5074,5075],{},"Why Laravel 12 over Node.js?",[27,5077,5078,5093],{},[30,5079,5080,5083,5084,97,5087,3026,5090,60],{},[33,5081,5082],{},"Rapid API Development:"," Laravel's Eloquent ORM and API Resource classes allowed me to quickly scaffold complex relationships between ",[57,5085,5086],{},"Products",[57,5088,5089],{},"Inventories",[57,5091,5092],{},"Members",[30,5094,5095,5098],{},[33,5096,5097],{},"Stability:"," Leveraging Laravel 12's strict typing and built-in security features (like Sanctum\u002FJWT integration) reduced the boilerplate needed for secure endpoints.",[16,5100,5101],{},[33,5102,5103],{},"Why Vue 3 & Tailwind v4?",[27,5105,5106,5116],{},[30,5107,5108,5111,5112,5115],{},[33,5109,5110],{},"Composition API:"," I utilized Vue 3's Composition API to create reusable logic hooks for data fetching and state management, keeping components like ",[57,5113,5114],{},"InventoryView.vue"," clean.",[30,5117,5118,5121],{},[33,5119,5120],{},"Performance:"," Tailwind v4 brings a unified toolchain with faster compilation times, essential for maintaining a swift developer feedback loop.",[74,5123,389],{"id":388},[113,5125,5127],{"id":5126},"_1-secure-api-routes-resource-mapping","1. Secure API Routes & Resource Mapping",[16,5129,5130,5131,5134],{},"I designed the backend to expose a clean, RESTful API. The routes are protected by a custom ",[57,5132,5133],{},"auth:api"," middleware group, ensuring that sensitive operations like inventory adjustments are restricted to authenticated users only.",[124,5136,5138],{"className":735,"code":5137,"language":737,"meta":129,"style":129},"# php\n# be\u002Froutes\u002Fapi.php\nRoute::middleware(\"auth:api\")->group(function () {\n    Route::apiResource(\"\u002Fproducts\", ProductController::class);\n    Route::apiResource(\"\u002Fmembers\", MemberController::class);\n    Route::apiResource(\"\u002Finventories\", InventoryController::class);\n\n    # Custom endpoints for aggregated data\n    Route::get(\"\u002Fproducts\u002Fall\", [ProductController::class, \"all\"]);\n});\n",[57,5139,5140,5144,5149,5170,5185,5199,5213,5217,5222,5240],{"__ignoreMap":129},[133,5141,5142],{"class":135,"line":136},[133,5143,3065],{"class":176},[133,5145,5146],{"class":135,"line":142},[133,5147,5148],{"class":176},"# be\u002Froutes\u002Fapi.php\n",[133,5150,5151,5154,5157,5159,5161,5164,5167],{"class":135,"line":148},[133,5152,5153],{"class":757},"Route::middleware(",[133,5155,5156],{"class":757},"\"auth:api\"",[133,5158,3188],{"class":182},[133,5160,3171],{"class":1127},[133,5162,5163],{"class":182},"group(",[133,5165,5166],{"class":757},"function",[133,5168,5169],{"class":182}," () {\n",[133,5171,5172,5175,5178,5180,5183],{"class":135,"line":154},[133,5173,5174],{"class":757},"    Route::apiResource(",[133,5176,5177],{"class":757},"\"\u002Fproducts\"",[133,5179,2534],{"class":757},[133,5181,5182],{"class":195}," ProductController::class",[133,5184,3273],{"class":182},[133,5186,5187,5189,5192,5194,5197],{"class":135,"line":214},[133,5188,5174],{"class":757},[133,5190,5191],{"class":757},"\"\u002Fmembers\"",[133,5193,2534],{"class":757},[133,5195,5196],{"class":195}," MemberController::class",[133,5198,3273],{"class":182},[133,5200,5201,5203,5206,5208,5211],{"class":135,"line":227},[133,5202,5174],{"class":757},[133,5204,5205],{"class":757},"\"\u002Finventories\"",[133,5207,2534],{"class":757},[133,5209,5210],{"class":195}," InventoryController::class",[133,5212,3273],{"class":182},[133,5214,5215],{"class":135,"line":236},[133,5216,784],{"emptyLinePlaceholder":315},[133,5218,5219],{"class":135,"line":242},[133,5220,5221],{"class":176},"    # Custom endpoints for aggregated data\n",[133,5223,5224,5227,5230,5232,5235,5238],{"class":135,"line":255},[133,5225,5226],{"class":757},"    Route::get(",[133,5228,5229],{"class":757},"\"\u002Fproducts\u002Fall\"",[133,5231,2534],{"class":757},[133,5233,5234],{"class":182}," [ProductController::class, ",[133,5236,5237],{"class":195},"\"all\"]",[133,5239,3273],{"class":182},[133,5241,5242],{"class":135,"line":268},[133,5243,1121],{"class":182},[113,5245,5247],{"id":5246},"_2-reusable-component-library","2. Reusable Component Library",[16,5249,5250],{},"Instead of hardcoding UI elements, I built a library of atomic components (e.g., BaseTable, BaseCard, BaseAdd) in the frontend. This promotes consistency and drastically reduces code duplication across different views like MemberView and ProductView.",[124,5252,5254],{"className":735,"code":5253,"language":737,"meta":129,"style":129},"# javascript\n# Example structure from fe\u002Fsrc\u002Fcomponents\nimport BaseTable from '@\u002Fcomponents\u002FBaseTable.vue'\nimport BaseCard from '@\u002Fcomponents\u002FBaseCard.vue'\n\n# Used dynamically across views to render data grids\n\u003CBaseTable :data=\"inventoryData\" :columns=\"tableColumns\" \u002F>\n",[57,5255,5256,5260,5265,5279,5291,5295,5300],{"__ignoreMap":129},[133,5257,5258],{"class":135,"line":136},[133,5259,4792],{"class":176},[133,5261,5262],{"class":135,"line":142},[133,5263,5264],{"class":176},"# Example structure from fe\u002Fsrc\u002Fcomponents\n",[133,5266,5267,5270,5273,5276],{"class":135,"line":148},[133,5268,5269],{"class":757},"import",[133,5271,5272],{"class":195}," BaseTable",[133,5274,5275],{"class":195}," from",[133,5277,5278],{"class":195}," '@\u002Fcomponents\u002FBaseTable.vue'\n",[133,5280,5281,5283,5286,5288],{"class":135,"line":154},[133,5282,5269],{"class":757},[133,5284,5285],{"class":195}," BaseCard",[133,5287,5275],{"class":195},[133,5289,5290],{"class":195}," '@\u002Fcomponents\u002FBaseCard.vue'\n",[133,5292,5293],{"class":135,"line":214},[133,5294,784],{"emptyLinePlaceholder":315},[133,5296,5297],{"class":135,"line":227},[133,5298,5299],{"class":176},"# Used dynamically across views to render data grids\n",[133,5301,5302,5304,5307,5309,5312,5315,5317,5320,5323],{"class":135,"line":236},[133,5303,1209],{"class":1127},[133,5305,5306],{"class":182},"BaseTable :data",[133,5308,1294],{"class":1127},[133,5310,5311],{"class":195},"\"inventoryData\"",[133,5313,5314],{"class":757}," :columns",[133,5316,1294],{"class":195},[133,5318,5319],{"class":757},"\"tableColumns\"",[133,5321,5322],{"class":182}," \u002F",[133,5324,5325],{"class":1127},">\n",[296,5327,5328],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":129,"searchDepth":142,"depth":142,"links":5330},[5331,5332,5333],{"id":13,"depth":142,"text":14},{"id":21,"depth":142,"text":22},{"id":71,"depth":142,"text":72,"children":5334},[5335,5336],{"id":76,"depth":148,"text":77},{"id":388,"depth":148,"text":389},"A modern, decoupled inventory management system featuring secure JWT authentication, role-based access control, and interactive data visualization for real-time stock tracking.","5 days","https:\u002F\u002Fgithub.com\u002Fszuryuu\u002Fsinvent",{},"\u002Fproject\u002Fsinvent",{"title":5001,"description":5337},"project\u002Fsinvent",[5027,1414,3339,5345,5346],"Tailwind CSS","Shadcn","0QZI8TjOhtT4UurGWlV3mIZAGFJ-kQC8OexJHGyqwpY",1776582961931]