[{"content":"Halo, nama saya Aghits Nidallah 👋. Saya seorang {full-stack,mobile} developer, mahasiswa, dan {manusia,robot} yang hidup dengan menikmati kopi 24/7 ☕️.\n","date":"21 April 2026","externalUrl":null,"permalink":"/","section":"","summary":"","title":"","type":"page"},{"content":"","date":"21 April 2026","externalUrl":null,"permalink":"/series/a-year-of-macbook-pro-m4-pro/","section":"Series","summary":"","title":"A Year of MacBook Pro M4 Pro","type":"series"},{"content":" The whole series is going to take more than 1 hour read. So please brew a coffee, or a black tea, or anything that has caffeine to boost your mental clarity throughout this yaps that I\u0026rsquo;m about to spit.\nIntroduction # The MacBook Pro M4 Pro (yes, the \u0026ldquo;Pro\u0026rdquo;, twice), was one of the state-of-the-art devices within that period. Comparing all of the consumer laptops I\u0026rsquo;ve found, this alone is the most promising laptop to meet my needs.\n20 hours of battery life, big and blazing fast storage, undeniable CPU strength (oh, with Ray Tracing too. Although I haven\u0026rsquo;t used it for anything yet, unless for Minecraft), more Thunderbolt 5 ports, HDMI port (what I needed the most), and an SD Card reader.\nThis laptop was an upgrade from MacBook Pro M1 8/512 13\u0026quot; JP, I bought the Japanese version since I was broke at that time, but needed that performance for development purposes. Back then, before the MacBook itself, I used the ASUS X441UV, which after upgrades, I got 1TB of storage, 16G of RAM, and extra 512G of storage by using a caddy.\nMy ASUS laptop did fine since I was using Linux, but Docker killed it. Containers loaded slowly, builds took forever, and hot-reload during development was painfully slow.\nNow, until this date, I\u0026rsquo;ve been using the MacBook Pro M4 Pro 24/1TB 14\u0026quot;. This huge leap in RAM is based on the condition where my MBP M1 could run out of memory due to Docker containers usage. I once had 5 containers running and it crashed my M1 so bad it force-restarted. I monitored the RAM, and it used 32G of swap file. That\u0026rsquo;s when I knew I needed more RAM.\nMy expectations were met, great screen, tactile keyboard, big touchpad, crushing performance, and outstanding sound quality. It can run Docker, with 14 containers, across +30 services, without breaking a sweat.\nThe unboxing experience itself is mostly the same as other Apple products, that slow-falling anticipation until you see the product itself. Apple beats every other brand in terms of unboxing experience.\nThe Specs \u0026amp; What They Mean in Real Life # Hardware Configuration # M4 Pro chip starts at 12 CPU cores (up to 14) and 16 GPU cores (up to 20). Even though it has fewer cores than Intel\u0026rsquo;s Core i9 15th Gen (which has 24 cores), it still outperforms Intel in single-core performance, power efficiency, and integrated graphics.\nThis line of MacBook offers the RAM size of 24G and 48G of RAM, I specifically chose 24G of RAM since it meets my price budget and current workload. 48G of RAM is overkill, I don\u0026rsquo;t have workloads that push past 24G. I buy what I need now, not what I might need later. Future-proofing RAM is bullshit for development work unless you\u0026rsquo;re doing creative stuff like video editing.\nFor the storage itself, 1TB of storage already meets my criteria, development environment is mostly small files that pile up in dependencies. Laravel projects average about 200M for the initial codebase (Composer vendors + Node modules). There are no big files that eat up storage such as 4K video, or ProRAW formats that easily took up space.\n14\u0026quot; of Liquid Retina Display is what I need the most. The transition from 13\u0026quot; to 14\u0026quot; itself is not much, but it actually is BIG in terms of window space that I can actually work with. Before upgrading, I had to use Stage Manager (which sucks) all the time to switch between app windows. Now, with help of Rectangle app, I can split the window into smaller view and can still see the content clearly.\nI was considering buying the Nano-texture as well, but after watching some reviews, I decided not to. Why, you ask? Coding is mainly reading texts, Nano-texture ruins the clarity in trade of less glare when outdoor. But let me ask you this question, how often you see a developer doing their work outdoors? Rarely.\nThe last thing, the most important buying factor, PORTS!!! 3 Thunderbolt 5 ports 😭. More ports = more freedom. MBP M1 was hell, just 2 Thunderbolt ports without Magsafe, so I had to plug the dongle all the time in order to be able to charge, use my external keyboard and mouse, and external screen as well.\nPorts on the left side of the MBP M4 Pro: Ports on the right side of the MBP M4 Pro: This laptop comes with an extra Thunderbolt port, Magsafe port, HDMI port, and SD Card reader as well. So, at least my 3 problems are fixed within this upgrade. Now I don\u0026rsquo;t really need a dongle specifically for charging and external display itself. Thanks a lot, Apple, for crafting this masterpiece 😭🙏.\nBuild Quality \u0026amp; Design # Now, it is the time to be honest, no sugarcoating, no lies in between. After a year, despite being used for heavy work, it still does SOLID performance. No physical degradation, only normal wear and tear (I\u0026rsquo;ll provide the picture later) on my palm rest, a chip on the left Command keycap, and classic Staingate problem.\nPalm rest chip: Command key chip: Staingate is where there\u0026rsquo;s a permanent keyboard mark on your screen after prolonged use. It chips away your Mac\u0026rsquo;s anti-reflective coating. Although it still protects your screen perfectly fine, cosmetically, it makes you look like you can\u0026rsquo;t properly clean your Mac. It\u0026rsquo;s a problem all MacBook users\u0026rsquo; face, so don\u0026rsquo;t worry if you have it. You can make it as an achievement, like a \u0026ldquo;MacBook ownership\u0026rdquo; or \u0026ldquo;I used this MacBook all day and I\u0026rsquo;m proud of it\u0026rdquo; kind of thing.\nStaingate: I\u0026rsquo;m pretty much surprised for the weight of the laptop itself. IT\u0026rsquo;S SO HEAVY OMG MY BACK ACHE A LOT AFTER CARRYING IT A WHOLE DAY 😭. I mean, 3.4 pounds (1.55kg), man 😭. Turns out the material Apple has given to this majestic laptop is no-joke. Solid aluminum case for protection and weight distribution gave a toll on my back 😭. But yeah, it\u0026rsquo;s still light enough compared to heavy-ahh gaming laptops. $2,499, well spent for a real workstation, not for a portable gaming laptops with jet-engine fans 👀.\nThe keyboard itself is pretty solid. It feels like it\u0026rsquo;s on the borderline between the blue switch and the brown switch. It still has that tactile experience, but not as loud as blue switch, yet not that deep as brown switch. Man, how do I put this? Just buy one Mac and try it for yourself, for real. No other keyboard can match this exact feeling of typing on a Mac.\nAh yes, the trackpad. This is where Mac users stand out. There is no need to put your finger to the bottom left to left-click, and to the bottom right to right click. The whole area of the trackpad is your control. One finger to left click, two fingers to right click, three fingers to drag the window, and four fingers to swap between the virtual workspace. Mac trackpads are designed for gestures, learn them, it helps you a lot.\nMac Trackpad Gestures, based on article written by Dan Hinkcley on Maciverse: The screen 😱. For $2,499, Apple gives you a solid 3K, top-notch clarity. One downside, though, you\u0026rsquo;re left with a big-ahh notch on top of your screen (thanks for nothing on this one, Apple). For a sidenote, I use Atoll to make use of this unused space, it makes the notch to be usable and mimic like iPhone\u0026rsquo;s Dynamic Island.\nAtoll usage: Perhaps, Apple named it Retina Display since it genuinely lives up to the name. Other brands, please adopt this technology, I can\u0026rsquo;t move out from Apple because of this 😭.\nCoding \u0026amp; Development Work # Performance # Compared to the MBP M1, this MBP M4 Pro cut my development time in half. Back then, within the same project with 20+ Node modules (excluding their dependencies), it went from 12s to roughly 5.5s. To load non-production assets, it also gives the performance boost for roughly 40% faster. To be honest, that doesn\u0026rsquo;t seem like much\u0026hellip;until your Mac\u0026rsquo;s RAM fills up with Docker containers.\nDocker containers are a big challenge for every laptop. Laptops are meant to be portable, so most of the performance factors are chopped away. But not with MBP M4 Pro, it still gives you the performance needed for a heavy coding session. M1 can handle 6 microservices just fine, but while working with 6 monolithic, production-ready containers? It choked, performance dropped, and heat builds up.\nUsing this laptop alone, I\u0026rsquo;ve run 4 Docker containers + 1 Android VM + 1 iPhone VM + 1 Android device via ADB + Cursor all at once, without issues. The heat this machine produces is still manageable around 80°C on the CPU\u0026rsquo;s internal temperature (tested using Mac-Stats), feels like 40°C on the palm rest, within non air-conditioned environment. Could be lower within air-conditioned room.\nMost of the time, with that similar kind of workload, I\u0026rsquo;m using around 22G of my 24G RAM, with minimal swap (1G). At peak, I\u0026rsquo;ve pushed it to 23G RAM used with 16G of swap. Long live my MBP M4 Pro 😭.\nDevelopment Setup # My workspace setup: I\u0026rsquo;ve been talking a lot about my development work, but not the development setup itself. So, let\u0026rsquo;s talk about it, shall we?\nI\u0026rsquo;m currently using Cursor as my IDE, I have been using it since this App was released and I can\u0026rsquo;t get myself away from it. The power of AI + the familiarity of VSCode boosts my productivity by a lot. I could bring an MVP to pre-production and show it to a customer in under a week, and ship it within a month with the help of AI. The true work here is to review the code whether it passes your criteria or not. It feels like you\u0026rsquo;re the QA team that reviews your team\u0026rsquo;s code.\nOn Terminal side, ZSH for the win 🔥! Powered with Oh My Zsh + Powerlevel10k + iTerm, it\u0026rsquo;s a perfect Terminal.\nAh, the package manager. Surely you want to use Homebrew as your Package Manager, although I can\u0026rsquo;t find a package manager as perfect as Arch\u0026rsquo;s Pacman / AUR for Mac, but Homebrew itself does its job as a package manager.\nIn the MBP M4 era, I rarely stumble upon a package / app that still uses x86_64 architecture, but Rosetta 2 itself is needed for the OrbStack requirement. Perhaps it needs to emulate few of the remaining x86_64 containers that haven\u0026rsquo;t been updated to match the power of arm64. So, the only case I need Rosetta 2 is to emulate some of the containers (mostly legacy projects that I haven\u0026rsquo;t refactored yet).\nApproximately 2-3 years ago, the native ARM apps are barely exists. I remember that a customer service on iBox told me that \u0026ldquo;You might have to wait about 3-6 months or so, until native ARM apps are available. Until then, you might want to depends on Rosetta\u0026rdquo;. It doesn\u0026rsquo;t actually need to take that long, turns out developers love Apple, most of the native ARM apps are released within that month.\nNow, the Docker. I want to say that OrbStack \u0026gt; Docker Desktop. It is lightweight, runs Docker Engine, can run K8s as well. It does what Docker Desktop can do, but in its lightweight version. Basically, it\u0026rsquo;s designed to run fast on M-series chips, you can find the OrbStack vs Docker Desktop comparison here. The only downside for me for using OrbStack is the Windows support, but who uses Winbugs in 2026 for coding? Oh yeah, one of my teammates, he still games 😅.\nFor the Database GUI, I specifically use TablePlus. Out of many Database GUI out there, TablePlus is my man. Simple, clean, lightweight, supports multiple database drivers. How about the database clients? Oh, I use Docker containers to manage those clients. I wanted to focus one database = one project, not one database for multiple projects. More focus = more productivity.\nOther development tools such as:\nGit for codebase management, the most essential; APIDog for API testing tool; Obsidian for note-taking and brainstorming, and; Nautika for project management. Nautika is my app that you can use freely to manage your project or your workplace projects. It adopts multi-tenancy so you can invite others to your workspace or be invited to other people\u0026rsquo;s workspace. Also, it is gamified so it can boost your productivity by competing with others. Have a try here on https://nautika.shiroyuki.dev, I\u0026rsquo;ll be waiting for your feedback 👋.\nSpecific Tech Stacks # On the web development world, we heard about React a lot. It\u0026rsquo;s exactly what I\u0026rsquo;ve been doing for the past year, learning and implementing its best practice on React. Although I\u0026rsquo;m not really fluent in JavaScript, I think I did a pretty good job for building Nautika by using React. Other than the React itself, I use Laravel Blade for the front-end templating.\nFront-end builds are effortless on the M4 Pro. It builds really fast. On my local machine, building its production assets took 11s compared to my production server which needs 47s build. The server itself needs average of 4.7x build time compared to my MBP M4 Pro (screenshot provided below).\nBelow is the build time from MBP M4 Pro Below is the build time from Nautika\u0026rsquo;s production server As a context, my server is using 8x Intel(R) Xeon(R) CPU E5-2683 v4@2.10GHz, 8G of RAM and 4G of swap.\nFor the Back-end development, I use Laravel a lot. Yeah, at this point you guys are probably blabbing about how slow PHP is, and I can\u0026rsquo;t deny it, it is slow as a turtle. But it delivers faster for me and my team. So Laravel is the first thing that comes in mind when building an API, or monolithic apps. Attack me from all sides, but we have FrankenPHP which handles requests 5x faster and can lower average latency by half.\nHere\u0026rsquo;s a result of the benchmark from Medium post by Ivan Vulvoic. Back then, Laravel development on Mac was hell. No other tools besides XAMPP, which is buggy and you need to hope that your MySQL service won\u0026rsquo;t crash. Then we have Laravel Valet, a specifically designed tool for Laravel environment, faster but need a MySQL standalone installed on your machine. Then we have Laravel Herd, a significantly better Laravel development environment app, but needs Pro version to use other service beside the local hosting. Finally we have Laravel Sail, currently the best out there, yet immensely heavy since you need Docker to run it. Since MBP M4 Pro did a pretty good job on Docker, there\u0026rsquo;s no performance issue whatsoever. Apple FTW 🔥🔥🔥!\nNow, realistically speaking about the database performance, there\u0026rsquo;s a lot of factors of what we can consider \u0026ldquo;good\u0026rdquo; performance. Optimization is definitely needed for a big, enterprise-level of database. But, for your daily database, let\u0026rsquo;s say we have a table with ~25k rows on SELECT * FROM clause.\nOn MBP M4 Pro, 27k rows returned at 141ms. On the server, 27k rows returned at 2.427s. Let the numbers and the screenshot explains. I don\u0026rsquo;t need to provide any more context 😅.\nBattery Life During Coding # Now, a section we\u0026rsquo;ve all been waiting for. The battery life. Battery life on past Apple laptops before M-series was a joke. It was draining fast, slow, and crappy. But now, it lasts long, fast, and literally you won\u0026rsquo;t need a charging brick for a whole day.\nApple claims 20 hours of life, but real-world usage varies. The \u0026ldquo;one day long\u0026rdquo; premise is easily broken on how you do your job. In my case, battery life on average development work is about ~6h of battery life, compared to the normal ~14h of battery life mostly by writing, browsing, and YouTube videos. Docker is taking a toll on your battery, and I\u0026rsquo;m not sugarcoating it, it doesn\u0026rsquo;t last long enough on my machine with all of the utilities/services that I needed already installed.\nThis screenshot shows battery life graph from an intensive coding session from 11PM to 2AM unplugged, drained ~60% of battery, 3 Docker containers, 1 IDE, 1 browser with multiple tabs, and YouTube Music. Battery life might be extended if I killed unnecessary apps and close some irrelevant tabs. I worked with my laptop plugged most of the time, since I rarely use it on the go, I mostly docked this machine to a place where I can plug this to a power brick, a dongle for external keyboard and mouse, and an external monitor at WQHD 180hz. When unplugged, extra peripherals give me ~4h of battery life.\nExtra peripherals drain battery because the external display requires GPU power and the Mac must drive more pixels at 180Hz. This keeps the GPU active and prevents power-saving modes, which explains the drop from ~6h to ~4h battery life.\nThis screenshot shows battery health of 1 year of solid intensive coding usage. It drains from 100% to 92%. This screenshot shows detailed System Report of Power usage to show Cycle Count. I know that this heavy workload drains a lot of battery, so to keep my laptop in its pristine condition, I have to keep the charge cycle low, and maintain a healthy battery health. Within a year of usage, my Mac\u0026rsquo;s battery health dips into 92% from 100%. For a Mac, 10-15% degradation a year is pretty normal, it will degrade slowly on the second year afterwards.\nThis battery health feature is overall helpful, but many new Apple users seem to be concerned about. To be honest, you don\u0026rsquo;t really have to worry about it since that\u0026rsquo;s just how batteries age. Every electrical components within your electronics will eventually degrade, and to be extremely honest, most of the consumer laptops will degrade faster than Apple\u0026rsquo;s.\nThe one thing you should consider a lot is the battery life, not the battery health. Change the battery once you think it is draining faster on normal usage.\nAfterwards, and what will be covered in the next article # That\u0026rsquo;s all for this session, I\u0026rsquo;ll be with you on the next series within 1 week. Next, we\u0026rsquo;ll be talking about the gaming and my workspace. Stay tuned!\nThumbnail by Apple\n","date":"21 April 2026","externalUrl":null,"permalink":"/artikel/2026/04/21/a-year-of-macbook-pro-m4-pro/","section":"Artikel-artikel","summary":"","title":"A Year of MacBook Pro M4 Pro: The Specs, The Work, and Reality.","type":"artikel"},{"content":"","date":"21 April 2026","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"5 Februari 2026","externalUrl":null,"permalink":"/tags/article/","section":"Tags","summary":"","title":"Article","type":"tags"},{"content":"I was trying to update packages to fix security issues when I hit the classic dependency nightmare: laravel/pulse and power-components/livewire-powergrid needs livewire/livewire:^3, but I need to upgrade to livewire/livewire:^4. Composer refuses to budge, can\u0026rsquo;t update one without the other, can\u0026rsquo;t update both separately. Classic.\nThe Error # Here\u0026rsquo;s what happens when you try to update just Livewire:\n❯ sail composer require livewire/livewire:\u0026#39;^4\u0026#39; ./composer.json has been updated Running composer update livewire/livewire Loading composer repositories with package information Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - Root composer.json requires livewire/livewire ^4, found livewire/livewire[v4.0.0, ..., v4.1.2] but these were not loaded, likely because it conflicts with another require. Problem 2 - laravel/pulse is locked to version v1.4.0 and an update of this package was not requested. - laravel/pulse v1.4.0 requires livewire/livewire ^3.4.9 -\u0026gt; found livewire/livewire[v3.4.9, ..., v3.7.8] but it conflicts with your root composer.json require (^4). Problem 3 - power-components/livewire-powergrid is locked to version v6.7.7 and an update of this package was not requested. - power-components/livewire-powergrid v6.7.7 requires livewire/livewire ^3.7|^4.0 -\u0026gt; found livewire/livewire[v3.7.0, ..., v3.7.8, v4.0.0, ..., v4.1.2] but these were not loaded, likely because it conflicts with another require. Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions. Installation failed, reverting ./composer.json and ./composer.lock to their original content. Composer is basically saying: \u0026ldquo;I found the versions you want, but I refuse to install them because of locked dependencies.\u0026rdquo;\nThe Solution # Update all conflicting packages in a single command with the -W flag (with-all-dependencies):\n❯ sail composer require livewire/livewire:\u0026#39;^4.1\u0026#39; laravel/pulse:\u0026#39;^1.5\u0026#39; power-components/livewire-powergrid:\u0026#39;^6.7\u0026#39; -W ./composer.json has been updated Running composer update livewire/livewire laravel/pulse power-components/livewire-powergrid --with-all-dependencies Loading composer repositories with package information Updating dependencies Lock file operations: 0 installs, 3 updates, 0 removals - Upgrading doctrine/sql-formatter (1.5.2 =\u0026gt; 1.5.3) - Upgrading laravel/pulse (v1.4.0 =\u0026gt; v1.5.0) - Upgrading livewire/livewire (v3.7.8 =\u0026gt; v4.1.2) Writing lock file Installing dependencies from lock file (including require-dev) Package operations: 0 installs, 3 updates, 0 removals - Downloading livewire/livewire (v4.1.2) - Downloading doctrine/sql-formatter (1.5.3) - Downloading laravel/pulse (v1.5.0) - Upgrading livewire/livewire (v3.7.8 =\u0026gt; v4.1.2): Extracting archive - Upgrading doctrine/sql-formatter (1.5.2 =\u0026gt; 1.5.3): Extracting archive - Upgrading laravel/pulse (v1.4.0 =\u0026gt; v1.5.0): Extracting archive Generating optimized autoload files \u0026gt; Illuminate\\Foundation\\ComposerScripts::postAutoloadDump \u0026gt; @php artisan package:discover --ansi INFO Discovering packages. barryvdh/laravel-debugbar ...................................................................................................... DONE cmixin/business-day ............................................................................................................ DONE laravel/boost .................................................................................................................. DONE laravel/breeze ................................................................................................................. DONE laravel/mcp .................................................................................................................... DONE laravel/pail ................................................................................................................... DONE laravel/pulse .................................................................................................................. DONE laravel/roster ................................................................................................................. DONE laravel/sail ................................................................................................................... DONE laravel/sanctum ................................................................................................................ DONE laravel/telescope .............................................................................................................. DONE laravel/tinker ................................................................................................................. DONE laravolt/avatar ................................................................................................................ DONE laravolt/indonesia ............................................................................................................. DONE livewire/livewire .............................................................................................................. DONE nesbot/carbon .................................................................................................................. DONE nunomaduro/collision ........................................................................................................... DONE nunomaduro/termwind ............................................................................................................ DONE opcodesio/log-viewer ........................................................................................................... DONE owen-it/laravel-auditing ....................................................................................................... DONE power-components/livewire-powergrid ............................................................................................ DONE tightenco/ziggy ................................................................................................................ DONE 98 packages you are using are looking for funding. Use the `composer fund` command to find out more! \u0026gt; @php artisan vendor:publish --tag=laravel-assets --ansi --force INFO No publishable resources for tag [laravel-assets]. Found 1 security vulnerability advisory affecting 1 package. Run \u0026#34;composer audit\u0026#34; for a full list of advisories. That\u0026rsquo;s it. By specifying all the interdependent packages together and using -W, Composer resolves everything in one go.\nKey Takeaway # When you hit circular dependency conflicts, don\u0026rsquo;t try updating packages one by one. List all the conflicting packages in a single composer require command with the -W flag. It forces Composer to resolve the entire dependency tree at once instead of fighting you at every step.\n(Oh, and that remaining security vulnerability? It\u0026rsquo;s in dev dependencies. I\u0026rsquo;ll fix it later. Priorities, right?)\n","date":"5 Februari 2026","externalUrl":null,"permalink":"/artikel/2026/02/05/fixing-composers-circular-dependency-hell/","section":"Artikel-artikel","summary":"","title":"Fixing Composer's Circular Dependency Hell","type":"artikel"},{"content":"","date":"5 Februari 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"For years, I\u0026rsquo;ve been fighting to get Sidecar-like functionality between my Mac and Android tablet. Spoiler: it\u0026rsquo;s been absolute hell.\nI\u0026rsquo;ve tried everything. Duet, Xdisplay, MirrCast, AirDroid Cast, AirScreen, you name it. Every single one disappointed me. They\u0026rsquo;re either locked behind subscriptions, riddled with ads, or just don\u0026rsquo;t work. And when they do work, the configurability is a joke.\nI was fed up. There was no cheap, reliable option unless you\u0026rsquo;re a networking wizard or you wait for someone else to solve it. Well, it\u0026rsquo;s 2026, and I finally found the solution.\nEnter Sunshine # LizardByte/Sunshine Self-hosted game stream host for Moonlight. C\u0026#43;\u0026#43; 36239 1853 Sunshine is a game streaming host for Moonlight that supports hardware encoding across AMD, Intel, Nvidia, and Apple Silicon GPUs. While it\u0026rsquo;s designed for game streaming, I realized it could be repurposed for display extension, and honestly, it works better than any dedicated screen-sharing app I\u0026rsquo;ve tried.\nInstalling Sunshine # Warning! You need Homebrew installed first. Installation is dead simple. Open your terminal and run:\nbrew update brew tap LizardByte/homebrew brew install sunshine This updates your Homebrew formulae, adds the LizardByte tap (think of it like a PPA on Ubuntu or AUR on Arch), and installs Sunshine.\nRunning Sunshine # Option 1: As a background service\nIf you want Sunshine running all the time:\nbrew services start lizardbyte/homebrew/sunshine This starts Sunshine as a background service that launches automatically on login.\nOption 2: On-demand (my preference)\nI don\u0026rsquo;t need this running 24/7, so I run it manually:\n/opt/homebrew/opt/sunshine/bin/sunshine $HOME/.config/sunshine/sunshine.conf That command is way too long to type repeatedly, so I aliased it in my .zshrc:\n# .zshrc alias sunshine=\u0026#34;/opt/homebrew/opt/sunshine/bin/sunshine $HOME/.config/sunshine/sunshine.conf\u0026#34; Now I just type sunshine whenever I need it.\nAccessing Sunshine\u0026rsquo;s Dashboard # Once running, navigate to https://localhost:47990 in your browser.\nOn first launch, you\u0026rsquo;ll create a username and password. After submitting, you\u0026rsquo;ll need to authenticate again through your browser\u0026rsquo;s login dialog (yes, twice, it\u0026rsquo;s annoying but necessary).\nNow you\u0026rsquo;re in the dashboard. But we\u0026rsquo;re not done yet.\nInstalling Moonlight on Your Android Device # Straightforward: grab Moonlight from the Play Store. I\u0026rsquo;m using a Xiaomi Pad 6, but any Android tablet should work.\nWe\u0026rsquo;ll come back to the Android side later. First, we need to set up a virtual display on MacOS.\nCreating a Virtual Display # You could use an HDMI dummy plug to trick MacOS into thinking there\u0026rsquo;s a monitor connected, but there\u0026rsquo;s an easier way: BetterDisplay.\nwaydabber/BetterDisplay Unlock your displays on your Mac! Flexible HiDPI scaling, XDR/HDR extra brightness, virtual screens, DDC control, extra dimming, PIP/streaming, EDID override and lots more! null 31277 590 After installing BetterDisplay, click its icon in the Menu Bar, navigate to Tools, and select Create New Virtual Display.\nYou\u0026rsquo;ll see this prompt:\nLeave the defaults and click Create Virtual Screen. Then toggle Connect to this virtual screen to on. This activates the virtual display so you can configure it.\nConnecting the Virtual Display to Sunshine # Find Your Display ID # You need the virtual display\u0026rsquo;s ID. In BetterDisplay, click Display Information for your virtual screen.\nScroll down to find the CGDirectDisplayID value. Mine is 7. Yours will likely be different.\nConfigure Sunshine # Open Sunshine\u0026rsquo;s Dashboard at https://localhost:47990 Go to Configuration → Audio/Video Set config.output_name_unix to your CGDirectDisplayID (mine is 7) Save That\u0026rsquo;s it. Sunshine now knows which display to stream.\nPairing Your Tablet # Warning! Both devices must be on the same network. If you\u0026rsquo;re on different networks, use Tailscale. Open Moonlight on your tablet.\nTap the (+) button to add a PC. You\u0026rsquo;ll need your Mac\u0026rsquo;s IP address.\nTo find your IP: hold Option and click the Wi-Fi icon in your Mac\u0026rsquo;s menu bar. Your IP address will be listed there.\nEnter the IP in Moonlight and tap OK.\nIf it finds your Mac, you\u0026rsquo;ll see it on the homepage.\nTap your Mac, and Moonlight will display a PIN.\nBack on your Mac, open Sunshine\u0026rsquo;s Dashboard and go to the PIN section.\nEnter the PIN and device name, then click Send.\nOnce paired, you\u0026rsquo;ll see Desktop as an option in Moonlight.\nTap Desktop, and boom. You\u0026rsquo;ve got a second display. Here\u0026rsquo;s my setup while writing this:\nAnd here\u0026rsquo;s how I arranged my screens:\nFinal Thoughts # This setup delivers the best Sidecar-like experience I\u0026rsquo;ve had between Mac and Android. The latency is genuinely impressive, around 40ms, which blows every paid screen-extension app out of the water.\nIs it perfect? No. I wouldn\u0026rsquo;t want to do heavy work on the extended display. It\u0026rsquo;s best for static content or background apps. But for those moments when I\u0026rsquo;m running out of screen real estate? This solution is a lifesaver.\nAfter years of frustration with overpriced, underperforming apps, I finally have something that actually works. And it\u0026rsquo;s completely free.\n","date":"27 Januari 2026","externalUrl":null,"permalink":"/artikel/2026/01/27/turning-my-unused-tablet-into-a-second-display/","section":"Artikel-artikel","summary":"","title":"Turning My Unused Tablet Into a Second Display Using Sunshine/Moonlight on MacOS","type":"artikel"},{"content":"This team lineup is based on my YouTube video, which has been published at July 13th 2025. Enjoy!\nSotheby # Overview # Euphoria # Psychube # Resonance # Jessica # Overview # Euphoria # Psychube # Resonance # Willow # Overview # Psychube # Resonance # Tuesday # Overview # Psychube # Resonance # Mountain Ghost # Mind Map # Edit (09-10-2025): I guess I won\u0026rsquo;t be uploading any Reverse 1999 or any blog sooner or later. The workload is too immense.\n","date":"15 Juli 2025","externalUrl":null,"permalink":"/r99/mountain-ghost-sp2-lineup/","section":"R99s","summary":"","title":"R99: Mountain Ghost SP2 Team Lineup","type":"r99"},{"content":"","date":"15 Juli 2025","externalUrl":null,"permalink":"/r99/","section":"R99s","summary":"","title":"R99s","type":"r99"},{"content":"","date":"15 Juli 2025","externalUrl":null,"permalink":"/tags/reverse-1999/","section":"Tags","summary":"","title":"Reverse: 1999","type":"tags"},{"content":"Koleksi jepretan foto yang diambil secara random, sebagai media penyalur hobi fotografi.\nTahun 2025 # Galeri-galeri Tahun Sebelumnya # 2024 2023 2022 2021 ","date":"14 April 2025","externalUrl":null,"permalink":"/galeri/","section":"Galeri","summary":"","title":"Galeri","type":"galeri"},{"content":"","date":"29 Maret 2025","externalUrl":null,"permalink":"/tags/bash/","section":"Tags","summary":"","title":"Bash","type":"tags"},{"content":"","date":"29 Maret 2025","externalUrl":null,"permalink":"/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":"Bash adalah sebuah Command-line Interpreter yang digunakan secara umum pada sistem operasi berbasis Unix-like, seperti Linux dan MacOS. Yang memungkinkan pengguna berinteraksi dengan sisten dengan cara mengotomatisasi tugas-tugas yang ingin diselesaikan melalui sebuah perintah.\nKalau Anda tertarik dengan latar belakang kenapa Saya memutuskan untuk menggunakan Docker dan meninggalkan sepenuhnya menginstal aplikasi secara langsung ke sistem, silahkan baca bagian Akar Permasalahan di bawah. Jika tidak, lewati bagian tersebut.\nAkar Permasalahan # Semenjak November 2024, Saya sudah tidak menggunakan coding environment yang diinstal ke mesin secara langsung. Mengapa? Hal ini didasari karena Homebrew memiliki package versioning management yang buruk. Kok bisa? Karena pada dasarnya Apple menganut Planned Obsolescence, yaitu dimana sebuah produk tidak akan mendapatkan fitur-fitur terbaru lagi setelah menyentuh versi tertentu. Ternyata hal ini tidak berlaku di hardware-nya saja, namun software-nya juga.\nWalaupun, masalah di atas dapat diselesaikan dengan mudah dengan menggunakan flag HOMEBREW_NO_AUTO_UPDATE=1, namun yang namanya manusia pasti tidak luput dari sifat pelupa. Terlebih lagi pada saat itu, tidak terlintas di kepala Saya untuk meng-export flag ini ke konfigurasi zsh Saya. Alhasil, alih-alih saya ingin meng-upgrade satu package saja, ternyata seluruh package ter-upgrade tanpa sepengetahuan Saya (kesalahan Saya juga tidak memonitor jalannya upgrade, package apa saja yang ter-upgrade, dan apa akibatnya). Salah satu akibat dari kelalaian ini sebetulnya simpel, yaitu naiknya versi MySQL dari versi 80300 ke versi 90001.\n\u0026ldquo;Lho, kan hanya upgrade versi saja, memang se-fatal apa sampai harus pindah ke Docker?\u0026rdquo;, tentunya sangat fatal. Karena jika dilihat dari error-nya sendiri, dia menampilkan Invalid MySQL server upgrade: Cannot upgrade from 80300 to 90001. Upgrade to next major version is only allowed from the last LTS release, which version 80300 is not.. Waduh artinya tuh apa bang Aghits? Artinya adalah engine dari MySQL mengenal bahwa database yang Saya miliki ternyata menggunakan versi 80300, sementara versi MySQL sekarang adalah 90001.\n\u0026ldquo;Kan tinggal di-downgrade saja versinya, apa susahnya?\u0026rdquo;, tidak semudah itu, karena pada dasarnya MySQL tidak mendukung Backward Compatibility, yang berarti perubahan versi major akan mengakibatkan perubahan secara fundamental pada database yang ada saat itu, dan pada saat mengembalikannya ke versi lama, terdapat beberapa variabel yang tidak dikenal sehingga mengakibatkan database tidak dapat dibaca. Seluruh perubahan fundamental akan tercatat pada ibdata1.\nKasus ini sudah terjadi tepatnya sebanyak 4x sepanjang karir pengembangan sistem Saya, dan pada akhirnya I had enough of this sh*t dan memulai perubahan radikal kepada seluruh aplikasi yang Saya kembangkan.\nDocker, dan Permasalahannya # Honestly, Docker is Amazing # Docker, Saya akui adalah suatu alat penunjang pengembangan yang powerful, karena dengan perintah yang simpel, aplikasi Anda dapat dijalankan tanpa menginstal satupun dependencies. Docker sangat bergantung kepada sebuah Dockerfile atau docker-compose, yang didalamnya terdapat perintah seperti berikut (Saya ambil contoh ini dari Laravel Sail):\nservices: laravel.test: build: context: ./vendor/laravel/sail/runtimes/8.4 dockerfile: Dockerfile args: WWWGROUP: \u0026#39;${WWWGROUP}\u0026#39; image: sail-8.4/app extra_hosts: - \u0026#39;host.docker.internal:host-gateway\u0026#39; ports: - \u0026#39;${APP_PORT:-80}:80\u0026#39; - \u0026#39;${VITE_PORT:-5173}:${VITE_PORT:-5173}\u0026#39; environment: WWWUSER: \u0026#39;${WWWUSER}\u0026#39; LARAVEL_SAIL: 1 XDEBUG_MODE: \u0026#39;${SAIL_XDEBUG_MODE:-off}\u0026#39; XDEBUG_CONFIG: \u0026#39;${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}\u0026#39; IGNITION_LOCAL_SITES_PATH: \u0026#39;${PWD}\u0026#39; volumes: - \u0026#39;.:/var/www/html\u0026#39; networks: - sail depends_on: - mysql mysql: image: \u0026#39;mysql/mysql-server:8.0\u0026#39; ports: - \u0026#39;${FORWARD_DB_PORT:-3306}:3306\u0026#39; environment: MYSQL_ROOT_PASSWORD: \u0026#39;${DB_PASSWORD}\u0026#39; MYSQL_ROOT_HOST: \u0026#39;%\u0026#39; MYSQL_DATABASE: \u0026#39;${DB_DATABASE}\u0026#39; MYSQL_USER: \u0026#39;${DB_USERNAME}\u0026#39; MYSQL_PASSWORD: \u0026#39;${DB_PASSWORD}\u0026#39; MYSQL_ALLOW_EMPTY_PASSWORD: 1 volumes: - \u0026#39;sail-mysql:/var/lib/mysql\u0026#39; - \u0026#39;./vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh\u0026#39; networks: - sail healthcheck: test: - CMD - mysqladmin - ping - \u0026#39;-p${DB_PASSWORD}\u0026#39; retries: 3 timeout: 5s networks: sail: driver: bridge volumes: sail-mysql: driver: local Dan pada saat menjalankan perintah ./vendor/bin/sail up -d untuk pertama kalinya, Docker akan menjalankan sebuah Dockerfile berdasarkan versi PHP yang secara default terpasang pada mesin. Contohnya jika versi PHP default Saya adalah PHP 8.4, maka Laravel Sail akan menjalankan Dockerfile yang ada pada directory vendor/laravel/sail/runtimes/8.4/Dockerfile:\nFROM ubuntu:24.04 LABEL maintainer=\u0026#34;Taylor Otwell\u0026#34; ARG WWWGROUP ARG NODE_VERSION=22 ARG MYSQL_CLIENT=\u0026#34;mysql-client\u0026#34; ARG POSTGRES_VERSION=17 WORKDIR /var/www/html ENV DEBIAN_FRONTEND=noninteractive ENV TZ=UTC ENV SUPERVISOR_PHP_COMMAND=\u0026#34;/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80\u0026#34; ENV SUPERVISOR_PHP_USER=\u0026#34;sail\u0026#34; RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \u0026amp;\u0026amp; echo $TZ \u0026gt; /etc/timezone RUN echo \u0026#34;Acquire::http::Pipeline-Depth 0;\u0026#34; \u0026gt; /etc/apt/apt.conf.d/99custom \u0026amp;\u0026amp; \\ echo \u0026#34;Acquire::http::No-Cache true;\u0026#34; \u0026gt;\u0026gt; /etc/apt/apt.conf.d/99custom \u0026amp;\u0026amp; \\ echo \u0026#34;Acquire::BrokenProxy true;\u0026#34; \u0026gt;\u0026gt; /etc/apt/apt.conf.d/99custom RUN apt-get update \u0026amp;\u0026amp; apt-get upgrade -y \\ \u0026amp;\u0026amp; mkdir -p /etc/apt/keyrings \\ \u0026amp;\u0026amp; apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \\ \u0026amp;\u0026amp; curl -sS \u0026#39;https://keyserver.ubuntu.com/pks/lookup?op=get\u0026amp;search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6\u0026#39; | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg \u0026gt; /dev/null \\ \u0026amp;\u0026amp; echo \u0026#34;deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main\u0026#34; \u0026gt; /etc/apt/sources.list.d/ppa_ondrej_php.list \\ \u0026amp;\u0026amp; apt-get update \\ \u0026amp;\u0026amp; apt-get install -y php8.4-cli php8.4-dev \\ php8.4-pgsql php8.4-sqlite3 php8.4-gd \\ php8.4-curl php8.4-mongodb \\ php8.4-imap php8.4-mysql php8.4-mbstring \\ php8.4-xml php8.4-zip php8.4-bcmath php8.4-soap \\ php8.4-intl php8.4-readline \\ php8.4-ldap \\ php8.4-msgpack php8.4-igbinary php8.4-redis php8.4-swoole \\ php8.4-memcached php8.4-pcov php8.4-imagick php8.4-xdebug \\ \u0026amp;\u0026amp; curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \\ \u0026amp;\u0026amp; curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \\ \u0026amp;\u0026amp; echo \u0026#34;deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main\u0026#34; \u0026gt; /etc/apt/sources.list.d/nodesource.list \\ \u0026amp;\u0026amp; apt-get update \\ \u0026amp;\u0026amp; apt-get install -y nodejs \\ \u0026amp;\u0026amp; npm install -g npm \\ \u0026amp;\u0026amp; npm install -g pnpm \\ \u0026amp;\u0026amp; npm install -g bun \\ \u0026amp;\u0026amp; curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg \u0026gt;/dev/null \\ \u0026amp;\u0026amp; echo \u0026#34;deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main\u0026#34; \u0026gt; /etc/apt/sources.list.d/yarn.list \\ \u0026amp;\u0026amp; curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg \u0026gt;/dev/null \\ \u0026amp;\u0026amp; echo \u0026#34;deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main\u0026#34; \u0026gt; /etc/apt/sources.list.d/pgdg.list \\ \u0026amp;\u0026amp; apt-get update \\ \u0026amp;\u0026amp; apt-get install -y yarn \\ \u0026amp;\u0026amp; apt-get install -y $MYSQL_CLIENT \\ \u0026amp;\u0026amp; apt-get install -y postgresql-client-$POSTGRES_VERSION \\ \u0026amp;\u0026amp; apt-get -y autoremove \\ \u0026amp;\u0026amp; apt-get clean \\ \u0026amp;\u0026amp; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* RUN setcap \u0026#34;cap_net_bind_service=+ep\u0026#34; /usr/bin/php8.4 RUN userdel -r ubuntu RUN groupadd --force -g $WWWGROUP sail RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail COPY start-container /usr/local/bin/start-container COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf COPY php.ini /etc/php/8.4/cli/conf.d/99-sail.ini RUN chmod +x /usr/local/bin/start-container EXPOSE 80/tcp ENTRYPOINT [\u0026#34;start-container\u0026#34;] Memang seluruh perintah-perintah di atas sangatlah panjang, dan untuk pengembang awam seperti Saya, Saya tidak perlu untuk mengoprek dengan jauh Dockerfile ataupun docker-compose.yml di atas. Sehingga memudahkan Saya untuk menjalankan aplikasi Laravel tanpa menginstal alat penunjang apapun.\nTapi\u0026hellip; # Dengan banyaknya proyek Laravel yang Saya handle, dan karena kemalasan Saya menggunakan CLI untuk mengakses Database CLI, maka Saya menggunakan aplikasi TablePlus (tidak disponsori) untuk mengakses Database-database yang Saya miliki. Sayangnya, Saya harus mengetahui IP dari Container Database yang ada pada aplikasi tersebut, dan yang bikin jengkel, IPnya kadang bisa berubah-ubah. Makanya setiap ada perubahan IP, Saya pasti tidak dapat mengakses Database tersebut secara langsung, jadi konfigurasi koneksinya harus Saya edit dulu, simpan, dan coba koneksikan kembali.\nUntuk bisa mengakses database itu kembali, Saya harus melakukan (n) tahap, yaitu:\nMenjalankan docker network ls untuk melihat seluruh jaringan yang terdaftar pada Docker. Output dari perintah tersebut adalah seperti ini: NETWORK ID NAME DRIVER SCOPE 1fedbf85b3f6 network_app_defaul bridge local 0e17478e344d bridge bridge local 535cf0ebaf20 laravel-12-base_sail bridge local Setelah mengetahui nama jaringan yang ingin Saya ketahui, Saya harus menjalankan docker network inspect {nama_jaringan} untuk mendapatkan informasi seperti berikut: [ { \u0026#34;Name\u0026#34;: \u0026#34;laravel-12-base_sail\u0026#34;, \u0026#34;Id\u0026#34;: \u0026#34;73900e13db161435016d83567ac1794995066fd106ff83b859e6d9b4f3fe3f22\u0026#34;, \u0026#34;Created\u0026#34;: \u0026#34;2025-03-29T04:58:40.95258208+07:00\u0026#34;, \u0026#34;Scope\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;Driver\u0026#34;: \u0026#34;bridge\u0026#34;, \u0026#34;EnableIPv6\u0026#34;: false, \u0026#34;IPAM\u0026#34;: { \u0026#34;Driver\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;Options\u0026#34;: null, \u0026#34;Config\u0026#34;: [ { \u0026#34;Subnet\u0026#34;: \u0026#34;192.168.181.0/24\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;192.168.181.1\u0026#34; } ] }, \u0026#34;Internal\u0026#34;: false, \u0026#34;Attachable\u0026#34;: false, \u0026#34;Ingress\u0026#34;: false, \u0026#34;ConfigFrom\u0026#34;: { \u0026#34;Network\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;ConfigOnly\u0026#34;: false, \u0026#34;Containers\u0026#34;: { \u0026#34;6a0072bf005951cc2a6f2ba9508f3c8553ef78235ae2067fce3d894d233f2312\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;laravel-12-base-mysql-1\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;5db0899761425c3247e6e5fca75a2edd6c258f9a0fa333a24ee5f837ac3e664b\u0026#34;, \u0026#34;MacAddress\u0026#34;: \u0026#34;02:42:c0:a8:b5:02\u0026#34;, \u0026#34;IPv4Address\u0026#34;: \u0026#34;192.168.181.2/24\u0026#34;, \u0026#34;IPv6Address\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;ef690e4080e6d3b63da5bdcd971363ca465cd91f1b3d7fb994c8280c0baa67aa\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;laravel-12-base-laravel.test-1\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;6944ea98448d54c455b5fd4a8852704f611b86b83cf1f4899c120f9e8dee0563\u0026#34;, \u0026#34;MacAddress\u0026#34;: \u0026#34;02:42:c0:a8:b5:03\u0026#34;, \u0026#34;IPv4Address\u0026#34;: \u0026#34;192.168.181.3/24\u0026#34;, \u0026#34;IPv6Address\u0026#34;: \u0026#34;\u0026#34; } }, \u0026#34;Options\u0026#34;: {}, \u0026#34;Labels\u0026#34;: { \u0026#34;com.docker.compose.config-hash\u0026#34;: \u0026#34;6e1770cb34d87dd58ce89c8ea5e219088a0a3caec213449c223c51ec5cf5677c\u0026#34;, \u0026#34;com.docker.compose.network\u0026#34;: \u0026#34;sail\u0026#34;, \u0026#34;com.docker.compose.project\u0026#34;: \u0026#34;laravel-12-base\u0026#34;, \u0026#34;com.docker.compose.version\u0026#34;: \u0026#34;2.32.4\u0026#34; } } ] \u0026ldquo;Lalu apa yang harus lu lihat dari informasi ini, Ghits?\u0026rdquo;, tentunya pada key Containers. Pada setiap proyek Laravel, Laravel Sail akan secara default membuat 2 container, yaitu: App Container, yang membungkus aplikasi Laravel, yang diberi label dengan format {nama-folder}-laravel.test; serta Database Container, yang (tentunya) membungkus database, yand diberi label dengan format {nama-folder}-mysql-(n). Lalu, dengan informasi kedua Container di atas, Saya harus mencopas IP-nya ke TablePlus. Voila, kelar seluruh masalah. Mempersingkat Manual Labour Work, sebuah effort, dalam kulit kacang. # Dengan melakukan seluruh step-by-step di atas, Tentunya ada rasa malas tersendiri, tiap hari harus inspeksi alamat IP-nya dulu lalu ubah IP-nya lagi di TablePlus. \u0026ldquo;Ada nggak sih cara lebih gampangnya?\u0026rdquo; yang tiba-tiba terbesit ke kepala Saya pada pukul 03:33 WIB, tepat waktu Sahur (puasa produktif coy).\n1. Ambil seluruh jaringan yang terdaftar # Pertama, Saya harus ambil dulu seluruh jaringan Docker yang terdaftar dengan menggunakan perintah docker network ls tadi.\n#!/bin/bash docker network ls Karena perintah di atas \u0026ldquo;sudah pasti jalan\u0026rdquo; karena perintahnya simpel, dan dikenal juga, tentunya nggak ada error. Dan tentunya bodoh juga, karena tujuannya nggak tercapai. Jadi tujuan saat ini adalah bagaimana caranya untuk mendapatkan seluruh nama jaringannya saja. Setelah menjalankan perintah docker network ls --help, petunjuk penggunaan dari perintah tersebut pun muncul, dan Saya tertarik dengan sebuah tautan yang merujuk ke Dokumentasi CLI Formatting. Setelah membaca-baca sedikit, akhirnya dapat lah perintah berikut:\n#!/bin/bash docker network ls --format \u0026#39;{{.Name}}\u0026#39; Dengan perintah tersebut, akhirnya Saya berhasil menampilkan nama jaringannya saja.\n\u0026gt; ./docker-ip-grabber.sh accounting_default bridge container-test_default 2. Mempercantik output-nya, untuk sementara\u0026hellip; # Karena output yang dihasilkan rada boring, jadi Saya mengubah scriptnya sehingga seluruh jaringan disimpan ke satu variabel terlebih dulu, lalu di-output-kan nanti.\n#!/bin/bash network_names=$(docker network ls --format \u0026#39;{{.Name}}\u0026#39;) echo \u0026#34;Available networks:\u0026#34; echo \u0026#34;$network_names\u0026#34; Sehingga outputnya menjadi:\n\u0026gt; ./docker-ip-grabber.sh Available networks: accounting_default bridge container-test_default Sebuah improvement, yes. Ingat kawan-kawan, proses sekecil apapun itu, tetaplah proses (sama halnya kek nulis artikel ini, duh, males banget asli klo udah panjang).\n3. Looping, dan network inspect satu-per-satu # Pada saat Bash menyimpan hasil dari docker network ls ke variabel network, sebenarnya isi dari variabel tersebut adalah sebuah array yang terpisah menjadi new line. Karena itu, Saya dapat melakukan sebuah operasi untuk tiap barisnya.\n#!/bin/bash network_names=$(docker network ls --format \u0026#39;{{.Name}}\u0026#39;) for network in $network_names; do echo \u0026#34;Inspecting network: $network\u0026#34; inspected_network=$(docker network inspect \u0026#34;$network\u0026#34;) echo $inspected_network echo \u0026#34;------------------------------------\u0026#34; done Tapi, hasilnya, tentu saja, hancur awoekawoekawoe\n\u0026gt; ./docker-ip-grabber.sh Inspecting network: accounting_default [ { \u0026#34;Name\u0026#34;: \u0026#34;accounting_default\u0026#34;, \u0026#34;Id\u0026#34;: \u0026#34;1fedbf85b3f6f1afcf5c95fb8113431b54db9e53f963a59cfffe22ec2ac51099\u0026#34;, \u0026#34;Created\u0026#34;: \u0026#34;2025-03-20T21:13:06.696006208+07:00\u0026#34;, \u0026#34;Scope\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;Driver\u0026#34;: \u0026#34;bridge\u0026#34;, \u0026#34;EnableIPv6\u0026#34;: false, \u0026#34;IPAM\u0026#34;: { \u0026#34;Driver\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;Options\u0026#34;: null, \u0026#34;Config\u0026#34;: [ { \u0026#34;Subnet\u0026#34;: \u0026#34;192.168.172.0/24\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;192.168.172.1\u0026#34; } ] }, \u0026#34;Internal\u0026#34;: false, \u0026#34;Attachable\u0026#34;: false, \u0026#34;Ingress\u0026#34;: false, \u0026#34;ConfigFrom\u0026#34;: { \u0026#34;Network\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;ConfigOnly\u0026#34;: false, \u0026#34;Containers\u0026#34;: { \u0026#34;cb562276b489693ec8341b7da6ea4d723b0bfbe53ff9198ae322cc61bde490c3\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;accounting-db-1\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;b15a4ae63cb8486235137de22f7035ed15cc8f1e0abce583f7dc6a6e97d2b4db\u0026#34;, \u0026#34;MacAddress\u0026#34;: \u0026#34;02:42:c0:a8:ac:02\u0026#34;, \u0026#34;IPv4Address\u0026#34;: \u0026#34;192.168.172.2/24\u0026#34;, \u0026#34;IPv6Address\u0026#34;: \u0026#34;\u0026#34; } }, \u0026#34;Options\u0026#34;: {}, \u0026#34;Labels\u0026#34;: { \u0026#34;com.docker.compose.config-hash\u0026#34;: \u0026#34;a1cd18b750e7f506cc38ce0afe80dc1d7d6d490a0413dc849e2fb87068e58eb1\u0026#34;, \u0026#34;com.docker.compose.network\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;com.docker.compose.project\u0026#34;: \u0026#34;accounting\u0026#34;, \u0026#34;com.docker.compose.version\u0026#34;: \u0026#34;2.32.4\u0026#34; } } ] ------------------------------------ Inspecting network: bridge [ { \u0026#34;Name\u0026#34;: \u0026#34;bridge\u0026#34;, \u0026#34;Id\u0026#34;: \u0026#34;0e17478e344d520bbbfa3d0a4d2905e90f8ded9f8b06615a34ba0be6d2020ef3\u0026#34;, \u0026#34;Created\u0026#34;: \u0026#34;2025-03-28T19:25:47.320998626+07:00\u0026#34;, \u0026#34;Scope\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;Driver\u0026#34;: \u0026#34;bridge\u0026#34;, \u0026#34;EnableIPv6\u0026#34;: false, \u0026#34;IPAM\u0026#34;: { \u0026#34;Driver\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;Options\u0026#34;: null, \u0026#34;Config\u0026#34;: [ { \u0026#34;Subnet\u0026#34;: \u0026#34;192.168.215.0/24\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;192.168.215.1\u0026#34; } ] }, \u0026#34;Internal\u0026#34;: false, \u0026#34;Attachable\u0026#34;: false, \u0026#34;Ingress\u0026#34;: false, \u0026#34;ConfigFrom\u0026#34;: { \u0026#34;Network\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;ConfigOnly\u0026#34;: false, \u0026#34;Containers\u0026#34;: {}, \u0026#34;Options\u0026#34;: { \u0026#34;com.docker.network.bridge.default_bridge\u0026#34;: \u0026#34;true\u0026#34;, \u0026#34;com.docker.network.bridge.enable_icc\u0026#34;: \u0026#34;true\u0026#34;, \u0026#34;com.docker.network.bridge.enable_ip_masquerade\u0026#34;: \u0026#34;true\u0026#34;, \u0026#34;com.docker.network.bridge.host_binding_ipv4\u0026#34;: \u0026#34;0.0.0.0\u0026#34;, \u0026#34;com.docker.network.bridge.name\u0026#34;: \u0026#34;docker0\u0026#34;, \u0026#34;com.docker.network.driver.mtu\u0026#34;: \u0026#34;1500\u0026#34; }, \u0026#34;Labels\u0026#34;: {} } ] ------------------------------------ 4. Ambil data \u0026ldquo;IPv4Address\u0026rdquo; # Nah caranya gimana tuh? Kalau di JavaScript kan, kita bisa pakai output[0].Containers[containerId].IPv4Address, kalau di Bash gimana? Dalam Bash, seluruh output perintah yang kita jalankan akan disimpan dalam bentuk plaintext, atau teks mentah. Jadi dari teks mentah tersebut, harus kita formatting / ubah menjadi JSON.\nDisitulah Saya berkenalan dengan perintah jq. jq ini adalah sebuah alat pemrosesan input JSON, sehingga data didalamnya bisa diolah melalui CLI.\nContohnya adalah seperti berikut:\n$ echo \u0026#39;{\u0026#34;foo\u0026#34;: 0}\u0026#39; | jq { \u0026#34;foo\u0026#34;: 0 } Setelah itu, Saya coba-coba untuk meng-echo output dari docker network inspect:\necho '[ { \u0026quot;Name\u0026quot;: \u0026quot;accounting_default\u0026quot;, \u0026quot;Id\u0026quot;: \u0026quot;1fedbf85b3f6f1afcf5c95fb8113431b54db9e53f963a59cfffe22ec2ac51099\u0026quot;, \u0026quot;Created\u0026quot;: \u0026quot;2025-03-20T21:13:06.696006208+07:00\u0026quot;, \u0026quot;Scope\u0026quot;: \u0026quot;local\u0026quot;, \u0026quot;Driver\u0026quot;: \u0026quot;bridge\u0026quot;, \u0026quot;EnableIPv6\u0026quot;: false, \u0026quot;IPAM\u0026quot;: { \u0026quot;Driver\u0026quot;: \u0026quot;default\u0026quot;, \u0026quot;Options\u0026quot;: null, \u0026quot;Config\u0026quot;: [ { \u0026quot;Subnet\u0026quot;: \u0026quot;192.168.172.0/24\u0026quot;, \u0026quot;Gateway\u0026quot;: \u0026quot;192.168.172.1\u0026quot; } ] }, \u0026quot;Internal\u0026quot;: false, \u0026quot;Attachable\u0026quot;: false, \u0026quot;Ingress\u0026quot;: false, \u0026quot;ConfigFrom\u0026quot;: { \u0026quot;Network\u0026quot;: \u0026quot;\u0026quot; }, \u0026quot;ConfigOnly\u0026quot;: false, \u0026quot;Containers\u0026quot;: { \u0026quot;cb562276b489693ec8341b7da6ea4d723b0bfbe53ff9198ae322cc61bde490c3\u0026quot;: { \u0026quot;Name\u0026quot;: \u0026quot;accounting-db-1\u0026quot;, \u0026quot;EndpointID\u0026quot;: \u0026quot;b15a4ae63cb8486235137de22f7035ed15cc8f1e0abce583f7dc6a6e97d2b4db\u0026quot;, \u0026quot;MacAddress\u0026quot;: \u0026quot;02:42:c0:a8:ac:02\u0026quot;, \u0026quot;IPv4Address\u0026quot;: \u0026quot;192.168.172.2/24\u0026quot;, \u0026quot;IPv6Address\u0026quot;: \u0026quot;\u0026quot; } }, \u0026quot;Options\u0026quot;: {}, \u0026quot;Labels\u0026quot;: { \u0026quot;com.docker.compose.config-hash\u0026quot;: \u0026quot;a1cd18b750e7f506cc38ce0afe80dc1d7d6d490a0413dc849e2fb87068e58eb1\u0026quot;, \u0026quot;com.docker.compose.network\u0026quot;: \u0026quot;default\u0026quot;, \u0026quot;com.docker.compose.project\u0026quot;: \u0026quot;accounting\u0026quot;, \u0026quot;com.docker.compose.version\u0026quot;: \u0026quot;2.32.4\u0026quot; } } ]' | jq\nHasilnya adalah sebagai berikut:\n[ { \u0026#34;Name\u0026#34;: \u0026#34;accounting_default\u0026#34;, \u0026#34;Id\u0026#34;: \u0026#34;1fedbf85b3f6f1afcf5c95fb8113431b54db9e53f963a59cfffe22ec2ac51099\u0026#34;, \u0026#34;Created\u0026#34;: \u0026#34;2025-03-20T21:13:06.696006208+07:00\u0026#34;, \u0026#34;Scope\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;Driver\u0026#34;: \u0026#34;bridge\u0026#34;, \u0026#34;EnableIPv6\u0026#34;: false, \u0026#34;IPAM\u0026#34;: { \u0026#34;Driver\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;Options\u0026#34;: null, \u0026#34;Config\u0026#34;: [ { \u0026#34;Subnet\u0026#34;: \u0026#34;192.168.172.0/24\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;192.168.172.1\u0026#34; } ] }, \u0026#34;Internal\u0026#34;: false, \u0026#34;Attachable\u0026#34;: false, \u0026#34;Ingress\u0026#34;: false, \u0026#34;ConfigFrom\u0026#34;: { \u0026#34;Network\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;ConfigOnly\u0026#34;: false, \u0026#34;Containers\u0026#34;: { \u0026#34;cb562276b489693ec8341b7da6ea4d723b0bfbe53ff9198ae322cc61bde490c3\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;accounting-db-1\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;b15a4ae63cb8486235137de22f7035ed15cc8f1e0abce583f7dc6a6e97d2b4db\u0026#34;, \u0026#34;MacAddress\u0026#34;: \u0026#34;02:42:c0:a8:ac:02\u0026#34;, \u0026#34;IPv4Address\u0026#34;: \u0026#34;192.168.172.2/24\u0026#34;, \u0026#34;IPv6Address\u0026#34;: \u0026#34;\u0026#34; } }, \u0026#34;Options\u0026#34;: {}, \u0026#34;Labels\u0026#34;: { \u0026#34;com.docker.compose.config-hash\u0026#34;: \u0026#34;a1cd18b750e7f506cc38ce0afe80dc1d7d6d490a0413dc849e2fb87068e58eb1\u0026#34;, \u0026#34;com.docker.compose.network\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;com.docker.compose.project\u0026#34;: \u0026#34;accounting\u0026#34;, \u0026#34;com.docker.compose.version\u0026#34;: \u0026#34;2.32.4\u0026#34; } } ] Thought Process yang Saya alami adalah sebagai berikut:\nAmbil data pertama dari JSON tersebut (dalam JS: data[0]). Mengambil data dari key Containers (dalam JS: data[0].Containers). Mengambil value dari IPv4Address (dalam JS: data[0].Containers[containerId].IPv4Address). Jika diimplementasi menggunakan perintah jq, maka tahapan-tahapan di atas diubah menjadi berikut:\necho '--data JSON disini--' | jq -r '.[]' echo '--data JSON disini--' | jq -r '.[].Containers' echo '--data JSON disini--' | jq -r '.[].Containers.[].IPv4Address' Setelah ketemu solusi sementaranya seperti ini, maka Saya ubah Bash script yang tadi menjadi seperti ini:\n#!/bin/bash network_names=$(docker network ls --format \u0026#39;{{.Name}}\u0026#39;) for network in $network_names; do echo \u0026#34;Inspecting network: $network\u0026#34; ipv4_addresses=$(docker network inspect \u0026#34;$network\u0026#34; | jq -r \u0026#39;.[].Containers[].IPv4Address\u0026#39;) if [[ -n \u0026#34;$ipv4_addresses\u0026#34; ]]; then echo \u0026#34;IPv4 Addresses in $network:\u0026#34; echo \u0026#34;$ipv4_addresses\u0026#34; else echo \u0026#34;No containers with IPv4 addresses found in $network.\u0026#34; fi echo \u0026#34;------------------------------------\u0026#34; done Hasilnya adalah\u0026hellip;..\n\u0026gt; ./docker-ip-grabber.sh Inspecting network: accounting_default IPv4 Addresses in accounting_default: 192.168.172.2/24 ------------------------------------ Inspecting network: bridge No containers with IPv4 addresses found in bridge. ------------------------------------ LHO LHO, KOK ADA /24-NYA??? Nah, itulah pemirsa, yang dinamakan sebagai \u0026ldquo;Subnet\u0026rdquo;. Orang jaringan pasti kenal betul sama istilah ini. Tentunya, Saya tidak perlu identitas \u0026ldquo;Subnet\u0026rdquo; ini untuk IP yang akan Saya masukkan ke TablePlus.\n5. Hapus Subnet # Jadi caranya, selain kita nge-pipeline hasil output plaintext kita ke jq, kita juga harus pipeline lagi ke perintah cut untuk mengeliminasi Subnet yang tidak diperlukan.\nCaranya adalah dengan menjalankan perintah cut -d'/' -f1. Artinya apa tuh?\nKita menggunting teks 192.168.172.2/24 dengan \u0026ldquo;delimiter\u0026rdquo; -d atau \u0026ldquo;acuan\u0026rdquo; garis miring (/). Perintah ini sama halnya dengan fungsi explode() pada PHP explode('/', '192.168.172.2'). Dengan perintah di atas, hasilnya adalah sebuah array dengan isi 192.168.172.2 dan 24. Kita panggil data pertama dengan cara penggunakan flag -f diikuti dengan angka urutan (dimulai dari 1, bukan 0). Dengan mengubah script yang kita buat jadi seperti ini, akhirnya kita mendapatkan IP-nya saja.\n#!/bin/bash network_names=$(docker network ls --format \u0026#39;{{.Name}}\u0026#39;) for network in $network_names; do echo \u0026#34;Inspecting network: $network\u0026#34; ipv4_addresses=$(docker network inspect \u0026#34;$network\u0026#34; | jq -r \u0026#39;.[].Containers[].IPv4Address\u0026#39; | cut -d\u0026#39;/\u0026#39; -f1) if [[ -n \u0026#34;$ipv4_addresses\u0026#34; ]]; then echo \u0026#34;IPv4 Addresses in $network:\u0026#34; echo \u0026#34;$ipv4_addresses\u0026#34; else echo \u0026#34;No containers with IPv4 addresses found in $network.\u0026#34; fi echo \u0026#34;------------------------------------\u0026#34; done \u0026gt; ./docker-ip-grabber.sh Inspecting network: accounting_default IPv4 Addresses in accounting_default: 192.168.172.2 ------------------------------------ Inspecting network: bridge No containers with IPv4 addresses found in bridge. ------------------------------------ 6. Hanya tampilkan Network yang aktif saja # Perintah di atas akan menampilkan seluruh Network yang ada, tanpa mempedulikan apakah Container tersebut sedang aktif atau tidak. Jadi simpelnya, kita hilangkan blok perintah else, lalu pindah seluruh perintah echo ke dalam blok perintah if.\n#!/bin/bash network_names=$(docker network ls --format \u0026#39;{{.Name}}\u0026#39;) for network in $network_names; do ipv4_addresses=$(docker network inspect \u0026#34;$network\u0026#34; | jq -r \u0026#39;.[].Containers[].IPv4Address\u0026#39; | cut -d\u0026#39;/\u0026#39; -f1) if [[ -n \u0026#34;$ipv4_addresses\u0026#34; ]]; then echo \u0026#34;Inspecting network: $network\u0026#34; echo \u0026#34;IPv4 Addresses in $network:\u0026#34; echo \u0026#34;$ipv4_addresses\u0026#34; echo \u0026#34;------------------------------------\u0026#34; fi done Jadi, hasil jika kita jalankan script yang kita buat sejauh ini:\n\u0026gt; ./docker-network-grabber.sh Inspecting network: accounting_default IPv4 Addresses in accounting_default: 192.168.172.2 ------------------------------------ Inspecting network: another-project_sail IPv4 Addresses in another-project_sail: 192.168.171.4 192.168.171.2 192.168.171.3 192.168.171.5 ------------------------------------ Inspecting network: laravel-12-base_sail IPv4 Addresses in laravel-12-base_sail: 192.168.181.2 192.168.181.3 ------------------------------------ YEY! Selesai! Eh\u0026hellip; tapi mana IP container untuk aplikasi, dan mana IP container untuk database? Waduh, nama Containernya belum masuk nih 🤦‍♂️. Capek deh.\n7. Menampilkan Nama Container di samping IP # Intinya, Saya harus menyimpan hasil dari perintah docker network inspect ke dalam satu variabel terlebih dahulu, lalu diolah lagi menggunakan jq, itulah yang terlintas pertama kali di kepala Saya. Jadi script-nya adalah sebagai berikut:\n#!/bin/bash network_names=$(docker network ls --format \u0026#39;{{.Name}}\u0026#39;) for network in $network_names; do inspected_network=$(docker network inspect \u0026#34;$network\u0026#34;) container_name=$(echo $inspected_network | jq -r \u0026#39;.[].Containers[].Name\u0026#39;) ipv4_addresses=$(echo $inspected_network | jq -r \u0026#39;.[].Containers[].IPv4Address\u0026#39; | cut -d\u0026#39;/\u0026#39; -f1) if [[ -n \u0026#34;$ipv4_addresses\u0026#34; ]]; then echo \u0026#34;Inspecting network: $network\u0026#34; echo \u0026#34;IPv4 Addresses in $network:\u0026#34; echo \u0026#34;$ipv4_addresses $container_name\u0026#34; echo \u0026#34;------------------------------------\u0026#34; fi done Dan pada saat dijalankan:\n\u0026gt; ./docker-network-grabber.sh Inspecting network: accounting_default IPv4 Addresses in accounting_default: 192.168.172.2accounting-db-1 ------------------------------------ Inspecting network: another-project_sail IPv4 Addresses in another-project_sail: 192.168.171.4 192.168.171.2 192.168.171.3 192.168.171.5another-project-pgsql-1 another-project-data-master-pqsql-1 another-project-pulse-pqsql-1 another-project-laravel.test-1 ------------------------------------ Inspecting network: laravel-12-base_sail IPv4 Addresses in laravel-12-base_sail: 192.168.181.2 192.168.181.3laravel-12-base-mysql-1 laravel-12-base-laravel.test-1 ------------------------------------ Lah kok formattingnya gak rapih gini?\n8. Hantaman Realita # Saya baru menyadari bahwa output dari perintah jq -r '.[].Containers[].Name' ataupun jq -r '.[].Containers[].IPv4Address' adalah berbentuk array. Karena itulah hasilnya tidak rapih.\nJika saja proyek kecil ini adalah sebuah proyek Laravel, Saya pasti bisa menggunakan Collection untuk mengubah datanya menjadi array key-value pairs dengan cara:\ncollect($networks) -\u0026gt;flatMap(function ($network) { $containers = $network[0][\u0026#39;Containers\u0026#39;]; return collect($containers) -\u0026gt;map(fn ($container, $containerName) =\u0026gt; [ \u0026#39;ip\u0026#39; =\u0026gt; $container[$containerName]-\u0026gt;IPv4Address, \u0026#39;name\u0026#39; =\u0026gt; $container[$containerName]-\u0026gt;Name, ]) -\u0026gt;toArray(); }) -\u0026gt;toArray(); Jadi, hal ini sepenuhnya Saya serahkan ke ChatGPT karena ilmu Saya belum mencapai level tersebut. Kurang lebih beginilah hasil perintah jq yang dibuatkan:\njq -r \u0026#39;.[].Containers | to_entries[] | select(.value.IPv4Address != null) | (.value.IPv4Address | sub(\u0026#34;/.*\u0026#34;; \u0026#34;\u0026#34;)) as $ip | .value.Name as $name | \u0026#34;\\($ip) \\($name)\u0026#34;\u0026#39; Gila, pipeline hell betul. Jadi, menurut pemahaman Saya jika diterjemahkan pipeline-per-pipeline:\nAmbil data Containers menggunakan .[].Containers. Ubah data masing-masing Container menjadi key-value pair. Jadi datanya bakal diubah menjadi seperti [ { \u0026#34;key\u0026#34;: \u0026#34;IPv4Address\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;192.168.xxx.xxx\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;Name\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;ContainerName\u0026#34; } ] Memfilter Container yang aktif dengan perintah select(.value.IPv4Address != null). Perintah select(boolean) akan mengembalikan key-value pair tersebut tanpa diubah jika kondisi pada parameter boolean terpenuhi. Bagaimana jika tidak terpenuhi? Tidak ada data apapun yang dikembalikan ke pengguna. Jika pada tahap (3) terpenuhi, maka hapus Subnet yang ada pada key IPv4Address dan menjadikannya sebagai variabel $ip. Ambil data dari .value.Name dan menjadikannya sebagai variabel $name. Keluarkan data yang diolah sehingga berbentuk menjadi 192.168.xxx.xxx ContainerName. 9. Hasil Akhir # Jadi, dari debugging Saya yang hampir menghabiskan waktu Subuh, hasil script-nya adalah sebagai berikut:\n#!/bin/bash network_names=$(docker network ls --format \u0026#39;{{.Name}}\u0026#39;) for network in $network_names; do output=$(docker network inspect \u0026#34;$network\u0026#34; | jq -r \u0026#39; .[].Containers | to_entries[] | select(.value.IPv4Address != null) | (.value.IPv4Address | sub(\u0026#34;/.*\u0026#34;; \u0026#34;\u0026#34;)) as $ip | .value.Name as $name | \u0026#34;\\($ip) \\($name)\u0026#34;\u0026#39;) if [[ -n \u0026#34;$output\u0026#34; ]]; then echo \u0026#34;Inspecting network: $network\u0026#34; echo \u0026#34;IPv4 Addresses in $network:\u0026#34; echo \u0026#34;$output\u0026#34; echo fi done Namun, ternyata hasilnya (lagi-lagi, masih, karena idealis) tidak seperti yang diharapkan:\n\u0026gt; ./docker-network-grabber.sh Inspecting network: accounting_default IPv4 Addresses in accounting_default: 192.168.172.2 accounting-db-1 Inspecting network: another-app_sail IPv4 Addresses in another-app_sail: 192.168.171.4 another-app-pgsql-1 192.168.171.2 another-app-data-master-pqsql-1 192.168.171.3 another-app-pulse-pqsql-1 192.168.171.5 another-app-laravel.test-1 Inspecting network: laravel-12-base_sail IPv4 Addresses in laravel-12-base_sail: 192.168.181.2 laravel-12-base-mysql-1 192.168.181.3 laravel-12-base-laravel.test-1 Hasilnya nggak rapih, cuma ada 1 spasi di antara IP dan nama Container. Kita mau keterbacaan di script ini jadi nomor 1 di antara hal lainnya. Jadi solusinya adalah kita gunakan perintah awk. awk akan menganggap setiap spasi pada variabel $output yang ada sebagai pemisah antara kolom. Jadi pada variabel $output ini, kita memiliki 2 kolom:\n192.168.171.4 another-app-pgsql-1 192.168.171.2 another-app-data-master-pqsql-1 192.168.171.3 another-app-pulse-pqsql-1 192.168.171.5 another-app-laravel.test-1 Jadi, jika kita ingin mengambil data IP saja, maka kita bisa ambil kolom pertama menggunakan perintah awk {print $1}, atau jika ingin mengambil nama Container-nya saja, kita bisa gunakan perintah awk {print $2}, atau jika ingin mengubahnya menjadi nama Container-nya dulu baru IP-nya kita bisa gunakan awk {print $2, $1}.\nSelanjutnya, untuk menambah spacing, kita akan memanfaatkan perintah printf. Digabung dengan perintah awk, hasilnya menjadi awk '{ printf \u0026quot;%-16s %s\\n\u0026quot;, $1, $2 }'.\nArti dari perintah di atas adalah:\nKeluarkan teks dengan formatting (printf) Rata kiri-kan (-) seluruh 16 karakter (16) pertama (%s) . Mengapa 16? Karena IPv4 memiliki panjang 16 karakter (termasuk titik). Keseluruhan perintah kolom formatter pertama adalah %-16s. Sediakan template untuk kolom kedua %s. Keluarkan new line. Lalu kita implementasikan ke script utama:\necho \u0026quot;$output\u0026quot; | awk '{ printf \u0026quot;%-16s %s\\n\u0026quot;, $1, $2 }'\nHasilnya adalah:\n\u0026gt; ./docker-network-grabber.sh Inspecting network: accounting_default IPv4 Addresses in accounting_default: 192.168.172.2 accounting-db-1 Inspecting network: another-app_sail IPv4 Addresses in another-app_sail: 192.168.171.4 another-app-pgsql-1 192.168.171.2 another-app-data-master-pqsql-1 192.168.171.3 another-app-pulse-pqsql-1 192.168.171.5 another-app-laravel.test-1 Inspecting network: laravel-12-base_sail IPv4 Addresses in laravel-12-base_sail: 192.168.181.2 laravel-12-base-mysql-1 192.168.181.3 laravel-12-base-laravel.test-1 10. Finishing Touch # Lagi-lagi, biar rapih, kita hapus beberapa baris yang sekiranya tidak diperlukan seperti baris \u0026ldquo;Inspecting network\u0026rdquo;, lalu \u0026ldquo;IPv4 Addresses in \u0026hellip;\u0026rdquo;. Jadi ya, kurang lebih script-nya seperti ini:\nif [[ -n \u0026#34;$output\u0026#34; ]]; then echo \u0026#34;$network\u0026#34; echo \u0026#34;$output\u0026#34; | awk \u0026#39;{ printf \u0026#34;%-16s %s\\n\u0026#34;, $1, $2 }\u0026#39; echo fi Untuk kalian, yang malas baca # Tolong banget ini mah baca dari awal, apresiasi sedikit dong 😭🙏. Nulis ginian doang butuh waktu 3 jam wkwkwk. Biar sama-sama saling tau struggle sama effort. Tapi kalau kalian tetap mau script lengkapnya, nih.\n📄 docker-network-grabber.sh #!/bin/bash network_names=$(docker network ls --format '{{.Name}}') for network in $network_names; do output=$(docker network inspect \u0026quot;$network\u0026quot; | jq -r ' .[].Containers | to_entries[] | select(.value.IPv4Address != null) | (.value.IPv4Address | sub(\u0026quot;/.*\u0026quot;; \u0026quot;\u0026quot;)) as $ip | .value.Name as $name | \u0026quot;\\($ip) \\($name)\u0026quot;') if [[ -n \u0026quot;$output\u0026quot; ]]; then echo \u0026quot;$network\u0026quot; echo \u0026quot;$output\u0026quot; | awk '{ printf \u0026quot;%-16s %s\\n\u0026quot;, $1, $2 }' echo fi done Well, mungkin untuk sekarang itu dulu aja. Jadi, sampai ketemu nanti di artikel lainnya, dan semoga artikel ini bermanfaat untuk kalian!\nPratinjau dalam artiken ini difoto oleh Ian Taylor di Unsplash\n","date":"29 Maret 2025","externalUrl":null,"permalink":"/artikel/2025/03/29/docker-ip-list-bash-script/","section":"Artikel-artikel","summary":"","title":"List IP Docker Container menggunakan Bash","type":"artikel"},{"content":"","date":"17 Juni 2024","externalUrl":null,"permalink":"/tags/artikel/","section":"Tags","summary":"","title":"Artikel","type":"tags"},{"content":"Dumas Presisi adalah sebuah aplikasi berbasis web, yang digunakan secara aktif oleh Kepolisian Negara Republik Indonesia untuk menampung aspirasi serta keluhan-keluhan masyarakat terhadap anggota Polri sampai dengan saat ini.\nBekerja sama dengan Itwasum Polri, Bareskrim Polri, dan Divpropam Polri, kami bersinergi untuk membawa Polri untuk menjadi sebuah instansi yang modern, terintegrasi, dan Presisi.\nPresisi sendiri merupakan sebuah slogan yang digaungkan oleh Kapolri Jenderal Sigit Listyo Prabowo yang memiliki arti Prediktif, Responsibilitas, dan Transparansi Berkeadilan. Menggantikan slogan sebelumnya, Promoter yang memiliki arti Profesional, Modern, dan Terpercaya.\nSejarah Dumas Presisi # Dumas Presisi adalah sebuah aplikasi yang diusung oleh Itwasum Polri, atau dikenal sebagai Inspektur Pengawasan Umum Polri, suatu satuan kerja yang berada di bawah naungan Kapolri secara langsung, yang bekerja untuk mengawasi dan membantu pimpinan pada tingkat Mabes Polri.\nAplikasi ini diluncurkan pada tanggal 24 Februari 2021 dalam kegiatan Rakerwas Itwasum Polri T.A 2021 di Gedung Rupatama Mabes Polri oleh Kapolri secara langsung. Kapolri menegaskan kepada Itwasum Polri untuk menyerap semua informasi yang disampaikan oleh masyarakat sebagai upaya memperkaya wawasan, serta menyempurnakan strategi, teknis, dan cara bertindak di lapangan.\nVersi 0 (Development Phase) # Disini adalah awal mula perjalanan kami dimana kami bertiga berada di jembatan penyebrangan Mabes Polri, terlihat wajah-wajah ceria dengan semangat yang masih membara. Tidak mengetahui kendala yang akan kami hadapi, kami pun berjalan selangkah menuju awal dari perjalanan yang panjang ini.\nDumas Presisi pada masa pengembangan adalah sebuah aplikasi yang sangat sederhana, dengan alur yang sangat sederhana juga. Masyarakat menginputkan NIK, mengisi formulir pengaduan ke tujuannya (Mabes Polri, Polda, atau Polres), lalu seorang admin akan menjawab pengaduan tersebut, dan selesai.\nDulu pada saat masa pengembangan, tim kami terdiri dari 3 orang: bapak Yono Maulana selaku direktur dan konsultan, Aghits Nidallah (saya sendiri), serta Muhamad Ahmadin. Kami memiliki timeframe yang sangat sempit, dengan durasi waktu kurang dari 1 bulan, kami harus men-deliver aplikasi yang sudah siap pakai, dan dapat disosialisasikan langsung kepada masyarakat. Karena itu, kami memutuskan untuk menggunakan Laravel dengan alasan development experience yang robust dan efisien. Alhasil, Dumas Presisi selesai pada tahap pengembangan dan siap digunakan dengan waktu pengembangan dalam waktu 2 minggu.\nTentu saja, fase ini adalah suatu fase yang sangat krusial, umpan balik dan masukkan dari pihak-pihak terkait tak luput dari fase pengembangan ini. Kami berusaha setiap hari untuk mengakomodir fitur-fitur yang belum ada pada aplikasi, secara terus menerus, untuk jangka waktu 3 bulan ke depan.\nRevisi demi revisi, fitur demi fitur, bug demi bug kami perbaiki dan sempurnakan, sampai aplikasi ini menjadi sebuah aplikasi utuh yang dapat digunakan (secara garis besar). Fast-forward beberapa minggu kedepan, peluncuran aplikasi Dumas Presisi pun dilaksanakan.\nVersi 1 (Initial Release, Fase Integrasi) # Bulan pertama dirilisnya Dumas Presisi, kami mendapatkan setidaknya 1.300 pengaduan yang dikirimkan oleh masyarakat seluruh Indonesia. Bukan angka yang fantastis tentunya, namun bagi kami angka tersebut merupakan suatu pencapaian yang hebat. Fakta bahwa aplikasi kami dipakai secara luas menimbulkan kepercayaan diri, kepuasan tersendiri, dan kekhawatiran lain yang mungkin muncul di kemudian hari.\nPada fase ini, kami mulai mengintegrasikan aplikasi kami dengan aplikasi-aplikasi lain, baik dari internal Polri maupun eksternal Polri. Dari segi internal, Dumas Presisi terintegrasi dengan Pusiknas Polri di bawah naungan Bareskrim Polri, serta DBMS di bawah naungan Propam Polri. Dari segi eksternal, kami terintegrasi dengan Disdukcapil untuk verifikasi NIK, aplikasi E-Lapor Kompolnas, serta Data Analytics yang dibangun oleh Paques Indonesia.\nPada fase integrasi ini, timbul masalah-masalah baru yang belum pernah kami hadapi sebelumnya, tentunya dari segi bagaimana kami akan mengolah data yang akan dikirim / diterima, memikirkan bagaimana caranya data tersebut tetap aman pada saat pengiriman, serta bagaimana data tersebut tetap tersinkronisasi walaupun data tersebut sudah berada di aplikasi lain.\nPada fase ini juga, aplikasi Dumas Presisi terpisah menjadi dua bagian, yaitu Dumas Presisi Front-end dan Dumas Presisi Back-end. Jika ditafsirkan menggunakan terminologi pemrograman, Dumas Presisi masih menjadi suatu aplikasi utuh secara full-stack, namun tidak pada kasus ini.\nDumas Presisi Front-end bertugas untuk menerima pengaduan yang dikirimkan oleh masyarakat dan menyerahkan laporan hasil tindak lanjut yang dikirimkan dari operator, sementara Dumas Presisi Back-end bertugas untuk mendata proses tindak lanjut yang terjadi di belakang layar secara aktual.\nFront-end yang dimaksud adalah aplikasi yang kita bangun, sementara Back-end adalah aplikasi yang dibangun oleh Pusiknas Polri. Dengan adanya dua aplikasi yang terpisah, terdapat banyak kesulitan yang membutuhkan pengiriman data secara manual setiap harinya. Tentunya hal ini adalah sebuah beban moral dan energi bagi kami, dan kami harus menyelesaikannya secepat mungkin.\nVersi 2 (The Great Unity, Fase Refaktor) # Di dalam codebase yang terburu-buru, akan terdapat juga kode yang hanya dapat dibaca pada saat pengembangan dan menjadi bahasa asing pada saat maintenance. Pada tahap ini, codebase Dumas Presisi harus diubah secara total menjadi kesatuan yang utuh.\nSalah satu perbaikan besar yang kami lakukan adalah yang kami sebut sebagai The Great Unity, yaitu penyatuan aplikasi Front-end dan aplikasi Back-end. Masa refaktor ini berlangsung dari awal Oktober 2021 sampai dengan Januari 2022. Dengan waktu sesingkat itu, kami berhasil memperbaiki codebase, serta menyatukan aplikasi lain sehingga operator di lapangan memiliki kemudahan.\nDisaat selesainya refaktor, banyak operator dari Polda-polda yang merasa kebingungan dikarenakan perubahan interface dan styling. Walaupun tidak secara signifikan berubah total, kami menyadari bahwa perubahan sekecil apapun, pengguna dengan sangat cepat menyadari \u0026ldquo;ada sesuatu yang salah\u0026rdquo; pada sistem kami.\nBerikut adalah tampilan dari Dashboard lama\nDan berikut adalah tampilan dari Dashboard baru\nMenyadari permasalah tersebut, kami mengajukan rencana sosialisasi yang dilakukan untuk memberikan bimbingan kepada operator tingkat Mabes yang berada di Mabes Polri, serta operator yang berada di Polda jajaran.\nBerikut adalah dokumentasi sosialisasi pada Mabes Polri.\nBerikut adalah dokumentasi sosialisasi pada Mabes Polri, untuk operator-operator di Polda jajaran.\nInti dari kedua sosialisasi ini berfokus kepada tampilan dan fitur-fitur baru yang berfokus kepada penyajian data statistika yang sewaktu-waktu mungkin dibutuhkan oleh pimpinan. Kami juga mensosialisasikan bahwa aplikasi Back-end tidak lagi dibutuhkan sehingga Operator Tindak Lanjut dapat mengakses data-datanya langsung melalui aplikasi Dumas Presisi.\nDalam waktu kurang dari 2 minggu saja, para Operator Tindak Lanjut sudah dapat beradaptasi dengan perubahan ini. Alasan dari cepatnya adaptasi ini adalah, kami tidak mengubah alur kerja yang ada di aplikasi Back-end lama, walaupun terdapat sedikit perubahan styling. Kami berasumsi bahwa UI dan UX masih dapat dipertahankan untuk sementara, namun tetap berubah menyempurnakannya seiring berjalannya waktu.\nPerubahan besar dan mendadak akan membutuhkan waktu adaptasi pengguna yang lebih lama, lebih baik untuk melakukan perubahan-perubahan kecil dan mesosialisasikannya secara perlahan (atau dengan membuat manual book terkait perubahan tersebut untuk menghemat biaya).\nVersi 3 (Continuous Maintenance, Fase Stabil) # Membutuhkan waktu hampir 2 tahun untuk Dumas Presisi mencapai titik stabil, dimana seluruh operator sudah dapat mengoperasikan aplikasi, dimana bug sudah jarang muncul ke permukaan.\nPada fase ini, kami mendapatkan banyak tantangan baru di ranah integrasi dan ranah penyajian data. Integrasi kami merambah ke beberapa aplikasi pengganti dari Bareskrim dan Propam. Aplikasi e-Wassidik yang diusung oleh Birowassidik di bawah naungan Bareskrim Polri, serta aplikasi WA Yanduan yang diusung oleh Biro Yanduan di bawah naungan Divpropam Polri. Kedua aplikasi ini bertugas untuk menindaklanjuti pengaduan-pengaduan yang diterima oleh masyarakat yang kasusnya tidak mungkin ditangani oleh Itwasum.\nDi ranah penyajian data, terdapat tantangan terbesar yang kami hadapi, yaitu permintaan pimpinan terhadap kami untuk membuat suatu fitur yang datanya dapat dilacak secara detil, dari masing-masing satker Mabes, Polda, dan Polres di seluruh Indonesia. Fitur ini kami namakan Backtrace.\nPada saat pengembangan Backtrace, kami menemukan beberapa ketidaksempurnaan perhitungan data, terlebih lagi disaat kami menyajikan data tersebut secara detil. Karena hal ini, kami melakukan normalisasi basis data secara masih, sehingga tidak lagi ada data yang tidak sesuai. Hal ini krusial dikarenakan pimpinan sangat menginginkan penyajian data yang detil dan penting, untuk digunakan kedepannya sebagai strategi pengambilan keputusan.\nSecara teknis, perbaikan ini memakan waktu yang sangat lama dan berkelanjutan, hingga pada akhir tahun 2022, kami pun masih memiliki konflik internal dari tim kami tentang bagaimana data tersebut dapat disajikan dengan benar.\nBerikut adalah dokumentasi kami disaat kami berdiskusi tentang bagaimana Dumas Presisi dan aplikasi-aplikasi yang kami bangun dapat berjalan dengan baik.\nFoto tersebut diambil pada tanggal 27 Januari 2023 pada pukul 01:33. Bisa dibilang, kami adalah tim yang tidak kenal waktu jika kami mengulas permasalahan-permasalahan dan mendiskusikan solusi terhadap permasalahan tersebut. Sebuah tim yang solid, dan terdiri dari para pemuda (tua dikit) yang kritis.\nKelanjutan dari Dumas Presisi # Sampai saat ditulisnya artikel ini, Dumas Presisi memiliki rencana untuk berintegrasi dengan aplikasi-aplikasi lain. Namun rencana tersebut masih belum kami ketahui secara pasti kapan dan bagaimana eksekusi dan penerapannya secara eksak.\nSatu hal yang pasti, Dumas Presisi akan masih terus berjalan, dan melayani seluruh masyarakat Indonesia dengan sepenuh hati.\nAkhir kata, kami ingin menyampaikan bahwa sejak awal aplikasi ini diluncurkan oleh Kapolri Jenderal Listyo Sigit Prabowo, Dumas Presisi telah:\nMengalami perubahan versi sebanyak 3x; Mengalami berbagai krisis teknis dan non-teknis, yang semuanya dapat terselesaikan dengan baik; Terintegrasi dengan lebih dari 5 aplikasi; Memiliki lebih dari 10 fitur utama, dan 35 sub-fitur yang secara aktif digunakan; Menerima lebih dari 30 ribu aduan yang sudah terselesaikan; Digunakan oleh lebih dari 1 ribu akun operator di seluruh Indonesia; Menjadi produk unggulan Polri Presisi terbaik dari hasil lembaga survey Charta Politika, periode bulan Mei hingga Oktober 2021, dengan menempati tingkat 3 teratas dengan perolehan persentase sebanyak 20%; ","date":"17 Juni 2024","externalUrl":null,"permalink":"/portfolio/website/dumas-presisi/","section":"Portfolios","summary":"","title":"Dumas Presisi","type":"portfolio"},{"content":"","date":"17 Juni 2024","externalUrl":null,"permalink":"/tags/portfolio/","section":"Tags","summary":"","title":"Portfolio","type":"tags"},{"content":"","date":"17 Juni 2024","externalUrl":null,"permalink":"/portfolio/","section":"Portfolios","summary":"","title":"Portfolios","type":"portfolio"},{"content":"","date":"17 Juni 2024","externalUrl":null,"permalink":"/tags/website/","section":"Tags","summary":"","title":"Website","type":"tags"},{"content":"","date":"6 Mei 2024","externalUrl":null,"permalink":"/tags/laravel/","section":"Tags","summary":"","title":"Laravel","type":"tags"},{"content":"","date":"6 Mei 2024","externalUrl":null,"permalink":"/tags/lks/","section":"Tags","summary":"","title":"Lks","type":"tags"},{"content":"","date":"6 Mei 2024","externalUrl":null,"permalink":"/series/modul-3-lks-provinsi-web-technology/","section":"Series","summary":"","title":"Modul 3 LKS Provinsi Web Technology","type":"series"},{"content":"","date":"6 Mei 2024","externalUrl":null,"permalink":"/tags/technology/","section":"Tags","summary":"","title":"Technology","type":"tags"},{"content":"Hai hai, kita sekarang ada di sub-modul terakhir nih, semoga masih semangat ya. Walaupun ini sub-modul terakhir, masih ada lagi modul Front-End, jadi jangan senang dulu wkwkwk. Perjalanan yang ditempuh baru setengah jalan, so I wish your luck.\nDi artikel kali ini kita akan membuat modul untuk mendapatkan data User yang belum memfollow User yang login, serta mendapatkan detail dari salah satu User. Kali ini artikelnya gak panjang-panjang kok, lumayan mudah juga. Jadi silahkan baca tutorialnya dengan santai, siapkan air minum, dan let\u0026rsquo;s get coding.\nMendapatkan User yang belum Follow # Di soal modul LKS Web Tech 2024 kali ini, ketentuan dari endpoint /api/v1/users adalah untuk mengambil data seluruh User yang belum diikuti oleh User yang sedang login, User yang login juga harus di-exclude dari daftar.\nBuat Invokable Controller Api/v1/UserController # Buat Controller tersebut menggunakan perintah ini pada command line:\nphp artisan make:controller Api/v1/UserController --invokable\nMengambil data User dari Request # Pada fungsi __invoke yang ada pada file app/Http/Controllers/Api/v1/UserController, tambahkan kode berikut untuk mengambil data User yang sedang login.\n$loggedInUser = $request-\u0026gt;user(); Mengambil User ID yang belum Diikuti # Kita akan menggunakan Reversed Approach untuk mendapatkan data User yang belum kita follow, maksud dari Reversed Approach adalah kita mengambil seluruh ID User yang sudah User itu follow sebagai array, lalu kita tambahkan ID User yang sedang login. Dengan adanya kumpulan dari User ID ini, kita ambil data User dari tabel users yang ID-nya bukan kumpulan dari User ID tersebut.\nKedengarannya sulit, namun jika diimplementasikan, kodenya cukup mudah:\n// Get the User IDs who followed the Logged In User $usersFollowed = $loggedInUser -\u0026gt;followings -\u0026gt;pluck(\u0026#39;following_id\u0026#39;); // Add the Logged In User ID to the array $usersFollowed[] = $loggedInUser-\u0026gt;id; Pertama, karena variabel $loggedInUser adalah sebuah instance dari App\\Models\\User yang memiliki relasi followings, kita bisa mendapatkan User-user yang sudah $loggedInUser ikuti. Setelah itu, kita panggil atribut followings-nya, dan karena setiap pemanggilan atribut relasi akan me-return instance \\Illuminate\\Database\\Eloquent\\Collection, kita dapat memanipulasi data followings-nya sebelum disajikan atau digunakan kembali.\nFungsi pluck() adalah sebuah fungsi Collection yang berguna untuk mengambil data dari sebuah properti yang disediakan. Contohnya adalah sebagai berikut:\n[ { \u0026#34;id\u0026#34;: 1, \u0026#34;full_name\u0026#34;: \u0026#34;Aghits Nidallah\u0026#34;, \u0026#34;username\u0026#34;: \u0026#34;irlnidallah\u0026#34;, \u0026#34;bio\u0026#34;: \u0026#34;Seorang full-stack developer, mahasiswa, dan {manusia,robot} yang hidup dengan menikmati kopi ☕️.\u0026#34;, \u0026#34;is_private\u0026#34;: false, \u0026#34;created_at\u0026#34;: \u0026#34;2024-05-03T21:36:32.000000Z\u0026#34;, \u0026#34;updated_at\u0026#34;: \u0026#34;2024-05-03T21:36:32.000000Z\u0026#34; }, { \u0026#34;id\u0026#34;: 3, \u0026#34;full_name\u0026#34;: \u0026#34;Nikarashi Hatsu\u0026#34;, \u0026#34;username\u0026#34;: \u0026#34;nikarashihatsu\u0026#34;, \u0026#34;bio\u0026#34;: \u0026#34;Hatsu Shiroyuki Alter\u0026#34;, \u0026#34;is_private\u0026#34;: false, \u0026#34;created_at\u0026#34;: \u0026#34;2024-05-05T14:57:53.000000Z\u0026#34;, \u0026#34;updated_at\u0026#34;: \u0026#34;2024-05-05T14:57:53.000000Z\u0026#34; } ] Response JSON di atas adalah sebuah contoh Response yang diberikan pada saat kita mengambil data dari tabel users. Anggaplah kita ingin mengambil id-nya saja, kita bisa menggunakan fungsi pluck('username'), sehingga datanya akan menjadi seperti berikut:\n[ \u0026#34;irlnidallah\u0026#34;, \u0026#34;nikarashihatsu\u0026#34; ] Sangat berguna bukan? Walaupun manipulasi Collection adalah sesuatu teknik yang membutuhkan pengalaman, mempelajarinya bukanlah suatu hal yang merugikan. Seiring Anda menggunakan Eloquent, Anda akan senantiasa memanipulasi Collection di suatu waktu.\nKembali ke konteks awal, karena hasil dari fungsi pluck() adalah berbentuk array, kita akan menambahkan User ID yang sedang login pada saat itu. Anggaplah skenarionya following_id yang sudah di-pluck seperti ini:\n// Get the User IDs who followed the Logged In User $usersFollowed = $loggedInUser -\u0026gt;followings -\u0026gt;pluck(\u0026#39;following_id\u0026#39;); // result: $usersFollowed = [2] // Add the Logged In User ID to the array $usersFollowed[] = $loggedInUser-\u0026gt;id; // Assuming the Logged In User ID is 1, then // result: $usersFollowed = [2, 1] Kita bisa menambahkan sebuah value ke dalam array yang sudah ada dalam sebuah variabel dengan menggunakan sintaks $variable[] = mixed.\nMengambil data User yang belum Diikut # Setelah kita mendapatkan seluruh User ID yang belum diikuti oleh User yang login, saatnya kita menyajikan datanya kepada User. Untuk hal ini, simpel saja, kita gunakan kode berikut:\nreturn response()-\u0026gt;json([ // Get the User who haven\u0026#39;t folowwed the Logged In User \u0026#39;users\u0026#39; =\u0026gt; User::query() -\u0026gt;whereNotIn(\u0026#39;id\u0026#39;, $usersFollowed) -\u0026gt;get(), ]); Fungsi whereNotIn merepresentasikan klausa SQL WHERE NOT IN yang menerima sebuah array. Jika diterjemahkan ke dalam bahasa manusia, arti dari kode di atas kurang lebih \u0026ldquo;Ambil data User dengan ID YANG BUKAN DARI ID (1, 2, 3, \u0026hellip;n)\u0026rdquo;. Setelah itu, kita sajikan ke User.\nSpoiler: Kode lengkap UserController \u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use Illuminate\\Http\\Request; class UserController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request) { $loggedInUser = $request-\u0026gt;user(); // Get the User IDs who followed the Logged In User $usersFollowed = $loggedInUser -\u0026gt;followings -\u0026gt;pluck(\u0026#39;following_id\u0026#39;); // Add the Logged In User ID to the array $usersFollowed[] = $loggedInUser-\u0026gt;id; return response()-\u0026gt;json([ // Get the User who haven\u0026#39;t folowwed the Logged In User \u0026#39;users\u0026#39; =\u0026gt; User::query() -\u0026gt;whereNotIn(\u0026#39;id\u0026#39;, $usersFollowed) -\u0026gt;get(), ]); } } Mendaftarkan Rute User Controller ke routes/api.php # Tambahkan kode ini di bawah Rute following yang ada di file routes/api.php:\nRoute::get(\u0026#39;/users\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\UserController::class); Spoiler: Kode lengkap sementara routes/api.php \u0026lt;?php use Illuminate\\Support\\Facades\\Route; Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;v1\u0026#39;], function () { Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;auth\u0026#39;], function () { Route::post(\u0026#39;/register\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\RegisterController::class); Route::post(\u0026#39;/login\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\LoginController::class); Route::post(\u0026#39;/logout\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\LogoutController::class) -\u0026gt;middleware(\u0026#39;auth:sanctum\u0026#39;); }); Route::group([\u0026#39;middleware\u0026#39; =\u0026gt; \u0026#39;auth:sanctum\u0026#39;], function () { Route::get(\u0026#39;/posts\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;index\u0026#39;]); Route::post(\u0026#39;/posts\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;store\u0026#39;]); Route::delete(\u0026#39;/posts/{post}\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;destroy\u0026#39;]) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post not found\u0026#39;, ]); }); Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;user\u0026#39;], function () { Route::post(\u0026#39;/{user:username}/follow\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\FollowController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); Route::delete(\u0026#39;/{user:username}/unfollow\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\UnfollowController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); Route::put(\u0026#39;/{user:username}/accept\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\AcceptController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); Route::get(\u0026#39;/{user:username}/followers\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\FollowerController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); }); Route::get(\u0026#39;/following\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\FollowingController::class); Route::get(\u0026#39;/users\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\UserController::class); }); }); Mendapatkan Detail User # Pada bagian ini, kita akan menambahkan beberapa field Response yang lumayan sulit jika tidak diteliti dengan baik. Field Response tambahannya adalah:\nis_your_account yang menandakan bahwa apakah akun yang dicari adalah Akun dari orang yang sedang Log In? following_status menandakan apakah status Follow yang diminta dari Akun yang dicari ini adalah not-following (belum follow), requested (sudah follow tapi belum diterima), serta following (sudah follow dan sudah diterima) posts_count banyaknya postingan pada akun yang dicari followers_count banyaknya user yang mengikuti Akun yang dicari followings_count banyaknya user yang diikuti oleh Akun yang dicari posts detail dari postingan Dengan field Response tambahan sebanyak itu, perlu Anda ketahui bahwa sebagian besar tambahannya dapat dilakukan dengan cara mudah dan menggunakan fitur yang sudah disediakan Laravel.\nNamun perlu diketahui juga bahwa kita memiliki kondisi tambahan yaitu \u0026ldquo;Field posts should be hidden if the user is a private user and the follow status is not following or is requested\u0026rdquo;. Jadi, kita terjemahkan dulu ke poin-poin penting bahwa field posts harus disembunyikan jika:\nis_private dari user yang dicari adalah true following_status dari user yang dicari adalah not-following atau requested. Jadi bisa disimpulkan bahwa field posts tidak akan disembunyikan jika following_status-nya adalah following. Sudah kebayang gimana kodenya? Santai, gampang kok, kita coba koding pelan-pelan dari sini.\nMembuat UserService # Sama seperti pada saat kita membuat FollowService, kita akan membuat UserService.\nBuat file baru bernama UserService.php pada folder app/Services, lalu tambahkan kode berikut:\n\u0026lt;?php namespace App\\Services; use App\\Models\\User; class UserService { // } Kode Cek Akun yang Dicari adalah Akun Sendiri # Kode berikut digunakan untuk mengecek apakah User yang sedang dicari adalah milik diri sendiri (User yang login):\n/** * Checks if the given user is the same as the logged-in user. * * @param \\App\\Models\\User $loggedInUser The currently logged-in user. * @param \\App\\Models\\User $userSearched The user being searched or viewed. * @return bool Returns true if the given user is the same as the logged-in user, otherwise false. */ public static function isOwnAccount(User $loggedInUser, User $userSearched): bool { return $loggedInUser-\u0026gt;id == $userSearched-\u0026gt;id; } Kode Cek Status Following # Kode berikut digunakan untuk mendapatkan status antara User yang sedang login dan User yang sedang dicari:\n/** * Gets the following status between the logged-in user and the searched user. * * @param \\App\\Models\\User $loggedInUser The currently logged-in user. * @param \\App\\Models\\User $userSearched The user being searched or viewed. * @return string Returns the following status: * - \u0026#39;not-following\u0026#39; if the logged-in user is not following the searched user. * - \u0026#39;following\u0026#39; if the logged-in user is following the searched user and the follow request is accepted. * - \u0026#39;requested\u0026#39; if the logged-in user has sent a follow request to the searched user, pending acceptance. */ public static function getFollowingStatus(User $loggedInUser, User $userSearched): string { $follow = FollowService::findFollowingRelationship($loggedInUser, $userSearched); if (empty($follow)) return \u0026#39;not-following\u0026#39;; return $follow-\u0026gt;is_accepted ? \u0026#39;following\u0026#39; : \u0026#39;requested\u0026#39;; } Kode Cek Akun yang Dicari adalah Akun Privat # Kode berikut digunakan untuk menentukan apakah Akun User yang dicari adalah akun privat.\n/** * Checks if the searched user\u0026#39;s account is private. * * @param \\App\\Models\\User $userSearched The user being searched or viewed. * @return bool Returns true if the searched user\u0026#39;s account is private, otherwise false. */ public static function isPrivate(User $userSearched): bool { return $userSearched-\u0026gt;is_private; } Kode Cek Apakah Status not-following atau requested # Kode berikut digunakan untuk menentukan apakah status Following antara User yang login dan User yang dicari adalah not-following atau requested.\n/** * Checks if the given status indicates that the user is not following or has sent a follow request. * * @param string $status The status to check. * @return bool Returns true if the status indicates that the user is not following or has sent a follow request, otherwise false. */ public static function isStatusNotFollowingOrRequested(string $status): bool { return in_array($status, [\u0026#39;not-following\u0026#39;, \u0026#39;requested\u0026#39;]); } Setelah seluruh kode UserService diimplementasi, kurang lebih lengkapnya adalah sebagai berikut:\nSpoiler: Kode lengkap UserService \u0026lt;?php namespace App\\Services; use App\\Models\\User; class UserService { /** * Checks if the given user is the same as the logged-in user. * * @param \\App\\Models\\User $loggedInUser The currently logged-in user. * @param \\App\\Models\\User $userSearched The user being searched or viewed. * @return bool Returns true if the given user is the same as the logged-in user, otherwise false. */ public static function isOwnAccount(User $loggedInUser, User $userSearched): bool { return $loggedInUser-\u0026gt;id == $userSearched-\u0026gt;id; } /** * Gets the following status between the logged-in user and the searched user. * * @param \\App\\Models\\User $loggedInUser The currently logged-in user. * @param \\App\\Models\\User $userSearched The user being searched or viewed. * @return string Returns the following status: * - \u0026#39;not-following\u0026#39; if the logged-in user is not following the searched user. * - \u0026#39;following\u0026#39; if the logged-in user is following the searched user and the follow request is accepted. * - \u0026#39;requested\u0026#39; if the logged-in user has sent a follow request to the searched user, pending acceptance. */ public static function getFollowingStatus(User $loggedInUser, User $userSearched): string { $follow = FollowService::findFollowingRelationship($loggedInUser, $userSearched); if (empty($follow)) return \u0026#39;not-following\u0026#39;; return $follow-\u0026gt;is_accepted ? \u0026#39;following\u0026#39; : \u0026#39;requested\u0026#39;; } /** * Checks if the searched user\u0026#39;s account is private. * * @param \\App\\Models\\User $userSearched The user being searched or viewed. * @return bool Returns true if the searched user\u0026#39;s account is private, otherwise false. */ public static function isPrivate(User $userSearched): bool { return $userSearched-\u0026gt;is_private; } /** * Checks if the given status indicates that the user is not following or has sent a follow request. * * @param string $status The status to check. * @return bool Returns true if the status indicates that the user is not following or has sent a follow request, otherwise false. */ public static function isStatusNotFollowingOrRequested(string $status): bool { return in_array($status, [\u0026#39;not-following\u0026#39;, \u0026#39;requested\u0026#39;]); } } Membuat Invokable Controller UserDetailController # Seperti halnya kita ingin membuat fitur baru, kita mulai dengan pembuatan Controller dulu. Jalankan perintah ini pada command line:\nphp artisan make:controller Api/v1/User/UserDetailController --invokable\nSetelah itu, buka file UserDetailController dan tambahkan Parameter User $user setelah Request $request pada fungsi __invoke().\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use Illuminate\\Http\\Request; class UserDetailController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { return response()-\u0026gt;json($user); } } Memuat data posts # Saat ini, kita sudah pernah membuat relasi post pada Model User, namun karena Model User dapat memiliki banyak Post, kita secara tidak sengaja memiliki typo pada Model User. Perhatikan kode berikut pada Model User:\n\u0026lt;?php namespace App\\Models; // use Illuminate\\Contracts\\Auth\\MustVerifyEmail; use Illuminate\\Database\\Eloquent\\Factories\\HasFactory; use Illuminate\\Database\\Eloquent\\Relations\\HasMany; use Illuminate\\Foundation\\Auth\\User as Authenticatable; use Illuminate\\Notifications\\Notifiable; use Laravel\\Sanctum\\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; public function post(): HasMany { return $this-\u0026gt;hasMany(Post::class); } Secara sekilas tidak ada yang salah, namun jika diteliti lagi, mengikuti standar Naming Convention Laravel, relasi post seharusnya bernama posts karena Model User dapat memiliki lebih dari 1 Post. Ubah nama fungsi relasi post menjadi posts lalu ikuti langkah selanjutnya.\nSetelah memperbaiki typo, sekarang kita akan memuat data postingan dari User yang kita cari. Kembali ke file UserDetailController, tambahkan kode berikut:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use Illuminate\\Http\\Request; class UserDetailController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { $user-\u0026gt;load([ \u0026#39;posts\u0026#39;, ]); return response()-\u0026gt;json($user); } } Fungsi dari $user-\u0026gt;load(...$relations) adalah memuat data relasi yang didefinisikan pada Mode User, kode ini hampir sama dengan fungsi statis User::with(), namun karena Model User sudah kita definisikan sebagai Parameter pada fungsi __invoke sebagai Model Binding, sehingga data User sudah diambil secara otomatis, kita hanya perlu memuat data tambahannya saja.\nMemuat Statistik Jumlah followers, followings, posts # Sama seperti kode sebelumnya, namun sedikit berbeda, kita perlu menggunakan fungsi $user-\u0026gt;loadCount(...$relations). Kodenya adalah sebagai berikut:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use Illuminate\\Http\\Request; class UserDetailController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { $user-\u0026gt;load([ \u0026#39;posts\u0026#39;, ]); $user-\u0026gt;loadCount([ \u0026#39;posts\u0026#39;, \u0026#39;followers\u0026#39;, \u0026#39;followings\u0026#39;, ]); return response()-\u0026gt;json($user); } } Menambahkan Field is_your_account # Untuk menambahkan field is_your_account ke Response JSON yang dibutuhkan oleh User, kita hanya butuh menambahkan field is_your_account pada variabel $user, dengan value yang kita dapatkan dari memanggil UserService::isOwnAccount. Kodenya adalah sebagai berikut:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use App\\Services\\UserService; use Illuminate\\Http\\Request; class UserDetailController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { $user-\u0026gt;load([ \u0026#39;posts\u0026#39;, ]); $user-\u0026gt;loadCount([ \u0026#39;posts\u0026#39;, \u0026#39;followers\u0026#39;, \u0026#39;followings\u0026#39;, ]); $user[\u0026#39;is_your_account\u0026#39;] = UserService::isOwnAccount($request-\u0026gt;user(), $user); return response()-\u0026gt;json($user); } } Menambahkan Field following_status # Field following_status digunakan untuk menentukan apakah User yang sedang login memiliki relasi Follow kepada User yang dicari. Jika tidak ada relasi, maka field harus memiliki value not-following. Jika ada relasi, dua kemungkinan akan terjadi: requested jika belum diterima, following jika sudah diterima. Kita hanya perlu memanggil UserService::getFollowingStatus. Kodenya adalah sebagai berikut:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use App\\Services\\UserService; use Illuminate\\Http\\Request; class UserDetailController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { $user-\u0026gt;load([ \u0026#39;posts\u0026#39;, ]); $user-\u0026gt;loadCount([ \u0026#39;posts\u0026#39;, \u0026#39;followers\u0026#39;, \u0026#39;followings\u0026#39;, ]); $user[\u0026#39;is_your_account\u0026#39;] = UserService::isOwnAccount($request-\u0026gt;user(), $user); $user[\u0026#39;following_status\u0026#39;] = UserService::getFollowingStatus($request-\u0026gt;user(), $user); return response()-\u0026gt;json($user); } } Menyembunyikan Field posts Jika Akun Privat, atau Status bukan following # Untuk pertama kalinya, kita akan menggunakan 2 fungsi disaat yang bersamaan. Fungsi tersebut adalah UserService::isPrivate($user) dan UserService::isStatusNotFollowingOrRequested. Kodenya adalah sebagai berikut:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use App\\Services\\UserService; use Illuminate\\Http\\Request; class UserDetailController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { $user-\u0026gt;load([ \u0026#39;posts\u0026#39;, ]); $user-\u0026gt;loadCount([ \u0026#39;posts\u0026#39;, \u0026#39;followers\u0026#39;, \u0026#39;followings\u0026#39;, ]); $user[\u0026#39;is_your_account\u0026#39;] = UserService::isOwnAccount($request-\u0026gt;user(), $user); $user[\u0026#39;following_status\u0026#39;] = UserService::getFollowingStatus($request-\u0026gt;user(), $user); if (UserService::isPrivate($user) || UserService::isStatusNotFollowingOrRequested($user[\u0026#39;following_status\u0026#39;])) { unset($user-\u0026gt;posts); } return response()-\u0026gt;json($user); } } Alasan kenapa kita menggunakan kode\nif (UserService::isPrivate($user) || UserService::isStatusNotFollowingOrRequested($user[\u0026#39;following_status\u0026#39;])) { unset($user-\u0026gt;posts); } adalah karena kemudahan keterbacaan. Jika kita menulis semua fungsi pengecekannya, kurang lebih kodenya adalah sebagai berikut:\nif ($user-\u0026gt;is_private || in_array($status, [\u0026#39;not-following\u0026#39;, \u0026#39;requested\u0026#39;])) { unset($user-\u0026gt;posts); } Kodenya mungkin lebih singkat, namun kita perlu waktu untuk membaca semuanya secara bersamaan.\nAkhir Kata # Kita sudah selesai mengerjakan modul Back-End dari Modul 3. Selanjutnya, kita akan belajar cara untuk menggunakan Vue.js sebagai Front-End. Terima kasih atas semangat dan ketekunan kalian dalam membaca seri artikel kali ini. Sampai bertemu di artikel selanjutnya 👋.\nKode Lengkap.\n","date":"6 Mei 2024","externalUrl":null,"permalink":"/artikel/2024/05/06/lks-provinsi-modul-3-step-5/","section":"Artikel-artikel","summary":"","title":"Tutorial Mengerjakan Soal Modul 3 LKS Provinsi Web Technology - User","type":"artikel"},{"content":"","date":"6 Mei 2024","externalUrl":null,"permalink":"/tags/web/","section":"Tags","summary":"","title":"Web","type":"tags"},{"content":"Halo semua, kembali lagi dengan seri artiikel Tutorial Mengerjakan Soal Modul 3 LKS Provinsi Web Technology, kali ini kita akan membahas cara membuat Back End sub-modul Follower. Seperti biasa, siapkan air minum dan mental, karena tutorial kali ini sama panjangnya seperti artikel-artikel sebelumnya.\nReady to code? Check them out!\nMenerima Follower # Kali ini, karena Model Follow sudah dibuat dan User bisa meminta Follow ke User lain, saatnya kita membuat fitur untuk menerima Follower. Studi kasus kali ini adalah:\nUser A User B Meminta Follow Request ke User B Menerima Follow Request dari User A User A tidak login User B yang login Saat ini, kita gunakan satu studi kasus dulu, supaya kalian tidak kewalahan. Langsung saja masuk ke tahap selanjutnya.\nMembuat Invokable Controller Api/v1/User/AcceptController # Seperti biasa, kita membuat Controller dengan mengeksekusi perintah ini:\nphp artisan make:controller Api/v1/User/AcceptController --invokable\nBuka file app/Http/Controllers/Api/v1/User/AcceptController dan kita akan menambahkan beberapa fitur pengecekan terlebih dahulu.\nMengecek Jika User A tidak Follow User B # Pertama-tama, kita harus mengecek apakah User A (user yang tidak login), sudah Follow User B (user yang login). Untuk itu, kita harus mengecek data dari tabel follows dengan kondisi secara terbalik.\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use Illuminate\\Http\\Request; class AcceptController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { $follower = Follow::query() -\u0026gt;where(\u0026#39;follower_id\u0026#39;, $user-\u0026gt;id) -\u0026gt;where(\u0026#39;following_id\u0026#39;, $request-\u0026gt;user()-\u0026gt;id) -\u0026gt;first(); if (empty($follower)) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;The user is not following you\u0026#39;, ], 422); } } } Saat ini, jika User A (yang meminta Follow ke User B) belum mem-Follow User A, maka Response yang dikembalikan akan terlihat seperti berikut:\n{ \u0026#34;message\u0026#34;: \u0026#34;The user is not following you\u0026#34; } Memindahkan Logika Pengecekan Tidak Follow ke FollowService # Karena kita menerapkan prinsip One Responsibility, pindahkan logika pengecekan Tidak Follow ke FollowService. Tambahkan kode berikut ke file FollowService:\n/** * Finds the follow relationship where the given user is the follower and the other user is being followed. * * @param \\App\\Models\\User $loggedInUser The user who logged in * @param \\App\\Models\\User $follower The user who is potentially following. * @return \\App\\Models\\Follow|null Returns the Follow model instance representing the follow relationship if found, otherwise null. */ public static function findFollowingRelationship(User $loggedInUser, User $follower): ?Follow { return Follow::query() -\u0026gt;where(\u0026#39;follower_id\u0026#39;, $follower-\u0026gt;id) -\u0026gt;where(\u0026#39;following_id\u0026#39;, $loggedInUser-\u0026gt;id) -\u0026gt;first(); } /** * Checks if a user is not following another user. * * @param \\App\\Models\\User $loggedInUser The user who logged in * @param \\App\\Models\\User $follower The user who is potentially following. * @return bool Returns true if the $following user is following the $follower user, otherwise false. */ public static function isUserNotFollowing(User $loggedInUser, User $follower): bool { return empty(self::findFollowingRelationship($loggedInUser, $follower)); } Fungsi findFollowingRelationship adalah kode untuk mengambil data dari database. Sementara fungsi isUserNotFollowing adalah kode untuk menentukan apakah User A sudah mengikuti User B.\nMengubah Kode Pengecekan pada AcceptController # Setelah kode Pengecekan Tidak Follow dipindah, kita modifikasi kode if yang tadi kita buat menjadi seperti ini:\nif (FollowService::isUserNotFollowing($request-\u0026gt;user(), $user)) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;The user is not following you\u0026#39;, ], 422); } Mengecek Jika Permintaan Follow Sudah Diterima # Kita tambahkan kode berikut setelah blok kode if yang ada di AcceptController:\n$following = FollowService::findFollowingRelationship($request-\u0026gt;user(), $user); if ($following?-\u0026gt;is_accepted ?? false) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Follow request is already accepted\u0026#39;, ], 422); } Ingat bahwa kita sudah pernah membuat fungsi findFollowingRelationship pada FollowService, karena logika yang kita buat kurang lebih sama, maka kita gunakan kode tersebut lagi.\nKarena hasil return dari findFollowingRelationship bisa saja mereturn Model Follow ataupun null jika data tidak ditemukan, maka kita gunakan fitur Null Safe operator untuk mendapatkan kolom is_accepted. Jika data Model Follow ada dan permintaan Follow sudah diterima oleh User B (user yang login), maka Controller akan memberikan pesan bahwa \u0026ldquo;Permintaan Follow dari User A sudah diterima\u0026rdquo;.\nMemindahkan Kode Pengecekan Permintaan Follow # Sekali lagi, karena $following?-\u0026gt;is_accepted terlalu abstrak bagi manusia, kita pindahkan kode ini ke suatu fungsi bernama isFollowRequestAlreadyAccepted ke FollowService. Dengan merepresentasikan kode ke dalam bentuk bahasa yang dipahami manusia, sesama pengembang dapat saling mengerti apa yang ingin dilakukan pengembang sebelumnya dalam kode ini.\nTambahkan kode berikut ke FollowService:\n/** * Checks if a follow request between two users has already been accepted. * * @param \\App\\Models\\User $loggedInUser The user who logged in * @param \\App\\Models\\User $follower The user who is potentially following. * @return bool Returns true if the follow request has already been accepted, otherwise false. */ public static function isFollowRequestAlreadyAccepted(User $loggedInUser, User $follower): bool { $following = self::findFollowingRelationship($loggedInUser, $follower); return $following?-\u0026gt;is_accepted ?? false; } Mengubah Kode Pengcekan Permintaan Follow pada AcceptController # Karena kode yang tadi sudah dipindahkan ke FollowController, kita tinggal mengubah kode Pengecekan Permintaan Follow kita menjadi:\nif (FollowService::isFollowRequestAlreadyAccepted($request-\u0026gt;user(), $user)) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Follow request is already accepted\u0026#39;, ], 422); } Response yang diterima User pada saat User B (user yang login) menerima permintaan Follow User A (user yang memberikan permintaan Follow ke User B) adalah sebagai berikut:\n{ \u0026#34;message\u0026#34;: \u0026#34;Follow request is already accepted\u0026#34; } Menerima Permintaan Follow # Setelah kedua pengecekan sudah dibuat, saatnya kita membuat kode yang digunakan untuk menerima permintaan Follow dari User A (user yang meminta follow ke User B) ke User B (user yang login).\nSeperti biasa kita gunakan blok try-catch:\ntry { $following = self::findFollowingRelationship($request-\u0026gt;user(), $user); $following-\u0026gt;update([ \u0026#39;is_accepted\u0026#39; =\u0026gt; true, ]); } catch (\\Throwable $th) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Accepting follow request failed: \u0026#39; . $th-\u0026gt;getMessage(), ], 500); } return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Follow request accepted\u0026#39;, ]); Memindahkan Kode Penerimaan Permintaan Follow ke FollowService # Pindahkan kode yang ada di blok try ke fungsi accept dalam FollowService, kodenya adalah sebagai berikut:\n/** * Accepts a follow request between two users. * * @param \\App\\Models\\User $loggedInUser The user who logged in * @param \\App\\Models\\User $follower The user who is potentially following. * @return bool Returns true if the follow request is successfully accepted, otherwise false. */ public static function accept(User $loggedInUser, User $follower): bool { $following = self::findFollowingRelationship($loggedInUser, $follower); return $following-\u0026gt;update([ \u0026#39;is_accepted\u0026#39; =\u0026gt; true, ]); } Mengubah Kode Penerimaan pada AcceptController # Setelah memindahkan Kode Penerimaan Permintaan Follow ke FollowService, kita harus mengubah kode yang ada di blok try menjadi seperti ini:\nFollowService::accept($request-\u0026gt;user(), $user); Kode lengkap AcceptController adalah kurang lebih seperti ini:\nSpoiler: Kode lengkap AcceptController \u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use App\\Services\\FollowService; use Illuminate\\Http\\Request; class AcceptController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { if (FollowService::isUserNotFollowing($request-\u0026gt;user(), $user)) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;The user is not following you\u0026#39;, ], 422); } if (FollowService::isFollowRequestAlreadyAccepted($request-\u0026gt;user(), $user)) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Follow request is already accepted\u0026#39;, ], 422); } try { FollowService::accept($request-\u0026gt;user(), $user); } catch (\\Throwable $th) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Accepting follow request failed: \u0026#39; . $th-\u0026gt;getMessage(), ], 500); } return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Follow request accepted\u0026#39;, ]); } } Mendaftarkan Rute Penerimaan Permintaan Follow # Controller yang sudah kita buat masih belum dapat digunakan oleh User, karena itu kita harus membuat Rutenya di routes/api.php. Tambahkan kode berikut di Rute Grup Middleware auth:sanctum dalam prefix user:\nRoute::put(\u0026#39;/{user:username}/accept\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\AcceptController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); Kode lengkap routes/api.php saat ini adalah seperti ini:\nSpoiler: Kode lengkap routes/api.php \u0026lt;?php use Illuminate\\Support\\Facades\\Route; Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;v1\u0026#39;], function () { Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;auth\u0026#39;], function () { Route::post(\u0026#39;/register\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\RegisterController::class); Route::post(\u0026#39;/login\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\LoginController::class); Route::post(\u0026#39;/logout\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\LogoutController::class) -\u0026gt;middleware(\u0026#39;auth:sanctum\u0026#39;); }); Route::group([\u0026#39;middleware\u0026#39; =\u0026gt; \u0026#39;auth:sanctum\u0026#39;], function () { Route::get(\u0026#39;/posts\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;index\u0026#39;]); Route::post(\u0026#39;/posts\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;store\u0026#39;]); Route::delete(\u0026#39;/posts/{post}\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;destroy\u0026#39;]) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post not found\u0026#39;, ]); }); Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;user\u0026#39;], function () { Route::post(\u0026#39;/{user:username}/follow\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\FollowController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); Route::delete(\u0026#39;/{user:username}/unfollow\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\UnfollowController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); Route::put(\u0026#39;/{user:username}/accept\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\AcceptController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); }); Route::get(\u0026#39;/following\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\FollowingController::class); }); }); Melihat Seluruh Follower dari Seorang User # Pada tahap ini, kita akan membuat fitur untuk melihat seluruh Follower dari seorang User.\nMembuat Invokable Controller Api/v1/User/FollowerController # Kita jalankan perintah berikut pada command line:\nphp artisan make:controller Api/v1/User/FollowerController --invokable\nMembuat Fungsi followers di FollowingService # Karena tujuan dari kode yang akan kita buat kurang lebih sama dengan fungsi following yang sudah ada dalam FollowingService, kita hanya butuh mencopasnya ke dalam fungsi baru yang bernama followers. Selain itu, alih-alih menggunakan atribut followings, kita akan gunakan atribut followers.\n/** * Retrieves the users who are following the given user. * * @param \\App\\Models\\User $user The user whose followers are to be retrieved. * @return \\Illuminate\\Suppoer\\Collection Returns a collection of users who are following the given user. */ public static function followers(User $user): Collection { /** @var \\Illuminate\\Database\\Eloquent\\Collection */ $followers = $user-\u0026gt;followers; return $followers -\u0026gt;map(function (Follow $follow) use ($user) { $userData = $follow-\u0026gt;follower; $userData[\u0026#39;is_requested\u0026#39;] = self::isRequested($follow-\u0026gt;follower, $user); return $userData; }) -\u0026gt;values(); } Mengimplementasikan Kode FollowService::followers ke FollowerController # Implementasikan kode berikut ke fungsi __invoke di FollowController:\nreturn response()-\u0026gt;json([ \u0026#39;followers\u0026#39; =\u0026gt; FollowService::followers($user), ]); Yap betul, kita tidak perlu kode yang terlalu panjang untuk FollowController, cukup 3 baris saja untuk mendapatkan data Follower dari seseorang, kurang lebih Responsenya adalah sebagai berikut:\n{ \u0026#34;followers\u0026#34;: [ { \u0026#34;id\u0026#34;: 1, \u0026#34;full_name\u0026#34;: \u0026#34;Aghits Nidallah\u0026#34;, \u0026#34;username\u0026#34;: \u0026#34;irlnidallah\u0026#34;, \u0026#34;bio\u0026#34;: \u0026#34;Seorang full-stack developer, mahasiswa, dan {manusia,robot} yang hidup dengan menikmati kopi ☕️.\u0026#34;, \u0026#34;is_private\u0026#34;: false, \u0026#34;created_at\u0026#34;: \u0026#34;2024-05-03T21:36:32.000000Z\u0026#34;, \u0026#34;updated_at\u0026#34;: \u0026#34;2024-05-03T21:36:32.000000Z\u0026#34;, \u0026#34;is_requested\u0026#34;: true } ] } Mendaftarkan Rute Follower # Seperti biasa, Controller yang sudah kita buat harus diimplementasikan di routes/api.php. Tambahkan kode berikut di Rute Grup Middleware auth:sanctum dalam prefix user:\nRoute::get(\u0026#39;/{user:username}/followers\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\FollowerController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); Kode lengkap routes/api.php saat ini adalah seperti ini:\nSpoiler: Kode lengkap routes/api.php \u0026lt;?php use Illuminate\\Support\\Facades\\Route; Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;v1\u0026#39;], function () { Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;auth\u0026#39;], function () { Route::post(\u0026#39;/register\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\RegisterController::class); Route::post(\u0026#39;/login\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\LoginController::class); Route::post(\u0026#39;/logout\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\LogoutController::class) -\u0026gt;middleware(\u0026#39;auth:sanctum\u0026#39;); }); Route::group([\u0026#39;middleware\u0026#39; =\u0026gt; \u0026#39;auth:sanctum\u0026#39;], function () { Route::get(\u0026#39;/posts\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;index\u0026#39;]); Route::post(\u0026#39;/posts\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;store\u0026#39;]); Route::delete(\u0026#39;/posts/{post}\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;destroy\u0026#39;]) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post not found\u0026#39;, ]); }); Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;user\u0026#39;], function () { Route::post(\u0026#39;/{user:username}/follow\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\FollowController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); Route::delete(\u0026#39;/{user:username}/unfollow\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\UnfollowController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); Route::put(\u0026#39;/{user:username}/accept\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\AcceptController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); Route::get(\u0026#39;/{user:username}/followers\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\FollowerController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); }); Route::get(\u0026#39;/following\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\FollowingController::class); }); }); Akhir Kata # Begitulah kode yang digunakan untuk mengimplementasikan Penerimaan Permintaan Follow dan Melihat seluruh Follower dari seorang User untuk saat ini. Kali ini artikelnya lumayan singkat, jadi bisa bernafas lega dulu untuk sementara. Jangan lupa untuk minum air, lakukan peregangan, dan jaga kesehatan.\nJumpa lagi di tutorial selanjutnya!\nKode lengkap.\n","date":"5 Mei 2024","externalUrl":null,"permalink":"/artikel/2024/05/05/lks-provinsi-modul-3-step-4/","section":"Artikel-artikel","summary":"","title":"Tutorial Mengerjakan Soal Modul 3 LKS Provinsi Web Technology - Followers","type":"artikel"},{"content":"Halo semua! Wah sepertinya gak bosen nih ngikutin tutorial ini, semoga kesehatan dan kesejahteraan selalu ada untuk kalian semua. Seperti biasa, ambil air, dan siapkan mental untuk melalui neraka ini (bercanda ges).\nKali ini kita akan membuat fitur Following, kurang lebih dimana User bakal saling ngefollow satu sama lain. Fitur gampang tapi logika di belakangnya yang ribet, jadi kita akan hadirkan satu Kelas baru dimana dia bakal nge-handle semua Request yang kita kirim. Let\u0026rsquo;s get going!\nMembuat Model Follow dan Migrationnya # Seperti biasa, kita buat dulu Model dan Migrationnya. Udah sering buat kan? Klo udah sering, gak perlu code snippet kan ya (bercanda juga, lihat spoiler di bawah klo buntu).\nSpoiler: Kode membuat Model Follow dan Migrationnya php artisan make:model Follow -m\nUbah kode Migration # Di modul LKS kali ini, skema tabel follows kurang lebih terlihat seperti ini:\nid BIGINT UNSIGNED PRIMARY_KEY auto_increment follower_id BIGINT UNSIGNED FOREIGN_KEY following_id BIGINT UNSIGNED FOREIGN_KEY is_accepted TINYINT DEFAULT 0 Buka file Migration yang sudah digenerate tadi di database/migrations/\u0026lt;timestamp\u0026gt;_create_follows_table.php, lalu pada fungsi up() tulis kode berikut:\nSchema::create(\u0026#39;follows\u0026#39;, function (Blueprint $table) { $table-\u0026gt;id(); $table-\u0026gt;foreignId(\u0026#39;follower_id\u0026#39;)-\u0026gt;constrained(\u0026#39;users\u0026#39;, \u0026#39;id\u0026#39;); $table-\u0026gt;foreignId(\u0026#39;following_id\u0026#39;)-\u0026gt;constrained(\u0026#39;users\u0026#39;, \u0026#39;id\u0026#39;); $table-\u0026gt;boolean(\u0026#39;is_accepted\u0026#39;)-\u0026gt;default(false); $table-\u0026gt;timestamps(); }); Nah kali ini, kita punya dua kolom yang memiliki relasi ke tabel yang sama, yaitu kolom follower_id dan following_id. Kali ini juga kita gunakan kedua argumen dari modifier constrained(string $table, string $column) untuk mendeklarasikan nama tabel dan nama kolom secara eksplisit, karena jika mengikuti Laravel Naming Convention, modifier constrained akan secara otomatis mencari tabel followers dan following.\nSetelah itu, seperti biasa kita jalankan php artisan migrate pada command line.\nUbah kode Model Follow # Buka file app/Models/Follow.php, tambahkan atribut $fillable dahulu sesuai nama kolom-kolom yang dibuat pada Migration tadi.\nSpoiler: Kode atribut `$fillable` model Follow \u0026lt;?php namespace App\\Models; use Illuminate\\Database\\Eloquent\\Factories\\HasFactory; use Illuminate\\Database\\Eloquent\\Model; class Follow extends Model { use HasFactory; protected $fillable = [ \u0026#39;follower_id\u0026#39;, \u0026#39;following_id\u0026#39;, \u0026#39;is_accepted\u0026#39;, ]; } Jangan lupa juga, karena kolom is_accepted yang kita buat tadi, kita harus memasukkan kolom tersebut ke atribut $casts dengan cast value boolean. Masih ingat caranya? Jika lupa, silahkan kembali ke Tutorial ke-1 bagian Penyesuaian Model User.\nJika sudah menambahkan atribut $casts, tambahkan fungsi Relasi untuk follower dan following seperti berikut:\n\u0026lt;?php namespace App\\Models; use Illuminate\\Database\\Eloquent\\Factories\\HasFactory; use Illuminate\\Database\\Eloquent\\Model; use Illuminate\\Database\\Eloquent\\Relations\\BelongsTo; class Follow extends Model { use HasFactory; public function follower(): BelongsTo { return $this-\u0026gt;belongsTo(User::class, \u0026#39;follower_id\u0026#39;, \u0026#39;id\u0026#39;); } public function following(): BelongsTo { return $this-\u0026gt;belongsTo(User::class, \u0026#39;following_id\u0026#39;, \u0026#39;id\u0026#39;); } protected $fillable = [ \u0026#39;follower_id\u0026#39;, \u0026#39;following_id\u0026#39;, \u0026#39;is_accepted\u0026#39;, ]; } Spoiler: Kode lengkap Model Follow (jangan buka sebelum mengimplementasikan atribut $casts) \u0026lt;?php namespace App\\Models; use Illuminate\\Database\\Eloquent\\Factories\\HasFactory; use Illuminate\\Database\\Eloquent\\Model; use Illuminate\\Database\\Eloquent\\Relations\\BelongsTo; class Follow extends Model { use HasFactory; public function follower(): BelongsTo { return $this-\u0026gt;belongsTo(User::class, \u0026#39;follower_id\u0026#39;, \u0026#39;id\u0026#39;); } public function following(): BelongsTo { return $this-\u0026gt;belongsTo(User::class, \u0026#39;following_id\u0026#39;, \u0026#39;id\u0026#39;); } protected $fillable = [ \u0026#39;follower_id\u0026#39;, \u0026#39;following_id\u0026#39;, \u0026#39;is_accepted\u0026#39;, ]; protected $casts = [ \u0026#39;is_accepted\u0026#39; =\u0026gt; \u0026#39;boolean\u0026#39;, ]; } Ubah kode Model User # Tidak hanya memodifikasi Model Follow yang kita buat tadi, kita juga harus menyesuaikan Model User untuk menerima relasi baru dari Model Follow. Tambahkan kode berikut setelah fungsi post(): HasMany:\npublic function followers(): HasMany { return $this-\u0026gt;hasMany(Follow::class, \u0026#39;following_id\u0026#39;, \u0026#39;id\u0026#39;); } public function followings(): HasMany { return $this-\u0026gt;hasMany(Follow::class, \u0026#39;follower_id\u0026#39;, \u0026#39;id\u0026#39;); } Sama seperti modifier constrained yang kita buat tadi di Migration, relasi hasMany juga menerima 3 parameter yaitu: Class Name, foreign_key, local_key. Karena Model Follow yang kita buat tadi tidak mengikut standar Laravel Naming Convention, kita harus mengisi parameter ke-2 dan ke-3 yang masing-masing berguna untuk meng-override naming convention yang sudah ditentukan.\nUntuk Relasi followers kita gunakan Foreign Key following_id, karena jika kita membiarkan ini kosong, Laravel akan secara otomatis menjari kolom user_id pada tabel followers. Karena nama tabel yang kita buat bukanlah followers dan tidak ada kolom user_id di dalamnya, makanya harus kita override. Begitu juga dengan Relasi followings.\nKalau Anda teliti, pasti Anda menyadari bahwa fungsi followers menggunakan Foreign Key following_id bukan follower_id. Kenapa demikian? Karena jika dilihat dari perspektif Anda (pengguna), followers adalah orang yang mengikuti Anda. Begitu juga dengan perspektif Anda (pengguna), following adalah orang-orang yang Anda ikuti.\nCukup membingungkan bukan? Jika masih bingung, lihat skema yang diberikan oleh modul LKS di bawah:\nMembuat Service FollowService # Yep, kali ini kita kedatangan file Kelas baru, yaitu Service. Service ini tidak kita buat menggunakan command line, namun Anda sendiri yang membuat Folder dan Filenya. Untuk membuat FollowService, terdapat 2 tahapan:\nBuat folder Services di directory app; Buat file FollowService.php di directory app/Services; Setelah itu, buka file yang barusan dibuat dan tulis kodenya sebagai berikut:\n\u0026lt;?php namespace App\\Services; class FollowService { } Ingat bahwa namespace App\\Services merepresentasikan dari directory yang digunakan. Pada saat file ini dibuat, file ini terdapat di directory app/Services. Karena itu kita harus menamakannya persis mengikuti penulisan PSR-4. Kita juga menuliskan class FollowService karena nama file kita bernama FollowService.php, jadi kita tuliskan nama filenya tanpa .php di akhiran.\nService FollowService ini akan kita gunakan untuk berbagai fungsi-fungsi yang seharusnya tidak ada di Controller.\nMengikuti Seseorang # Pada bagian ini, sub-modul pertama yang ditentukan oleh modul LKS adalah untuk mengikuti seseorang. Pertama-tama seperti biasa kita membuat Controllernya terlebih dahulu. Perlu diketahui bahwa sub-modul ini memiliki endpoint /api/v1/users/:username/follow.\nMembuat Controller # Saat ini kita membuat Invokable Controller, seperti biasa kita mengeksekusi perintah ini pada command line:\nphp artisan make:controller Api/v1/User/FollowController --invokable\n\u0026ldquo;Lho kenapa ada /v1/User-nya? Di tutorial sebelumnya langsung v1/NamaController\u0026rdquo;? Hal ini dikarenakan kita mengikuti standar Routing Convention. Dimana setiap Prefix merepresentasikan directory Controller itu sendiri, secara teknis hal ini memudahkan pengembang untuk mencari Controller terkait.\nSetelah mengeksekusi perintah di atas, saatnya kita memulai perkodingan.\nMenambahkan Parameter User $user ke fungsi __invoke(Request $request) # Karena kita membutuhkan Model User sebagai data seseorang yang akan kita Follow, kita harus menambahkannya setelah parameter Request $request. Kode lengkapnya adalah seperti ini:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use Illuminate\\Http\\Request; class FollowController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { // } } Mencegah Anda mengikuti diri Anda sendiri # Bukan aplikasi yang sempurna jika Anda bisa mengikuti diri Anda sendiri. Akan sangat lucu jika seseorang User dapat mengikuti dirinya sendiri, jadi kita harus mencegah hal itu supaya tidak terjadi.\nPertama-tama kita harus mengecek apakah User yang sedang login adalah user yang sama:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use Illuminate\\Http\\Request; class FollowController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { if ($request-\u0026gt;user()-\u0026gt;id == $following-\u0026gt;id) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;You are not allowed to follow yourself\u0026#39;, ], 422); } } } Sekarang kita memiliki kode yang mencegah Anda mengikuti diri Anda sendiri. Namun kode ini sulit dibaca dan sulit dimengerti, karena itu FollowService akan datang untuk membantu.\nPindahkan Pengecekan Kode ke FollowService # Buka file FollowService.php, lalu tambahkan fungsi berikut di dalam Kelas FollowService:\n/** * Checks if a user is trying to follow themselves. * * @param App\\Models\\User $follower The user attempting to follow. * @param App\\Models\\User $following The user being followed. * @return bool Returns true if the user is attempting to follow themselves, otherwise false. */ public static function isFollowingSelf(User $follower, User $following): bool { return $follower-\u0026gt;id == $following-\u0026gt;id; } Pada contoh kode di atas, bagian terpentingnya adalah kode yang dimulai dari public static function sampai dengan kurawal tutup }, kode di atas dan selebihnya dinamakan Dokumentasi atau PHPDocs. Dokumentasi membantu pengembang lainnya untuk memahami apa fungsi dari kode yang Anda tulis jikalau kode tersebut masih terlalu abstrak untuk dimengerti.\nPada fungsi isFollowingSelf ini kita memiliki 2 Parameter yaitu Model User sebagai $follower dan Model User sebagai $following. Betul sekali, Parameter sebuah fungsi dapat memiliki tipe data yang sama. Anda juga bebas memiliki berapa banyak Paremeter yang dibutuhkan, namun untuk sekarang, ikuti kode yang Saya tulis saat ini. Pastikan juga fungsi isFollowingSelf me-return boolean.\nUbah Pengecekan Kode di FollowController # Setelah memindahkan pengecekan antar dua Argumen, kita harus memodifikasi kode yang kita tulis di FollowController tadi, kurang lebih hasil jadinya adalah sebagai berikut:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use App\\Services\\FollowService; use Illuminate\\Http\\Request; class FollowController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { if (FollowService::isFollowingSelf($request-\u0026gt;user(), $user)) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;You are not allowed to follow yourself\u0026#39;, ], 422); } } } Nah, kode kita terlihat lebih mudah dibaca kan? Jika Anda masih tidak dapat membaca kode ini karena skill Bahasa Inggris yang kurang, Saya sarankan untuk belajar ekstra antara bahasa Pemrograman dan Bahasa Inggris :D\nMencegah User untuk Mengikuti User yang Sudah Diikuti # Bayangkan jika ada User yang men-spam tombol Follow lebih dari satu kali, karena terdapat XHR Request antara browser dan server, ada kemungkinan bahwa state tombol Follow belum berubah ke tombol Unfollow. Karena itu kita harus mencegah hal ini terjadi.\nMengecek Apakah User sudah mengikuti User yang Sudah Diikut # Setelah kode pencegahan untuk mengikuti diri sendiri, tambahkan kode berikut:\n$userFollowed = Follow::query() -\u0026gt;where(\u0026#39;follower_id\u0026#39;, $request-\u0026gt;user()-\u0026gt;id) -\u0026gt;where(\u0026#39;following_id\u0026#39;, $user-\u0026gt;id) -\u0026gt;first(); if (! empty($userFollowed)) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;You are already followed\u0026#39;, \u0026#39;status\u0026#39; =\u0026gt; $userFollowed-\u0026gt;is_accepted ? \u0026#39;following\u0026#39; : \u0026#39;requested\u0026#39;, ], 422); } Pertama-tama kita mengecek ke basis data apakah kita sudah mengikuti User yang akan kita ikuti. Jika data tidak kosong, maka Controller akan mengembalikan response berupa json dengan pesan \u0026ldquo;You are already followed\u0026rdquo; dengan status apakah User yang diikuti tersebut sudah menerimanya atau belum. Jika sudah diterima, maka field status akan menampilkan following, sebaliknya akan menampilkan requested.\nPindahkan Pengecekan Sudah Mengikuti ke FollowService # Karena kode di atas terlalu sulit untuk dimengerti, lagi-lagi kita pindahkan ke Service FollowService. Berikut ini adalah kodenya, mohon tambahkan kodenya setelah fungsi isFollowingSelf:\n/** * Checks if a user is already following another user. * * @param \\App\\Models\\User $follower The user who is potentially following * @param \\App\\Models\\User $following The user who is potentially being followed. * @return \\App\\Models\\Follow|null Returns the Follow model instance if the user is already following, otherwise null. */ public static function isAlreadyFollowing(User $follower, User $following): ?Follow { $userFollowed = Follow::query() -\u0026gt;where(\u0026#39;follower_id\u0026#39;, $follower-\u0026gt;id) -\u0026gt;where(\u0026#39;following_id\u0026#39;, $following-\u0026gt;id) -\u0026gt;first(); return $userFollowed; } Kurang lebih kodenya sama, namun kita harus mengembalikan data Model Follow jika ditemukan. Perhatikan bahwa return type dari fungsi isAlreadyFollowing adalah ?Follow. Tanda tanya sebelum Model Follow menandakan bahwa return type Follow adalah opsional, bisa jadi ada dan bisa jadi juga tidak ada sama sekali. Kode yang kita buat bisa jadi sudah ada datanya di basis data (akan mengembalikan Model Follow), dan bisa jadi juga belum (akan mengembalikan null).\nUbah Pengecekan Apakah Sudah Mengikuti di FollowController # Karena sudah dipindahkan, kita akan mengubah Controller kita menjadi seperti ini:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use App\\Services\\FollowService; use Illuminate\\Http\\Request; class FollowController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { if (FollowService::isFollowingSelf($request-\u0026gt;user(), $user)) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;You are not allowed to follow yourself\u0026#39;, ], 422); } if (! empty($userFollowed = FollowService::isAlreadyFollowing($request-\u0026gt;user(), $user))) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;You are already followed\u0026#39;, \u0026#39;status\u0026#39; =\u0026gt; $userFollowed-\u0026gt;is_accepted ? \u0026#39;following\u0026#39; : \u0026#39;requested\u0026#39;, ], 422); } } } Anda mungkin bertanya kenapa ada kode $userFollowed = FollowService...? Hal ini dikarenakan jika fungsi isAlreadyFollowing mengembalikan data Model Follow, kita dapat menggunakannya kembali sebagai $userFollowed. Lihat response json yang kita buat pada field status. Disitu kita menggunakan variabel $userFollowed untuk mengecek apakah permintaan Follow yang dikirim oleh pengguna sudah diterima atau belum.\nMengikuti User # Akhirnya setelah 2 pengecekan yang intens, kita masuk ke bagian yang mudahnya, yaitu menyimpan data Follow. Seperti biasa kita menggunakan blok try-catch sebagai langkah pencegahan jika terjadi error. Tambahkan kode ini setelah kode terakhir di FollowController:\ntry { $follow = Follow::create([ \u0026#39;follower_id\u0026#39; =\u0026gt; $request-\u0026gt;user()-\u0026gt;id, \u0026#39;following_id\u0026#39; =\u0026gt; $user-\u0026gt;id, ]);; } catch (\\Throwable $th) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Following user failed: \u0026#39; . $th-\u0026gt;getMessage(), ], 500); } return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Follow success\u0026#39;, \u0026#39;status\u0026#39; =\u0026gt; $follow-\u0026gt;is_accepted ? \u0026#39;following\u0026#39; : \u0026#39;requested\u0026#39;, ], 201); Seperti biasa, kita akan mengembalikan response berupa json pada saat error dengan pesan \u0026ldquo;Following user failed: Error\u0026rdquo; dan mengembalikan response yang sama dengan pesan \u0026ldquo;Follow success\u0026rdquo; jika berhasil.\nMemindahkan Kode \u0026ldquo;Ikuti\u0026rdquo; Seseorang ke FollowService # Sudah bisa dilihat polanya sekarang? Saat ini kita hanya:\nMenulis kode pada FollowController Memindahkan kode yang sulit dibaca ke FollowService dengan nama fungsi yang mudah dimengerti Mengubah kode lama di FollowController supaya menggunakan fungsi yang ada pada FollowService Kita akan mengimplementasikan fungsi follow juga sekarang. Buka file FollowService lalu tambahkan kode ini:\n/** * Creates a follow relationship between two users. * * @param \\App\\Models\\User $follower The user who is following. * @param \\App\\Models\\User $following The user who is being followed. * @return \\App\\Models\\Follow Returns the Follow model instance representing the new follow relationship. */ public static function follow(User $follower, User $following): Follow { return Follow::create([ \u0026#39;follower_id\u0026#39; =\u0026gt; $follower-\u0026gt;id, \u0026#39;following_id\u0026#39; =\u0026gt; $following-\u0026gt;id, ]); } Ubah Kode Penyimpanan data Follow di FollowController # Ubah kode pada blok try Anda ke:\n$follow = FollowService::follow($request-\u0026gt;user(), $user); Yap, semudah itu. Untuk saat ini, kita tidak perlu menyentuk FollowController lagi.\nKode lengkap FollowController \u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use App\\Services\\FollowService; use Illuminate\\Http\\Request; class FollowController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { if (FollowService::isFollowingSelf($request-\u0026gt;user(), $user)) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;You are not allowed to follow yourself\u0026#39;, ], 422); } if (! empty($userFollowed = FollowService::isAlreadyFollowing($request-\u0026gt;user(), $user))) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;You are already followed\u0026#39;, \u0026#39;status\u0026#39; =\u0026gt; $userFollowed-\u0026gt;is_accepted ? \u0026#39;following\u0026#39; : \u0026#39;requested\u0026#39;, ], 422); } try { $follow = FollowService::follow($request-\u0026gt;user(), $user); } catch (\\Throwable $th) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Following user failed: \u0026#39; . $th-\u0026gt;getMessage(), ], 500); } return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Follow success\u0026#39;, \u0026#39;status\u0026#39; =\u0026gt; $follow-\u0026gt;is_accepted ? \u0026#39;following\u0026#39; : \u0026#39;requested\u0026#39;, ], 201); } } Mendaftarkan Rute Follow # Buka file routes/api.php Anda, lalu pada grup Middleware auth:sanctum, setelah kode Rute DELETE /posts/{post} tambahkan kode berikut:\nRoute::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;user\u0026#39;], function () { Route::post(\u0026#39;/{user:username}/follow\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\FollowController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); Kita akan menggunakan prefix user sehingga URL endpoint yang akan diakses memiliki URL seperti /api/v1/user. Selanjutnya kita daftarkan Rute Follow yang mengarah ke FollowController.\nSebelumnya Anda sudah familiar dengan Named Parameter seperti /posts/{post} di tutorial sebelumnya, namun kenapa kita menggunakan /{user:username} kali ini? Hal ini dikarenakan kita akan menggunakan kolom username dari model User sebagai parameternya. Jadi alih-alih kita mengirimkan User ID nanti pada saat kita mengembangkan modul Front-Endnya, kita akan mengirimkan username-nya saja.\nKita juga gunakan modifier missing untuk mengkustomisasi pesan yang kita kirimkan ke User saat User dengan username yang dicari tidak ditemukan pada basis data.\nBerhenti Mengikuti Seseorang # Berhenti Mengikuti biasa kita kenal sebagai \u0026ldquo;Unfollow\u0026rdquo;, jika Anda belum pernah mendengar istilah ini, berarti Anda terlalu baik sampai belum pernah unfollow orang di Instagram 😜.\nMembuat Invokable Controller UnfollowController # Kita eksekusi perintah ini di command line:\nphp artisan make:controller Api/v1/User/UnfollowController --invokable\nSetelah itu, kita buka Controllernya.\nMengecek Apakah User Sudah Mengikut User yang ingin Diunfollow # Kita harus mengecek apakah User yang ingin kita unfollow ada atau tidak pada basis data. Trivia saja, untuk menghapus data yang bahkan tidak ada pada basis data sebenarnya tidak akan menyebabkan error, namun pada saat dieksekusi, DB Engine tidak akan me-return apapun. Jadi ada baiknya kita memberikan Response custom berupa pesan bahwa \u0026ldquo;Anda belum mengikuti User tersebut\u0026rdquo;.\nMenambahkan Parameter User $user ke fungsi __invoke(Request $request) # Sama seperti FollowController, kita juga harus menambahkan Parameter User $user ke fungsi __invoke(). Hal ini kita gunakan untuk mengambil data User yang dikirimkan sebagai parameter di Rute API.\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use Illuminate\\Http\\Request; class UnfollowController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { // } } Mengecek Apakah User Sudah Mengikuti # Untuk mengecek apakah User yang sedang login sudah mengikuti User yang ingin di-unfollow, tambahkan kode berikut di fungsi __invoke pada UnfollowController Anda:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use App\\Services\\FollowService; use Illuminate\\Http\\Request; class UnfollowController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { if (empty(FollowService::isAlreadyFollowing($request-\u0026gt;user(), $user))) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;You are not following the user\u0026#39;, ], 422); } } } \u0026hellip;Lho, kita tidak memindahkan kode apapun ke FollowService? Mengapa demikian? Hal ini dikarenakan fungsi isAlreadyFollowing sudah didefinisikan dan dapat digunakan dimanapun. Konsep ini dinamakan Code Reusability, yaitu konsep penggunaan ulang kode yang sekiranya berulang-ulang dan redundan dapat terjadi di file yang berbeda-beda. Code Reusability ini membantu Anda untuk mempersingkat kode dan mencegah terjadinya ketidak-konsistenan kode di antara file.\nBerhenti Mengikuti # Nah karena pengecekan sebelumnya sudah bisa langsung digunakan, maka kita tinggal buat saja fungsi Unfollownya. Seperti biasa kita akan gunakan blok try-catch:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use App\\Services\\FollowService; use Illuminate\\Http\\Request; class UnfollowController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { if (empty(FollowService::isAlreadyFollowing($request-\u0026gt;user(), $user))) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;You are not following the user\u0026#39;, ], 422); } try { Follow::query() -\u0026gt;where(\u0026#39;follower_id\u0026#39;, $request-\u0026gt;user()-\u0026gt;id) -\u0026gt;where(\u0026#39;following_id\u0026#39;, $user-\u0026gt;id) -\u0026gt;delete(); } catch (\\Throwable $th) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Unfollowing user failed: \u0026#39; . $th-\u0026gt;getMessage(), ], 500); } return response()-\u0026gt;noContent(); } } Perhatikan bahwa pada saat data berhasil dihapus, kita mengembalikan response()-\u0026gt;noContent() alih-alih response()-\u0026gt;json() seperti biasa. Kenapa? Karena kita sudah tidak memiliki data apapun yang dapat dikembalikan ke pengguna, mudahnya karena data tersebut sudah dihapus.\nMemindahkan Fungsi Unfollow ke FollowService # Yak, kita pindahkan lagi kode Unfollow kita ke FollowService, tambahkan kode berikut ke file FollowService:\n/** * Removes a follow relationship between two users. * * @param \\App\\Models\\User $follower The user who is currently following. * @param \\App\\Models\\User $following The user who is currently being followed. * @return bool Returns true if the follow relationship is successfully removed, otherwise false. */ public static function unfollow(User $follower, User $following): bool { return Follow::query() -\u0026gt;where(\u0026#39;follower_id\u0026#39;, $follower-\u0026gt;id) -\u0026gt;where(\u0026#39;following_id\u0026#39;, $following-\u0026gt;id) -\u0026gt;delete(); } Fungsi delete pada Model Query akan mengembalikan boolean, jadi hasilnya pasti antara true jika data berhasil dihapus, atau false jika gagal.\nMengubah Kode Unfollow di UnfollowController # Pada blok try, ubah kodenya menjadi seperti ini:\nFollowService::unfollow($request-\u0026gt;user(), $user); Simpel bukan?\nKode lengkap UnfollowController \u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\User; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use App\\Services\\FollowService; use Illuminate\\Http\\Request; class UnfollowController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request, User $user) { if (empty(FollowService::isAlreadyFollowing($request-\u0026gt;user(), $user))) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;You are not following the user\u0026#39;, ], 422); } try { FollowService::unfollow($request-\u0026gt;user(), $user); } catch (\\Throwable $th) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Unfollowing user failed: \u0026#39; . $th-\u0026gt;getMessage(), ], 500); } return response()-\u0026gt;noContent(); } } Mendaftarkan Rute Unfollow # Lagi-lagi kita membuka file routes/api.php untuk menambahkan Rute, memang membosankan tapi ini adalah hal yang selalu terjadi di bidang pengembangan web, jadi mohon bersabar dan ikuti saja alurnya. Semoga suatu saat Anda menjadi web developer yang jago dan bermartabak (eh bermartabat).\nTambahkan kode ini setelah Rute /{user:username}/follow\nRoute::delete(\u0026#39;/{user:username}/unfollow\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\UnfollowController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); Kode lengkap routes/api.php untuk saat ini \u0026lt;?php use Illuminate\\Support\\Facades\\Route; Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;v1\u0026#39;], function () { Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;auth\u0026#39;], function () { Route::post(\u0026#39;/register\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\RegisterController::class); Route::post(\u0026#39;/login\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\LoginController::class); Route::post(\u0026#39;/logout\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\LogoutController::class) -\u0026gt;middleware(\u0026#39;auth:sanctum\u0026#39;); }); Route::group([\u0026#39;middleware\u0026#39; =\u0026gt; \u0026#39;auth:sanctum\u0026#39;], function () { Route::get(\u0026#39;/posts\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;index\u0026#39;]); Route::post(\u0026#39;/posts\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;store\u0026#39;]); Route::delete(\u0026#39;/posts/{post}\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;destroy\u0026#39;]) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post not found\u0026#39;, ]); }); Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;user\u0026#39;], function () { Route::post(\u0026#39;/{user:username}/follow\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\FollowController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); Route::delete(\u0026#39;/{user:username}/unfollow\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\UnfollowController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); }); }); }); Mengambil Data User yang Diikuti # Ini adalah tahap terakhir di tutorial kali ini, pengingat rutin untuk minum air dan jangan patah semangat!\nMembuat Invokable Controller FollowingController # Nah, pada titik ini, jangan lupa bahwa FollowController dan FollowingController merupakan dua Controller yang berbeda. Saat ini kita hanya fokus di FollowingController. Buatlah FollowingController menggunakan perintah:\nphp artisan make:controller Api/v1/FollowingController\nKarena Controller ini akan dapat diakses secara umum, kita tidak butuh parameter apapun seperti Rute follow atau unfollow.\nAmbil Data User (Follow) yang Diikuti # Sekarang, kita harus mengambil data siapa saja pengguna yang sudah diikuti berdasarkan pengguna yang sedang login, untuk itu kita tulis kode berikut:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1; use App\\Http\\Controllers\\Controller; use Illuminate\\Http\\Request; class FollowingController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request) { /** @var \\Illuminate\\Database\\Eloquent\\Collection */ $followings = $request-\u0026gt;user()-\u0026gt;followings; return response()-\u0026gt;json([ \u0026#39;following\u0026#39; =\u0026gt; $followings, ]); } } Saat ini kode kita hanya mengembalikan data mentah dari tabel Follow, jika digambarkan, Responsenya adalah seperti berikut:\n{ \u0026#34;following\u0026#34;: [ { \u0026#34;id\u0026#34;: 6, \u0026#34;follower_id\u0026#34;: 1, \u0026#34;following_id\u0026#34;: 2, \u0026#34;is_accepted\u0026#34;: false, \u0026#34;created_at\u0026#34;: \u0026#34;2024-05-04T23:36:37.000000Z\u0026#34;, \u0026#34;updated_at\u0026#34;: \u0026#34;2024-05-04T23:36:37.000000Z\u0026#34; } ] } Tidak terlalu cantik bukan? Bahkan kita juga tidak mengetahui data apa yang dimaksud. Karena itu kode tadi akan kita modifikasi lebih lanjut menggunakan metode Eloquent Mapping.\nEloquent Mapping # Saya akan mengubah kode di atas menjadi kode berikut, akan sulit menjelaskannya satu-per-satu jadi akan Saya jelaskan sekaligus:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1; use App\\Http\\Controllers\\Controller; use Illuminate\\Http\\Request; class FollowingController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request) { /** @var \\Illuminate\\Database\\Eloquent\\Collection */ $followings = $request-\u0026gt;user()-\u0026gt;followings; return response()-\u0026gt;json([ \u0026#39;following\u0026#39; =\u0026gt; $followings -\u0026gt;map(function (Follow $follow) { $userData = $follow-\u0026gt;following; return $userData; }) -\u0026gt;values();, ]); } } Pertama-tama, kode $followings = $request-\u0026gt;user()-\u0026gt;followings akan menghasilkan sebuah Collection yang dihasilkan oleh Eloquent, karena itu dinamakan \\Illuminate\\Database\\Eloquent\\Collection yang merupakan turunan dari \\Illuminate\\Support\\Collection. Laravel sudah memperhatikan kemudahan manipulasi data sebelum penyajian. Salah satu fungsi yang sangat berguna untuk manipulasi data ini adalah fungsi map().\nFungsi map() adalah suatu fungsi yang menerima sebuah koleksi array, yang masing-masing datanya akan di-loop lalu dapat kita manipulasi. Fungsi map() menerima Callback dengan Argumen Model yang berkaitan. Karena $followings adalah koleksi dari sekumpulan Model Follow, maka kita gunakan Follow $follow sebagai Argumennya.\nDalam blok map(), kita buat satu variabel baru bernama $userData yang berisi data Model User yang diambil dari relasi following. Jika Anda lupa mengenai hal ini, silahkan buka file app/Models/Follow dan cek apakah fungsi following tersedia.\nJika pada titik ini kode Anda tidak error, maka Anda dapat melanjutkan ke tahap selanjutnya. Response yang dihasilkan dari kode ini kurang lebih seperti ini:\n{ \u0026#34;following\u0026#34;: [ { \u0026#34;id\u0026#34;: 2, \u0026#34;full_name\u0026#34;: \u0026#34;Hatsu Shiroyuki\u0026#34;, \u0026#34;username\u0026#34;: \u0026#34;hatsushiroyuki\u0026#34;, \u0026#34;bio\u0026#34;: \u0026#34;An Impostor\u0026#34;, \u0026#34;is_private\u0026#34;: false, \u0026#34;created_at\u0026#34;: \u0026#34;2024-05-04T19:46:40.000000Z\u0026#34;, \u0026#34;updated_at\u0026#34;: \u0026#34;2024-05-04T19:46:40.000000Z\u0026#34; } ] } Mengecek Apakah User yang diikuti Mengirimkan Permintaan Follow # Pada blok kode map() yang kita tulis tadi, kita harus tambahkan kode pengecekan apakah User yang kita ikuti sudah mengirimkan Permintaan Follow atau belum. Studi kasus nyata yang sering kita jumpai adalah fitur \u0026ldquo;Follow Back\u0026rdquo; atau \u0026ldquo;Follback\u0026rdquo; yang ada di Instagram. Kurang lebih seperti itu fiturnya.\nUbah kode FollowingController menjadi seperti ini:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1; use App\\Http\\Controllers\\Controller; use Illuminate\\Http\\Request; class FollowingController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request) { /** @var \\Illuminate\\Database\\Eloquent\\Collection */ $followings = $request-\u0026gt;user()-\u0026gt;followings; return response()-\u0026gt;json([ \u0026#39;following\u0026#39; =\u0026gt; $followings -\u0026gt;map(function (Follow $follow) use ($request) { $userData = $follow-\u0026gt;following; $userData[\u0026#39;is_requested\u0026#39;] = Follow::query() -\u0026gt;where(\u0026#39;follower_id\u0026#39;, $request-\u0026gt;user()-\u0026gt;id) -\u0026gt;where(\u0026#39;following_id\u0026#39;, $follow-\u0026gt;following_id) -\u0026gt;exists(); return $userData; }) -\u0026gt;values();, ]); } } Perhatikan bahwa pada Callback map() kita tambahkan use($request) setelah function(Follow $follow). Kode ini dibutuhkan supaya kita dapat menggunakan instance $request di dalam blok map().\nUntuk kode pengecekannya sendiri, simpelnya kita mengecek apakah data User yang kita follow sudah mengirimkan request \u0026ldquo;Follback\u0026rdquo;. Jika sudah ada, maka valuenya adalah true, sebaliknya false.\nHasil kodenya adalah sebagai berikut:\n{ \u0026#34;following\u0026#34;: [ { \u0026#34;id\u0026#34;: 2, \u0026#34;full_name\u0026#34;: \u0026#34;Hatsu Shiroyuki\u0026#34;, \u0026#34;username\u0026#34;: \u0026#34;hatsushiroyuki\u0026#34;, \u0026#34;bio\u0026#34;: \u0026#34;An Impostor\u0026#34;, \u0026#34;is_private\u0026#34;: false, \u0026#34;created_at\u0026#34;: \u0026#34;2024-05-04T19:46:40.000000Z\u0026#34;, \u0026#34;updated_at\u0026#34;: \u0026#34;2024-05-04T19:46:40.000000Z\u0026#34;, \u0026#34;is_requested\u0026#34;: true } ] } Response di atas sudah sesuai dengan apa yang diharapkan dari modul LKS, namun kita perlu memperbaiki kodenya supaya jadi lebih rapi.\nMemindahkan Logika Pengambilan Data ke FollowService # Karena kode yang kita buat di atas terlalu berantakan dan tidak bisa dibaca dengan baik, kita akan coba untuk memindahkan kode pengambilan datanya ke fungsi tersendiri di FollowService.\nPertama-tama buat fungsi baru bernama following() di bawah fungsi unfollow() pada file FollowService, yang memiliki 1 Parameter yaitu User $user dan memiliki return type \\Illuminate\\Support\\Collection. Kurang lebih kodenya seperti ini:\n/** * Retrieves the users that the given user is following. * * @param \\App\\Models\\User $follower The user whose followings are to be retrieved. * @return \\Illuminate\\Support\\Collection Returns a collection of users that the given user is following. */ public static function following(User $user): Collection { /** @var \\Illuminate\\Database\\Eloquent\\Collection */ $followings = $user-\u0026gt;followings; return $followings -\u0026gt;map(function (Follow $follow) use ($user) { $userData = $follow-\u0026gt;following; $userData[\u0026#39;is_requested\u0026#39;] = Follow::query() -\u0026gt;where(\u0026#39;follower_id\u0026#39;, $user-\u0026gt;id) -\u0026gt;where(\u0026#39;following_id\u0026#39;, $follow-\u0026gt;following_id) -\u0026gt;exists(); return $userData; }) -\u0026gt;values(); } Karena kodenya sudah dipindah, kita bisa memodifikasi kode pada FollowingController kita menjadi seperti ini:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1; use App\\Http\\Controllers\\Controller; use App\\Services\\FollowService; use Illuminate\\Http\\Request; class FollowingController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request) { return response()-\u0026gt;json([ \u0026#39;following\u0026#39; =\u0026gt; FollowService::following($request-\u0026gt;user()), ]); } } Jadi lebih simpel bukan?\nMemindahkan Logika Pengecekan Sudah Follow Back # Logika yang kita tulis di atas sudah hampir sempurna, namun tetap saja baris kode $userData['is_requested'] sangat tidak rapih. Kita butuh memindahkannya ke fungsi lain. Untuk kasus ini, buat fungsi baru bernama isRequested yang menerima 2 Argumen yaitu User $follower, dan User $following.\nTambahan kodenya kurang lebih seperti ini:\n/** * Checks if a follow relationship is requested between two users. * * @param \\App\\Models\\User $follower The user who initiated the follow request. * @param \\App\\Models\\User $following The user who is being followed. * @return bool Returns true if a follow relationship is requested between the two users, otherwise false. */ public static function isRequested(User $follower, User $following): bool { return Follow::query() -\u0026gt;where(\u0026#39;follower_id\u0026#39;, $follower-\u0026gt;id) -\u0026gt;where(\u0026#39;following_id\u0026#39;, $following-\u0026gt;id) -\u0026gt;exists(); } Sehingga pada kode blok map() yang ada pada fungsi following() bisa kita ubah menjadi:\n/** * Retrieves the users that the given user is following. * * @param \\App\\Models\\User $follower The user whose followings are to be retrieved. * @return \\Illuminate\\Support\\Collection Returns a collection of users that the given user is following. */ public static function following(User $user): Collection { /** @var \\Illuminate\\Database\\Eloquent\\Collection */ $followings = $user-\u0026gt;followings; return $followings -\u0026gt;map(function (Follow $follow) use ($user) { $userData = $follow-\u0026gt;following; // To determine if the logged-in user is already being followed // by $follow-\u0026gt;following (the user being followed), we need to // reverse the comparison, checking if $follow-\u0026gt;following is // following the logged-in user ($user). $userData[\u0026#39;is_requested\u0026#39;] = self::isRequested($follow-\u0026gt;following, $user); return $userData; }) -\u0026gt;values(); } Nah, lebih simpel bukan? Silahkan komparasi kode final antara FollowingController dan FollowService untuk saat ini:\nSpoiler: Kode lengkap FollowingController \u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1; use App\\Http\\Controllers\\Controller; use App\\Services\\FollowService; use Illuminate\\Http\\Request; class FollowingController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request) { return response()-\u0026gt;json([ \u0026#39;following\u0026#39; =\u0026gt; FollowService::following($request-\u0026gt;user()), ]); } } Spoiler: Kode lengkap FollowService \u0026lt;?php namespace App\\Services; use App\\Models\\Follow; use App\\Models\\User; use Illuminate\\Support\\Collection; class FollowService { /** * Checks if a user is trying to follow themselves. * * @param App\\Models\\User $follower The user attempting to follow. * @param App\\Models\\User $following The user being followed. * @return bool Returns true if the user is attempting to follow themselves, otherwise false. */ public static function isFollowingSelf(User $follower, User $following): bool { return $follower-\u0026gt;id == $following-\u0026gt;id; } /** * Checks if a user is already following another user. * * @param \\App\\Models\\User $follower The user who is potentially following * @param \\App\\Models\\User $following The user who is potentially being followed. * @return \\App\\Models\\Follow|null Returns the Follow model instance if the user is already following, otherwise null. */ public static function isAlreadyFollowing(User $follower, User $following): ?Follow { $userFollowed = Follow::query() -\u0026gt;where(\u0026#39;follower_id\u0026#39;, $follower-\u0026gt;id) -\u0026gt;where(\u0026#39;following_id\u0026#39;, $following-\u0026gt;id) -\u0026gt;first(); return $userFollowed; } /** * Creates a follow relationship between two users. * * @param \\App\\Models\\User $follower The user who is following. * @param \\App\\Models\\User $following The user who is being followed. * @return \\App\\Models\\Follow Returns the Follow model instance representing the new follow relationship. */ public static function follow(User $follower, User $following): Follow { return Follow::create([ \u0026#39;follower_id\u0026#39; =\u0026gt; $follower-\u0026gt;id, \u0026#39;following_id\u0026#39; =\u0026gt; $following-\u0026gt;id, ]); } /** * Removes a follow relationship between two users. * * @param \\App\\Models\\User $follower The user who is currently following. * @param \\App\\Models\\User $following The user who is currently being followed. * @return bool Returns true if the follow relationship is successfully removed, otherwise false. */ public static function unfollow(User $follower, User $following): bool { return Follow::query() -\u0026gt;where(\u0026#39;follower_id\u0026#39;, $follower-\u0026gt;id) -\u0026gt;where(\u0026#39;following_id\u0026#39;, $following-\u0026gt;id) -\u0026gt;delete(); } /** * Retrieves the users that the given user is following. * * @param \\App\\Models\\User $follower The user whose followings are to be retrieved. * @return \\Illuminate\\Support\\Collection Returns a collection of users that the given user is following. */ public static function following(User $user): Collection { /** @var \\Illuminate\\Database\\Eloquent\\Collection */ $followings = $user-\u0026gt;followings; return $followings -\u0026gt;map(function (Follow $follow) use ($user) { $userData = $follow-\u0026gt;following; // To determine if the logged-in user is already being followed // by $follow-\u0026gt;following (the user being followed), we need to // reverse the comparison, checking if $follow-\u0026gt;following is // following the logged-in user ($user). $userData[\u0026#39;is_requested\u0026#39;] = self::isRequested($follow-\u0026gt;following, $user); return $userData; }) -\u0026gt;values(); } /** * Checks if a follow relationship is requested between two users. * * @param \\App\\Models\\User $follower The user who initiated the follow request. * @param \\App\\Models\\User $following The user who is being followed. * @return bool Returns true if a follow relationship is requested between the two users, otherwise false. */ public static function isRequested(User $follower, User $following): bool { return Follow::query() -\u0026gt;where(\u0026#39;follower_id\u0026#39;, $follower-\u0026gt;id) -\u0026gt;where(\u0026#39;following_id\u0026#39;, $following-\u0026gt;id) -\u0026gt;exists(); } } Mendaftarkan Rute Following # Untuk mendaftarkan Rute Following kali ini, cukup mudah. Anda hanya butuh menambahkan kode berikut di Rute Grup Middleware auth:sanctum saja:\nRoute::get('/following', \\App\\Http\\Controllers\\Api\\v1\\FollowingController::class);\nSehingga kode lengkap routes/api.php kurang lebih seperti ini:\nSpoiler: Kode lengkap routes/api.php \u0026lt;?php use Illuminate\\Support\\Facades\\Route; Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;v1\u0026#39;], function () { Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;auth\u0026#39;], function () { Route::post(\u0026#39;/register\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\RegisterController::class); Route::post(\u0026#39;/login\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\LoginController::class); Route::post(\u0026#39;/logout\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\LogoutController::class) -\u0026gt;middleware(\u0026#39;auth:sanctum\u0026#39;); }); Route::group([\u0026#39;middleware\u0026#39; =\u0026gt; \u0026#39;auth:sanctum\u0026#39;], function () { Route::get(\u0026#39;/posts\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;index\u0026#39;]); Route::post(\u0026#39;/posts\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;store\u0026#39;]); Route::delete(\u0026#39;/posts/{post}\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;destroy\u0026#39;]) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post not found\u0026#39;, ]); }); Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;user\u0026#39;], function () { Route::post(\u0026#39;/{user:username}/follow\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\FollowController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); Route::delete(\u0026#39;/{user:username}/unfollow\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\User\\UnfollowController::class) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;User not found\u0026#39;, ]); }); }); Route::get(\u0026#39;/following\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\FollowingController::class); }); }); Akhir Kata # Kali ini artikel yang benar-benar panjang ya? Ingat, perjalanan yang ditempuh dengan usaha yang sungguh-sungguh akan menghasilkan sebuah reward yang sungguh-sungguh juga. Tetap gigih, berdo\u0026rsquo;a untuk kesuksesan Anda, serta jaga kesehatan karena perjalanan ini masih panjang.\nSee you on the flip side!\nKode lengkap.\n","date":"5 Mei 2024","externalUrl":null,"permalink":"/artikel/2024/05/05/lks-provinsi-modul-3-step-3/","section":"Artikel-artikel","summary":"","title":"Tutorial Mengerjakan Soal Modul 4 LKS Provinsi Web Technology - Following","type":"artikel"},{"content":"Hai hai, kembali lagi di seri artikel Modul 3 LKS Provinsi bidang Web Technology. Kali ini kita akan membuat REST API untuk Modul Post. Kali ini akan banyak pembahasan lebih mendalam dan lebih matang, karena itu, siapkan air dan fokus kepada tutorial kodingnya. Perhatikan langkah demi langkah secara teliti untuk dapat mengerjakannya dengan baik.\nMembuat Model Post beserta Migrationnya # Kali ini, karena tabel Post belum tersedia oleh Laravel, kita akan belajar untuk membuat Model beserta Migrationnya secara langsung. Untuk membuatnya, jalankan perintah berikut:\nphp artisan make:model Post -m\nPerhatikan flag -m yang diberikan, flag tersebut menandakan bahwa \u0026ldquo;Hei Laravel, buatkan saya Model Post beserta Migrationnya secara bersamaan. Setelah itu, Anda akan melihat bahwa ada 2 file yang dibuat pada directory app/Models/Post.php dan database/migrations/\u0026lt;timestamp\u0026gt;_create_posts_table.php.\nMenggunakan flag -m juga mencegah Anda untuk menjalankan dua perintah yang berbeda yaitu:\nphp artisan make:model Post; dan php artisan make:migration \u0026quot;create posts table\u0026quot; Memodifikasi Migration Post # Buka file database/migrations/\u0026lt;timestamp\u0026gt;_create_posts_table.php dan Anda akan melihat kode seperti ini:\n\u0026lt;?php use Illuminate\\Database\\Migrations\\Migration; use Illuminate\\Database\\Schema\\Blueprint; use Illuminate\\Support\\Facades\\Schema; return new class extends Migration { /** * Run the migrations. */ public function up(): void { Schema::create(\u0026#39;posts\u0026#39;, function (Blueprint $table) { $table-\u0026gt;id(); $table-\u0026gt;timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists(\u0026#39;posts\u0026#39;); } }; Berdasarkan ketentuan yang disediakan oleh modul LKS, skema tabel Post harus berisi sebagai berikut:\nid BIGINT(20) UNSIGNED PRIMARY auto_increment user_id BIGINT(20) UNSIGNED FOREIGN_KEY caption VARCHAR(255) created_at TIMESTAMP deleted_at TIMESTAMP Perhatikan bahwa terdapat dua kolom spesial yaitu user_id dan deleted_at. Kolom user_id menampung ID dari seorang User, dan kolom deleted_at menandakan bahwa Post tersebut sudah dihapus. Metode ini dinamakan Soft Delete, mencegah adanya data yang terhapus secara permanen dari basis data tanpa jejak.\nUntuk membuat kolom-kolom tersebut, ubah kode pada fungsi up() menjadi seperti ini:\nSchema::create(\u0026#39;posts\u0026#39;, function (Blueprint $table) { $table-\u0026gt;id(); $table-\u0026gt;foreignId(\u0026#39;user_id\u0026#39;)-\u0026gt;constrained(); $table-\u0026gt;string(\u0026#39;caption\u0026#39;); $table-\u0026gt;timestamps(); $table-\u0026gt;softDeletes(); }); Perhatikan bahwa terdapat $table-\u0026gt;foreignId('user_id')-\u0026gt;constrained(), kode ini memerintahkan Migration untuk membuat kolom Foreign Key dengan nama user_id, yang memiliki relasi langsung dengan tabel users kolom id. Fungsi foreignId menginstruksikan Migration untuk membuat kolom dengan tipe data BIGINT.\nSementara itu, perhatikan juga bahwa terdapat $table-\u0026gt;softDeletes(), kode ini memerintahkan Migration untuk membuat kolom deleted_at dengan tipe data TIMESTAMP.\nSetelah semua kode sesuai dengan yang Saya instruksikan, jangan lupa jalankan php artisan migrate untuk membuat tabel posts pada basis data Anda.\nMemodifikasi Kode Model Post # Buka file app/Models/Post, Anda akan melihat kode berikut:\n\u0026lt;?php namespace App\\Models; use Illuminate\\Database\\Eloquent\\Factories\\HasFactory; use Illuminate\\Database\\Eloquent\\Model; class Post extends Model { use HasFactory; } Tampak kosong untuk sekarang, namun kita akan mengimplementasikan beberapa Trait, serta Atribut yang dibutuhkan.\nMengimplementasikan Trait SoftDeletes # Pertama-tama, mari kita implementasikan Trait SoftDeletes untuk menginstruksikan Laravel bahwa Model Post ini adalah tipe Model yang tidak boleh dihapus secara permanen. Untuk mengimplementasikannya Anda harus menggunakan Trait Illuminate\\Database\\Eloquent\\SoftDeletes. Kode lengkapnya adalah berikut:\n\u0026lt;?php namespace App\\Models; use Illuminate\\Database\\Eloquent\\Factories\\HasFactory; use Illuminate\\Database\\Eloquent\\Model; use Illuminate\\Database\\Eloquent\\SoftDeletes; class Post extends Model { use HasFactory, SoftDeletes; } Mengisi Atribut $fillable # Dengan mengisi atribut $fillable, Anda dapat menggunakan Model ini untuk menjadikannya dapat di-mass-created, sehingga Anda tidak perlu mendefinisikan kolomnya satu per-satu pada saat penyimpanan data.\n\u0026lt;?php namespace App\\Models; use Illuminate\\Database\\Eloquent\\Factories\\HasFactory; use Illuminate\\Database\\Eloquent\\Model; use Illuminate\\Database\\Eloquent\\SoftDeletes; class Post extends Model { use HasFactory, SoftDeletes; protected $fillable = [ \u0026#39;user_id\u0026#39;, \u0026#39;caption\u0026#39;, ]; } Membuat Relasi ke Model User # Kita kedepannya ingin untuk mendapatkan data User secara otomatis, tanpa harus menggunakan klausa JOIN pada saat mengambil data Post. Untuk itu kita dapat menggunakan relasi BelongsTo. Ikuti kode berikut:\n\u0026lt;?php namespace App\\Models; use Illuminate\\Database\\Eloquent\\Factories\\HasFactory; use Illuminate\\Database\\Eloquent\\Model; use Illuminate\\Database\\Eloquent\\Relations\\BelongsTo; use Illuminate\\Database\\Eloquent\\SoftDeletes; class Post extends Model { use HasFactory, SoftDeletes; public function user(): BelongsTo { return $this-\u0026gt;belongsTo(User::class); } protected $fillable = [ \u0026#39;user_id\u0026#39;, \u0026#39;caption\u0026#39;, ]; } Perhatikan bahwa Saya menulis kode\npublic function user(): BelongsTo { return $this-\u0026gt;belongsTo(User::class); } Ini adalah standar penulisan kode untuk Model Relation BelongsTo, Anda akan belajar banyak tipe relasi lebih banyak di dokumentasi resmi Laravel. Kode ini memiliki arti bahwa \u0026ldquo;Model Post ini dimiliki oleh User\u0026rdquo;.\nAnda mungkin bertanya-tanya apa maksud dari user(): BelongsTo? Arti dari kode tersebut adalah fungsi user() memiliki return type BelongsTo. Kode Anda akan error jika tidak menggunakan use Illuminate\\Database\\Eloquent\\Relations\\BelongsTo di atas file karena PHP tidak mengetahui apakah BelongsTo adalah sebuah tipe data, atau sebuah kelas.\nUntuk saat ini, penulisan kode Untuk Model Post telah selesai. Selanjutnya kita akan membuat Model PostAttachment.\nMembuat Model PostAttachment beserta Migrationnya # Seperti pada saat kita membuat Model Post, Anda harus membuat Model dan Migrationnya secara langsung. Cobalah untuk mengetik perintah untuk membuat Model dan Migrationnya secara langsung sebelum melihat spoiler di bawah. Buktikan bahwa Anda belajar dan mengikuti tutorial dengan sungguh-sungguh.\nSpoiler: Kode Membuat Model PostAttachment beserta Migrationnya php artisan make:model PostAttachment -m\nJika Anda telah berhasil menjalankan kode tersebut, mari kita modifikasi Migrationnya terlebih dahulu.\nMemodifikasi Migration Post Attachments # Untuk tabel post_attachments yang akan kita buat, modul LKS sudah menyediakan skema sebagai berikut:\nid BIGINT(20) UNSIGNED PRIMARY auto_increment post_id BIGINT(20) UNSIGNED FOREIGN_KEY storage_path VARCHAR(255) Buka file database/migrations/\u0026lt;timestamp\u0026gt;_create_post_attachments_table.php, dan masukkan kode berikut ke dalam fungsi up() pada Migration Anda:\nSchema::create(\u0026#39;post_attachments\u0026#39;, function (Blueprint $table) { $table-\u0026gt;id(); $table-\u0026gt;foreignId(\u0026#39;post_id\u0026#39;)-\u0026gt;constrained(); $table-\u0026gt;string(\u0026#39;storage_path\u0026#39;); $table-\u0026gt;timestamps(); }); Anda tentunya sudah familiar dengan fungsi foreignId(), string(), dan modifier constrained() bukan? Jadi jika ditanya oleh penguji, semoga Anda dapat menjawabnya. Jika masih belum, silahkan kembali ke artikel sebelumnya dan baca apa maksud dari fungsi-fungsi tersebut.\nMemodifikasi Model PostAttachment # Buka file app/Models/PostAttachment dan tuliskan atribut $fillable berdasarkan kolom-kolom yang Anda buat. Jangan buka spoiler di bawah kecuali Anda sudah membuat atribut $fillable. Jika sudah, silahkan Anda komparasikan kode yang sudah Anda buat dengan apa yang sudah Saya buat.\nSpoiler: Kode Model PostAttachment dengan atribut $fillable \u0026lt;?php namespace App\\Models; use Illuminate\\Database\\Eloquent\\Factories\\HasFactory; use Illuminate\\Database\\Eloquent\\Model; class PostAttachment extends Model { use HasFactory; protected $fillable = [ \u0026#39;post_id\u0026#39;, \u0026#39;storage_path\u0026#39;, ]; } Jika Anda berhasil menulis kode atribut $fillable dengan kolom-kolom yang sesuai, selamat! Anda sudah mempelajari cara membuat atribut. Selanjutnya, implementasikan kode bahwa \u0026ldquo;Model PostAttachment ini dimiliki oleh Post menggunakan Relasi BelongsTo. Coba buat kode tersebut lalu komparasikan kodenya di spoiler bawah.\nSpoiler: Kode Model PostAttachment dengan relasi BelongsTo \u0026lt;?php namespace App\\Models; use Illuminate\\Database\\Eloquent\\Factories\\HasFactory; use Illuminate\\Database\\Eloquent\\Model; use Illuminate\\Database\\Eloquent\\Relations\\BelongsTo; class PostAttachment extends Model { use HasFactory; public function post(): BelongsTo { return $this-\u0026gt;belongsTo(Post::class); } protected $fillable = [ \u0026#39;post_id\u0026#39;, \u0026#39;storage_path\u0026#39;, ]; } Jika kode Model PostAttachment sudah mirip dengan kode yang Saya tulis, silahkan lanjut ke langkah selanjutnya.\nMemodifikasi Model Post (lagi) # Eits, belum selesai sampai situ, kita harus mengimplementasikan sebuah kode yang menginstruksikan Laravel bahwa Model Post memiliki banyak PostAttachment. Bagaimana caranya? Ikuti kode berikut:\n\u0026lt;?php namespace App\\Models; use Illuminate\\Database\\Eloquent\\Factories\\HasFactory; use Illuminate\\Database\\Eloquent\\Model; use Illuminate\\Database\\Eloquent\\Relations\\BelongsTo; use Illuminate\\Database\\Eloquent\\Relations\\HasMany; use Illuminate\\Database\\Eloquent\\SoftDeletes; class Post extends Model { use HasFactory, SoftDeletes; public function user(): BelongsTo { return $this-\u0026gt;belongsTo(User::class); } public function post_attachments(): HasMany { return $this-\u0026gt;hasMany(PostAttachment::class); } protected $fillable = [ \u0026#39;user_id\u0026#39;, \u0026#39;caption\u0026#39;, ]; } Disini kita belajar salah satu relasi baru yang bernama HasMany. Relasi ini memiliki arti bahwa Model Post ini memiliki banyak PostAttachment, atau dalam bahasa manusianya \u0026ldquo;Post ini memiliki banyak Lampiran\u0026rdquo;. Jika sudah selesai, mari kita buat Controllernya di langkah selanjutnya.\nMemodifikasi Model User # Eits, lagi-lagi kita belum selesai. Sebelum membuat PostController, kita harus memodifikasi sedikit kode Model User. Namun, tidak akan ada tantangan jika Saya memberikan Anda kode lengkapnya tanpa Anda mencobanya terlebih dahulu, maka dari itu:\nTugas: Implementasikan bahwa Model User memiliki relasi HasMany ke Model Post. Komparasikan kode Anda dengan kode Saya untuk mengecek apakah langkah Anda sudah benar.\nSpoiler: Kode Model User dengan relasi HasMany kepada Model Post \u0026lt;?php namespace App\\Models; // use Illuminate\\Contracts\\Auth\\MustVerifyEmail; use Illuminate\\Database\\Eloquent\\Factories\\HasFactory; use Illuminate\\Database\\Eloquent\\Relations\\HasMany; use Illuminate\\Foundation\\Auth\\User as Authenticatable; use Illuminate\\Notifications\\Notifiable; use Laravel\\Sanctum\\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; public function post(): HasMany { return $this-\u0026gt;hasMany(Post::class); } /** * The attributes that are mass assignable. * * @var array\u0026lt;int, string\u0026gt; */ protected $fillable = [ \u0026#39;full_name\u0026#39;, \u0026#39;email\u0026#39;, \u0026#39;password\u0026#39;, \u0026#39;username\u0026#39;, \u0026#39;bio\u0026#39;, \u0026#39;is_private\u0026#39;, ]; /** * The attributes that should be hidden for serialization. * * @var array\u0026lt;int, string\u0026gt; */ protected $hidden = [ \u0026#39;password\u0026#39;, \u0026#39;remember_token\u0026#39;, ]; /** * The attributes that should be cast. * * @var array\u0026lt;string, string\u0026gt; */ protected $casts = [ \u0026#39;email_verified_at\u0026#39; =\u0026gt; \u0026#39;datetime\u0026#39;, \u0026#39;password\u0026#39; =\u0026gt; \u0026#39;hashed\u0026#39;, \u0026#39;is_private\u0026#39; =\u0026gt; \u0026#39;boolean\u0026#39;, ]; } Jika kode Anda sudah mirip dengan kode di atas, mari lanjut (kali ini asli) ke langkah selanjutnya!\nMengubah File .env # Sebelum lanjut ke langkah selanjutnya, kita harus mengubah sedikit file .env. Buka file .env dan cari variabel FILESYSTEM_DISK, ubah valuenya dari local ke public.\nSetelah itu jalankan perintah php artisan storage:link di command line supaya semua lampiran yang User unggah dapat dibaca oleh browser.\nMembuat Controller PostController # Kali ini kita akan membuat PostController dengan approach yang berbeda, yaitu dengan cara membuat Controller bertipe API Resource. Untuk membuatnya, eksekusi perintah ini:\nphp artisan make:controller Api/v1/PostController --api --model=Post\nBerbeda dengan tutorial Autentikasi sebelumnya yang menggunakan flag --invokable yang hanya berisikan 1 fungsi didalamnya yaitu __invoke(Request $request), kita akan membuat banyak fungsi-fungsi yang masing-masing bertanggung jawab untuk menerima Request dengan tipe yang berbeda-beda. Menggunakan flag --api akan membuat fungsi:\nindex(), digunakan untuk mengambil banyak data terkait dengan sebuah Model; store(Request $request), digunakan untuk menyimpan data Model ke basis data; show(Model $model), digunakan untuk mengambil 1 data spesifik terkait dengan Model; update(Request $request, Model $model), digunakan untuk mengubah data Model dari basis data; delete(Model $model), digunkana untuk menghapus data dari basis data; Menjalankan perintah di atas akan membuat file app/Http/Controllers/Api/v1/PostController.php lalu ikuti langkah-langkah selanjutnya.\nImplementasi fungsi store # Fungsi store digunakan untuk menyimpan data Model Post ke basis data. Akan ada beberapa langkah sebelum fungsi ini selesai, jadi Saya mohon untuk teliti dalam mengimplementasikan fungsi ini.\nValidasi Data Request # Seperti biasa, kita harus memvalidasi data yang masuk sebelum disimpan ke database. Dalam modul LKS, kita diberikan informasi validasi sebagai berikut:\ncaption required attachments required, array of image files, mimes: jpg, jpeg, png, gif, or webp Kode untuk validasi di atas adalah sebagai berikut:\n$data = $request-\u0026gt;validate([ \u0026#39;caption\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;], \u0026#39;attachments\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;], \u0026#39;attachments.*\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;image\u0026#39;, \u0026#39;mimes:png,jpg,jpeg,webp,gif\u0026#39;], ]); Arti dari aturan validasi di atas adalah sebagai berikut:\ncaption haruslah diisi; attachments haruslah diisi, dengan aturan masing-masing file yang diunggah adalah: berbentuk image atau gambar yang valid; dengan memiliki format png,jpg,jpeg,webp,gif Perhatikan bahwa ada aturan validasi 'attachments.*, aturan ini berarti bahwa semua file yang ada pada key attachments haruslah memiliki aturan blablabla. Pada Laravel, aturan ini disebut dengan Multiple Field Validation.\nSelanjutnya, apakah Anda ingat arti O dari prinsip SOLID? Jika Anda masih ingat, ikuti tugas berikut:\nTugas: Pindahkan aturan validasi dari fungsi store pada PostController ke file StorePostRequest Menyimpan Data Post # Untuk menyimpan data Model Post, tambahkan kode berikut ini setelah baris kode Validation:\ntry { $post = Post::create([ \u0026#39;user_id\u0026#39; =\u0026gt; $request-\u0026gt;user()-\u0026gt;id, \u0026#39;caption\u0026#39; =\u0026gt; $data[\u0026#39;caption\u0026#39;], ]); } catch (\\Throwable $th) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post failed to be created: \u0026#39; . $th-\u0026gt;getMessage(), ], 500); } return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post created successfully\u0026#39;, ], 201); Kode di atas menginstruksikan bahwa kita menyimpan sebuah Post dengan user_id terkait ke User yang sedang login saat itu, serta menyimpan caption. Jika gagal, maka kita kembalikan response berbentuk json dengan pesan \u0026ldquo;Post failed to be created: Error\u0026rdquo;. Jika berhasil kita kembalikan response berbentuk json dengan pesan \u0026ldquo;Post created successfully\u0026rdquo;.\nMenyimpan Lampiran Yang Dikirim # Perlu diingat bahwa kita belum mengimplementasikan kode untuk menyimpan data Lampiran. Untuk menyimpan kode Lampiran, kita harus memodifikasi kode seperti berikut:\ntry { DB::beginTransaction(); $post = Post::create([ \u0026#39;user_id\u0026#39; =\u0026gt; $request-\u0026gt;user()-\u0026gt;id, \u0026#39;caption\u0026#39; =\u0026gt; $data[\u0026#39;caption\u0026#39;], ]); /** @var \\Illuminate\\Http\\UploadedFile $attachment */ foreach ($data[\u0026#39;attachments\u0026#39;] as $attachment) { $post-\u0026gt;post_attachments()-\u0026gt;create([ \u0026#39;storage_path\u0026#39; =\u0026gt; $attachment-\u0026gt;store(date(\u0026#39;Y-m-d\u0026#39;) . \u0026#39;/uploads\u0026#39;), ]); } } catch (\\Throwable $th) { DB::rollBack(); return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post failed to be created: \u0026#39; . $th-\u0026gt;getMessage(), ], 500); } DB::commit(); return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post created successfully\u0026#39;, ], 201); Perhatikan bahwa tepat setelah blok kode try kita mengimplementasikan DB::beginTransaction(). Kode ini menginstruksikan Controller bahwa kode-kode setelah ini adalah bagian dari Database Transaction. Kita menerapkan kode ini untuk mencegah data tersimpan ke basis data jika suatu saat terjadi kegagalan di salah satu baris pada blok try.\nSetelah itu, kita implementasikan kode foreach untuk mengambil semua data lampiran yang sudah kita validasi untuk disimpan ke tabel post_attachments. Jika Anda bertanya-tanya apa maksud dari /** @var \\Illuminate\\Http\\UploadedFile $attachment */, kode ini menginstruksikan IDE untuk memberikan tanda bahwa variabel $attachment merupakan bagian dari \\Illuminate\\Http\\UploadedFile. IDE tidak mengetahui secara default tipe data $attachment, karena itu kita mengimplementasikannya secara eksplisit.\nKode berikut\n$post-\u0026gt;post_attachments()-\u0026gt;create([ \u0026#39;storage_path\u0026#39; =\u0026gt; $attachment-\u0026gt;store(date(\u0026#39;Y-m-d\u0026#39;) . \u0026#39;/uploads\u0026#39;), ]); Menginstruksikan bahwa kita akan menyimpan lampiran ke relasi post_attachments yang ada di Model Post, dengan data untuk kolom storage_path berisi directory dimana $attachment disimpan.\nPada blok catch kita implementasikan DB::rollBack() untuk menginstruksikan Controller bahwa \u0026ldquo;sudah terjadi error di blok try dan kita ingin data-data sementara yang ada di basis data dihapus.\nSementara setelah blok try-catch kita imlpementasikan DB::commit() untuk menginstruksikan Controller bahwa \u0026ldquo;seluruh kode telah berhasil dijalankan dan tidak ada kendala sama sekali, jadi silahkan simpan semua data sementara\u0026rdquo;.\nImplementasi fungsi delete # Pada fungsi delete, kita ingin menghapus data Model Post beserta data PostAttachment yang terkait. Untuk itu, implementasikan kode ini:\ntry { DB::beginTransaction(); $post-\u0026gt;post_attachments()-\u0026gt;delete(); $post-\u0026gt;delete(); } catch (\\Throwable $th) { DB::rollBack(); return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post failed to be deleted: \u0026#39; . $th-\u0026gt;getMessage(), ], 500); } DB::commit(); return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post deleted successfully\u0026#39;, ]); Lagi-lagi, karena terdapat lebih dari satu operasi yang menyangkut eksekusi perintah di basis data, kita harus mengimplementasikan DB::beginTransaction(), DB::rollBack() dan DB::commit().\nPada blok try kita mencoba untuk menghapus lampirannya terlebih dahulu, baru menghapus Post-nya. Hal ini untuk mencegah lampiran tidak dapat dihapus karena \u0026ldquo;parent\u0026rdquo; dari post_attachment sudah tidak ada pada basis data.\nImplementasi fungsi index # Ini adalah bagian terakhir dalam mengimplementasikan PostController, jangan lupa untuk peregangan sejenak dan minum air secukupnya. Sudah? Mari kita lanjutkan sesi belajar kita.\nSebelum itu, pada modul LKS kita diharuskan untuk memvalidasi parameter page dan size. Validasi ini penting dikarenakan kita akan mengimplementasikan Paginasi, supaya Post yang kita ambil tidak terlalu banyak dan dibatasi maksimal 10 Post per-sekali Request.\nAturan validasi yang didapat dari modul LKS adalah sebagai berikut:\npage integer, minimum 0 and default 0 size integer, minimum 1 and default 10 Dengan data di atas, kita bisa tulis aturan Validasinya sebagai berikut:\n$data = $request-\u0026gt;validate([ \u0026#39;page\u0026#39; =\u0026gt; [\u0026#39;nullable\u0026#39;, \u0026#39;integer\u0026#39;, \u0026#39;gte:0\u0026#39;], \u0026#39;size\u0026#39; =\u0026gt; [\u0026#39;nullable\u0026#39;, \u0026#39;integer\u0026#39;, \u0026#39;gte:1\u0026#39;] ]); Karena kedua parameter tidak diperlukan maka kita atur ke nullable, dan karena kedua parameter berbentuk angka kita atur validasinya ke integer. Setelah itu untuk parameter page dan size, kita memiliki aturan minimum yaitu 0 dan 1, karena itu kita tuliskan aturan gte:0 dan gte:1. Aturan gte:\u0026lt;num\u0026gt; memiliki arti \u0026ldquo;greater or equal than\u0026rdquo; atau \u0026ldquo;lebih besar dari sama dengan\u0026rdquo;.\nSetelah itu, kita tuliskan kode berikut setelah validasi:\nreturn response()-\u0026gt;json([ \u0026#39;page\u0026#39; =\u0026gt; $data[\u0026#39;page\u0026#39;] ?? 0, \u0026#39;size\u0026#39; =\u0026gt; $data[\u0026#39;size\u0026#39;] ?? 10, \u0026#39;posts\u0026#39; =\u0026gt; Post::query() -\u0026gt;with([ \u0026#39;user\u0026#39;, \u0026#39;post_attachments\u0026#39;, ]) -\u0026gt;offset($data[\u0026#39;page\u0026#39;] ?? 0) -\u0026gt;limit($data[\u0026#39;size\u0026#39;] ?? 10) -\u0026gt;orderByDesc(\u0026#39;created_at\u0026#39;) -\u0026gt;get(), ]); Kita menginstruksikan fungsi index untuk mengirimkan response berbentuk json dengan key sebagai berikut:\npage jika User mengirimkan instruksi buka \u0026ldquo;halaman \u0026rdquo;, jika tidak ada kirimkan 0; size jika Use rmengirimkan instruksi ambil data sebanyak \u0026ldquo; Post\u0026rdquo;, jika tidak ada kirimkan 10; posts ambil semua Post dengan: data relasi ke: user yang membuat Post Lampiran-lampiran yang ada di post_attachment yang terkait dengan Post buka halaman $data['page'] atau default 0 (halaman pertama) batasi data Post sebanyak $data['size'] atau default 10 urutkan data Post dengan data posting terbaru Tugas: Pindahkan aturan validasi dari fungsi index pada PostController ke file IndexPostRequest Spoiler: Kode Lengkap PostController \u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1; use App\\Http\\Controllers\\Controller; use App\\Http\\Requests\\Api\\v1\\IndexPostRequest; use App\\Http\\Requests\\Api\\v1\\StorePostRequest; use App\\Models\\Post; use Illuminate\\Support\\Facades\\DB; class PostController extends Controller { /** * Display a listing of the resource. */ public function index(IndexPostRequest $request) { $data = $request-\u0026gt;validated(); return response()-\u0026gt;json([ \u0026#39;page\u0026#39; =\u0026gt; $data[\u0026#39;page\u0026#39;] ?? 0, \u0026#39;size\u0026#39; =\u0026gt; $data[\u0026#39;size\u0026#39;] ?? 10, \u0026#39;posts\u0026#39; =\u0026gt; Post::query() -\u0026gt;with([ \u0026#39;user\u0026#39;, \u0026#39;post_attachments\u0026#39;, ]) -\u0026gt;offset($data[\u0026#39;page\u0026#39;] ?? 0) -\u0026gt;limit($data[\u0026#39;size\u0026#39;] ?? 10) -\u0026gt;orderByDesc(\u0026#39;created_at\u0026#39;) -\u0026gt;get(), ]); } /** * Store a newly created resource in storage. */ public function store(StorePostRequest $request) { $data = $request-\u0026gt;validated(); try { DB::beginTransaction(); $post = Post::create([ \u0026#39;user_id\u0026#39; =\u0026gt; $request-\u0026gt;user()-\u0026gt;id, \u0026#39;caption\u0026#39; =\u0026gt; $data[\u0026#39;caption\u0026#39;], ]); /** @var \\Illuminate\\Http\\UploadedFile $attachment */ foreach ($data[\u0026#39;attachments\u0026#39;] as $attachment) { $post-\u0026gt;post_attachments()-\u0026gt;create([ \u0026#39;storage_path\u0026#39; =\u0026gt; $attachment-\u0026gt;store(date(\u0026#39;Y-m-d\u0026#39;) . \u0026#39;/uploads\u0026#39;), ]); } } catch (\\Throwable $th) { DB::rollBack(); return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post failed to be created: \u0026#39; . $th-\u0026gt;getMessage(), ], 500); } DB::commit(); return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post created successfully\u0026#39;, ], 201); } /** * Remove the specified resource from storage. */ public function destroy(Post $post) { try { DB::beginTransaction(); $post-\u0026gt;post_attachments()-\u0026gt;delete(); $post-\u0026gt;delete(); } catch (\\Throwable $th) { DB::rollBack(); return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post failed to be deleted: \u0026#39; . $th-\u0026gt;getMessage(), ], 500); } DB::commit(); return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post deleted successfully\u0026#39;, ]); } } Implementasi Rute Post # Kita sudah menuliskan seluruh fungsi yang dibutuhkan untuk lomba kali ini pada PostController, namun User belum dapat mengakses endpoint Post dikarenakan kita belum mendefinisikannya.\nUntuk itu mari kita buka file routes/api.php dan tambahkan kode ini setelah Grup Rute /auth:\nRoute::group([\u0026#39;middleware\u0026#39; =\u0026gt; \u0026#39;auth:sanctum\u0026#39;], function () { Route::get(\u0026#39;/posts\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;index\u0026#39;]); Route::post(\u0026#39;/posts\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;store\u0026#39;]); Route::delete(\u0026#39;/posts/{post}\u0026#39;, [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, \u0026#39;destroy\u0026#39;]) -\u0026gt;missing(function () { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Post not found\u0026#39;, ]); }); }); Perhatikan bahwa kita mendefinisikan Rute Grup posts ini mengimplementasikan Middleware auth:sanctum. Seperti yang sudah kita pelajari sebelumnya, kita mencegah pengguna untuk dapat mengakses endpoint ini tanpa memiliki kredensial. Jadi User harus login terlebih dulu sebelum mendapatkan / mengirim / menghapus data Post.\nPerhatikan juga di masing-masing endpoint kita menuliskan [\\App\\Http\\Controllers\\Api\\v1\\PostController::class, 'index'], kode ini menginstruksikan endpoint tersebut menggunakan Controller PostController pada fungsi index. Begitu juga pada endpoint-endpoint lainnya.\nAnda mungkin bertanya-tanya pada endpoint DELETE /posts/{post} apa yang dimaksud dari {post} tersebut? Baris kode ini disebut dengan Named Parameter, yang artinya Fasad Route menginstruksikan ke instance Request bahwa rute ini membutuhkan ID dari suatu Post, mengambilnya, dan mengirimkan datanya ke Controller sebagai Argumen.\nSetelah itu, jika data dari suatu Post tidak ditemukan atau sudah dihapus, maka kita mengirimkan pesan \u0026ldquo;Post not found\u0026rdquo; dalam modifier missing(Callback).\nMembuat PostPolicy # Pada saat ini, kita berhasil membuat kode yang dapat menghapus sebuah Postingan. Namun semua User dapat menghapus data tersebut. Kita tidak ingin hal itu terjadi dan kita hanya ingin User yang membuat Post tersebutlah yang boleh menghapus datanya. Karena itu kita harus membuat sebuah Policy.\nPolicy adalah sebuah file untuk mendefinisikan aturan-aturan tentang siapa saja yang bisa memanipulasi data dari sebuah Model. Untuk membuat PostPolicy, jalankan perintah ini di command line:\nphp artisan make:policy PostPolicy --model=Post\nFlag --model menginstruksikan kepada Artisan bahwa untuk membuat PostPolicy ini, kita merujuk kepada sebuah Model bernama Post.\nSetelah perintah berhasil dijalankan, file Policy akan disimpan ke dalam folder app/Policies. Buka app/Policies/PostPolicy.php dan Anda akan melihat kode berikut:\n\u0026lt;?php namespace App\\Policies; use App\\Models\\Post; use App\\Models\\User; use Illuminate\\Auth\\Access\\Response; class PostPolicy { /** * Determine whether the user can view any models. */ public function viewAny(User $user): bool { // } /** * Determine whether the user can view the model. */ public function view(User $user, Post $post): bool { // } /** * Determine whether the user can create models. */ public function create(User $user): bool { // } /** * Determine whether the user can update the model. */ public function update(User $user, Post $post): bool { // } /** * Determine whether the user can delete the model. */ public function delete(User $user, Post $post): bool { // } /** * Determine whether the user can restore the model. */ public function restore(User $user, Post $post): bool { // } /** * Determine whether the user can permanently delete the model. */ public function forceDelete(User $user, Post $post): bool { // } } Dikarenakan seluruh fungsi mewajibkan isinya me-return value berupa boolean, kita harus memberikan semua fungsinya return type. Ubahlah dari masing-masing fungsi dengan return value:\nFungsi Return Value Keterangan viewAny true Kita ingin siapapun dapat melihat Postingan apapun view true Kita ingin siapapun dapat melihat detail Postingan siapapun create true Kita ingin siapapun dapat membuat Postingan update $user-\u0026gt;id == $post-\u0026gt;user_id Kita ingin User yang membuat Post tersebutlah yang bisa mengubah Postingannya delete $user-\u0026gt;id == $post-\u0026gt;user_id Kita ingin User yang membuat Post tersebutlah yang bisa menghapus Postingannya restore false Kita tidak ingin siapapun dapat mengembalikan Posting yang sudah dihapus forceDelete false Kita tidak ingin siapapun dapat menghapus Posting secara permanen Jika Anda sudah mengimplementasikan kode di atas, kurang lebih kodenya akan seperti ini (pastikan untuk coba dahulu sebelum komparasi kode):\nSpoiler: Kode PostPolicy \u0026lt;?php namespace App\\Policies; use App\\Models\\Post; use App\\Models\\User; use Illuminate\\Auth\\Access\\Response; class PostPolicy { /** * Determine whether the user can view any models. */ public function viewAny(User $user): bool { return true; } /** * Determine whether the user can view the model. */ public function view(User $user, Post $post): bool { return true; } /** * Determine whether the user can create models. */ public function create(User $user): bool { return true; } /** * Determine whether the user can update the model. */ public function update(User $user, Post $post): bool { return $user-\u0026gt;id == $post-\u0026gt;user_id; } /** * Determine whether the user can delete the model. */ public function delete(User $user, Post $post): bool { return $user-\u0026gt;id == $post-\u0026gt;user_id; } /** * Determine whether the user can restore the model. */ public function restore(User $user, Post $post): bool { return false; } /** * Determine whether the user can permanently delete the model. */ public function forceDelete(User $user, Post $post): bool { return false; } } Mengimplementasikan PostPolicy ke PostController # Walaupun PostPolicy sudah kita implementasikan kode-kodenya, kita perlu menginstruksikan PostController untuk mengimplementasikan masing-masing fungsinya. Untuk itu, kita modifikasi sedikit PostController kita menjadi seperti ini:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1; // ... Kode `use` class PostController extends Controller { public function __construct() { return $this-\u0026gt;authorizeResource(Post::class); } // ... Sisa kode setelahnya di bawah } Implementasikan fungsi __construct() yang didalamnya mereturn $this-\u0026gt;authorizeResource(Post::class). Fungsi __construct adalah sebuah fungsi yang dieksekusi sebelum kode apapun yang ada pada suatu Kelas. $this-\u0026gt;authorizeResource(Model) menginstruksikan Controller untuk menggunakan sebuah Policy dari suatu Model. Simpelnya, semua aturan-aturan akses yang sudah kita definisikan tadi dalam PostPolicy akan diimplementasikan ke masing-masing Fungsi yang ada di PostController.\nDilansir dari dokumentasi resmi Laravel, berikut adalah fungsi-fungsi Policy yang diimplementasikan ke fungsi-fungsi Controller\nFungsi Controller Fungsi Policy index viewAny swow view create create store create edit update update update destroy delete Forbidden Exception Handling # Terakhir, sebelum kita menyelesaikan tutorial kali ini, kita harus mengimplementasikan Exception Handling jika ada data User yang menghapus data Posting orang lain. Sebelum kita mengimplementasikannya, error yang diberikan oleh Laravel kurang lebih seperti ini:\n{ \u0026#34;message\u0026#34;: \u0026#34;This action is unauthorized.\u0026#34;, \u0026#34;exception\u0026#34;: \u0026#34;Symfony\\\\Component\\\\HttpKernel\\\\Exception\\\\AccessDeniedHttpException\u0026#34;, \u0026#34;file\u0026#34;: \u0026#34;/project-dir/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php\u0026#34;, \u0026#34;line\u0026#34;: 491, \u0026#34;trace\u0026#34;: [ { \u0026#34;file\u0026#34;: \u0026#34;/project-dir/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php\u0026#34;, \u0026#34;line\u0026#34;: 463, \u0026#34;function\u0026#34;: \u0026#34;prepareException\u0026#34;, \u0026#34;class\u0026#34;: \u0026#34;Illuminate\\\\Foundation\\\\Exceptions\\\\Handler\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;-\u0026gt;\u0026#34; }, // ... sisa Stack Trace ] } Kita tidak ingin menunjukan bahwa kita memiliki error yang secara eksplisit ditunjukkan ke User, maka dari itu kita harus tambahkan kode berikut ke file app/Exception/Handler.php:\n\u0026lt;?php namespace App\\Exceptions; use Illuminate\\Auth\\Access\\AuthorizationException; // Copas ini use Illuminate\\Foundation\\Exceptions\\Handler as ExceptionHandler; use Throwable; class Handler extends ExceptionHandler { /** * The list of the inputs that are never flashed to the session on validation exceptions. * * @var array\u0026lt;int, string\u0026gt; */ protected $dontFlash = [ \u0026#39;current_password\u0026#39;, \u0026#39;password\u0026#39;, \u0026#39;password_confirmation\u0026#39;, ]; /** * Register the exception handling callbacks for the application. */ public function register(): void { $this-\u0026gt;reportable(function (Throwable $e) { // }); } public function render($request, Throwable $e) // Copas seluruh fungsi ini { if ($e instanceof AuthorizationException) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Forbidden\u0026#39;, ], 403); } return parent::render($request, $e); } } Akhir Kata # Untuk saat ini, selamat Anda telah menyelesaikan Sub-modul kedua dari Modul Back End. Selanjutnya dapat jadi lebih mudah / lebih sulit. Jadi, semangat belajar, dan jangan lupa berdo\u0026rsquo;a untuk kesuksesan Anda!\nKode lengkap.\n","date":"5 Mei 2024","externalUrl":null,"permalink":"/artikel/2024/05/05/lks-provinsi-modul-3-step-2/","section":"Artikel-artikel","summary":"","title":"Tutorial Mengerjakan Soal Modul 3 LKS Provinsi Web Technology - Post","type":"artikel"},{"content":"Halo semua 👋, jika kalian membaca ini, berarti Anda telah lolos ke tahap selanjutnya dan siap berkompetisi dengan beberapa kompetetitor lain. Selamat berpusing-pusing ria, dan jangan lupa untuk semangat belajarnya.\nDalam dalam modul ini, kita harus membuat sebuah REST API menggunakan Laravel 10. Untuk tahap pertama, kita akan membahas bagaimana caranya kita membuat Endpoint Autentikasi yaitu Register, Login, dan Register. Mari kita lihat soal lengkap dari sub-modul Autentikasi yang akan kita buat.\nPenyesuaian Tabel users # Sebelum itu, kita harus menyesuaikan tabel users yang secara default sudah disediakan oleh Laravel. Pada awalnya, Laravel hanya akan menyediakan tabel users dengan skema sebagai berikut:\nKolom Tipe Data id BIGINT UNSIGNED PRIMARY_KEY auto_increment name VARCHAR(255) email VARCHAR(255) email_verified_at TIMESTAMP DEFAULT NULL password VARCHAR(255) remember_token VARCHAR(100) created_at TIMESTAMP NULL updated_at TIMESTAMP NULL Dan kita akan mengubahnya menjadi berikut:\nKolom Tipe Data id BIGINT UNSIGNED PRIMARY_KEY auto_increment full_name VARCHAR(255) username VARCHAR(255) bio VARCHAR(255) is_private BOOLEAN password VARCHAR(255) remember_token VARCHAR(100) created_at TIMESTAMP NULL updated_at TIMESTAMP NULL Menambahkan Kolom username, bio, dan is_private # Dikarenakan pada soal kita membutuhkan kolom-kolom tersebut, kita akan menggunakan fitur Laravel yang bernama Migrations. Migrations berguna untuk mengubah struktur tabel tanpa menyentuh klien basis data seperti phpMyAdmin. Migrations juga berguna jika Anda bekerja dengan beberapa orang lain sebagai tim dengan menjaga supaya struktur data tetap konsisten.\nUntuk menambahkan kolom username, bio, dan is_private, kita perlu mengeksekusi perintah berikut pada command line:\nphp artisan make:migration \u0026quot;add username bio and is_private columns to users table\u0026quot;\nMenjalankan perintah tersebut akan membuat sebuah file dengan format \u0026lt;timestamp\u0026gt;_add_username_bio_and_is_private_columns_to_users_table.php pada folder database/migrations. Buka file tersebut dan Anda akan melihat file seperti ini:\n\u0026lt;?php use Illuminate\\Database\\Migrations\\Migration; use Illuminate\\Database\\Schema\\Blueprint; use Illuminate\\Support\\Facades\\Schema; return new class extends Migration { /** * Run the migrations. */ public function up(): void { Schema::table(\u0026#39;users\u0026#39;, function (Blueprint $table) { // }); } /** * Reverse the migrations. */ public function down(): void { Schema::table(\u0026#39;users\u0026#39;, function (Blueprint $table) { // }); } }; Pada sebuah Migration, Anda akan melihat fungsi up yang digunakan untuk memanipulasi sebuah tabel pada sebuah basis data. Sementara fungsi down digunakan untuk mengembalikan skema pada state sebelumnya. Bisa dikatakan bahwa fungsi down harus berisi kebalikan dari fungsi up.\nPada sebuah Migration juga, Anda akan melihat kode Schema::table('users', function (Blueprint $table) { }), kode ini menandakan bahwa kita akan membuat perubahan pada tabel users.\nKita akan ubah Migration di atas sebagai berikut:\n\u0026lt;?php use Illuminate\\Database\\Migrations\\Migration; use Illuminate\\Database\\Schema\\Blueprint; use Illuminate\\Support\\Facades\\Schema; return new class extends Migration { /** * Run the migrations. */ public function up(): void { Schema::table(\u0026#39;users\u0026#39;, function (Blueprint $table) { $table-\u0026gt;after(\u0026#39;name\u0026#39;, function (Blueprint $table) { $table-\u0026gt;string(\u0026#39;username\u0026#39;); $table-\u0026gt;string(\u0026#39;bio\u0026#39;); $table-\u0026gt;boolean(\u0026#39;is_private\u0026#39;); }); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table(\u0026#39;users\u0026#39;, function (Blueprint $table) { $table-\u0026gt;dropColumn([ \u0026#39;username\u0026#39;, \u0026#39;bio\u0026#39;, \u0026#39;is_private\u0026#39;, ]); }); } }; Pada fungsi up yang sudah kita modifikasi, kita menggunakan sintaks $table-\u0026gt;after(string $column, Callback) untuk menambahkan beberapa kolom setelah kolom yang sudah kita tentukan pada parameter $column. Pada kasus ini kita menambahkan kolom:\nusername - string / VARCHAR(255) bio - string / VARCHAR(255) is_private - boolean Seperti yang kita katakan tadi bahwa fungsi down merupakan kebalikan dari fungsi up, maka kita akan menghapus kolom-kolom di atas.\nSetelah disimpan, kita harus menjalankan Migration tersebut dengan menjalankan php artisan migrate pada command line. Setelah Migration berhasil dijalankan, maka kita akan melihat perubahan pada tabel users sebagai berikut:\nKolom Tipe Data id BIGINT UNSIGNED PRIMARY_KEY auto_increment name VARCHAR(255) username VARCHAR(255) bio VARCHAR(255) is_private BOOLEAN email VARCHAR(255) email_verified_at TIMESTAMP DEFAULT NULL password VARCHAR(255) remember_token VARCHAR(100) created_at TIMESTAMP NULL updated_at TIMESTAMP NULL Mengubah kolom name menjadi full_name # Untuk mengubah kolom name menjadi full_name, kita perlu mengeksekusi perintah berikut pada command line:\nphp artisan make:migration \u0026quot;change name to full_name in users table\u0026quot;\nMenjalankan perintah tersebut akan membuat sebuah file dengan format \u0026lt;timestamp\u0026gt;_change_name_to_full_name_in_users_table.php pada folder database/migrations. Buka file tersebut dan ubahlah file tersebut sampai terlihat seperti ini:\n\u0026lt;?php use Illuminate\\Database\\Migrations\\Migration; use Illuminate\\Database\\Schema\\Blueprint; use Illuminate\\Support\\Facades\\Schema; return new class extends Migration { /** * Run the migrations. */ public function up(): void { Schema::table(\u0026#39;users\u0026#39;, function (Blueprint $table) { $table-\u0026gt;renameColumn(\u0026#39;name\u0026#39;, \u0026#39;full_name\u0026#39;); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table(\u0026#39;users\u0026#39;, function (Blueprint $table) { $table-\u0026gt;renameColumn(\u0026#39;full_name\u0026#39;, \u0026#39;name\u0026#39;); }); } }; Fungsi dari $table-\u0026gt;renameColumn(string $before, string $after) adalah untuk mengubah nama kolom.\nJangan tertukar antara fungsi rename dan renameColumn. Fungsi rename akan mengubah nama tabel, sementara renameColumn akan mengubah nama salah satu kolom dari tabel tersebut. Setelah disimpan, kita harus menjalankan Migration tersebut dengan menjalankan php artisan migrate pada command line. Setelah Migration berhasil dijalankan, maka kita akan melihat perubahan pada tabel users sebagai berikut:\nKolom Tipe Data id BIGINT UNSIGNED PRIMARY_KEY auto_increment full_name VARCHAR(255) username VARCHAR(255) bio VARCHAR(255) is_private BOOLEAN email VARCHAR(255) email_verified_at TIMESTAMP DEFAULT NULL password VARCHAR(255) remember_token VARCHAR(100) created_at TIMESTAMP NULL updated_at TIMESTAMP NULL Menghapus kolom email dan email_verified_at # Untuk menghapus kolom email menjadi email_verified_at, kita perlu mengeksekusi perintah berikut pada command line:\nphp artisan make:migration \u0026quot;drop email and email_verified_at columns in users table\nMenjalankan perintah tersebut akan membuat sebuah file dengan format \u0026lt;timestamp\u0026gt;_drop_email_and_email_verified_at_columns_in_users_table.php pada folder database/migrations. Buka file tersebut dan ubahlah file tersebut sampai terlihat seperti ini:\n\u0026lt;?php use Illuminate\\Database\\Migrations\\Migration; use Illuminate\\Database\\Schema\\Blueprint; use Illuminate\\Support\\Facades\\Schema; return new class extends Migration { /** * Run the migrations. */ public function up(): void { Schema::table(\u0026#39;users\u0026#39;, function (Blueprint $table) { $table-\u0026gt;dropColumn([ \u0026#39;email\u0026#39;, \u0026#39;email_verified_at\u0026#39;, ]); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table(\u0026#39;users\u0026#39;, function (Blueprint $table) { $table-\u0026gt;after(\u0026#39;is_private\u0026#39;, function (Blueprint $table) { $table-\u0026gt;string(\u0026#39;email\u0026#39;)-\u0026gt;unique(); $table-\u0026gt;timestamp(\u0026#39;email_verified_at\u0026#39;)-\u0026gt;nullable(); }); }); } }; Fungsi dari $table-\u0026gt;dropColumn(array $columns) adalah untuk menghapus kolom-kolom yang ada pada tabel tersebut, Anda dapat menghapus satu atau lebih kolom.\nDikarenakan pada fungsi up kita menghapus dua kolom, maka pada fungsi down kita harus menambahkan dua tabel dengan nama kolom dan tipe data yang sesuai.\nemail - string / VARCHAR(255) email_verified_at - TIMESTAMP NULL Pada bagian ini juga kita dapat melihat dua modifier berupa unique dan nullable. Modifier unique menandakan bahwa kolom tersebut haruslah unik dan tidak boleh ada data yang serupa pada baris itu, contohnya pada baris 1 terdapat email yourlovelydev@gmail.com, maka baris selanjutnya tidak boleh ada email seperti itu juga. Modifier nullable menandakan bahwa kolom tersebut boleh dikosongkan dan dapat diisi lain kali.\nSetelah disimpan, kita harus menjalankan Migration tersebut dengan menjalankan php artisan migrate pada command line. Setelah Migration berhasil dijalankan, maka kita akan melihat perubahan pada tabel users sebagai berikut:\nKolom Tipe Data id BIGINT UNSIGNED PRIMARY_KEY auto_increment full_name VARCHAR(255) username VARCHAR(255) bio VARCHAR(255) is_private BOOLEAN password VARCHAR(255) remember_token VARCHAR(100) created_at TIMESTAMP NULL updated_at TIMESTAMP NULL Penyesuaian Model User # Model adalah suatu kelas dalam Laravel yang berfungsi untuk memanipulasi data berdasarkan tabel yang merepresentasikan nama model tersebut. Seperti contohnya Model User (singular), maka nama tabelnya adalah users (plural). Contoh lainnya adalah model Transaction, maka nama tabelnya adalah transactions. Intinya, nama Model berbentuk singular, dan nama tabelnya berbentuk plural. Seluruh Model terdapat pada folder app/Models.\nBuka Model User yang terdapat pada folder app/Models, Anda akan melihat kode seperti ini:\n\u0026lt;?php namespace App\\Models; // use Illuminate\\Contracts\\Auth\\MustVerifyEmail; use Illuminate\\Database\\Eloquent\\Factories\\HasFactory; use Illuminate\\Foundation\\Auth\\User as Authenticatable; use Illuminate\\Notifications\\Notifiable; use Laravel\\Sanctum\\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; /** * The attributes that are mass assignable. * * @var array\u0026lt;int, string\u0026gt; */ protected $fillable = [ \u0026#39;name\u0026#39;, \u0026#39;email\u0026#39;, \u0026#39;password\u0026#39;, ]; /** * The attributes that should be hidden for serialization. * * @var array\u0026lt;int, string\u0026gt; */ protected $hidden = [ \u0026#39;password\u0026#39;, \u0026#39;remember_token\u0026#39;, ]; /** * The attributes that should be cast. * * @var array\u0026lt;string, string\u0026gt; */ protected $casts = [ \u0026#39;email_verified_at\u0026#39; =\u0026gt; \u0026#39;datetime\u0026#39;, \u0026#39;password\u0026#39; =\u0026gt; \u0026#39;hashed\u0026#39;, ]; } Struktur pada Model User ini terdapat sebagai berikut:\nTraits, dapat dilihat pada bagian atas setelah kurawal ({), menggunakan keyword use. Pada Model User ini menggunakan Trait HasApiTokens, HasFactory, dan Notifiable. Masing-masing berfungsi untuk menandakan bahwa Model ini dapat memiliki API Token, memiliki Laravel Factory, dan dapat menerima Notifikasi. Attributes, adalah variabel (atau bisa disebut properti) yang merepresentasikan nilai dari karakteristik kelas tersebut. Pada Model User ini terdapat 3 Atribut utama yaitu: $fillable, yang berfungsi untuk me-whitelist kolom-kolom apa saja yang boleh diisi pada saat Anda menggunakan metode \u0026ldquo;mass-fill\u0026rdquo;; $hidden, yang berfungsi untuk menyembunyikan kolom-kolom pada saat penyajian data; serta $casts, yang berfungsi untuk menngubah tipe data yang ada pada basis data, dan merepresentasikannya dalam tipe data yang ada pada PHP, atau tipe data baru yang sudah dikembangkan. Model User adalah sebuah model yang mengekstensi / menuruni sifat-sifat dari Authenticatable yang didalamnya berisi fungsi-fungsi untuk mengautentikasi pengguna ke aplikasi, sehingga User dapat digunakan untuk masuk / keluar di aplikasi yang Anda bangun. Kelas Authenticatable sendiri menuruni sifat-sifat dari Model sehingga dapat berinteraksi dengan basis data.\nKita akan menyesuaikan kolom-kolom baru yang ada pada tabel users dengan atribut-atribut pada model User:\n\u0026lt;?php namespace App\\Models; // use Illuminate\\Contracts\\Auth\\MustVerifyEmail; use Illuminate\\Database\\Eloquent\\Factories\\HasFactory; use Illuminate\\Foundation\\Auth\\User as Authenticatable; use Illuminate\\Notifications\\Notifiable; use Laravel\\Sanctum\\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; /** * The attributes that are mass assignable. * * @var array\u0026lt;int, string\u0026gt; */ protected $fillable = [ - \u0026#39;name\u0026#39;, + \u0026#39;full_name\u0026#39;, \u0026#39;email\u0026#39;, \u0026#39;password\u0026#39;, + \u0026#39;username\u0026#39;, + \u0026#39;bio\u0026#39;, + \u0026#39;is_private\u0026#39;, ]; /** * The attributes that should be hidden for serialization. * * @var array\u0026lt;int, string\u0026gt; */ protected $hidden = [ \u0026#39;password\u0026#39;, \u0026#39;remember_token\u0026#39;, ]; /** * The attributes that should be cast. * * @var array\u0026lt;string, string\u0026gt; */ protected $casts = [ \u0026#39;email_verified_at\u0026#39; =\u0026gt; \u0026#39;datetime\u0026#39;, \u0026#39;password\u0026#39; =\u0026gt; \u0026#39;hashed\u0026#39;, + \u0026#39;is_private\u0026#39; =\u0026gt; \u0026#39;boolean\u0026#39;, ]; } Autentikasi # Setelah seluruh penyesuaian terhadap tabel users dan Model User sudah selesai, saatnya kita mengerjakan soal LKS bagian Autentikasi. Bagian ini terbagi menjadi 3 sub-bagian yaitu Register yang digunakan oleh user untuk mendaftar, Login yang digunakan oleh user untuk masuk ke sistem dan mendapatkan data-data yang ada pada aplikasi, serta Logout yang digunakan oleh user untuk keluar dari aplikasi.\nRegister # Membuat RegisterController # Pertama, kita harus membuat Controller yang akan menanggung beban Request yang digunakan untuk pendaftaran. Untuk membuat controller, jalankan perintah berikut:\nphp artisan make:controller Api/v1/Auth/RegisterController --invokable\nPerintah di atas akan membuat Controller di folder app/Http/Controllers/Api/v1/Auth/. Buka Controller tersebut dan Anda akan melihat kode seperti berikut:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\Auth; use App\\Http\\Controllers\\Controller; class RegisterController extends Controller { /** * Handle the incoming request. */ public function __invoke(RegisterRequest $request) { // } } Tampak kosong untuk sekarang, tapi kita akan memodifikasi kode ini lain kali.\nMendaftarkan Rute Registrasi # Untuk mendaftarkan rute registrasi kita ke Controller yang barusan dibuat, buka file routes/api.php, Anda akan melihat kode sebagai berikut:\n\u0026lt;?php use Illuminate\\Http\\Request; use Illuminate\\Support\\Facades\\Route; /* |-------------------------------------------------------------------------- | API Routes |-------------------------------------------------------------------------- | | Here is where you can register API routes for your application. These | routes are loaded by the RouteServiceProvider and all of them will | be assigned to the \u0026#34;api\u0026#34; middleware group. Make something great! | */ Route::middleware(\u0026#39;auth:sanctum\u0026#39;)-\u0026gt;get(\u0026#39;/user\u0026#39;, function (Request $request) { return $request-\u0026gt;user(); }); Ubah file ini sehingga terlihat seperti ini:\n\u0026lt;?php use Illuminate\\Support\\Facades\\Route; Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;v1\u0026#39;], function () { Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;auth\u0026#39;], function () { Route::post(\u0026#39;/register\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\RegisterController::class); }); }); Pada file ini, kita berkenalan dengan sebuah Fasad bernama Route, Fasad Route ini berfungsi untuk mendaftarkan rute-rute yang mungkin ada di aplikasi yang kita bangun. Pengembang memiliki kebebasan untuk mendefinisikan bagaimana rute aplikasi yang dibangun, namun disarankan untuk tetap menggunakan standar penulisan kode dan praktek terbaik yang sudah ada di kalangan para pengembang.\nPertama-tama kita akan membuat sebuah grup rute v1 yang mendefinisikan bahwa kumpulan rute-rute yang ada pada bagian ini merupakan rute versi pertama. Setelah itu, definisikan grup rute auth yang mendefinisikan bahwa kumpulan rute-rute yang ada bagian ini merupakan rute autentikasi.\nSetelah itu, baru kita daftarkan rute register kita yang akan di-handle oleh RegisterController yang barusan kita buat. Endpoint register ini kita atur untuk hanya dapat menerima metode POST.\nMengubah file RegisterController # Setelah mendaftarkan rute register, kita sebenarnya sudah dapat menerima data yang dikirimkan oleh user, namun kita belum mengolahnya sehingga User dapat masuk ke aplikasi. Sehingga kita harus memodifikasi RegisterController supaya data yang dikirimkan dapat memperbolehkan User untuk masuk ke aplikasi.\nKita modifikasi RegisterController sehingga dilihat sebagai berikut:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\Auth; use App\\Http\\Controllers\\Controller; use App\\Http\\Requests\\Api\\v1\\Auth\\RegisterRequest; use App\\Models\\User; class RegisterController extends Controller { /** * Handle the incoming request. */ public function __invoke(RegisterRequest $request) { $userData = $request-\u0026gt;validate([ \u0026#39;full_name\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;], \u0026#39;bio\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;, \u0026#39;max:100\u0026#39;], \u0026#39;username\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;, \u0026#39;unique:users,username\u0026#39;, \u0026#39;min:3\u0026#39;, \u0026#39;regex:/^[a-zA-Z0-9._]+$/\u0026#39;], \u0026#39;password\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;, \u0026#39;confirmed\u0026#39;], \u0026#39;is_private\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;boolean\u0026#39;], ]); try { $user = User::create($userData); $token = $user-\u0026gt;createToken(\u0026#39;facegram\u0026#39;); } catch (\\Throwable $th) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Register failed: \u0026#39; . $th-\u0026gt;getMessage(), ], 500); } return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Register success\u0026#39;, \u0026#39;token\u0026#39; =\u0026gt; $token-\u0026gt;plainTextToken, \u0026#39;user\u0026#39; =\u0026gt; $user, ]); } } Dalam tahap ini, pertama-tama kita harus memvalidasi data yang dikirimkan oleh user, jika merujuk pada soal, seluruh data yang dikirimkan harus divalidasi sebagai berikut:\nKolom Aturan Validasi full_name required bio required, max 100 chars username required, min 3 chars, unique, only alphanumeric, dot \u0026ldquo;.\u0026rdquo; or underscore \u0026ldquo;_\u0026rdquo; allowed) password required, min 6 chars is_private boolean Validasi-validasi yang ada pada Laravel, lengkapnya dapat dilihat pada dokumentasi berikut, namun yang digunakan saat ini adalah:\nrequired, memvalidasi kolom tersebut wajib diisi string, memvalidasi kolom tersebut berbentuk string atau teks max:num, memvalidasi kolom tersebut tidak boleh lebih dari num karakter unique:table,column, memvalidasi data dari kolom tersebut tidak boleh sama dari data yang ada di tabel table dan kolom column min:num, memvalidasi kolom tersebut minimal berisi num karakter regex:pattern, memvalidasi kolom tersebut harus berbentuk sebuah pola pattern, pada kasus ini kita menggunakan regex /^[a-zA-Z0-9._]+$/ yang berarti menerima karakter a s/d z, A s/d Z, 0 s/d 9, serta memperbolehkan titik . dan underscore _ confirmed, memvalidasi kolom tersebut harus sama dengan data \u0026lt;column\u0026gt;_confirmation boolean, memalidasi kolom tersebut harus berbentuk boolean (true / false) Perhatikan kode berikut:\n$userData = $request-\u0026gt;validate([ \u0026#39;full_name\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;], \u0026#39;bio\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;, \u0026#39;max:100\u0026#39;], \u0026#39;username\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;, \u0026#39;unique:users,username\u0026#39;, \u0026#39;min:3\u0026#39;, \u0026#39;regex:/^[a-zA-Z0-9._]+$/\u0026#39;], \u0026#39;password\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;, \u0026#39;confirmed\u0026#39;], \u0026#39;is_private\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;boolean\u0026#39;], ]); Kelas \\Illuminate\\Http\\Request memiliki fungsi validate() yang digunakan untuk memvalidasi data yang dikirimkan. Setelah data yang dikirimkan berhasil divalidasi, maka data akan di-assign ke sebuah variabel bernama $userData, variabel ini akan kita gunakan pada tahap selanjutnya.\nSetelah melakukan validasi, kita harus menyimpan data user yang dikirimkan ke basis data, perhatikan kode berikut:\ntry { $user = User::create($userData); $token = $user-\u0026gt;createToken(\u0026#39;facegram\u0026#39;); } catch (\\Throwable $th) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Register failed: \u0026#39; . $th-\u0026gt;getMessage(), ], 500); } Blok kode try-catch berfungsi untuk \u0026ldquo;mencoba\u0026rdquo; potongan kode, lalu jika terdapat sebuah error, maka kita dapat melakukan sesuatu terhadap error tersebut alih-alih menampilkan error kepada user.\nUntuk menyimpan data User, kita harus memanggil fungsi statis create yang terdapat pada model User dengan memanggil User::create(array $properties). Karena hasil dari validasi yang sukses adalah berbentuk array, maka variabel $userData bisa dapat langsung digunakan sebagai argumen fungsi create(). Jika User::create($userData) berhasil dijalankan, maka hasil dari eksekusi tersebut akan mereturn sebuah objek User yang dapat disajikan ke user. Kita menyimpannya ke variabel $user untuk digunakan nanti.\nSetelah menyimpan data User, harapannya adalah user dapat langsung masuk ke aplikasi, karena itu kita harus membuat Sanctum Token juga. Untuk membuat Sanctum Token, kita harus menggunakan Model yang menggunakan Trait HasApiTokens, karena model User sudah mengimplementasi Trait tersebut, maka kita dapat langsung membuatnya dengan memanggil fungsi createToken(string $tokenName). Pada kasus ini kita akan menyimpan token yang sudah dibuat ke variabel $token.\nNamun dari kedua baris kode yang ada di blok try, jika ada salah satu dari eksekusi kode yang bermasalah, maka blok catch akan menerima error tersebut dan kita dapat mengolah error tersebut sebelum dikirimkan ke user. Approach catch kita kali ini adalah mengembalikan Response berbentuk JSON dengan pesan Register failed: \u0026lt;error message\u0026gt;. Keyword return menandakan bahwa kode setelahnya siap untuk dikirimkan kembali kepada user. Yang dikembalikan adalah sebuah response(), berbentuk json(array $data, int $status). HTTP Status 500 yang kita tulis menandakan bahwa error terjadi karena ada kesalahan pada server.\nJika seluruh kode yang ada di blok try berhasil dijalankan, maka selanjutnya Controller akan mengirimkan response() berbentuk json() yang ada di bawahnya yaitu:\nreturn response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Register success\u0026#39;, \u0026#39;token\u0026#39; =\u0026gt; $token-\u0026gt;plainTextToken, \u0026#39;user\u0026#39; =\u0026gt; $user, ]); Menyesuaikan Validasi # Dalam konsep SOLID, terdapat konsep One Responsibility, yaitu setiap file atau kelas hanya boleh memiliki satu tanggung jawab. Karena Controller berfungsi sebagai penjembatan antara Request dan basis data, adanya proses validasi menjadikan Controller RegisterController memiliki dua tanggung jawab, karena itu kita harus memisahkan peran tersebut ke sebuah Request. Untuk membuat sebuah Request yang sesuai dengan konteks ini, jalankan perintah berikut:\nphp artisan make:request Api/v1/Auth/RegisterRequest\nFile ini akan membuat sebuah file yang diletakkan di app/Http/Requests/Api/v1/Auth/RegisterRequest, buka file tersebut dan Anda akan melihat kode ini:\n\u0026lt;?php namespace App\\Http\\Requests\\Api\\v1\\Auth; use Illuminate\\Foundation\\Http\\FormRequest; class RegisterRequest extends FormRequest { /** * Determine if the user is authorized to make this request. */ public function authorize(): bool { return false; } /** * Get the validation rules that apply to the request. * * @return array\u0026lt;string, \\Illuminate\\Contracts\\Validation\\ValidationRule|array\u0026lt;mixed\u0026gt;|string\u0026gt; */ public function rules(): array { return [ // ]; } } Pertama-tama, kita akan mengubah value return yang ada pada fungsi authorize menjadi true, lalu memindahkan seluruh aturan validasi yang ada di RegisterController ke RegisterRequest. Hasil kode akhir akan berbentuk seperti ini:\n\u0026lt;?php namespace App\\Http\\Requests\\Api\\v1\\Auth; use Illuminate\\Foundation\\Http\\FormRequest; class RegisterRequest extends FormRequest { /** * Determine if the user is authorized to make this request. */ public function authorize(): bool { return true; } /** * Get the validation rules that apply to the request. * * @return array\u0026lt;string, \\Illuminate\\Contracts\\Validation\\ValidationRule|array\u0026lt;mixed\u0026gt;|string\u0026gt; */ public function rules(): array { return [ \u0026#39;full_name\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;], \u0026#39;bio\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;, \u0026#39;max:100\u0026#39;], \u0026#39;username\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;, \u0026#39;unique:users,username\u0026#39;, \u0026#39;min:3\u0026#39;, \u0026#39;regex:/^[a-zA-Z0-9._]+$/\u0026#39;], \u0026#39;password\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;, \u0026#39;confirmed\u0026#39;], \u0026#39;is_private\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;boolean\u0026#39;], ]; } } Setelah itu, pada file RegisterController, kita harus mengubah penulisannya sedikit. Pertama, tambahkan kode:\nuse App\\Http\\Requests\\Api\\v1\\Auth\\RegisterRequest; di barisan paling atas sebelum class RegisterController extends Controller, lalu mengubah public function __invoke(Request $request) menjadi public function __invoke(RegisterRequest $request). Perubahan kode lengkapnya adalah sebagai berikut:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\Auth; use App\\Http\\Controllers\\Controller; + use App\\Http\\Requests\\Api\\v1\\Auth\\RegisterRequest; use App\\Models\\User; - use Illuminate\\Http\\Request; class RegisterController extends Controller { /** * Handle the incoming request. */ + public function __invoke(RegisterRequest $request) - public function __invoke(Request $request) { + $userData = $request-\u0026gt;validated(); - $userData = $request-\u0026gt;validate([ - \u0026#39;full_name\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;], - \u0026#39;bio\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;, \u0026#39;max:100\u0026#39;], - \u0026#39;username\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;, \u0026#39;unique:users,username\u0026#39;, \u0026#39;min:3\u0026#39;, \u0026#39;regex:/^[a-zA-Z0-9._]+$/\u0026#39;], - \u0026#39;password\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;string\u0026#39;, \u0026#39;confirmed\u0026#39;], - \u0026#39;is_private\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;boolean\u0026#39;], - ]); try { $user = User::create($userData); $token = $user-\u0026gt;createToken(\u0026#39;facegram\u0026#39;); } catch (\\Throwable $th) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Register failed: \u0026#39; . $th-\u0026gt;getMessage(), ], 500); } return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Register success\u0026#39;, \u0026#39;token\u0026#39; =\u0026gt; $token-\u0026gt;plainTextToken, \u0026#39;user\u0026#39; =\u0026gt; $user, ]); } } Jika Anda sudah berhasil mencapai titik ini, selamat! Anda hanya perlu mencobanya menggunakan software API Testing seperti Postman, Thunder Client, atau APIDog. Pilih sesuai yang Anda inginkan. Serta jangan lupa, perjalanan baru dimulai sekarang.\nLogin # Seperti biasa, alur untuk pembuatan sebuah endpoint baru adalah dengan membuat Controllernya terlebih dahulu, untuk membuat LoginController, jalankan perintah berikut pada command line:\nphp artisan make:controller Api/v1/Auth/LoginController --invokable\nMendaftarkan Rute Login # Abaikan dulu penulisan kode LoginController untuk sementara waktu, dan kita fokus ke pendaftaran rute. Seperti yang sebelumnya dilakukan, buka file routes/api.php, lalu daftarkan LoginController ke rute login.\nSebagai tantangan, jangan buka dulu spoiler di bawah sebelum Anda bisa mengetahui apa yang harus Anda lakukan. Ingat bahwa Anda sedang belajar dan bukan meniru.\nSpoiler routes/api.php setelah ditambahkan \u0026lt;?php use Illuminate\\Support\\Facades\\Route; Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;v1\u0026#39;], function () { Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;auth\u0026#39;], function () { Route::post(\u0026#39;/register\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\RegisterController::class); Route::post(\u0026#39;/login\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\LoginController::class); }); }); Apakah rute /v1/auth/login sudah Anda tambahkan? Jika sudah, silahkan buka spoiler di atas dan komparasikan perbedaannya dengan kode Anda. Jika sudah sesuai, silahkan ke langkah berikutnya.\nMengubah file LoginController # Buka file app/Http/Controllers/Api/v1/Auth/LoginController.php, lalu kita modifikasi kodenya hingga terlihat sebagai berikut:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\Auth; use App\\Http\\Controllers\\Controller; use App\\Models\\User; use Illuminate\\Http\\Request; use Illuminate\\Support\\Facades\\Auth; class LoginController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request) { $userData = $request-\u0026gt;validate([ \u0026#39;username\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;], \u0026#39;password\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;], ]); if (! Auth::attempt($userData)) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Wrong username or password\u0026#39;, ], 401); } $user = User::query() -\u0026gt;where(\u0026#39;username\u0026#39;, $userData[\u0026#39;username\u0026#39;]) -\u0026gt;first(); $token = $user-\u0026gt;createToken(\u0026#39;facegram\u0026#39;); return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Login success\u0026#39;, \u0026#39;token\u0026#39; =\u0026gt; $token-\u0026gt;plainTextToken, \u0026#39;user\u0026#39; =\u0026gt; $user, ]); } } Untuk validasi, simpel saja karena tidak ada kebutuhan khusus yang didefinisikan dalam soal, sehingga kita hanya perlu menandakan bahwa kolom username dan password adalah required.\nSetelah itu, kita akan menggunakan Fasad Auth untuk \u0026ldquo;mencoba\u0026rdquo; masuk ke dalam sistem menggunakan data yang sudah divalidasi. Metode attempt(array $data) akan me-return boolean, true jika berhasil dan false jika gagal. Perhatikan tanda seru atau \u0026ldquo;bang\u0026rdquo; pada kode if (! Auth::attempt($userData)), kode itu dibaca sebagai \u0026ldquo;Jika Fasad Auth GAGAL untuk mencoba masuk ke sistem dengan $userData yang diberikan, maka\u0026hellip;\u0026rdquo; kita akan memberikan response berupa json dengan pesan \u0026ldquo;Wrong username or password\u0026rdquo; dengan HTTP Response Code 401 yang berarti \u0026ldquo;Unauthorized\u0026rdquo;.\nKode setelah blok if adalah kode yang dieksekusi \u0026ldquo;Jika Fasad Auth BERHASIL\u0026rdquo;, setelahnya kita mengambil data User dengan menggunakan kolom username sebagai klausa WHERE, lalu membuat token dari User tersebut, dan terakhir kita memberikan response berupa json dengan data yang sudah kita ambil.\nMemindahkan Validasi ke LoginRequest # Apakah Anda memngingat prinsip O pada SOLID yang berarti \u0026ldquo;One Responsibility\u0026rdquo;? Kita akan mengimplementasikannya pada Login juga. Seperti biasa kita jalankan perintah untuk membuat Request dengan perintah:\nphp artisan make:request Api/v1/Auth/LoginRequest\nLalu buka file app/Http/Requests/Api/v1/Auth/LoginRequest.php, ubah return value authorize menjadi true, lalu pindahkan aturan validasi yang ada pada LoginController ke fungsi rules pada LoginRequest. Hasil kodenya adalah sebagai berikut:\n\u0026lt;?php namespace App\\Http\\Requests\\Auth\\v1; use Illuminate\\Foundation\\Http\\FormRequest; class LoginRequest extends FormRequest { /** * Determine if the user is authorized to make this request. */ public function authorize(): bool { return true; } /** * Get the validation rules that apply to the request. * * @return array\u0026lt;string, \\Illuminate\\Contracts\\Validation\\ValidationRule|array\u0026lt;mixed\u0026gt;|string\u0026gt; */ public function rules(): array { return [ \u0026#39;username\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;], \u0026#39;password\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;], ]; } } Tentu saja, kita juga harus mengubah kode yang ada pada LoginController, kurang lebih sebagai berikut:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\Auth; use App\\Http\\Controllers\\Controller; + use App\\Http\\Requests\\Auth\\v1\\LoginRequest; use App\\Models\\User; - use Illuminate\\Http\\Request; use Illuminate\\Support\\Facades\\Auth; class LoginController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request) { - $userData = $request-\u0026gt;validate([ - \u0026#39;username\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;], - \u0026#39;password\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;], - ]); + if (! Auth::attempt($request-\u0026gt;validated())) { - if (! Auth::attempt($userData)) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Wrong username or password\u0026#39;, ], 401); } $user = User::query() + -\u0026gt;where(\u0026#39;username\u0026#39;, $request-\u0026gt;username) - -\u0026gt;where(\u0026#39;username\u0026#39;, $userData[\u0026#39;username\u0026#39;]) -\u0026gt;first(); $token = $user-\u0026gt;createToken(\u0026#39;facegram\u0026#39;); return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Login success\u0026#39;, \u0026#39;token\u0026#39; =\u0026gt; $token-\u0026gt;plainTextToken, \u0026#39;user\u0026#39; =\u0026gt; $user, ]); } } Logout # Fitur Logout adalah fitur terakhir yang ada pada Autentikasi ini. Sebelum melanjutkan, Anda sudah mengetahui siklus Pembuatan Controller dan Pendaftaran Rute kan? Jika masih belum paham, di bawah ini adalah ringkasannya:\nBuat Controller Daftarkan Rute Modifikasi Controller Validasi Data Manipulasi Data Return Value Sebagai tantangan kali ini, coba Anda buat Controller LogoutController, tulis perintahnya dulu di command line, namun jangan eksekusi perintahnya terlebih dulu. Jika Anda rasa perintah yang Anda tulis sudah benar, silahkan cross-check dengan perintah yang saya buat di bawah:\nSpoiler: Perintah pembuatan Controller LogoutController php artisan make:controller Api/v1/Auth/LogoutController --invokable\nJika kodenya sama dengan apa yang Anda tulis, selamat! Ini membuktikan Anda belajar dengan sungguh-sungguh. Selanjutnya mungkin Anda sudah tau bahwa kita akan mendaftarkan Rutenya ke routes/api.php, namun dikarenakan kita memiliki satu fitur yang mencegah orang tanpa \u0026ldquo;Token\u0026rdquo; bisa menggunakan endpoint logout, maka harus Saya jelaskan juga arti kode di balik fungsi tersebut.\nMendaftarkan Rute Logout # File routes/api.php yang kita sudah kenal dimodifikasi sampai dengan terlihat sebagai berikut:\n\u0026lt;?php use Illuminate\\Support\\Facades\\Route; Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;v1\u0026#39;], function () { Route::group([\u0026#39;prefix\u0026#39; =\u0026gt; \u0026#39;auth\u0026#39;], function () { Route::post(\u0026#39;/register\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\RegisterController::class); Route::post(\u0026#39;/login\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\LoginController::class); Route::post(\u0026#39;/logout\u0026#39;, \\App\\Http\\Controllers\\Api\\v1\\Auth\\LogoutController::class)-\u0026gt;middleware(\u0026#39;auth:sanctum\u0026#39;); }); }); Tidak seperti Rute register dan login, kita memiliki tambahan modifier middleware(array|string|null $middleware). Middleware auth:sanctum menginstruksikan kepada Fasad Auth bahwa seluruh orang yang mengakses Rute logout harus memiliki Bearer Token. Hal ini mencegah Rute logout tidak dapat diakses oleh orang yang belum pernah login.\nMengubah file LogoutController # Ubah file app/Http/Controllers/Api/v1/Auth/LogoutController sehingga menjadi kode seperti ini:\n\u0026lt;?php namespace App\\Http\\Controllers\\Api\\v1\\Auth; use App\\Http\\Controllers\\Controller; use Illuminate\\Http\\Request; class LogoutController extends Controller { /** * Handle the incoming request. */ public function __invoke(Request $request) { $user = $request-\u0026gt;user(); try { $user-\u0026gt;tokens()-\u0026gt;delete(); } catch (\\Throwable $th) { return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Logout failed: \u0026#39; . $th-\u0026gt;getMessage(), ], 500); } return response()-\u0026gt;json([ \u0026#39;message\u0026#39; =\u0026gt; \u0026#39;Logout success\u0026#39;, ]); } } Kita tidak perlu melakukan validasi apapun, karena tujuan kita pada modul Logout kali ini hanyalah menghapus token-token yang terkait pada suatu User, sehingga token-token tersebut dinyatakan \u0026ldquo;Invalid\u0026rdquo; sehingga User tidak dapat mengakses Rute dengan Middleware auth:sanctum menggunakan token yang sama.\nKode $request-\u0026gt;user() adalah perintah untuk mendapatkan instance User. Di latar belakang, auth:sanctum mencoba untuk mendapatkan data User dari Access Token yang diberikan melalui Header Request, sehingga kita dapat mengambil data User dari Fasad Request tanpa harus mengetahui username dan password di setiap Controller.\nPada blok try, kita coba untuk menghapus data token-token yang terkait pada User tersebut, lalu jika gagal, maka simpelnya kita akan return response sebagai json dengan pesan bahwa \u0026ldquo;Logout failed: error\u0026rdquo;. Jika berhasil maka kita kirim response berbentuk json dengan pesan bahwa \u0026ldquo;Logout success\u0026rdquo;.\nAkhir kata # Ini adalah artikel panjang yang memiliki durasi sekitar 20 menit, jadi dibutuhkan kesabaran untuk membaca secara teliti langkah demi langkah yang harus diimplementasikan. Terima kasih sudah membaca sampai akhir, semoga Anda lolos dalam LKS Provinsi 2024!\nKode lengkap.\n","date":"4 Mei 2024","externalUrl":null,"permalink":"/artikel/2024/05/04/lks-provinsi-modul-3-step-1/","section":"Artikel-artikel","summary":"","title":"Tutorial Mengerjakan Soal Modul 3 LKS Provinsi Web Technology - Autentikasi","type":"artikel"},{"content":"Halo semua 👋, Saya ucapkan selamat kepada semua yang berhasil lolos ke tingkat Provinsi, dan semoga kalian diberikan kesehatan dan semangat koding.\nPada seri artikel kali ini, Saya akan membahas step-by-step dan penjelasan secara detail untuk mengerjakan soal Modul 3 dari LKS tingkat Provinsi bidang Web Technology. Artikel ini diperuntukkan untuk siswa-siswa yang ingin belajar untuk Lomba ataupun yang bukan untuk lomba (sekadar meningkatkan skill), serta untuk siswa-siswa peserta LKS yang tidak memiliki waktu dan trainer yang dapat membantu Anda mengasah skill lomba saat ini.\nLKS menjadi salah satu lomba tahunan yang diselenggarakan untuk mencari bibit kompeten dari siswa SMK seluruh Indonesia, jika Anda adalah siswa yang lolos pada tingkat Provinsi, Saya ucapkan selamat karena Anda adalah siswa terbaik dari Kabupaten / Kota tempat Anda belajar.\nAkhir kata, silahkan Anda baca seri dari artikel ini satu per-satu dengan sabar dan teliti, serta senantiasa berdo\u0026rsquo;a untuk kesehatan Anda dalam fase latihan. Ingat, latihan dengan cukup namun sering akan lebih efektif daripada latihan intens namun sesekali. Jangan ragu untuk memberikan komentar jika ada pertanyaan / kritik, like artikel ini jika membantu, dan jangan lupa untuk melakukan sedikit peregangan badan di sela-sela latihan.\nAkhir kata, Saya ucapkan semoga sukses dan melaju ke tingkat Nasional!\n","date":"4 Mei 2024","externalUrl":null,"permalink":"/artikel/2024/05/04/lks-provinsi-modul-3/","section":"Artikel-artikel","summary":"","title":"Soal Modul 3 LKS Provinsi Web Technology","type":"artikel"},{"content":"","date":"6 Maret 2024","externalUrl":null,"permalink":"/tags/akatekno/","section":"Tags","summary":"","title":"Akatekno","type":"tags"},{"content":"Pugugo adalah suatu sistem yang dapat digunakan oleh Korporat sebagai media pembagian lowongan kerja, serta dapat digunakan oleh Pelamar untuk melamar suatu pekerjaan pada suatu korporat / perusahaan. Aplikasi ini memiliki beberapa modul yang masing-masing akan dijelaskan pada bagiannya tersendiri.\nPreambule\nSecara teknis, aplikasi ini dirancang menggunakan stack sebagai berikut:\nLaravel 10 Laravel Livewire 3 Tailwind CSS Alpine.JS Autentikasi # Autentikasi adalah proses untuk memverifikasi identitas pengguna atau sistem. Dalam bahasa yang lebih sederhana, autentikasi bertujuan untuk memastikan bahwa pengguna yang mencoba mengakses sesuatu adalah orang yang berhak melakukannya.\nMirip dengan verifikasi identitas di dunia nyata, misalnya saat penjagaan keamanan meminta kartu identitas pengunjung, autentikasi digital berfungsi untuk meminimalisir akses yang tidak sah ke informasi atau sistem sensitif. Proses ini menjadi langkah awal yang krusial dalam menjaga keamanan data dan mencegah penyalahgunaan informasi.\nLogin / Masuk # Modul ini memungkinkan pengguna yang sudah terdaftar untuk dapat masuk ke aplikasi dengan memberikan data Email dan Kata Sandi. Pada halaman ini terdapat beberapa pintasan untuk dapat masuk melalui akun Google, atau GitHub; serta terdapat juga pintasan untuk mengakses halaman Lupa Kata Sandi jika suatu saat pengguna melupakan kata sandinya.\nDaftar / Registrasi # Registrasi mengacu pada proses verifikasi identitas pengguna baru yang ingin mendaftarkan akun di aplikasi. Proses ini melibatkan beberapa langkah dan terbagi menjadi dua:\nDaftar Sebagai Pelamar # Untuk dapat mendaftar sebagai pelamar, akan terdapat beberapa inputan yang harus diisi yaitu:\nNama Lengkap - diisi dengan nama lengkap pengguna. Email - diisi dengan email yang valid karena akan digunakan untuk verifikasi. Kata Sandi - diisi dengan Kata Sandi yang aman, kuat, serta mudah diingat. Konfirmasi Kata Sandi - diisi persis dengan inputan Kata Sandi. Referral Code (opsional) - diisi dengan kode referral pengguna lain yang memiliki Premium Plan. Setelah mengisi seluruh data, pengguna akan dapat mengakses sistem, namun akan terdapat pesan untuk segera memverifikasikan emailnya. Pengguna yang belum memverifikasikan emailnya akan memiliki akses terbatas seperti:\nTidak dapat melamar pekerjaan. Tidak dapat mencari lowongan secara lengkap. Tidak dapat melihat detail perusahaan. Tampilan email yang akan diterima oleh pengguna dapat dilihat seperti berikut: Setelah email diverifikasi oleh pengguna, maka pengguna tersebut dapat mengakses seluruh modul sistem yang tersedia tanpa adanya limitasi.\nDaftar Sebagai Korporat # Untuk dapat mendaftar sebagai korporat, akan terdapat dua langkah yang masing-masing memiliki beberapa inputan:\nInformasi Personal, yang mewajibkan korporat yang mendaftar untuk mengisi identitas PIC (HRD, Pimpinan Perusahaan, atau seseorang yang berhak untuk memegang akun Pugugo). Data-data yang harus diberikan adalah: Nama Lengkap PIC - diisi dengan nama lengkap PIC. Email PIC - diisi dengan email PIC yang valid. Jenis Kelamin - pilih salah satu dengan jenis kelamin PIC yang sesuai. Jabatan - diisi dengan jabatan orang yang mendaftarkan perusahaannya. Kata Sandi - diisi dengan Kata Sandi yang aman, kuat, serta mudah diingat. Konfirmasi Kata Sandi - diisi persis dengan inputan Kata Sandi. Informasi Korporat, dengan inputan sebagai berikut: Nama Perusahaan - diisi dengan nama Perusahaan. Surat Izin Usaha - unggah SIU Perusahaan, input unggahan yang menerima file berupa gambar (jpg, png) atau PDF. KTP PIC - unggah KTP PIC akun Pugugo, input unggahan yang menerima file berupa gambar (jpg, png) atau PDF. Setelah mengisi seluruh data dengan lengkap, seperti halnya verifikasi akun pelamar, korporat akan melihat halaman verifikasi terlebih dahulu untuk dapat mengakses sistem. Akun korporat harus melakukan beberapa langkah-langkah yang terkirim pada email PIC dan memverifikasikan akunnya secara bersamaan.\nWalaupun korporat sudah memverifikasikan emailnya secara mandiri, namun diperlukan proses verifikasi pembayaran awal, verifikasi SIU, serta pencocokan identitas KTP dengan identitas PIC yang didaftarkan ke sistem.\nSetelah Admin Pugugo sudah memverifikasikan data identitas perusahaan dan PIC secara manual, maka akun korporat tersebut dapat menerbitkan lowongan kerja yang ada di perusahaannya.\nPenting! Pastikan Anda bersungguh-sungguh untuk menggunakan fitur pendaftaran korporat. Ketidaksesuaian data akan berakibat pada tidak terverifikasinya akun korporat dan dapat berujung pada blacklist sistem kami. Kami tidak menyediakan opsi refund untuk pembayaran awal administratif korporat. Lupa Kata Sandi # Lupa kata sandi merujuk pada situasi di mana pengguna tidak dapat mengingat kata sandi yang digunakan untuk login ke akun mereka. Hal ini bisa terjadi karena berbagai alasan, seperti:\nPengguna lupa kata sandi yang sebenarnya. Pengguna salah mengetikkan kata sandi. Pengguna belum pernah membuat akun dan menggunakan kata sandi \u0026ldquo;default\u0026rdquo; yang diberikan oleh aplikasi. Sebagian besar aplikasi web yang mengharuskan login menyediakan fitur \u0026ldquo;lupa kata sandi\u0026rdquo; untuk membantu pengguna yang mengalami situasi tersebut. Fitur ini biasanya berupa tautan atau tombol yang terletak di halaman login.\nUntuk mengatur ulang kata sandi, Anda harus memasukkan alamat email yang pernah Anda daftarkan melalui sistem. Sistem akan mengirimkan sebuah tautan yang dapat membantu Anda untuk mengatur ulang kata sandi.\nSetelah Anda mengakses tautan yang dikirimkan ke email, Anda akan melihat sebuah halaman yang berisikan 3 inputan yaitu:\nEmail - terisi otomatis, tidak dapat diubah Kata Sandi - diisi dengan kata sandi yang baru. Konfirmasi Kata Sandi - diisi persis dengan inputan Kata Sandi. Setelah Anda menekan tombol Perbarui Kata Sandi, Anda akan dialihkan kembali ke halaman Login untuk masuk ke sistem Pugugo.\nKorporat # Pada bagian ini, Anda akan mempelajari bagaimana untuk melengkapi profil, menerbitkan lowongan kerja, menerima pelamar, serta meningkatkan akun ke Premium.\nMelengkapi Profil # Untuk melengkapi profil, Anda harus menekan tombol akun yang ada di kanan atas, lalu klik sub-menu Profil.\nSetelah itu, Anda akan melihat halaman profil seperti ini.\nDi bagian kiri, terdapat beberapa menu yang digunakan untuk mengatur profil Anda.\nProfil Saya - berisi informasi profil yang harus Anda lengkapi. Sosial Media - berisi informasi sosial media boleh dilengkapi untuk meningkatkan kredibilitas. Keamanan - berisi form untuk mengubah kata sandi login. Google Map - berisi informasi Google Map yang di-embed untuk memberikan informasi kepada pelamar di mana lokasi kantor berada. Di bagian kanan berisi inputan-inputan dari masing-masing menu yang ada di bagian kiri. Screenshot di atas menunjukkan bahwa halaman saat ini adalah halaman Profil Saya. Masing-masing bagian akan dijelaskan secara lengkap.\nProfil Saya # Menu ini berisi informasi pribadi dan informasi perusahaan. Terdapat beberapa sub-bagian yang butuh dilengkapi.\nFoto Profil Foto Profil diisi dengan logo korporat atau perusahaan Anda. Harap menggunakan logo yang jelas dengan resolusi yang direkomendasikan 512x512 dengan format PNG atau format JPG.\nInformasi Korporat Informasi Korporat diisi dengan nama perusahaan Anda.\nInformasi Pribadi Informasi Pribadi diisi dengan identitas diri Anda. Terdapat beberapa inputan yaitu:\nNama Awal, diisi dengan nama awal Anda. Nama Akhir, diisi dengan nama akhir Anda, jika nama akhir Anda mengandung lebih dari 1 kata, maka cantumkan seluruh nama Anda kecuali nama awal. Email, diisi dengan email Anda, digunakan untuk login, disarankan untuk tidak mengganti email jika tidak ada pendelegasian akun. Nomor Telepon, diisi dengan nomor telepon yang valid, gunakan angka 62 di awal, dan isikan seluruh nomor tanpa ada spasi atau strip. Jenis Kelamin, pilih salah satu yang sesuai dengan jenis kelamin Anda. Jabatan, diisi dengan posisi yang Anda jabat sekarang. Bio, diisi dengan biodata singkat Anda, tidak perlu diisi secara lengkap. Alamat Informasi Alamat diisikan dengan alamat kantor saat ini. Terdapat pilihan Provinsi, Kota/Kabupaten, Kecamatan, Desa, serta Kode Pos.\nSosial Media # Pada bagian ini terdapat beberapa sosial media yang dapat kami akomodir dan ditampilkan ke Detail Informasi Perusahaan, tujuannya adalah untuk menunjukkan kredibilitas tambahan bahwa perusahaan yang Anda pegang adalah sebuah perusahaan yang valid dan dapat diakui. Sosial Media yang kami akomodir antara lain:\nFacebook YouTube WhatsApp Instagram TikTok Snapchat Pinterest Reddit LinkedIN X (sebelumnya dikenal dengan Twitter) Untuk mengisi sosial media, diharapkan untuk hanya mencantumkan ID dari masing-masing sosial media, karena kami sudah mengakomodir potongan tautan yang digunakan untuk masing-masing sosial media.\nKeamanan # Menu Keamanan ini diperuntukkan hanya untuk mengubah kata sandi. Kami sarankan untuk mengubah kata sandi Anda minimal 1 tahun sekali untuk menghindari akses secara bruteforce.\nGoogle Map # Menu Google Map ini digunakan untuk menandakan di mana kantor Anda berada. Untuk menggunakan embed Google Map, silahkan ikuti langkah-langkah berikut:\nBuka Google Map, dan cari tempat kantor Anda berada. Lalu klik tombol Bagikan atau Share. Akan terbuka dialog bagikan dari Google Maps, klik tab Embed a map, pilih ukuran yang disarankan yaitu Small atau Medium, lalu tekan tombol Copy HTML. Tempelkan kode embed ke inputan Embed Link yang tersedia pada aplikasi, lalu klik Simpan. Anda akan melihat sebuah dialog bahwa inputan berhasil disimpan dan Anda akan dapat melihat bahwa identitas lokasi perusahaan Anda sudah muncul. Menambah Lowongan Pekerjaan # Pada bagian ini Anda akan mempelajari bagaimana caranya untuk menambah Lowongan Pekerjaan. Mohon untuk ikuti langkah-langkah berikut dengan seksama.\nBuka menu Unggah Pekerjaan yang terdapat pada menu utama. Anda akan melihat beberapa informasi seperti berapa banyak lowongan pekerjaan yang dapat Anda buat, lalu daftar lowongan kerja yang sudah Anda buat. Pada screenshot di atas tidak terdapat satu-pun lowongan kerja yang sudah diterbitkan, mari kita terbitkan salah satu.\nKlik tombol Tambah yang ada di kanan atas, tepat di bawah profil Anda. Setelah itu Anda akan melihat beberapa lowongan data lowongan pekerjaan yang harus anda isikan. Pada Step 1: Administrasi, Anda akan melihat inputan sebagai berikut.\nMohon isikan inputan berikut sesuai dengan ketentuan yang kami sediakan:\nNama Pekerjaan - diisi dengan nama pekerjaan yang sedang Anda butuhkan. Tipe Pekerjaan - pilih salah satu yang sesuai dengan tipe pekerjaan yang Anda butuhkan, opsi yang tersedia adalah Harian, Magang, Penuh Waktu, Paruh Waktu, dan Kontrak. Tempat Pekerjaan - pilih salah satu yang sesuai dengan tempat yang akan Anda berikan untuk pelamar, opsi yang tersedia adalah On-site, Remote, serta Hybrid. Gaji - diisi dengan angka gaji eksak, boleh diisi dengan gaji minimum yang akan diterima atau diisi dengan gaji rata-rata karyawan lain yang ada di perusahaan Anda. Deadline - pilih tenggat waktu lowongan kerja ini ditampilkan di aplikasi. Batas waktu yang disediakan oleh aplikasi adalah 1 bulan (terhitung 30 hari) sejak hari lowongan kerja dibuat. Deskripsi - diisi dengan deskripsi lengkap, tanpa job desc, kualifikasi, preferensi korporat, serta fasilitas untuk pelamar. Dibutuhkan Segera (opsional) - centang jika lowongan pekerjaan dibutuhkan dengan segera. Sembunyikan Gaji (opsional) - centang jika gaji dari pekerjaan yang Anda buat memiliki sifat variatif berdasarkan kemampuan pelamar atau butuh negosiasi. Setelah semua inputan pada Step 1 selesai diinputkan, silahkan menuju ke Step 2 untuk pengisian Job Desc, Kualifikasi, Preferensi Korporat, dan Fasilitas untuk Pelamar.\nPada Step 2 ini terdapat beberapa inputan dinamis yang bisa ditambahkan dan dihapus, diantaranya:\nJob Descriptions - diisi dengan job desc yang harus dilakukan oleh pelamar pada saat mereka diterima di perusahaan Anda, pastikan Anda menggunakan 1 inputan untuk 1 job desc. Kualifikasi - diisi dengan kualifikasi pelamar sesuai yang Anda cari. Preferensi Korporat - diisi dengan bagaimana pelamar harus beradaptasi dengan lingkungan perusahaan. Fasilitas untuk Pelamar - diisi dengan fasilitas-fasilitas yang akan diberikan perusahaan saat mereka diterima di perusahaan Anda. Setelah semua dirasa lengkap dan sesuai, silahkan tekan tombol Simpan untuk menerbitkan lowongan kerja Anda. Anda akan dapat melihat data lowongan pekerjaan Anda di halaman ini dan dapat melihat berapa banyak pelamar yang sudah melamar di lowongan kerja tersebut. Pada masing-masing lowongan pekerjaan akan terdapat statistik singkat berupa:\nBanyaknya (n) tampilan Banyaknya (n) lowongan dibagikan Banyaknya (n) lowongan disimpan / ditandai Banyaknya (n) pelamar Menerima / Menolak Pelamar # Pada bagian ini, Anda akan mempelajari bagaimana untuk menerima / menolak pelamar. Mohon ikuti langkah-langkah berikut secara seksama:\nBuka menu Unggah Pekerjaan pada navigasi yang terletak di atas aplikasi. Pilih salah satu lowongan pekerjaan. Dapat Anda lihat di atas bahwa lowongan ini telah dilihat sebanyak 5x, dibagikan sebanyak 2x, disimpan dan dilamar sebanyak 1x. Untuk melihat data pelamar yang ada pada lowongan kerja tersebut, tekan tombol Pelamar.\nAkan muncul sebuah dialog yang berisikan:\nFilter pelamar berdasarkan skill. Badge yang bisa ditekan on/off untuk memfilter secara otomatis. Seluruh data pelamar. Tekan salah satu pelamar. Anda akan melihat detail data pelamar tersebut. Setelah melihat secara sekilas seluruh data pelamar, maka keputusan diterima / ditolaknya ada di tangan Anda. Untuk menerima / menolak pelamar tekan tombol Terima atau Tolak yang ada di kanan bawah. Anda akan melihat sebuah modal persetujuan sebelum data diterima / ditolak dikirimkan ke aplikasi. Setelah menerima / menolak pelamar, maka status pelamar tersebut akan berubah menjadi diterima / ditolak. Sebuah notifikasi berisikan diterima/ditolaknya pelamar juga akan terkirim melalui Email dan in-app notification. Pelamar # Pada bagian ini, Anda akan mempelajari bagaimana untuk melengkapi profil, melamar kerja, serta meningkatkan akun ke Premium.\nMelengkapi Profil # Untuk melengkapi profil, Anda harus menekan tombol akun yang ada di kanan atas, lalu klik sub-menu Profil.\nSetelah itu, Anda akan melihat halaman profil seperti ini.\nDi bagian kiri, terdapat beberapa menu yang digunakan untuk mengatur profil Anda.\nProfil Saya - berisi informasi profil yang harus Anda lengkapi. Sosial Media - berisi informasi sosial media. Keamanan - berisi form untuk mengubah kata sandi login. Pengalaman - berisi form untuk menambah pengalaman kerja Anda. Pendidikan - berisi form untuk menambah data pendidikan Anda. Bidang Keahlian - berisi form untuk menambah data keahlian Anda. Skill - berisi form untuk menambah soft skill atau hard-skill Anda. Portfolio - berisi form untuk menambah portfolio Anda. Sertifikasi - berisi form untuk menambah sertifikasi yang telah Anda tempuh. Buat CV - modul spesial yang memungkinkan Anda untuk membuat CV secara otomatis berdasarkan data-data yang telah Anda inputkan pada sistem. Di bagian kanan berisi inputan-inputan dari masing-masing menu yang ada di bagian kiri. Screenshot di atas menunjukkan bahwa halaman saat ini adalah halaman Profil Saya. Masing-masing bagian akan dijelaskan secara lengkap.\nProfil Saya # Menu ini berisi informasi pribadi. Terdapat beberapa sub-bagian yang butuh dilengkapi.\nFoto Profil Foto Profil diisi dengan logo korporat atau perusahaan Anda. Harap menggunakan logo yang jelas dengan resolusi yang direkomendasikan 512x512 dengan format PNG atau format JPG.\nInformasi Pribadi Informasi Pribadi diisi dengan identitas diri Anda. Terdapat beberapa inputan yaitu:\nNama Awal, diisi dengan nama awal Anda. Nama Akhir, diisi dengan nama akhir Anda, jika nama akhir Anda mengandung lebih dari 1 kata, maka cantumkan seluruh nama Anda kecuali nama awal. Email, diisi dengan email Anda, digunakan untuk login, disarankan untuk tidak mengganti email jika tidak ada pendelegasian akun. Nomor Telepon, diisi dengan nomor telepon yang valid, gunakan angka 62 di awal, dan isikan seluruh nomor tanpa ada spasi atau strip. Jenis Kelamin, pilih salah satu yang sesuai dengan jenis kelamin Anda. Jabatan, diisi dengan posisi yang Anda jabat sekarang. Bio, diisi dengan biodata singkat Anda, tidak perlu diisi secara lengkap. Alamat Informasi Alamat diisikan dengan alamat kantor saat ini. Terdapat pilihan Provinsi, Kota/Kabupaten, Kecamatan, Desa, serta Kode Pos.\nSosial Media # Pada bagian ini terdapat beberapa sosial media yang dapat kami akomodir dan ditampilkan pada saat Anda melamar ke sebuah perusahaan. Sosial Media yang kami akomodir antara lain:\nFacebook YouTube WhatsApp Instagram TikTok Snapchat Pinterest Reddit LinkedIN X (sebelumnya dikenal dengan Twitter) Untuk mengisi sosial media, diharapkan untuk hanya mencantumkan ID dari masing-masing sosial media, karena kami sudah mengakomodir potongan tautan yang digunakan untuk masing-masing sosial media.\nKeamanan # Menu Keamanan ini diperuntukkan hanya untuk mengubah kata sandi. Kami sarankan untuk mengubah kata sandi Anda minimal 1 tahun sekali untuk menghindari akses secara bruteforce.\nPengalaman # Menu Pengalaman ini digunakan untuk menambahkan pengalaman kerja Anda yang terdahulu. Halaman ini akan menunjukkan seluruh pengalaman Anda yang sudah diinputkan ke sistem. Untuk menambah data pengalaman, silahkan ikuti langkah-langkah berikut:\nKlik tombol tambah yang ada di kanan atas panel. Setelah itu Anda akan melihat sebuah modal yang berisi inputan-inputan data. Pada bagian Administrasi Perusahaan, inputan-inputan tersebut adalah:\nLogo Perusahaan - unggah logo perusahaan dengan format PNG atau JPG dengan resolusi yang disarankan 512x512. Nama Perusahaan - diisi dengan nama perusahaan dimana Anda pernah bekerja. Jabatan / Posisi - diisi dengan jabatan yang pernah Anda pegang di perusahaan tersebut. Tipe Pekerjaan - pilih salah satu tipe pekerjaan yang sesuai. Terdapat opsi Harian, Magang, Penuh Waktu, Paruh Waktu, Kontrak. Tanggal Mulai - pilih tanggal Anda mulai bekerja di perusahaan tersebut. Tanggal Selesai (opsional) - pilih tanggal Anda selesai bekerja di perusahaan tersebut, kosongkan jika masih bekerja. Job Desc (wajib isi satu) - diisi dengan job desc Anda pada perusahaan tersebut. Pada bagian Alamat, inputan-inputan tersebut adalah:\nProvinsi - pilih Provinsi tempat di mana Anda bekerja. Kota / Kabupaten - pilih Kota / Kabupaten tempat di mana Anda bekerja. Lokasi - diisi dengan alamat lengkap perusahaan Anda bekerja. Setelah semuanya dirasa lengkap, silahkan tekan tombol simpan untuk menyimpan data pengalaman Anda. Setelah Anda menekan tombol simpan, akan ada sebuah dialog yang menyatakan data pengalaman Anda berhasil disimpan di sistem. Anda juga akan dapat melihat data pengalaman kerja yang baru diinputkan. Pendidikan # Menu Pendidikan ini digunakan untuk menambahkan pendidikan yang telah Anda tempuh. Halaman ini akan menunjukkan seluruh pendidikan Anda yang sudah diinputkan ke sistem. Untuk menambah data pendidikan, silahkan ikuti langkah-langkah berikut:\nKlik tombol tambah yang ada di kanan atas panel. Setelah itu Anda akan melihat sebuah modal yang berisi inputan-inputan data. Pada modal ini, inputan-inputan tersebut adalah:\nTipe Pendidikan - pilih salah satu yang ingin Anda tambahkan, terdapat beberapa opsi yaitu SMA, SMK, Diploma, S1, S2, dan S3. Nama Instansi - diisi dengan nama instansi pendidikan di mana Anda menempuh jenjang pendidikan. Jurusan - diisi dengan jurusan yang Anda tempuh di instansi tersebut. Provinsi - diisi dengan Provinsi instansi pendidikan tersebut berada. Kota / Kabupaten - diisi dengan Kota / Kabupaten instansi pendidikan tersebut berada. Tanggal Mulai - pilih tanggal Anda mulai belajar di instansi pendidikan tersebut. Tanggal Selesai (opsional) - pilih tanggal Anda selesai belajar di instansi pendidikan tersebut, kosongkan jika masih menempuh jenjang tersebut. Nilai Rata-rata UN / IPK (akan muncul setelah memilih Tipe Pendidikan) - diisi dengan nilai rata-rata UN atau IPK Anda. Setelah semuanya dirasa lengkap, silahkan tekan tombol simpan untuk menyimpan data pendidikan Anda. Setelah Anda menekan tombol simpan, akan ada sebuah dialog yang menyatakan data pendidikan Anda berhasil disimpan di sistem. Anda juga akan dapat melihat data pendidikan yang baru diinputkan. Bidang Keahlian # Menu Bidang Keahlian ini digunakan untuk menambahkan bidang keahlian yang telah Anda kuasai. Halaman ini akan menunjukkan seluruh bidang keahlian Anda yang sudah diinputkan ke sistem. Untuk menambah data bidang keahlian, silahkan ikuti langkah-langkah berikut:\nKlik tombol tambah yang ada di kanan atas panel. Setelah itu Anda akan melihat sebuah modal yang berisi inputan-inputan data. Pada modal ini, inputan-inputan tersebut adalah:\nNama Bidang Keahlian - diisi dengan bidang keahlian yang Anda kuasai. Penjelasan Singkat Bidang Keahlian - diisi dengan penjelasan singkat tentang bidang yang dikuasai. Setelah semuanya dirasa lengkap, silahkan tekan tombol simpan untuk menyimpan data bidang keahlian Anda. Setelah Anda menekan tombol simpan, akan ada sebuah dialog yang menyatakan data bidang keahlian Anda berhasil disimpan di sistem. Anda juga akan dapat melihat data bidang keahlian yang baru diinputkan. Skill # Menu Skill ini digunakan untuk menambahkan skill yang telah Anda kuasai. Halaman ini akan menunjukkan seluruh skill Anda yang sudah diinputkan ke sistem. Untuk menambah data skill, silahkan ikuti langkah-langkah berikut:\nKlik tombol tambah yang ada di kanan atas panel. Setelah itu Anda akan melihat sebuah modal yang berisi inputan-inputan data. Pada modal ini, inputan-inputan tersebut adalah:\nNama Skill - diisi dengan skill yang Anda kuasai. Penjelasan Singkat Skill - diisi dengan penjelasan singkat tentang skill yang dikuasai. Setelah semuanya dirasa lengkap, silahkan tekan tombol simpan untuk menyimpan data skill Anda. Setelah Anda menekan tombol simpan, akan ada sebuah dialog yang menyatakan data skill Anda berhasil disimpan di sistem. Anda juga akan dapat melihat data skill yang baru diinputkan. Portfolio # Menu Portfolio ini digunakan untuk menambahkan Portfolio yang telah Anda selesaikan. Halaman ini akan menunjukkan seluruh Portfolio Anda yang sudah diinputkan ke sistem. Untuk menambah data Portfolio, silahkan ikuti langkah-langkah berikut:\nKlik tombol tambah yang ada di kanan atas panel. Setelah itu Anda akan melihat sebuah modal yang berisi inputan-inputan data. Pada modal ini, inputan-inputan tersebut adalah:\nNama Portfolio - diisi dengan Portfolio yang Anda telah selesaikan. Lampiran (opsional) - unggah hasil dari Portfolio Anda terkait projek tersebut. Lampiran yang dapat diunggah adalah file berbentuk PDF dengan maksimal ukuran 2MB. Penjelasan Singkat Portfolio - diisi dengan penjelasan singkat tentang portfolio yang diselesaikan. Tanggal Penyelesaian Portfolio - diisi dengan tanggal penyelesaian portfolio. Setelah semuanya dirasa lengkap, silahkan tekan tombol simpan untuk menyimpan data Portfolio Anda. Setelah Anda menekan tombol simpan, akan ada sebuah dialog yang menyatakan data Portfolio Anda berhasil disimpan di sistem. Anda juga akan dapat melihat data Portfolio yang baru diinputkan. Sertifikasi # Menu Sertifikasi ini digunakan untuk menambahkan Sertifikasi yang telah Anda selesaikan. Halaman ini akan menunjukkan seluruh Sertifikasi Anda yang sudah diinputkan ke sistem. Untuk menambah data Sertifikasi, silahkan ikuti langkah-langkah berikut:\nKlik tombol tambah yang ada di kanan atas panel. Setelah itu Anda akan melihat sebuah modal yang berisi inputan-inputan data. Pada modal ini, inputan-inputan tersebut adalah:\nNama Sertifikasi - diisi dengan Sertifikasi yang Anda telah selesaikan. Bidang Sertifikasi - diisi dengan bidang pada sertifikasi yang Anda telah selesaikan. Penjelasan Singkat Sertifikasi - diisi dengan penjelasan singkat tentang sertifikasi yang diselesaikan. Tanggal Akhir Berlakunya Sertifikasi - diisi dengan tanggal akhir berlakunya sertifikasi. Lampiran (opsional) - unggah hasil dari Sertifikasi Anda terkait sertifikasi tersebut. Lampiran yang dapat diunggah adalah file berbentuk PDF dengan maksimal ukuran 2MB. Setelah semuanya dirasa lengkap, silahkan tekan tombol simpan untuk menyimpan data Sertifikasi Anda. Setelah Anda menekan tombol simpan, akan ada sebuah dialog yang menyatakan data Sertifikasi Anda berhasil disimpan di sistem. Anda juga akan dapat melihat data Sertifikasi yang baru diinputkan. Buat CV # Menu ini digunakan untuk membuat CV secara otomatis melalui sistem. Terdapat 2 jenis CV yaitu CV free template dan premium template yang mengharuskan Anda untuk meng-upgrade akun Anda untuk mendapatkan fitur ini. Untuk membuat CV, ikuti langkah-langkah berikut:\nTekan salah satu template CV yang ingin Anda buat, lalu klik tombol Cetak yang terletak di kanan bawah panel. Setelah itu, Anda akan melihat halaman cetak yang secara otomatis sudah mengikuti template. Anda hanya perlu mengatur beberapa pengaturan tampilan cetak, lalu tekan tombol Simpan. Data CV akan tersimpan dalam bentuk PDF di PC / Laptop Anda. Data CV lengkap memiliki contoh seperti ini: Mendapatkan Informasi Lowongan Baru # Seluruh pelamar yang sudah terdaftar pada sistem akan mendapatkan notifikasi melalui Email dan melalui in-app notification.\nMelalui email:\nMelalui in-app notification:\nMelamar Pekerjaan # Pada bagian ini, Anda akan mempelajari cara untuk melamar pekerjaan pada aplikasi Pugugo. Secara teknis pada saat aplikasi ini dijalankan, seharusnya akan terdapat banyak lowongan kerja yang tersedia pada aplikasi ini. Pelamar akan diberikan banyak opsi lowongan kerja yang sesuai untuk dipilih dan dilamar. Untuk melamar sebuah lowongan pekerjaan, silahkan ikuti langkah-langkah berikut:\nTemukan lowongan kerja yang sesuai dengan bakat dan minat Anda. Seperti contoh adalah lowongan menjadi Full-stack Web Developer yang diterbitkan oleh PT. Akatekno Inovasi Indonesia. Sebelum melamar, Anda dapat melakukan banyak hal seperti:\nMelihat detail dari lowongan kerja tersebut. Menyimpan lowongan kerja tersebut. Membagikan lowongan kerja tersebut. Melihat detail dari perusahaan yang menerbitkan lowongan kerja tersebut. Baca ketentuan dan persyaratan yang dibutuhkan untuk lowongan tersebut dengan cara menekan tombol Lihat Detail. Setelah membaca ketentuan dan persyaratan secara seksama, silahkan tekan tombol Lamar.\nMengisi persyaratan lamaran kerja. Setelah Anda menakan tombol Lamar, terdapat beberapa inputan yang beberapa harus diisi, pada bagian Informasi Pelamar, terdapat inputan sebagai berikut:\nNama Lengkap - diisi dengan nama lengkap Anda. Jika Anda sudah melengkapi inputan ini pada halaman Profil, inputan ini akan terisi secara otomatis. Nomor HP - diisi dengan nomor HP Anda yang dapat dihubungi. Jika Anda sudah melengkapi inputan ini pada halaman Profil, inputan ini akan terisi secara otomatis. Namun perlu diperhatikan bahwa nomor HP harus berformat awal 62 dan diisi tanpa strip atau spasi. Alamat - diisi dengan alamat lengkap Anda sekarang tinggal, inputan ini tidak akan terisi secara otomatis. Kode Pos - diisi dengan Kode Pos tempat Anda sekarang tinggal, inputan ini akan terisi secara otomatis. Pada bagian Resume, terdapat beberapa inputan juga yang dapat diisi untuk meningkatkan Anda lolos ke tahap selanjutnya:\nResume - input unggahan dokumen resume, Anda dapat mengunggah file berupa Dokumen Microsoft Word, atau PDF dengan ukuran maksimal 2MB. Kata Pengantar - input isian yang akan tampil pada saat perusahaan membuka detail pelamar pada lowongan kerja tersebut. Setelah dirasa semuanya sudah lengkap, silahkan tekan tombol Kirim Lamaran yang ada di kanan bawah modal. Aksi ini akan mengirimkan data Lamaran Anda ke perusahaan terkait.\nData Lamaran yang sudah dikirim tidak dapat diubah atau dihapus dari sistem. Pastikan data Lamaran yang Anda kirim sudah sesuai dengan yang diinginkan oleh perusahaan dan mengirim kata pengantar yang baik dan benar. Setelah Anda mengirim lamaran, maka Anda tidak dapat melamar pada lowongan kerja tersebut untuk yang ke-dua kalinya. Akan terdapat Status Lamaran yang menandakan bahwa lamaran Anda pada lowongan tersebut masih Pending, Ditolak atau Diterima.\nAnda juga dapat melihat seluruh lowongan yang sudah Anda lamar pada menu Lamaran Saya yang terdapat pada menu navigasi yang terdapat pada bagian atas aplikasi.\nMenyimpan Lowongan Pekerjaan # Untuk menyimpan sebuah lowongan, pilih salah satu lowongan yang terdaftar, lalu klik tombol Simpan.\nSeluruh lamaran yang sudah Anda simpan dapat dibedakan melalui warna tombol Simpan.\nAnda dapat melihat seluruh lowongan yang sudah disimpan pada menu Lowongan Disimpan yang terdapat pada menu navigasi yang terdapat pada bagian atas aplikasi.\nMembagikan Lowongan Pekerjaan # Untuk menyimpan sebuah lowongan, pilih salah satu lowongan yang terdaftar, lalu klik tombol Bagikan.\nAnda akan melihat sebuah dialog bahwa tautan lowongan telah disalin ke clipboard\nHasil tautan yang disalin seperti ini: https://pugugo.test?vacancyId=9b7f1988-53ae-49ae-947e-ace8b8007ec8.\nPremium Plan # Pada bagian ini, akan menjelaskan bagaimana caranya Korporat / Pelamar dapat membeli Premium Plan dari sistem. Akan terdapat perbedaan Premium Plan dari masing-masing tipe akun, bisa dilihat pada screenshot di bawah:\nPremium Plan Pelamar:\nPremium Plan Korporat:\nUntuk mengajukan sebuah Premium Plan silahkan tekan tombol Mulai pada harga yang sesuai untuk akun Anda. Setelah itu akan muncul sebuah modal berisikan informasi plan tersebut dan Anda diwajibkan untuk mengirimkan bukti transfer kepada nomor yang tertera di modal.\nSetelah mengunggah dan memastikan data benar, klik tombol Kirim Bukti Pembayaran. Bukti pembayaran dapat berupa file gambar PNG atau JPG, serta PDF, dengan ukuran file tidak melebihi 4MB.\nSetelah mengirimkan bukti pembayaran, Anda tidak dapat menggunakan layanan ini lagi terhitung 30 hari setelah Anda mengirimkan bukti pembayaran. Fitur ini dirancang untuk mencegah spam dan mencegah oknum tidak bertanggung jawab untuk merusak sistem.\nAnda juga harus menunggu seorang Admin Pugugo untuk memverifikasikan pembayaran, jika data yang dikirimkan benar dan sudah diverifikasi, maka akun Anda akan berubah menjadi Premium.\nKode Referral # Penting! Kode Referral hanya berlaku untuk sesama pelamar, dan hanya akan muncul ketika pelamar tersebut sudah memiliki Premium Plan. Bagian Kode Referral akan dapat diakses dari halaman profil, dan masing-masing akun memiliki kode referral yang unik.\nAkan terdapat 4 tombol yang dapat ditekan dan masing-masing memiliki aksi yang berbeda.\nSalin Kode Referral Salin Tautan Referral Lihat Histori Referral Tarik Saldo Referral Kita akan membahas masing-masing tombol secara detil di bawah ini:\nSalin Kode Referral # Untuk menyalin kode referral, Anda hanya harus menekan tombol Salin Kode Referral\nHasil dari Kode Referral yang disalin akan masuk ke clipboard dan dapat ditempel, contoh dari hasil aksi tersebut adalah sebagai berikut PGvmVkCS.\nSalin Tautan Referral # Untuk menyalin tautan referral, Anda hanya harus menekan tombol Salin Tautan Referral\nHasil dari Tautan Referral yang disalin akan masuk ke clipboard dan dapat ditempel, contoh dari hasil aksi tersebut adalah sebagai berikut https://pugugo.test/register?referralCode=PGvmVkCS.\nLihat Histori Referral # Untuk melihat histori referral, yaitu poin referral yang didapatkan / diambil, Anda hanya harus menekan tombol Lihat Histori Referral\nAkan tampil sebuah modal berikut:\nContoh data lengkapnya adalah sebagai berikut:\nPenjelasan dari masing-masing status adalah sebagai berikut:\nValid - pengguna menggunakan kode referral tersebut dan sudah mendaftar Premium Plan dan sudah diverifikasi. Invalid - pengguna menggunakan kode referral, namun belum mendaftar Premium Plan atau belum diverifikasi. Tarik Saldo Referral # Untuk menarik saldo referral, Anda hanya harus menekan tombol Tarik Saldo Referral.\nAnda harus mengikuti langkah-langkah yang dijelaskan pada dialog di atas, lalu menunggu Admin untuk memberikan saldonya ke rekening Anda.\n","date":"6 Maret 2024","externalUrl":null,"permalink":"/artikel/2024/03/06/pugugo/","section":"Artikel-artikel","summary":"","title":"Manual Book Pugugo","type":"artikel"},{"content":"","date":"6 Maret 2024","externalUrl":null,"permalink":"/tags/pugugo/","section":"Tags","summary":"","title":"Pugugo","type":"tags"},{"content":"","date":"6 Maret 2024","externalUrl":null,"permalink":"/tags/tutorial/","section":"Tags","summary":"","title":"Tutorial","type":"tags"},{"content":"","date":"13 Oktober 2023","externalUrl":null,"permalink":"/tags/android/","section":"Tags","summary":"","title":"Android","type":"tags"},{"content":"","date":"13 Oktober 2023","externalUrl":null,"permalink":"/tags/android-studio/","section":"Tags","summary":"","title":"Android Studio","type":"tags"},{"content":"","date":"13 Oktober 2023","externalUrl":null,"permalink":"/tags/belajar/","section":"Tags","summary":"","title":"Belajar","type":"tags"},{"content":"Karena keterbatasan spesifikasi PC yang kita miliki, sering kita tidak dapat menjalankan emulator dengan lancar. Disinial dimana USB Debugging sangat membantu!\nCatatan: Cara untuk mengaktifkan USB debugging berbeda-beda pada setiap merk, pastikan teman-teman mencari tutorial yang tepat dengan menggunakan bantuan Mbah Google!\nMengaktifkan USB Debugging # Kasus yang akan Saya contohkan kali ini adalah menggunakan HP Poco X3 NFC, jadi jika teman-teman memiliki HP dengan brand sejenis, mungkin bisa follow-along.\nBuka pengaturan, lalu buka About Phone Setelah itu, tekan MIUI version (For POCO) berkali-kali sampai dengan teman-teman lihat notifikasi \u0026ldquo;Now you are a developer\u0026rdquo;. Karena Saya sudah mengaktifkan opsi ini, notifikasinya berubah menjadi \u0026ldquo;No need, you are already a developer.\u0026rdquo; Kembali ke menu pengaturan awal, scroll ke paling bawah, lalu buka Additional settings Scroll ke bawah lagi, lalu buka Developer options Scroll sedikit ke bawah, lalu perbolehkan USB Debugging Menjalankan aplikasi ke HP melalui USB Debugging # Colokkan kabel USB ke PC dan HP teman-teman, dan teman-teman akan melihat dialog seperti ini. Tekan OK. Di Android Studio, pastikan teman-teman menggunakan device yang sesuai, lalu klik tombol play. Tunggu Gradle Build, lalu teman-teman akan melihat dialog seperti ini pada HP teman-teman. Tekan Install. Tada! Aplikasi terinstal! There you have it! See you on the next article!\n","date":"13 Oktober 2023","externalUrl":null,"permalink":"/artikel/2023/10/13/usb-debugging/","section":"Artikel-artikel","summary":"","title":"Catatan Belajar Juara Android 2023 - #7 USB Debugging","type":"artikel"},{"content":"","date":"13 Oktober 2023","externalUrl":null,"permalink":"/tags/debugging/","section":"Tags","summary":"","title":"Debugging","type":"tags"},{"content":"","date":"13 Oktober 2023","externalUrl":null,"permalink":"/tags/google/","section":"Tags","summary":"","title":"Google","type":"tags"},{"content":"","date":"13 Oktober 2023","externalUrl":null,"permalink":"/tags/juara-android/","section":"Tags","summary":"","title":"Juara Android","type":"tags"},{"content":"","date":"13 Oktober 2023","externalUrl":null,"permalink":"/series/juara-android-2023/","section":"Series","summary":"","title":"Juara Android 2023","type":"series"},{"content":"","date":"13 Oktober 2023","externalUrl":null,"permalink":"/tags/kotlin/","section":"Tags","summary":"","title":"Kotlin","type":"tags"},{"content":"","date":"13 Oktober 2023","externalUrl":null,"permalink":"/tags/usb/","section":"Tags","summary":"","title":"Usb","type":"tags"},{"content":"Hai! Kembali lagi di artikel #JuaraAndroid2023. Kali ini kita akan membahas tentang Emulator. Emulator ini bekerja sebagai perangkat disimulasikan pada mesin kita, jadi walaupun kita tidak memiliki HP Android, kita bisa menjalankan aplikasi yang kita bangun di PC kita langsung.\nPerlu diperhatikan bahwa Emulator akan memakan resource yang cukup besar, jadi wajar jika teman-teman merasakan sebuah \u0026ldquo;Lag\u0026rdquo; atau bahkan \u0026ldquo;Stutter\u0026rdquo;, jadi harap bersabar ketika menjalankan emulator karena performanya tidak akan sama dengan memiliki HP Android secara langsung.\nMenambahkan Emulator # Pada aplikasi Android Studio, klik dropdown yang ada di sebelah kiri tombol play, lalu klik Device Manager Device Manager akan muncul panel di sebelah kanan. Panel ini akan menunjukkan beberapa Virtual Device yang sudah dibuat, karena saya belum membuat satupun, jadi kosong.\nUntuk menambah Virtual Device baru, klik Create virtual device. Akan muncul sebuah dialog yang berisikan daftar-daftar device yang tersedia. Pilih device yang teman-teman inginkan, kali ini Saya ingin menggunakan Pixel 7 Pro. Setelah itu klik Next. Pilih juga API level yang diinginkan, kali ini Saya menggunakan API 34 (Android 13). Karena Saya belum memiliki imagenya, Saya harus mengunduh image dari API tersebut dengan cara mengklik tombol unduh yang berada di sebelah Release Name dari masing-masing API. Setelah menekan unduh, akan muncul popup yang berisi progress pengunduhan\nSetelah selesai mengunduh, klik Finish dan teman-teman akan diarahkan kembali ke dialog pertama. Klik API yang tadi diunduh, lalu klik Next\nAtur nama AVD sesuai yang diinginkan, lalu klik Finish Selamat, Anda telah membuat Android Virtual Device. Untuk menjalankan AVD, simpelnya, klik tombol play pada AVD di panel Device Manager.\nRun pertama biasanya akan memakan waktu yang cukup lama, jadi bisalah saambil ngopi-ngopi dulu :coffee:\nNanti, akan muncul sebuah tampilan Android yang berada di panel kanan, persis di bawah Device Manager. Itulah AVD teman-teman.\nMenjalankan aplikasi pada AVD # Untuk menjalankan aplikasi, klik tombol play yang ada di kanan atas. Pastikan AVD yang digunakan sudah sesuai dengan AVD mana yang ingin teman-teman pakai.\nTunggu beberapa saat untuk Gradle Build dan voila!, aplikasi dijalankan!\n","date":"13 Oktober 2023","externalUrl":null,"permalink":"/artikel/2023/10/13/emulator/","section":"Artikel-artikel","summary":"","title":"Catatan Belajar Juara Android 2023 - #6 Emulator","type":"artikel"},{"content":"","date":"13 Oktober 2023","externalUrl":null,"permalink":"/tags/emulator/","section":"Tags","summary":"","title":"Emulator","type":"tags"},{"content":"Akhirnya, yang ditunggu-tunggu, Android Studio! Setelah beberapa hari belajar dasar Kotlin, saatnya instalasi Android Studio. Tapi keknya nggak bakal langsung terjun bikin aplikasinya deh. :laughing:\nJadi, Android Studio ini IDE resmi untuk pembangunan aplikasi Android, yang dibangun dan didistribusikan oleh Google. IDE ini adalah alat yang digunakan buat mendesain, membangun, menjalankan, dan menguji coba sebuah perangkat lunak yang dibangun.\nAndroid Studio menggunakan Intellij IDEA sebagai fondasinya dan plugin Android sudah termasuk didalamnya. Saya gak akan jelasin secara detail untuk instalasinya karena saya udah punya, jadi untuk yang mau download, silahkan download di sini.\nAplikasi Android Pertamaku # Untuk permulaan, kita akan membuat aplikasi Android menggunakan template Empty Activity.\nBuka aplikasi Android Studio, karena Saya pakai Mac, jadi cari pakai Spotlight saja Pada dialog Welcome to Android Studio, klik New Project. Karena ada perbedaan versi, tampilan bisa jadi berbeda-beda pada setiap versinya, namun secara esensi, fungsi yang sama akan tetap ada di interfacenya. Setelah New Project diklik, akan muncul dialog dengan daftar template yang sudah disediakan oleh Android Studio\nProject Template ini adalah blueprint yang disediakan untuk tipe aplikasi tertentu. Template membuat struktur project yang dibutuhkan oleh Android Studio untuk membangun projek teman-teman, template yang dipilih akan memberikan kode yang dibutuhkan sebagai awalan, sehingga pengembangan awal bisa dilakukan lebih cepat.\nPastikan pilih tab Phone and Tablet\nKlik template Empty Activity. Template ini akan membuat projek simpel yang bisa digunakan untuk membangun aplikasi Compose. Hanya memiliki satu halaman dan menampilkan teks \u0026quot;Hello Android!\u0026quot;.\nKlik Next, lalu dialog New Project akan terbuka. Akan ada beberapa inputan yang perlu diatur\nAtur projeknya seperti ini:\nInputan Name digunakan untuk nama dari project teman-teman, untuk ini gunakan \u0026ldquo;Greeting Card\u0026rdquo; saja.\nBiarkan Package Name seperti yang sudah ada, karena akan secara otomatis mengikuti nama project.\nBiarkan Save Location seperti yang sudah ada.\nPilih API 24: Android 7.0 (Nougat) dari pilihan minimum SDK.\nSetelah itu, klik Finish, disini yang bakal lama karena setup project, mengunduh / mengupgrade beberapa library, Gradle Sync, dll. Tunggu saja, proses ini berlangsung dari beberapa detik ke beberapa menit tergantung spek dan internet masing-masing.\nNah, disini ada yang sedikit berbeda dari pengalaman saya terakhir membuka aplikasi ini 2 tahun yang lalu. Pertama, karena udah gak familiar ya, jadi asing banget tampilannya; Kedua, UInya overwhelming, banyak yang harus dipelajari.\nDisini kan ada 3 tampilan, kiri, tengah, dan kanan, nah menurut Google ini nama-nama tampilannya:\nProject View (kiri) yang menampilkan folder dan file Code View (tengah) dimana teman-teman mengedit kode Design View (kanan) preview tentang bagaimana aplikasi akan terlihat Project File # Di bagian kiri pada Android Studio, terdapat Project Tab. Project Tab menunjukkan file dan folder dari suatu project. Nanti akan ada folder yang berbentuk seperti com.example.greeting tergantung nama project yang teman-teman berikan di awal-awal.\nSecara default, dropdown Android yang ada di bagian atas adalah tampilan default yang didesain untuk mempermudah navigasi teman-teman pada saat pengembangan. Namun tentunya hal ini akan berbeda jika dibuka oleh file explorer seperti Windows Explorer, Dolphin, atau Finder. Untuk mengubah hierarki folder seperti layaknya file explorer, ubah dropdown Android ke Project Source File.\nUntuk mengembalikan lagi ke hierarki semula, ubah ke Android.\nMengubah Teks # Konteks mengubah teks kali ini bukanlah mengubah teks pada Kotlin, tapi bagaimana caranya memanipulasi teks pada aplikasi Android yang akan kita bangun.\nPertama-tama mari kita lihat kode yang ada pada file MainActivity.kt. Ada beberapa fungsi yang secara dibuat secara otomatis pada saat pembuatan projek kosong tadi. Kita ambil potongan kode dari kelas MainActivity:\nclass MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { GreetingTheme { // A surface container using the \u0026#39;background\u0026#39; color from the theme Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { Greeting(\u0026#34;Android\u0026#34;) } } } } } Fungsi onCreate() adalah sebuah entry point dari aplikasi Android ini, yang akan memanggil fungsi-fungsi lainnya untuk membangun sebuah user interface yang solid. Perbedaannya dengan Kotlin, Kotlin menggunakan fungsi main() sebagai entry pointnya.\nFungsi setContent() yang ada di dalam fungsi onCreate() digunakan untuk mendefinisikan tata letak fungsi composable. Semua fungsi yang ditandai dengan anotasi @Composable dapat dipanggil dari fungsi setContent() atau dari fungsi Composable lainnya. Anotasi tersebut akan menginstruksikan kompiler Kotlin bahwa fungsi ini digunakan oleh Jetpack Compose untuk membangun User Interface.\nSelanjutnya, kita lihat fungsi Greeting() yang merupakan fungsi Composable, perhatikan adanya anotasi @Composable di atas fungsinya. Fungsi Composable ini mengambil beberapa input dan membuat tampilannya pada layar.\n@Composable fun Greeting(name: String, modifier: Modifier = Modifier) { Text(text = \u0026#34;Hello $name!\u0026#34;) } Sama saja seperti materi Fungsi Pada Kotlin, namun ada beberapa perbedaan:\nAda anotasi @Composable sebelum penulisan fungsi; Nama fungsi @Composable menggunakan PascalCase; Fungsi @Composable tidak me-return apapun; Saat ini, fungsi Greeting() membutuhkan name dan mengucapkan Hello ke orang tersebut. Kita akan ubah fungsi ini supaya menampilkan pesan yang lebih friendly dan akrab.\nUbah pesan dari fungsi Greeting() untuk memperkenalkan diri Anda sendiri, alih-alih mengatakan \u0026ldquo;Hello\u0026rdquo;: @Composable fun Greeting(name: String, modifier: Modifier = Modifier) { Text(text = \u0026#34;Hai, nama saya adalah $name!\u0026#34;) } Preview Android akan secara otomatis memperbarui previewnya: Nah, lebih mantap sekarang. Tapi, bisa dilihat lagi preview yang digunakan ini bukan nama teman-teman. Jadi, saatnya kita mengubah nama \u0026ldquo;Android\u0026rdquo; menjadi namamu. Bukan nama sendiri juga gak masalah sih, yang penting nama hehe.\nFungsi GreetingPreview() yang ada di paling bawah script adalah sebuah fitur yang keren (pake banget) dimana teman-teman bisa melihat bagaimana composable teman-teman terlihat, tanpa harus membangun keseluruhan aplikasi. Untuk membuat sebuah preview, anotasikan fungsi tersebut menggunakan anotasi @Composable dan @Preview. Anotasi @Preview akan menginstruksikan Android Studio bahwa composable tersebut harus ditampilkan pada Design View.\n@Preview(showBackground = true) @Composable fun GreetingPreview() { GreetingTheme { Greeting(\u0026#34;Android\u0026#34;) } } Kita perbarui fungsi GreetingPreview() menjadi nama selain Android. Let\u0026rsquo;s name it Shiroyuki. @Preview(showBackground = true) @Composable fun GreetingPreview() { GreetingCardTheme { Greeting(\u0026#34;Shiroyuki\u0026#34;) } } Jrenggggg, Greeting Preview akan secara otomatis diupdate!\nMengubah Background Color # Untuk mengatur warna latar (background color) yang berbeda, kita perlu mengelilingi Text menggunakan Surface. Surafe adalah sebuah kontainer yang merepresentasikan bagian dari sebuah UI, dimana kita dapat mengubah tampilannya, seperti warna latar atau pinggiran.\nUntuk mengelilingi Text menggunakan sebuah Surface, klik baris yang ingin diubah, lalu tekan (Alt+Enter untuk Windows atau Option+Enter untuk Mac), lalu pilih Surround with widget. Pilih Surround with Container Secara default, Android Studio akan menggunakan Box, tapi kalian bisa mengubah ini ke tipe kontainer yang lain.\n@Composable fun Greeting(name: String, modifier: Modifier = Modifier) { Box { Text( text = \u0026#34;Hai, nama saya adalah $name!\u0026#34;,, modifier = modifier ) } } Hapus Box dan ketikkan Surface() @Composable fun Greeting(name: String, modifier: Modifier = Modifier) { Surface() { Text( text = \u0026#34;Hai, nama saya adalah $name!\u0026#34;,, modifier = modifier ) } } Pada kontainer Surface, tambahkan parameter color, atur valuenya ke Color. @Composable fun Greeting(name: String, modifier: Modifier = Modifier) { Surface(color = Color) { Text( text = \u0026#34;Hai, nama saya adalah $name!\u0026#34;,, modifier = modifier ) } } Pada saat teman-teman mengetik Color, teman-teman akan melihat bahwa Color ini memiliki underline merah, yang artinya Android Studio tidak dapat memecahkan masalah ini. Untuk memecahkan masah ini, tekan (Alt+Enter untuk Windows atau Option+Enter untuk Mac), lalu pilih Import. Akan muncul beberapa opsi, gunakan androidx.compose.ui.graphics\nPerhatikan underline merah yang masih ada pada Color. Untuk memperbaiki ini, ketikkan titik (.) setelah Color, lalu pilih salah satu warna yang teman-teman inginkan. Saat ini, Saya ingin menggunakan warna merah (Red).\nKarena warnanya terlalu mencolok, Saya ubah warna tersebut menjadi warna tema Shiroyuki.dev, yaitu #ef4444: @Composable fun Greeting(name: String, modifier: Modifier = Modifier) { Surface(color = Color(0xffef4444)) { Text( text = \u0026#34;Hai, nama saya adalah $name!\u0026#34;, modifier = modifier ) } } Format dari Color yang digunakan adalah ARGB (Alpha, Red, Green, Blue). Hasilnya adalah seperti berikut:\nYey! Sekarang keliatan lebih natural! \\( ^^) /\nMenambahkan Padding # Sekarang, teks yang kita buat sudah memiliki warna latar, sekarang kita akan menambahkan padding di sekitar teks. Kita akan menggunakan Modifier kali ini.\nTambahkan padding ke modifier yang ada pada Text dengan ukuran 24.dp:\n@Composable fun Greeting(name: String, modifier: Modifier = Modifier) { Surface(color = Color(0xffef4444)) { Text( text = \u0026#34;Hai, nama saya adalah $name!\u0026#34;, modifier = modifier.padding(24.dp) ) } } Karena dp ini adalah unit yang tidak dikenal, maka teman-teman harus mengimportnya. Android Studio akan secara otomatis merekomendasikan unit.dp\nHasilnya adalah sebagai berikut:\nYey! Akhirnya kita berhasil menambahkan padding. Kelihatannya lebih luas ya? Well, there you have it!\nKode Lengkap # Klik untuk melihat kode lengkap package com.example.greeting import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.example.greeting.ui.theme.GreetingTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { GreetingTheme { // A surface container using the \u0026#39;background\u0026#39; color from the theme Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { Greeting(\u0026#34;Android\u0026#34;) } } } } } @Composable fun Greeting(name: String, modifier: Modifier = Modifier) { Surface(color = Color(0xffef4444)) { Text( text = \u0026#34;Hai, nama saya adalah $name!\u0026#34;, modifier = modifier.padding(24.dp) ) } } @Preview(showBackground = true) @Composable fun GreetingPreview() { GreetingTheme { Greeting(\u0026#34;Shiroyuki\u0026#34;) } } Ringkasan # Untuk membuat projek baru: buka Android Studio, klik New Project \u0026gt; Empty Activity \u0026gt; Next, masukkan nama yang digunakan untuk projekmu, lalu konfigurasikan pengaturan yang ingin digunakan; Untuk melihat bagaimana tampilan dari aplikasimu, gunakan panel Preview; Fungsi Composable hampir sama dengan fungsi biasa, dengan beberapa perbedaan: nama fungsi menggunakan PascalCase, lalu tambahkan anotasi @Composable sebelum pendeklarasian fungsi, dan @Composable tidak me-return apapun; Sebuah Modifier digunakan untuk mendekorasi Composable; ","date":"7 Oktober 2023","externalUrl":null,"permalink":"/artikel/2023/10/07/android-studio/","section":"Artikel-artikel","summary":"","title":"Catatan Belajar Juara Android 2023 - #5 Android Studio","type":"artikel"},{"content":"Hai hai, ketemu lagi di artikel #JuaraAndroid2023. Disini Saya belajar tentang fungsi. Sebelumnya kita melihat program sederhana yang mencetak Hai, Ibu!, nah secara tidak langsung, program tersebut memiliki dua fungsi, yaitu:\nmain() yang dibutuhkan di setiap program Kotlin, sebagai permulaan dari program. println() yang berfungsi untuk mencetak teks. Fungsi sejatinya digunakan untuk memecah blok kode menjadi beberapa baris kode yang bisa digunakan sewaktu-waktu. Teman-teman mungkin menyadari bahwa fungsi main() tidak dipanggil secara eksplisit oleh pengembang, namun fungsinya tetap terpanggil pada saat program dijalankan, mengapa? Hal itu dikarenakan kompiler Kotlin menggunakan main() sebagai permulaan program.\nMendefinisikan dan memanggil fungsi # Yap, fungsi, seperti yang kita tahu bisa didefinisikan dan dipanggil setiap waktu. Anggap saja kita ingin membuat sebuah fungsi yang digunakan untuk mencetak ucapan selamat ulang tahun.\nfun main() { println(\u0026#34;Selamat ulang tahun, Wafda! 🎉\u0026#34;) println(\u0026#34;Sekarang umurmu 18 tahun!\u0026#34;) } Program sederhana diatas akan membuat output seperti ini:\nSelamat ulang tahun, Wafda! 🎉 Sekarang umurmu 18 tahun! Sekarang, bagaimana caranya untuk kita agar bisa membuat fungsi tersebut bisa dipakai sewaktu-waktu? Tentunya dengan cara membuat fungsi baru lagi, anggap saja fungsi baru tersebut akan kita namakan ucapanUltah(), berikut contoh kodenya:\nfun main() { ucapanUltah() } fun ucapanUltah() { println(\u0026#34;Selamat ulang tahun, Wafda! 🎉\u0026#34;) println(\u0026#34;Sekarang umurmu 18 tahun!\u0026#34;) } Cukup simpel bukan? Lalu, tetapi\u0026hellip;bagaimana jika kita ingin mengubah nama orang yang ulang tahun beserta umurnya? Kita tidak mungkin membuat setiap fungsi seperti ucapanUltahWafdaKe18() serta ucapanUltahWafdaKe19() dan seterusnya kan? Disitulah argumen / parameter fungsi dibutuhkan. Akan kita bahas di bagian selanjutnya.\nMengembalikan sebuah value dari sebuah fungsi # Jika teman-teman baru dalam pemrograman, bahasa yang strictly-typed mungkin akan menjadi hal yang sulit dicerna, namun, karena Kotlin adalah bahasa yang cukup dinamis tetapi tetap strict, bisa Saya katakan bahwa Kotlin adalah bahasa yang sempurna untuk dipelajari sebagai pemula.\nReturn value mengacu kepada nilai yang dikembalikan oleh sebuah fungsi atau ekspresi. Anggaplah kita ingin membuat sebuah fungsi yang berfungsi untuk menambahkan angka a dan angka b. Apa yang kita ekspektasikan? Tentu saja jumlah dari a + b, dengan tipe data angka.\nfun tambah(a: Int, b: Int): Int { return a + b; } Jika fungsi yang teman-teman buat adalah fungsi singkat, tanpa ada pengoperasian yang kompleks, teman-teman bisa membuat inline function atau one-line function, yaitu fungsi yang dibuat hanya dengan menggunakan 1 baris kode:\nfun tambah(a: Int, b: Int): Int = a + b Jadi, secara anatomi fungsi yang ada pada Kotlin, sejauh ini kita bisa simpulkan seperti berikut:\nReturn type # Secara default, jika teman-teman tidak menspesifikasikan return type, maka Kotlin akan secara otomatis menggunakan return type Unit. Unit pada Kotlin kurang lebih sama seperti:\nvoid pada Java; Void / empty tuple () pada Swift; None pada Python; self / static pada PHP; dll. Yuk kita belajar cara menggunakan return type dengan memodifikasi fungsi ucapanUltah() yang sudah kita buat di atas.\nPada fungsi ucapanUltah(), tambahkan titik dua : setelah kurung tutup, dan spesifikasikan return type-nya sebagai Unit fun main() { ucapanUltah() } fun ucapanUltah(): Unit { println(\u0026#34;Selamat ulang tahun, Wafda! 🎉\u0026#34;) println(\u0026#34;Sekarang umurmu 18 tahun!\u0026#34;) } Kita jalankan programnya, dan semuanya berjalan normal seperti biasa Selamat ulang tahun, Wafda! 🎉 Sekarang umurmu 18 tahun! Perhatikan bahwa dengan return type Unit, ataupun tanpa return type sama sekali, program masih bisa berjalan dengan normal.\nMengembalikan String dari fungsi ucapanUltah() # Untuk mendemokan bagaimana sebuah fungsi dapat mengembalikan sebuah nilai, kita akan modifikasi ucapanUltah() untuk mengembalikan String, alih-alih kita mencetak outputnya ke konsol.\nUbah return type Unit ke String fun main() { ucapanUltah() } fun ucapanUltah(): String { println(\u0026#34;Selamat ulang tahun, Wafda! 🎉\u0026#34;) println(\u0026#34;Sekarang umurmu 18 tahun!\u0026#34;) } Ketika dijalankan, kita akan mendapatkan error, karena fungsi membutuhkan return type yang valid. Karena tidak ada nilai yang dikembalikan / di-return, maka compiler bingung dan mencetak Exception: A \u0026#39;return\u0026#39; expression required in a function with a block body (\u0026#39;{...}\u0026#39;) Tapi don\u0026rsquo;t worry, kita tetap bisa mengubah kode, ingat moto kita bahwa \u0026ldquo;Jika kode tersebut salah, berarti kita menuju jalan yang benar\u0026rdquo;.\nKarena kita hanya bisa menggunakan 1 return pada setiap fungsi, maka kita harus mengubah fungsi println() menjadi masing-masing variabel yang merepresentasikan ucapan fun ucapanUltah(): String { val ucapanNama = \u0026#34;Selamat ulang tahun, Wafda! 🎉\u0026#34; val ucapanUsia = \u0026#34;Sekarang umurmu 18 tahun!\u0026#34; } Menggunakan sintaks string template yang ada di seri ke-3, kita bisa menggabungkan 2 pesan di atas menjadi 1 return. Kita juga akan menggunakan karakter \\n yang digunakan untuk memisah baris.\nfun ucapanUltah(): String { val ucapanNama = \u0026#34;Selamat ulang tahun, Wafda! 🎉\u0026#34; val ucapanUsia = \u0026#34;Sekarang umurmu 18 tahun!\u0026#34; return \u0026#34;$ucapanNama\\n$ucapanUsia\u0026#34; } Pada fungsi main(), karena kita telah mengubah fungsi ucapanUltah() untuk mengembalikan sebuah nilai, teman-teman bisa simpan hasil kembaliannya pada sebuah variabel string. Sehingga kode lengkapnya terlihat seperti ini: fun main() { val ucapan: String = ucapanUltah() println(ucapan) } fun ucapanUltah(): String { val ucapanNama = \u0026#34;Selamat ulang tahun, Wafda! 🎉\u0026#34; val ucapanUsia = \u0026#34;Sekarang umurmu 18 tahun!\u0026#34; return \u0026#34;$ucapanNama\\n$ucapanUsia\u0026#34; } Hasil dari program di atas adalah sebagai berikut:\nSelamat ulang tahun, Wafda! 🎉 Sekarang umurmu 18 tahun! Karena ucapanUltah() adalah hasil dari return langsung, kita bisa menggunakan secara langsung fungsi ucapanUltah() ke println() sehingga kita tidak perlu mengalokasikan sedikit memori ke variabel ucapan.\nfun main() { println(ucapanUltah()) } fun ucapanUltah(): String { val ucapanNama = \u0026#34;Selamat ulang tahun, Wafda! 🎉\u0026#34; val ucapanUsia = \u0026#34;Sekarang umurmu 18 tahun!\u0026#34; return \u0026#34;$ucapanNama\\n$ucapanUsia\u0026#34; } Parameter / Argumen # Secara tidak langsung, jika teman-teman perhatikan, fungsi println() memiliki 1 argumen yang menerima String untuk dicetak pada terminal / konsol. Jadi, sebenarnya sejauh ini teman-teman sudah mengerti cara menggunakan parameter.\nSecara umum, parameter adalah nilai yang diberikan ke fungsi, atau konstruktor saat mereka dipanggil. Sehingga parameter yang digunakan dapat diproses pada blok kode sebuah fungsi. Anatominya adalah sebagai berikut:\nTentu saja, tidak ada batas dalam penggunaan parameter yang ingin teman-teman gunakan, jadi, bebaslah berkreasi!\nUntuk kasus ini, yuk kita ubah fungsi ucapanUltah() sehingga bisa menerima Nama dan Usia:\nfun ucapanUltah(nama: String, usia: Int): String { val ucapanNama = \u0026#34;Selamat ulang tahun, $nama! 🎉\u0026#34; val ucapanUsia = \u0026#34;Sekarang umurmu $usia tahun!\u0026#34; return \u0026#34;$ucapanNama\\n$ucapanUsia\u0026#34; } Apa keuntungan yang kita dapat dari perubahan fungsi tersebut? Kita bisa secara dinamis mengubah Nama dan Usia sesuai dengan data yang kita inginkan. Seperti contoh:\nfun main() { println(ucapanUltah(\u0026#34;Wafda\u0026#34;, 18)) } fun ucapanUltah(nama: String, usia: Int): String { val ucapanNama = \u0026#34;Selamat ulang tahun, $nama! 🎉\u0026#34; val ucapanUsia = \u0026#34;Sekarang umurmu $usia tahun!\u0026#34; return \u0026#34;$ucapanNama\\n$ucapanUsia\u0026#34; } Program di atas akan mencetak teks seperti berikut:\nSelamat ulang tahun, Wafda! 🎉 Sekarang umurmu 18 tahun! Karena fungsi bersifat bisa dipanggil berkali-kali, sesuai dengan apa yang kita inginkan, maka kita bisa menggunakan fungsi tersebut berulang kali, sesuka hati, terserah kita:\nfun main() { println(ucapanUltah(\u0026#34;Wafda\u0026#34;, 18)) println(ucapanUltah(\u0026#34;Wangi\u0026#34;, 14)) } fun ucapanUltah(nama: String, usia: Int): String { val ucapanNama = \u0026#34;Selamat ulang tahun, $nama! 🎉\u0026#34; val ucapanUsia = \u0026#34;Sekarang umurmu $usia tahun!\u0026#34; return \u0026#34;$ucapanNama\\n$ucapanUsia\u0026#34; } Hasil dari program di atas adalah sebagai berikut:\nSelamat ulang tahun, Wafda! 🎉 Sekarang umurmu 18 tahun! Selamat ulang tahun, Wangi! 🎉 Sekarang umurmu 14 tahun! Pretty fun ya? Definitely fun as hell.\nNamed Arguments # Fungsi ucapanUltah() yang telah kita buat sejauh ini menerima 2 parameter, yaitu nama dan usia. Namun pada saat pemanggilan ucapanUltah(), secara sekilas kita tidak bisa mengetahui parameter apa yang kita isikan.\nIDE modern seperti VSCode atau Android Studio akan secara otomatis memberikan informasi argumen apa saja yang teman-teman butuhkan, namun pada IDE yang lebih simpel seperti Notepad++ atau Sublime Text yang tidak memiliki fitur tersebut, teman-teman sesama programmer akan mendapat suatu kebingungan. \u0026ldquo;Lho tetiba ada fungsi ucapanUltah() tapi gak jelas parameter apa yang dipakai\u0026rdquo;.\nJadi, untuk menambah keterbacaan kode, kita menggunakan Named Arguments. Pengimplementasiannya pada fungsi main() akan terlihat sebagai berikut:\nfun main() { println(ucapanUltah(nama = \u0026#34;Wafda\u0026#34;, usia = 18)) println(ucapanUltah(nama = \u0026#34;Wangi\u0026#34;, usia = 14)) } Lalu apa keuntungan dari Named Arguments ini? Pertama, keterbacaan kode menjadi lebih baik; Kedua, walaupun posisi argumen dibolak-balik, fungsi akan tetap berjalan karena kompiler mengetahui argumen apa yang digunakan:\nfun main() { println(ucapanUltah(nama = \u0026#34;Wafda\u0026#34;, usia = 18)) println(ucapanUltah(usia = 14, nama = \u0026#34;Wangi\u0026#34;)) } Kode di atas akan tetap berjalan normal seperti biasa dengan output sebagai berikut\nSelamat ulang tahun, Wafda! 🎉 Sekarang umurmu 18 tahun! Selamat ulang tahun, Wangi! 🎉 Sekarang umurmu 14 tahun! Default Arguments # Kita mengetahui bahwa ucapanUltah() sekarang memiliki 2 argumen, namun apa yang terjadi jika fungsi ucapanUltah() dipanggil langsung tanpa mengirimkan nilai-nilai argumennya? Anggap kita memiliki sebuah program seperti ini:\nfun main() { println(ucapanUltah()) } fun ucapanUltah(nama: String, usia: Int): String { val ucapanNama = \u0026#34;Selamat ulang tahun, $nama! 🎉\u0026#34; val ucapanUsia = \u0026#34;Sekarang umurmu $usia tahun!\u0026#34; return \u0026#34;$ucapanNama\\n$ucapanUsia\u0026#34; } Kotlin akan memberikan Exception / error seperti ini:\nNo value passed for parameter \u0026#39;nama\u0026#39; No value passed for parameter \u0026#39;usia\u0026#39; Disinilah Default Arguments dibutuhkan, jadi, walaupun tanpa nilai argumen yang diberikan, program akan tetap bisa berjalan menggunakan nilai default yang sudah dispesifikasikan. Mari ubah fungsi ucapanUltah() kita:\nfun ucapanUltah(nama: String = \u0026#34;Aghits\u0026#34;, usia: Int = 22): String { val ucapanNama = \u0026#34;Selamat ulang tahun, $nama! 🎉\u0026#34; val ucapanUsia = \u0026#34;Sekarang umurmu $usia tahun!\u0026#34; return \u0026#34;$ucapanNama\\n$ucapanUsia\u0026#34; } Kode lengkapnya akan terlihat seperti ini, dengan tambahan beberapa contoh ucapan yang sudah kita buat di atas:\nfun main() { println(ucapanUltah()) println(ucapanUltah(nama = \u0026#34;Wafda\u0026#34;, usia = 18)) println(ucapanUltah(usia = 14, nama = \u0026#34;Wangi\u0026#34;)) } fun ucapanUltah(nama: String = \u0026#34;Aghits\u0026#34;, usia: Int = 22): String { val ucapanNama = \u0026#34;Selamat ulang tahun, $nama! 🎉\u0026#34; val ucapanUsia = \u0026#34;Sekarang umurmu $usia tahun!\u0026#34; return \u0026#34;$ucapanNama\\n$ucapanUsia\u0026#34; } Sekarang, programnya akan bisa berjalan tanpa ada error. Yay!\nSelamat ulang tahun, Aghits! 🎉 Sekarang umurmu 22 tahun! Selamat ulang tahun, Wafda! 🎉 Sekarang umurmu 18 tahun! Selamat ulang tahun, Wangi! 🎉 Sekarang umurmu 14 tahun! Ringkasan # Fungsi didefinisikan menggunakan keyword fun; Fungsi membantu program yang besar sehingga lebih mudah dimaintain, dan mencegah repetisi kode; Fungsi dapat mengembalikan sebuah nilai yang bisa disimpan dalam variabel; Fungsi dapat mengambil parameter sehingga dapat digunakan dalam body dari sebuah fungsi; Argumen adalah nilai-nilai yang dikirimkan pada saat pengambilan fungsi; Teman-teman bisa menggunakan nama argumen pada saat pemanggilan fungsi. Ketika menggunakan named arguments, teman-teman bisa membolak-balik orientasi argumen tanpa memengaruhi fungsi sedikitpun; Teman-teman bisa menspesifikasikan default argument yang memungkinkan teman-teman bisa memanggil fungsi tersebut tanpa menggunakan argumen apapun; ","date":"6 Oktober 2023","externalUrl":null,"permalink":"/artikel/2023/10/06/fungsi-pada-kotlin/","section":"Artikel-artikel","summary":"","title":"Catatan Belajar Juara Android 2023 - #4 Fungsi pada Kotlin","type":"artikel"},{"content":"","date":"6 Oktober 2023","externalUrl":null,"permalink":"/tags/fungsi/","section":"Tags","summary":"","title":"Fungsi","type":"tags"},{"content":"","date":"5 Oktober 2023","externalUrl":null,"permalink":"/tags/development/","section":"Tags","summary":"","title":"Development","type":"tags"},{"content":"","date":"5 Oktober 2023","externalUrl":null,"permalink":"/series/development-log/","section":"Series","summary":"","title":"Development Log","type":"series"},{"content":"Yap, bulan Oktober, tentunya bulan Hacktoberfest! Suatu event tahunan dimana pengembang berlomba-lomba untuk memberikan kontribusinya ke open-source, baik itu melalui GitHub atau GitHub. Flexing sedikit ah hehehe:\nYoi, Hacktoberfest Saya kelar di hari ke-5. A trully intensive yet fun experience. Ini pertama kalinya Saya serius di Hacktoberfest, tahun sebelumnya Saya cuma daftar, nggak ngikut sampai selesai karena kerjaan sama nggak tau mana repositori yang harus dicontribute, susah-susah soalnya wkwkwk :grinning_face_with_sweat:.\nAnyway, selamat datang di Development Log. Development Log adalah suatu seri yang berisi tentang hal-hal apa saja yang telah Saya bangun, kembangkan, atau kontribusikan terhadap suatu project baik itu open-source atau closed-source. Kebanyakan dari kontribusi open-source akan Saya cantumkan tautan issue / PR-nya, namun untuk closed-source akan Saya sertakan short commit-hashnya saja.\nWell, let\u0026rsquo;s get coding.\nOpen-source # ozdemirburak/iris PHP library for color manipulation and conversion. PHP 120 21 [2023-10-04] feat: add CMYK [2023-10-04] feat: color clone [2023-10-05] feat: safe-alpha trait [2023-10-06] fix: rgba to hlsa and hexa to hsla EbookFoundation/free-programming-books :books: Freely available programming books Python 385832 66112 [2023-10-04] Improves Repo\u0026rsquo;s Code of Conducts, Contributing, and How-to Indonesian Grammars NikarashiHatsu/codeigniter4-kit A starter kit for CodeIgniter4 PHP 23 4 [2023-10-05] OPEN ISSUE: Upgrade CodeIgniter to v4.4.1 [2023-10-05] OPEN ISSUE: Upgrade DaisyUI to v3.9.1 [2023-10-05] OPEN ISSUE: Upgrade package.json and composer.json libraries\u0026rsquo; version` [2023-10-05] OPEN ISSUE: Feature Request: Edit User Profile from the Dashboard [2023-10-05] OPEN ISSUE: Hacktoberfest Issues Closed-source # AkateknoID/Pugugo # [2023-10-26] feat (corporate registration): siu and ktp pic attachment for corporat (#4c0ee97). Thanks for your help, shout out to Fahdi Labib! Hanjuan.net/simperkim # [2023-10-04] change (guest layout): ubah penggunaan livewire filtering menjadi menggunakan menu secara langsung (#eef53f1) [2023-10-04] change (system): hapus gambar gedung (#bb68ae0) [2023-10-04] feat (map overview): custom warna poligon (#95c20aa) [2023-10-04] feat (map overview): custom warna icon (#edbdaa3) [2023-10-05] fix (point input): area can not be null (#eb2bc2a) [2023-10-05] fix (point input): latitude and longitude didn\u0026rsquo;t persists on edit (#31567a5) [2023-10-20] feat (rtlh): kolom kerusakan atap (#63d20abb) [2023-10-20] feat (rtlh): kolom pencahayaan, pengyhawaan, ada wc (#feef49b5) [2023-10-20] feat (rtlh): jumlah jiwa dalam kk (#e2da9809) [2023-10-20] feat (rtlh): import rtlh (#f28871a1) [2023-10-21] feat: crud jumlah rumah (#be8c19f4) [2023-10-21] feat: crud jumlah penduduk (#733c2e7f) [2023-10-21] feat: template import rtlh (#3ae5c68b) [2023-10-22] help (tech): import data rtlh terbaru [2023-10-22] feat (rtlh): hapus rtlh (53134d40) [2023-10-22] fix (map overview): rtlh not showing (#6e1dfdb6) [2023-10-22] feat (dashboard): menu map data rtlh [2023-10-23] feat (perumahan kumuh): import delineasi kumuh (#4e5a071) [2023-10-27] fix (rtlh): page tidak responsive pada mobile (#ab9c6800) [2023-10-27] change (map overview): ubah map ke satellite view (#b59484f3) [2023-10-27] change (map overview): ubah icon dan warna (#ceee2e6a) Hanjuan.net/sikpkp # [2023-10-11] chore (technical): ubah nama aplikasi ke Sidakan (Sistem Informasi Data Pembudidayaan Ikan) [2023-10-13] change (logo): logo diubah bernuansa produksi ikan (#7c2ffce) [2023-10-13] fix (auth): memperbaiki texting dan gambar sesuai tema apliakasi (#57d767e) [2023-10-18] feat: homepage (#688fb16) [2023-10-18] feat: dokumen legalitas pokdakan (#c957bc7) [2023-10-18] change (label): ubah terminologi TPI ke Pokdakan di seluruh halaman (#33f5acb) [2023-10-18] fix (chart): label \u0026ldquo;Banyaknya Panen\u0026rdquo; seharusnya \u0026ldquo;Nilai Produksi\u0026rdquo; (#f6b4ef2) [2023-10-18] feat (homepage): memasukkan list Pokdakan pada homepage (#ee9cada) [2023-10-18] refactor (flow): mengubah dari per-tpi ke per-kecamatan (#04e7780) [2023-10-19] change (target): mengubah target bulanan menjadi target tahunan per kecamatan (#d57a6c9) [2023-10-19] feat (pokdakan): add kolom nama ketua (#4c22179) [2023-10-19] feat (pokdakan): add kolom nomor hp (#8ff427e) [2023-10-19] refactor (produksi): remove pokdakan dari produksi (#f6ab4bf) [2023-10-19] change (label): laporan per-pokdakan diganti per-kecamatan (#7811740) [2023-10-19] change (laporan): ubah seluruh laporan ke per-kecamatan instead of per-pokdakan (#6bffe56) [2023-10-19] change (print laporan): ubah seluruh print laporan ke per-kecamatan (#780e09f) [2023-10-19] chore (user): add user kecamatan seeder (#984886d) [2023-10-19] feat (user): cetak akun kecamatan (#f5d1b40) [2023-10-19] chore (auth): hide navbar \u0026amp; footer (#c03c5f2) [2023-10-19] feat (auth): login dengan data per-tahun (#8ae1701) [2023-10-19] feat (produksi): super admin bisa pilih kecamatan (#6c643d9) [2023-10-19] feat (pokdakan): kolom unique nomor registrasi pokdakan (#f57d6e7) [2023-10-19] feat (auth): rule input \u0026amp; filtering tahunan (#6a43dae) [2023-10-23] fix (berita): berita tidak bisa dibaca (#26d3c77) [2023-10-23] styling (berita): tambah margin untuk estetika (#9a7b2a9) [2023-10-25] chore (pokdakan): dokumen legalitas dibuat opsional (#7ad3b2d) [2023-10-26] feat (produksi): add jenis ikan on-demand (#270fa67) [2023-10-26] feat (produksi): multiple input ikan (#d62a293) Hanjuan.net/sikanda # [2023-10-04] feat (menu): add available menus to be used (#209338e) [2023-10-04] chore (technical): merge dependabot bumps [2023-10-05] feat (dashboard): initial dashboard menus (#b2659c2) [2023-10-05] chore (dashboard): convert page to livewire (#f0ddc4b) [2023-10-05] feat (modal): mockup penerimaan perlengkapan (#123c52e) [2023-10-05] feat (modal): mockup distributor (#2598580) [2023-10-05] chore (dashboard): set distributor button to open distributor modal (#a77066a) [2023-10-05] fix (modal, distributor): label penanggungjawab overflow (#7be8e60) [2023-10-05] feat (modal): perlengkapan (#3deee02) [2023-10-05] fix (modal, perlengkapan): typo and wrong icon (#af99723) [2023-10-05] chore (system): install laravel sail #4de5e5c [2023-10-05] chore (technical): deploy [2023-10-05] fix (route): invalid livewire route action (#b209a9b8) [2023-10-05] fix (system): \\App\\View\\Components\\Layout\\App didn\u0026rsquo;t comply PSR-4 standard (#4e6700d) [2023-10-11] chore (technical): proses bisnis [2023-10-23] change (transaksi penerimaan): pindah field yang salah section (#2cb2f4c) [2023-10-23] feat (master): master kategori (#e047a85) [2023-10-23] feat (transaksi penerimaan): add kategori (#c171197) [2023-10-23] feat (master): master satuan barang (#7b10238) [2023-10-23] feat (master): master barang (#729ece0) [2023-10-24] feat (master): master distributor (#e87284f) [2023-10-24] feat (master): master rak penyimpanan (#8b35683) [2023-10-24] feat (master): master data item (#84735e3) [2023-10-25] feat (master): master instansi / SKPD (#60b49ee) [2023-10-25] feat (master): master unit / instalasi (#79194ec) [2023-10-25] feat (master): master penanggung jawab (#b92a4fc) [2023-10-25] feat (master): master sub-unit (#9d8f542) [2023-10-25] feat (dashboard): modal instalasi / skpd (#542969b) [2023-10-25] feat (dashboard): modal pengaturan (#8cc52ef) [2023-10-25] feat (dashboard): modal ubah kata sandi (#c946dbd) [2023-10-25] feat (dashboard): modal laporan (#9a09ec5) [2023-10-25] feat (dashboard): modal laporan rencana kebutuhan item (#1dec4a2) [2023-10-25] feat (dashboard): modal form input penerimaan (#69f77a7) PT. Bengkel Web Indonesia/dumas-presisi # [2023-10-03] help (tech): resync laporan super app presisi yang tidak tersinkron [2023-10-07] fix (statistik grafik v2): satker pengelola masih sebagai satker terlapor (#6fcdb65) [2023-10-07] feat (backtrace summary): filtering periodik (#59eb794) [2023-10-07] chore (technical fix): jumlah tanggapan tidak sesuai dengan jumlah status penyelesaian [2023-10-07] revert #e0d1d97 (pengaduan selesai): laporan dengan status selesai diproses dikembalikan lagi ke halaman tindak lanjut status (#3496f3b) [2023-10-07] chore (technical): weekly backup [2023-10-15] feat (backtrace): backtrace filtering (#2beaa1f) [2023-10-15] feat (ai): AI Dumas Presisi (#01b9228) [2023-10-16] feat (laporan): memperbolehkan edit laporan sementara untuk admin instansi (#732b6e0) [2023-10-18] help (technical): fix beberapa laporan yang tidak bisa dikirim ulang ke satker pengelola [2023-10-19] feat (surat): hapus surat (#2035aaa) [2023-10-19] feat (disposisi surat): hapus disposisi surat (#d398210) [2023-10-19] change (backtrace): pindah satker Itwasum, Itwasda, dan Siwas ke bagian Satker Pengelola APIP (#fbec97c) [2023-10-19] change (REST): ubah pesan pengembalian laporan dari Yanduan ke Dumas (#ff46841) [2023-10-19] fix (tindak lanjut status): pengaduan selesai Diproses tidak berpindah ke halaman Pengaduan Selesai (#a91a0d7) [2023-10-19] fix (backtrace): backtrace direktorat Bareskrim tidak terakumulasi (#bf99817) [2023-10-19] change (backtrace): limit pangkat terlapor ke masing-masing tingkatan pengaduan (#e13134c) [2023-10-19] change (surat): fitur approval surat konvensional tidak perlu approval untuk dikirim ke satker (#e39608e) [2023-10-19] feat (statistik): ranking penyelesaian polda (#653ebdd) [2023-10-19] fix (pengaduan): laporan yang dikembalikan oleh Yanduan tidak bisa diedit (#a7a8f62) [2023-10-20] help (technical): generate user admin polda pimpinan (#9f1e7ae) [2023-10-20] fix (statistik): anev kata kunci tidak terkait ke masing-masing tingkatan (#5120a8a) [2023-10-20] fix (statistik): substansi permasalahan tidak terkait ke masing-masing tingkatan (#b47ce08) [2023-10-21] fix (statistik): demografi agama tidak terkait ke masing-masing tingkatan (#ab51dde) [2023-10-21] fix (statistik): demografi masyarakat tidak terkait ke masing-masing tingkatan (#d57e70) [2023-10-21] fix (statistik): demografi masyarakat pendidikan tidak terkait ke masing-masing tingkatan (#718a41c) [2023-10-21] fix (statistik): demografi masyarakat pekerjaan tidak terkait ke masing-masing tingkatan (#d46447a) [2023-10-21] fix (statistik): filtering mabes tampil pada akun polda pada halaman statistik harian (#48f21fa) [2023-10-21] fix (statistik): statistik feedback tidak terkait ke masing-masing tingkatan (#3d8e334) [2023-10-21] fix (statistik): statistik berkadar polda tidak fetch data polda (#21a0dc0) [2023-10-21] chore (statistik): hide menu statistik konsultasi v2 untuk polda (#8c3d28a) [2023-10-21] feat: backtrace polda (#66ff81d) [2023-10-21] feat: backtrace summary polda (#d9567d6) [2023-10-21] help (technical): generate user pimpinan polda [2023-10-21] help (technical): generate user pimpinan polda [2023-10-25] help (technical): generate user pimpinan polda [2023-10-25] technical: rapat dengan tim WA Yanduan PT. Bengkel Web Indonesia/surat-riau # [2023-10-11] feat (flow polres): antar satker tingkat si (#5c6a8d1) [2023-10-11] feat (flow polres): tujuan polres lain (#ba75ae2) [2023-10-11] feat (flow polres): tujuan polda a.n kapolres / wakapolres (#ba75ae2) [2023-10-11] feat (flow polres): antar satker tingkat bag (#218010b) [2023-10-11] feat (flow polres): antar satker tingkat sat (#045fa4c) [2023-10-11] chore (technical): upsert user Polres Kep. Meranti, Rohil, Bengkalis [2023-10-11] feat (user role): role grouping (#c5afc4f) [2023-10-11] fix (flow polres): wakapolres tidak bisa ttd (#fa3eb8f) [2023-10-11] fix (flow polres): template surat tidak ngeload (#3af2091) [2023-10-11] fix (flow polres): surat polres ke polres tidak masuk melalui si um terlebih dahulu (#686ab8f) [2023-10-11] test (technical): test flow lengkap antar polres [2023-10-11] feat (user role): tambah user admin (#bed3bdd) [2023-10-11] fix (disposisi): disposisi tidak tampil pada user role kasatker (#32951ae, #f3d01d1) [2023-10-11] chore (seeder): user admin seeder (#8c6553a) [2023-10-12] fix (user): role group not defined (#ded3228) [2023-10-12] fix (surat): wakapolres tidak bisa ttd (#fa3eb8f) [2023-10-12] fix (surat): surat polda -\u0026gt; polres tidak masuk ke sium (#2373665) [2023-10-12] fix (surat): surat polres -\u0026gt; polres tidak masuk ke sium (#686ab8f) PT. Quantum Teknologi Indonesia/kenangan-backend # More info will be added later\nPT. Quantum Teknologi Indonesia/meepo-backend # More info will be added later\nPT. Quantum Teknologi Indonesia/meepo-web # More info will be added later\n","date":"5 Oktober 2023","externalUrl":null,"permalink":"/artikel/2023/10/05/october-development-log/","section":"Artikel-artikel","summary":"","title":"Development Log - 2023 {Hack}toberfest","type":"artikel"},{"content":"","date":"5 Oktober 2023","externalUrl":null,"permalink":"/tags/log/","section":"Tags","summary":"","title":"Log","type":"tags"},{"content":"Pada kesempatan kali ini, Saya akanbelajar untuk membuat variabel pada Kotlin. Kita kembali ke pelajaran SD dulu sebentar, yuk. Dulu, pada saat kita belajar untuk menghitung persegi panjang, ada beberapa informasi yang kita butuhkan untuk menentukan apakah persegi panjang itu valid:\nBidang tersebut memiliki panjang; Bidang tersebut memiliki lebar; Bidang tersebut tidak boleh memiliki panjang dan lebar yang sama, jika sama, maka sudah bisa dipastikan bidang datar tersebut adalah sebuah Persegi. Nah, panjang dan lebar inilah yang kita sebut sebagai variabel. Supaya kita dapat mengingat rumus tersebut dengan mudah, maka diperpendek lah variabel tersebut menjadi p dan l. Simpel kan? Itulah variabel, in a nutshell.\nTipe Data # Ada beberapa tipe data yang harus kita pahami, supaya kita bisa membuat aplikasi yang bebas dari bug:\nTipe Data Kotlin Data apa yang bisa digunakan Contoh String Teks \u0026ldquo;Tambah kontak\u0026rdquo; \u0026ldquo;Cari\u0026rdquo; \u0026ldquo;Masuk\u0026rdquo; Int Angka integer 32 1293490 -59281 Double Angka desimal 2.0 501.0292 -31723.99999 Float Angka desimal, namun kurang akurat daripada Double, memiliki f atau F di akhir angka 5.0f -1630.209f 1.2940278F Boolean true atau false. Gunakan tipe data ini jika hanya ada dua value. true dan false termasuk keywords pada Kotlin true false Tipe data untuk Angka # Ternyata, ada tipe data lainnya yang merepresentasikan angka. Teman-teman dapat menggunakan tipe data ini untuk penggunaan memori yang lebih optimal:\nTipe Ukuran (bits) Value minimal Value maksimal Byte 8 -128 127 Short 16 -32768 32767 Int 32 -2,147,483,648 (-231) 2,147,483,647 (231 - 1) Long 64 -9,223,372,036,854,775,808 (-263) 9,223,372,036,854,775,807 (263 - 1) Tipe data Float # Untuk bilangan ril, Kotlin menyediakan tipe floating-point Float dan Double yang melekat pada standar IEEE 754. Float mencerminkan single-precision, sementara Double mencerminkan double-precision.\nPerbedaannya adalah:\nTipe Ukuran (bits) Bit signifikan Bit eksponen Digit desimal Float 32 24 8 6-7 Double 64 53 11 15-16 Untuk menginisialisasi variabel Double atau Float, adalah dengan cara menggunakan angka yang dipisahkan oleh titik (.). Contohnya:\nval pi = 3.14 // double // val one: Double = 1 // Error: type mismatch val oneDouble = 1.0 // double Untuk menspesifikasikan Float secara eksplisit, gunakan suffix f atau F. Jika pecahannya lebih dari 6-7 digit, maka angka tersebut akan dibulatkan:\nval e = 2.7182818284 // Double val eFloat = 2.7182818284f // Float, angka aslinya adalah 2.7182817 Berbeda dengan bahasa pemrograman lainnya, Kotlin tidak memiliki konversi antar angka. Contohnya, sebuah fungsi dengan parameter Double hanya bisa digunakan oleh variabel Double, bukan Float atau Int, atau tipe data numerik lainnya:\nfun main() { fun printDouble(d: Double) { print(d) } val i = 1 val d = 1.0 val f = 1.0f printDouble(d) // 1.0 // printDouble(i) // Error: Type mismatch // printDOuble(f) // Error: Type mismatch } Untuk referensi selengkapnya, teman-teman bisa baca di Konsep Angka Kotlin.\nAnatomi Variabel # Sebuah variabel secara singkat membutuhkan expression dan value. Contohnya sebuah variabel count memiliki value 2.\nfun main() { val count: Int = 2 println(count) // 2 } Pada Kotlin sendiri, pendeklarasian variabel menggunakan keyword val, diikuti dengan expression atau name, lalu data type (jika ingin dideklarasikan secara eksplisit), lalu =, dilanjutkan dengan initial value. Kurang lebih seperti ini ilustrasinya:\nDengan konteks variabel count di atas, kita dapat mengatakan bahwa variabel count memiliki tipe data Int dan memiliki initial value ke 2:\nUntuk penulisan nama variabel tersendiri, Google menyarankan untuk:\nTidak menggunakan (keyword Kotlin)[https://kotlinlang.org/docs/keyword-reference.html] sebagai nama variabel; Menggunakan nama yang mendeskripsikan data yang akan disimpan; Menggunakan camelCase, contoh numberOfEmails, cityName, bookPublicationDate; String Template # Terkadang kita ingin menggabungkan tipe data satu ke tipe data lainnya untuk menyajikan informasi. Pada kasus ini, anggap saja kita ingin menampilkan \u0026ldquo;Anda memiliki 2 pesan yang belum dibaca\u0026rdquo;. Jadi, ayo coba kita buat:\nKita buat aplikasi sederhana, yang digunakan untuk menampilkan pesannya terlebih dahulu tanpa angka: fun main() { val count: Int = 2 println(\u0026#34;Anda memiliki pesan yang belum dibaca\u0026#34;) } Jalankan program tersebut, lalu kita akan melihat pesan Anda memiliki pesan yang belum dibaca. Tentunya pesan ini tidak masuk akal, karena kita tidak tahu berapa banyak pesan yang belum kita baca. Untuk memperbaiki output tersebut, kita membutuhkan sebuah string template. Expresi untuk menggunakan string template adalah menggunakan simbol dolar ($), yang dilanjut oleh nama variabel. fun main() { val count: Int = 2 println(\u0026#34;Anda memiliki $count pesan yang belum dibaca\u0026#34;) } Ketika kita jalankan programnya, maka output akan menampilkan Anda memiliki 2 pesan yang belum dibaca. Type Inference # Terkadang kita malas atau bahkan tidak mengetahui tipe data apa yang dikirimkan oleh server kepada aplikasi kita. Untuk itu, kita bisa men-ignore tipe data yang ingin kita gunakan pada saat pendeklarasian variabel.\nHati-hati, walaupun diperbolehkan, namun keambiguan tipe data bisa jadi berdampak buruk kepada aplikasi yang Anda buat. Jadi, pastikan untuk memvalidasi tipe data apa yang akan digunakan, atau definisikan tipe data yang ingin Anda pakai secara eksplisit. Sebagai contoh kita ingin mempersingkat waktu pengembangan dengan cara tidak menuliskan tipe data yang digunakan, kita bisa saja menuliskan\nval count = 2 // kode di atas sama saja dengan: val count: Int = 2 Kompiler Kotlin mengetahui bahwa kamu ingin menyimpan 2 ke dalam variabel count, jadi dia akan menggunakan tipe Int secara otomatis. Namun perlu diingat, jika tidak ada initial value yang digunakan pada saat teman-teman membuat variabel, maka Kotlin mewajibkan untuk meulis tipe data. Contoh\nval count: Int val videoTitle: String Operasi Aritmetika pada String Template # Seringkali kita ingin melakukan operasi aritmetika secara langsung tanpa menggunakan variabel untuk mempersingkat waktu, anggap saja kita ingin membuat sebuah pesan \u0026ldquo;Anda memiliki n pesan belum dibaca\u0026rdquo;, namun kita juga memiliki beberapa pesan yang sudah dibaca.\nUntuk mencapai hal ini, kita bisa menggunakan operasi aritmetika secara langsung, contoh:\nfun main() { val unreadCount: Int = 5 val readCount: Int = 100 println(\u0026#34;Anda memiliki ${unreadCount - readCount} pesan dalam inbox Anda\u0026#34;) } Atau seperti ini:\nfun main() { val numberOfPhotos: Int = 100 val photosDeleted: Int = 5 println(\u0026#34;Anda memiliki $numberOfPhotos foto\u0026#34;) println(\u0026#34;$photosDeleted sudah dihapus\u0026#34;) println(\u0026#34;${numberOfPhotos - photosDeleted} foto yang tersisa\u0026#34;) } Memperbarui data variabel # Pada saat pengembangan, value dari variabel tentunya akan berubah seiring waktu teman-teman mendapatkan datanya secara asinkronus dari server, atau berubah karena inputan pengguna. Untuk mencapai hal ini, kita harus menggunakan keyword var.\nPerbedaan antara val dan var adalah sebagai berikut:\nval - Initial value tidak dapat diubah var - Initial value dapat diubah Contohnya:\nfun main() { val cartTotal = 0 cartTotal = 20 // println(\u0026#34;Total: $cartTotal\u0026#34;) // Val cannot be reassigned } Variabel dengan keyword val adalah sebuah variabel yang bersifat read-only, yaitu variabel yang hanya bisa dibaca. Setelah variabel tersebut di-assign, maka teman-teman tidak dapat mengubah isinya.\nfun main() { var cartTotal = 0 println(\u0026#34;Total: $cartTotal\u0026#34;) // 0 cartTotal = 20 println(\u0026#34;Total: $cartTotal\u0026#34;) // 20 } Ingat bahwa teman-teman harus menggunakan var pada saat mendeklarasi variabel jika kamu berekspektasi bahwa isinya dapat berubah. Selain itu, selalu gunakan val untuk mendeklarasikan variabel. Praktek ini memastikan kode teman-teman jauh lebih aman.\nOperator Increment dan Decrement # Operator ini, sama saja seperti bahasa pemrograman lainnya, digunakan untuk menambah atau mengurangi sebuah value. Ada beberapa cara untuk menggunakan increment atau decrement:\nvar count = 10 // Increment // Cara 1: println(\u0026#34;Anda memiliki $count pesan yang belum dibaca\u0026#34;) // 10 count = count + 1 println(\u0026#34;Anda memiliki $count pesan yang belum dibaca\u0026#34;) // 11 // Cara 2 println(\u0026#34;Anda memiliki $count pesan yang belum dibaca\u0026#34;) // 11 count++ println(\u0026#34;Anda memiliki $count pesan yang belum dibaca\u0026#34;) // 12 // Decrement // Cara 1: println(\u0026#34;Anda memiliki $count pesan yang belum dibaca\u0026#34;) // 12 count = count - 1 println(\u0026#34;Anda memiliki $count pesan yang belum dibaca\u0026#34;) // 11 // Cara 2: println(\u0026#34;Anda memiliki $count pesan yang belum dibaca\u0026#34;) // 11 count-- println(\u0026#34;Anda memiliki $count pesan yang belum dibaca\u0026#34;) // 10 String Concatenation # Teman-teman juga dapat menyatukan dua string menggunakan simbol plus (+)\nfun main() { val nextMeeting = \u0026#34;Next meeting\u0026#34; val date = \u0026#34;January 1\u0026#34; println(nextMeeting + date) // Next meetingJanuary 1 } Dilihat dari contoh di atas, bisa dilihat bahwa Kotlin akan secara otomatis menggabungkan dua string menjadi satu. Namun, secara formatting akan terlihat jelek, kita bisa memperbaiki kode tersebut menjadi seperti ini:\nfun main() { val nextMeeting = \u0026#34;Next meeting: \u0026#34; val date = \u0026#34;January 1\u0026#34; val reminder = nextMeeting + date println(nextMeeting + date) // Next meeting: January 1 } String Escaping # Seperti bahasa pemrograman lainnya, Kotlin juga dapat men-escape string dengan menggunakan simbol backslash (\\). Seperti contoh:\nfun main() { println(\u0026#34;Say \\\u0026#34;hello\\\u0026#34;\u0026#34;) // Say \u0026#34;hello\u0026#34; } Kotlin String Styling Guide # Sebelumnya, kita sudah mengenal gaya penulisan untuk fungsi yang direkomendasikan oleh Google, nah, sekarang kita juga akan mempelajari bagaimana cara penulisan variabel yang baik dan benar:\nNama variabel harus menggunakan camelCase Setelah deklarasi variabel, harus ada spasi setelah titik dua untuk menspesifikasikan tipe data Harus ada spasi sebelum dan sesudah operator seperti operator assignment (=), penambahan (+), pengurangan (-), perkalian (*), dan pembagian (/), dll Sejalan dengan kompleksitas program yang dibuat, ada anjuran untuk menmbatasi 100 karakter per-baris, untuk memastikan kemudahan dan keterbacaan kode secara horizontal. Komentar # Ada dua tipe komentar yaitu inline dan multi-line, berikut adalah contohnya:\n// Ini adalah komentar *inline* /** * Ini adalah komentar multi-line yang dapat digunakan * ketika teman-teman membuat suatu fungsi atau variabel * yang membutuhkan penjelasan panjang lebar. */ Contoh penggunaannya dalam program untuk menjelaskan apa yang terjadi:\n/** * Program ini menampilkan berapa banyak pesan * dalam inbox pengguna */ fun main() { // Buat variabel untuk pesan-pesan yang belum dibaca var count = 10 println(\u0026#34;Anda memiliki $count pesan yang belum dibaca\u0026#34;) // Anda memiliki 10 pesan yang belum dibaca // Kurangi 1 pesan count-- println(\u0026#34;Anda memiliki $count pesan yang belum dibaca\u0026#34;) // Anda memiliki 9 pesan yang belum dibaca } Ringkasan # Ringkasan dari pembelajaran kali ini adalah:\nSebuah variabel adalah sebuah kontainer untuk suatu data; Variabel harus dideklarasikan sebelum digunakan; Gunakan keyword val untuk variabel yang read-only dan tidak bisa diubah; Gunakan keyword var untuk variabel yang mutable atau bisa diubah; Penggunaan val lebih dianjurkan daripada var; Untuk mendeklarasikan sebuah variabel, mulai keyword dengan val atau var, dilanjut dengan nama variabel, tipe data, dan initial value, contohnya val count: Int = 2; Dengan Type Inference, penulisan tipe data dapat diabaikan jika initial value diberikan; Tipe data Kotlin dasar meliputi Int, String, Boolean, Float, dan Double; Gunakan operator assignment (=) untuk meng-assign sebuah value ke dalam variabel, pada saat deklarasi atau memperbarui data variabel; Gunakan increment operator (++) untuk menambahkan 1 value kepada sebuah integer, dan decrement operator (--) untuk mengurangi 1 value kepada sebuah integer; Gunakan simbol plus (+) untuk menggabungkan String. Penggabungan variabel juga dapat digunakan pada tipe data lainnya seperti Int dan Boolean ke String; ","date":"4 Oktober 2023","externalUrl":null,"permalink":"/artikel/2023/10/04/variabel-pada-kotlin/","section":"Artikel-artikel","summary":"","title":"Catatan Belajar Juara Android 2023 - #3 Variabel Pada Kotlin","type":"artikel"},{"content":"Pada kesempatan kali ini, Saya akan membuat aplikasi Android menggunakan Kotlin. Tapi sebelum itu, tentu saja Saya harus mempelajari basicnya terlebih dahulu. Kotlin adalah bahasa pemrograman yang direkomendasikan oleh Google untuk membuat aplikasi Android.\nSebuah bahasa pemrograman modern yang membantu pengembang untuk menjadi lebih produktif. Dengan kode yang lebih sedikit, Kotlin dapat memperoleh fungsionalitas yang sama dibanding dengan bahasa pemrograman lainnya. Aplikasi yang dibuat Kotlin juga lebih jarang crash, sehingga aplikasi yang dibuat dapat lebih stabil.\nUntuk pengembangnya sendiri, secara developer-experience, dengan Kotlin kita dapat membuat aplikasi Android dengan waktu yang lebih singkat. Let\u0026rsquo;s go gak sih?\nKotlin Playground # Jadi, Google ini punya Kotlin Playground, sebuah codelab yang disajikan untuk kita developer untuk bermain-main dengan kode Kotlin. Berikut adalah screenshotnya: In the background, Kotlin Playground menggunakan Java Virtual Machine, tentu saja tetap menggunakan Java sebagai bahasa utamanya. Jadi kode Kotlin yang kita buat akan di-compile terlebhi dahulu.\nHere comes the programming\u0026hellip; # Saatnya memulai mengkodingan. Pertama-tama, Saya belajar untuk membuat / mendefinisikan sebuah fungsi. Sebuah fungsi / function adalah sebuah set instruksi yang digunakan untuk melakukan sebuah pekerjaan.\nUntuk membuat sebuah fungsi, ada beberapa bagian yang dibutuhkan:\nFungsi membutuhkan sebuah nama, sehingga bisa dipanggil nanti; Fungsi juga bisa saja membutuhkan beberapa input atau informasi yang dibutuhkan ketika fungsi dipanggil. Fungsi menggunakan inputan-inputan tersebut untuk menyelesaikan tujuannya. Beberapa input bisa jadi opsional, bahkan tidak memerlukan input satupun. Fungsi harus memiliki body, yang mencakup instruksi-instruksi untuk melakukan sebuah pekerjaan. Untuk menerjemahkan diagram di atas ke sintaks Kotlin, kita menggunakan keywork fun (menyenangkan, ya? :smile:) yang harus didefinisikan terlebih dahulu, lalu dilanjut oleh nama fungsinya, dilanjut oleh input-input dalam tanda kurung, lalu dialnjut oleh kurawal diantara body-nya.\nPenamaan fungsi juga tidak bisa sembarangan, ada beberapa aturan yang harus diikuti seperti:\nTidak boleh menggunakan Kotlin Keyword; Dianjurkan menggunakan penulisan camelCase, misal calculateTip, displayErrorMessage, atau takePhoto; Berikut adalah program pertama Saya menggunakan Kotlin:\nfun main() { println(\u0026#34;Halo, Ibu!\u0026#34;) // Halo, Ibu! } Saya belum bisa memahami cara menggunakan input parameter / argumen di bahasa ini, mungkin akan dijelaskan di bagian selanjutnya. :grinning_face_with_sweat:\nKotlin Function Styling Guide # Karena Saya ingin menjadi developer hebat dan tidak abal-abal, tentunya kerapihan kode juga Saya pelajari betul. Tentunya, ini akan membantu Saya dan tim (semoga, kedepannya) untuk membuat sebuah aplikasi bersama-sama. Jadi, ada beberapa rekomendasi yang disarankan oleh Google:\nNama fungsi harus berbentuk camelCase, dan harus menggunakan Kata Kerja atau Frasa Kata Kerja; Setiap statement harus berada dalam barisnya sendiri; Kurawal pembuka harus ada di akhir barus dimana fungsi berada; Harus ada spasi sebelum kurawal pembuka; Body fungsi harus di-inden dengan 4 spasi, bukan menggunakan tab; Kurawal penutup harus berada di baris akhir, sejajar dengan keyword fun; Ringkasan # Sebuah program Kotlin membutuhkan fungsi main sebagai entri program; Untuk mmbuat sebuah fungsi, gunakan keyword fun, dilanjut dengan nama fungsi, input-input jika ada diapit dengan tanda kurung, dilanjut dengan body yang diapit dengan kurawal; Nama fungsi harus menggunakan camelCase; Menggunakan println() untuk mencetak teks ke output; Mematuhi aturan penulisan Kotlin; Kesalahan itu wajar, troubleshooting is eternal; ","date":"4 Oktober 2023","externalUrl":null,"permalink":"/artikel/2023/10/04/program-kotlin-pertamaku/","section":"Artikel-artikel","summary":"","title":"Catatan Belajar Juara Android 2023 - #2 Program Kotlin Pertamaku","type":"artikel"},{"content":"Tahun ini, Saya bertekad untuk mengupgrade skill Saya yang sudah sekian lama tidak diasah. Tentunya walaupun otak ini terbiasa dengan Laravel dan PHP, lama-lama jadi bosan juga :grinning_face_with_sweat:, tapi ya namanya manusia, harus tetap belajar dan terus belajar. Apalagi dunia teknologi akan selalu berkembang pesat setiap harinya.\nPada kesempatan ini, Saya mengikuti #JuaraAndroid2023, dimana Saya akan belajar untuk membuat sebuah aplikasi Android menggunakan Kotline dan Compose. Konsep Compose sendiri termasuk baru untuk Saya, jadi Saya tidak dapat memberikan penjelasan secara singkat ataupun lengkap terlebih dahulu :face_with_tears_of_joy:.\nBefore I begin\u0026hellip;presummably\u0026hellip; # Sebelum Saya memulai pembelajaran, Saya disuguhkan oleh sebuah artikel yang berjudul \u0026ldquo;Before You Begin\u0026rdquo;. Isinya hanyalah prolog tentang bagaimana kursus ini akan berjalan. Jadi, secara singkatnya Saya akan belajar membuat beberapa aplikasi menggunakan Jetpack Compose, yang merupakan cara modern untuk membuat UI yang cantik nan elegan :nail_polish:.\nKursus ini juga akan menyediakan beberapa Code Labs dan video Code-along, yang berarti Saya bisa mengotak-ngatik potongan kode yang mereka sediakan, serta memberikan penjelasan langsung secara side-by-side, surely an impressive method of learning.\nAkhir kata, Google memberikan saran kepada developer baru ataupun yang sudah pernah menyentuh pengembangan Android. Untuk developer baru, disarankan agar mereka mengikuti learning path yang disediakan. Sedangkan untuk developer lama, diperbolehkan sekali untuk melewati beberapa materi yang sudah pernah dipahami atau dipelajari.\nI\u0026hellip;welcomed? # Setelah prolog, Saya disambut dengan sebuah video selamat datang. Presenter dari video ini tidak lain adalah instruktur-instruktur yang akan mentutor Saya dari awal hingga akhir (semoga sampai mahir). Sedikit fakta, ada 2,5 miliar perangkat Android, dari perangkat seluler, tablet, TV, dll. Sebuah kebanggaan tersendiri jika Saya bisa membuat sebuah aplikasi yang bisa membantu menyelesaikan masalah dari 2,5 miliar pengguna tersebut.\nMaju sedikit di videonya, tujuan dari kursus ini adalah Saya dapat memahami cara menggunakan Android Studio dan Kotlin. Menurut mereka, hal Saya butuhkan untuk mengikuti kursus ini adalah:\nKomputer Koneksi Internet Literasi Komputer Dasar Kemampuan Matematika Dasar Tentu saja, semuanya sudah terakomodir. I\u0026rsquo;m a computer-person, haha. Selain itu, mereka juga mengatakan bahwa It\u0026rsquo;s also nice to have:\nHP Android / Tablet Familiaritas untuk menggunakan Android Motivasi (ini yang paling sulit sih :grinning_face_with_sweat:) Secara realistik, Saya bisa menyelesaikan kursus ini dalam beberapa minggu atau beberapa bulan kedepan. Stay tuned ya di artikel-artikel lainnya :smile:.\n","date":"4 Oktober 2023","externalUrl":null,"permalink":"/artikel/2023/10/04/catatan-belajar-juara-android-2023-introduction/","section":"Artikel-artikel","summary":"","title":"Catatan Belajar Juara Android 2023 - #1 Introduction","type":"artikel"},{"content":"","date":"23 Mei 2023","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"Yeah, it\u0026rsquo;s one year and I can passthrough this\u0026hellip;\nDisclaimer # You are completely responsible for your hardware and software. This guide makes no guarentees that the process will work for you, or will not void your waranty on various parts or break your computer in some way. Everything from here on out is at your own risk.\nThis tutorial is for laptop that iGPU is still connected to External Monitor/Dummy HDMI. If your laptop\u0026rsquo;s dGPU connected to External Monitor/Dummy HDMI, this tutorial is not for you.\nStory behind this thing # I\u0026rsquo;m using gaming laptop with linux for working but I still want to be a \u0026ldquo;gamers\u0026rdquo;. There\u0026rsquo;s some limitation when I gaming in Linux especially the Anti Cheat that cannot be used with Proton. The same goes for productive apps like Adobe Premiere/After Effect or Microsoft Office that 100% not compatible with Wine.\n\u0026ldquo;Why not dual boot?\u0026rdquo;\nI must re-sync all the clock system in Windows and Linux when I\u0026rsquo;m switching the OS. I must install the same app that I\u0026rsquo;ve use in Linux to Windows just for communication. Like Discord for gaming. This nerd blood is too strong in me\u0026hellip; Pros \u0026amp; Cons # For pros:\nCould be faster than dual booting (this is true in my case, it\u0026rsquo;s depends on your system). You have ability to switch mode to Hybrid (Optimus) and Integrated (Isolated) with a single command line. Using virtual disk, you can bring it all the way. virt-manager running without sudo like the other tutorial. For cons:\nThis method using preallocated qemu-img that consume a bunch of storage for a better performance. You still have to reboot your laptop to switch your GPU. You\u0026rsquo;ll need to change some source code from envycontrol. My Setup # For this tutorial, I\u0026rsquo;m using MSI Katana GF66 11UD with MUXless scheme and using Garuda Linux distributon. For the specs, you can search it in internet.\nPrerequisites # A laptop that can support VT-d/VT-g. Some Intel processor above 7th Generation have this feature. Enable the Intel Virtualization in BIOS first before start geeking. Installing QEMU with virt-manager. You can follow this tutorial for the step of installation. After installing QEMU, install QEMU Hooks to automate bind/unbind the GPU. virtio-win drivers and guest tools. I suggest you to download the ISO file. spice-guest-tools. Looking Glass, for better gaming. [OPTIONAL] Windows File System Proxy to connect your local drive to the VM. Procedures # Enable Intel IOMMU and Load VFIO Modules # Modify your kernel parameter (usually in /etc/default/grub if you using GRUB), and add:\nintel_iommu=on iommu=pt kvm.ignore_msrs=1 Don\u0026rsquo;t forget to update your GRUB after modify your kernel parameter.\nAdd this module to your initramfs: (in my case, I\u0026rsquo;m using dracut that location in /etc/dracut.conf.d/)\nadd_driver+=\u0026#34; vfio vfio_iommu_type1 vfio_pci vfio_virqfd \u0026#34; Don\u0026rsquo;t forget to update your initramfs after adding some module.\nRestart your laptop.\nDisable NVIDA From Accessing X-Server # Check your GPU. You can use lspci -nnk | grep -i nvidia to check if your GPU is still be used by Nvidia/Nouveau module.\nCopy all envycontrol setup.py code to your laptop.\nSearch this line of code:\nif graphics_mode == \u0026#39;integrated\u0026#39;: cleanup() # blacklist all nouveau and Nvidia modules create_file(BLACKLIST_PATH, BLACKLIST_CONTENT) # power off the Nvidia GPU with udev rules create_file(UDEV_INTEGRATED_PATH, UDEV_INTEGRATED) rebuild_initramfs() Give the command that generate file to power off the Nvidia GPU because we still need to use the GPU. The code will look like this:\nif graphics_mode == \u0026#39;integrated\u0026#39;: cleanup() # blacklist all nouveau and Nvidia modules create_file(BLACKLIST_PATH, BLACKLIST_CONTENT) # power off the Nvidia GPU with udev rules #create_file(UDEV_INTEGRATED_PATH, UDEV_INTEGRATED) rebuild_initramfs() [OPTIONAL, if you using Dracut] search this line of code:\ndef rebuild_initramfs(): # Debian and Ubuntu derivatives if os.path.exists(\u0026#39;/etc/debian_version\u0026#39;): command = [\u0026#39;update-initramfs\u0026#39;, \u0026#39;-u\u0026#39;, \u0026#39;-k\u0026#39;, \u0026#39;all\u0026#39;] # RHEL and SUSE derivatives elif os.path.exists(\u0026#39;/etc/redhat-release\u0026#39;) or os.path.exists(\u0026#39;/usr/bin/zypper\u0026#39;): command = [\u0026#39;dracut\u0026#39;, \u0026#39;--force\u0026#39;, \u0026#39;--regenerate-all\u0026#39;] # EndeavourOS with dracut elif os.path.exists(\u0026#39;/usr/lib/endeavouros-release\u0026#39;) and os.path.exists(\u0026#39;/usr/bin/dracut\u0026#39;): command = [\u0026#39;dracut-rebuild\u0026#39;] Change and operator to or in EndeavourOS with dracut because the tools wont execute the dracut-rebuild if you\u0026rsquo;re using dracut without using EndeavourOS distribution.\nCopy or make symlink to /usr/bin and execute this command:\nsudo envycontrol -s integrated Reboot your laptop, check your GPU with lspci -nnk | grep -i nvidia. It should your GPU not using nvidia kernel module anymore. Note: If you want to back to Hybrid mode, just execute this command again:\nsudo envycontrol -s nvidia And make it sure your GPU using nvidia kernel after rebooting.\nNote2: Check this out, maybe you can follow my lazy ideas.\nInstall KVM # You can follow the instruction for installation VM from BlandManStudios video. Some people don\u0026rsquo;t recommend this video because giving bad information or something. But if you want to see how to install an OS to QEMU, I think you can follow his tutorial.\nFor QEMU Hooks, here is some references that I use for my setup. Don\u0026rsquo;t copy-paste my hooks because maybe my scene is not same as you.\nprepare/begin/start.sh\n#!/bin/bash # Helpful to read output when debugging set -x # Stop Cloudflare WARP Service systemctl stop warp-svc # Unbind VTconsole echo 0 \u0026gt; /sys/class/vtconsole/vtcon0/bind # Avoid a Race condition by waiting 2 seconds. This can be calibrated to be shorter or longer if required for your system sleep 2 # Unbind the GPU from display driver virsh nodedev-detach pci_0000_01_00_0 # Load VFIO Kernel Module modprobe vfio-pci release/end/revert.sh\n#!/bin/bash set -x # Start Cloudflare WARP Service systemctl start warp-svc # Re-Bind GPU to Nvidia Driver virsh nodedev-reattach pci_0000_01_00_1 # Rebind VT consoles echo 1 \u0026gt; /sys/class/vtconsole/vtcon0/bind After you install VM. Don\u0026rsquo;t forget to install virtio-driver and spice-guest-tools for better VM QoL.\nInstalling Looking Glass # You can follow the installation for setup IVSHMEM and KVM Permission from Looking Glass wiki here. Here\u0026rsquo;s some arguments reference to launch looking glass with my scheme:\n# Launching Looking Glass with changing ScrollLock button to RightCtrl looking-glass-client -m 97 -c DXGI [OPTIONAL] VirtIO FS # After installing virtio-driver, add filesystem hardware with virtiofs and specify your location in VM Manager.\nReferences # https://lantian.pub/en/article/modify-computer/laptop-intel-nvidia-optimus-passthrough.lantian/ https://github.com/joeknock90/Single-GPU-Passthrough https://gist.github.com/Misairu-G/616f7b2756c488148b7309addc940b28 Complete reference directly from its author: ikr4-m/dotfiles My heavily dotfiles for my battle station Shell 5 0 ","date":"23 Mei 2023","externalUrl":null,"permalink":"/artikel/2023/05/23/gpu-laptop-passthrough-intel-x-nvidia/","section":"Artikel-artikel","summary":"","title":"How To: MUXless GPU Passthrough (NVIDIA x Intel Edition)","type":"artikel"},{"content":"","date":"23 Mei 2023","externalUrl":null,"permalink":"/authors/ikramullah/","section":"Authors","summary":"","title":"Ikramullah","type":"authors"},{"content":"Sekumpulan artikel yang ditulis pada saat gabut.\n","date":"16 Mei 2023","externalUrl":null,"permalink":"/artikel/","section":"Artikel-artikel","summary":"","title":"Artikel-artikel","type":"artikel"},{"content":"Catatan pribadi dan proses perkuliahan.\n","date":"16 Mei 2023","externalUrl":null,"permalink":"/lainnya/kuliah/","section":"Lainnyas","summary":"","title":"Catatan Kuliah","type":"lainnya"},{"content":"Koleksi jepretan foto yang diambil secara random, sebagai media penyalur hobi fotografi.\nTahun 2024 # Galeri-galeri Tahun Sebelumnya # 2023 2022 2021 ","date":"16 Mei 2023","externalUrl":null,"permalink":"/galeri/2024/","section":"Galeri","summary":"","title":"Galeri","type":"galeri"},{"content":"Koleksi jepretan foto yang diambil secara random, sebagai media penyalur hobi fotografi.\n2021 # ","date":"16 Mei 2023","externalUrl":null,"permalink":"/galeri/2021/","section":"Galeri","summary":"","title":"Galeri 2021","type":"galeri"},{"content":"Koleksi jepretan foto yang diambil secara random, sebagai media penyalur hobi fotografi.\n2022 # ","date":"16 Mei 2023","externalUrl":null,"permalink":"/galeri/2022/","section":"Galeri","summary":"","title":"Galeri 2022","type":"galeri"},{"content":"Koleksi jepretan foto yang diambil secara random, sebagai media penyalur hobi fotografi.\n2023 # ","date":"16 Mei 2023","externalUrl":null,"permalink":"/galeri/2023/","section":"Galeri","summary":"","title":"Galeri 2023","type":"galeri"},{"content":"","date":"16 Mei 2023","externalUrl":null,"permalink":"/lainnya/","section":"Lainnyas","summary":"","title":"Lainnyas","type":"lainnya"},{"content":"Hasil terjemahan lagu-lagu Jepang - Inggris - Indonesia.\n","date":"16 Mei 2023","externalUrl":null,"permalink":"/lainnya/lirik-lagu/","section":"Lainnyas","summary":"","title":"Lirik Lagu","type":"lainnya"},{"content":"Aplikasi yang bisa diinstal dan digunakan pada perangkat berbasis OS Android.\n","date":"16 Mei 2023","externalUrl":null,"permalink":"/portfolio/android/","section":"Portfolios","summary":"","title":"Portfolio Android","type":"portfolio"},{"content":"Desain UI/UX untuk website / aplikasi.\n","date":"16 Mei 2023","externalUrl":null,"permalink":"/portfolio/ui-ux/","section":"Portfolios","summary":"","title":"Portfolio UI/UX","type":"portfolio"},{"content":"Website atau aplikasi web yang bisa diakses via peramban web.\n","date":"16 Mei 2023","externalUrl":null,"permalink":"/portfolio/website/","section":"Portfolios","summary":"","title":"Portfolio Website","type":"portfolio"},{"content":"","date":"8 April 2023","externalUrl":null,"permalink":"/tags/j-pop/","section":"Tags","summary":"","title":"J-Pop","type":"tags"},{"content":"","date":"8 April 2023","externalUrl":null,"permalink":"/tags/laplacian/","section":"Tags","summary":"","title":"Laplacian","type":"tags"},{"content":"流れる雲間に　指先重ねて Nagareru kumomani yubisaki kasanete Between the flowing clouds, our fingertips intertwined\n隠したいつかと　同じ色の陽を Kakushita itsuka to onaji iro no hi wo Hiding the sun of the same color we wished to keep secret\n光を追いかけ　知らない街を Hikari wo oikake shiranai machi wo Chasing after the light, through unfamiliar streets\n独りで歩いた　また会う日夢見て Hitori de aruita mata au hi yume mite I walked alone, dreaming of the day we would meet again\n伝えたいよ　忘れられない Tsutaetai yo wasurarenai I want to convey it, the unforgettable\nあの景色も　今は瞳の奥に Ano keshiki mo ima wa hitomi no oku ni scenery, now deep within my eyes\n色褪せないように Iro asenai you ni So it won't fade away\n空を辿る路　足跡一つでも Sora wo tadoru michi ashiato hitotsu demo Even if just one footprint on the path across the sky\nこの時の果てに　あなたがもしいるなら Kono toki no hate ni anata ga moshi iru nara Leads me to you at the end of this moment\n少しだけでいい Sukoshi dake de ii Even if it's just a little.\n彷徨う私にも Samayou watashi ni mo To me, wandering aimlessly\n彼方から　風の歌を届けて Kanata kara kaze no uta wo todokete Please send a song of the wind from afar.\n儚く浮かんだ　白夜の月は Hakanaku ukanda hakuya no tsuki wa The faintly appearing moon in the white night\nあの日と同じで　影を追い続ける Ano hi to onaji de kage wo oitsuzukeru Continues to chase after shadows, just like that day\n教えて欲しいこの手の　ぬくもりの行方 Oshiete hoshii kono te no nukumori no yukue I want you to show me the whereabouts of this warmth in my hands\n逃がさないように　そっと握り締めた Nigasanaiyou ni sotto nigirishimeta So I gently hold onto it, not letting it escape\n空が歩み止め　明日が来なくても Sora ga ayumi tome ashita ga kowakutemo Even if the sky stops moving and tomorrow doesn't come\nこの時の果てで　あなたと繋がるなら Kono toki no hate de anata to tsunagaru nara If I can connect with you at the end of this moment\n今だけでいい　彷徨う私から Ima dakedeii samayou watashi kara Just for now, from this wandering me\n彼方までこの歌声　届いて Kanata made kono utagoe todoite Let the sound of my voice reach into the distance.\n空を辿る路　足跡一つでも Sora wo tadoru michi ashiato hitotsu demo On the path across the sky, even with just one footprint\nこの時がいつか　夕闇に飲み込まれても Kono toki ga itsuka yuuyami ni nomikomare te mo Even if this moment is swallowed by the twilight someday\n少しだけ彷徨う私にも Sukoshi dake samayou watashi ni mo Just a little, to me wandering aimlessly\n彼方から風の歌を　届けて Kanata kara kaze no uta wo todokete Please send a song of the wind from afar.\nListen on:\nYouTube Music Spotify Apple Music ","date":"8 April 2023","externalUrl":null,"permalink":"/lainnya/lirik-lagu/2023/laplacian-newton-and-the-apple-tree-kaze-no-uta/","section":"Lainnyas","summary":"","title":"Laplacian \"Newton and the Apple Tree\", MAMI - Kaze no Uta (風の歌) Japanese, Romaji, English","type":"lainnya"},{"content":"","date":"8 April 2023","externalUrl":null,"permalink":"/tags/lyrics/","section":"Tags","summary":"","title":"Lyrics","type":"tags"},{"content":"","date":"8 April 2023","externalUrl":null,"permalink":"/tags/mami/","section":"Tags","summary":"","title":"Mami","type":"tags"},{"content":"","date":"8 April 2023","externalUrl":null,"permalink":"/tags/newton-and-the-apple-tree/","section":"Tags","summary":"","title":"Newton and the Apple Tree","type":"tags"},{"content":"","date":"8 April 2023","externalUrl":null,"permalink":"/tags/official-hige-dandism/","section":"Tags","summary":"","title":"Official Hige Dandism","type":"tags"},{"content":"\u0026ldquo;Subtitle\u0026rdquo; is a Japanese song by the band Official Hige Dandism, released on their album \u0026ldquo;Traveler\u0026rdquo; in 2019. The song is a blend of pop, rock, and jazz genres, featuring upbeat instrumentation and catchy melodies.\nThe lyrics of \u0026ldquo;Subtitle\u0026rdquo; express the feeling of being lost in life and the search for one\u0026rsquo;s purpose. The protagonist of the song is depicted as feeling disconnected from the world around them, but ultimately finds hope and determination to move forward. The song has been well-received in Japan, becoming a popular hit and earning Official Hige Dandism a wider international audience.\nJapanese # 「凍りこおりついた心こころには太陽たいようを」　そして「僕ぼくが君きみにとってそのポジションを」\nそんなだいぶ傲慢ごおまんな思いおもい込みこみを拗こじらせてたんだよ\nごめんね (ごめんね)　笑わらって (笑わらって)　やって\n火傷やけどしそうなほどのポジティブの 冷たつめたさと残酷ざんこくさに気付きづいたんだよ\nきっと君きみに渡わたしたいものはもっとひんやり熱いあついもの\n綺麗事きれいごとじゃないけど綺麗きれいで揺ゆるぎないもの\nうわべよりも胸むねの奥おくの奥おくを温あたためるもの\n理想りそうだけはあるけど 心こころのどこ探さがしても まるで見みつからないんだよ\n伝つたえたい伝つたわらない その不条理ふじょうりが今いま キツく縛しばりつけるんだよ\n臆病おくびょうな僕ぼくの この一挙手一投足いっきょうしゅいっとうそくを\n言葉ことばはまるで雪ゆきの結晶けっしょう 君きみにプレゼントしたくても\n夢中むちゅになればなるほどに 形かたちは崩くずれ落おちて溶とけていって 消きえてしまうけど\nでも僕ぼくが選えらぶ言葉ことばが そこに託たくされた想おもいが\n君きみの胸むねを震ふるわすのを 諦あきらめられない 愛あいしてるよりも愛あいが届とどくまで\nもう少すこしだけ待まってて\n薄着うすぎでただそばに立たってても 不必要ふひつように汗あせをかいてしまう僕ぼくなんかもう\nどうしたって生温なまるくて君きみを痛いためつけてしまうのだろう\n「手てのひらが熱あついほど心こころは冷つめたいんでしょう?」\n冗談じょうだんでもそんな残酷ざんこくなこと言いわないでよ\n別べつに言いえばいいけど 全ゼン人生じんせいを賭かけても ちゃんと覆くつがえさしてよ\n救つくいたい 救つくわれたい このイコールが今いま 優やさしく剥はがしていくんだよ\n堅かたい理論りろん武装ぶそう プライドの過剰かじょう包装ほうそうを\n正ただしさよりも優やさしさが欲ほしい そしてそれを受うけ取とれるのは\nイルミネーションみたいな 不特定多数ふとくていたすうじゃなくてただ1人ひとり\n君きみであってほしい\nかけた言葉ことばで 割われたヒビを直なおそうとして\n足たしすぎた熱量ねつりょうで 引ひかれてしまったカーテン\nそんな失敗しっぱい作さくを 重かさねて 重かさねて 重かさねて\n見みつけたいんだいつか 最高さいこうの一言いちごん一句いっくを\n言葉ことばはまるで雪ゆきの結晶けしょう 君きみにプレゼントしたとして\n時間じかんが経たってしまえば大抵たいてい 記憶きおくから溢こぼれれ落おちて溶とけていって\n消きえてしまう でも\n絶たえず僕ぼくらのストーリーに 添そえられた字幕じまくのように\n思おもい返かえした時とき 不意ふいに目めをやる時ときに\n君きみの胸むねを震ふらわすもの 探さがし続つづけたい\n愛あいしてるよりも愛あいが届とどくまで\nもう少すこしだけ待まってて\n言葉ことばなど何なにも欲ほししくないほど 悲かなしみに凍いてつく夜よるでも\n勝手かってに君きみのそばで あれこれと考かんがえてる\n雪ゆきが溶とけても残のこってる\nRomaji # \u0026ldquo;Koori tsuita kokoro ni ha taiyou wo\u0026rdquo;, soshite \u0026ldquo;Boku ga kimi ni totte sono POJISHON wo\u0026rdquo;\nSonna daibu gooman na omoikomi wo kojirasete ta nda yo\nGomen ne (gomen ne), waratte (waratte), yatte\nYakedo shishou na hodo no POJITIBU no, tsumeta sa to zankoku sa ni kidzuita nda yo\nKitto kimi ni watashitai mono ha motto hinyari atsui mo no\nKireigoto janai kedo, kirei de yuruginai mo no\nUwabeyori mo mune no oku no oku wo atatameru mo no\nRisou dake ha aru kedo, kokoro no doko sagashitemo, marude mitsukaranai nda yo\nTsutaetai, tsutawaranai, sono fujouri ga ima, KITSU ku shibaritsukeru nda yo\nOkubyou na boku no, kono ikkyoshuittousoku wo\nKotoba ha maru de yuki no kesshou, kimi ni PUREZENTO shitakute mo\nMuchuu ni nareba naru hodo ni, katachi wa kuzure ochite tokete itte, kiete shimau kedo\nDemo boku ga erabu kotoba ga, soko ni takusareta omoi ga\nKimi no mune wo furuwasu no wo, akirame rarenai, aishiteru yori mo ai ga todoku made\nMou shukoshi dake matte te\nUsugide tada soba ni tattete mo, fuhitsuyou ni ase wo kaiteshimau boku nanka mou\nDoushitatte namarukute kimi wo itametsukete shimau no darou\n\u0026ldquo;Te no hiraga atsui hodo, kokoro ha tsumetai ndeshou?\u0026rdquo;\nJoudan de mo sonna zankoku na koto iwanaide yo\nBetsuni iebaii kedo, zen jinsei wo kakete mo, chanto kutsugaesa shite yo\nTsukuitai, tsukuwaretai, kono IKOORU ga ima, yasahiku hagashite ikunda yo\nKatai riron busou, PURAIDO no kajou housou wo\nTadashisa yori mo yasahisa ga hoshii, soshite sore wo uketoreru no ha\nIRUMINEESHON mitai na, futokuteitasuu janakute tada hitori\nKimi de atte hoshii\nKaketa kotoba de, wareta hibi wo naosou to shite\nTashi sugita netsuryou de, hikarete shimatta KAATEN\nSonna shippai saku wo, kasanete, kasanete, kasanete\nMitsuketai nda itsuka, saikou no ichigon ikku wo\nKotoba ha maru de yuki no keshou, kimi ni PUREZENTO shita toshite\nJikan ga tatteshimaeba taitei, kioku kara kobore ochite tokete ite\nKiete shimau demo\nTaezu boku ra no SUTOORI ni, soe rareta jimaku no you ni\nOmoi kaeshita toki, fui ni me wo yaru toki ni\nKimi no, mune wo furawasu mono, sagashi tsudzuketai\nAishiteru yori mo ai ga todoku made\nMou shukoshi dake matte te\nKotoba na do nani mo hoshikunai hodo, kanashimi ni itetsuku yoru demo\nKatte ni kimi no soba de, are kore to kangae teru\nYuki ga tokete mo, nokotteru\nTranslation # \u0026ldquo;Sun for a frozen heart\u0026rdquo; and \u0026ldquo;I want to be in that position for you\u0026rdquo;\nI complicated my strong arrogant assumption\nSorry, laugh at me\nI noticed a coldness and viciousness of burning positivity\nProbably something I want to give to you is a colder hot thing\nIt\u0026rsquo;s not fine-sounding talk but something beautiful and unwavering\nSomething warms up the core in the center of your heart, not just the surface\nI just have my ideal, but I can\u0026rsquo;t find it anywhere in my heart at all\nThis irrationality now binds everything that this coward guy like me does tightly\nI want to express but I fail to do so\nWords are now like crystals, even though I want to give you them as a gift\nThe more I get into you, the shapes collapses, melt away and disappear\nBut the words I choose and my feelings in them\nCannot give up on vibrating your heart. Instead of \u0026ldquo;I love you,\u0026rdquo;\nwait for a little bit for my love to reach you\nI get sweaty unnecessarily just by standing next to you while I\u0026rsquo;m dressed lightly\nAnything I do is lukewarm and I end up hurting you\n\u0026ldquo;A person with hotter palms has a colder heart, right?\u0026rdquo;\nYou may be joking, but don\u0026rsquo;t say such a cruel thing\nYou can say it, but let me definitely overturn what you said, even if I need to bet my whole life on it\nI want to save = I want to be saved, this equal sign now peels off\ngently both inflexible theoretical arguments and overwrapping for pride\nI want kindness more than correctness, and unlike\nthe illumination for the crowds, I want you to be only\nperson who receives it\nI tried to to fix the broken cracks with my broken words\nBut my excessive energy pushed you to pull a curtain\nAfter repeating my failures, over and over\nI want to find the best words and phrases it one day\nWords are now like crystals, even though I gave you them as a gift\nUsually, as time passes, memories overflow and melt away\nfrom your memories.\nBut like the Subtitles that are always added to our stories\nWhen we remember, when we abruptly take a quick glance\nI want to keep looking for things that vibrate your heart\nInstead of saying \u0026ldquo;I love you\u0026rdquo; to reach you\nPlease wait a little bit more\nEven on frozen nights by sadness when you do not want any words\nI selfishly think about many things by your side\nThey remain even after the snow melts away\nListen on:\nYouTube Music Spotify Apple Music ","date":"8 April 2023","externalUrl":null,"permalink":"/lainnya/lirik-lagu/official-hige-dandism-subtitle/","section":"Lainnyas","summary":"","title":"Official Hige Dandism - Subtitle (Romaji, Furigana, and Translation)","type":"lainnya"},{"content":"PT. Bengkel Web Indonesia built the Hukum Waris app as a Commercial app. This SERVICE is provided by PT. Bengkel Web Indonesia and is intended for use as is.\nThis page is used to inform visitors regarding our policies with the collection, use, and disclosure of Personal Information if anyone decided to use our Service.\nIf you choose to use our Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that we collect is used for providing and improving the Service. We will not use or share your information with anyone except as described in this Privacy Policy.\nThe terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which are accessible at Hukum Waris unless otherwise defined in this Privacy Policy.\nInformation Collection and Use # For a better experience, while using our Service, we may require you to provide us with certain personally identifiable information. The information that we request will be retained by us and used as described in this privacy policy.\nThe app does use third-party services that may collect information used to identify you.\nLink to the privacy policy of third-party service providers used by the app\nGoogle Play Services AdMob Google Analytics for Firebase Firebase Crashlytics Log Data # We want to inform you that whenever you use our Service, in a case of an error in the app we collect data and information (through third-party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing our Service, the time and date of your use of the Service, and other statistics.\nCookies # Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device\u0026rsquo;s internal memory.\nThis Service does not use these “cookies” explicitly. However, the app may use third-party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service.\nService Providers # We may employ third-party companies and individuals due to the following reasons:\nTo facilitate our Service; To provide the Service on our behalf; To perform Service-related services; or To assist us in analyzing how our Service is used. We want to inform users of this Service that these third parties have access to their Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose.\nSecurity # We value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and we cannot guarantee its absolute security.\nLinks to Other Sites # This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by us. Therefore, we strongly advise you to review the Privacy Policy of these websites. We have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.\nChildren’s Privacy # These Services do not address anyone under the age of 13. We do not knowingly collect personally identifiable information from children under 13 years of age. In the case we discover that a child under 13 has provided us with personal information, we immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact us so that we will be able to do the necessary actions.\nChanges to This Privacy Policy # We may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. We will notify you of any changes by posting the new Privacy Policy on this page.\nThis policy is effective as of 2022-08-16\nContact Us # If you have any questions or suggestions about our Privacy Policy, do not hesitate to contact us at yourlovelydev@gmail.com.\n","date":"17 Maret 2023","externalUrl":null,"permalink":"/privacy-policy/hawada/","section":"Privacy-Policies","summary":"","title":"Hawada (Hukum Waris Perdata) Privacy Policy","type":"privacy-policy"},{"content":"","date":"17 Maret 2023","externalUrl":null,"permalink":"/privacy-policy/","section":"Privacy-Policies","summary":"","title":"Privacy-Policies","type":"privacy-policy"},{"content":"","date":"15 Maret 2023","externalUrl":null,"permalink":"/tags/php/","section":"Tags","summary":"","title":"Php","type":"tags"},{"content":"","date":"15 Maret 2023","externalUrl":null,"permalink":"/tags/regex/","section":"Tags","summary":"","title":"Regex","type":"tags"},{"content":"","date":"15 Maret 2023","externalUrl":null,"permalink":"/series/whatsapp-text-formatting-pada-php/","section":"Series","summary":"","title":"WhatsApp Text Formatting Pada PHP","type":"series"},{"content":"Selamat datang di Part 3 dari tutorial ini. Kita lanjut membahas tentang mengubah UTF-16 dan UTF-32 ke bentuk UTF-8 HTML Entity yang bisa dibaca oleh browser.\n3. Unicode UTF-16 dan UTF-32 # Sebagai pengingat, saya berhasil menemukan pola RegEx yang tepat untuk pola UTF-16 dan UTF-32 yaitu /(\\\\\\(u|U)[a-fA-F0-9]{4,8})/. Masalah selanjutnya adalah bagaimana cara mengubah kode unicode di bawah menjadi HTML Entity:\nMenggunakan fungsi preg_replace() secara langsung seperti ini tidak akan berhasil karena kita hanya menambah #\u0026amp;x di awal dan ; di akhir.\n\u0026lt;?php namespace App\\Services; class WhatsAppService { public static function format_message(string $raw_message): string { $nl2br_message = nl2br($raw_message); $bold = preg_replace(\u0026#39;/\\*(.*?)\\*/\u0026#39;, \u0026#39;\u0026lt;b\u0026gt;$1\u0026lt;/b\u0026gt;\u0026#39;, $nl2br_message); $italic = preg_replace(\u0026#39;/\\_(.*?)\\_/\u0026#39;, \u0026#39;\u0026lt;i\u0026gt;$1\u0026lt;/i\u0026gt;\u0026#39;, $bold); $strikethrough = preg_replace(\u0026#39;/\\~(.*?)\\~/\u0026#39;, \u0026#39;\u0026lt;strike\u0026gt;$1\u0026lt;/strike\u0026gt;\u0026#39;, $italic); $monospace = preg_replace(\u0026#39;/\\```(.*?)\\```/\u0026#39;, \u0026#39;\u0026lt;code\u0026gt;$1\u0026lt;/code\u0026gt;\u0026#39;, $strikethrough); $unicode = preg_replace(\u0026#39;/(\\\\\\(u|U)[a-fA-F0-9]{4,8})/\u0026#39;, \u0026#39;#\u0026amp;x$1;\u0026#39;, $monospace); return $unicode; } } Salah total karena jika dilihat, hasilnya akan seperti ini:\nDead end. Saya stuck. Tidak bisa berpikir lebih jauh. Saya bangkit dari workspace, jalan-jalan sedikit, nyeruput kopi yang sudah agak dingin, ambil nafas, lalu lanjut ngoding lagi.\nPermasalahan di atas membuat saya berpikir \u0026ldquo;pola yang saya buat harus menemukan kata-kata yang sudah dispesifikasikan, lalu setiap kata-kata itu saya hapus \\u-nya, lalu tambahkan #\u0026amp;x di awal dan ; di akhir\u0026rdquo;.\nSetelah browsing sejenak, saya menemukan fungsi ajaib, preg_replace_callback(). Langsung saya coba implementasikan ke WhatsAppService, hasilnya adalah sebagai berikut:\n\u0026lt;?php namespace App\\Services; class WhatsAppService { public static function format_message(string $raw_message): string { $nl2br_message = nl2br($raw_message); $bold = preg_replace(\u0026#39;/\\*(.*?)\\*/\u0026#39;, \u0026#39;\u0026lt;b\u0026gt;$1\u0026lt;/b\u0026gt;\u0026#39;, $nl2br_message); $italic = preg_replace(\u0026#39;/\\_(.*?)\\_/\u0026#39;, \u0026#39;\u0026lt;i\u0026gt;$1\u0026lt;/i\u0026gt;\u0026#39;, $bold); $strikethrough = preg_replace(\u0026#39;/\\~(.*?)\\~/\u0026#39;, \u0026#39;\u0026lt;strike\u0026gt;$1\u0026lt;/strike\u0026gt;\u0026#39;, $italic); $monospace = preg_replace(\u0026#39;/\\```(.*?)\\```/\u0026#39;, \u0026#39;\u0026lt;code\u0026gt;$1\u0026lt;/code\u0026gt;\u0026#39;, $strikethrough); $unicode = preg_replace_callback( [\u0026#39;/(\\\\\\(u|U)[a-fA-F0-9]{4,8})/\u0026#39;], function ($matches) { $code = preg_replace(\u0026#39;/\\\\\\u|\\\\\\U/\u0026#39;, \u0026#39;\u0026#39;, $matches[0]); return \u0026#34;\u0026amp;#x$code;\u0026#34;; }, $monospace ); return $unicode; } } Tebak apa yang terjadi? SUKSES BESAR!\nSaya bisa istirahat untuk sejenak. Menghabiskan kopi yang sudah dingin. Lalu rebahan, meluruskan punggung. Tidak berhenti sampai situ, ada 1 masalah lagi yang belum diselesaikan yaitu\n4. Link yang tidak bisa diklik layaknya WhatsApp # Lihat pesan di atas, lalu lihat pada link YouTube-nya, masih berbentuk plain text, dan tidak bisa diklik. Simpel saja, caranya sama seperti formatting pesan pada Part 2, langsung wrap \u0026ldquo;Pola Link\u0026rdquo; pada sebuah tag \u0026lt;a\u0026gt;.\nKarena waktu sudah larut malam, dan banyak sekali kasus RegEx untuk sebuah link, maka saya browsing dan menemukan pola RegEx berikut pada StackOverflow, berikut adalah polanya:\n/(http(s)?:\\/\\/.)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#\\=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?\u0026amp;\\/\\=]*)/. Sedikit penjelasan:\n/(http(s)?:\\/\\/.)?(www\\.) mencari apakah pesan tersebut dimulai dengan http://, https://, atau langsung www.. (www\\.)?[-a-zA-Z0-9@:%._\\+~#\\=]{2,256}\\.[a-z]{2,6} mencari apakah pola www. dilanjutkan dengan karakter URL yang valid dengan rentang 2 sampai dengan 256 karakter. Dilanjutkan dengan tld yang memiliki rentang 2 sampai dengan 6 karakter. \\b([-a-zA-Z0-9@:%_\\+.~#?\u0026amp;\\/\\=]*)/ mencari pola URL yang valid. Hasil dari keseluruhan kode adalah sebagai berikut:\n\u0026lt;?php namespace App\\Services; class WhatsAppService { public static function format_message(string $raw_message): string { $nl2br_message = nl2br($raw_message); $bold = preg_replace(\u0026#39;/\\*(.*?)\\*/\u0026#39;, \u0026#39;\u0026lt;b\u0026gt;$1\u0026lt;/b\u0026gt;\u0026#39;, $nl2br_message); $italic = preg_replace(\u0026#39;/\\_(.*?)\\_/\u0026#39;, \u0026#39;\u0026lt;i\u0026gt;$1\u0026lt;/i\u0026gt;\u0026#39;, $bold); $strikethrough = preg_replace(\u0026#39;/\\~(.*?)\\~/\u0026#39;, \u0026#39;\u0026lt;strike\u0026gt;$1\u0026lt;/strike\u0026gt;\u0026#39;, $italic); $monospace = preg_replace(\u0026#39;/\\```(.*?)\\```/\u0026#39;, \u0026#39;\u0026lt;code\u0026gt;$1\u0026lt;/code\u0026gt;\u0026#39;, $strikethrough); $url = preg_replace( \u0026#39;/(http(s)?:\\/\\/.)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#\\=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?\u0026amp;\\/\\=]*)/\u0026#39;, \u0026#39;\u0026lt;a class=\u0026#34;text-blue-500\u0026#34; href=\u0026#34;$0\u0026#34; target=\u0026#34;_blank\u0026#34;\u0026gt;$0\u0026lt;/a\u0026gt;\u0026#39;, $monospace ); $unicode = preg_replace_callback( [\u0026#39;/(\\\\\\(u|U)[a-fA-F0-9]{4,8})/\u0026#39;], function ($matches) { $code = preg_replace(\u0026#39;/\\\\\\u|\\\\\\U/\u0026#39;, \u0026#39;\u0026#39;, $matches[0]); return \u0026#34;\u0026amp;#x$code;\u0026#34;; }, $url ); return $unicode; } } \u0026hellip;dan ta-da! Hasilnya adalah seperti ini:\nKesimpulan # Ekspresi Regular atau RegEx, menurut saya adalah sebuah aspek dalam pemrograman yang sangat kuat dan potensial. RegEx dapat membantu teman-teman dalam memecahkan masalah yang sangat spesifik seperti yang saya hadapi sekarang.\nTerlepas dari sulitnya belajar RegEx, saya terbebas dari kemungkinan bahwa saya akan menulis ratusan baris kode demi membuat sebuah Formatter untuk pesan WhatsApp. Selain itu, saya juga terlepas dari kemungkinan waktu pengembangan yang panjang karena ketidaktahuan saya.\nIntinya, belajar dan terus belajar. Kita tidak akan tahu ilmu tersebut berguna atau tidak sampai kita bisa menyelesaikan suatu permasalahan dengan ilmu yang kita dapatkan.\nKode lengkap bisa Anda dapatkan pada repository saya. Terima kasih banyak sudah membaca 👋!\n#StayCode #CoffeeIsMyInspiration #Coffee24/7.\nThumbnail oleh Asterfolio dari Unsplash\n","date":"15 Maret 2023","externalUrl":null,"permalink":"/artikel/2023/03/15/whatsapp-text-formatting-pada-php-part-3/","section":"Artikel-artikel","summary":"","title":"WhatsApp Text Formatting Pada PHP Part 3","type":"artikel"},{"content":"Pada pengembangan aplikasi Dumas Presisi, sebuah projek aplikasi yang saya handle, pengimplementasian komunikasi WhatsApp melalui Website adalah salah satu fitur yang sudah lama kami ingin implementasikan. Namun karena kurang luasnya pengalaman dan penguasaan ilmu yang kami miliki, setelah 2 tahun projek ini di-launching oleh Kapolri pada hari Rabu, 24 Februari 2021, kami baru bisa mengimplementasikan fitur ini sebagai sarana konsultasi Masyarakat.\nProses pengintegrasian kami rasa cukup rumit karena adanya beberapa gateway yang harus kami untuk sarana analisis dan debugging. Jika dihitung berdasarkan hari, kami baru bisa mengimplementasikan fitur ini setelah 1 bulan pengembangan.\nPrototyping # Dibantu oleh TailwindCSS dan DaisyUI, kami bisa mengimplementasikan mockup fitur ini secara mudah. Apalagi dengan pembaharuan DaisyUI v2.51.4, penambahan fitur Chat Bubble sangat membantu kecepatan pembuatan mockup ini. Berikut adalah screenshot mockup pertama kami:\nSangat simpel bukan? Belum ada yang istimewa saat ini. Sebagai informasi tambahan, fitur ini dipresentasikan keesokan harinya kepada pimpinan. Tentunya kami berharap adanya masukkan dan kritik dari pimpinan terhadap fitur yang kami bangun.\nSemua berjalan lancar\u0026hellip; sampai hal yang tidak kami duga pun terjadi.\nPermasalahan # Issue yang sama sekali tidak kami duga karena tidak muncul pada saat testing, muncul pada saat presentasi. Tidak ada yang \u0026ldquo;terlukai\u0026rdquo; oleh bug ini, namun kami harus menyelesaikannya secepat mungkin.\n\u0026ldquo;Apa sih masalahnya sehingga kami merasa issue tersebut harus bisa diselesaikan? Toh kan cuma plain text yang di-render?\u0026rdquo;\nHaha, pada permukaan, betul yang kami render ini adalah plain text, namun jika plain text-nya terlihat seperti ini, apakah enak dipandang mata?\nHotline WhatsApp Dumas Presisi adalah sama seperti nomor WhatsApp pada umumnya, kami menerima keluhan, saran, bahkan broadcast yang secara random dikirimkan oleh masyarakat. Jadi, seluruh pesan yang dikirimkan kepada Hotline kami, akan kami terima apa adanya juga pada aplikasi.\nSetelah membaca sekilas pesan di atas, kami memetakan ada 4 permasalahan:\nLine break Formatting Emoji Link Mari kita selesaikan permasalahan ini satu per satu 🔥.\n1. Line Break yang tidak ter-render # Di PHP, untuk pesan plain text seperti ini, mudah bagi kita para pengembang untuk mengubah New Line menjadi Break Line yang bisa di-render HTML. Fungsi tersebut adalah nl2br(), yang merupakan kependekan dari New line to \u0026lt;br/\u0026gt;.\nPertama, saya membuat sebuah Service yang bernamakan WhatsAppService, kurang lebih, seperti ini file-nya:\n\u0026lt;?php namespace App\\Services; class WhatsAppService { public static function formatMessage(string $raw_message): string { return $raw_message; } } Service di atas masih belum bisa mengubah New Line menjadi \u0026lt;br/\u0026gt;, karena itu kita gunakan fungsi nl2br supaya pesan yang kita terima bisa terformat dengan baik:\n\u0026lt;?php namespace App\\Services; class WhatsAppService { public static function format_message(string $raw_message): string { $nl2br_message = nl2br($raw_message); return $nl2br_message; } } Setelah menerapkan fungsi nl2br, berikut adalah hasil dari pesan yang sudah diformat:\n2. Formatting yang tidak ter-render secara langsung # Dilansir dari halaman FAQ WhatsApp terkait dengan Text Formatting, kami mendapatkan 4 macam formatting yang harus diimplementasikan:\nBold, direpresentasikan dengan string yang diapit oleh bintang ( *string* ) Italic, direpresentasikan dengan string yang diapit oleh garis bawah ( _string_ ) Strikethrough, direpresentasikan dengan string yang diapit oleh tilde ( ~string~ ) Monospace, direpresentasikan dengan string yang diapit oleh tiga backtick ( ```string``` ) Saya berpikir, bagaimana caranya untuk me-replace simbol-simbol itu dengan tag HTMl ya? Beberapa metode terlintas di otak saya.\nPertama, saya bisa menggunakan str_replace() untuk mengubah simbol menjadi tag HTML. Namun saya juga ingat bahwa tag HTML haruslah ditutup. Berdasarkan fakta tersebut, saya tidak bisa semerta-merta menggunakan str_replace().\nKedua, saya bisa menggunakan preg_match() untuk mengambil seluruh simbol, lalu mengubahnya menjadi tag HTML. Sama seperti kasus di atas, tag HTML yang di-replace harus juga ditutup kembali. Selain itu, masalah yang akan dihadapi pada saat saya menggunakan preg_match() ialah fungsi tersebut hanya dapat me-return int yang mengindikasikan berapa banyak simbol yang ditemukan pada sebuah pesan.\nApproach yang saya lakukan menggunakan RegEx sudah benar, cuma saja saya hanya perlu menemukan fungsi yang tepat. Setelah berdiri sejenak, menyeduh kopi, dan meminumnya, saya menemukan sebuah solusi yang mujarab, preg_replace(). Long life coffee, our source of motivation ☕️☕️☕️.\npreg_replace() adalah sebuah fungsi yang mencari sebuah pola berdasarkan bahasa ekspresi regular yang kita berikan, mendapatkan kata-kata berdasarkan pola yang kita berikan, lalu mengubahnya sesuai dengan apa yang kita inginkan.\nEkspresi Regular atau yang biasa sering kita sebut sebagai RegEx adalah bahasa ter-alien yang pernah saya pelajari. Bukan hanya sulit dipahami, namun juga kita harus memahami beberapa aturan baku. Saya pribadi baru bisa memahami bagaimana RegEx bekerja setelah belajar intensif selama 1 bulan.\nPenasaran bagaimana saya mengimplementasikannya? Kita lanjut ke Part 2 ya! Sampai jumpa di sana 👋.\nThumbnail oleh Asterfolio dari Unsplash\n","date":"15 Maret 2023","externalUrl":null,"permalink":"/artikel/2023/03/15/whatsapp-text-formatting-pada-php-part-1/","section":"Artikel-artikel","summary":"","title":"WhatsApp Text Formatting Pada PHP Part 1","type":"artikel"},{"content":"Selamat datang di Part 2 dari tutorial ini. Kita lanjut membahas tentang pengimplementasian formatting menggunakan RegEx.\n2. Formatting yang tidak ter-render secara langsung # Sebagai pengingat, kita butuh 4 macam formatting yang harus diimplementasikan:\nBold, direpresentasikan dengan string yang diapit oleh bintang ( *string* ) Italic, direpresentasikan dengan string yang diapit oleh garis bawah ( _string_ ) Strikethrough, direpresentasikan dengan string yang diapit oleh tilde ( ~string~ ) Monospace, direpresentasikan dengan string yang diapit oleh tiga backtick ( ```string``` ) Pada RegEx, kita harus bisa menerjemahkan sebuah pola yang ada ke dalam bahasa RegEx. Seperti contoh di bawah ini adalah sebuah pola RegEx yang digunakan untuk mendapatkan kata atau kalimat yang diapit oleh bintang, RegEx ini akan kita gunakan untuk formatting bold:\n/\\*(.*?)\\*/ - Pola Ekspresi Regular untuk formatting bold pada WhatsApp. Percaya atau tidak, itu adalah pola yang valid. Bagaimana cara membacanya? Akan saya jelaskan secara singkat.\n/ garis miring yang ada di awal dan di akhir pola RegEx disebut sebagai Delimiter. Delimiter ini berfungsi untuk membatasi scope dari pola RegEx yang akan dibaca; \\* setelah garis miring di awal dan sebelum garis miring di akhir menandakan bahwa ada simbol \u0026ldquo;bintang\u0026rdquo; di awal dan di akhir kata atau kalimat; () dalam kurung berfungsi sebagai penanda \u0026ldquo;grup\u0026rdquo;. Grup pada RegEx berfungsi untuk membuat sebuah \u0026ldquo;capture group\u0026rdquo; untuk mengekstrak sebuah substring; . dalam RegEx PHP berarti mencari semua karakter, entah itu string, integer, atau simbol, kecuali line break; * berarti mencocokkan 0 atau lebih karakter yang ada pada pola; serta ? adalah lazy, yang berarti RegEx akan mencari karakter sesedikit mungkin. Secara default, quantifier RegEx bersifat greedy yang berarti mencari karakter sebanyak mungkin. Bagaimana, pusing 😅? Demikian juga saya yang mencoba untuk membuat fitur ini. Namun karena pengalaman, hal seperti ini berhasil saya capai dengan cepat. Berikut adalah daftar RegEx yang berhasil saya kompulir untuk formatting pesan WhatsApp:\nTipe format Pola RegEx Bold /\\*(.*?)\\*/ Italic /\\_(.*?)\\_/ Strikethrough /\\~(.*?)\\~/ Monospace /\\```(.*?)\\```/ Setelah mendapatkan pola-pola tersebut, saya implementasikan ke dalam WhatsAppService:\n\u0026lt;?php namespace App\\Services; class WhatsAppService { public static function format_message(string $raw_message): string { $nl2br_message = nl2br($raw_message); $bold = preg_replace(\u0026#39;/\\*(.*?)\\*/\u0026#39;, \u0026#39;\u0026lt;b\u0026gt;$1\u0026lt;/b\u0026gt;\u0026#39;, $nl2br_message); $italic = preg_replace(\u0026#39;/\\_(.*?)\\_/\u0026#39;, \u0026#39;\u0026lt;i\u0026gt;$1\u0026lt;/i\u0026gt;\u0026#39;, $bold); $strikethrough = preg_replace(\u0026#39;/\\~(.*?)\\~/\u0026#39;, \u0026#39;\u0026lt;strike\u0026gt;$1\u0026lt;/strike\u0026gt;\u0026#39;, $italic); $monospace = preg_replace(\u0026#39;/\\```(.*?)\\```/\u0026#39;, \u0026#39;\u0026lt;code\u0026gt;$1\u0026lt;/code\u0026gt;\u0026#39;, $strikethrough); return $monospace; } } Kita lihat hasilnya pada aplikasi\n\u0026hellip;daaan berhasil. Sebuah pencapaian yang menurut saya luar biasa keren. Suatu langkah untuk sebuah aplikasi yang lebih sempurna.\n3. Unicode UTF-16 dan UTF-32 # Unicode yang saya lansir dari Wikipedia adalah sebuah standar teknis yang dirancang untuk mengizinkan teks dan simbol dari semua sistem tulisan di dunia untuk ditampilkan dan dimanipulasi. Namun, tidak semua Unicode yang dikirimkan bisa di-parse dan di-render begitu saja, harus ada tahap konversi lagi sehingga karakter yang diinginkan bisa ditampilkan.\nUTF-8 adalah standar encoding yang digunakan pada kebanyakan website dan browser. Secara umum, browser dan search engine akan gagal untuk memproses UTF-16, karenanya UTF-16 tidak digunakan pada teknologi website.\nOke, langsung saja ke permasalahan utamanya. Diberikan contoh kasus seperti ini:\nBerdasarkan dari pengalaman sebelumnya, saya memikirkan skenario berikut:\nHilangkan \\u dan \\U pada plain teks; lalu Tampilkan hasil teks dari unicode tersebut ke bentuk HTML dengan me-wrap hasil penggantian polanya dengan \u0026amp;#$1; Awalnya, saya menggunakan RegEx sebagai berikut /(\\\\\\(u|U)[a-zA-Z0-9])/, namun setelah saya coba mengecek bagaimana pola ini bekerja di Regexr.com, ternyata terdapat kesalahan pada pola yang saya buat:\nKarena pada awalnya saya mengira ini adalah emoji, maka saya mengambil salah satu unicode acak 0001f64f, lalu mencoba mencarinya di Google. Berikut adalah screenshot yang saya ambil dari laman fileformat.com\n\u0026ldquo;Betul saja, yang saya dapatkan adalah emoji!\u0026rdquo; ucap saya dalam hati. Setelah berhasil mengidentifikasi unicode tersebut adalah sebuah emoji yang dikonversi dari UTF-32, saya harus mengubahnya ke dalam bentuk UTF-8, atau langsung sebagai Hexadecimal HTML Entity. Saya pun merubah pola RegEx yang sudah saya buat menjadi seperti ini\n- /(\\\\\\(u|U)[a-zA-Z0-9])/ + /(\\\\\\(u|U)[a-zA-Z0-9]{4,8})/ Ya, saya hanya menambahkan prasyarat bahwa karakter unicode yang harus saya ambil harus memiliki panjang 4 s/d 8 karakter. Namun apa yang saya dapat? Kesalahan pola, lagi 😅.\nJika teman-teman lihat hasil match yang ada pada kalimat pertama, pola RegEx yang saya buat mengambil \\u2728RIZK. RIZK adalah pola yang tidak ingin saya ambil. Bagaimanakah solusinya?\nSimpelnya, saya ubah range huruf yang saya cari. Cukup dari a-f dan A-F.\n- /(\\\\\\(u|U)[a-zA-Z0-9]{4,8})/ + /(\\\\\\(u|U)[a-fA-F0-9]{4,8})/ Saya pun berhasil mendapatkan pola RegEx yang sesuai dan bisa diimplementasikan ke aplikasi:\nJadi, pola RegEx yang \u0026ldquo;Fix\u0026rdquo; adalah seperti ini /(\\\\\\(u|U)[a-fA-F0-9]{4,8})/. Sedikit penjelasan:\n/ garis miring yang ada di awal dan di akhir pola RegEx disebut sebagai Delimiter. Delimiter ini berfungsi untuk membatasi scope dari pola RegEx yang akan dibaca; \\\\\\ tiga backslash digunakan untuk meng-escape karakter backslash pada PHP; () dalam kurung berfungsi sebagai penanda \u0026ldquo;grup\u0026rdquo;. Grup pada RegEx berfungsi untuk membuat sebuah \u0026ldquo;capture group\u0026rdquo; untuk mengekstrak sebuah substring. Grup pada RegEx di atas berisi (u|U) yang berarti kita menerima huruf u kecil atau U kapital setelah backslash; dilanjutkan dengan [a-fA-F0-9] yang berarti kita menerima huruf kecil a sampai dengan f, huruf A sampai dengan F kapital, lalu angka 0 sampai dengan 9. {4,8} berarti kita menerima karakter apapun yang ada pada prasyarat sebelumnya yaitu [a-fA-F0-9] dengan panjang sebanyak 4 sampai dengan 8 karakter. Begitulah cerita bagaimana saya mendapatkan pola UTF-16 dan UTF-32. Lalu bagaimana cara saya mengubah pola yang sudah didapatkan supaya bisa ditampilkan dalam bentuk HTML? Kita lanjut ke Part 3, sampai jumpa di sana 👋!\nThumbnail oleh Asterfolio dari Unsplash\n","date":"15 Maret 2023","externalUrl":null,"permalink":"/artikel/2023/03/15/whatsapp-text-formatting-pada-php-part-2/","section":"Artikel-artikel","summary":"","title":"WhatsApp Text Formatting Pada PHP Part 2","type":"artikel"},{"content":"Situasi ini dimulai ketika saya berencana untuk melakukan git pull pada sebuah proyek di server CentOS 7. Namun, berbeda dengan pengalaman saya sebelumnya, respon yang muncul adalah sebagai berikut:\nssh: Could not resolve hostname github.com: Temporary failure in name resolution fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. Sangat mengherankan, karena sebelumnya saya selalu bisa melakukan pull tanpa masalah apapun. Setelah berkonsultasi dengan seseorang yang ahli dalam menangani Server, solusinya cukup sederhana, yaitu dengan mengedit file /etc/resolv.conf dan menambahkan dua baris berikut:\nnameserver 8.8.8.8 nameserver 8.8.4.4 Jika Anda masih baru dalam menggunakan Linux, Anda dapat menggunakan perintah nano /etc/resolv.conf atau vi /etc/resolv.conf untuk mengedit file tersebut. Namun, perlu diingat bahwa cara penggunaan kedua text editor tersebut berbeda.\nSebagai tambahan informasi, jika Anda tidak memiliki akses untuk mengedit file tersebut, Anda bisa menambahkan perintah pkexec sebelum perintah nano atau vi. Dalam hal ini, Anda akan diminta untuk memasukkan password root untuk dapat mengedit file resolv.conf.\nSekian tutorial singkat sekaligus catatan pengembangan saya, semoga bermanfaat dan membantu teman-teman sekalian.\nThumbnail oleh Erik Mclean dari Unsplash\n","date":"23 Februari 2023","externalUrl":null,"permalink":"/artikel/2023/02/cara-mengatasi-could-not-resolve-hostname-github-com-pada-linux/","section":"Artikel-artikel","summary":"","title":"Cara Mengatasi Could Not Resolve Hostname Github Com Pada Linux","type":"artikel"},{"content":"","date":"23 Februari 2023","externalUrl":null,"permalink":"/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":"","date":"23 Februari 2023","externalUrl":null,"permalink":"/tags/tips/","section":"Tags","summary":"","title":"Tips","type":"tags"},{"content":"","date":"10 Februari 2023","externalUrl":null,"permalink":"/tags/blog/","section":"Tags","summary":"","title":"Blog","type":"tags"},{"content":"Pyramid Automation Testing, dilansir dari buku The Clean Coder yang ditulis oleh Robert C. Martin, adalah sebuah gambaran tentang jenis-jenis Test yang dibutuhkan oleh organisasi pengembang profesional.\nAda 5 bagian dari piramid ini yang masing-masing membutuhkan setidaknya persentase coverage dari setiap Test. Berikut adalah daftarnya:\nExploratory Test - 5% System Test - 10% Integration Test - 20% Component Test - 50% Unit Test - 100% Unit Test # Pada bagian ini, Test dengan bahasa pemrograman yang digunakan untuk membangun sistem ditulis oleh programmer, dan kembali untuk programmer. Test yang ditulis harus bisa menspesifikasikan kebutuhan kode pada level yang paling rendah, jika memungkinkan pada level abstraksi.\nUnit Test ini biasanya ditulis terlebih dahulu sebelum membuat kode production yang nantinya akan dijalankan secara otomatis oleh Continuous Integration sebelum dikirimkan ke production.\nUnit Test harus mengcover keseluruhan sistem paling sedikit 90%. Robert mengatakan bahwa 100% adalah hal yng mustahil, namun setidaknya kita mencapai titik asimptotik mendekati 100%.\nComponent Test # Component Test adalah Test yang ditulis oleh Quality Assurance dan Business Analyst, dengan bantuan programmer. Test ini harus bisa dimengerti oleh seluruh Stakeholder, QA, BA, serta programmer yang menulis Test atau yang tidak menulisnya.\nTest ini setidaknya meng-cover 50% dari sistem. Sifat dari Test ini lebih mencerminkan pada happy-path daripada unhappy-path.\nHappy path =\u0026gt; Mencerminkan bagaimana komponen itu berfungsi selayaknya, biasanya ditulis oleh BA atau Stakeholder yang menginginkan bagaimana komponen itu berjalan sesuai keinginan.\nUn-happy path =\u0026gt; Skenario-skenario terburuk yang mungkin terjadi pada komponen yang akan ditest, contohnya kemungkinan error yang akan terjadi, Exception yang akan dilempar. Un-happy path biasanya digunakan pada Test-Driven Development dan Behavioural-Driven Development.\nIntegration Test # Integration Test adalah tipe Test yang akan sangat berarti pada sistem yang lebih besar, yang memiliki banyak komponen. Testing ini akan menghimpun beberapa kopmonen sekaligus, dan mengecek kesesuaian komunikasi antar komponen. Test ini biasanya ditulis oleh System Architect atau Lead Designer.\nTest ini sering disebut juga sebagai Choreography Test, karena menggambarkan \u0026ldquo;bagaimana setiap komponen berdansa seirama dengan komponen lainnya\u0026rdquo;.\nTest ini tidak mengetes alur bisnis sama sekali, hanya mengetes komunikasi antar komponen. Biasanya, Integration Test tidak akan dijalankan bersamaan dengan CI, namun dijalankan setiap malam, atau mingguan karena cukup memakan waktu yang lama. Pada level ini, kita mengetes performa dan throughput dari sistem.\nSystem Test # Test tipe ini adalah tipe test yang terotomatisasi yang mengecek seluruh sistem secara terintegrasi. Test tipe ini tidak mengecek alur bisnis secara langsung, namun mengetes apakah sistem sudah disambung bersamaan dengan benar. Kita mengetest performa dan throughput dari sistem.\nTest ini ditulis oleh System Architect dan Technical Lead, yang biasanya ditulis dengan bahasa dan environment yang sama dengan Integration Test untuk UI. Test ini jarang dijalankan, karena durasi testingnya yang lama.\nSystem Test setidaknya meng-cover 10% dari sistem. Tujuan dari Test ini hanya memastikan susunan sistemnya, bukan perilaku sistemnya. Asumsimnya adalah testing perilaku sudah dipastikan ada pada Unit Test dan Component Test.\nManual Exploratory Test # Disini adalah bagian dimana manusia atau pengguna terlibat langsung. Karena kita sebagai pengembang tidak bisa menulis Test untuk keseluruhan sistem secara sempurna, maka dibutuhkanlah peran manusia.\nManusia adalah makhluk hidup kreatif yang akan selalu berfikir untuk merusak sebuah sistem yang dirancang secara sistematis. Kreatifitas manusia inilah yang akan digunakan untuk menjelajahi bagaimana perilaku sistem yang \u0026ldquo;seharusnya\u0026rdquo; terjadi, sesuai dengan ekspektasi mereka.\nBagian ini bukanlah Test yang diotomasi, namun lebih ke diskenariokan, yang dieksekusi secara manual, oleh masing-masing individu dengan gaya penggunaan yang unik. Secara spesifik, pekerjaan pada bidang ini adalah Bug Hunter.\nSeluruh bug yang ditemukan pada level ini akan ditulis kembali pada Unit Test, Component Test, serta Integration Test untuk memastikan bug yang sudah ditemukan tidak akan terjadi lagi di kemudian hari.\n","date":"10 Februari 2023","externalUrl":null,"permalink":"/artikel/2022/02/10/mengenal-testing-automation-pyramid/","section":"Artikel-artikel","summary":"","title":"Mengenal Testing Automation Pyramid","type":"artikel"},{"content":"","date":"10 Februari 2023","externalUrl":null,"permalink":"/tags/tech/","section":"Tags","summary":"","title":"Tech","type":"tags"},{"content":"","date":"7 Februari 2023","externalUrl":null,"permalink":"/tags/lifestyle/","section":"Tags","summary":"","title":"Lifestyle","type":"tags"},{"content":"Yap, kata-kata deskripsi singkat di atas adalah lontaran kata-kata yang saya terima dari teman-teman saya. Bukan hanya dari teman-teman, bahkan sebagian dari anggota keluarga saya mengatakan hal yang sama. \u0026ldquo;Beli laptop kok mahal banget sih? Liat tuh punya X, harga 5 juta doang bisa ngegame lancar\u0026rdquo;.\nReaksi awal saya tentunya kesal, bahkan sekarang saya cenderung menghindari orang-orang yang mengatakan sedemikian rupa. Di mata orang lain, mungkin laptop hanyalah sebuah perangkat yang menunjang kebutuhan Office bagi pekerja kantoran, tugas bagi mahasiswa, atau gaming bagi gamer kelas berat.\nAlasan saya kesal termasuk masuk akal, karena kebutuhan utama penggunaan laptop saya sehari-hari adalah untuk pekerjaan. Sebagai programmer, sebuah Personal Computer, entah itu Laptop, Desktop, ataupun AIO akan kita gunakan penuh dari pagi sampai malam (bahkan 24 jam kalau kejar deadline).\nNamun fakta bahwa saya mengeluarkan uang yang cukup besar untuk sebuah MacBook tidak bisa dipungkiri, toh barangnya ada, saya bawa laptop ini di tas setiap hari, jadi ya mau sekesal apapun juga ya harus dimaklumi.\nWalaupun awalnya blind buy, tapi setelah satu tahun pemakaian, saya merasakan perbedaan yang sangat jauh pada beberapa aspek jika dibanding dengan laptop biasa. Alasannya beragam.\nLoad Time # Foto oleh Clint Patterson dari Unsplash\nLoad Time berarti waktu berapa lama sebuah perangkat dapat menyelesaikan suatu tugas. Sebagai contoh pada saat kita membuka browser, contohnya Google Chrome, kita akan menunggu beberapa detik setelah kita meng-klik ikon browser tersebut. Berapa detik kita menunggu Google Chrome terbuka itu disebut dengan Load Time.\nSama halnya dengan Game, entah itu membuka daerah baru, atau pada saat transisi ke suatu ruangan, atau sebelum kita melawan final-boss, akan ada loading sebelum kita bisa melaksanakan aktivitas kita selanjutnya.\nTentu saja, ngoding juga perlu Load Time cepat. Saya pernah mendengar beberapa keluhan dari teman-teman programmer lainnya, bahkan teman-teman sesama mahasiswa Teknik Informatika:\nEh lu tau enggak? Gue kalau udah nungguin loading bisa sambil makan atau ngemil dulu. Atau tak tinggal boker. Lama banget dah! 🤦‍♂️\nAtau bahkan lebih parah:\nGhits, gue pernah ya nungguin Flutter keinstal, lama banget, sampai pas nunggu gue main HP kan. Tapi ketika gue main HP, gue lupa waktu tuh, tau-tau udah malam.\nBuset dah. Tapi ya load time tidak bisa kita salahkan sepenuhnya dalam kasus ini, karena faktor utama penyebab load time lama adalah ketidakmampuan prosesor atau RAM dalam memproses dan mengalokasikan data yang diterima. Tentu saja kecepatan HDD / SSD juga berpengaruh.\nSaya sendiri memiliki pengalaman untuk mem-benchmark MacBook dengan laptop ASUS X441UV saya yang lama. Untuk membuka sebuah project Flutter, sampai running, perbedaan load time-nya sangat drastis.\nDevice Running Time MacBook Pro M1 2020 24s ASUS X441UV 1m 12s Jadi kesimpulannya, dengan mengeliminasi load time, kita sebagai pengembang bisa berfokus ke hal lain yang lebih penting seperti memikirkan logika dari suatu sistem yang dibangun. Mengeliminasi load time juga menghasilkan waktu produktif yang lebih lama.\nBattery Life # Foto oleh Panos Sakalakis dari Unsplash\nPada dasarnya, kebutuhan laptop itu bisa dibawa kemana saja, tanpa perlu mengkhawatirkan tidak ada stop kontak. Masalah utama pada laptop sekarang adalah masalah baterai. Laptop-laptop pada umumnya, memiliki battery cycle yang akan menurun seiring dengan penggunaan. Kasus yang saya alami pada laptop ASUS saya, laptop itu bisa bertahan tanpa perlu nge-charge di tahun pertama. Tahun kedua sudah mulai ngedrop, dan harus full charge di bulan ketiga.\nAda tiga opsi yang saya bisa lakukan:\nOpsi termurah, Colok terus, risikonya kalau tiba-tiba tercabut atau mati listrik. Agak mahal, ganti baterai. Opsi mahal, ganti laptop. Pada saat itu, sebagai pelajar SMK, saya tidak memiliki uang untuk membeli baterai. Jadi saya ambil opsi pertama. Sebenarnya risikonya tinggi, jika pada saat Windows Update, dayanya tercabut, ada kemungkinan Windowsnya corrupt. Atau pada saat ngegame atau ngedesain, ada kemungkinan data progressnya hilang.\nHari ini, pada saat blog ini ditulis, MacBook saya sudah berusia 1 tahun lebih, dan battery lifenya masih di angka 87%. Masih bisa survive seharian penuh dibawah workload yang banyak.\nBattery health 100% ke 87% dalam satu tahun termasuk boros karena ada sedikit kesalahan yang saya perbuat, saya menginstal VSCode versi Intel sementara MacBook saya menggunakan arsitektur ARM. Tentunya saat itu, terjadi battery drain dari 100% ke 20% dalam waktu 2 jam. Sangat boros.\nPerforma # Foto dari Tekno Cerdas.\nSiapa yang mengira bahwa Apple Silicon bisa melebihi performa prosesor Intel yang pada saat itu terbaru dan terkuat? Dilansir dari Techjourneyman, saat Apple Silicon M1 dirilis, ia berhasil mengalahkan prosesor Intel Generasi ke-10 yang terbaiknya (i9-10980HK) sehingga Intel harus melawan kembali.\nTentunya Intel berhasil mengalahkan M1 dengan merilis i9-11900K, namun dikalahkan lagi secara mutlak oleh M1 Max dan M1 Ultra. Secara performa, dan Integrated GPU, M1 jauh di atas Intel, karena Intel bergantung kepada External GPU seperti Nvidia.\n\u0026ldquo;Dengan performa sebaik itu, dan kekuatan setinggi itu, bagimana penggunaan baterainya, apakah boros?\u0026rdquo; Tidak. Karena arsitektur M1 yang merupakan ARM yang berfokus kepada keseimbangan ukuran prosesor, penggunaan daya yang lebih rendah, panas yang lebih rendah, kecepatan, dan kehidupan baterai yang lebih panjang. Berbeda dengan prosesor yang memiliki arsitektur x86 yang mengoptimisasikan performa, namun memakan daya yang sangat besar.\nKekurangan # Tidak adil jika saya hanya mengutarakan kelebihan MacBook sementara tidak mengutarakan kekurangannya. Tentunya, hal-hal yang saya rasakan ini memiliki impact yang sangat besar bagi saya:\nLearning Curve yang lumayan terjal. Sebagai pengguna MacBook baru, MacOS memiliki banyak fitur yang mengandalkan shortcut jika ingin bekerja lebih cepat. Terlebih lagi, perbedaan UI dari Windows ke MacOS sangatlah berbeda.\nTidak modular, sekalinya Anda membeli MacBook, tidak ada opsi untuk meng-upgradenya dari sisi storage, RAM, ataupun prosesor. Ya, semua komponen telah tersolder ke motherboard.\nAplikasi Windows tidak bisa diinstal pada MacOS. Walaupun ada beberapa pengembang yang bisa membawa aplikasinya ke MacOS, namun kebanyakan juga tidak bisa diinstal begitu saja. Solusinya, Anda bisa menggunakan versi web dari aplikasi tersebut, atau diinstal pada sebuah Virtual Machine seperti Parallels.\nFitur-fitur yang sudah lama disediakan oleh Windows, belum ada di MacOS. Fitur yang sering saya butuhkan di Windows adalah volume mixer. Software simpel yang bertujuan untuk mengatur suara tiap aplikasi, tidak disediakan pada MacOS. Entah dalam tahap pengembangan, atau Apple sengaja tidak membuatnya.\nKapitalisme at it\u0026rsquo;s best. Banyak program berbayar yang overpriced, bahkan untuk penambahan storage iCloud, kita harus membayar subscription. Setiap fitur bawaan Apple seperti Music, TV, Podcasts semuanya berbayar.\nPort. MacBook rilisan tahun 2020 ke atas, dengan seri 13-inch, tidak memiliki port yang cukup untuk kebutuhan sehari-hari. Hanya 2 port thunderbolt type C yang disediakan oleh Apple, dan tidak ada port lain. Jadi terpaksa saya harus membeli dongle, yang tentunya tak kalah mahal juga.\nAkhir kata # Disamping mewahnya Logo Apple yang disajikan, banyak kelebihan dari MacBook yang sudah saya rasakan. Namun apakah MacBook ini worth to buy, jujur, tidak. Saya secara pribadi lebih prefer ke Linux, dengan pengoptimasian yang sedemikian rupa (namun rumit), Anda bisa mendapatkan performa setara MacBook atau lebih dengan menggunakan Linux tanpa mengeluarkan budget puluhan juta.\nBahkan bisa saya katakan, laptop ASUS X441UV saya hampir mencapai performa MacBook M1 untuk basic daily usage ketika menggunakan Arch Linux. Jadi, untuk kalian yang ingin membeli produk Apple, pertimbangkan ulang budget-nya, ya 😬.\n","date":"7 Februari 2023","externalUrl":null,"permalink":"/artikel/2022/02/07/macbook-gaya-doang-atau-emang-gahar/","section":"Artikel-artikel","summary":"","title":"Macbook Gaya Doang Atau Emang Gahar","type":"artikel"},{"content":"4 Reality adalah sebuah tim Virtual YouTuber (biasa disebut VTuber) yang berada di Indonesia. Dinamakan 4 Reality karena penemu dari tim VTuber ini terdiri dari 4 karakter: Kurokami Itsuki, Raihan Ikeda, Karen, dan Vyula.\nDisamping munculnya Generasi ke-1 4 Reality, mereka juga menggagas tim Utaite (yang berfokus kepada konten cover lagu / menyanyi), juga terdiri dari 4 karakter: Cyanpile, Aura Lily, Yua Deyanara, Miasviel.\nWebsite ini dibangun dengan menggunakan:\nVue.js v3 - menggunakan compiler Vite v1 TailwindCSS v2 Vercel Pengembangan # Pengembangan website ini didasarkan pada rasa kesukaan saya terhadap salah satu karakter Utaite Aura Lily, suatu Peri cantik yang charming dan lucu. Setelah menjelajahi server Discord 4 Reality, saya bertemu Karen yang juga memanage seluruh asset dari tim ini. Dengan inisiatif ingin membuatkan Aura Lily sebuah wallpaper, saya mendapatkan desain karakternya dengan mudah.\nBerikut adalah desain wallpaper Aura Lily yang selanjutnya akan menjadi ide pengembangan website 4 Reality:\nDulu saya sangat menyukai konsep dari Broken Grid, namun tantangan terbesar yang akan saya hadapi ketika pengembangan adalah yang saya sebut sebagai stylesheet chaos. Kondisi dimana akan ada banyak sekali stylesheet yang redundan dan cenderung hanya bisa dipakai satu kali untuk karakter yang spesifik. Beruntung saya menemukan framework CSS yang bisa mengatasi ini, TailwindCSS.\nBisa dibayangkan bagaimana sulitnya membuat suatu halaman profil karakter yang memuat banyak informasi seperti:\nNama talent Jenis karakter yang diperankan Sosial media Motto Palet warna yang digunakan Informasi tambahan lainnya Setelah desain Aura Lily yang sedemikian rumit selesai, selanjutnya saya berfikir \u0026ldquo;Bagaimana jika saya kembangkan kembali website ini?\u0026rdquo;. Lihat pernyataan kembangkan kembali, pernyataan ini muncul karena website 4 Reality pada saat itu sudah ada, namun sangat jauh dari kata \u0026ldquo;layak\u0026rdquo;.\nTemplate Bootstrap 4 yang seadanya, ditambah dengan asset raw yang begitu besar hingga mencapai 10MB per gambar sehingga load time sangat terpengaruh, serta desainnya yang sangat simpel (karena template, tentunya).\nDilema Profile Section # Tantangan selanjutnya bukanlah bagaimana saya memikirkan tentang bagaimana desain websitenya, namun tentang bagaimana saya bisa menyajikan 8 talent sekaligus dalam suatu halaman, tanpa perlu mengganti halaman. Vue Dynamic Component akhirnya digunakan untuk menyimpan setiap informasi talent. Jadi pengunjung website tinggal meng-klik ikon talent yang ingin dilihat maka secara otomatis detail dari talent tersebut akan muncul.\nBerikut adalah rancangan dari Profile Section:\nNamun tentu saja, ada informasi yang tidak akan terlihat jika desain dari Profile Section ini berbentuk scrolling, tidak ada informasi \u0026ldquo;Bagian mana talent ini berasal? Apakah dia VTuber, atau Utaite? Lalu bagaimana jika ada Generasi 2, Generasi 3, dan seterusnya?\u0026rdquo; Tentu saja, ini adalah dilema tersendiri. Saya memiliki 2 opsi:\nMenampilkan informasi karakter apa yang diperankan oleh talent pada detail profilnya, dengan risiko desain akan menjadi tidak seimbang karena penambahan kata \u0026ldquo;Gen 1\u0026rdquo; atau \u0026ldquo;Utaite\u0026rdquo; yang tegas. Mengkategorikan karakter berdasarkan peranan masing-masing talent, dengan cara membuat \u0026ldquo;Tab\u0026rdquo; Saya memutuskan untuk menggunakan opsi 2, opsi yang paling mudah diimplementasikan, serta tidak mengubah keseluruhan desain yang sudah ada. Berikut adalah tampilan yang saya ambil dari website langsung\nTerus bagian lainnya gimana, Tsu? # Sebagai konteks, Tsu disini adalah sapaan dari kata Hatsu (初 / はつ). Nickname saya pada saat itu adalah HatsuShiroyuki (初白雪) - Salju Putih Pertama.\nItulah kalimat pertanyaan yang Karen ucapkan ketika saya selesai mengerjakan Character Section, sebuah pertanyaan sederhana namun cukup menguras otak saya untuk memikirkan bagaimana desain yang cocok untuk section Home, Visi Misi, Kontak Kami, dan Footer.\nBetul-betul bukan hal yang mudah, karena impresi pertama yang saya berikan adalah nuansa Broken Grid yang chaotic, namun tetap seimbang dan enak dipandang.\nHome # Section Home secara harfiah hanya menampilkan overview dari keseluruhan website. Setelah menguras otak cukup lama hanya untuk section Home, saya mengirimkan draft section home ini kepada Karen dan Itsuki:\nTidak ada penolakan, namun lebih kepada saran:\nGimana kalau di bawah 4 Reality Team ini lu tambahin kata-kata ini, Tsu: \u0026ldquo;Together Everyone Achieves More And There is no I in Team.\u0026rdquo;\n— Itsuki\nSama sekalian di atas ada header, terus warnanya jangan tok hitam. Agak ke abu-abu juga boleh keknya.\n— Karen\nChallenge accepted, Ki, Ren. Setelah beberapa menit menyesuaikan perubahan, berikut adalah desain yang saya kirimkan lagi ke mereka:\nSemua menyetujui desain Home Section, dilanjut ke About Us Section.\nAbout Us # Section ini juga adalah sebuah tantangan tersendiri karena kita ingin menampilkan 1 visi dan 3 misi mereka, namun sekaligus menceritakan secara singkat tentang tim mereka.\nSaya menggagas ide dimana saya akan menggunakan sub-section atas untuk Tentang Tim, dan sub-section bawah untuk Visi dan Misi. Tentunya desain ini tidak diterima dengan baik, dan cenderung merusak nuansa Broken Grid yang digunakan:\nTentu saja, saya rasa kalian sebagai pembaca juga sama kesalnya \u0026ldquo;kok gitu?\u0026rdquo;. Secara teknis tentu akan merusak tema, namun bagaimana lagi, otak juga sudah stuck 💀. Saya meminta waktu untuk mengistirahatkan otak saya selama beberapa hari, juga untuk mencari inspirasi untuk section ini.\n2 hari telah berlalu, dan saya mengirimkan desain ini:\nKonsep yang saya buat untuk About Us Section adalah sebagai berikut:\nPojok kiri atas berisi \u0026lsquo;V\u0026rsquo; atau \u0026lsquo;M\u0026rsquo;, yang berarti Visi atau Misi. Konten visi atau misi tidak terlalu panjang. Susunan Card akan berurut ke bawah, bukan zig-zag pada tampilan Tablet. About 4 Reality yang ada di bagian kiri berbentuk sticky, jadi akan secara otomatis scroll ke bawah ketika user juga scroll ke bawah. Ini alasan utama mengapa bagian kiri terkesan kosong. Desain diterima oleh semua anggota 4 Reality, lalu saya langsung menggabungkan desain yang ada, lalu slicing desain tersebut ke kode.\nSlicing # Slicing adalah tahap paling lama, memikirkan bagaimana tampilan dapat sesuai pada setiap viewport bukanlah suatu hal yang mudah. Pengetahuan akan responsive web design akan sangat berguna pada tahap ini.\nUntuk Home Section, relatif mudah, hanya memakan waktu beberapa jam untuk dapat menjadikannya responsif di seluruh viewport.\nAbout Us Section juga relatif mudah. Tampilkan Card secara zig-zag di bagian kanan pada viewport Desktop, tampilkan secara berurutan dari atas ke bawah pada viewport Tablet, dan hapus property sticky pada viewport Mobile.\nCharacter Section ini tantangan berat dari seluruh section.\nSaya harus membangun seluruh desain yang unik pada setiap Talent. Ditambah lagi ada permintaan unik dari beberapa Talent yang entah menginginkan opsi 2 warna, atau penggunaan arah nama yang berbeda (cek detail Aura Lily sebagai contoh). Saya harus mengkategorikan setiap Talent kepada bagian yang sesuai. Saya harus memastikan seluruh desain dapat dilihat dengan baik di seluruh viewport, lagi-lagi harus responsif. Sisanya untuk section Kontak dan Sosial Media hanya berupa section simpel yang tidak terlalu bagus, tetapi masih selaras dengan tema warna yang digunakan.\nAkhir kata # Sejauh karir saya dalam pengembangan website, 4 Reality ini adalah website ter-kompleks yang pernah saya buat. Namun hasil dari kerja keras ini terbayar dengan baik karena Website 4 Reality menjadi pemenang dalam 2 event Wibucode:\nJuara 1 Event Front-end 2021 Wibucode pada tanggal 14 Juni 2021\nJuara 2 Event Tailwind CSS 2021 Wibucode pada tanggal 13 Desember 2021\nSebuah prestasi kecil yang membanggakan 😬.\nTerima kasih saya ucapkan kepada seluruh pihak yang terkait pada pengembangan Website ini. Terutama Karen yang selalu memberikan umpan balik yang positif dan membangun, Itsuki dan Nervia (staf) yang sering memberikan kritik pedas dan tajam, serta Aura Lily yang menjadi motivasi utama dibalik pengembangan Website ini.\n","date":"6 Februari 2023","externalUrl":null,"permalink":"/portfolio/website/4reality/","section":"Portfolios","summary":"","title":"4 Reality - Web Tim Vtuber Indonesia","type":"portfolio"},{"content":"","date":"20 Oktober 2022","externalUrl":null,"permalink":"/tags/coding/","section":"Tags","summary":"","title":"Coding","type":"tags"},{"content":"","date":"20 Oktober 2022","externalUrl":null,"permalink":"/series/prinsip-solid/","section":"Series","summary":"","title":"Prinsip SOLID","type":"series"},{"content":"Oke, kita sudah belajar bagaimana prinsip Single Responsibilty secara abstrak. Sekarang kita coba implementasikan ke framework PHP tercinta, Laravel.\nStudi Kasus # Anggaplah kita memiliki sebuah controller PostController:\n\u0026lt;?php namespace App\\Http\\Controllers; use App\\Models\\Post; use Illuminate\\Http\\Request; use Illuminate\\Http\\Response; class PostController { public function store(Request $request): Response { $request-\u0026gt;validate([ \u0026#39;title\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;min:20\u0026#39;, \u0026#39;max:100\u0026#39;], \u0026#39;body\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;], ]); Post::create($request-\u0026gt;only(\u0026#39;title\u0026#39;, \u0026#39;body\u0026#39;)); return redirect() -\u0026gt;route(\u0026#39;post.index\u0026#39;) -\u0026gt;withMessage(\u0026#39;Post created successfully\u0026#39;); } } Awalnya, controller ini sangatlah simpel, sehingga metode store() memiliki 3 tanggung jawab: memvalidasi data, menyimpan data, dan mengirimkan umpan balik berupa redirect dengan pesan.\nNamun bagaimana jika kita juga harus mengirimkan notifikasi kepada pengguna yang sudah berlangganan kepada aplikasi yang kita buat? Bagaimana jika kita butuh menyimpan gambar juga? Jika kedua tanggung jawab itu berada pada suatu metode store(), maka secara langsung metode ini memiliki 5 tanggung jawab.\nLalu bagaimana kita memisahkan meringankan tanggung jawab metode store() ini?\nLangkah 1: Memisahkan Validator ke Request class # Laravel memiliki sebuah namespace class khusus yang digunakan sebagai Validator. Yaitu Request class. Kita bisa membuatnya dengan cara menjalankan command Artisan php artisan make:request StorePostRequest.\nNaming convention yang digunakan Laravel untuk Request class adalah [Method][Model]Request. Contoh lain jika kita ingin melakukan update, maka Request class yang akan kita bangun adalah UpdatePostRequest.\nKita pindahkan fungsi validasi yang ada di PostController#store ke StorePostRequest#rules. Hasilnya adalah seperti berikut:\n\u0026lt;?php namespace App\\Http\\Requests; use Illuminate\\Foundation\\Http\\FormRequest; class StorePostRequest extends FormRequest { /** * Determine if user is authorized to make this request. * * @return bool */ public function authorize() { return false; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ \u0026#39;title\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;, \u0026#39;min:20\u0026#39;, \u0026#39;max:100\u0026#39;], \u0026#39;body\u0026#39; =\u0026gt; [\u0026#39;required\u0026#39;], ]; } } \u0026lt;?php namespace App\\Http\\Controllers; use App\\Models\\Post; use App\\Http\\Requests\\StorePostRequest; use Illuminate\\Http\\Response; class PostController { // `Request $request` berubah menjadi `StorePostRequest $request` public function store(StorePostRequest $request): Response { Post::create($request-\u0026gt;only(\u0026#39;title\u0026#39;, \u0026#39;body\u0026#39;)); // Blok kode menyimpan gambar // Blok kode mengirimkan email kepada pelanggan return redirect() -\u0026gt;route(\u0026#39;post.index\u0026#39;) -\u0026gt;withMessage(\u0026#39;Post created successfully\u0026#39;); } } Nah, kita telah meringankan sedikit beban metode store(). Selanjutnya adalah…\nLangkah 2: Membuat class Service (layanan) untuk model Post # Terminologi Service disini adalah suatu class yang berfungsi untuk mengolah data yang masuk melalui Request. Tidak ada naming convention untuk Service pada Laravel, jadi kita buat manual saja di \\App\\Services\\PostService.php.\n\u0026lt;?php namespace App\\Services; class PostService { public static function store(array $data): void { Post::create($data); // Blok kode menyimpan gambar // Blok kode mengirimkan email kepada pelanggan } } Karena metode store dari PostService adalah metode statis, maka kita bisa langsung memanggilnya pada PostController sebagai berikut:\n\u0026lt;?php namespace App\\Http\\Controllers; use App\\Models\\Post; use App\\Http\\Requests\\StorePostRequest; use App\\Services\\PostService; use Illuminate\\Http\\Response; class PostController { public function store(StorePostRequest $request): Response { PostService::create($request-\u0026gt;only(\u0026#39;title\u0026#39;, \u0026#39;body\u0026#39;)); return redirect() -\u0026gt;route(\u0026#39;post.index\u0026#39;) -\u0026gt;withMessage(\u0026#39;Post created successfully\u0026#39;); } } Nah sekarang, metode store dari PostController memiliki 1 tanggung jawab. Ia hanya bertanggung jawab untuk me-redirect ke rute post.index. Anda juga bisa menambahkan try-catch untuk mengantisipasi error yang akan ada.\nNamun, metode store dari PostService memiliki 3 tanggung jawab. Secara prinsip SOLID, blok kode “Menyimpan Gambar” serta blok kode “Mengirim email kepada pelanggan” harus dipisah ke file tersendiri.\nSaya tidak akan menjelaskan secara lengkap kode dari kedua Service yang akan dipisah, namun kurang lebih struktur barunya adalah sebagai berikut:\n🗂 app/ ├── 🗂 Http/ │ └── 🗂 Controllers/ │ └── 📁 PostController/ │ └── #store(StorePostRequest $request): Response ├── 🗂 Notifications/ │ └── 📁 NewPostNotification/ │ ├── #__construct(): void │ ├── #via(mixed $notifiable): array │ ├── #toMail(mixed $notifiable): MailMessage │ └── #toArray(mixed $notifiable): array └── 🗂 Services/ ├── 📁 PostService/ │ └── #::create(array $data): void └── 📁 ImageService/ └── #::upload(UploadedFile $file): void Begitulah kurang lebih penerapan prinsip SOLID yang pertama yaitu Single Responsibility. Terima kasih sudah membaca!\nDaftar Pustaka # Thumbnail oleh Mitchell Luo dari Unsplash\n","date":"20 Oktober 2022","externalUrl":null,"permalink":"/artikel/2022/10/20/prinsip-solid-1-single-responsibility-laravel-example/","section":"Artikel-artikel","summary":"","title":"Prinsip SOLID #1: Single Responsibility - Laravel Example","type":"artikel"},{"content":"Single responsibility berarti, suatu metode haruslah memiliki satu tanggung jawab. Tidak boleh lebih dari satu. Dikutip dari SOLID, Single Responsibility berarti sebuah kelas harus memiliki satu, dan hanya satu alasan untuk diubah.\nStudi kasus. # Anggaplah kita membuat suatu aplikasi POS, Bapak Harry menginginkan sebuah laporan penjualan yang akan di-export ke bentuk CSV. Simpel saja, kita buat seperti ini:\n\u0026lt;?php namespace App\\Reports; use Illuminate\\Support\\Facades\\DB; class SaleReport { public function export(): string { $sales = DB::table(\u0026#39;sales\u0026#39;)-\u0026gt;latest()-\u0026gt;get(); return \u0026#39;CSV format\u0026#39;; } } Tapi, pak Harry ingin data bisa di-export pada rentang tanggal waktu tertentu, bukan data terakhir saja. Yah, karena permintaan client bisa berubah sewaktu-waktu, kita lakukan perubahan menjadi seperti ini:\n\u0026lt;?php namespace App\\Reports; use Illuminate\\Support\\Facades\\DB; class SaleReport { public function export( string $dateStart, string $dateEnd ): string { $sales = DB::table(\u0026#39;sales\u0026#39;) -\u0026gt;whereBetween(\u0026#39;created_at\u0026#39;, [$dateStart, $dateEnd]) -\u0026gt;get(); return \u0026#39;CSV format\u0026#39;; } } Revisi di-acc, dan kita bisa istirahat untuk hari ini. Keesokan harinya, setelah bangun tidur, cek notifikasi “Eh, tambahin bentuk export untuk format lain ya. Usahakan bisa PDF, Excel, dan JSON”. Waduh nambah kompleks dong kelas SaleReport kita. Ya sudahlah gak apa-apa, kita ngoding lagi:\n\u0026lt;?php namespace App\\Reports; use Illuminate\\Support\\Facades\\DB; class SaleReport { public function export( string $dateStart, string $dateEnd, string $format = \u0026#39;csv\u0026#39; ): string { $sales = DB::table(\u0026#39;sales\u0026#39;) -\u0026gt;whereBetween(\u0026#39;created_at\u0026#39;, [$dateStart, $dateEnd]) -\u0026gt;get(); if ($format == \u0026#39;pdf\u0026#39;) { return \u0026#39;PDF format\u0026#39;; } if ($format == \u0026#39;xls\u0026#39;) { return \u0026#39;XLS format\u0026#39;; } if ($format == \u0026#39;json\u0026#39;) { return \u0026#39;JSON format\u0026#39;; } return \u0026#39;CSV format\u0026#39;; } } Lho he? Kok jadi panjang banget kelas SaleReport kita? Apalagi metode export() kita punya banyak tanggung jawab, mulai dari mengabil data sales, ngambil data berdasarkan tanggal, nge-export data pula. Ada 3 tanggung jawab yang metode export() miliki. Kita harus refaktor kode ini biar jadi rapih.\nPrinsip [S]ingle Responsibility. Satu kelas atau metode hanya memiliki satu tanggung jawab. # Kita bagi menjadi dua kategori kelas:\nKelas untuk mengambil data, SaleReport Kelas untuk meng-eksport data, xExport Pertama, kita mulai dengan memindahkan script untuk meng-export data ke masing-masing file berbeda:\n\u0026lt;?php namespace App\\Reports; use Illuminate\\Support\\Collection; class PdfExport { public function export(Collection $saleData): string { return \u0026#39;PDF export\u0026#39;; } } \u0026lt;?php namespace App\\Reports; use Illuminate\\Support\\Collection; class XlsExport { public function export(Collection $saleData): string { return \u0026#39;XLS export\u0026#39;; } } \u0026lt;?php namespace App\\Reports; use Illuminate\\Support\\Collection; class JsonExport { public function export(Collection $saleData): string { return \u0026#39;JSON export\u0026#39;; } } \u0026lt;?php namespace App\\Reports; use Illuminate\\Support\\Collection; class CsvExport { public function export(Collection $saleData): string { return \u0026#39;CSV export\u0026#39;; } } Kedua, kita refaktor kelas SaleReport menjadi seperti ini:\n\u0026lt;?php namespace App\\Reports; use Illuminate\\Support\\Collection; use Illuminate\\Support\\Facades\\DB; class SaleReport { public function between( string $dateStart, string $dateEnd ): Collection { return DB::table(\u0026#39;sales\u0026#39;) -\u0026gt;whereBetween(\u0026#39;created_at\u0026#39;, [$dateStart, $dateEnd]) -\u0026gt;get(); } } Lihat perubahan metode export menjadi between. Karena kelas SaleReport bertanggung jawab untuk mengambil data saja, maka “mengambil data dari rentang waktu sekian hingga sekian” adalah salah satu tanggung jawab yang benar.\nTapi, apakah satu kelas hanya boleh memiliki satu metode saja?\nTentu saja tidak. Kita bisa menambahkan beberapa metode lain, NAMUN tetap memiliki tanggung jawab sebagai “mengambil data”. Contohnya bagaimana? Anggap saja pak Harry menginginkan data berdasarkan Bulan, dan Tahun secara spesifik.\n\u0026lt;?php namespace App\\Reports; use Illuminate\\Support\\Collection; use Illuminate\\Support\\Facades\\DB; class SaleReport { public function between( string $dateStart, string $dateEnd ): Collection { return DB::table(\u0026#39;sales\u0026#39;) -\u0026gt;whereBetween(\u0026#39;created_at\u0026#39;, [$dateStart, $dateEnd]) -\u0026gt;get(); } public function forYear(int $year): Collection { return DB::table(\u0026#39;sales\u0026#39;) -\u0026gt;whereYear(\u0026#39;created_at\u0026#39;, $year) -\u0026gt;get(); } public function forMonth(int $month): Collection { return DB::table(\u0026#39;sales\u0026#39;) -\u0026gt;whereMonth(\u0026#39;created_at\u0026#39;, $month) -\u0026gt;get(); } } Sekarang kita memiliki beberapa file sebagai berikut:\n🗂 app/ └── 🗂 Reports/ ├── 📁 SaleReport.php/ │ ├── #between(string $dateStart, string $dateEnd): Collection │ ├── #forYear(int $year): Collection │ └── #forMonth(int $month): Collection ├── 📁 PdfExport.php/ │ └── #export(Collection $saleData): string ├── 📁 XlsExport.php/ │ └── #export(Collection $saleData): string ├── 📁 JsonExport.php/ │ └── #export(Collection $saleData): string └── 📁 CsvExport.php/ └── #export(Collection $saleData): string Nah, sekarang kita memiliki kelas SaleReport yang memiliki tanggung jawab HANYA mengambil data penjualan. Lalu bagaimana kita mengaplikasikan kode yang telah kita tulis?\nPengaplikasian prinsip [S]ingle Responsibility # Anggap saja kode kita berjalan pada suatu route:\n\u0026lt;?php use App\\Reports\\SaleReport; use App\\Reports\\CsvExport; use Carbon\\Carbon; use Illuminate\\Support\\Facades\\Route; Route::get(\u0026#39;/\u0026#39;, function() { $saleReport = new SaleReport(); $csvExport = new CsvExport(); return $csvExport-\u0026gt;export([ $saleReport-\u0026gt;between([ Carbon::now()-\u0026gt;subYear(), Carbon::now() ]) ); // returns: \u0026#39;CSV export\u0026#39; }); Bisa dilihat kode kita jauh lebih rapi dibandingkan dengan kode yang kita tulis sebelum refaktor.\nDaftar Pustaka # Thumbnail oleh Mitchell Luo dari Unsplash\n","date":"20 Oktober 2022","externalUrl":null,"permalink":"/artikel/2022/10/20/prinsip-solid-1-single-responsibility-brainstorming/","section":"Artikel-artikel","summary":"","title":"Prinsip SOLID #1: Single Responsibility - Brainstorming","type":"artikel"},{"content":"","date":"16 Agustus 2022","externalUrl":null,"permalink":"/tags/coffee/","section":"Tags","summary":"","title":"Coffee","type":"tags"},{"content":"So, coffee has been an exciting (and expensive) part of my life, it has been a year since I\u0026rsquo;m into this brewing field, and I guess it is my time to show what I have learnt throughout this year. Because I\u0026rsquo;m into this field, I write this article about coffee and its tools which I have in my home, and potentially, you could.\nThis is not a sponsored article, for real. I\u0026rsquo;m writing this just for fun and for knowledge purposes.\nCoffee Beans # Photo by Anastasiia Chepinska on Unsplash\nCoffee beans are the most essential part of a coffee brewing, I mean, you want to brew a cup coffee, not a tea, right? Talking about coffee beans, there are three varieties of coffee you can see on the market:\nArabica; which has fruity, acid, and vivid taste. Robusta; which has bitter, woody, and earthy taste. Blend; which is a mix of Arabica and Robusta, and usually used for an espresso brew. Now you know about the coffee varieties, let\u0026rsquo;s talk about the grind size. Photo courtesy of Know Your Grinder\nTo be honest, let\u0026rsquo;s talk about this grind size in a second. Grind size is hard to measure, it is more like a sense to decide which grind size is which. Grind size is varies from every type of grinding machine or grind tool manufacturer, they have their own standard. For example, my Timemore Slim Chestnut has a grind size 5 to 11 for an espresso grind, 15 to 17 for a pourover, and more. So, take it as a grain of beans when it comes to grind size.\nIn general, there are three settings of the grind size (which are indicated in bold). There are:\nType Particle Size General Use Extra Coarse 1.5mm Cold Brew Coarse 1mm French Press, Plunger, Percorator Medium-coarse 0.8mm to 0.7mm Cupping, Chemex, Clever Dripper, Flat Bottom Medium 0.6mm Cone-shaped filters Medium-fine 0.5mm V60, Siphon, Aeropress Fine 0.35mm Moka Pot, Espresso Extra Fine 0.1mm Turkish Coffee/Cezve Table reference are taken from Roastercoffees on Pinterest.\nTools # This section is the most expensive of all, even though it\u0026rsquo;s a one-time buy, the initial cost is relatively high in comparison to the coffee beans. These are some what I have in my home:\nThe Grinder, Timemore Chestnut Slim # Photo by Timemore Malaysia on Bunamarket\nYes, the grinder itself. This tool is the most expensive tool I have. It cost around $100, and it is a hand-grinder. Because it is a hand grinder, I can easily carry the grinder and the beans for a brew to wherever I want. But, the cons are, it is heavy, and not suitable for an espresso grind. I mean, it is possible to make an espresso grind, but it is very time consuming and consume a lot of energy.\nA grinder in your hand can help you to make a fresh grounded coffee brew every single day. I prefer a fresh ground coffee because it has more aroma, more flavor, and more complexity.\nDripper, V60 # Photo by Ke Vin on Unsplash\nBy mentioning V60, you need two tools to get. A V60 dripper, and a V60 filter. Filter comes in two types, there are paper filters, and mesh filters. Paper filters are cheap, and potentially filters more softer coffee particles rather than mesh filters. But mesh filters can save you extra money in the long run because it is reusable and washable.\nThis tool is the cheapest one you can get. Buying a V60 kit will cost you around $10, depends on the material used on V60 dripper and the paper quality. The kit won\u0026rsquo;t include the serving kit such as the carafe to hold the brewed coffee, you can directly put a mug under the V60 dripper if you want.\nDripper, and a carafe, Chemex # Photo by Zarak Khan on Unsplash\nChemex, to me, is a fancier V60, it has two purposes in one Chemex. A holder for V60 filter, and a carafe. It also make coffee looks beautiful to see under the light.\nBrewing in a Chemex is a little bit slower than V60. My brew time is usually 3m 15s for a V60 brew, resulting in 225ml of coffee, but for a Chemex, using the same brew profile, it could be around 4m.\nThe reason why a Chemex brew is slower is because, there are little to none of airflow. The only airflow Chemex has is only in the pouring tunnel, which are usually clogged during the brewing process. This issue could be fixed by using a chopstick in the tunnel to let it breathe. A V60 dripper has a spiral-shaped little bumps to help the airflow, meanwhile the Chemex has none.\nThat is the reason why Chemex brew needs coarser coffee beans than a V60 brew. A Chemex bootleg would cost around $10, meanwhile the real one would cost $80.\nMoka Pot # Photo by Mostafa Mahmoudi on Unsplash\nWhen it come to espresso brew, this tool is the closest one and the cheapest one to get, at least to mimic the strength of an espresso itself. Moka Pot brew is very technical, and difficult brew to master. It has a lot of things to consider, the coffee, the grind size, the water temperature, the technique used, it all combines into one Moka Pot brew.\nIt took me 4 months of trials and errors to finally pull a great, strong but balanced shot, which is ideal for my espresso standard. In comparison for an espresso shot to a Moka Pot shot is the amount of coffee and the muddiness. Moka Pot brew tends to have a lot of water, resulting in 2 to 3 shots of espresso (a shot is 30ml, so it should be 90ml), and muddy. This muddiness came from the extra fine grind that passes through the filter, which Moka Pot\u0026rsquo;s filter hole are larger than espresso machine.\nThe one I have is Conalli Moka Pot, which can produce 3 shots of espresso in a single brew. It costs $20.\nScale, Latina Newton Scale # Photo by Philocoffee on Tokopedia\nFor a good brew, it is important to have a scale. To be honest, it is a geeky kind-of-thing, when you bought a scale and a thermometer for a single brew, but really, it is worth the money you spend. The feeling of making a good brew to start your day is just a satisfaction in another level. It cost $15, an entry level scale.\nThermometer, Latina Thermo Blanco Lollipop # Photo by Philocoffee on Tokopedia\nAnother entry level thermometer, not so accurate but helpful to make a good brew. Again, it\u0026rsquo;s \u0026ldquo;geeky\u0026rdquo; to actually have either or both a scale and/or a thermometer. Owning a thermometer will prevent your brew from under-extracting or over-extracting.\nWater temperature will greatly impact the quality of the brew, but if you don\u0026rsquo;t have one, use this rule of thumb:\nIf the beans\u0026rsquo; roast profile is a light roast, let the water boils and chill it down for a while for around 30s, this will put down the water temperature to 95ºC. It is my standard temperature for doing a light roast. Preferences might vary. For a medium roast, the water temperature needs to be around 88ºC to 92ºC. It also usually used for a specialty roasts where the roaster mix some additive during the process. For a dark roast, it should be around 82ºC to 85ºC. Kettle, Latina Olive Kettle # Photo by Philocoffee on Tokopedia\nIf you\u0026rsquo;re noticing, I\u0026rsquo;ve been buying this Latina brand recently, because it is fairly cheap, and good for an amateur brew. It can pulls a great brew, so why not? This kettle is small, so it fits me for my daily home brew, it can carry 420ml of water.\nAs you can see the design, the handle is metal, so the heat from the chamber is also transferred to the handle. So every time I make a brew, my hand burns a little. This one costs $7.\nThe end? # So, that\u0026rsquo;s all about my brewing kit. In total, it would costs around $250, it could cost more if I bought mid-end thermometer, kettle, and scale, but I think it\u0026rsquo;s not worth it for now. Just a reminder that even a professional barista can make a good coffee without an expensive machinery. This remarks that anything you own, can brings you to mastery.\nFootnote # Thumbnail by Julien Labelle on Unsplash.\n","date":"16 Agustus 2022","externalUrl":null,"permalink":"/artikel/2022/08/16/my-home-brewing-kit-an-expensive-hobby/","section":"Artikel-artikel","summary":"","title":"My Home Brewing Kit an Expensive Hobby","type":"artikel"},{"content":"Hai, lama tak jumpa 👋. Udah lama gak nulis juga, jadi agak kangen sedikit sama blog yang isinya daging semua. Well, berhubung ada waktu, jadi let\u0026rsquo;s go 😬.\nJadi kasusnya itu, barusan Saya mau refaktor project legacy, tapi karena ini adalah fase awal refaktor, jadi sebisa mungkin databasenya nggak berganti dulu. Karena pasti butuh waktu ekstra juga buat remigrate database lama ke database yang baru dengan struktur dan skema yang berbeda. Mari kita kupas.\nLaravel Breeze # Laravel Breeze ini adalah sebuah package yang disediakan oleh Laravel, secara official yang bisa teman-teman unduh disini. Breeze ini udah nyediain view untuk Login, Register, Lupa Password, Verifikasi Email, dan lain sebagainya yang bisa mempersingkat waktu development kalian.\nSekedar info barangkali males buka dokumentasi, cara instal Breeze adalah sebagai berikut:\n# 1. Tambahkan Breeze ke packages.json atau dengan cara menjalankan perintah: composer require laravel/breeze --dev # 2. Instal Breeze Scaffolding untuk Laravel dengan cara menjalankan perintah: php artisan breeze:install # 3. Jalankan npm install \u0026amp;\u0026amp; npm run dev npm install \u0026amp;\u0026amp; npm run dev Dengan kelebihannya ini, Breeze memiliki beberapa kelemahan jika digunakan secara bawaan. Breeze hanya mengenal Email dan Password sebagai \u0026ldquo;key\u0026rdquo; dari autentikasi mereka. Lantas, bagaimana jika Saya ingin mengubah kolom autentikasi default Breeze? Cukup mudah sebetulnya, cuma butuh waktu sekitar 3 menitan, namun karena Saya orang dokumentasi, jadi Saya sekalian ngulik ke file vendor-nya juga wkwkwk.\nSkema Database # Anggap saja Saya memiliki tabel user dengan struktur seperti ini:\nmysql\u0026gt; SHOW COLUMNS FROM MUSR; +------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+-------+ | USRID | int | NO | PRI | NULL | | | USREM | varchar(100) | NO | | NULL | | | USRNM | varchar(100) | NO | | NULL | | | USRPW | text | NO | | NULL | | +------------+--------------+------+-----+---------+-------+ 4 rows in set (0.00 sec) Agak complicated karena strukturnya aneh ya, tapi sedikit penjelasan:\nUSRID adalah singkatan dari user_id USREM adalah singkatan dari email USRNM adalah singkatan dari username USRPW adalah singkatan dari password Nyeleneh semisal kita terbiasa ngikutin naming convention dari Laravel. Apalagi nama tabelnya MUSR, bukan users, tapi no worries, I got you covered.\nOverriding Breeze\u0026rsquo;s Authentication # CATATAN: Project ini dibuat untuk Laravel 9.x, jika teman-teman menggunakan versi dibawahnya, mohon cek dokumentasi lebih lanjut untuk mengecek ulang apakah metode yang sama sudah diimplementasi pada versi-versi sebelumnya.\nPertama-tama, kita harus ubah metode authenticate() pada App\\Http\\Requests\\Auth\\LoginRequest. Fungsi ini ada pada baris ke-44:\n\u0026lt;?php // app/Http/Requests/Auth/LoginRequest.php // ... class LoginRequest extends FormRequest { // ... public function authenticate() { $this-\u0026gt;ensureIsNotRateLimited(); if (! Auth::attempt($this-\u0026gt;only(\u0026#39;email\u0026#39;, \u0026#39;password\u0026#39;), $this-\u0026gt;boolean(\u0026#39;remember\u0026#39;))) { RateLimiter::hit($this-\u0026gt;throttleKey()); throw ValidationException::withMessages([ \u0026#39;email\u0026#39; =\u0026gt; trans(\u0026#39;auth.failed\u0026#39;), ]); } RateLimiter::clear($this-\u0026gt;throttleKey()); } // ... } Nah, jika teman-teman lihat pada bagian Auth::attempt(), Breeze secara default menggunakan email dan password sebagai key dari autentikasinya. Untuk menyesuaikan skema Breeze dengan skema tabel User yang saya punya, kita harus mengubahnya dengan kode sebagai berikut:\n\u0026lt;?php // app/Http/Requests/Auth/LoginRequest.php // ... class LoginRequest extends FormRequest { // ... public function authenticate() { $this-\u0026gt;ensureIsNotRateLimited(); if (! Auth::attempt([\u0026#39;USREM\u0026#39; =\u0026gt; $this-\u0026gt;email, \u0026#39;password\u0026#39; =\u0026gt; $this-\u0026gt;password], $this-\u0026gt;boolean(\u0026#39;remember\u0026#39;))) { RateLimiter::hit($this-\u0026gt;throttleKey()); throw ValidationException::withMessages([ \u0026#39;email\u0026#39; =\u0026gt; trans(\u0026#39;auth.failed\u0026#39;), ]); } RateLimiter::clear($this-\u0026gt;throttleKey()); } // ... } Nah, dengan ini, autentikasi \u0026ldquo;Custom* bisa kita implementasikan.\n\u0026ldquo;Bentar Ghits, ngubah 1 file doang?\u0026rdquo; nggak kok wkwkwk, kita akan ubah 1 file lagi\nKedua, sesuaikan model User. Hal ini meliputi perubahan atribut $fillable, $hidden, dll:\n\u0026lt;?php // app/Models/User.php class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable, SoftDeletes; protected $table = \u0026#34;MUSR\u0026#34;; protected $primaryKey = \u0026#34;USRID\u0026#34;; public $incrementing = false; public function getAuthPassword() { return $this-\u0026gt;USRPW; } /** * The attributes that are mass assignable. * * @var array\u0026lt;int, string\u0026gt; */ protected $fillable = [ \u0026#39;USREM\u0026#39;, \u0026#39;USRPW\u0026#39;, \u0026#39;USRNM\u0026#39;, ]; /** * The attributes that should be hidden for serialization. * * @var array\u0026lt;int, string\u0026gt; */ protected $hidden = [ \u0026#39;USRPW\u0026#39;, ]; } Jangan bingung dulu, berikut penjelasannya:\nAtribut $table akan mengatur nama tabel yang kita pakai. Teman-teman juga bisa menggunakan nama tabel lain untuk autentikasi kalian. Seperti pengguna, mod_user, mod_anggota, dlsb. Bebaskanlah kreasi kalian, tapi tetap gunakan nama yang meaningful. Atribut $primaryKey akan mengatur Primary Key yang digunakan. Secara default, Laravel akan mengatur kolom ini ke id. Menurut dokumentasinya, Primary Key yang dimaksud oleh Laravel adalah sebuah kolom yang Auto Incrementable. Atribut $incrementing adalah penanda bahwa atribut $primaryKey adalah sebuah AUTO_INCREMENT. Karena USRID adalah bukan sebuah Auto Increment, maka Saya mengatur atribut ini ke false. Metode getAuthPassword(): string adalah sebuah metode yang digunakan oleh kontrak Authenticatable, yang dimana ia akan memberikan hashed value dari password yang ada di database, lalu diverifikasi oleh kontrak UserProvider. Pembahasan ini akan sangat mendalam untuk dibahas di artikel ini, jadi akan saya simpan untuk pembahasan lain hari. Intinya, teman-teman harus me-return value dari kolom \u0026ldquo;password\u0026rdquo; yang ada pada database. Daaaaaan, voilla. Aplikasi yang Anda bangun sekarang bisa diakses menggunakan tabel user legacy 🥳🎉. Sekian artikelnya, semoga bermanfaat. Jika ada pertanyaan terkait artikel ini, silahkan cantumkan di kolom komentar.\n","date":"1 Agustus 2022","externalUrl":null,"permalink":"/artikel/2022/08/01/autentikasi-custom-laravel-9-dengan-breeze/","section":"Artikel-artikel","summary":"","title":"Autentikasi Custom Laravel 9 Dengan Breeze","type":"artikel"},{"content":"Bayangkan di hari yang panas terik, kerjaan banyak, suntuk, haus juga. Semua perasaan jadi satu. Mau marah tapi nggak bisa, ngelawan hukum alam apalagi. Nggak ada solusi yang lebih ngepas ketimbang buat kopi dengan metode Filtered Iced Coffee Brew, atau lebih dikenal sebagai Japanese Cold Brew.\nMetode ini berbeda dari Cold Brew biasa yang biasanya menggunakan French Press, lalu disimpan di dalam lemari es dan didiamkan selama seharian penuh. Japanese Cold Brew menggunakan es batu, langsung di dalam server. Jadi kopi ini bisa langsung dinikmati tanpa harus menunggu esok hari.\nTulisan ini Saya rangkum dari channel YouTube James Hoffman di videonya yang berjudul Better than Cold Brew: How to make Iced Filter Coffee.\nPre-brewing # Untuk takaran kopi, gunakan rasio 65g / liter, atau 5g kopi lebih banyak dari Filtered Coffee biasa. Jadi semisal teman-teman biasa menggunakan 15g kopi untuk V60 atau Flat Bottom, tambahkan 5g lebih banyak untuk metode ini. Grind size harus sedikit lebih fine daripada Filtered Coffee. Di grinder Timemore Slim 3 yang saya gunakan, untuk Filtered Coffee biasa Saya gunakan grind setting 17. Jadi untuk Japanese Cold Brew ini, saya gunakan grind setting 14 s/d 15. Rasio air, karena ada keterlibatan es batu disini, kita harus membagi berapa gram yang harus dialokasikan untuk es batu, dan berapa gram yang dialokasikan untuk air panas. Untuk es batu, kita gunakan 40% dari total brew. Jadi jika kita mentargetkan 225g air untuk satu kali brew, maka kita bisa pakai 90g untuk es, dan 135g air panas. It\u0026rsquo;s Brewing Time # Sebelum membuat kopi, pastikan paper filter dibilas terlebih dahulu dengan air panas. Hal ini bertujuan untuk menghilangkan rasa kertas yang terdapat pada paper filter. Berpengaruh untuk sebagian orang, dan tidak berpengaruh untuk sebagian yang lain.\nMasukkan 90g es batu ke dalam server. Siapkan kopi di paper filter, kalian akan membutuhkan alat V60 atau Flat Bottom untuk metode ini. Tare timbangan kopi, dan mulai timer. Untuk fase blooming, gunakan 2x s/d 3x dari berat kopi. Jadi untuk 20g kopi, kalian akan membutuhkan 40g s/d 60g air. Fase blooming ini penting untuk mengatur acidity dari kopi, lebih sedikit air yang digunakan untuk blooming, maka rasa dari kopi akan semakin asam, semakin banyak maka akan semakin manis. Blooming phase akan berlangsung selama 45 detik. Brewing phase akan berlangsung selama 2,5 menit s/d 3 menit. Di jangka waktu ini, sisa air dibagi 3x pour. Jika sisa air adalah 75g, maka setiap pour adalah 25g. Interval setiap pour adalah 30 detik. Sama seperti blooming phase, fase ini bisa dibagi sesuai preferensi 2x s/d 4x pour. Dan selamat, teman-teman berhasil membuat Japanese Iced Coffee. Jika teman-teman masih bingung, berikut tabel brew timeline yang Saya gunakan sebagai referensi untuk setiap personal brew untuk kopi sebanyak 20g kopi. 90g es dan 135g air panas.\nWaktu Fase Pour Air 0s Blooming 40g 45s Brewing, gentle swirl 32g 1m 15s Brewing 32g 1m 45s Brewing, gentle stir 1x clockwise, 1x counter-clockwise 32g 2m 15s Tunggu sisa drawdown s/d 3m - Foto oleh Wade Austin Ellis dari Unsplash.\n","date":"24 Juli 2022","externalUrl":null,"permalink":"/artikel/2022/07/24/how-to-brew-filtered-iced-coffee/","section":"Artikel-artikel","summary":"","title":"Cara Membuat Filtered Iced Coffee (Japanese Brew)","type":"artikel"},{"content":"Siapa yang tidak tahu Faker? Sebuah library yang berguna untuk membuat Factory sehingga kita bisa mendapatkan ratusan, ribuan, atau jutaan data dummy dalam waktu singkat. Dalam update Laravel 9.x 28 hari lalu saat tulisan ini dibuat, Pull Request yang dibuat Tim Mac Donald disetujui dan di-merge ke Laravel 9.x.\nSeluruh pengguna Laravel 9 saat ini bisa menggunakan helper ini langsung pada view sehingga mempercepat proses mockup.\nContoh langsung cara penggunaan dari Pull Request di atas adalah sebagai berikut:\n// resources/views/sebuah-view.blade.php @for($i = 0; $i \u0026lt; 10; $i++) \u0026lt;dl\u0026gt; \u0026lt;dt\u0026gt;Name\u0026lt;/dt\u0026gt; \u0026lt;dd\u0026gt;{{ fake()-\u0026gt;name() }}\u0026lt;/dd\u0026gt; \u0026lt;dt\u0026gt;Phone\u0026lt;/dt\u0026gt; \u0026lt;dd\u0026gt;{{ fake()-\u0026gt;phoneNumber() }}\u0026lt;/dd\u0026gt; \u0026lt;/dl\u0026gt; @endfor Atau jika teman-teman ingin melihat cara penggunaan langsungnya, berikut adalah contoh dari sebuah projek yang Saya sedang kembangkan:\n@for ($i = 0; $i \u0026lt; 12; $i++) \u0026lt;a href=\u0026#34;{{ route(\u0026#39;show\u0026#39;, $productName = str()-\u0026gt;slug(fake()-\u0026gt;productName)) }}\u0026#34; class=\u0026#34;col-span-12 sm:col-span-6 md:col-span-4 lg:col-span-3 xl:col-span-2 border border-gray-200 rounded\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;aspect-w-1 aspect-h-1\u0026#34;\u0026gt; \u0026lt;img src=\u0026#34;https://via.placeholder.com/300.png\u0026#34; alt=\u0026#34;\u0026#34; class=\u0026#34;w-full h-full rounded-t object-cover\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;p-2\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;flex flex-col justify-between h-full\u0026#34;\u0026gt; \u0026lt;p class=\u0026#34;font-bold text-orange-500 tracking-wide\u0026#34;\u0026gt; Rp{{ number_format(rand(150000, 1000000), 0, \u0026#39;.\u0026#39;, \u0026#39;.\u0026#39;) }} \u0026lt;/p\u0026gt; \u0026lt;p class=\u0026#34;text-gray-600 text-sm mb-1\u0026#34;\u0026gt; {{ str()-\u0026gt;headline($productName) }} \u0026lt;/p\u0026gt; \u0026lt;p class=\u0026#34;flex text-xs items-center mb-2\u0026#34;\u0026gt; \u0026lt;x-phosphor-star-fill class=\u0026#34;w-4 h-4 text-yellow-400\u0026#34; /\u0026gt; \u0026lt;span class=\u0026#34;border-r border-gray-300 pr-2 mx-1\u0026#34;\u0026gt; {{ rand(1, 5) }} \u0026lt;/span\u0026gt; \u0026lt;span\u0026gt; {{ rand(1, 50) }} Ulasan \u0026lt;/span\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;button class=\u0026#34;transition duration-300 ease-in-out text-center w-full text-sm bg-blue-500 hover:bg-blue-600 rounded p-2 text-white mt-auto\u0026#34;\u0026gt; Lihat Produk \u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/a\u0026gt; @endfor dan berikut adalah hasilnya: Tim Mac Donald berhasil mewakilkan seluruh perasaan Saya pada proses prototyping dengan mengeluarkan statement\nWhen it comes to a Rapid Application Development framework like Laravel, speed of prototyping a feature is through the roof. But often you will want to prototype a view without having the even create any backing models or anything. You just wanna throw some tailwind and some blade at the problem and get some eyes on it.\nFoto oleh Mohammad Rahmani dari Unsplash.\n","date":"19 Juli 2022","externalUrl":null,"permalink":"/artikel/2022/07/19/percepat-pembuatan-mockup-laravel-menggunakan-helper-faker/","section":"Artikel-artikel","summary":"","title":"Percepat Pembuatan Mockup Laravel Menggunakan Helper Faker","type":"artikel"},{"content":"Hai, Devs! Hari ini Saya ingin sharing tentang 2 helper yang sangat membantu untuk membuat sebuah blog. Langsung ke inti saja.\nSlug # Pembuatan slug selalu digunakan pada hampir semua blog. Dari beberapa opsi URI ke detail tulisan di internet, beberapa adalah pilihannya:\nDate-Slug, biasanya ditulis dalam format YYYY-mm-dd-nama-blog. Biasanya digunakan oleh penulis yang menulis banyak artikel dalam satu hari. Date, hanya ditulis dalam format YYYY-mm-dd. Digunakan oleh penulis yang menulis satu artikel dalam satu hari, jarang digunakan. Slug, nama artikel ditulis dalam lowercase, serta menggunakan pemisah tanda strip sebagai pengganti spasi. Opsi ini paling banyak digunakan karena calon audiens bisa mengetahui sebagian isi konten hanya dengan melihat URL-nya saja. ID, detail artikel diambil dalam bentuk angka urut sesuai dengan apa yang disimpan pada database. Jarang digunakan dan juga tidak dianjurkan karena crawler bisa melihat berapa artikel yang ada di website tersebut. Selain itu, jika tidak ada pengaman yang diatur dalam controller, maka website tersebut akan rawan terkena SQL Injection. Kita ambil kasus pada saat kita akan menyimpan tulisan:\n\u0026lt;?php // app/Http/Controllers/BlogController.php use App\\Models\\Blog; use Illuminate\\Http\\Request; namespace App\\Http\\Controllers; class BlogController { // ... public function store(Request $request) { // ... $blog = new Blog; $blog-\u0026gt;title = $request-\u0026gt;title; // Input: Laravel 9 impact for the developers around the world $blog-\u0026gt;slug = strtolower(str_replace(\u0026#39; \u0026#39;, \u0026#39;-\u0026#39;, $request-\u0026gt;title)); // dd($blog-\u0026gt;slug); // laravel-9-impact-for-the-developers-around-the-world $blog-\u0026gt;save(); // ... } // ... } Kita bisa mempersimpel kode kita dengan menggunakan Fasad Str dari Laravel. Cara menggunakannya adalah sebagai berikut:\n\u0026lt;?php // app/Http/Controllers/BlogController.php use App\\Models\\Blog; use Illuminate\\Http\\Request; use Illuminate\\Support\\Str; namespace App\\Http\\Controllers; class BlogController { // ... public function store(Request $request) { // ... $blog = new Blog; $blog-\u0026gt;title = $request-\u0026gt;title; // Input: Laravel 9 impact for the developers around the world $blog-\u0026gt;slug = Str::slug($request-\u0026gt;title); // dd($blog-\u0026gt;slug); // laravel-9-impact-for-the-developers-around-the-world $blog-\u0026gt;save(); // ... } // ... } Lebih simpel bukan? Namun jika teman-teman lebih suka menggunakan helper ketimbang fasad, maka teman-teman bisa menggunakan kode berikut:\n\u0026lt;?php // app/Http/Controllers/BlogController.php use App\\Models\\Blog; use Illuminate\\Http\\Request; namespace App\\Http\\Controllers; class BlogController { // ... public function store(Request $request) { // ... $blog = new Blog; $blog-\u0026gt;title = $request-\u0026gt;title; // Input: Laravel 9 impact for the developers around the world $blog-\u0026gt;slug = str()-\u0026gt;slug($request-\u0026gt;title); // dd($blog-\u0026gt;slug); // laravel-9-impact-for-the-developers-around-the-world $blog-\u0026gt;save(); // ... } // ... } Tidak suka simbol - di URL teman-teman? Jangan khawatir, teman-teman bisa menggunakan _ dengan menambahkannya sebagai parameter ke-2:\n\u0026lt;?php // app/Http/Controllers/BlogController.php use App\\Models\\Blog; use Illuminate\\Http\\Request; namespace App\\Http\\Controllers; class BlogController { // ... public function store(Request $request) { // ... $blog = new Blog; $blog-\u0026gt;title = $request-\u0026gt;title; // Input: Laravel 9 impact for the developers around the world $blog-\u0026gt;slug = str()-\u0026gt;slug($request-\u0026gt;title, \u0026#39;_\u0026#39;); // dd($blog-\u0026gt;slug); // laravel_9_impact_for_the_developers_around_the_world $blog-\u0026gt;save(); // ... } // ... } Diambil dari dokumentasi str Helper Laravel, fungsi str() me-return sebuah instans dari Illuminate\\Support\\Stringable. Fungsi ini ekuivalen dengan Str::of.\nPenggunaan helper str() hanya tersedia pada Laravel 9 ke atas. Jika teman-teman menggunakan Laravel 8 ke bawah, gunakanlah fasad Str.\nHeadline # Jika teman-teman membuat blog berbahasa Inggris, teman-teman akan terbantu dengan helper ini. str()-\u0026gt;headline() mengubah huruf kecil ke huruf besar pada setiap hurufnya secara otomatis. Helper ini juga menghapus simbol - dan _. Sangat berguna jika teman-teman hanya menyimpan slug, dan tidak mempunyai kolom title.\nBerikut adalah contohnya:\n\u0026lt;?php // app/Http/Controllers/BlogController.php use App\\Models\\Blog; use Illuminate\\Http\\Request; namespace App\\Http\\Controllers; class BlogController { // ... public function store(Request $request) { // ... $blog = new Blog; // Headline input: \u0026#34;Create an immersive-blog using Laravel\u0026#34; $blog-\u0026gt;title = str()-\u0026gt;headline($request-\u0026gt;title); // dd($blog-\u0026gt;title) // \u0026#34;Create An Immersive Blog Using Laravel\u0026#34; $blog-\u0026gt;slug = str()-\u0026gt;slug($request-\u0026gt;title); $blog-\u0026gt;save(); // ... } // ... } Sekian informasi yang bisa saya tulis, semoga bermanfaat 👋. Thumbnail oleh Sincerely Media dari Unsplash.\n","date":"19 Juli 2022","externalUrl":null,"permalink":"/artikel/2022/07/19/membuat-blog-dengan-laravel-ketahui-2-helper-berguna-ini/","section":"Artikel-artikel","summary":"","title":"Membuat Blog Dengan Laravel? Ketahui 2 Helper Berguna Ini","type":"artikel"},{"content":"","date":"16 Juli 2022","externalUrl":null,"permalink":"/tags/codeigniter/","section":"Tags","summary":"","title":"Codeigniter","type":"tags"},{"content":"Halo semua, lama tidak berjumpa. Akhirnya bisa nulis lagi setelah sibuk di projek lain. Kali ini Saya ingin membahas salah satu class yang sering digunakan di CodeIgniter. Entah apakah teman-teman sadar atau tidak, kita sering berinteraksi dengan suatu class bernama IncomingRequest. Yuk kenalan lebih dalam.\nApa itu IncomingRequest class? # Class ini adalah suatu representatif berbentuk objek yang didapat dari sebuah HTTP Request yang dikirim dari sebuah client, seperti browser contohnya. Atau saat kalian membuat RESTful API, maka mungkin teman-teman menggunakan Postman sebagai client-nya. IncomingRequest berisi metode-metode dari \\CodeIgniter\\HTTP\\Request serta CodeIgniter\\HTTP\\Message.\nBagaimana cara menggunakan IncomingRequest? # Sebenarnya kita secara tidak sadar sering menggunakan class ini di sebuah Controller, dengan cara memanggil $this-\u0026gt;request. Nah, property request ini sebenarnya sudah terinstansiasi pada CodeIgniter\\Controller, jadi secara universal bisa diakses dari controller apapun.\nNamun jika teman-teman bukan berada pada sebuah controller dan ingin mengakses isi dari request, contoh kasus seperti pada saat membuat suatu service, teman-teman bisa menggunakan kode ini:\n\u0026lt;?php $request = \\Config\\Services::request(); Cara menentukan tipe request apa yang dipakai # Asal request bisa dibagi menjadi beberapa tipe, diantaranya melalui AJAX atau CLI. Request dari kedua tipe request tersebut bisa dicek menggunakan metode isAJAX() atau isCLI(), keduanya me-return boolean.\n\u0026lt;?php // Check for AJAX request. if ($request-\u0026gt;isAJAX()) { // ... } // Check for CLI Request if ($request-\u0026gt;isCLI()) { // ... } Metode isAJAX() sangat bergantung pada header X-Requested-With, yang sementara tidak dikirim secara default oleh XHR JavaScript (contoh: fungsi fetch). Teman-teman bisa menambahkan header ini secara manual.\nTeman-teman juga bisa mengecek metode yang dikirim dari HTTP dengan menggunakan metode getMethod().\n\u0026lt;?php // Returns \u0026#39;post\u0026#39; $method = $request-\u0026gt;getMethod(); Secara default, metode ini selalu mengirimkan value berbentuk lowercase, jadi jika teman-teman butuh versi uppercase-nya, bisa di-wrap dengan metode strtoupper\n\u0026lt;?php // Returns \u0026#39;GET\u0026#39; $method = strtoupper($request-\u0026gt;getMethod()); Mengambil data dari Input # Nah, ada beberapa kontroversi di komunitas CodeIgniter. Ada yang bilang bahwa \u0026ldquo;mengambil input harus dengan getVar() atau harus dengan getPost\u0026rdquo;, menurut Saya pribadi, teman-teman bebas bisa menggunakan apapun yang teman-teman suka asalkan sesuai konteksnya.\nMaksudnya sesuai konteks gimana? Pada suatu metode di suatu controller, jika metode tersebut berfokus pada request yang menggunakan GET, maka gunakanlah $request-\u0026gt;getGet(). Jika terfokus pada POST, maka gunakanlah $request-\u0026gt;getPost().\n\u0026ldquo;Semisal Saya sedang membuat RESTful API dan dari kedua metode tersebut tidak cocok seperti PUT atau PATCH, itu gimana?\u0026rdquo; gunakanlah $request-\u0026gt;getRawInput().\nIni adalah bagian dari standarisasi kode Saya, dimana tentu saja penggunaan metode dari sebuah class haruslah spesifik menurut konteks. Teman-teman tetap bebas menggunakan metode manapun karena tulisan ini hanya berisi opini pribadi.\nJadi, kurang lebih perbedaannya seperti ini:\nMetode HTTP Metode Request GET $request-\u0026gt;getGet() POST $request-\u0026gt;getPost() PUT/PATCH $request-\u0026gt;getRawInput() Sebagai tambahan, ada beberapa kasus dimana teman-teman mengirim request dari kedua HTTP method seperti GET dan POST secara bersamaan. Bagaimana cara mengecek apakah salah satu value ini ada di setiap method? Gunakan getGetPost() atau getPostGet():\nMetode Keterangan $request-\u0026gt;getGetPost() Mengecek GET dulu, lalu POST $request-\u0026gt;getPostGet() Mengecek POST dulu, lalu GET Mengambil sebuah value atau banyak value? # Tergantung pemakaian, sebagai contoh Saya biasa menggunakan mass-insert, jadi Saya nggak perlu capek-capek ngambil setiap data yang ada di request, terus diinput ke model:\n\u0026lt;?php // app/Controllers/BlogController.php class BlogController extends BaseController { public function store() { try { $data = $this-\u0026gt;request-\u0026gt;getPost([ \u0026#39;kolom_a\u0026#39;, \u0026#39;kolom_b\u0026#39;, \u0026#39;kolom_c\u0026#39;, ]); $blog = new \\App\\Models\\BlogModel; if (!$blog-\u0026gt;insert($data)) { return redirect() -\u0026gt;back() -\u0026gt;with(\u0026#39;errors\u0026#39;, $blog-\u0026gt;errors()) -\u0026gt;withInput(); } } catch (\\Throwable $th) { return redirect() -\u0026gt;back() -\u0026gt;with(\u0026#39;error\u0026#39;, \u0026#39;Gagal membuat blog: \u0026#39; . $th-\u0026gt;getMessage()); } return redirect() -\u0026gt;back() -\u0026gt;with(\u0026#39;success\u0026#39;, \u0026#39;Berhasil membuat blog\u0026#39;); } } Nah, jika asumsinya nama-nama field yang ada pada view adalah SAMA PERSIS dengan kolom-kolom yang ada di tabel, maka teman-teman bisa langsung ambil datanya dari request. Di atas adalah contoh kasus mengambil banyak value, lantas bagaimana kasusnya jika ingin mengambil satu value?\nDari kasus di atas, Saya akan ambil kasus dimana Saya ingin menambahkan thumbnail untuk blog Saya:\n\u0026lt;?php // app/Controllers/BlogController.php class BlogController extends BaseController { public function store() { try { $data = $this-\u0026gt;request-\u0026gt;getPost([ \u0026#39;kolom_a\u0026#39;, \u0026#39;kolom_b\u0026#39;, \u0026#39;kolom_c\u0026#39;, ]); $blog = new \\App\\Models\\BlogModel; if (!$blog-\u0026gt;insert($data)) { return redirect() -\u0026gt;back() -\u0026gt;with(\u0026#39;errors\u0026#39;, $blog-\u0026gt;errors()) -\u0026gt;withInput(); } $thumbnail = $this-\u0026gt;request-\u0026gt;getFile(\u0026#39;thumbnail\u0026#39;); if ($thumbnail != null \u0026amp;\u0026amp; $thumbnail-\u0026gt;isValid()) { $blog-\u0026gt;thumbnail = $thumbnail-\u0026gt;store(); $blog-\u0026gt;save(); } } catch (\\Throwable $th) { return redirect() -\u0026gt;back() -\u0026gt;with(\u0026#39;error\u0026#39;, \u0026#39;Gagal membuat blog: \u0026#39; . $th-\u0026gt;getMessage()); } return redirect() -\u0026gt;back() -\u0026gt;with(\u0026#39;success\u0026#39;, \u0026#39;Berhasil membuat blog\u0026#39;); } } Itu saja yang bisa Saya sampaikan dalam tulisan singkat ini, untuk referensi lebih lengkap silahkan kunjungi Dokumentasi CI4 - IncomingRequest. Sampai jumpa di kesempatan lain 👋.\n","date":"16 Juli 2022","externalUrl":null,"permalink":"/artikel/2022/07/16/kupas-ci4-incomingrequest-class/","section":"Artikel-artikel","summary":"","title":"Kupas CI4: IncomingRequest Class","type":"artikel"},{"content":"","date":"4 Juli 2022","externalUrl":null,"permalink":"/series/standarisasi-ci4/","section":"Series","summary":"","title":"Standarisasi CI4","type":"series"},{"content":"Hai, nggak kerasa udah 4 artikel yang sudah Saya tulis di seri \u0026ldquo;Standarisasi CI4\u0026rdquo;. Karena mungkin banyak dari teman-teman yang butuh rangkuman cepatnya, akan Saya kutip sesuai urutan flow pembuatan CRUD. Tapi jangan lupa untuk baca artikelnya satu-per-satu ya, karena banyak penjelasan yang cukup penting juga demi pemahaman teman-teman.\nEnvironment # CI_ENVIRONMENT digunakan untuk mengatur CI apakah error handling akan ditampilkan atau tidak. Konstanta ini merepresentasikan apakah aplikasi berada di lingkungan development atau production. app_baseURL digunakan untuk mengatur URL default aplikasi. database.default.database digunakan untuk mengatur database mana yang akan digunakan. database.default.username digunakan untuk mengatur username yang akan digunakan untuk mengakses database. database.default.password digunakan untuk mengatur password dari user yang akan digunakan untuk mengakses database. Routing # Single-method, digunakan jika suatu modul hanya memiliki sedikit metode; alternatif jika $routes-\u0026gt;resource() ataupun $routes-\u0026gt;presenter() dianggap mubazir. \u0026lt;?php // app/Config/Routes.php $routes-\u0026gt;get(\u0026#39;/user\u0026#39;, \u0026#39;UserController::index\u0026#39;); $routes-\u0026gt;get(\u0026#39;/user/new\u0026#39;, \u0026#39;UserController::new\u0026#39;); $routes-\u0026gt;get(\u0026#39;/user/(:num)/edit\u0026#39;, \u0026#39;UserController::edit/$1\u0026#39;); $routes-\u0026gt;get(\u0026#39;/user/(:num)\u0026#39;, \u0026#39;UserController::show/$1\u0026#39;); $routes-\u0026gt;post(\u0026#39;/user\u0026#39;, \u0026#39;UserController::create\u0026#39;); $routes-\u0026gt;patch(\u0026#39;/user/(:num)\u0026#39;, \u0026#39;UserController::update/$1\u0026#39;); // Gunakan Patch atau Put, sama saja. $routes-\u0026gt;put(\u0026#39;/user/(:num)\u0026#39;, \u0026#39;UserController::update/$1\u0026#39;); $routes-\u0026gt;delete(\u0026#39;/user/(:num)\u0026#39;, \u0026#39;UserController::delete/$1\u0026#39;); Restful routing, digunakan jika suatu modul adalah bagian dari CRUD atau RESTful API. \u0026lt;?php // app/Config/Routes.php $routes-\u0026gt;resource(\u0026#39;user\u0026#39;, [\u0026#39;controller\u0026#39; =\u0026gt; \u0026#39;UserController\u0026#39;]); $routes-\u0026gt;presenter(\u0026#39;blog\u0026#39;, [\u0026#39;controller\u0026#39; =\u0026gt; \u0026#39;BlogController\u0026#39;]); Option pada RESTful routing: \u0026lt;?php // app/Config/Routes.php $routes-\u0026gt;resource(\u0026#39;user\u0026#39;, [ \u0026#39;controller\u0026#39; =\u0026gt; \u0026#39;UserController\u0026#39;, // Nama Controller yang digunakan \u0026#39;placeholder\u0026#39; =\u0026gt; \u0026#39;:num\u0026#39;, // Mengatur regex yang digunakan controller untuk penerimaan parameter \u0026#39;websafe\u0026#39; =\u0026gt; true, // Mengatur versi yang aman untuk HTTP (hanya berlaku untuk resource, bukan presenter) \u0026#39;only\u0026#39; =\u0026gt; [\u0026#39;method1\u0026#39;, \u0026#39;method2\u0026#39;], // Mengatur metode apa saja yang bisa dipakai \u0026#39;except\u0026#39; =\u0026gt; [\u0026#39;method3\u0026#39;, \u0026#39;method4\u0026#39;], // Mengatur metode apa saja yang akan dibuang ]); Controller # Controller biasa. Penggunaan: digunakan jika modul tersebut digunakan untuk komputasi, dan bukan untuk CRUD. Bisa juga digunakan sebagai modul ekstensi dari sebuah modul CRUD. Command: php spark make:controller Nama --suffix Bare Controller. Penggunaan: digunakan jika seluruh controller tidak memiliki fungsi atau helper yang di-load secara default. Command: php spark make:controller Nama --suffix --bare Resource Controller. Penggunaan: digunakan bersamaan dengan $routes-\u0026gt;resource() pada konfigurasi rute. Biasanya digunakan dalam pembuatan RESTful API. Command: php spark make:controller Nama --suffix --restful controller Presenter Controller. Penggunaan: digunakan bersamaan dengan $routes-\u0026gt;presenter() pada konfigurasi rute. Biasanya digunakan dalam pembuatan CRUD. Gunakan opsi ini jika teman-teman ingin membuat modul CRUD dengan lebih cepat. Command: php spark make:controller Nama --suffix --restful presenter. Model # Buat model menggunakan command line dengan perintah php spark make:model Nama --suffix. Flag --suffix akan menambahkan kata Model di akhir nama secara otomatis. Atur penggunaan tabel yang digunakan pada suatu model dengan mengatur property $table. Atur bentuk balikan dari model dengan mengatur property $returnType ke array atau object. Property $allowedFields digunakan untuk mengatur kolom-kolom apa saja yang bisa diisi. Property $validationRules digunakan untuk mengatur validasi dari kolom-kolom yang ada di property $allowedFields. Property $validationMessages digunakan untuk mengatur pesan error dari validasi yang tidak memenuhi kriteria $validationRules. Model Callback berguna untuk memanipulasi data sebelum data tersebut diolah lebih lanjut. Validasi model akan berjalan saat fungsi insert(), update(), atau save() dipanggil. Daftar Pustaka # Standarisasi CI4 - Routing oleh Aghits Nidallah. Standarisasi CI4 - Environment oleh Aghits Nidallah. Standarisasi CI4 - Controller oleh Aghits Nidallah. Standarisasi CI4 - Model Query oleh Aghits Nidallah. ","date":"4 Juli 2022","externalUrl":null,"permalink":"/artikel/2022/07/04/standarisasi-ci4-tl-dr/","section":"Artikel-artikel","summary":"","title":"TL:DR Standarisasi CI4 Part 1 s/d Part 4","type":"artikel"},{"content":"Halo semua, kembali lagi di seri \u0026ldquo;Standarisasi CI4\u0026rdquo;. Kali ini kita akan membahas Controller, sebuah aspek dari MVC yang berguna untuk memanipulasi data.\nDi tulisan ini, kita akan mengupas tentang:\nNaming convention, bagaimana sebuah penamaan mempengaruhi kode. Bare Controller, sebuah controller kosong. Restful Controller, sebuah controller dengan beberapa metode yang sudahpre-generated. Naming Convention # Pada saat inisiasi sebuah projek CI4, mereka sendiri secara default tidak menamakan Controller Home sebagai HomeController, namun hanya Home saja. Untuk Saya, CI4 sendiri kesannya tidak ingin mengimplementasikan sebuah standar penamaan, tapi lebih kepada kebebasan penamaan.\nSecara teknis, anggaplah jika controller Home yang sudah ada sebagai bawaan CI4 ini adalah suatu controller di aplikasi besar. Secara nama, bagaimana kita mengidentifikasikan bahwa Home ini adalah sebuah controller?\nDari namespace yang terdapat pada file controller tersebut. Dengan melihat struktur folder dimana file itu berada. Dengan menggunakan Intellisense. Oke, ketiga poin di atas masuk akal. Tapi, apakah kita bisa menyimpulkan bahwa file tersebut adalah sebuah controller? Tentunya tidak. Karena itu, suffix dari sebuah controller sangatlah dibutuhkan. Bukan hanya controller, namun hampir dari semua modul seperti migration, seeder, filter, entity, dll. Sebab, suffix bisa merepresentasikan sebuah file, tanpa kita harus membuka apa isi dari file tersebut.\nUntuk membuat sebuah controller yang ber-suffix, kita jalankan:\nphp spark make:controller Home --suffix\nPerintah di atas akan secara otomatis menambahkan tambahan Controller di akhir nama. Perintah di atas akan membuat sebuah file baru bernama HomeController. Berikut contoh lainnya:\n# 1 php spark make:controller User --suffix CodeIgniter v4.2.1 Command Line Tool - Server Time: 2022-07-03 06:05:44 UTC-05:00 File created: APPPATH/Controllers/UserController.php # 2 php spark make:controller Master/Mahasiswa --suffix CodeIgniter v4.2.1 Command Line Tool - Server Time: 2022-07-03 06:06:39 UTC-05:00 File created: APPPATH/Controllers/Master/MahasiswaController.php Flag --suffix akan membantu kita membuat sebuah controller tanpa harus mengetikkan \u0026ldquo;Controller\u0026rdquo; secara eksplisit ke parameter nama.\nBare Controller # Bare Controller adalah sebuah controller kosong, teman-teman bisa mengisi metode dari controller ini sesuka hati. Gunakan Bare Controller jika teman-teman tidak menggunakan service atau helpers tambahan secara default.\nKenapa? Karena ada perbedaan secara fundamental dari controller yang dibuat dengan flag --bare dan tanpa --bare. Apa perbedaannya? Mari kita lihat.\n\u0026lt;?php // Dibuat dengan perintah `php spark make:controller ABare --bare --suffix` namespace App\\Controllers; use CodeIgniter\\Controller; class ABareController extends Controller { public function index() { // } } Lalu\n\u0026lt;?php // Dibuat dengan perintah `php spark make:controller WithoutBareFlag --suffix` namespace App\\Controllers; use App\\Controllers\\BaseController; class WithoutBareFlagController extends BaseController { public function index() { // } } Perbedaan dari kedua controller di atas adalah penggunaan ekstensi dari Parent Controller. Controller yang menggunakan flag --bare akan menggunakan controller bawaan dari CI4 yaitu CodeIgniter\\Controller, sementara controller yang tidak menggunakan flag --bare akan menggunakan App\\Controllers\\BaseController.\nLalu apa perbedaannya? Perbedaannya adalah, CodeIgniter\\Controller adalah class utama controller di CI4, sementara App\\Controllers\\BaseController adalah class yang meng-extends CodeIgniter\\Controller.\nJadi secara default, tidak ada perbedaan yang mendasar. Namun akan terdapat perbedaan jika Anda ingin \u0026ldquo;menerapkan sebuah helper atau service secara default di semua controller\u0026rdquo;. Seperti contoh jika teman-teman ingin menggunakan helper html dan text secara di seluruh controller:\n\u0026lt;?php // app/Controllers/BaseController.php namespace App\\Controllers; use CodeIgniter\\Controller; abstract class BaseController extends Controller { // ... protected $helpers = [\u0026#39;html\u0026#39;, \u0026#39;text\u0026#39;]; // ... Atau contoh lain jika teman-teman ingin membuat sebuah property session yang bisa diakses secara umum di semua controller:\n\u0026lt;?php // app/Controllers/BaseController.php namespace App\\Controllers; use CodeIgniter\\Controller; abstract class BaseController extends Controller { // ... /** * @var \\CodeIgniter\\Session\\Session; */ protected $session; public function initController(/* ... */) { // Do Not Edit This Line parent::initController($request, $response, $logger); $this-\u0026gt;session = \\Config\\Services::session(); } } Catatan: Jangan pernah mengedit file yang ada pada folder system bawaan CI4, jika ingin merubah behaviour dari sebuah class, buatlah sebuah child class yang meng-extends class tersebut. Biarkan script murni tanpa adanya modifikasi.\nRestful Controller # Restful Controller adalah sebuah controller yang didalamnya sudah terdapat berbagai macam metode yang bisa langsung digunakan untuk rute resource atau presenter (silahkan baca tulisan sebelumnya yang berjudul Standarisasi CI4 - Routing untuk detail lebih lanjut mengenai rute resource atau presenter).\nAda 2 tipe Restful Controller, Resource Controller dan Presenter Controller\nResource Controller # Resource Controller lebih cocok digunakan untuk pengembang yang sedang membuat REST API karena adanya kesesuaian metode-metode yang di-generate dengan $routes-\u0026gt;resource().\nCara membuat Resource Controller adalah php spark make:controller User --suffix --restful controller. Secara otomatis, perintah ini akan membuat sebuah controller yang meng-extends CodeIgniter\\RESTful\\ResourceConroller:\n\u0026lt;?php // app/Controllers/UserController namespace App\\Controllers; use CodeIgniter\\RESTful\\ResourceController; class UserController extends ResourceController { public function index() { } public function show($id = null) { } public function new() { } public function create() { } public function edit($id = null) { } public function update($id = null) { } public function delete($id = null) { } } Presenter Controller # Presenter Controller lebih cocok digunakan untuk pengembang yang sedang membuat fitur CRUD pada sebuah modul melalui website karena controller ini sesuai dengan metode-metode yang diimplementasikan oleh $routes-\u0026gt;presenter().\nCara membuat sebuah Presenter Controller adalah dengan menjalankan perintah php spark make:controller User --suffix --restful presenter. Secara otomatis, perintah ini akan membuat sebuah controller yang meng-extends CodeIgniter\\RESTful\\ResourcePresenter:\n\u0026lt;?php // app/Controllers/UserController namespace App\\Controllers; use CodeIgniter\\RESTful\\ResourcePresenter; class UserController extends ResourcePresenter { public function index() { } public function show($id = null) { } public function new() { } public function create() { } public function edit($id = null) { } public function update($id = null) { } public function remove($id = null) { } public function delete($id = null) { } } Daftar Pustaka # Extending the Controllers dari Dokumentasi CI4. Pengalaman menggunakan CI4 selama 6 bulan. ","date":"3 Juli 2022","externalUrl":null,"permalink":"/artikel/2022/07/03/standarisasi-ci4-controller/","section":"Artikel-artikel","summary":"","title":"Standarisasi CI4 - Controller","type":"artikel"},{"content":"Halo, selamat datang lagi ke seri artikel \u0026ldquo;Standarisasi CI4\u0026rdquo;. Kali ini kita akan membahas sebuah file khusus bernama .env (baca: dot-envi). \u0026ldquo;Kenapa kok ada titiknya?\u0026rdquo; karena simpelnya, dotenv ini adalah sebuah file pengaturan, dimana sistem UNIX (seperti Linux, dan Mac) tidak bisa membaca file ini secara eksplisit, walaupun Windows bisa.\nSaya membuat tulisan ini sebab keresahan pribadi Saya terhadap teman-teman yang mengatur konfigurasi sesuai mesinnya sendiri, tanpa memikirkan bagaimana kedepannya jika kode ini di-maintain pengembang lain.\nFile .env ini berisi pengaturan-pengaturan sensitif seperti kredensial database, key dari sebuah REST API, atau bahkan sebuah key untuk meng-enkripsi sebuah data yang ada pada database teman-teman.\nKenapa kita harus mengatur .env? # Jika kita bekerja dengan tim, kita harus mengetahui bahwa mesin setiap orang berbeda-beda. Bisa saja kasusnya seperti ini:\nDeveloper A Menggunakan XAMPP Databasenya memiliki password dengan nilai root. Base URL dari projectnya adalah localhost/project-ci Developer B Menggunakan Laragon Databasenya memiliki password dengan nilai toor. Base URL dari projectnya adalah project-ci.dev Developer C Menggunakan NGINX Databasenya tidak ber-password Base URL dari projectnya adalah 127.0.0.1:8080/project_ci Developer D Menggunakan Valet Databasenya memiliki password dengan nilai laravelisthebest. Base URL dari projectnya adalah project-ci.test Jika teman-teman menggunakan Git sebagai version control, maka tentu teman-teman akan mengetahui bahwa file config lokal akan terpantau perubahannya, dan akan terkirim ke upstream karena tidak masuk ke dalam daftar .gitignore. Karena itu, .env sangat berguna pada projek tim karena setiap pengembang bisa menyesuaikan konfigurasinya sendiri, secara independen sesuai mesin masing-masing.\nLalu bagaimana cara mengatur .env? # Sangat mudah, pada saat menginisialisasi suatu project CI4, Anda akan melihat struktur seperti ini:\n📁　project-ci/ ├─ 📁　app/ ├─ 📁　public/ ├─ 📁　system/ ├─ 📁　tests/ ├─ 📁　writable/ ├─ CHANGELOG.md ├─ composer.json ├─ deptrac.yaml ├─ env ├─ LICENSE ├─ README.md ├─ SECURITY.md ├─ spark Teman-teman bisa buka file bernama env, lalu simpan file tersebut sebagai file baru bernama .env. Jangan hapus atau rename file env biasa, file ini berfungsi sebagai template konfigurasi untuk developer lain.\nPENTING:\nUntuk mengatur sebuah konstanta environment variables, sangatlah mudah. Anda hanya perlu menghapus tanda # di awal sebuah konstanta, lalu mengatur isi dari konstanta itu setelah tanda =.\nMari kita bahas isi dari file .env ini satu-per-satu. Saya akan bahas pengaturan-pengaturan yang sering Saya gunakan:\nEnvironment # #-------------------------------------------------------------------- # ENVIRONMENT #-------------------------------------------------------------------- # CI_ENVIRONMENT = production Environment akan memberikan petunjuk ke CI4 apakah aplikasi ini ada di fase \u0026ldquo;Pengembangan\u0026rdquo;/development, atau \u0026ldquo;Dirilis\u0026rdquo;/production. Pengaturan ini mempengaruhi bagaimana CI4 meng-handle sebuah error.\nPada environment development semua error akan dikeluarkan oleh CI4, memberikan insight secara penuh terhadap keseluruhan error. Dimana letak kesalahan secara persis. **Anda harus mengatur konstan CI_ENVIRONMENT ke development pada masa pengembangan.\nLalu bagaimana CI4 meng-handle kode ketika CI_ENVIRONMENT diatur ke production? Simpelnya, seluruh error akan disembunyikan dalam sebuah halaman 500: Internal Server Error. Secara default, CI4 akan menampilkan halaman Whoops! We seem to have hit a snag. Please try again later\u0026hellip; Hanya atur CI_ENVIRONMENT ke production pada saat aplikasi sudah dirilis, karena kita tidak ingin pengguna tahu dimana letak error yang sebenarnya.\n\u0026ldquo;Lho, kalau kita nggak tau errornya dimana pas production, terus gimana caranya kita memperbaiki bug yang ada?\u0026rdquo; tenang, kita punya file Logs! Logs ini, menurut Saya adalah media \u0026ldquo;penyempurnaan\u0026rdquo; sebuah aplikasi. Jadi seluruh error yang terjadi pada saat production bisa teman-teman temukan di folder writable/logs/log-{date}.log. File ini akan menyimpan seluruh error yang terjadi pada hari itu.\nIsi dari sebuah file log, berisi kurang lebih seperti ini:\nCRITICAL - 2022-07-02 05:06:10 --\u0026gt; Class \u0026#34;App\\Controllers\\User\u0026#34; not found in APPPATH/Controllers/Home.php on line 9. 1 SYSTEMPATH/CodeIgniter.php(896): App\\Controllers\\Home-\u0026gt;index() 2 SYSTEMPATH/CodeIgniter.php(466): CodeIgniter\\CodeIgniter-\u0026gt;runController(Object(App\\Controllers\\Home)) 3 SYSTEMPATH/CodeIgniter.php(349): CodeIgniter\\CodeIgniter-\u0026gt;handleRequest(null, Object(Config\\Cache), false) 4 FCPATH/index.php(55): CodeIgniter\\CodeIgniter-\u0026gt;run() 5 /Users/shiroyuki/.composer/vendor/laravel/valet/server.php(234): require(\u0026#39;FCPATH/index.php\u0026#39;) Base URL # Saya bilang di atas bahwa:\nJika kita bekerja dengan tim, kita harus mengetahui bahwa mesin setiap orang berbeda-beda\nItu betul adanya, dan ini adalah salah satu pengaturan yang krusial. Karena setiap pengembang memiliki base URL yang berbeda-beda tergantung pada setiap host engine yang dimiliki.\n#-------------------------------------------------------------------- # APP #-------------------------------------------------------------------- # app_baseURL = \u0026#39;\u0026#39; Mengatur base URL sangat penting pada saat pemanggilan URL pada aplikasi. Kita akan simulasikan keadaan fungsi base_url() pada setiap developer jika mereka tidak mengatur konstanta app_baseURL.\nPerlu diketahui bahwa secara default, fungsi base_url() akan mengembalikan nilai http://localhost:8080. Berikut adalah hasil akses ke browser, jika setiap pengembang tidak mengatur konstanta app_baseURL terlebih dahulu:\nPengembang A: 404: Error Not Found Pengembang B: 404: Error Not Found Pengembang C: 200: OK Pengembang D: 404: Error Not Found Maka dari itu, setiap pengembang perlu mengatur konstansta app_baseURL-nya masing-masing. Jika disimulasikan, maka setiap pengembang akan mengatur konstanta app_baseURL menjadi seperti ini:\nPengembang A: Mengatur app_baseURL ke localhost/project-ci Hasil akses browser: 200: OK Pengembang B: Mengatur app_baseURL ke project-ci.dev Hasil akses browser: 200: OK Pengembang C: Mengatur app_baseURL ke 127.0.0.1:8080/project-ci Hasil akses browser: 200: OK Pengembang D: Mengatur app_baseURL ke project-ci.test Hasil akses browser: 200: OK Bagaimana? Sudah paham bagaimana pentingnya mengatur Base URL kan? Yuk kita lanjut ke pengaturan penting lainnya.\nDatabase # #-------------------------------------------------------------------- # DATABASE #-------------------------------------------------------------------- # database.default.hostname = localhost # database.default.database = ci4 # database.default.username = root # database.default.password = root # database.default.DBDriver = MySQLi # database.default.DBPrefix = Sama halnya seperti pengaturan konstanta app_baseURL, setiap pengembang juga mungkin memiliki pengaturan yang berbeda-beda dalam mengakses database-nya.\nSecara default, CI4 akan mengatur konfigurasi database seperti ini (akan Saya ambil beberapa pengaturan yang sering dipakai):\n// app/Config/Database.php public $default = [ \u0026#39;hostname\u0026#39; =\u0026gt; \u0026#39;localhost\u0026#39;, \u0026#39;username\u0026#39; =\u0026gt; \u0026#39;\u0026#39;, \u0026#39;password\u0026#39; =\u0026gt; \u0026#39;\u0026#39;, \u0026#39;database\u0026#39; =\u0026gt; \u0026#39;\u0026#39;, \u0026#39;DBDriver\u0026#39; =\u0026gt; \u0026#39;MySQLi\u0026#39;, \u0026#39;port\u0026#39; =\u0026gt; 3306, ]; Ini yang akan terjadi kepada seluruh pengembang tidak mengatur konfigurasi database pada saat mencoba mengambil data dari database:\nCodeIgniter\\Database\\Exceptions\\DatabaseException #8 Unable to connect to the database. Main connection [MySQLi]: Access denied for user \u0026#39;\u0026#39;@\u0026#39;localhost\u0026#39; (using password: NO) Ya, setiap pengembang tidak dapat terkoneksi ke database, bahkan pada konfigurasi XAMPP yang tidak menggunakan password secara default. Solusinya? Atur konstansta environment berikut sesuai mesin masing-masing:\nPengembang A: Mengatur database.default.username ke root Mengatur database.default.password ke root Pengembang B: Mengatur database.default.username ke root Mengatur database.default.password ke toor Pengembang C: Mengatur database.default.username ke root Pengembang D: Mengatur database.default.username ke root Mengatur database.default.password ke laravelisthebest Dengan ini, setiap pengembang tidak akan mendapatkan error terkait koneksi ke database. Rangkuman konstanta .env yang penting # CI_ENVIRONMENT, ubah ke development, production atau testing. Ubah ke development jika aplikasi dalam tahap pengembangan. Ubah ke produciton jika aplikasi sudah dirilis. Ubah ke testing jika ingin menjalankan test. app.baseURL, mengatur URL yang akan dipanggil oleh fungsi base_url(). Hal ini akan mempengaruhi pemanggilan assets dan routing. app.forceGlobalSecureRequests, atur ke true atau false. Pengaturan ini akan memaksa skema URL yang digunakan ke https. database.default.hostname, mengatur nama host dari server database yang dipakai. database.default.database, mengatur nama database yang ingin dipakai. database.default.username, mengatur username dari user dari host. database.default.password, mengatur password dari user yang terdaftar di host. database.default.DBDriver, mengatur engine dari database yang dipakai. Daftar Pustaka # Configuration dari Dokumentasi CI4 Pengalaman pribadi menggunakan CI4 selama 6 bulan. ","date":"2 Juli 2022","externalUrl":null,"permalink":"/artikel/2022/07/02/standarisasi-ci4-environment/","section":"Artikel-artikel","summary":"","title":"Standarisasi CI4 - Environment","type":"artikel"},{"content":"Hai, kembali lagi di seri tulisan \u0026ldquo;Standarisasi CI4\u0026rdquo;. Kali ini kita akan membahas tentang query builder yang terdapat pada CI4. Selama ini, Saya melihat penggunaan model pada CI4 ini tidak terarah. Ada yang pakai native SQL di class model-nya, ada yang nge-query secara native di Controller, wah banyak deh yang bisa bikin Model ini acak-acakan.\nBerikut adalah cuplikan kode dari sebuah project legacy yang Saya maintain:\n\u0026lt;?php // app/Models/SebuahModel.php class SebuahModel extends Model { public function daftarData($id) { $sql = \u0026#34;SELECT * FROM table WHERE id = ? ORDER BY col1, col2\u0026#34;; return $this-\u0026gt;db-\u0026gt;query($sql, $id); } } Setelah ngeliat kode di atas, secara spontan Saya langsung hadeh, kebiasaan. Tapi gimana lagi ya, Saya punya standar kode tersendiri untuk setiap Framework, dan CI4 bukanlah pengecualian. Jadi, ayo kita ulik apa yang salah, dan kembali ke kaidah per-CI-an yang benar dan lurus.\nNaming Convention # Seperti halnya controller serta modul-modul lain, model juga memerlukan suffix. Suffix di CI4 ini ibarat sebuah identitas untuk menyatakan bahwa class ini adalah bagian dari sebuah modul. Contohnya, BlogController adalah sebuah controller yang bernama Blog, lalu AuthenticationFilter adalah sebuah filter yang digunakan untuk autentikasi.\nSama halnya dengan modul lain, Model haruslah memiliki sebuah suffix untuk membedakan bahwa ia adalah sebuah model, bukan sebuah controller ataupun filter.\nUntuk membuat sebuah model dengan suffix Model, teman-teman memiliki 2 opsi:\nDengan menuliskan nama secara eksplisit, seperti contoh php spark make:model UserModel; atau Dengan menggunakan flag --suffix, seperti contoh php spark make:model User --suffix. Keduanya adalah perintah yang valid untuk membuat sebuah model bernama UserModel.\nGunakan Command untuk men-generate Model. # Jujur, Saya kurang suka ketika pada suatu projek CI4, pengembang tidak menggunakan Spark untuk membuat filter, migration, controller, bahkan di kasus ini model pun sampai dibikin manual. Mungkin karena terbawa suasana CI3 yang serba manual ya? Atau tidak mengetahui fitur spark ini?\nBerhubung teman-teman udah ada disini, yuk sekalian kita belajar cara membuat model dengan menggunakan Spark. Untuk membuat sebuah model, kita jalankan php spark make:model NamaModel, sebagai contoh:\nphp spark make:model User --suffix CodeIgniter v4.2.1 Command Line Tool - Server Time: 2022-07-02 03:38:01 UTC-05:00 File created: APPPATH/Models/UserModel.php Nah, perintah di atas akan membuat satu model bernamakan User, ayo kita cek apa isi di dalamnya:\n\u0026lt;?php // app/Models/UserModel.php namespace App\\Models; use CodeIgniter\\Model; class UserModel extends Model { protected $DBGroup = \u0026#39;default\u0026#39;; protected $table = \u0026#39;users\u0026#39;; protected $primaryKey = \u0026#39;id\u0026#39;; protected $useAutoIncrement = true; protected $insertID = 0; protected $returnType = \u0026#39;array\u0026#39;; protected $useSoftDeletes = false; protected $protectFields = true; protected $allowedFields = []; // Dates protected $useTimestamps = false; protected $dateFormat = \u0026#39;datetime\u0026#39;; protected $createdField = \u0026#39;created_at\u0026#39;; protected $updatedField = \u0026#39;updated_at\u0026#39;; protected $deletedField = \u0026#39;deleted_at\u0026#39;; // Validation protected $validationRules = []; protected $validationMessages = []; protected $skipValidation = false; protected $cleanValidationRules = true; // Callbacks protected $allowCallbacks = true; protected $beforeInsert = []; protected $afterInsert = []; protected $beforeUpdate = []; protected $afterUpdate = []; protected $beforeFind = []; protected $afterFind = []; protected $beforeDelete = []; protected $afterDelete = []; } \u0026ldquo;Buset, Ghits, kok banyak banget isinya? Kita sih nggak perlu kode-kode ini kali,\u0026rdquo; iya, tapi bentar dulu Saya jelasin sedikit, biar temen-temen juga paham. Kita akan pakai model ini sebagai contoh. Akan Saya hapus beberapa kode yang tidak diperlukan untuk demo ini.\nUntuk kali ini, kita tidak membutuhkan section Callback, dan Dates. Jadi akan kita hapus kedua section tersebut untuk mempersingkat kode. Juga, akan Saya hapus beberapa property yang secara otomatis diatur oleh CI4, jadi kurang lebih seperti ini hasilnya:\n\u0026lt;?php // app/Models/UserModel.php namespace App\\Models; use CodeIgniter\\Model; class UserModel extends Model { protected $table = \u0026#39;users\u0026#39;; protected $returnType = \u0026#39;array\u0026#39;; protected $allowedFields = []; protected $validationRules = []; protected $validationMessages = []; } Yuk kita bahas satu-per-satu tentang property-property yang ada:\n$table mengatur nama tabel yang dipakai oleh model. $returnType mengatur tipe keluaran pada saat kita menggunakan fungsi find() atau findAll(). Bisa diisi dengan array atau object. $allowedFields mengatur kolom-kolom apa saja yang boleh digunakan untuk diinput ke dalam tabel users. $validationRules mengatur validasi terhadap kolom-kolom yang digunakan. $validationMessages mengatur pesan-pesan ketika terjadi error pada saat memvalidasi data yang diinput. Fair enough ya? Yuk kita mulai gimana cara ngeimplementasiin kode ini, mengikuti standar yang Saya pakai.\nAtur property $returnType ke \u0026lsquo;object\u0026rsquo; # Gunanya adalah, supaya kita bisa menggunakan arrow function karena data yang di-return adalah berbentuk objek, bukan array. Bagi yang belum tahu, kode penggunaan arrow function adalah seperti ini:\n\u0026lt;?php // Objek echo $user-\u0026gt;name; // Array echo $user[\u0026#39;name\u0026#39;]; Secara pribadi, penggunaan arrow function ini lebih menghemat kode, karena untuk mengakses suatu data, kita hanya perlu menggunakan -\u0026gt;property, bukan ['property']. Lumayan lah, ngehemat 2 karakter juga. Kita akan coba implementasikan penggunaan model ini menjadi lebih singkat.\nJadi, pada model yang kita buat tadi, kita atur:\n\u0026lt;?php // app/Models/UserModel.php namespace App\\Models; use CodeIgniter\\Model; class UserModel extends Model { protected $table = \u0026#39;users\u0026#39;; protected $returnType = \u0026#39;object\u0026#39;; protected $allowedFields = []; protected $validationRules = []; protected $validationMessages = []; } Kita akan bahas property $allowedFields, $validationRules, dan $validationMessages lebih lanjut nanti.\nImplementasi Model pada Controller # Nah, jika teman-teman baca tulisan kemarin Saya yang berjudul Standarisasi CI4 - Routing dan Standarisasi CI4 - Controller, teman-teman pasti menyadari bahwa $route-\u0026gt;presenter akan sedara otomatis meng-assign 8 metode (7 metode untuk $route-\u0026gt;resource) yang digunakan pada Controller kan? Jika lupa, berikut metode-metode yang diassign:\nGET index GET show/$1 GET new POST create, atau bisa juga pakai POST / GET edit/$1 POST update/$1 GET remove/$1 (tidak ada di $route-\u0026gt;resource) POST delete/$1 Kita akan mencoba mengimplementasikan best-pratice dari model yang telah kita buat ke masing-masing metode di controller tersebut. Implementasi query builder pada controller menurut Saya adalah best practice dari CI4. Penggunaan bisa bervariasi tergantung masing-masing pengembang.\nImplementasi pada index() # Metode index() pada sebuah controller, biasanya mengembalikan sebuah view yang berisi seluruh data yang tersedia pada sebuah tabel.\n\u0026lt;?php // app/Controllers/UserController.php namespace App\\Controllers; use App\\Models\\UserModel; use CodeIgniter\\RESTful\\ResourcePresenter; class UserController extends ResourcePresenter { public function index() { return view(\u0026#39;users/index\u0026#39;, [ \u0026#39;users\u0026#39; =\u0026gt; (new UserModel())-\u0026gt;findAll(), ]); } } Agak asing? Atau sudah pernah pakai? Intinya, seluruh query dilakukan langsung di controller, bukan di dalam model. Nanti kita akan bahas kenapa kita juga butuh query di dalam model di akhir artikel.\nImplementasi pada show() # Metode show() biasanya menampilkan sebuah detail dari suatu model. Jika index() hanya menampilkan seluruh data secara singkat, makan show() ini digunakan untuk menampilkan salah satu data secara detail.\nBerikut adalah implementasinya:\n\u0026lt;?php // app/Controllers/UserController.php namespace App\\Controllers; use App\\Models\\UserModel; use CodeIgniter\\RESTful\\ResourceController; class UserController extends ResourceController { public function show($id = null) { if ((new UserModel())-\u0026gt;find($id) === null) return \u0026#34;Data tidak ditemukan\u0026#34;; return view(\u0026#39;users/show\u0026#39;, [ \u0026#39;user\u0026#39; =\u0026gt; (new UserModel())-\u0026gt;find($id), ]); } } Karena model pada CI4 tidak memiliki metode findOrFail() secara default, maka simpelnya, kita butuh mengecek dulu apakah data tersebut ada di database, jika tidak ada maka tampilkan pesan \u0026ldquo;Data tidak ditemukan\u0026rdquo;. Contoh di atas adalah contoh kasar, implementasi kode mungkin berbeda untuk tiap projek.\nImplementasi pada new() # Sebenarnya tidak ada yang perlu diimplementasi pada metode ini selain kita hanya butuh mereturn instance dari view(). Namun ada kalanya kita ingin menampilkan beberapa data untuk sebuah dropdown atau pilihan\n\u0026lt;?php // app/Controllers/UserController.php namespace App\\Controllers; use App\\Models\\ProvinceModel; use App\\Models\\UserModel; use CodeIgniter\\RESTful\\ResourcePresenter; class UserController extends ResourcePresenter { public function new() { return view(\u0026#39;users/new\u0026#39;, [ \u0026#39;provinces\u0026#39; =\u0026gt; (new ProvinceModel)-\u0026gt;findAll(), ]); } } Implementasi pada create() # Nah disinilah dimana beberapa akan diimplementasi. Pertama kita akan mengatur $allowedFields, $validationRules dan $validationMessages pada model, lalu kita akan menggunakan try-catch block pada saat menyimpan data.\nAnggap saja pada tabel users kita memiliki kolom sebagai berikut:\nmysql\u0026gt; describe users; +------------+--------------+------+-----+-------------------+-------------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+-------------------+-------------------+ | id | int | NO | PRI | NULL | auto_increment | | email | varchar(255) | NO | UNI | NULL | | | name | varchar(255) | NO | | NULL | | | password | varchar(255) | NO | | NULL | | | created_at | timestamp | NO | | CURRENT_TIMESTAMP | DEFAULT_GENERATED | | updated_at | timestamp | YES | | NULL | | +------------+--------------+------+-----+-------------------+-------------------+ 6 rows in set (0.03 sec) Kita akan atur $allowedFields terlebih dahulu pada UserModel, kurang lebih seperti ini:\n\u0026lt;?php // app/Models/UserModel.php namespace App\\Models; use CodeIgniter\\Model; class UserModel extends Model { // ... protected $allowedFields = [ \u0026#39;email\u0026#39;, \u0026#39;name\u0026#39;, \u0026#39;password\u0026#39;, ]; // ... } Lalu, kita atur $validationRules dan $validationMessages seperti ini:\n\u0026lt;?php // app/Models/UserModel.php namespace App\\Models; use CodeIgniter\\Model; class UserModel extends Model { // ... protected $validationRules = [ \u0026#39;email\u0026#39; =\u0026gt; \u0026#39;required|valid_email|is_unique[users.email,id,{id}]\u0026#39;, \u0026#39;name\u0026#39; =\u0026gt; \u0026#39;required|alpha_numeric_space|min_length[3]\u0026#39;, \u0026#39;password\u0026#39; =\u0026gt; \u0026#39;required|min_length[8]\u0026#39;, \u0026#39;password_confirmation\u0026#39; =\u0026gt; \u0026#39;required_with[password]|matches[password]\u0026#39;, ]; protected $validationMessages = [ \u0026#39;email\u0026#39; =\u0026gt; [ \u0026#39;valid_email\u0026#39; =\u0026gt; \u0026#39;Email harus memiliki format yang valid.\u0026#39;, \u0026#39;is_unique\u0026#39; =\u0026gt; \u0026#39;Maaf, email sudah terdaftar. Silahkan coba menggunakan email lain.\u0026#39;, ], \u0026#39;name\u0026#39; =\u0026gt; [ \u0026#39;min_length\u0026#39; =\u0026gt; \u0026#39;Panjang minimal untuk nama adalah {param} karakter.\u0026#39;, ], \u0026#39;password\u0026#39; =\u0026gt; [ \u0026#39;min_length\u0026#39; =\u0026gt; \u0026#39;Panjang minimal untuk password adalah {param} karakter.\u0026#39;, ], \u0026#39;password_confirmation\u0026#39; =\u0026gt; [ \u0026#39;matches\u0026#39; =\u0026gt; \u0026#39;Password tidak sama.\u0026#39;, ], ]; // ... } Pelajari lebih lengkap tentang validation di dokumentasi CI4 bagian Validation.\nNah setelah mengatur semua itu pada model, langsung saja kita implementasikan pada controller, berikut kodenya:\n\u0026lt;?php namespace App\\Controllers; use App\\Models\\UserModel; use CodeIgniter\\RESTful\\ResourcePresenter; class UserController extends ResourcePresenter { public function create() { try { $user = new UserModel(); $data = $this-\u0026gt;request-\u0026gt;getPost([\u0026#39;name\u0026#39;, \u0026#39;email\u0026#39;, \u0026#39;password\u0026#39;, \u0026#39;password_confirmation\u0026#39;]); if ($user-\u0026gt;insert($data) === false) { return redirect()-\u0026gt;back()-\u0026gt;with(\u0026#39;errors\u0026#39;, $user-\u0026gt;errors()); } } catch (\\Throwable $th) { return redirect()-\u0026gt;back()-\u0026gt;with(\u0026#39;error\u0026#39;, \u0026#39;Gagal menambahkan data: \u0026#39; . $th-\u0026gt;getMessage()); } return redirect()-\u0026gt;back()-\u0026gt;with(\u0026#39;success\u0026#39;, \u0026#39;Berhasil menambahkan data\u0026#39;); } } Ya, akan ada 3 return yang diskenariokan.\nReturn ketika model gagal menyimpan data karena validasi gagal. Validasi akan dijalankan secara otomatis pada saat menjalankan fungsi insert(), update(), atau save(). Return ketika terjadi error pada server atau secara syntax pada bagian blok catch. Return ketika model berhasil menyimpan data. Catatan: kode di atas bukanlah kode terbaik untuk menyimpan User, karena password akan disimpan secara plaintext. Untuk menyimpan password yang secara otomatis ter-enkripsi, teman-teman perlu mengatur bagian callback pada model.\n\u0026lt;?php // app/Models/UserModel.php namespace App\\Models; use CodeIgniter\\Model; class UserModel extends Model { // ... protected $allowCallbacks = true; protected $beforeInsert = [\u0026#39;hash_password\u0026#39;]; protected $beforeUpdate = [\u0026#39;hash_password\u0026#39;]; protected function hash_password(array $properties) { if (!isset($properties[\u0026#39;data\u0026#39;][\u0026#39;password\u0026#39;])) { return $properties; } $properties[\u0026#39;data\u0026#39;][\u0026#39;password\u0026#39;] = password_hash($properties[\u0026#39;data\u0026#39;][\u0026#39;password\u0026#39;], PASSWORD_DEFAULT); return $properties; } // ... } Kode di atas akan mengenkripsi password pada saat menambahkan dan memperbarui data.\nImplementasi pada edit() # Sama halnya seperti new(), hanya saja disini kita harus mengambil data model terlebih dulu. Disini gabungkan kode dari show() serta new(). Pertama kita harus mengecek dulu apakah model dengan id yang dicari tersedia atau tidak, jika tersedia, maka return sebuah view.\n\u0026lt;?php // app/Controllers/UserController.php namespace App\\Controllers; use App\\Models\\ProvinceModel; use App\\Models\\UserModel; use CodeIgniter\\RESTful\\ResourcePresenter; class UserController extends ResourcePresenter { public function edit($id = null) { if ((new UserModel())-\u0026gt;find($id) === null) return \u0026#34;Data tidak ditemukan\u0026#34;; return view(\u0026#39;users/new\u0026#39;, [ \u0026#39;provinces\u0026#39; =\u0026gt; (new ProvinceModel())-\u0026gt;findAll(), \u0026#39;user\u0026#39; =\u0026gt; (new UserModel())-\u0026gt;find($id), ]); } } Implementasi pada update() # Untuk kode update, ini lumayan panjang karena ada beberapa lapisan yang harus divalidasi.\n\u0026lt;?php // app/Controllers/UserController.php namespace App\\Controllers; use App\\Models\\ProvinceModel; use App\\Models\\UserModel; use CodeIgniter\\RESTful\\ResourcePresenter; class UserController extends ResourcePresenter { public function update($id = null) { if ((new UserModel())-\u0026gt;find($id) === null) return \u0026#34;Data tidak ditemukan\u0026#34;; try { $user = new UserModel(); $data = $this-\u0026gt;request-\u0026gt;getPost([\u0026#39;name\u0026#39;, \u0026#39;email\u0026#39;]); $data[\u0026#39;id\u0026#39;] = $id; // Memperbarui password if ( $this-\u0026gt;request-\u0026gt;getPost(\u0026#39;password\u0026#39;) !== \u0026#34;\u0026#34; \u0026amp;\u0026amp; $user-\u0026gt;update($id, $this-\u0026gt;request-\u0026gt;getPost([\u0026#39;password\u0026#39;, \u0026#39;password_confirmation\u0026#39;])) === false ) { return redirect()-\u0026gt;back()-\u0026gt;with(\u0026#39;errors\u0026#39;, $user-\u0026gt;errors()); } // Memperbarui data if ($user-\u0026gt;update($id, $data) === false) { return redirect()-\u0026gt;back()-\u0026gt;with(\u0026#39;errors\u0026#39;, $user-\u0026gt;errors()); } } catch (\\Throwable $th) { return redirect()-\u0026gt;back()-\u0026gt;with(\u0026#39;error\u0026#39;, \u0026#39;Gagal memperbarui data: \u0026#39; . $th-\u0026gt;getMessage()); } return redirect()-\u0026gt;back()-\u0026gt;with(\u0026#39;success\u0026#39;, \u0026#39;Berhasil memperbarui data\u0026#39;); } } Kode di atas jika dijelaskan kurang lebih seperti ini:\nMengecek apakah data ada pada database; Meng-assign data yang ada pada $this-\u0026gt;request ke variabel $data, lalu memasukkan id untuk menghindari error pada saat unique validation. Bisa teman-teman baca terkait ini di bagian Validation Placeholder. Memperbarui password jika kolom password diisi ada. Memperbarui data user. Implementasi pada remove() # Kurang lebih sama seperti show(), implementasi view remove() kurang lebih hanya menampilkan form pesan konfirmasi seperti Apakah Anda yakin ingin menghapus User atas nama {n}?.\n\u0026lt;?php // app/Controllers/UserController.php namespace App\\Controllers; use App\\Models\\ProvinceModel; use App\\Models\\UserModel; use CodeIgniter\\RESTful\\ResourcePresenter; class UserController extends ResourcePresenter { public function remove($id = null) { if ((new UserModel())-\u0026gt;find($id) === null) return \u0026#34;Data tidak ditemukan\u0026#34;; return view(\u0026#39;users/remove\u0026#39;, [ \u0026#39;user\u0026#39; =\u0026gt; (new UserModel())-\u0026gt;find($id), ]); } * Implementasi pada delete() # Metode ini digunakan untuk menghapus data. Sangat straightforward\n\u0026lt;?php // app/Controllers/UserController.php namespace App\\Controllers; use App\\Models\\ProvinceModel; use App\\Models\\UserModel; use CodeIgniter\\RESTful\\ResourcePresenter; class UserController extends ResourcePresenter { public function delete($id = null) { if ((new UserModel())-\u0026gt;find($id) === null) return \u0026#34;Data tidak ditemukan\u0026#34;; try { (new UserModel())-\u0026gt;delete($id); } catch (\\Throwable $th) { return redirect()-\u0026gt;back()-\u0026gt;with(\u0026#39;error\u0026#39;, \u0026#39;Gagal menghapus data: \u0026#39; . $th-\u0026gt;getMessage()); } return redirect()-\u0026gt;back()-\u0026gt;with(\u0026#39;success\u0026#39;, \u0026#39;Berhasil menghapus data\u0026#39;); } } daaan selesai! Kita sudah berada di akhir artikel. Terima kasih untuk teman-teman yang sudah membaca seri \u0026ldquo;Menstandarisasi CI4\u0026rdquo;. Masih ada beberapa tulisan di seri ini untuk kedepannya, dan tentunya akan lebih menarik lagi.\nSpoiler: modul yang akan dibahas adalah Migration, Seeder, serta Filter 👀. Stay tuned ya!\nDaftar Pustaka: # Dokumentasi CI4, Validation Dokumentasi CI4, Defining Callbacks ","date":"2 Juli 2022","externalUrl":null,"permalink":"/artikel/2022/07/02/standarisasi-ci4-model-query/","section":"Artikel-artikel","summary":"","title":"Standarisasi CI4 - Model Query","type":"artikel"},{"content":"CodeIgniter, siapa yang tidak kenal dengan salah satu framework PHP ini? Mungkin bagi teman-teman yang baru masuk ke dunia PHP, framework ini sering dibicarakan sebagai salah satu framework yang beginner-friendly. Bagaimana tidak? Kodenya yang native-like, kode yang mirip sekali dengan kode PHP Native, sehingga semua orang bisa memahaminya.\nNamun ada salah satu hal, yang tidak Saya sukai tentang CodeIgniter ini. Tidak adanya standarisasi di setiap kodenya, bahkan dokumentasinya terlalu acak, sehingga dari banyaknya tutorial di luar sana, tidak ada satupun yang sama. Hampir dari seluruh tutorial CodeIgniter yang pernah Saya ikuti berbeda. Entah itu berdasarkan code of conductnya, standar kodenya, semua mengikuti preferensi sendiri-sendiri. Sehingga tiba pada saatnya di sebuah projek CodeIgniter besar yang membutuhkan tim untuk mengerjakannya, kode menjadi acak-acakan dan tidak beraturan.\nKarena itu, di seri \u0026ldquo;Standarisasi CodeIgniter\u0026rdquo; ini, saya akan mencoba untuk memperbaiki, serta menstandarisasi beberapa hal, sehingga kode yang ditulis menjadi lebih rapi. Tentu saja ini adalah tulisan berdasarkan opini pribadi, sehingga teman-teman bisa mengkritisi, menyarankan, atau bahkan menyanggah opini Saya di kolom komentar.\nRouting # Routing adalah suatu aspek paling penting yang ada di sebuah Framework yang berbasis MVC (Model, View, Controller). Beberapa hal yang Saya benci dari hal ini adalah segmentasi URL. Banyak dari tutorial yang Saya ikuti, menggunakan getSegment() untuk mendapatkan sebuah segmentasi dari URL. Menurut Saya, hal ini dapat dipersingkat.\nSatu hal yang pasti, sebelum memulai ini, nonaktifkan fitur Auto-routing pada CI4, menurut dokumentasi pada file app/Config/Routes.php: The Auto Routing (legacy) is very dangerous. It is easy to create vulnerable apps where controller filters or CSRF are bypassed. Uncomment baris 28 pada app/Config/Routes.php untuk menonaktifkan fitur Auto-routing.\nAnggaplah pada suatu tutorial, ada salah satu rute seperti ini:\n\u0026lt;?php // app/Config/Routes.php $routes-\u0026gt;get(\u0026#39;/users/(:num)/edit\u0026#39;, \u0026#39;UserController::edit\u0026#39;); dan, pada app/Controllers/UserController.php@edit, berisi:\n\u0026lt;?php // app/Controllers/UserController.php public function edit() { $uri = current_url(true); $user_id = $uri-\u0026gt;getSegment(3); echo $user_id; } Saya akui, fungsi segmentasi URL adalah suatu layanan CI4 yang sangat kuat, namun hal ini akan berpengaruh sangat besar, ketika Anda menggunakan fitur Route resource CI4. Kita akan coba perbaiki kode di atas menjadi seperti berikut:\n\u0026lt;?php // app/Config/Routes.php $routes-\u0026gt;get(\u0026#39;/users/(:num)/edit\u0026#39;, \u0026#39;UserController::edit/$1\u0026#39;); Kode di atas, kurang lebih sama, namun CI4 akan segmentasi :num sebagai parameter pertama pada metode edit().\n\u0026lt;?php // app/Controllers/UserController.php public function edit($user_id) { echo $user_id; } Dengan kode yang lebih singkat, kita bisa melakukan semua hal yang terkait dengan segmentasi menjadi efisien, bahkan ketika ada perubahan skema routing terjadi\n\u0026lt;?php // app/Config/Routes.php $routes-\u0026gt;get(\u0026#39;/users/edit_user/(:num)\u0026#39;, \u0026#39;UserController::edit/$1\u0026#39;); UserController masih akan bisa menerima parameter pertama dengan normal:\n\u0026lt;?php // app/Controllers/UserController.php public function edit($user_id) { echo $user_id; } Route Resource # Route resource adalah sebuah fitur yang ada pada CI4 yang berguna untuk mempersingkat CRUD. Mengapa demikian? Bayangkan teman-teman memiliki beberapa rute seperti ini:\n\u0026lt;?php // app/Config/Routes.php $routes-\u0026gt;get(\u0026#39;/user\u0026#39;, \u0026#39;UserController::index\u0026#39;); $routes-\u0026gt;get(\u0026#39;/user/new\u0026#39;, \u0026#39;UserController::new\u0026#39;); $routes-\u0026gt;get(\u0026#39;/user/(:num)/edit\u0026#39;, \u0026#39;UserController::edit/$1\u0026#39;); $routes-\u0026gt;get(\u0026#39;/user/(:num)\u0026#39;, \u0026#39;UserController::show/$1\u0026#39;); $routes-\u0026gt;post(\u0026#39;/user\u0026#39;, \u0026#39;UserController::create\u0026#39;); $routes-\u0026gt;patch(\u0026#39;/user/(:num)\u0026#39;, \u0026#39;UserController::update/$1\u0026#39;); // Gunakan Patch atau Put, sama saja. $routes-\u0026gt;put(\u0026#39;/user/(:num)\u0026#39;, \u0026#39;UserController::update/$1\u0026#39;); $routes-\u0026gt;delete(\u0026#39;/user/(:num)\u0026#39;, \u0026#39;UserController::delete/$1\u0026#39;); Tentunya me-manage rute di atas sangatlah tidak efisien, selain memperbanyak file Routes, file Routes juga sulit dibaca karena banyaknya kode, hanya untuk 1 modul CRUD. Solusinya? Route Resource!\n// app/Config/Routes.php $routes-\u0026gt;resource(\u0026#39;user\u0026#39;, [ \u0026#39;controller\u0026#39; =\u0026gt; \u0026#39;UserController\u0026#39;, ]); CI4 akan mengintruksikan routing dengan URI user menggunakan UserController sebagai Controller Resource. Jika kita jalankan php spark routes pada terminal, maka kita akan dapatkan response sebagai berikut:\nphp spark routes +--------+----------------+--------------------------------------------+----------------+---------------+ | Method | Route | Handler | Before Filters | After Filters | +--------+----------------+--------------------------------------------+----------------+---------------+ | GET | user | \\App\\Controllers\\UserController::index | | toolbar | | GET | user/new | \\App\\Controllers\\UserController::new | | toolbar | | GET | user/(.*)/edit | \\App\\Controllers\\UserController::edit/$1 | | toolbar | | GET | user/(.*) | \\App\\Controllers\\UserController::show/$1 | | toolbar | | POST | user | \\App\\Controllers\\UserController::create | | toolbar | | PATCH | user/(.*) | \\App\\Controllers\\UserController::update/$1 | | toolbar | | PUT | user/(.*) | \\App\\Controllers\\UserController::update/$1 | | toolbar | | DELETE | user/(.*) | \\App\\Controllers\\UserController::delete/$1 | | toolbar | | CLI | ci(.*) | \\CodeIgniter\\CLI\\CommandRunner::index/$1 | | | +--------+----------------+--------------------------------------------+----------------+---------------+ Referensi Route Resource lengkap adalah sebagai berikut:\n\u0026lt;?php // app/Config/Routes.php $routes-\u0026gt;resource(\u0026#39;uri\u0026#39;, $options); $options yang Route Resource CI4 terima adalah sebagai berikut:\ncontroller, mengatur Controller apa yang akan digunakan.\nAnda bisa menggunakan opsi ini seperti:\n\u0026lt;?php // app/Config/Routes.php $routes-\u0026gt;resource(\u0026#39;user\u0026#39;, [\u0026#39;controller\u0026#39; =\u0026gt; \u0026#39;UserController\u0026#39;]); $routes-\u0026gt;resource(\u0026#39;blog\u0026#39;, [\u0026#39;controller\u0026#39; =\u0026gt; \u0026#39;BlogController\u0026#39;]); $routes-\u0026gt;resource(\u0026#39;comments\u0026#39;, [\u0026#39;controllers\u0026#39; =\u0026gt; \u0026#39;Blog/CommentsController\u0026#39;]); placeholder, mengatur regex yang digunakan oleh Router. Secara default, CI4 menerima (:any) atau (.*) yang berarti menerima seluruh bentuk parameter, entah itu numerik, huruf, atau simbol.\n\u0026lt;?php // app/Config/Routes.php $routes-\u0026gt;resource(\u0026#39;user\u0026#39;, [ \u0026#39;controller\u0026#39; =\u0026gt; \u0026#39;UserController\u0026#39;, \u0026#39;placeholder\u0026#39; =\u0026gt; \u0026#39;:num\u0026#39;, ]); Opsi ini akan mengintruksikan Router untuk menerima hanya numerik sebagai parameter. Jika kita jalankan php spark routes pada terminal, maka ini yang akan ditampilkan:\nphp spark routes +--------+--------------------+--------------------------------------------+----------------+---------------+ | Method | Route | Handler | Before Filters | After Filters | +--------+--------------------+--------------------------------------------+----------------+---------------+ | GET | user | \\App\\Controllers\\UserController::index | | toolbar | | GET | user/new | \\App\\Controllers\\UserController::new | | toolbar | | GET | user/([0-9]+)/edit | \\App\\Controllers\\UserController::edit/$1 | | toolbar | | GET | user/([0-9]+) | \\App\\Controllers\\UserController::show/$1 | | toolbar | | POST | user | \\App\\Controllers\\UserController::create | | toolbar | | PATCH | user/([0-9]+) | \\App\\Controllers\\UserController::update/$1 | | toolbar | | PUT | user/([0-9]+) | \\App\\Controllers\\UserController::update/$1 | | toolbar | | DELETE | user/([0-9]+) | \\App\\Controllers\\UserController::delete/$1 | | toolbar | | CLI | ci(.*) | \\CodeIgniter\\CLI\\CommandRunner::index/$1 | | | +--------+--------------------+--------------------------------------------+----------------+---------------+ websafe, jika Anda mengetahui bahwa protokol HTTP tidak dapat menggunakan metode seperti PATCH, PUT, atau DELETE, maka inilah solusinya.\n\u0026lt;?php // app/Config/Routes.php $routes-\u0026gt;resource(\u0026#39;user\u0026#39;, [ \u0026#39;controller\u0026#39; =\u0026gt; \u0026#39;UserController\u0026#39;, \u0026#39;placeholder\u0026#39; =\u0026gt; \u0026#39;:num\u0026#39;, \u0026#39;websafe\u0026#39; =\u0026gt; true, ]); Kode di atas akan menghasilkan rute seperti ini:\nphp spark routes +--------+----------------------+--------------------------------------------+----------------+---------------+ | Method | Route | Handler | Before Filters | After Filters | +--------+----------------------+--------------------------------------------+----------------+---------------+ | GET | user | \\App\\Controllers\\UserController::index | | toolbar | | GET | user/new | \\App\\Controllers\\UserController::new | | toolbar | | GET | user/([0-9]+)/edit | \\App\\Controllers\\UserController::edit/$1 | | toolbar | | GET | user/([0-9]+) | \\App\\Controllers\\UserController::show/$1 | | toolbar | | POST | user | \\App\\Controllers\\UserController::create | | toolbar | | POST | user/([0-9]+)/delete | \\App\\Controllers\\UserController::delete/$1 | | toolbar | | POST | user/([0-9]+) | \\App\\Controllers\\UserController::update/$1 | | toolbar | | PATCH | user/([0-9]+) | \\App\\Controllers\\UserController::update/$1 | | toolbar | | PUT | user/([0-9]+) | \\App\\Controllers\\UserController::update/$1 | | toolbar | | DELETE | user/([0-9]+) | \\App\\Controllers\\UserController::delete/$1 | | toolbar | | CLI | ci(.*) | \\CodeIgniter\\CLI\\CommandRunner::index/$1 | | | +--------+----------------------+--------------------------------------------+----------------+---------------+ Seperti yang teman-teman bisa lihat, ada 2 metode baru yang dibuat dari opsi websafe ini, keduanya dapat membantu teman-teman untuk menghapus (DELETE), serta memperbarui data (UPDATE).\n+--------+----------------------+--------------------------------------------+----------------+---------------+ | Method | Route | Handler | Before Filters | After Filters | +--------+----------------------+--------------------------------------------+----------------+---------------+ | POST | user/([0-9]+)/delete | \\App\\Controllers\\UserController::delete/$1 | | toolbar | | POST | user/([0-9]+) | \\App\\Controllers\\UserController::update/$1 | | toolbar | only dan except. Sebuah opsi untuk \u0026ldquo;hanya gunakan\u0026rdquo; atau \u0026ldquo;kecuali\u0026rdquo;. Dari kedua opsi ini, teman-teman hanya bisa menggunakan salah satu, keduanya tidak bisa dipakai secara bersamaan. Hal ini berguna jika teman-teman tidak ingin menggunakan beberapa, atau hanya menggunakan salah satu dari sebuah rute. only dan except hanya menerima array yang berisi metode yang digunakan.\n\u0026lt;?php // app/Config/Routes.php $routes-\u0026gt;resource(\u0026#39;user\u0026#39;, [ \u0026#39;controller\u0026#39; =\u0026gt; \u0026#39;UserController\u0026#39;, \u0026#39;placeholder\u0026#39; =\u0026gt; \u0026#39;:num\u0026#39;, \u0026#39;websafe\u0026#39; =\u0026gt; true, \u0026#39;only\u0026#39; =\u0026gt; [\u0026#39;index\u0026#39;, \u0026#39;create\u0026#39;, \u0026#39;show\u0026#39;], ]); Kode di atas menghasilkan rute sebagai berikut\nphp spark routes +--------+---------------+------------------------------------------+----------------+---------------+ | Method | Route | Handler | Before Filters | After Filters | +--------+---------------+------------------------------------------+----------------+---------------+ | GET | user | \\App\\Controllers\\UserController::index | | toolbar | | GET | user/([0-9]+) | \\App\\Controllers\\UserController::show/$1 | | toolbar | | POST | user | \\App\\Controllers\\UserController::create | | toolbar | | CLI | ci(.*) | \\CodeIgniter\\CLI\\CommandRunner::index/$1 | | | +--------+---------------+------------------------------------------+----------------+---------------+ Bisa dilihat, bahwa hanya metode index, show, serta create yang digunakan. Metode lain akan dikecualikan, dan tidak bisa digunakan. Sekarang kita akan mencoba opsi except\n\u0026lt;?php // app/Config/Routes.php $routes-\u0026gt;resource(\u0026#39;user\u0026#39;, [ \u0026#39;controller\u0026#39; =\u0026gt; \u0026#39;UserController\u0026#39;, \u0026#39;placeholder\u0026#39; =\u0026gt; \u0026#39;:num\u0026#39;, \u0026#39;websafe\u0026#39; =\u0026gt; true, \u0026#39;except\u0026#39; =\u0026gt; [\u0026#39;index\u0026#39;, \u0026#39;create\u0026#39;, \u0026#39;show\u0026#39;], ]); Kode di atas akan menghasilkan\nphp spark routes +--------+----------------------+--------------------------------------------+----------------+---------------+ | Method | Route | Handler | Before Filters | After Filters | +--------+----------------------+--------------------------------------------+----------------+---------------+ | GET | user/new | \\App\\Controllers\\UserController::new | | toolbar | | GET | user/([0-9]+)/edit | \\App\\Controllers\\UserController::edit/$1 | | toolbar | | POST | user/([0-9]+)/delete | \\App\\Controllers\\UserController::delete/$1 | | toolbar | | POST | user/([0-9]+) | \\App\\Controllers\\UserController::update/$1 | | toolbar | | PATCH | user/([0-9]+) | \\App\\Controllers\\UserController::update/$1 | | toolbar | | PUT | user/([0-9]+) | \\App\\Controllers\\UserController::update/$1 | | toolbar | | DELETE | user/([0-9]+) | \\App\\Controllers\\UserController::delete/$1 | | toolbar | | CLI | ci(.*) | \\CodeIgniter\\CLI\\CommandRunner::index/$1 | | | +--------+----------------------+--------------------------------------------+----------------+---------------+ Bisa teman-teman lihat bahwa metode index, show, serta create tidak ditemukan dalam daftar rute.\nRoute Presenter # Route Presenter adalah versi HTTP-safe dari Route Resource. Bisa dibilang secara singkat dari penggunaan keduanya sedikit berbeda, perbedaannya Saya buat seperti di bawah:\nUse case Route Resource Route Presenter Penamaan routing Lebih cocok untuk API Lebih contoh untuk HTTP Metode yang dihasilkan GET. POST, PUT, PATCH, DELETE GET, POST Opsi yang tersedia untuk parameter $option controller, websafe, placeholder controller, placeholder Sekarang, bagaimana caranya menerapkan Route Presenter ini?\n\u0026lt;?php // app/Config/Routes.php $routes-\u0026gt;presenter(\u0026#39;user\u0026#39;, [ \u0026#39;controller\u0026#39; =\u0026gt; \u0026#39;UserController\u0026#39;, ]); Kode di atas akan menghasilkan daftar rute seperti ini:\nphp spark routes +--------+------------------+--------------------------------------------+----------------+---------------+ | Method | Route | Handler | Before Filters | After Filters | +--------+------------------+--------------------------------------------+----------------+---------------+ | GET | user | \\App\\Controllers\\UserController::index | | toolbar | | GET | user/show/(.*) | \\App\\Controllers\\UserController::show/$1 | | toolbar | | GET | user/new | \\App\\Controllers\\UserController::new | | toolbar | | GET | user/edit/(.*) | \\App\\Controllers\\UserController::edit/$1 | | toolbar | | GET | user/remove/(.*) | \\App\\Controllers\\UserController::remove/$1 | | toolbar | | GET | user/(.*) | \\App\\Controllers\\UserController::show/$1 | | toolbar | | POST | user/create | \\App\\Controllers\\UserController::create | | toolbar | | POST | user/update/(.*) | \\App\\Controllers\\UserController::update/$1 | | toolbar | | POST | user/delete/(.*) | \\App\\Controllers\\UserController::delete/$1 | | toolbar | | POST | user | \\App\\Controllers\\UserController::create | | toolbar | | CLI | ci(.*) | \\CodeIgniter\\CLI\\CommandRunner::index/$1 | | | +--------+------------------+--------------------------------------------+----------------+---------------+ Bisa teman-teman lihat di atas, bahwa tidak ada sekalipun metode PUT, PATCH, atau DELETE yang digunakan. Maka bisa Saya simpulkan bahwa Route Presenter adalah HTTP-safe.\nPerlu diingat, penggunaan Route Resource atau Route Presenter mewajibkan teman-teman untuk menggunakan standar penamaan metode yang ada di dalam Controller. Penamaan tersebut antara lain:\nMetode Route Resource Route Presenter index Mengembalikan semua data, bisa juga berbentuk pagination Halaman Index show Mengembalikan data spesifik Halaman Detail new - Halaman Form pembuatan data create Menyimpan data Menyimpan data edit - Halaman Form pengeditan data update Memperbarui data Memperbarui data remove - Halaman konfirmasi penghapusan data delete Menghapus data Menghapus data Pemanggilan Route # Entah kenapa CI4 tidak dapat mengimplementasikan named route seperti halnya Laravel. Padahal fungsi ini sangat membantu, karena, jika suatu saat ada suatu perubahan pada rute, teman-teman harus mengubah semua kode yang teman-teman panggil. Seperti contoh seperti ini:\n\u0026lt;?php // app/Config/Routes.php $routes-\u0026gt;presenter(\u0026#39;user\u0026#39;, [\u0026#39;controller\u0026#39; =\u0026gt; \u0026#39;UserController\u0026#39;]); // diubah menjadi: $routes-\u0026gt;presenter(\u0026#39;dashboard/user\u0026#39;, [\u0026#39;controller\u0026#39; =\u0026gt; \u0026#39;UserController\u0026#39;]); Lalu bayangkan teman-teman memiliki kode seperti:\n// app/views/sebuah-view.php \u0026lt;?= base_url(\u0026#39;/user/\u0026#39; . $user-\u0026gt;id); ?\u0026gt; // Kode di atas akan menghasilkan: // \u0026#39;/user/$id\u0026#39; // // Namun, setelah perubahan: // \u0026#39;/user/$id\u0026#39; \u0026lt;- akan menghasilkan error 404 karena rute tidak ditemukan di 12 file atau halaman yang berbeda. Menggantinya satu-per-satu merupakan sebuah aksi refaktor yang sangat melelahkan. Maka dari itu, kita akan gunakan fungsi route_to yang disediakan CI4. Fungsi ini sama saja seperti fungsi named route pada Laravel. Penggunaannya seperti ini:\n// app/views/sebuah-view.php \u0026lt;?= base_url(route_to(\u0026#39;UserController::show\u0026#39;, $user-\u0026gt;id)) ?\u0026gt; // Kode di atas akan menghasilkan: // \u0026#39;/user/$id\u0026#39; // // Setelah perubahan route: // \u0026#39;/dashboard/user/$id\u0026#39; Nah, sangat membantu kan teman-teman? Setiap perubahan apapun yang terjadi di dalam app/Config/Routes.php, CI4 akan secara otomatis mengkonversi rute lama ke rute baru. Fungsi ini akan membantu teman-teman untuk mengubah rute, tanpa adanya refaktor.\nTerima kasih sudah membaca tulisan yang panjang ini, semoga bermanfaat!\nDaftar Pustaka # Working with URLs, dokumentasi CodeIgniter 4 URI routing, dokumentasi CodeIgniter 4 Pengalaman pribadi menggunakan CI4 selama 6 bulan. ","date":"2 Juli 2022","externalUrl":null,"permalink":"/artikel/2022/07/02/standarisasi-ci4-routing/","section":"Artikel-artikel","summary":"","title":"Standarisasi CI4 - Routing","type":"artikel"},{"content":"Tahapan desain sistem ini memikirkan bagaimana menggambarkan sistem dari mendapatkan gambaran pada tahap analisis sistem.\nDesain Sistem dibagi menjadi dua bagian:\nDesain Sistem Secara Umum/General Design System, atau disebut conceptual design. Desain Sistem Terinci, atau disebut physical design. Arti Desain # Tahap setelah analisis dari siklus pengembangan sistem. Pendefinisian dari kebutuhan fungsional. Persiapan untuk rancang bangun implementasi. Menggambarkan bagaimana suatu sistem dibentuk. Dapat berupa penggambaran, perencanaan \u0026amp; pembuatan sketsa/pengaturan dari beberapa elemen yang terpisah ke dalam satu kesatuan yang utuh \u0026amp; berfungsi. Termasuk menyangkut mengkonfirmasi komponen-komponen perangkat lunak \u0026amp; perangkat system keras dari suatu sistem. Tujuan Desain # Untuk memenuhi kebutuhan pemakai sistem. Untuk memberikan gambaran yang jelas \u0026amp; rancang bangun yang lengkap kepada pemrogram komputer \u0026amp; ahli-ahli teknik lain yang terlibat. Agar Tujuan tercapai, analis sistem harus mencapai sasaran:\nDesain Sistem harus berguna, mudah dipahami \u0026amp; nantinya mudah digunakan. Desain Sistem harus dapat mendukung tujuan utama perusahaan, sesuai dengan yang telah didefinisikan pada tahap perencanaan sistem. Desain Sistem harus efisien \u0026amp; efektif untuk dapat mendukung pengolahan transaksi, pelaporan manajemen \u0026amp; mendukung keputusan yang dilakukan manajemen. Desain Sistem harus dapat mempersiapkan rancang bangun yang terinci, yang terdiri antara lain: data, informasi, file, metode-metode, prosedur, SDM, H/W, S/W. Daftar Pustaka # Foto oleh Balázs Kétyi dari Unsplash. Muhamad Ahmadin dari DetikCepat. ","date":"1 Juli 2022","externalUrl":null,"permalink":"/lainnya/kuliah/desain-sistem/","section":"Lainnyas","summary":"","title":"Desain Sistem","type":"lainnya"},{"content":"","date":"1 Juli 2022","externalUrl":null,"permalink":"/tags/kuliah/","section":"Tags","summary":"","title":"Kuliah","type":"tags"},{"content":"","date":"1 Juli 2022","externalUrl":null,"permalink":"/authors/muhamadahmadin/","section":"Authors","summary":"","title":"Muhamadahmadin","type":"authors"},{"content":"Teknik pengumpulan data merupakan teknik atau metode yang digunakan untuk mengumpulkan data yang akan diteliti. Artinya, teknik pengumpulan data memerlukan langkah yang strategis dan juga sistematis untuk mendapatkan data yang valid dan juga sesuai dengan kenyataannya.\nSelain itu, teknik atau metode pengumpulan data ini biasanya digunakan untuk peneliti demi mengumpulkan data yang merujuk pada satu kata abstrak yang tidak diwujudkan dalam benda, tetapi hanya dapat dilihat penggunaannya. Misalnya adalah melalui angket, wawancara, pengamatan, uji atau tes, dokumentasi, dan lain sebagainya.\nDilakukannya pengumpulan data untuk penelitian agar data dan teori yang ada di dalamnya valid dan juga sesuai kenyataan, sehingga peneliti harus benar-benar terjun langsung dan mengetahui teknik pengumpulan data tersebut. Dengan demikian, peneliti akan mengetahui validitas atau kebenaran konsep penelitiannya.\nSecara umum, teknik pengumpulan data ini digunakan peneliti untuk dapat mengumpulkan data atau informasi berdasarkan fakta pendukung yang ada di lapangan demi keperluan penelitian dan teknik yang dilakukan sangat ditentukan oleh metodologi penelitian yang dipilih oleh peneliti itu sendiri.\nDi dalam melakukan teknik pengumpulan data atau proses mengumpulkan data, keberadaan instrumen penelitian menjadi bagian yang sangat integral dan termasuk ke dalam komponen metodologi penelitian karena instrumen penelitiannya berupa alat yang digunakan untuk mengumpulkan, memeriksa, dan menyelidiki masalah yang diteliti.\nTentu saja, keberadaan instrumen tersebut akan membantu berbagai penelusuran terhadap gejala yang ada pada penelitian sehingga dapat digunakan untuk membuktikan kebenaran atau untuk menyanggah berbagai hipotesis. Oleh sebab itu, instrumen yang digunakan harus memiliki validitas dan reliabilitas yang baik.\nSebelum memulai melakukan teknik pengumpulan data, ada beberapa hal yang harus diperhatikan yaitu:\nData biasanya sudah ditentukan oleh beberapa variabel penelitian. Ketika semua data terkumpul, langkah berikutnya adalah mengolah data, sehingga data yang dikumpulkan memiliki arti karena diolah dengan sistematis. Data yang sudah diolah tersebut dipakai dan dipilih berdasarkan data yang berhubungan atau relevan dengan konsep, kejadian, atau objek penelitian. Datanya bisa berbentuk huruf, angka, simbol, gambar, dan lainnya. Setelah itu, pengumpulan data dilakukan untuk memperoleh informasi yang dibutuhkan dalam rangka mencapai tujuan penelitian yang diungkap dalam bentuk hipotesis yaitu jawaban sementara terhadap pertanyaan penelitian. Data yang sudah dikumpulkan ditentukan oleh berbagai variabel yang ada di dalam hipotesis dan dikumpulkan dalam bentuk sampel yang sudah ditentukan sebelumnya dan sampelnya digunakan untuk menganalisis sasaran penelitian. Proses Pengumpulan Data # Dalam teknik pengumpulan data, tentu saja ada proses yang harus dilakukan. Prosesnya harus terlaksana secara sistematis dan terarah agar data yang dikumpulkan bisa dibuktikan kebenarannya. Karena pada dasarnya, proses pengumpulan data dalam teknik mengumpulkan data ini nanti harus bisa membuktikan hipotesis dari data yang hasilnya sudah dikumpulkan oleh peneliti.\nBerikut ini, ada 8 tahap atau proses yang harus dilakukan sebagai tahapan pengumpulan data:\nTinjau literatur dan konsultasi dengan ahli\nProses atau tahap pertama yang harus dilakukan untuk mengumpulkan data yakni mengumpulkan berbagai informasi yang berhubungan dengan masalah penelitian. Informasi ini diperoleh melalui tinjauan literatur dan konsultasi dengan para ahli sehingga peneliti benar-benar mengerti isu, konsep, dan variabel yang ada di dalam penelitian.\nMempelajari dan melakukan pendekatan terhadap kelompok masyarakat di mana data akan dikumpulkan\nTahap kedua atau proses yang dilakukan setelah tinjauan literatur adalah peneliti harus mempelajari dan melakukan pendekatan terhadap kelompok masyarakat yang kemudian penelitiannya bisa diterima dan juga berkaitan dengan tokoh-tokoh yang bersangkutan.\nMembina dan memanfaatkan hubungan yang baik dengan responden dan lingkungannya\nTahap selanjutnya adalah membina hubungan baik dengan responden dan lingkungannya. Ini termasuk pada mempelajari bagaimana kebiasaan yang dilakukan responden dan cara berpikir mereka, melakukan sesuatu, bahasa yang digunakan, dan lain sebagainya untuk mendukung berlangsungnya penelitian.\nUji coba atau pilot study\nSelanjutnya, tahapan yang harus dilakukan adalah melakukan uji coba instrumen penelitian pada kelompok masyarakat yang merupakan bagian dari populasi, bukan sampel. Maksudnya untuk mengetahui apakah instrumen yang digunakan cukup dipahami, bisa digunakan, komunikatif atau tidak, dan lain sebagainya.\nMerumuskan dan menyusun pertanyaan\nSetelah itu, instrumen yang sudah didapatkan disusun dalam bentuk pertanyaan yang relevan dengan tujuan penelitian. Pertanyaan yang dirumuskan harus mengandung makna yang signifikan dan substantif.\nMencatat dan memberi kode (recording and coding)\nSetelah instrumen penelitian disiapkan, dilakukan pencatatan terhadap data yang dibutuhkan dari setiap responden. Berbagai informasi yang diperoleh ini perlu dicatat guna memudahkan proses analisis.\nCross checking, validitas, dan reliabilitas\nSetelah itu, dilakukan metode cross checking terhadap data yang didapatkan untuk menguji lagi kebenarannya dan memeriksa sehingga tidak ada keraguan terhadap validitas dan reliabilitasnya.\nPengorganisasian dan kode ulang data yang telah terkumpul supaya dapat dianalisis\nTerakhir, setelah data terkumpul, penulis harus melakukan koordinasi terhadap berbagai data yang sudah dikumpulkan, dan Anda bisa mulai menganalisis data tersebut sehingga tidak ada data yang kurang valid.\nTeknik Pengumpulan Data # Setelah memahami pengertian dan juga proses pengumpulan data, selanjutnya akan dijelaskan mengenai berbagai teknik pengumpulan data. Setidaknya ada empat teknik pengumpulan data. Berikut ini merupakan teknik pengumpulan data menurut Sugiyono (2017).\nObservasi\nTeknik observasi artinya melakukan pengamatan dan pencatatan secara sistematis mengenai gejala yang tampak pada objek penelitian. Observasi ini tergolong teknik pengumpulan data yang paling mudah dilakukan dan biasanya juga banyak digunakan untuk statistika survei, misalnya meneliti sikap dan perilaku suatu kelompok masyarakat.\nDengan teknik observasi, peneliti biasanya terjun ke lokasi yang bersangkutan untuk memutuskan alat ukur yang tepat untuk digunakan.\nKuesioner\nTeknik yang kedua adalah kuestioner atau kuesioner yang artinya teknik pengumpulan data dengan cara memberikan seperangkat pertanyaan atau pernyataan kepada orang lain yang berperan sebagai responden agar dapat menjawab pertanyaan dari peneliti. Meski terlihat mudah, teknik ini cukup sulit dilakukan jika jumlah respondennya besar dan tersebar di berbagai wilayah.\nAda beberapa prinsip yang harus diperhatikan saat memilih teknik pengumpulan data kuesioner, yaitu:\nIsi dan tujuan pertanyaannya ditujukan untuk mengukur mana yang harus ada dalam skala yang jelas dan dalam pilihan jawaban. Bahasa yang digunakan harus sesuai dengan kemampuan responden, sehingga tidak mungkin menggunakan bahasa yang penuh dengan istilah asing atau bahasa asing yang tidak dimengerti responden. Tipe dan bentuk pertanyaannya bisa terbuka atau tertutup. Terbuka artinya jawaban yang diberikan bebas, dan tertutup artinya responden hanya boleh memilih jawaban yang sudah disediakan. Interview (Wawancara)\nTeknik wawancara atau interview ini dilakukan secara tatap muka melalui tanya jawab antara peneliti atau pengumpul data dengan responden atau narasumber atau sumber data. Teknik pengumpulan data dengan wawancara biasanya dilakukan sebagai studi pendahuluan, karena teknik ini tidak mungkin dilakukan jika respondennya dalam jumlah besar.\nDocument (Dokumen)\nTeknik pengumpulan data yang terakhir adalah dokumen yang mana peneliti mengambil sumber penelitian atau objek dari dokumen atau catatan dari peristiwa yang sudah berlalu, baik dalam bentuk tulisan, gambar, atau karya monumental dari seseorang. Bisa diambil dari catatan harian, sejarah kehidupan, biografi, peraturan, dan lain sebagainya.\nDaftar Pustaka # Foto oleh Campaign Creator dari Unsplash. Muhamad Ahmadin dari DetikCepat ","date":"1 Juli 2022","externalUrl":null,"permalink":"/lainnya/kuliah/pengumpulan-data/","section":"Lainnyas","summary":"","title":"Pengumpulan Data","type":"lainnya"},{"content":"Analisis Sistem atau System Analysis adalah suatu teknik atau metode pemecahan masalah dengan cara menguraikan system ke dalam komponen-komponen pembentuknya untuk mengetahui bagaimana komponen-komponen tersebut bekerja dan saling berinteraksi satu sama lain untuk mencapai tujuan system.\nSystem Analysis biasanya dilakukan dalam membuat System Design. System Design adalah salah satu langkah dalam teknik pemecahan masalah dimana komponen-komponen pembentuk system digabungkan sehingga membentuk satu kesatuan system yang utuh. Hasil dari System Design merupakan gambaran system yang sudah diperbaiki. Teknik dari System Design ini meliputi proses penambahan, penghilangan, dan pengubahan komponen-komponen dari system semula.\nLangkah-langkah Analisa Sistem # Langkah-langkah dalam tahap analisa sistem akan hampir sama dengan yang akan langkah-langkah yang akan dilakukan dalam mendefinisikan proyek-proyek sistem yang akan dibesarkan di tahap perencanaan sistem. Perbedaannya terletak pada ruang-ruang lingkup tugasnya. Di analisa sistem ini, penelitian yang akan dilakukan oleh analisis sistem adalah penelitian terinci, sedangkan di perencanaan sistem sifatnya hanya penelitian pendahuluan\nDi dalam tahap analisa sistem terdapat beberapa langkah-langkah dasar yang harus dilakukan oleh analis sistem, sebagai berikut yakni:\nIdentify, merupakan mengidentifikasikan masalah Understand, merupakan memahami kerja dari sistem yang ada Analyze, merupakan menganalisis sistem Report, merupakan membuat laporan hasil analisis Tujuan Analisa Sistem # Tujuan analisis sistem informasi yakni utuk merancang sistem baru maupun menyempurnakan sistem yang sudah ada sebelumnya. Berikut ini, tujuan dari analisis keuangan diantaranya yakni:\nKita dapat Membuat keputusan jika sistem saat ini bermasalah ataupun juga tidak berfungsi dengan baik \u0026amp; hasil analisisnya akan digunakan sebagai dasar untuk memperbaiki sistem. Mengetahui ruang lingkup pekerjaan yang akan dapat ditandatangani. Mengidentifikasi masalah atau mencari pemecah masalahnya. Mempelajari sistem yang sedang berjalan saat ini. Memberikan pelayanan kebutuhan informasi kepada fungsi manajerial di dalam pengendalian pelaksanaan kegiatan operasional perusahaan. Membantu para pengambil keputusan. Mengevaluasi sistem yang telah ada. Merumuskan tujuan yang ingin dicapai berupa pengolahan data maupun pembuatan laporan baru. Menyusun suatu tahap rencana pengembangan sistem. Analisa dan Perancangan Sistem Informasi (ANSI); adalah proses penguraian suatu pokok dan menyelidiki kedaan yang sebenarnya dalam sebuah entitas atau guna mencari indikasi komponen dan unsur-unsur penting dalam membangun sebuah sistem informasi. Analisis PIECES # PIECES adalah singkatan dari Performance (kinerja) berkaitan dengan analisis kemampuan dan kapasitas kerja sistem; Information (informasi) terkait dengan laporan informasi yang dihasilkan sistem; Economy (ekonomi) berkaitan dengan penghematan waktu, keuangan dan tenaga sistem; kontrol terkait dengan keamanan; Efficiency (efisiensi) atau ketepatan fungsi sistem dengan tidak membuang waktu, biaya, ruang dan tenaga; dan Services (layanan) sistem.\nDaftar Pustaka # Foto oleh National Cancer Institute dari Unsplash. Muhamad Ahmadin dari DetikCepat. ","date":"1 Juli 2022","externalUrl":null,"permalink":"/lainnya/kuliah/analisa-sistem/","section":"Lainnyas","summary":"","title":"Analisa Sistem","type":"lainnya"},{"content":"Pengembangan sistem (system development) dapat berarti menyusun suatu sistem yang baru untuk menggantikan sistem yang lama secara keseluruhan atau memperbaiki sistem yang telah ada.\nPerancangan Sistem dapat dibagi dalam 2 bagian, yaitu:\nPerancangan sistem secara umum/perancangan konseptual, perancangan logikal/perancangan secara makro Perancangan sistem terinci/perancangan sistem secara fisik. Sistem yang lama perlu diperbaiki atau diganti disebapkan karena beberapa hal, yaitu sebagai berikut ini:\nKetidakberesan\nKetidakberesan dalam sistem yang lama menyebabkan sistem tidak dapat beroperasi sesuai yang di harapkan. Ketidak beresan ini berupa:\nKecurangan-kecurangan disengaja yang menyebabkan tidak amannya harta kekayaan perusahaan dan kebenaran dari data sehingga kurang menjamin; Kesalahan-kasalahan yang tidak di sengaja yang juga dapat menyebabkan kebenaran dari data kurang terjamin; Tidak efisiennya operasi; Tidak ditaatinya kebijaksanaan manajemen yang telah ditetapkan Pertumbuhan Organisasi\nPertumbuhan organisasi yang menyebabkan harus disusunnya sistem yang baru. Pertumbuhan organisasi diantaranya adalah kebutuhan informasi yang semakin luas, volume pengolahan data semakin maningkat, perubahan prinsip akuntansi yang baru. Karena adanya perubhan ini, maka menyebabkan sistem yang lama tidak efektif lagi, sehingga sistem yang lama sudah tidak dapat memenuhi lagi semua kebutuhan informasi yang dibutuhkan manajemen.\nTahapan Perancangan Sistem # Tahap setelah analisis dari siklus pengembangan sistem Pendefinisian dari kebutuhan-kebutuhan sistem Persiapan untuk rancang bangun implementasi Menggambarkan bagaimana suatu sistem dibentuk Dapat berupa penggambaran, perencanaan dan pembuatan sketsa atau pengaturan dari beberapa elemen yang terpisah ke dalam satu kesatuan yang utuh dan berfungsi. Tujuan Perancangan Sistem # Tujuan dari Perancangan Proses Sistem adalah Untuk menjaga agar proses data lancar dan teratur sehingga menghasilkan informasi yang benar dan Untuk mengawasi proses dari sistem Perancangan\nUntuk memenuhi kebutuhan kepada pemakai sistem Untuk memberikan gambaran yang jelas dan rancang bangun yang lengkap kepada pemrogram komputer (Programmer) dan useryang terlibat Alat dan Teknik Pengembangan Sistem # Untuk dapat melakukan langkah-langkah sesuai dengan yang diberikan oleh metodologi pengembangan sistem yang terstruktur, maka dibutuhkan alat dan teknik untuk melaksanakannya. Alat-alat yang digunakan dalam suatu metodologi umumnya berupa suatu gambar atau diagram atau grafik. Penggunaan diagram atau gambar ini dipandang lebih mengena dan lebih mudah dimengerti seperti kata suatu ungkapan “Suatu gambar bernilai ribuan kata“. Selain berbentuk gambar, alat-alat yang digunakan juga ada yang tidak berupa gambar atau grafil seperti misalnya data dictionary, structured english, serta formulir-formulir untuk mencatat dan menyajikan data.\nAnalisis Sistem dan Pemrogram # Analis sistem adalah orang yang menganalisis sistem untuk mengidentifikasikan pemecahan yang beralasan. Sebutan lain untuk analis sistem ini adalah analis informasi, analis bisnis, perancang sistem, konsultan sistem dan ahli teknik sistem. Pemrogram adalah orang yang menulis kode program untuk suatu aplikasi tertentu berdasarkan rancang bangun yang telah dibuat oleh analis sistem.\nPengetahuan dan Keahlian yang diperlukan Analis Sistem # Pengetahuan dan keahlian tentang teknik pengolahan data, teknologi komputer dan pemrograman komputer. Pengetahuan tentang bisnis secara umum. Pengetahuan tentang metode kuatitatif. Keahlian pemecahan masalah. Keahlian komunikasi antar personil Keahlian membina hubungan antar personil Tim Pengembangan sistem # Manager analisis sistem Ketua analis sistem Analis sistem senior Analis sistem Analis sistem junior Pemrogram aplikasi junior Pemrogram aplikasi Pemrogram aplikasi junior Daftar Pustaka # Foto oleh charlesdeluvio dari Unsplash. Muhammad Ahmadin dari DetikCepat. ","date":"1 Juli 2022","externalUrl":null,"permalink":"/lainnya/kuliah/tinjauan-umum-pengembangan-sistem/","section":"Lainnyas","summary":"","title":"Tinjauan Umum Pengembangan Sistem","type":"lainnya"},{"content":"Kemarin Saya memposting sebuah video tentang redirection ke website lokal jika Saya ingin membuka YouTube atau media sosial lainnya yang notabene mengurangi waktu produktif. Videonya cukup viral, Saya lihat tulisan ini dibuat sudah ada 66 shares. Karena ada yang minta tutorial, ya sudah Saya buatkan disini 😅.\nSebagai catatan saja bahwa ini harus kalian lakukan manual, jadi nggak bisa otomatis apalagi di jam tertentu bisa ngeredirect. Kalian harus punya concern sendiri terhadap jam produktif kalian. Semoga sampai sini kalian paham. Ah iya, barangkali temen-temen disini punya pengalaman buat bikin ekstensi Chrome dengan fungsi yang serupa, PM Saya di Facebook ya. Ntar kita kembangin bareng-bareng.\nOke pertama, kalian harus install ekstensi ini, namanya Redirector. Secara pribadi Saya belum mencoba mencari ekstensi ini untuk Firefox ataupun Edge, nanti kalian cari sendiri. Setelah diinstal, buka ekstensinya. Nanti ada interface seperti ini, kalian klik Edit Redirects.\nNanti kalian akan masuk ke halaman ekstensinya Redirector. Klik Create new redirect. Nanti bakal ada modal yang muncul, kalian isi seperti ini: Penjelasan:\nDescription diisi dengan situs apa yang ingin kalian redirect. Contoh di atas, Facebook. Example URL diisi dengan contoh URL yang kalian ingin redirect, bisa diisi apa saja, tapi secara umum jika parameter Include pattern diisi sama Wildcard, ngeakses https://facebook.com pun bakal diredirect. Include pattern diisi dengan Example URL, bedanya setelah garis miring terakhir pakai Wildcard. Kalian bisa lihat contohnya di ekstensi ini setelah instalasi. Redirect to diisi dengan situs yang kalian ingin tuju. Contohnya saya buat ke http://produktif.test, Saya pake domain .test karena Saya menggunakan Laravel Valet. Kalian bisa isi dengan http://127.0.0.1/\u0026lt;nama_project\u0026gt; atau http://localhost/\u0026lt;nama_project\u0026gt; jika development environment kalian bukan menggunakan Valet. Pattern type bisa kalian pilih Wildcard atau Regex. Jika kalian pilih Wildcard, pastikan Include pattern-nya ada tanda bintang. Jika Regex, kalian harus memahami pattern dari URL yang ingin diredirect. Pattern description diisi dengan deskripsi pattern yang kalian buat. Setelah itu, klik Save.\nKalian bisa import Redirector yang sudah Saya buat, situs yang Saya redirect:\nYouTube Facebook Twitter Instagram Tokopedia (ya, klo gabut Saya sering bolak-balik sini) Unduh Redirector.json.\nUntuk halaman redirectnya sendiri, Saya yakin kalian sudah bisa buat sendiri. Atau jika ingin pakai kode Saya, cek di sini. Setelah semua siap, pastikan Redirectornya nyala ya. Sekian tutorial singkatnya. Semoga membantu, dan terima kasih sudah membaca!\nThumbnail oleh Robert Rugiero dari Unsplash.\n","date":"14 Mei 2022","externalUrl":null,"permalink":"/artikel/2022/05/14/cara-membuat-redirect-untuk-situs-tertentu/","section":"Artikel-artikel","summary":"","title":"Cara Membuat Redirect Untuk Situs Tertentu","type":"artikel"},{"content":"","date":"14 Mei 2022","externalUrl":null,"permalink":"/tags/productivity/","section":"Tags","summary":"","title":"Productivity","type":"tags"},{"content":"Thumbnail oleh Markus Spiske di Unsplash\nPada saat pengembangan blog ini, Saya ingin menambahkan sebuah fitur view yang digunakan untuk meninjau tulisan mana yang ramai pengunjung/pembaca. Tentunya akan ada dua kasus saat pembuatan view ini:\nPembaca yang terautentikasi (pada saat tulisan ini dibuat, form login belum dibuat), sistem akan mengirimkan user_id ke tabel view. Pembaca yang BELUM terautentikasi/anonim, sistem akan mengirimkan null ke field user_id. Skema migrasi yang Saya buat adalah seperti ini:\nSchema::create(\u0026#39;views\u0026#39;, function (Blueprint $table) { $table-\u0026gt;id(); $table-\u0026gt;foreignId(\u0026#39;user_id\u0026#39;)-\u0026gt;constrained()-\u0026gt;nullable(); $table-\u0026gt;foreignId(\u0026#39;blog_id\u0026#39;)-\u0026gt;constrained(); $table-\u0026gt;timestamps(); }); Testing tahap pertama, ketika User terautentikasi, data view dapat tersimpan dengan baik di database. Tanpa error. Namun Saya tersadar pada saat testing berjalan, session Saya masih ada, dan ketika saya log out, terjadilah error:\nTernyata cara penyelesaiannya cukup simpel, dan tidak membutuhkan waktu lama. Kita hanya perlu mengubah:\n// Sebelum $table-\u0026gt;foreignId(\u0026#39;user_id\u0026#39;)-\u0026gt;constrained()-\u0026gt;nullable(); // Setelah $table-\u0026gt;foreignId(\u0026#39;user_id\u0026#39;)-\u0026gt;nullable()-\u0026gt;constrained(); Kode selengkapnya:\nSchema::create(\u0026#39;views\u0026#39;, function (Blueprint $table) { $table-\u0026gt;id(); $table-\u0026gt;foreignId(\u0026#39;user_id\u0026#39;)-\u0026gt;nullable()-\u0026gt;constrained(); $table-\u0026gt;foreignId(\u0026#39;blog_id\u0026#39;)-\u0026gt;constrained(); $table-\u0026gt;timestamps(); }); Terima kasih sudah membaca!\n","date":"23 April 2022","externalUrl":null,"permalink":"/artikel/2022/04/23/membuat-relasi-supaya-bisa-di-nullable-pada-laravel/","section":"Artikel-artikel","summary":"","title":"Membuat Relasi Supaya Bisa di-Nullable Pada Laravel","type":"artikel"},{"content":"Tim Laravel telah meluncurkan v9.8, dengan fitur pengaksesan form data dari model Eloquent, log level yang bisa dikustomisasi per-tipe eksekusi, komponen anonim pada path custom, dll.\nForm helper \u0026ldquo;old\u0026rdquo; sekarang bisa menerima model # Andrew Arscott mengubah helper old() sehingga bisa menerima model sebagai argumen keduanya.\n{{-- Dulu --}} \u0026lt;input type=\u0026#34;text\u0026#34; name=\u0026#34;name\u0026#34; value=\u0026#34;{{ old(\u0026#39;name\u0026#39;, $user-\u0026gt;name) }}\u0026#34; {{-- Sekarang --}} \u0026lt;input type=\u0026#34;text\u0026#34; name=\u0026#34;name\u0026#34; value=\u0026#34;{{ old(\u0026#39;name\u0026#39;, $user) }} Mengizinkan Penyesuaian Tingkat Log pada Exception Handling # Tom Witkowski mengkontribusikan fitur penyesuaian tingkat log untuk Exception yang dilaporkan pada Exception Handler.\nuse PDOException; use Psr\\Log\\LogLevel; /** * A list of exception types with their corresponding custom log level * * @var array\u0026lt;class-string\u0026lt;\\Throwable\u0026gt;, \\Psr\\Log\\LogLevel::*\u0026gt; */ protected $levels = [ PDOException::class =\u0026gt; LogLevel::CRITICAL ]; Lihat Pull Request #41925 untuk detail implementasi.\nMenemukan Komponen Blade Anonim di Jalur Tambahan # Ralph J. Smit berkontribusi kemampuan untuk menemukan komponen Blade anonim di jalur tambahan:\n// AppServiceProvider.php public function boot() { Blade::anonymousComponentNamespace(\u0026#39;flights.bookings\u0026#39;, \u0026#39;flights\u0026#39;); } Contoh penggunaan komponen: \u0026lt;x-flights::panel :flight=\u0026quot;$flight\u0026quot; /\u0026gt;\nMetode Set pada Factory # Ralph J. Smit mengkontribusikan sebuah metode set() kepada model factory untuk mengatur satu attribut pada model:\n// Sebelum: EloquentModel::factory() -\u0026gt;create([\u0026#39;name\u0026#39; =\u0026gt; \u0026#39;foo\u0026#39;]); // Setelah: EloquentModel::factory() -\u0026gt;set(\u0026#39;name\u0026#39;, \u0026#39;foo\u0026#39;) -\u0026gt;create(); // Sebelum EloquentModel::factory() -\u0026gt;someMethod() -\u0026gt;create([\u0026#39;country\u0026#39; =\u0026gt; \u0026#39;NL\u0026#39;]); // Setelah EloquentModel::factory() -\u0026gt;someMethod() -\u0026gt;set(\u0026#39;country\u0026#39;, \u0026#39;NL\u0026#39;) -\u0026gt;create(); Catatan Peluncuran # Anda bisa melihat daftar fitur baru, perubahan, dan komparasi dari 9.7.0 dan 9.8.0 pada GitHub.\nBerita diambil dan diterjemahkan dari Laravel News.\n","date":"14 April 2022","externalUrl":null,"permalink":"/artikel/2022/04/14/laravel-9-8-diluncurkan/","section":"Artikel-artikel","summary":"","title":"Laravel 9 8 Diluncurkan","type":"artikel"},{"content":"Secara sederhana suatu sistem dapat diartikan sebagai suatu kumpulan atau himpunan dari unsur, komponen, atau variabel yang terorganisir, saling berinteraksi, saling tergantung satu sama lain, dan terpadu. Salah satu teori mengenai sistem secara umum pertama kali yaitu, teori yang diuraikan oleh Kenneth Boulding terutama menekankan perhatian terhadap setiap bagian yang membentuk sebuah sistem.\nTeori sistem mengatakan bahwa setiap unsur pembentuk organisasi harus mendapat perhatian penuh dari pemimpin organisasi secara merata baik komponen fisik maupun non-fisik.Unsur-unsur yang mewakili suatu sistem secara umum adalah memasukan (input),pengolahan (processing), dan keluaran (output).\nKonsep lain yang terkandung di dalam definisi tentang sistem adalah konsep sinergi. Konsep ini di dalam suatu sistem adalah output dari suatu organisasi di harapkan lebih besar daripada output individual atau output masing-masing bagian.\nKegiatan bersama dari bagian yang terpisah, tetapi saling berhubungan secara bersama-sama akan menghasilkan efek total yang lebih besar daripada jumlah bagian secara individu dan terpisah. Karena itu, sistem organisasi mengutamakan pekerjaan-pekerjaan di dalam tim.\nSelain itu, cara pandang sistem suatu pelaksanaan pekerjaan secara integratif baik menyangkut manusia, perkakas, metode, maupun sumber daya yang dimanfaatkan. Karena itu, ada banyak macam cara untuk mengkategorikan suastu sistem. Seperti: sistem terbuka atau tertutup, sistem manusia, sistem mesin, atau gabungan keduanya, sistem deterministik atau probabilistik dan masih banyak lagi.\nIstilah sistem untuk sekarang ini masih banyak dipakai. Sebuah sistem terdiri atas bagian-bagian atau komponen yang terpadu untuk mencapai suatu tujuan. Model dasar dari bentuk sistem ini adalah adanya masukan, pengolahan, dan keluaran. Akan tetapi, sistem ini dapat dikembangkan hingga menyertakan media penyimpanan. Sistem dapat terbuka dan tertutup, akan tetapi sistem informasi biasanya adalah sistem terbuka yang dapat menerima beberapa masukan dari lingkungan luarnya.\nMelakukan unsur sistem atas subsistem adalah tindakan penting dalam menyederhanakan perancangan sistem. Penggunaan sistem biasanya membutuhkan beberapa mekanisme pemisah untuk mengurangi kerumitan dalam koordinasi dan komunikasi. Pengendalian dalam sistem adalah berdasar kepada umpan balik yang dapat terbuka dan tertutup. Sedangkan penyaringan dapat digunakan untuk persyaratan pengolahan dengan mengurangi masukan. Hukum variasi kebutuhan menjadi penting untuk merancang sistem pengendalian karena menyatakan perlunya suatu metode yang mengadakan tanggapan pengendalian bagi setiap keadaan variable yang terkendali. Rancangan sistem diterapkan dalam tahapan teratur pada analisi sistem dan pada manajemen proyek. Konsep-konsep sistem juga mempunyai penerapan langsung pada perancangan sistem informasi (sisfo).\nKonsep Dasar Informasi # Sistem informasi adalah suatu sistem didalam suatu organisasi yang mempertemukan kebutuhan pengelolaan transaksi harian, mendukung operasi, bersifat manajerial, dan kegiatan strategi dari suatu organisasi dan menyediakan pihak luar tertentu dengan laporan-laporan yang dibutuhkan.\nSistem Informasi (SI) adalah kombinasi dari teknologi informasi dan aktivitas orang yang menggunakan teknologi itu untuk mendukung operasi dan manajemen. Dalam arti yang sangat luas, istilah sistem informasi yang sering digunakan merujuk kepada interaksi antara orang, proses algoritmik, data, dan teknologi. Dalam pengertian ini, istilah ini digunakan untuk merujuk tidak hanya pada penggunaan organisasi teknologi informasi dan komunikasi (TIK), tetapi juga untuk cara di mana orang berinteraksi dengan teknologi ini dalam mendukung proses bisnis.\nAda yang membuat perbedaan yang jelas antara sistem informasi, dan komputer sistem TIK, dan proses bisnis. Sistem informasi yang berbeda dari teknologi informasi dalam sistem informasi biasanya terlihat seperti memiliki komponen TIK. Hal ini terutama berkaitan dengan tujuan pemanfaatan teknologi informasi. Sistem informasi juga berbeda dari proses bisnis. Sistem informasi membantu untuk mengontrol kinerja proses bisnis.\nAlter berpendapat untuk sistem informasi sebagai tipe khusus dari sistem kerja. Sistem kerja adalah suatu sistem di mana manusia dan/atau mesin melakukan pekerjaan dengan menggunakan sumber daya untuk memproduksi produk tertentu dan/atau jasa bagi pelanggan. Sistem informasi adalah suatu sistem kerja yang kegiatannya ditujukan untuk pengolahan (menangkap, transmisi, menyimpan, mengambil, memanipulasi dan menampilkan) informasi\nSistem informasi terdiri dari komponen-komponen yang disebut dengan istilah blok bangunan (building block) yaitu:\nBlok masukkan (input block). Input mewakili data yang masuk ke dalam sistem informasi. Input disini termasuk metode-metode dan media yang digunakan untuk menangkap data yang akan dimasukkan, yang dapat berupa dokumen dasar Blok model (model block). Blok ini terdiri dari kombinasi prosedur, logika dan metode matematik yang akan memanipulasi data input dan data yang tersimpan di basis data dengan cara yang sudah tertentu untuk menghasilkan keluaran yang sudah diinginkan. Blok keluaran (output block)Produk dari sistem informasi adalah keluaran yang merupakan informasi yang berkualitas dan dokumentasi yang berguna untuk semua tingkatan manajemen serta semua pemakai sistem. Blok teknologi (technology block). Teknologi digunakan untuk menerima input, menjalankan model, menyimpan dan mengakses data, menghasilkan dan mengirimkan keluaran dan membantu pengendalian diri secara keseluruhan. Teknologi terdiri dari unsur utama: Teknisi (brainware) Perangkat lunak (software) Perangkat keras (hardware) Blok basis data (database block). Merupakan kumpulan dari data yang saling berhubungan satu dengan yang lainnya, tersimpan diperangkat keras komputer dan digunakan perangkat lunak untuk memanipulasinya. Blok kendali (control block). Banyak faktor yang dapat merusak sistem informasi, misalnya bencana alam, api, temperatur tinggi, air, debu, kecurangan-kecurangan, kejanggalan sistem itu sendiri, kesalahan-kesalahan ketidakefisienan, sabotase dan sebagainya. Beberapa pengendalian perlu dirancang dan diterapkan untuk meyakinkan bahwa hal-hal yang dapat merusak sistem dapat dicegah atau bila terlanjur terjadi kesalahan dapat langsung diatasi. Konsep Dasar Sistem Informasi # Sistem informasi adalah suatu sistem didalam suatu organisasi yang mempertemukan kebutuhan pengelolaan transaksi harian, mendukung operasi, bersifat manajerial, dan kegiatan strategi dari suatu organisasi dan menyediakan pihak luar tertentu dengan laporan-laporan yang dibutuhkan.\nDefinisi sistem informasi adalah suatu sistem di dalam suatu organisasi yang mempertemukan kebutuhan pengolahan transaksi harian yang mendukung operasi bersifat manajerial dan kegiatan strategi dari suatu organisasi dan menyediakan pihak luar tertentu dengan laporan-laporan yang diperlukan.\n","date":"9 April 2022","externalUrl":null,"permalink":"/lainnya/kuliah/konsep-dasar-sistem-informasi/","section":"Lainnyas","summary":"","title":"Konsep Dasar Sistem, Konsep Dasar Informasi, dan Konsep Dasar Sistem Informasi","type":"lainnya"},{"content":"Privacy Policy for Wayan Polairud Presisi App\nAt Wayan Polairud Presisi App, accessible from our application, one of our main priorities is the privacy of our visitors. This Privacy Policy document contains types of information that is collected and recorded by Wayan Polairud Presisi and how we use it.\nIf you have additional questions or require more information about our Privacy Policy, do not hesitate to contact us.\nLocation # Wayan Polairud is an app intended for a police sub-group called Dit Polairud. Dit Polairud is a sub-group where they worked mainly managing and securing the seas of a province, in this case, it is Bali. The app itself is generally used in two parts:\nIt’s for general use, where it doesn’t need any special permission except the internet, which is used to access weathers information, and the location of a specific spot through webview activity, and;\nFor internal use where all of the personnel’s activity within the Dit Polairud must be tracked. With the permission of Dir Polairud (the headmaster of Dit Polairud), all of the personnel must be tracked by any means of the way as long as the application is active.\nThe intent of background location will be used to track a sea patrol personnel, where the internet in the ocean is mostly non-existent, we need the offline GPS tracking, saved it to our local storage, and when the application is connected to the internet, the data is pushed to the server.\nEvery personnel who uses this app will receive a popup screen to access the Location permission, by accepting this location access, they have concerns to access the Location service.\nLog Files # Wayan Polairud Presisi App follows a standard procedure of using log files. These files log visitors when they visit websites. All hosting companies do this and a part of hosting services’ analytics. The information collected by log files include internet protocol (IP) addresses, browser type, Internet Service Provider (ISP), date and time stamp, referring/exit pages, and possibly the number of clicks. These are not linked to any information that is personally identifiable. The purpose of the information is for analyzing trends, administering the site, tracking users’ movement on the website, and gathering demographic information. Our Privacy Policy was created with the help of the Privacy Policy Generator.\nGoogle DoubleClick DART Cookie # Google is one of a third-party vendor on our site. It also uses cookies, known as DART cookies, to serve ads to our site visitors based upon their visit to https://polairud.bali.polri.go.id/ and other sites on the internet. However, visitors may choose to decline the use of DART cookies by visiting the Google ad and content network Privacy Policy at the following URL – https://policies.google.com/technologies/ads\nThird-party Privacy Policy # Third-party ad servers or ad networks uses technologies like cookies, JavaScript, or Web Beacons that are used in their respective advertisements and links that appear on Wayan Polairud Presisi App, which are sent directly to users’ browser. They automatically receive your IP address when this occurs. These technologies are used to measure the effectiveness of their advertising campaigns and/or to personalize the advertising content that you see on websites that you visit.\nNote that Wayan Polairud Presisi App has no access to or control over these cookies that are used by third-party advertisers.\nThird-party Advertisement Policy # Wayan Polairud Presisi App’s Privacy Policy does not apply to other advertisers or websites. Thus, we are advising you to consult the respective Privacy Policies of these third-party ad servers for more detailed information. It may include their practices and instructions about how to opt-out of certain options.\nYou can choose to disable cookies through your individual browser options. To know more detailed information about cookie management with specific web browsers, it can be found at the browsers’ respective websites.\nChildren Information # Another part of our priority is adding protection for children while using the internet. We encourage parents and guardians to observe, participate in, and/or monitor and guide their online activity.\nWayan Polairud Presisi App does not knowingly collect any Personal Identifiable Information from children under the age of 13. If you think that your child provided this kind of information on our website, we strongly encourage you to contact us immediately and we will do our best efforts to promptly remove such information from our records.\n","date":"9 Agustus 2020","externalUrl":null,"permalink":"/privacy-policy/wayan-polairud/","section":"Privacy-Policies","summary":"","title":"Wayan Polairud Presisi Privacy Policy","type":"privacy-policy"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"}]