<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Black Nerd's Journey]]></title><description><![CDATA[Black Nerd's Journey]]></description><link>https://blacknerd.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 07 Jun 2026 01:51:46 GMT</lastBuildDate><atom:link href="https://blacknerd.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Creature of Habit: Devlog Chronicles - Part 5]]></title><description><![CDATA[(Belated) 1 Year Anniversary
A bit behind on the anniversary note, but November 10th 2025 marked the one-year anniversary of the Initial commit for the project! Ohhhh boi, this has been a long journey, and hopefully, soon, a journey that will be comi...]]></description><link>https://blacknerd.dev/creature-of-habit-devlog-chronicles-part-5</link><guid isPermaLink="true">https://blacknerd.dev/creature-of-habit-devlog-chronicles-part-5</guid><category><![CDATA[Svelte]]></category><category><![CDATA[app development]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[habits]]></category><category><![CDATA[SQL]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Tue, 16 Dec 2025 03:25:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1765808507293/1b65cf4f-d37e-432c-93b9-d8c4ec259f06.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-belated-1-year-anniversary">(Belated) 1 Year Anniversary</h2>
<p>A bit behind on the anniversary note, but November 10th 2025 marked the one-year anniversary of the <a target="_blank" href="https://github.com/kdleonard93/creatures-of-habit/commit/1f219e483db700792ff6b7c8b8fe359286255de1">Initial commit</a> for the project! Ohhhh boi, this has been a long journey, and hopefully, soon, a journey that will be coming to an end. I learned a ton from working on this project and will be doing a final post to conclude this series, but in this article, I want to focus on the beta for the app! Currently using Railway for deployment of the Web App and Tauri for the desktop app, but the desktop app isn’t built just yet. I thought it would be a simple launch alongside the web app, but I will get into why it wasn’t further along in this article. But here it is in all its glory:</p>
<h1 id="heading-creatures-of-habithttpscreatures-of-habit-productionuprailwayapp"><a target="_blank" href="https://creatures-of-habit-production.up.railway.app/">» Creatures of Habit</a> «</h1>
<p>Get in there and create an account, stress the app out, and hopefully you can at least admire the work behind it, if not find some use for it. I’ve sent this to family and a few of my close friends to give some feedback before I let the world stress test it. Got some great feedback from all of them, and I also found PostHog to be an amazing tool when people actually started messing around in the app. Well worth the time investment there, and will be recommending them to anyone looking for a free analytics alternative. But after the few fixes and QOL improvements, I feel like it’s ready for others to criticize my app and judge me 😂. We’ll keep this one short.</p>
<h2 id="heading-how-do-i-get-started-you-asked">“How do I get started?” you asked??</h2>
<p>Well, let me tell ya, it’s easy as 1,2,3 (and 4).</p>
<h3 id="heading-step-1-create-an-account">Step 1 - Create an Account</h3>
<p>Pretty straightforward step. Get your account and your creature created, then you’re off to the races</p>
<h3 id="heading-step-2-create-your-first-habit">Step 2 - Create Your First Habit</h3>
<p>You can create a new habit through the dashboard or on the <code>/habits</code> page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765851921713/06fc5a22-e2ba-48fe-b299-5b1052be4e25.png" alt class="image--center mx-auto" /></p>
<p>Here is what the habit form will look like. Make your choices and set the habit to start on a future date as well.</p>
<h3 id="heading-step-3-complete-the-daily-quest">Step 3 - Complete the Daily Quest</h3>
<p>After you’re done adding habits, head over to the quests page and complete the Daily Quest. You gain a reward for completing the quest regardless of how many questions you get right, but of course, getting the correct answers has its benefits. If you get 3 answers right, you get a stat point boost and some additional experience. You get all 5 correct, and you get 2 stat points and even more exp. So definitely aim to get the right answers</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765853736055/90ce7ca3-5fec-48a9-ae07-76a77d6b3967.png" alt class="image--center mx-auto" /></p>
<p>.</p>
<h3 id="heading-maybe-step-4-complete-any-habits-that-were-due-immediately">(Maybe) Step 4 - Complete Any Habits That Were Due immediately.</h3>
<p>Get to it!</p>
<h2 id="heading-the-delay-for-desktop">The Delay For Desktop</h2>
<p>That subtitle kinda sounds like an IT started a band on the side for fun….. A band I would prob go see lol. But anyway, the reason for the delay with the desktop app stems from two things: Code Signing and Distribution.</p>
<h4 id="heading-distribution">Distribution</h4>
<p>I need to follow 2 different procedures for getting a proper distribution for both Windows and Mac (Sorry Linux folks ✌🏾). I want to get these into both the Microsoft and Apple stores for distribution, but for now, the easiest solution is to add installers for both of the operating systems and then just add a section for people to download it on the homepage or when you’re signed into the app. But in order to do this, I need Code Signing.</p>
<h4 id="heading-code-signing">Code Signing 🫩</h4>
<p>Apple is lame as hell. To list it on the App Store, you NEED to pay the $99/year Apple Developer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765853196158/16082219-b783-4d2d-9d1f-412bef761e04.png" alt class="image--center mx-auto" /></p>
<p>Now I get it, this is meant to keep nefarious actors from spamming the app store with trash or corrupted apps. What I DON’T get is why the hell does using the free account still prevent me from notorizing my app, so it when it’s downloaded from the DMG, it’s gonna show up as “not verified” when opening the app. So I’ll prob still work on the free account before deciding to actually pay for this subscription. With Windows, I just need to create an account, and so far, I haven’t seen anything about a subscription, but if one pops up, they can kick rocks too.</p>
<p>Outside of the cost hurdle, the builds should be simple enough for both Windows and macOS.</p>
<h2 id="heading-work-so-far">Work So Far</h2>
<p>Check out the projects repo → <a target="_blank" href="https://github.com/kdleonard93/creatures-of-habit">https://github.com/kdleonard93/creatures-of-habit</a> if you wanna peep the work done since the last article or even since I first started. 495 COMMITS TOTAL!!! I’ve never seen a greener year in my life.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765855062428/f05b818f-9be1-4aa6-9193-e037e9c2c494.png" alt class="image--center mx-auto" /></p>
<p>🥲 Super hot start and solid second half of the year. Proud of all the hours I put into this.</p>
<h2 id="heading-whats-next">What’s Next?</h2>
<p>So I have a couple more features that I want to add. One being a streak system, and another being FULLY dynamic quest questions and answers.</p>
<h4 id="heading-streak-system">Streak System</h4>
<p>The streak system was part of the plan since the beginning, but it became the least of my concerns when building this out, and I forgot to include it in my roadmap updates. So I wanted to add that as one of the final touches.</p>
<h4 id="heading-dynamic-questions">Dynamic Questions</h4>
<p>Currently, the questions are partially dynamic. That being there is a bunch of pre-written questions and answers built in, and each quest refresh picks 5 random from the genre it chooses. I don’t want to use any AI for this since I don’t plan to actually make any money from this app to pay for it, so I’m going to need to rewrite my quest logic a bit to give the user a better sense of constant randomness.</p>
<p>After those two things and any bugs that pop up, I’m gonna be pretty much finished! I have the last 2 weeks off of the month, so the goal is to knock out these two things before the new year. Bunch of things are going on in my life outside of this project, so I’m not sweating if it’s not fully complete by the time the year is up, but I’ll try to give an update nevertheless.</p>
<p>Until next time ✌🏾.</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-work-or-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my work or want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://bsky.app/profile/digitaldopamine.dev"><strong>🦋 Bluesky</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Creatures of Habit: Devlog Chronicles - Part 4]]></title><description><![CDATA[Project Progress
We’re getting close to the end, folks! Most of the main features are complete and working, except for a handful of bugs that aren’t breaking the app. However, I’d like to address them before pushing this to production. For those curi...]]></description><link>https://blacknerd.dev/creatures-of-habit-devlog-chronicles-part-4</link><guid isPermaLink="true">https://blacknerd.dev/creatures-of-habit-devlog-chronicles-part-4</guid><category><![CDATA[Svelte]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[DrizzleORM]]></category><category><![CDATA[app development]]></category><category><![CDATA[SQLite]]></category><category><![CDATA[#codenewbies]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Wed, 15 Oct 2025 16:42:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758847173467/d06645dc-bc16-47d3-adad-1ac6cab4cf8e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-project-progress">Project Progress</h2>
<p>We’re getting close to the end, folks! Most of the main features are complete and working, except for a handful of bugs that aren’t breaking the app. However, I’d like to address them before pushing this to production. For those curious, here are the features that have been built and completed:</p>
<pre><code class="lang-markdown"><span class="hljs-bullet">-</span> <span class="hljs-strong">**Quest System**</span> (DB, API, UI, rewards)
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Authentication**</span> (login, signup, password recovery, rate limiting)
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Habit Tracking**</span> (create, complete, categories, frequencies, streaks)
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Character System**</span> (creatures, stats, XP, leveling)
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Equipment System**</span> (schema, definitions, display) (Item creation/generation will be a future enhancement)
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Dashboard**</span> (daily progress, habit overview)
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Settings**</span> (preferences UI + backend)
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Security**</span> (rate limiting, security headers, CSRF)
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Tauri Integration**</span> (desktop-ready)
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Test Coverage**</span> (142 passing tests)
</code></pre>
<p>At first glance, by a layman, this might look like a very short list of completed features, but let me tell you, I’ve never worked so in-depth within the noted areas for any project I’ve worked on in the past. The amount of code needed to fulfill the specific definition of done for these milestones is considerably greater than if I were to just create a standard habit tracking app, caused by my own hand and ambition, lol. The Character Progression, Custom Authentication (should have just used BetterAuth 🥲 but I now have a tested example for when it DOES make sense to create my own auth system lol), Quest Systems, and Security/Test coverage are the main culprits for this added complexity. However, I don’t (fully) regret my decisions one bit! I’m 1 month away from my year of starting the work on it, and it’s looking like I’ll be hitting my timeline for when I wanted to have an MVP for this project. With that said, let's get into the main updates since Part 3.</p>
<h2 id="heading-just-a-few-new-features">Just a Few New Features</h2>
<p>I feel most of my work since the last post has been more “re-working” of existing features, bug fixes, and enhancing a few areas, whether it was for UX or if it was for my own repo organization and efficiency. There were a few features introduced, though, that wrapped up the feature work needed for the MVP.</p>
<ul>
<li><p><strong>Quest System</strong></p>
</li>
<li><p><strong>Waitlist</strong></p>
</li>
<li><p><strong>Dynamic Navigation</strong></p>
</li>
</ul>
<p>These ^ are the features that add the final pieces to MVP, and the bulk of the addition was for the Quest System. That feature required the reworking of a few of my existing components, like the stat allocation system, character progression, and the need for an update to the schema, let alone building out the quest system logic. I honestly didn’t know how I’d approach this at first, but I figured I’d keep it simple.</p>
<h2 id="heading-quest-system">Quest System</h2>
<h3 id="heading-how-the-quests-work">How the Quests Work</h3>
<p>The quests are generated on a daily basis, and the user gets one quest per day. They are not entirely dynamic at the moment, but the order and correct answers are randomized. Each question has a skill check that has a chance of success based on the user’s corresponding stat. There are 3 levels of rewards for completing the quest.</p>
<ul>
<li><p>Less than 3 questions right: 50 EXP</p>
</li>
<li><p>3 out of 5 questions right: 100 EXP + 1 Stat Boost point</p>
</li>
<li><p>5 out of 5 questions right: 100 EXP + 2 Stat Boost points</p>
</li>
</ul>
<p>Once the quest is complete, you’ll see the start button greyed out and a (broken) time displayed on the page. I’ll eventually get around to fixing the timer, but not too worried about that for the MVP.</p>
<h3 id="heading-the-overhaul-needed-for-the-quest-service">The Overhaul Needed for the Quest Service</h3>
<p>Off the rip, new Quest Service tables were needed:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760472171533/b4a2c8cf-74f0-4e92-8c3e-6e24f06446e4.png" alt class="image--center mx-auto" /></p>
<p>Okay, boring part out of the way. If you're curious about how those tables were built out, parse through the <a target="_blank" href="https://github.com/kdleonard93/creatures-of-habit/blob/master/src/lib/server/db/schema.ts">GitHub repo</a> to your heart’s desire. What I want to get into is the challenges and nitty-gritty of how the quest system works under the hood.</p>
<h3 id="heading-the-daily-quest-generation-challenge"><strong>The Daily Quest Generation Challenge</strong></h3>
<p>The first major hurdle was figuring out how to generate "one quest per day" without creating duplicates or letting users spam quests. So I took this approach:</p>
<p><strong><em>Template-Based Generation</em></strong>: I didn’t have the time (or, quite frankly, the skillset 🥲) to do what I wanted, which was to have an LLM generate unique questions and answers at random. So I created a seeding system that populates <code>quest_templates</code> with pre-written quest scenarios. Each template includes:</p>
<ul>
<li><p>A narrative setting (forest, dungeon, city, etc.)</p>
</li>
<li><p>Difficulty level (easy, medium, hard)</p>
</li>
<li><p>Base reward values</p>
</li>
</ul>
<p><strong><em>Daily Instance Creation:</em></strong> The <code>getDailyQuest()</code> function in <code>questService.ts</code> checks if the user already has a quest for today by:</p>
<ul>
<li><p>Querying for any quest instances created today (using date comparison)</p>
</li>
<li><p>If none exists, creates a new instance from a random template</p>
</li>
<li><p>Generating 5 questions using the <code>generateQuestQuestions()</code> helper</p>
</li>
</ul>
<p>This approach got me as close to what I wanted within my capacity: consistent quest quality from templates, but enough randomization to keep things fresh.</p>
<h3 id="heading-stat-check-system-the-feature-thats-supposed-to-hook-ya"><strong>Stat Check System: The Feature That’s Supposed to Hook Ya.</strong></h3>
<p>So up until this point, the stats didn’t really mean anything. Adding the skill checks for the quest system gave these stats purpose in life. The only stat that really meant anything was “Constitution” but it only increased the user’s health…….which is never altered since there are no battles lol, so in the end it was useless too. But while stats are going to be connected in the future with other features if/when they are added (Equipment requirements, Boss Fights, etc.), having them connected to the Quests is good enough for MVP.</p>
<p>The logic works like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> userStat = userStats[question.requiredStat]; <span class="hljs-comment">// e.g., user's strength = 12</span>
<span class="hljs-keyword">const</span> threshold = question.difficultyThreshold; <span class="hljs-comment">// e.g., 10 for medium difficulty</span>
<span class="hljs-keyword">const</span> successChance = <span class="hljs-built_in">Math</span>.min(<span class="hljs-number">0.95</span>, <span class="hljs-built_in">Math</span>.max(<span class="hljs-number">0.05</span>, userStat / threshold));
<span class="hljs-keyword">const</span> passed = <span class="hljs-built_in">Math</span>.random() &lt; successChance;
</code></pre>
<p>So if you have 15 strength and the question requires 10 strength, you have a 95% chance of success (capped at 95% to keep things interesting). But if you only have 5 strength, you're looking at a 50% chance. This creates a real incentive to invest in your stats through the stat boost point system.</p>
<h3 id="heading-the-stat-boost-point-economy"><strong>The Stat Boost Point Economy</strong></h3>
<p>This is where the quest system ties back into the progression loop. Completing quests with 3+ correct answers awards stat boost points, which users can spend to permanently increase any stat. This created a nice feedback loop:</p>
<ol>
<li><p>Complete habits → Gain XP → Level up</p>
</li>
<li><p>Take daily quest → Answer questions (success based on stats)</p>
</li>
<li><p>Complete quest → Earn stat boost points</p>
</li>
<li><p>Spend points to increase stats</p>
</li>
<li><p>Higher stats → Better quest success rates → More points</p>
</li>
</ol>
<p>But this also meant I had to refactor the entire stat system. Previously, stats were just static values from character creation. Now they needed to be:</p>
<ul>
<li><p><strong>Mutable</strong> (users can boost them)</p>
</li>
<li><p><strong>Tracked separately</strong> (base stats vs. equipment bonuses vs. racial bonuses)</p>
</li>
<li><p><strong>Calculated dynamically</strong> (effective stats = base + race + class + equipment)</p>
</li>
</ul>
<p>I added a <code>statBoostPoints</code> column to the <code>creature_stats</code> table and created new API endpoints:</p>
<ul>
<li><p><code>GET /api/character/stat-boost-points</code> - Fetch current stats and available points</p>
</li>
<li><p><code>POST /api/character/boost-stat</code> - Spend a point to increase a stat</p>
</li>
</ul>
<h3 id="heading-question-generation-the-rule-based-approach"><strong>Question Generation: The Rule-Based Approach</strong></h3>
<p>I initially considered using an LLM to generate quest questions dynamically, but that introduced too many variables (API costs, latency, content moderation, consistency). Instead, I built a rule-based question bank system in <code>questHelpers.ts</code>.</p>
<p>Each stat type has a pool of ~10-15 question templates with thematic choices:</p>
<ul>
<li><p><strong>Strength questions:</strong> Physical challenges.</p>
</li>
<li><p><strong>Intelligence questions:</strong> Puzzles and logic problems.</p>
</li>
<li><p><strong>Wisdom questions:</strong> Moral dilemmas and perception checks.</p>
</li>
<li><p><strong>Charisma questions:</strong> Social situations and persuasion.</p>
</li>
<li><p><strong>Dexterity questions:</strong> Agility and precision tasks.</p>
</li>
<li><p><strong>Constitution questions:</strong> Endurance and resilience tests.</p>
</li>
</ul>
<p>The <code>generateQuestQuestions()</code> function randomly selects 5 questions (one per stat, or weighted by quest difficulty), shuffles them, and randomizes which choice (A or B) is correct. This gives the illusion of variety without the complexity of true procedural generation.</p>
<h3 id="heading-the-integration-testing-headache"><strong>The Integration Testing Headache</strong></h3>
<p>Getting the quest system done was only half the battle. I ran into multiple roadblocks getting it to work reliably in the CI workflow.</p>
<p>The integration tests initially failed in GitHub Actions because I was using mock Turso database URLs that didn't actually exist. The tests would try to connect to <code>libsql://</code><a target="_blank" href="http://mock-db.turso.io"><code>mock-db.turso.io</code></a> and get HTTP 404 errors. The solution was to:</p>
<ol>
<li><p>Switch to local SQLite databases for testing (<a target="_blank"><code>file:test.db</code></a>)</p>
</li>
<li><p>Create test-specific versions of quest service functions</p>
</li>
<li><p>Set up proper test data with users, creatures, stats, and quest templates</p>
</li>
<li><p>Ensure cleanup between tests to avoid state pollution</p>
</li>
</ol>
<p>I also had to update the test database schema in <code>test-db.ts</code> to include all the quest-related tables and their foreign key relationships. This was tedious but necessary to catch bugs before they hit production.</p>
<h3 id="heading-the-reward-calculation-logic"><strong>The Reward Calculation Logic</strong></h3>
<p>The reward system needed to be fair, but also needed to incentivize good performance. Behold the simple as hell tier system:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> correctAnswers = questInstance.correctAnswers;
<span class="hljs-keyword">let</span> expReward = questInstance.expRewardBase; <span class="hljs-comment">// 50 XP base</span>
<span class="hljs-keyword">let</span> statBoostPoints = <span class="hljs-number">0</span>;

<span class="hljs-keyword">if</span> (correctAnswers &gt;= <span class="hljs-number">5</span>) {
  expReward += questInstance.expRewardBonus; <span class="hljs-comment">// +100 XP</span>
  statBoostPoints = <span class="hljs-number">2</span>;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (correctAnswers &gt;= <span class="hljs-number">3</span>) {
  expReward += questInstance.expRewardBonus; <span class="hljs-comment">// +100 XP</span>
  statBoostPoints = <span class="hljs-number">1</span>;
}
</code></pre>
<p>This creates clear breakpoints: getting 3 right is the minimum for bonus rewards, and perfect scores are doubly rewarded. It also means even if you fail the quest, you still get <em>something</em> (50 XP), so it never feels like a complete waste.</p>
<h3 id="heading-things-that-could-improve-the-quest-system"><strong>Things That Could Improve the Quest System</strong></h3>
<ol>
<li><p><strong>Use a state machine</strong> for quest status transitions (available → active → completed) to prevent edge cases</p>
</li>
<li><p><strong>Add quest categories</strong> (combat, exploration, social) to give users more variety</p>
</li>
<li><p><strong>Implement a cooldown system</strong> instead of strict "one per day" to be more forgiving of different timezones</p>
</li>
<li><p><strong>Cache quest questions</strong> on the client to reduce API calls during the quest flow</p>
</li>
<li><p><strong>Add quest history</strong> so users can see their past performance and track improvement</p>
</li>
</ol>
<p>Not going to even think about these, though, until after this is cleaned up and ready for production. These improvements would just be for if people actually start using this app…..which I doubt will happen <strong>😄.</strong></p>
<h2 id="heading-waitlist-feature">Waitlist Feature</h2>
<p>I wanted a way to gather a bit of data related to interest in this app. This feature is more so a test to see if folks are interested in things I’m making, and from what I’ve researched about people creating products or applications, is that the waitlist is the easiest and quickest field test for interest in your product. I don’t think too many people will actually sign up, but the hope is that a few will, and it will give me a bit of insight into how to effectively use a waitlist for the next thing I build. The way I built mine out is a bit different from how one might typically set it up due to the stack I used, but here is the gist of how this was set up.</p>
<h3 id="heading-the-database-schema-more-than-just-emails"><strong>The Database Schema: More Than Just Emails</strong></h3>
<p>Most waitlists just capture an email address and call it a day. But since I'm treating this as a learning experience, I wanted to capture data that would actually be useful for understanding any interested users:</p>
<pre><code class="lang-typescript">user_waitlist {
  id: UUID
  email: unique, required
  ipAddress: optional
  userAgent: optional
  referralSource: optional
  subscribedAt: timestamp
}
</code></pre>
<p>The analytics fields (<code>ipAddress</code>, <code>userAgent</code>, <code>referralSource</code>) give me insights into:</p>
<ul>
<li><p>Geographic distribution of interest</p>
</li>
<li><p>Device types (mobile vs. desktop)</p>
</li>
<li><p>Traffic sources (social media, direct, referrals)</p>
</li>
</ul>
<p>These are all optional and anonymized in logs, so I'm not storing anything sensitive, but they'll help me understand where interested audiences are coming from and what devices they're using.</p>
<h3 id="heading-rate-limiting-to-swat-spam"><strong>Rate Limiting to Swat 👋🏾 Spam</strong></h3>
<p>One thing I learned early on is that any public-facing form needs rate limiting. Without it, someone could easily spam the database with fake emails or run a script to fill it with trash data.</p>
<p>I reused my existing <code>rateLimit</code> middleware (which was built for the authentication endpoints) and applied it to the waitlist API:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">await</span> rateLimit(event, {
  windowMs: <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>, <span class="hljs-comment">// 1 hour window</span>
  maxRequests: <span class="hljs-number">5</span>,           <span class="hljs-comment">// 5 submissions per hour</span>
  message: <span class="hljs-string">'Too many waitlist submissions. Please try again later.'</span>
});
</code></pre>
<p>Five submissions per hour felt sufficient—strict enough to prevent abuse, but lenient enough that someone who mistypes their email isn't locked out.</p>
<h3 id="heading-the-duplicate-email-problem"><strong>The Duplicate Email Problem</strong></h3>
<p>This was one of those "seems simple, but actually isn't" problems. What happens when someone tries to sign up twice? I needed to handle three scenarios:</p>
<ol>
<li><p><strong>New email</strong> → Add to database, show success</p>
</li>
<li><p><strong>Duplicate email (caught by my query)</strong> → Show "already signed up" message</p>
</li>
<li><p><strong>Duplicate email (caught by database constraint)</strong> → Show "already signed up" message</p>
</li>
</ol>
<p>The tricky part was that SQLite's unique constraint throws an error on duplicates, but I wanted to handle this gracefully without showing users a scary error message.</p>
<p>My solution was to check proactively first:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> existingEntry = <span class="hljs-keyword">await</span> db.select()
  .from(schema.userWaitlist)
  .where(eq(schema.userWaitlist.email, email))
  .limit(<span class="hljs-number">1</span>);

<span class="hljs-keyword">if</span> (existingEntry.length &gt; <span class="hljs-number">0</span>) {
  <span class="hljs-keyword">return</span> json({
    success: <span class="hljs-literal">true</span>,
    message: <span class="hljs-string">'You\'re already on our waitlist!'</span>,
    alreadySignedUp: <span class="hljs-literal">true</span>,
    redirectTo: <span class="hljs-string">'/waitlist/thank-you'</span>
  });
}
</code></pre>
<p>Then catch any constraint violations as a fallback:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (message.includes(<span class="hljs-string">'unique'</span>) || message.includes(<span class="hljs-string">'constraint'</span>)) {
  <span class="hljs-keyword">return</span> json({
    success: <span class="hljs-literal">true</span>, <span class="hljs-comment">// Still return success!</span>
    message: <span class="hljs-string">'You\'re already on our waitlist!'</span>,
    alreadySignedUp: <span class="hljs-literal">true</span>,
    redirectTo: <span class="hljs-string">'/waitlist/thank-you'</span>
  });
}
</code></pre>
<p>Notice that I return <code>success: true</code> even for duplicates. From the user's perspective, their goal (being on the waitlist) is achieved, so it's a success. This was a UX decision that took me a minute to arrive at. Initially, I was returning an error, but that felt wrong.</p>
<h3 id="heading-privacy-conscious-analytics-with-posthog"><strong>Privacy-Conscious Analytics with PostHog</strong></h3>
<p>I wanted to track waitlist conversions without storing raw email addresses in my analytics platform. The solution was to hash emails on the client side before sending them to PostHog:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">anonymizeEmail</span>(<span class="hljs-params">email: <span class="hljs-built_in">string</span></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">string</span>&gt; </span>{
  <span class="hljs-keyword">const</span> emailHash = <span class="hljs-keyword">await</span> crypto.subtle.digest(<span class="hljs-string">'SHA-256'</span>, 
    <span class="hljs-keyword">new</span> TextEncoder().encode(email));
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Array</span>.from(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Uint8Array</span>(emailHash))
    .map(<span class="hljs-function"><span class="hljs-params">b</span> =&gt;</span> b.toString(<span class="hljs-number">16</span>).padStart(<span class="hljs-number">2</span>, <span class="hljs-string">'0'</span>))
    .join(<span class="hljs-string">''</span>);
}

posthog.capture(<span class="hljs-string">'waitlist_submission'</span>, {
  email_hash: emailHash,
  success: <span class="hljs-literal">true</span>,
  redirectTo: result.redirectTo || <span class="hljs-literal">null</span>,
  error: <span class="hljs-literal">null</span>
});
</code></pre>
<p>This gives me conversion tracking and funnel analysis without compromising user privacy. The hash is one-way, so I can't reverse it to get the original email, but I can still track unique conversions and identify patterns. This felt like the right approach for a privacy-conscious app.</p>
<h3 id="heading-the-thank-you-page-adding-social-proof"><strong>The Thank You Page: Adding Social Proof</strong></h3>
<p>After submission, users are redirected to a thank you page that shows how many people have joined the waitlist:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> waitlistCount = <span class="hljs-keyword">await</span> db.select({count: count()})
  .from(userWaitlist);

<span class="hljs-keyword">return</span> {
  potentialHeroes: {
    usersJoined: waitlistCount[<span class="hljs-number">0</span>]?.count ?? <span class="hljs-number">0</span>,
    launchDate: <span class="hljs-string">'Q1 2026'</span>,
  }
};
</code></pre>
<p>"Join {# of waitlist users} early adopters" is more compelling than just "Thanks for signing up" and it’s a nice lil tally for the public to see how many other people would be enjoying the app with them.</p>
<p>The count is queried server-side on every page load, so it's always up-to-date. For a high-traffic site, I'd probably cache this, but for a pre-launch waitlist, the real-time count feels more valuable.</p>
<h3 id="heading-automatic-referral-source-tracking"><strong>Automatic Referral Source Tracking</strong></h3>
<p>One feature I'm particularly happy with is the automatic referral tracking:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> referralSource = validatedData.referralSource || 
  event.request.headers.get(<span class="hljs-string">'referer'</span>) || 
  <span class="hljs-string">'direct'</span>;
</code></pre>
<p>This automatically captures where users came from, even without custom UTM parameters. I can still add custom referral parameters (like <code>?ref=twitter</code>) to track specific campaigns, but the automatic fallback to the HTTP referer header means I get data even without them.</p>
<p>This will help me answer questions like:</p>
<ul>
<li><p>Which social media platform drives the most signups?</p>
</li>
<li><p>Are people finding me through organic search or paid ads?</p>
</li>
</ul>
<h3 id="heading-input-validation-keeping-data-clean"><strong>Input Validation: Keeping Data Clean</strong></h3>
<p>I used Zod for runtime validation of the email input:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> waitlistSchema = z.object({
  email: z.string().email(<span class="hljs-string">'Please enter a valid email address'</span>),
  referralSource: z.string().optional(),
});
</code></pre>
<p>This catches malformed emails before they hit the database and provides user-friendly error messages. The validation runs server-side, so even if someone bypasses the frontend validation (by using curl or Postman), they can't submit garbage data.</p>
<h3 id="heading-what-i-learned"><strong>What I Learned</strong></h3>
<p>Building this waitlist feature taught me a few things:</p>
<ol>
<li><p><strong>Rate limiting is non-negotiable</strong> - Any public form needs protection from spam</p>
</li>
<li><p><strong>Error handling is UX</strong> - How you handle duplicates and errors matters as much as the happy path</p>
</li>
<li><p><strong>Analytics need privacy</strong> - You can track conversions without compromising user data</p>
</li>
<li><p><strong>Social proof works</strong> - Showing the signup count makes the page feel more alive</p>
</li>
</ol>
<p>There are a handful of things I still need to add though:</p>
<ul>
<li><p>Email verification</p>
</li>
<li><p>A referral program (…..maybe)</p>
</li>
<li><p>More fields to capture user interests or feature requests</p>
</li>
<li><p>An admin dashboard to view signups and export emails</p>
</li>
</ul>
<p>But for a first attempt at a waitlist? I'm happy with how it turned out. It's secure, it handles edge cases, and it's already set to provide me with useful data about who's interested in the app.</p>
<h2 id="heading-dynamic-navigation-update">Dynamic Navigation Update</h2>
<p>This one is cut and dry, I updated the nav to be different for folks who aren’t signed in and for people who are. Initially, most of the nav items were all related to a user’s profile, quests, and creature info. So, I needed to improve the UX so that the navigation that showed for the user was usable and useful.</p>
<h4 id="heading-unauthenticated-user">Unauthenticated User</h4>
<p>For unauthenticated users, the user still sees the homepage, but there is a brand new Waitlist landing page they can go to where they can sign up. But notice that the nav only has 4 links, which are all informational:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760543941868/80113bc3-a2f7-466b-b912-052d34500c86.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Waitlist</p>
</li>
<li><p>Features</p>
</li>
<li><p>How It Works</p>
</li>
<li><p>FAQ</p>
</li>
</ul>
<p>These links are also in the footer, but this gives the user more visibility to informational pages about this app and what would eventually include information about myself and Digital Dopamine LLC 😏.</p>
<p>There’s not much else to get into regarding this feature since most of the work was for the waitlist portion, but here is the portion of my <code>Header.svelte</code> component was updated:</p>
<pre><code class="lang-typescript">    <span class="hljs-comment">// User navigation items</span>
    <span class="hljs-keyword">const</span> userNavItems = [
      { href: <span class="hljs-string">'/dashboard'</span>, label: <span class="hljs-string">'Dashboard'</span> },
      { href: <span class="hljs-string">'/habits'</span>, label: <span class="hljs-string">'Habits'</span> },
      { href: <span class="hljs-string">'/character/details'</span>, label: <span class="hljs-string">'Character'</span> },
      { href: <span class="hljs-string">'/quests'</span>, label: <span class="hljs-string">'Quests'</span> }
    ];

    <span class="hljs-comment">// Marketing navigation items (shown when not authenticated)</span>
    <span class="hljs-keyword">const</span> marketingNavItems = [
      { href: <span class="hljs-string">'/waitlist'</span>, label: <span class="hljs-string">'Waitlist'</span> },
      { href: <span class="hljs-string">'/features'</span>, label: <span class="hljs-string">'Features'</span> },
      { href: <span class="hljs-string">'/how-to-play'</span>, label: <span class="hljs-string">'How It Works'</span> },
      { href: <span class="hljs-string">'/faq'</span>, label: <span class="hljs-string">'FAQ'</span> }
    ];

    <span class="hljs-comment">// Auth items for consistent usage</span>
    <span class="hljs-keyword">const</span> authItems: <span class="hljs-built_in">Array</span>&lt;{ href: <span class="hljs-built_in">string</span>; label: <span class="hljs-built_in">string</span>; variant: ButtonVariant }&gt; = [
      { href: <span class="hljs-string">'/login'</span>, label: <span class="hljs-string">'Log in'</span>, variant: <span class="hljs-string">'secondary'</span> },
      { href: <span class="hljs-string">'/signup'</span>, label: <span class="hljs-string">'Sign up'</span>, variant: <span class="hljs-string">'default'</span> }
    ];

    <span class="hljs-keyword">const</span> path = $derived($page.url.pathname);
    <span class="hljs-keyword">const</span> isAuthenticated = $derived(!!$page.data.user);
    <span class="hljs-keyword">const</span> navItems = $derived(isAuthenticated ? userNavItems : marketingNavItems);
</code></pre>
<p>The header uses Svelte 5's <code>$derived</code> runes to automatically switch between two navigation menus based on whether the user is authenticated. Authenticated users see the “user navigation”, while visitors see “marketing pages”. This keeps the navigation clean and contextually relevant.</p>
<h2 id="heading-cleanup-and-stompin-on-bugs">Cleanup and Stompin’ on Bugs</h2>
<p>Was two-stepping on bugs so good, Orkin called and asked for consulting. And if you need a visual example of what I mean…..</p>
<p><img src="https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExdDVpc2k2YzA2bnNsMzRtY3cwN2NjOHdtNTMwOXl1b29oNTl0ZGx4OCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/1o1fLTWAXiCms5Cxc6/giphy.gif" alt class="image--center mx-auto" /></p>
<p>This ^…….This is how I was steppin’ on em 😂. All jokes aside, there were a TON of bug fixes that went into place over the last couple of months, as well as some overall cleanup. Removed a good amount of code that had no purpose anymore….or at all lol. I also made a few visual updates to the character creation so it feels a bit more prod-ready.</p>
<h4 id="heading-icons-logo">Icons + Logo</h4>
<p>All icons, including my logo, got an update. I’m leveraging <code>game-icons</code> in place of my custom-made (and very sloppy-looking 🥴) icons, I made at the start of this project. The OG logo, while not sloppy, still looked a little too pixelated for what I was aiming for, so I used another random icon from game-icons and altered it to give it a custom color scheme. It’s all still SVGs, so that’s a plus, and if needed, I can go in and customize each icon as I see fit. But for now, most of them will just remain the default color of white with black outlines.</p>
<h2 id="heading-looking-ahead"><strong>Looking Ahead</strong></h2>
<p>As we approach the finish line for MVP, there are just a few main things I want to take care of before making a Beta live for people to test out:</p>
<ol>
<li><p>API Endpoint Fixes</p>
</li>
<li><p>Fix Failing Tests</p>
</li>
<li><p>A bit of UX + UI polishing</p>
</li>
<li><p>Basic documentation</p>
</li>
</ol>
<p>This should be about 2 to maybe 3 weeks of work, depending on how busy I am with other life responsibilities. But there is a chance I get some additional free time earlier than that and might power through the final bit of work I want to complete. But that’s it for now and the next post you’ll see from me is when the app is live on a <em>very cool</em> URL 😉.</p>
<p>Until next time, folks. Be easy and keep pushin’ 🤘🏾.</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-work-or-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my work or want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://bsky.app/profile/digitaldopamine.dev"><strong>🦋 Bluesky</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Creatures of Habit: Devlog Chronicles - Part 3]]></title><description><![CDATA[Check-in time, A LOT has been worked on.
Sup folks, it's been nearly two months since my last post, and a ton of work has been done on “Creatures of Habit”. It’s so much that by the time I went through every little thing, I’d lose the attention of 5 ...]]></description><link>https://blacknerd.dev/creatures-of-habit-devlog-chronicles-part-3</link><guid isPermaLink="true">https://blacknerd.dev/creatures-of-habit-devlog-chronicles-part-3</guid><category><![CDATA[Svelte]]></category><category><![CDATA[turso]]></category><category><![CDATA[DrizzleORM]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[app development]]></category><category><![CDATA[SQLite]]></category><category><![CDATA[habits]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Thu, 31 Jul 2025 00:15:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1753405630912/269c805d-9931-42cd-a3e3-1c82c3f6c5e8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-check-in-time-a-lot-has-been-worked-on">Check-in time, A LOT has been worked on.</h2>
<p>Sup folks, it's been nearly two months since my last post, and a ton of work has been done on “Creatures of Habit”. It’s so much that by the time I went through every little thing, I’d lose the attention of 5 people still keeping up with my articles.</p>
<p><img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExM2Rub25zd2ppaHA5eHhuejB4NXk5bnY0ZjJmOGkybGJ0dTI4MnZkZCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9cw/rnHMRKkJ7EYc2RBLrH/giphy.gif" alt class="image--center mx-auto" /></p>
<p>But bad jokes aside, I’ve worked on numerous fixes and improvements to the UI, set up the base work for staging deploys, implemented Tauri for mobile and desktop app capabilities, dedicated local db setup (in progress), and Auth0 implementation (research, no code yet). I’ve also been doing things to situate myself in the proper position if I ever want to ship this app. It’s growing into something I never imagined I’d be able to make, but I’ve been loving the journey. So I’ve officially registered my business “Digital Dopamine LLC” and trademarked it ✊🏾. I’ve been having discussions with some peers of mine, and it seems we are all clear that we want to be able to work for ourselves at some point. They all have different professions, and when combining our heads and skills, we see a lot of opportunity to be had, even during these troubling times here in the States.</p>
<p>With that said, I’m going to cover the following in this article:</p>
<ul>
<li><p>An overview of the code that has been committed.</p>
</li>
<li><p>Background on the LLC and Trademark decision</p>
</li>
<li><p>Other project ideas that sprouted from the conversations I’ve had. None of which I’ll be starting before getting this app to a good spot.</p>
</li>
</ul>
<p>So let’s get into it.</p>
<h2 id="heading-code-updates-galore">Code Updates Galore</h2>
<p>Most of the code updates related to UI fixes, theme changes, big improvements to data integrity, improved performance, with some maintenance &amp; debugging updates to top it off. Like always, you can check out the pull requests merged in since my last post (May 27th) → <a target="_blank" href="https://github.com/kdleonard93/creatures-of-habit/pulls?q=is%3Apr+is%3Aclosed">https://github.com/kdleonard93/creatures-of-habit/pulls?q=is%3Apr+is%3Aclosed</a>. Below is a quick screenshot for the folks who don’t know their way around GitHub.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753410759919/6fd164c2-b906-41f1-a2e6-a7a15960146f.png" alt class="image--center mx-auto" /></p>
<p>I follow the same flow of putting every update behind a PR, even if it’s just me working on this project. My current automated testing suite does a great job of helping me catch issues before successfully committing and pushing. This helped me prevent a few breaking changes when updating packages and Svelte versions, amongst other things like lint errors.</p>
<h3 id="heading-changes-to-the-theme">Changes to the Theme</h3>
<p>There have been many changes to the theme. Mostly visually, but there are a few things updated with the logic on some components. The main one being the Progress bar. In my last article, I mentioned I added the progress bar along with some other UX improvements for the Dashboard. But I discovered a handful of bugs with the Progress bar, like issues with habit completions, how the state is updated when new habits are added after already completing a few or all habits, and resetting that state each day. It was tricky as hell. I had an issue with what I was retrieving from the DB as a completed VS active habit. The math didn’t account for new habits being added, so I pretty much had an update for it to take: <code>(number of rows where completed = true / total number of rows) * 100</code>. This way, once new habits were added, the total changed, fixing my calculation. Then I was able to pass the function’s return down as a prop and assign the value to the Progress Bar UI that’s on the user’s Dashboard.</p>
<p>Outside of that, most changes were just a bunch of CSS and Tailwind config updates. Below is a quick video of me touring the “New and Improved Design!!!!!!”:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.loom.com/share/1e88db6da12643d4b562db0f9409cd44?sid=89d48ab7-8d49-47ee-b940-64ab77490f4a">https://www.loom.com/share/1e88db6da12643d4b562db0f9409cd44?sid=89d48ab7-8d49-47ee-b940-64ab77490f4a</a></div>
<p> </p>
<h3 id="heading-taurica-taurida-and-tauris">Taurica, Taurida, and Tauri(s)</h3>
<p>If the section title doesn’t make sense, check out the <a target="_blank" href="https://en.wikipedia.org/wiki/Tauri">wiki</a>. When trying to make a unique title, I learned that the Tauri were ancient people who settled in the Crimea peninsula. Not sure if the Tauri framework was inspired by the Tauri people, but it made for a good title 😄.</p>
<p>Anyway, to get back on track, I wanted to discuss Tauri and why I’ve integrated this framework into my app. Better yet, I’ll just post their sales pitch below:</p>
<blockquote>
<p>Tauri is a framework for building tiny, fast binaries for all major desktop and mobile platforms. Developers can integrate any frontend framework that compiles to HTML, JavaScript, and CSS for building their user experience while leveraging languages such as Rust, Swift, and Kotlin for backend logic when needed.</p>
</blockquote>
<p>Basically, this framework will allow me to launch this app on Mobile and Desktop as well as Web. The only additional headache is to at least learn to understand Rust. So far, I have not needed to write anything extensive in Rust that wasn’t a part of their <a target="_blank" href="https://tauri.app/start/frontend/sveltekit/">SvelteKit setup docs</a>. It was pretty smooth to get it implemented as well. Just an add of the cli with <code>pnpm add -D @tauri-apps/cli@latest</code> a quick <code>pnpm tauri init</code> did about 85% of what’s needed to make this app cross-platform. The remaining 15% was updated by me, but very minimally, and there is a dope repo I found from this <a target="_blank" href="https://www.reddit.com/r/sveltejs/comments/1kqtmdl/announcing_v20_of_tauri_svelte_5_shadcnsvelte/?share_id=NW2Nbxy6bLhw_WsYNcvER&amp;utm_content=2&amp;utm_medium=android_app&amp;utm_name=androidcss&amp;utm_source=share&amp;utm_term=3">post in the sveltejs subreddit</a>: <a target="_blank" href="https://github.com/alysonhower/tauri2-svelte5-shadcn">https://github.com/alysonhower/tauri2-svelte5-shadcn</a>. Which was perfect for my project since I’m using Svelte v5 as well. So referencing his config files helped a lot in getting mine set. That’s it! I’d say I’m 95% cross-platform ready, with the remaining 5% saved for figuring out how to push this to production once I get to that point.</p>
<h3 id="heading-small-but-impactful">Small But Impactful</h3>
<p>The rest of the code changes were pretty small in terms of functionality, but there were heavy visual upgrades, Product &amp; Support pages built out, and dummy content added to them. There was also some research done on tools I wanted to add, like Auth0, as well as get a local DB setup, so I’m not constantly manipulating the real DB. A local DB is even more important to have before this app launches, so I can test properly after it’s live. I won’t get into the details of the code added for this part since it seems unnecessary, but you can see me go through the pages in the video above.</p>
<h2 id="heading-llc-me-baby">LLC Me, Baby</h2>
<p>Not too long ago, at my place of work, which currently pays my bills, there was an available company with Rocket Lawyer. During that meeting, I learned that as a Cars.com employee, I get a few free benefits with their services, one being setting up an LLC. It just felt like the right move to do, considering how rapidly things are changing in the industry now and in our society in general. There’s no telling when Rocket Lawyer’s partnership might be replaced with a different one.</p>
<p>So I pulled the trigger, and while I should have planned a big spend like this a bit better 😅, it was relatively affordable in terms of securing the name of your business and trademark. So the plan is to release any products/projects moving forward under <code>Digital Dopamine</code>. I’ve had the name for a while, as my portfolio website’s URL is <a target="_blank" href="https://digitaldopamine.dev/">digitaldopamine.dev</a>, and it hits home for me. Today’s digital age is all about quick and fast dopamine hits from social media, gaming, or any form of visual entertainment, and I saw no better name for my company that will be building apps and tools for the geeks around the web. Which leads me to the next project I started discussing with some close friends of mine.</p>
<h2 id="heading-new-appfor-next-year">New App…..For Next Year</h2>
<p>One of my good friends loves to cook and wanted to start a smash burger business in the burbs of Chicago, since there are no good smash burger locations once you leave the metropolitan area. I initially offered just to make his landing page, but after talking through the idea for an hr or so, I asked if there was any app that he would love that isn’t out at the moment. We found an app called RecipeMe, and while that app does a lot of good things, it was missing one big feature. Not that they will be reading my article, but I don’t want to say exactly what feature we were talking about, but it would require an LLM implementation, and I have a few ideas to branch off of that as well, that’s catered to our culture of cooking. One thing I need to do is start researching the OG recipes from our parents and grandparents and get that data stored in a Notion doc or something. That way, once I’m ready to start building it, I’ll be prepped with unique homemade recipes (hopefully) not found anywhere else on the internet.</p>
<h2 id="heading-looking-ahead">Looking Ahead</h2>
<p>That’s about it for this write-up. Please check out the repo if you want a deeper dive into what code has been implemented since Part 2. Creatures of Habit is coming together nicely, but I still have a lot of work left to do. A quick look at what’s ahead:</p>
<ol>
<li><p><strong>Push Notifications</strong>: I need to figure out how to get push notifications working on mainly the Desktop and Mobile apps, and then follow that up with web.</p>
</li>
<li><p><strong>User Management Enhancement</strong>: I need to implement a “Forgot Your Password” feature as well as a “Delete Your Profile” feature, along with other user management improvements.</p>
</li>
<li><p><strong>Offline Support Research (cont.)</strong>: While we have Tauri set up, we still need to get an “Offline First” implementation in.</p>
</li>
</ol>
<p>Until next time, folks. Be easy and keep pushin’ 🤘🏾.</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-work-or-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my work or want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://bsky.app/profile/digitaldopamine.dev"><strong>🦋 Bluesky</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Creatures of Habit: Devlog Chronicles - Part 2]]></title><description><![CDATA[Refining the Foundation and Gearing Up +1
As you can see from the new terrain in this cover image vs the last article's, we've made it out of the "Wacced out Woods" and have arrived at "Finessin' Fields". Time to gear up ✊🏾.
Over the last couple of ...]]></description><link>https://blacknerd.dev/creatures-of-habit-devlog-chronicles-part-2</link><guid isPermaLink="true">https://blacknerd.dev/creatures-of-habit-devlog-chronicles-part-2</guid><category><![CDATA[Web Development]]></category><category><![CDATA[Svelte]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[TDD (Test-driven development)]]></category><category><![CDATA[UX]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Wed, 28 May 2025 02:58:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748635382254/a7ab8ed8-198f-42c4-ba1f-2c2c5c327084.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-refining-the-foundation-and-gearing-up-1"><strong>Refining the Foundation and Gearing Up +1</strong></h2>
<p>As you can see from the new terrain in this cover image vs the last article's, we've made it out of the "Wacced out Woods" and have arrived at "Finessin' Fields". Time to gear up ✊🏾.</p>
<p>Over the last couple of months, I've been working on improving the quality of the code and my testing implementation. I also needed to get a workflow set for automation since this is turning into a pretty big codebase. I never knew how much would need to go into an app like this…..or this is just a lesson on choosing a more fitting tech stack next time lol.</p>
<p><img src="https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExNTlqdWN0YjR1d3BsN3l4cm5kanJ2NnV6NGs0bnlhYTFuMHZobDFnbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/ogb8RQdu8zQyc/giphy.gif" alt class="image--center mx-auto" /></p>
<p>For this write up I’ll go over the following:</p>
<ul>
<li><h3 id="heading-testing-amp-quality">🧪 Testing &amp; Quality:</h3>
<p>  <strong>- Test Driven Development (TDD) Implementation- Component Testing Templates- TypeScript Import Fixes</strong></p>
</li>
<li><h3 id="heading-user-experience">📊 User Experience:</h3>
<p>  <strong>- Responsive Dashboard with Daily Habit Overview- Visual Progress Bars for Habit Completion- Quick-Complete Functionality from Dashboard</strong></p>
</li>
<li><h3 id="heading-developer-workflow">🔄 Developer Workflow:</h3>
<p>  <strong>- Biome for Code Quality (Replacing ESLint)- CI Workflow with TypeScript &amp; Style Checks- Pre-commit &amp; Pre-push Hooks</strong></p>
</li>
</ul>
<p>As always, you can check out the code on the repo here → <a target="_blank" href="https://github.com/kdleonard93/creatures-of-habit">https://github.com/kdleonard93/creatures-of-habit</a>. Let's dive into the details <strong>Testing &amp; Quality Assurance</strong></p>
<p>One of the biggest shifts in my development approach has been embracing Test Driven Development (TDD). Word on the street is that for the type of app im making, which will be extremely component heavy, benefits from this type of approach to avoid unforeseen troubles down the line when I have actual users….which will be friends, family, and whoever is bored enough to sign up 😏 so maybe i could have stuck with my OG approach. I digress, the point is implementing this way for testing required me to yet again, learn something new and say goodby to some other knowledge i recently gained for a totally diff project or for the job that actually feeds me.</p>
<h3 id="heading-test-driven-development-implementation">Test Driven Development Implementation</h3>
<p>So here I am, embracing the TDD workflow: write tests first, implement the minimum code to make them pass, then refactor while keeping tests green. This approach has already caught several bugs before they made snuck their way into a future production build and has forced me to think more clearly about component interfaces and responsibilities.</p>
<p>The TDD cycle looks a little something like this:</p>
<ol>
<li><p>Write a test that defines how the code should work</p>
</li>
<li><p>Run the test (it should fail)</p>
</li>
<li><p>Write the minimum code to make the test pass</p>
</li>
<li><p>Run the test again (it should pass)</p>
</li>
<li><p>Refactor the code while keeping tests passing</p>
</li>
<li><p>Repeat for additional features or edge cases</p>
</li>
</ol>
<p>Here's a real example from my implementation of the <code>DailyProgressSummary</code> component:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// First, I wrote the test</span>
it(<span class="hljs-string">'calculates completion percentage correctly'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> habits = [
    { id: <span class="hljs-string">'1'</span>, completedToday: <span class="hljs-literal">true</span> },
    { id: <span class="hljs-string">'2'</span>, completedToday: <span class="hljs-literal">false</span> },
    { id: <span class="hljs-string">'3'</span>, completedToday: <span class="hljs-literal">true</span> }
  ];

  <span class="hljs-keyword">const</span> { getByText } = render(DailyProgressSummary, { props: { habits } });
  expect(getByText(<span class="hljs-string">'67%'</span>)).toBeInTheDocument();
  expect(getByText(<span class="hljs-string">'(2/3)'</span>)).toBeInTheDocument();
});
</code></pre>
<p>Then I implemented the component with just enough code to make the test pass:</p>
<pre><code class="lang-svelte"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ts"</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">let</span> habits: </span></span><span class="javascript">{ <span class="hljs-attr">id</span>: string, <span class="hljs-attr">completedToday</span>: boolean }</span><span class="xml"><span class="javascript">[] = [];

  $: totalHabits = habits.length;
  $: completedHabits = habits.filter(<span class="hljs-function"><span class="hljs-params">h</span> =&gt;</span> h.completedToday).length;
  $: completionPercentage = totalHabits &gt; <span class="hljs-number">0</span> 
    ? <span class="hljs-built_in">Math</span>.round((completedHabits / totalHabits) * <span class="hljs-number">100</span>) 
    : <span class="hljs-number">0</span>;
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"bg-card p-3 rounded-lg border shadow-sm flex flex-col items-center"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-sm font-medium mb-1"</span>&gt;</span>Today's Progress<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex items-center gap-2"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-2xl font-bold"</span>&gt;</span></span><span class="javascript">{completionPercentage}</span><span class="xml">%<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-muted-foreground text-sm"</span>&gt;</span>(</span><span class="javascript">{completedHabits}</span><span class="xml">/</span><span class="javascript">{totalHabits}</span><span class="xml">)<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
</code></pre>
<h3 id="heading-component-testing-templates"><strong>Component Testing Templates</strong></h3>
<p>To standardize my testing approach, I created reusable testing templates for different types of components:</p>
<ul>
<li><p><strong>UIComponentTest</strong>: For Svelte UI components.</p>
</li>
<li><p><strong>APIEndpointTest</strong>: For API endpoints and server routes.</p>
</li>
<li><p><strong>UtilityTest</strong>: For utility functions and helpers.</p>
</li>
</ul>
<p>These templates include best practices, common test patterns, and examples that make it much easier to write consistent, high-quality tests. No more staring at a blank file wondering how to structure a test!</p>
<h3 id="heading-typescript-fun-amp-import-fixes"><strong>TypeScript Fun &amp; Import Fixes</strong></h3>
<p>I ran into an annoying issue with TypeScript not recognizing Svelte component imports in test files. Here’s the error:</p>
<pre><code class="lang-bash">Cannot find module <span class="hljs-string">'$lib/components/habits/HabitForm.svelte'</span> or its corresponding <span class="hljs-built_in">type</span> declarations
</code></pre>
<p>Initially, I used the <code>@ts-ignore</code> hack to bypass the issue, but I knew that wasn't sustainable. After some research, I implemented a proper solution by creating type declarations for Svelte components in my test environment. This ensures TypeScript understands the imports and provides proper type checking. The <code>svelte.d.ts</code> is below for a quick reference:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// General declaration for all Svelte files</span>
<span class="hljs-keyword">declare</span> <span class="hljs-keyword">module</span> '*.svelte' {
  <span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { ComponentType } <span class="hljs-keyword">from</span> <span class="hljs-string">'svelte'</span>;
  <span class="hljs-keyword">const</span> component: ComponentType&lt;<span class="hljs-built_in">any</span>&gt;;
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> component;
}

<span class="hljs-comment">// Specific declaration for the component causing the error</span>
<span class="hljs-keyword">declare</span> <span class="hljs-keyword">module</span> '$lib/components/dashboard/DailyProgressSummary.svelte' {
  <span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { ComponentType } <span class="hljs-keyword">from</span> <span class="hljs-string">'svelte'</span>;
  <span class="hljs-keyword">const</span> component: ComponentType&lt;<span class="hljs-built_in">any</span>&gt;;
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> component;
}

<span class="hljs-comment">// Add other specific component paths as needed</span>
<span class="hljs-keyword">declare</span> <span class="hljs-keyword">module</span> '$lib/components/habits/HabitForm.svelte' {
  <span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { ComponentType } <span class="hljs-keyword">from</span> <span class="hljs-string">'svelte'</span>;
  <span class="hljs-keyword">const</span> component: ComponentType&lt;<span class="hljs-built_in">any</span>&gt;;
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> component;
}

<span class="hljs-comment">// Server module declarations</span>
<span class="hljs-keyword">declare</span> <span class="hljs-keyword">module</span> '$lib/server/streaks/calculations' {
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> StreakStatus {
    streakMaintained: <span class="hljs-built_in">boolean</span>;
    daysInStreak: <span class="hljs-built_in">number</span>;
    streakWeeks?: <span class="hljs-built_in">number</span>;
  }

  <span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> StreakUpdateResult {
    currentStreak: <span class="hljs-built_in">number</span>;
    longestStreak: <span class="hljs-built_in">number</span>;
    lastCompletedAt: <span class="hljs-built_in">string</span>;
  }

  <span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">determineStreakStatus</span>(<span class="hljs-params">
    habitData: { id: <span class="hljs-built_in">string</span>; userId: <span class="hljs-built_in">string</span>; frequencyId: <span class="hljs-built_in">string</span> },
    currentDate: <span class="hljs-built_in">Date</span>
  </span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">StreakStatus</span>&gt;</span>;

  <span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateStreakAfterCompletion</span>(<span class="hljs-params">
    habitId: <span class="hljs-built_in">string</span>,
    userId: <span class="hljs-built_in">string</span>
  </span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">StreakUpdateResult</span>&gt;</span>;
}

<span class="hljs-keyword">declare</span> <span class="hljs-keyword">module</span> '$lib/server/db' {
  <span class="hljs-keyword">type</span> MockFunction&lt;T = <span class="hljs-built_in">any</span>&gt; = T &amp; {
    mockReturnThis: <span class="hljs-function">() =&gt;</span> MockFunction&lt;T&gt;;
    mockImplementation: (fn: <span class="hljs-function">(<span class="hljs-params">...args: <span class="hljs-built_in">any</span>[]</span>) =&gt;</span> <span class="hljs-built_in">any</span>) =&gt; MockFunction&lt;T&gt;;
    mockImplementationOnce: (fn: <span class="hljs-function">(<span class="hljs-params">...args: <span class="hljs-built_in">any</span>[]</span>) =&gt;</span> <span class="hljs-built_in">any</span>) =&gt; MockFunction&lt;T&gt;;
    mockReturnValue: <span class="hljs-function">(<span class="hljs-params">value: <span class="hljs-built_in">any</span></span>) =&gt;</span> MockFunction&lt;T&gt;;
  };

  <span class="hljs-keyword">type</span> MockDB = {
    select: MockFunction&lt;<span class="hljs-function">() =&gt;</span> MockDB&gt;;
    <span class="hljs-keyword">from</span>: MockFunction&lt;<span class="hljs-function">(<span class="hljs-params">table: <span class="hljs-built_in">any</span></span>) =&gt;</span> MockDB&gt;;
    where: MockFunction&lt;<span class="hljs-function">(<span class="hljs-params">condition: <span class="hljs-built_in">any</span></span>) =&gt;</span> MockDB&gt;;
    orderBy: MockFunction&lt;<span class="hljs-function">(<span class="hljs-params">field: <span class="hljs-built_in">any</span></span>) =&gt;</span> MockDB&gt;;
    limit: MockFunction&lt;<span class="hljs-function">(<span class="hljs-params">num: <span class="hljs-built_in">number</span></span>) =&gt;</span> MockDB&gt;;
    update: MockFunction&lt;<span class="hljs-function">(<span class="hljs-params">table: <span class="hljs-built_in">any</span></span>) =&gt;</span> MockDB&gt;;
    set: MockFunction&lt;<span class="hljs-function">(<span class="hljs-params">data: <span class="hljs-built_in">any</span></span>) =&gt;</span> MockDB&gt;;
    execute: MockFunction&lt;<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">any</span>[]&gt;;
  };

  <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> db: MockDB;
}
</code></pre>
<p>I think there is an update being proposed that should make the components accessible from server files, making imports in test files much simpler.</p>
<h2 id="heading-user-experience-enhancements"><strong>📊 User Experience Enhancements</strong></h2>
<h3 id="heading-responsive-dashboard-with-daily-habit-overview"><strong>Responsive Dashboard with Daily Habit Overview</strong></h3>
<p>The dashboard has been completely revamped to provide users with a clear overview of their daily progress. The centerpiece is the new component that shows:</p>
<ul>
<li><p>Overall completion percentage for the day</p>
</li>
<li><p>Visual progress bar showing completion status</p>
</li>
<li><p>Fraction of completed habits (e.g., "2/5")</p>
</li>
</ul>
<p>This gives users immediate feedback on how they're doing for the day, creating that dopamine hit when they see their progress increase.</p>
<p>But here’s the kicker…..it doesn’t actually work as expected 😂. And that pointed out a major issue in the component tests that I’ve created thus far, which is that they work fine when using mock data but when trying to use the actual data stored in my db, I get a bunch of new errors that I haven’t been able to figure out. For now though, the mock data will be just fine and this is just gonna have to be one of the areas I come back to in the future.</p>
<h3 id="heading-visual-progress-bars-for-habit-completion"><strong>Visual Progress Bars for Habit Completion</strong></h3>
<p>Every habit now has its own progress visualization that clearly shows completion status at a glance. The dashboard implementation provides immediate visual feedback on habit tracking progress.</p>
<p>The progress bars are complemented by a color-coding system based on the habit's difficulty:</p>
<ul>
<li><p>Easy habits: Green</p>
</li>
<li><p>Medium habits: Yellow</p>
</li>
<li><p>Hard habits: Red</p>
</li>
</ul>
<p>This intuitive color scheme helps users quickly identify the difficulty level of each habit, making it easier to prioritize and manage their daily tasks. The visual indicators add both functionality and a splash of color to the interface, enhancing both usability and visual appeal.</p>
<p><strong>Quick-Complete Functionality from Dashboard</strong></p>
<p>Users can now complete habits directly from the dashboard with a single click, eliminating the need to navigate to individual habit pages. This seemingly small change has dramatically improved the user experience - it's all about reducing friction for those daily habit completions</p>
<p>The quick-complete buttons use the existing success variant in the Button component, maintaining visual and logical consistency.</p>
<h2 id="heading-developer-workflow-improvements"><strong>🔄 Developer Workflow Improvements</strong></h2>
<h3 id="heading-biome-for-code-quality"><strong>Biome for Code Quality</strong></h3>
<p>I've switched from ESLint to Biome for code quality checks. Biome is faster, more integrated, and provides both linting and formatting in a single tool. The configuration is simpler, and it plays nicely with the rest of my toolchain. I also can’t help myself when I see newer tools get a bunch of praise.</p>
<p>Setting up Biome was straightforward. Example:</p>
<pre><code class="lang-json"><span class="hljs-comment">// package.json scripts</span>
<span class="hljs-string">"format"</span>: <span class="hljs-string">"biome format --write ."</span>,
<span class="hljs-string">"format:check"</span>: <span class="hljs-string">"biome check"</span>,
<span class="hljs-string">"lint"</span>: <span class="hljs-string">"biome lint ."</span>,
<span class="hljs-string">"lint:fix"</span>: <span class="hljs-string">"biome lint . --apply"</span>
</code></pre>
<h3 id="heading-ci-workflow-with-typescript-amp-style-checks"><strong>CI Workflow with TypeScript &amp; Style Checks</strong></h3>
<p>The continuous integration workflow now includes:</p>
<ol>
<li><p>TypeScript type checking</p>
</li>
<li><p>Biome code style checks</p>
</li>
<li><p>Automated testing</p>
</li>
<li><p>Build verification</p>
</li>
</ol>
<p>So now I have a pretty <a target="_blank" href="https://www.loom.com/i/fa6e8b856df047549c8aa88fa1662a0f">decent set of tests</a> that need to pass before the PR is merged into <code>master</code>. The CI workflow runs on GitHub Actions and prevents merging code that doesn't meet the quality standards.</p>
<h3 id="heading-pre-commit-amp-pre-push-hooks"><strong>Pre-commit &amp; Pre-push Hooks</strong></h3>
<p>To catch issues before they even reach the repository, I've set up pre-commit hooks that run:</p>
<ul>
<li><p>TypeScript type checking</p>
</li>
<li><p>Biome style checks</p>
</li>
<li><p>All of the tests I have set up</p>
</li>
</ul>
<p>And a pre-push hook that ensures the project builds successfully before pushing code.</p>
<p>This multi-layered approach to quality assurance has already prevented several bugs from entering the codebase and has made the development process more robust. I’ve gained most of my CI knowledge from the work being done at my current company, and it’s been extremely helpful in catching errors as I continue developing the app.</p>
<h2 id="heading-quick-peek-of-whats-ahead"><strong>Quick Peek of What’s Ahead</strong></h2>
<p>While I've made significant progress on the engineering practices and user experience, there's still plenty on the roadmap:</p>
<ol>
<li><p><strong>Complete the XP System</strong>: Implement experience points for character progression and refine creature creation statistics.</p>
</li>
<li><p><strong>Enhance Dashboard Visualizations</strong>: Add streak tracking and long-term progress visualizations, plus fix habit completion data accuracy.</p>
</li>
<li><p><strong>Offline Support Research</strong>: Investigate offline capabilities for potential mobile app development.</p>
</li>
</ol>
<p>With our improved development practices in place, we're well-positioned to implement new features efficiently and maintain high quality standards….well that’s he hope at least 😉. Stay tuned for what's next on deck 🤘🏾.</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-work-or-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my work or want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://bsky.app/profile/digitaldopamine.dev"><strong>🦋 Bluesky</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Creatures of Habit: Devlog Chronicles - Part 1]]></title><description><![CDATA[Checkpoint Reached +1
Hot damn, looking back at all the incremental work done on my project, can’t help but pat myself on the back because this is the first app I have that has a SOLID foundation from front to back. Now by solid foundation, I mean mo...]]></description><link>https://blacknerd.dev/creatures-of-habit-devlog-chronicles-part-1</link><guid isPermaLink="true">https://blacknerd.dev/creatures-of-habit-devlog-chronicles-part-1</guid><category><![CDATA[Svelte]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[DrizzleORM]]></category><category><![CDATA[turso]]></category><category><![CDATA[habits]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Tue, 18 Mar 2025 01:26:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741748950386/6c782483-593f-4ad5-aa96-e29ec2cb0818.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-checkpoint-reached-1">Checkpoint Reached +1</h2>
<p>Hot damn, looking back at all the incremental work done on my project, can’t help but pat myself on the back because this is the first app I have that has a SOLID foundation from front to back. Now by solid foundation, I mean most of my core systems were built out and (most 😉) have tests to compliment them. Not only that, the tech stack used required me to create and manage my own schemas, authentication, and session handling. Compared to my last project <a target="_blank" href="https://github.com/kdleonard93/Leo_Ledger">“Leo Ledger”</a>, the scope itself has been completely different, in size and complexity. Trying to mesh a classic habit tracking app with RPG-like mechanics to gamify it has been my biggest challenge yet. A lot of this has been fun to learn and understand Svelte 5 and TypeScript a bit more, but I’m no expert now and have had my fair share of rage…..which feels like a right of passage with learning TypeScript lol.</p>
<p>Lo and behold, I’ve reached a major milestone, but am far from finished. In this article, I want to go over the new features added since my last entry:</p>
<ul>
<li><h3 id="heading-features">🔮 Features:</h3>
<p>  <strong>- Habit Completion</strong></p>
<p>  <strong>- XP Setup.</strong></p>
<p>  <strong>- Notifications System, Reminder, &amp; Testing Route.</strong></p>
<p>  <strong>-</strong> <a target="_blank" href="https://posthog.com/"><strong>PostHog</strong></a> <strong>Implementation (Analytics Tracking).</strong></p>
</li>
<li><h3 id="heading-fixes">🪚 Fixes:</h3>
<p>  <strong>-</strong> <code>Edit Habit</code> <strong>Functionality Fix.</strong></p>
<p>  <strong>-</strong> <code>Logout</code> <strong>Route POST action Fix for 1 of 2 CTAs.</strong></p>
</li>
<li><h3 id="heading-enhancements">🦾 Enhancements:</h3>
<p>  <strong>- New theme to give the app a bit of personality (NOT FINAL).</strong></p>
<p>  <strong>- Updated the</strong> <code>Signup</code> <strong>Route’s CTA with</strong> <code>{isSubmitting}</code> <strong>for a better UX.</strong></p>
<p>  <strong>- Implemented</strong> <code>Toast</code> <strong>in the app’s notification system.</strong></p>
<p>  <strong>- CodeRabbit</strong> <strong><em>(Temporary; Experimenting with it.)</em></strong></p>
</li>
</ul>
<p>As always you can peep the code on the repo here → <a target="_blank" href="https://github.com/kdleonard93/creatures-of-habit">https://github.com/kdleonard93/creatures-of-habit</a>. Let’s get started with the <strong>Features!</strong></p>
<h2 id="heading-new-features-enhancements">🔮 New Features + Enhancements</h2>
<p>Everything in my feature setup seems like it might be a small portion of the grand scope of the project but let me tell ya, that wasn’t the case.</p>
<h3 id="heading-habit-completion-amp-xp-feature">Habit Completion &amp; XP Feature</h3>
<p>This one was a bit of both a feature and an enhancement. The feature of adding a completed route and functionality enhanced the overall habit management system. Now there is a graveyard where all of your completed habits can go. On the <code>/completed</code> route, you can permanently delete those habits from the database. This ties into the XP feature since upon completing a habit, depending on the difficulty, you gain experience points!<br />However…….those points are worthless at the moment 😂. The XP feature isn’t fully fleshed out yet, but there is activity in the console with proper fetch requests. Below, you can see an example of 1 completed and 1 deleted habit (Need to add an indicator for each to differentiate them in the graveyard….that’s what I’m calling the <code>/complete</code> route from now on). Once I permanently delete one, you can see all of the action in the console:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.loom.com/share/fd92fb687cb143d0a89283d46bdc7566">https://www.loom.com/share/fd92fb687cb143d0a89283d46bdc7566</a></div>
<p> </p>
<ul>
<li><p>Once I click on “Delete Permanently” and confirm my choice, the application sends a DELETE request to the server.</p>
</li>
<li><p>After the deletion is complete, it invalidates (refreshes) the relevant data with a GET request.</p>
</li>
<li><p>Throughout this process, PostHog is tracking these events for analytics.</p>
</li>
</ul>
<p>Fun stuff right?? As you can also see in the video, the theme is updated and has some color! There will probably be further adjustments there but that’ll be down the line.</p>
<h3 id="heading-notifications-system-reminder-amp-testing-route">Notifications System, Reminder, &amp; Testing Route</h3>
<p>The notifications system was another BIG feature of the project. I wanted to create a flexible system that would support multiple notification types (in-app, email, SMS)so a plugin architecture was recommended as the best approach. This allows me to add new notification channels in the future without changing the core notification logic.</p>
<p>I implemented the system with three key components:</p>
<ol>
<li><p><strong>NotificationManager</strong>: The central hub that handles scheduling, displaying, and managing notifications</p>
</li>
<li><p><strong>NotificationStore</strong>: A Svelte store that maintains the state of active notifications</p>
</li>
<li><p><strong>Notification Plugins</strong>: Email and SMS plugins that demonstrate the extensibility of the system</p>
</li>
</ol>
<p>Here's the code from the <code>NotificationManager.ts</code> file that shows the plugin architecture:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Notification Plugin Interface</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> NotificationPlugin {
    send(notification: Notifications): <span class="hljs-built_in">void</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> NotificationManager {
    <span class="hljs-keyword">private</span> timeouts: <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">number</span>&gt; = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();
    <span class="hljs-keyword">private</span> plugins: NotificationPlugin[] = [];
    <span class="hljs-keyword">private</span> permission: NotificationPermission = <span class="hljs-string">'default'</span>;

    <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
        <span class="hljs-built_in">this</span>.requestPermission();
    }

    <span class="hljs-keyword">public</span> registerPlugin(plugin: NotificationPlugin): <span class="hljs-built_in">void</span> {
        <span class="hljs-built_in">this</span>.plugins.push(plugin);
    }

    <span class="hljs-comment">// Other code...</span>

    <span class="hljs-keyword">public</span> showNotification(id: <span class="hljs-built_in">string</span>, message: <span class="hljs-built_in">string</span>, <span class="hljs-keyword">type</span>: <span class="hljs-string">'email'</span> | <span class="hljs-string">'sms'</span> | <span class="hljs-string">'in-app'</span>): <span class="hljs-built_in">void</span> {
        <span class="hljs-comment">// Create notification record</span>
        <span class="hljs-keyword">const</span> notification: Notifications = {
            id,
            message,
            <span class="hljs-keyword">type</span>,
            timestamp: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()
        };

        <span class="hljs-comment">// Add to the notifications store</span>
        notifications.update(<span class="hljs-function"><span class="hljs-params">n</span> =&gt;</span> [...n, notification]);

        <span class="hljs-comment">// Send to plugins</span>
        <span class="hljs-built_in">this</span>.plugins.forEach(<span class="hljs-function"><span class="hljs-params">plugin</span> =&gt;</span> plugin.send(notification));
    }
}
</code></pre>
<p>Another difficult task was implementing the <strong>Habit Reminder</strong> feature, which allows users to set custom reminders for each habit. This uses the notification system but adds scheduled notifications based on time:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">scheduleReminder</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">if</span> (!reminderTime) {
        toast.error(<span class="hljs-string">'Please select a time for the reminder'</span>);
        <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// Parse the time input (HH:MM)</span>
        <span class="hljs-keyword">const</span> [hours, minutes] = reminderTime.split(<span class="hljs-string">':'</span>).map(<span class="hljs-built_in">Number</span>);

        <span class="hljs-comment">// Calculate when the reminder should trigger</span>
        <span class="hljs-keyword">const</span> now = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>();
        <span class="hljs-keyword">const</span> reminderDate = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>();
        reminderDate.setHours(hours, minutes, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>);

        <span class="hljs-comment">// If the time is earlier today, schedule for tomorrow</span>
        <span class="hljs-keyword">if</span> (reminderDate &lt;= now) {
            reminderDate.setDate(reminderDate.getDate() + <span class="hljs-number">1</span>);
        }

        <span class="hljs-keyword">const</span> delay = reminderDate.getTime() - now.getTime();

        <span class="hljs-comment">// Schedule the notification</span>
        notificationManager.scheduleNotification(
            <span class="hljs-string">`habit-reminder-<span class="hljs-subst">${props.habitId}</span>`</span>,
            <span class="hljs-string">`Time to complete your habit: <span class="hljs-subst">${props.habitTitle}</span>`</span>,
            <span class="hljs-string">'in-app'</span>,
            delay
        );

        <span class="hljs-comment">// Store reminder in localStorage for persistence</span>
        <span class="hljs-keyword">if</span> (hasLocalStorage) {
            <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">`reminder_<span class="hljs-subst">${props.habitId}</span>`</span>, <span class="hljs-built_in">JSON</span>.stringify({
                time: reminderTime,
                habitTitle: props.habitTitle
            }));
        }

        reminderEnabled = <span class="hljs-literal">true</span>;
        toast.success(<span class="hljs-string">`Reminder set for <span class="hljs-subst">${reminderTime}</span>`</span>);
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error scheduling reminder:'</span>, error);
        toast.error(<span class="hljs-string">'Could not schedule reminder'</span>);
    }
}
</code></pre>
<p>The magic happens in how the system handles timing. When you select 8:00 AM for your "Morning Workout" habit, the system doesn't just blindly schedule it. It first checks if 8:00 AM has already passed today - if it has, it schedules the reminder for tomorrow instead. This prevents the annoying scenario where setting a reminder would trigger it immediately if that time has already passed.</p>
<p>I also made sure that reminders persist between sessions by storing them in localStorage. Each habit gets its own localStorage entry with a unique key: <code>reminder_${habitId}</code>.</p>
<p>The UI for reminders is clean and straightforward:</p>
<ul>
<li><p>If you haven't set a reminder yet, you see a time picker and a "Set Reminder" button</p>
</li>
<li><p>Once a reminder is set, the UI changes to show the scheduled time and offers a "Remove Reminder" option</p>
</li>
</ul>
<p>This kind of context-aware UI helps keep the interface clean while giving users exactly the controls they need.</p>
<p>I spent some time researching edge cases with reminder systems. What happens if a user sets a reminder for 11:59 PM? What if they set multiple reminders across different habits? Does the system handle timezone changes? These were all important considerations to ensure a robust user experience.</p>
<h3 id="heading-notification-testing-route">Notification Testing Route</h3>
<p>To help debug and demonstrate the notification system, I built a dedicated <code>/notifications</code> testing route. This special route lets me trigger different notification types, test delay timing, and verify that the UI components render correctly.</p>
<p>The testing page provides controls for:</p>
<ul>
<li><p>Sending immediate notifications</p>
</li>
<li><p>Scheduling delayed notifications (with customizable delay)</p>
</li>
<li><p>Testing different notification types (in-app, email, SMS)</p>
</li>
<li><p>Clearing all pending notifications</p>
</li>
</ul>
<p>This testing route has been invaluable during development. Rather than trying to debug notification issues in the middle of the main application flow, I can isolate and test notification functionality directly by just going to my created route.</p>
<p>The best part is that this testing route uses the exact same notification components and manager as the main application, so any fixes or improvements I make here automatically benefit the entire system.</p>
<p>As the application grows, I plan to expand this testing route to include more sophisticated scenarios, like simulating a day's worth of habit reminders compressed into a 1-minute demo, or testing how the system handles overlapping notifications. This dedicated testing environment will ensure that the notification system remains reliable even as new features are added. Before moving on to the PostHog implementation, I added a quick video below showing the notification testing route in action:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.loom.com/share/e54a8b41b6174acb81b751a31acef302">https://www.loom.com/share/e54a8b41b6174acb81b751a31acef302</a></div>
<p> </p>
<h3 id="heading-posthog-implementation-analytics-tracking">PostHog Implementation (Analytics Tracking)</h3>
<p>Adding analytics to "Creatures of Habit" was a game-changer for understanding how users interact with the app. I chose PostHog because it's open-source, respects user privacy, and gives me powerful event tracking without complexity.</p>
<p>Implementing PostHog was surprisingly straightforward. I created a dedicated configuration file to centralize all PostHog settings:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { PostHogConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'posthog-js'</span>;
<span class="hljs-keyword">import</span> { PUBLIC_POSTHOG_KEY } <span class="hljs-keyword">from</span> <span class="hljs-string">'$env/static/public'</span>;


<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'🔍 PostHog: Config file loaded'</span>);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> posthogConfig: Partial&lt;PostHogConfig&gt; = {
    api_host: <span class="hljs-string">'https://us.i.posthog.com'</span>,
    capture_pageview: <span class="hljs-literal">false</span>,
    capture_pageleave: <span class="hljs-literal">false</span>,
    disable_session_recording: <span class="hljs-literal">true</span>,
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getPostHogKey = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!PUBLIC_POSTHOG_KEY) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'❌ PostHog: API key is not defined'</span>);
    }
    <span class="hljs-keyword">return</span> PUBLIC_POSTHOG_KEY;
};
</code></pre>
<p>Notice how I've explicitly disabled session recording and automated <code>pageview/pageleave</code> tracking. I wanted complete control over what events get tracked, rather than having a flood of automatic events that might obscure what's truly important. Like I couldn't track a damn thing without movement events rapidly firing lol it was straight overkill.</p>
<p>The initialization happens in my layout file, ensuring PostHog is available throughout the application:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/routes/+layout.ts</span>
<span class="hljs-keyword">import</span> posthog <span class="hljs-keyword">from</span> <span class="hljs-string">'posthog-js'</span>
<span class="hljs-keyword">import</span> { browser } <span class="hljs-keyword">from</span> <span class="hljs-string">'$app/environment'</span>;
<span class="hljs-keyword">import</span> { posthogConfig, getPostHogKey } <span class="hljs-keyword">from</span> <span class="hljs-string">'$lib/plugins/PostHog'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> load = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">if</span> (browser) {
        posthog.init(getPostHogKey(), posthogConfig);
    }
    <span class="hljs-keyword">return</span> {};
};
</code></pre>
<p>I'm using the <code>browser</code> check to ensure PostHog only initializes in client-side contexts, avoiding any server-side rendering issues.</p>
<p>For page navigation tracking, I implemented a custom solution using SvelteKit's <code>afterNavigate</code> hook:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/routes/+layout.svelte</span>
<span class="hljs-keyword">if</span> (browser) {
    afterNavigate(<span class="hljs-function">() =&gt;</span> {
        posthog.capture(<span class="hljs-string">'$pageview'</span>);
    });
}
</code></pre>
<p>The real power comes from tracking specific user actions. Now I can see exactly which features users engage with most. For example, when a user completes a habit:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">completeHabit</span>(<span class="hljs-params">habitId: <span class="hljs-built_in">string</span></span>) </span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// API call to complete the habit</span>
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`/api/habits/<span class="hljs-subst">${habitId}</span>/complete`</span>, {
            method: <span class="hljs-string">'POST'</span>
        });

        <span class="hljs-comment">// Process the response...</span>

        <span class="hljs-comment">// Track the habit completion event</span>
        posthog.capture(<span class="hljs-string">'habit_completed'</span>, {
            habitId,
            difficulty: habitData.difficulty,
            experienceEarned: data.experienceEarned
        });

        toast.success(<span class="hljs-string">`Gained <span class="hljs-subst">${data.experienceEarned}</span> XP!`</span>);
        <span class="hljs-comment">// ...</span>
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error completing habit:'</span>, error);
    }
}
</code></pre>
<p>Now I can answer questions like:</p>
<ul>
<li><p>Which habit difficulties do users prefer?</p>
</li>
<li><p>How many habits do users typically track?</p>
</li>
<li><p>What times of day do users engage most with the app?</p>
</li>
<li><p>Which features lead to higher retention?</p>
</li>
</ul>
<p>This data will be invaluable for prioritizing future features. If I see that 80% of users are setting reminders for their habits, I'll know to invest more in expanding the reminder functionality. Conversely, if a feature shows minimal engagement, I can either improve it or deprioritize further development in that area.</p>
<p>The best part is that all of this happens with minimal impact on performance and user privacy. PostHog is lightweight, and I'm only tracking the specific events that provide actionable insights.</p>
<h2 id="heading-fixes-small-changes-big-impact">🪚 Fixes: Small Changes, Big Impact</h2>
<h3 id="heading-edit-habit-functionality-fix">Edit Habit Functionality Fix</h3>
<p>This issue was racking my brain for a while. The edit functionality was almost working, but the form wasn't properly populated with the existing habit data. Users would click "Edit" on a habit, and the form would appear with blank or incorrect values.</p>
<p>After some debugging, I discovered the issue was in how I was handling the frequency data. The API was returning frequency information in a different structure than what my form component expected.</p>
<p>The fix required adjusting the data mapping in the edit page component:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/routes/habits/[id]/edit/+page.svelte</span>
<span class="hljs-keyword">const</span> initialData: HabitData = {
    title: data.habit.title,
    categoryId: data.habit.categoryId ?? <span class="hljs-literal">undefined</span>,
    description: data.habit.description || <span class="hljs-string">''</span>,
    frequency: data.habit.frequencyId ? <span class="hljs-string">'custom'</span> : <span class="hljs-string">'daily'</span>,
    customFrequency: {
        days: []
    },
    difficulty: data.habit.difficulty,
    startDate: data.habit.startDate ?? <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString()
};
</code></pre>
<p>The key insight was realizing that I needed to check for the existence of <code>frequencyId</code> to determine if this was a custom frequency habit. Previously, I was checking a property that sometimes didn't exist, causing the form to render incorrectly.</p>
<p>I also updated the submission handler to use the correct HTTP method and endpoint:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleSubmit</span>(<span class="hljs-params">formData: HabitData</span>) </span>{
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`/api/habits/<span class="hljs-subst">${data.habit.id}</span>`</span>, {
        method: <span class="hljs-string">'PUT'</span>,
        headers: {
            <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>
        },
        body: <span class="hljs-built_in">JSON</span>.stringify(formData)
    });

    <span class="hljs-keyword">if</span> (!response.ok) {
        <span class="hljs-keyword">const</span> errorData = <span class="hljs-keyword">await</span> response.json();
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(errorData.error);
    }

    <span class="hljs-keyword">await</span> goto(<span class="hljs-string">'/habits'</span>);
}
</code></pre>
<p>This might seem like a small change, but it had a massive impact on user experience. Now users can properly edit their habits instead of having to delete and recreate them. I’m still having some issues with the proper date and time showing on the “completed” tasks:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742260233295/9474293f-350a-40f4-b737-fc88bfecd003.png" alt class="image--center mx-auto" /></p>
<p>But it’s not breaking anything so that’s something I have noted to work on as a bug fix down the line.</p>
<h3 id="heading-logout-route-post-action-fix">Logout Route POST Action Fix</h3>
<p>I had two logout buttons in the app - one in the main navigation and another in the dashboard. Strangely, only one was working correctly. After investigating, I realized I wasn't using SvelteKit's <code>enhance</code> directive on the dashboard logout form.</p>
<p>The fix was simple but crucial:</p>
<pre><code class="lang-typescript">&lt;form action=<span class="hljs-string">"/logout"</span> method=<span class="hljs-string">"POST"</span> use:enhance&gt;
    &lt;Button <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span> variant=<span class="hljs-string">"outline"</span> size=<span class="hljs-string">"sm"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"flex items-center gap-2"</span>&gt;
        &lt;LogOut <span class="hljs-keyword">class</span>=<span class="hljs-string">"h-4 w-4"</span> /&gt;
        Logout
    &lt;/Button&gt;
&lt;/form&gt;
</code></pre>
<p>The <code>enhance</code> directive is crucial here - it tells SvelteKit to handle the form submission using client-side JavaScript when possible, rather than triggering a full page reload. Without it, the form was submitted traditionally, which disrupted the expected application flow.</p>
<p>I also removed the secondary Logout CTA cause it was redundant. This fix ensures that the logout button works consistently and provides a smooth user experience.</p>
<h3 id="heading-updated-signup-routes-cta-with-issubmitting-for-better-ux">Updated Signup Route's CTA with <code>isSubmitting</code> for Better UX</h3>
<p>One of my pet peeves is form buttons that don't provide feedback. When you click "Sign Up" on most websites, you're left wondering if anything is happening, especially if there's network latency.</p>
<p>I solved this by adding a loading state to the signup form's button:</p>
<pre><code class="lang-typescript">&lt;Button on:click={nextStep} disabled={isSubmitting}&gt;
    {#<span class="hljs-keyword">if</span> isSubmitting}
        &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"mr-2 h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent"</span>&gt;&lt;/div&gt;
        {currentStep === totalSteps ? <span class="hljs-string">'Creating Account...'</span> : <span class="hljs-string">'Processing...'</span>}
    {:<span class="hljs-keyword">else</span>}
        {currentStep === totalSteps ? <span class="hljs-string">'Create Account'</span> : <span class="hljs-string">'Next'</span>}
    {/<span class="hljs-keyword">if</span>}
&lt;/Button&gt;
</code></pre>
<p>This provides two crucial UX improvements:</p>
<ol>
<li><p>Visual feedback via the spinning loader</p>
</li>
<li><p>Text feedback that changes based on the current step</p>
</li>
</ol>
<p>Additionally, the button gets disabled during submission, preventing accidental double-clicks that could lead to duplicate accounts or other errors.</p>
<p>It's a small enhancement that dramatically improves perceived performance and reduces user anxiety during the critical signup process.</p>
<h3 id="heading-implemented-toast-in-the-apps-notification-system">Implemented Toast in the App's Notification System</h3>
<p>To provide non-intrusive feedback throughout the app, I integrated Svelte Sonner for toast notifications. These small, temporary messages appear at the top of the screen to confirm actions or display errors without disrupting the user experience.</p>
<h3 id="heading-coderabbit-temporary-experimenting-with-it">CodeRabbit (Temporary; Experimenting with it)</h3>
<p>I don’t know about this one. Feels like a bit of overkill and has yet to point out anything that was a critical error. However, I’m going to give it some more time. I think this would be a slippery slope though if it DOES work. I can’t lean on AI too heavily or It’ll erode my basic debugging skills I’m already lacking a bit of lol. Nonetheless, I AM finding it helpful for catching small issues and suggesting improvements. One particularly helpful aspect has been its suggestions around TypeScript typing. It's helped me make my type definitions more precise and catch potential null reference errors.</p>
<p>All that said, I’ll continue treating it as an experiment for now and evaluate its usefulness as the project progresses.</p>
<h2 id="heading-looking-ahead">Looking Ahead</h2>
<p>While I've made significant progress, there's still a lot to do:</p>
<ol>
<li><p><strong>Stats &amp; XP System Enhancement</strong>: The stats need to actually affect things in the app and it also needs to be adjusted to have a <code>min</code> and <code>max</code> at character creation. Right now it’s just character lore at this point.</p>
</li>
<li><p><strong>Homepage Redesign</strong>: I need a real homepage that demonstrates the unique RPG-habit connection with interactive elements and clear CTAs for engagement and sign-ups.</p>
</li>
<li><p><strong>Quests and Campaigns</strong>: What’s an RPG app without quests?! This system has yet to be prototyped so I’d need to do some research.</p>
</li>
<li><p><strong>Dashboard and UI Improvements</strong>: Enhance the user interfaces with character-updated status displays, streak indicators, and progress charts to improve the overall experience.</p>
</li>
<li><p><strong>Progressive Achievements</strong>: Tiered achievement system with visual badges, titles, and an achievement gallery to provide additional motivation through accomplishments.</p>
</li>
</ol>
<p>The journey of building this app has been pretty damn rewarding, even more so than the finance tracking app. Mainly based on the pure scope and how much I've learned thus far. It’s also shown me that there is still a shit ton to learn about these types of apps and how to properly build them. Trying to turn a habit-tracking app and transforming it into a dope RPG experience has stressed me out 😅 but also has pushed me in certain areas. Stay tuned for what’s next on deck 🤘🏾.</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-work-or-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my work or want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://bsky.app/profile/digitaldopamine.dev"><strong>🦋 Bluesky</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Creatures of Habit: Devlog Chronicles - Prelude]]></title><description><![CDATA[Biggest Challenge Yet
Boy, has this been a busy year so far, and it’s not even a month old. Since my last article, I’ve been putting in a ton of time on this project. As soon as the new year hit, I made it a goal to work on this or a different projec...]]></description><link>https://blacknerd.dev/creatures-of-habit-coding-challenges-progress-check</link><guid isPermaLink="true">https://blacknerd.dev/creatures-of-habit-coding-challenges-progress-check</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[app development]]></category><category><![CDATA[Svelte]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[DrizzleORM]]></category><category><![CDATA[turso]]></category><category><![CDATA[Hashnode]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Fri, 24 Jan 2025 14:14:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1737723304938/9d658be5-130a-4bb7-b753-29cded84aa8c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-biggest-challenge-yet">Biggest Challenge Yet</h2>
<p>Boy, has this been a busy year so far, and it’s not even a month old. Since <a target="_blank" href="https://blacknerd.dev/how-to-build-custom-authentication-in-sveltekit-a-session-and-cookie-overview">my last article</a>, I’ve been putting in a ton of time on this project. As soon as the new year hit, I made it a goal to work on this or a different project every day to see how long I could keep my coding streak. I’m 24 days in now, and there is no sign of breaking, but then again, it’s been less than a month 😅.</p>
<p>The main accomplishment with this is to build up my consistency. I recently got a new promotion working on a different team (which also started this year) so I’ve been trying to skill up on my day-to-day consistency. Like many others, I assume the wild nature of how life in the US has been unfolding has been…..distracting, to say the least. But with that aside, I’m aiming for 2025 to be my year of consistency. I’m talking like BIG CONSISTENT energy, where my entire GitHub contribution graph for this year is fully green and I have at least one solid project in production, that project being <a target="_blank" href="https://github.com/kdleonard93/creatures-of-habit">Creatures of Habit</a>.</p>
<h2 id="heading-state-of-the-app-and-roadmap">State of the App and Roadmap</h2>
<p>After writing up my authentication, I made a roadmap and plan on what I needed to accomplish for an MVP. Below is the rough plan and I’m only going to post the sub-tasks of Phases 1 and 2. Phases 3-5 will just show the feature/goal for that sprint since it’ll be a while until I start them and didn’t want to bloat this article:</p>
<h3 id="heading-phase-1-foundation-amp-authentication-4-6-weeks">Phase 1: Foundation &amp; Authentication (4-6 weeks)</h3>
<ol>
<li><p>User System Setup</p>
<ul>
<li><p><s>Implement secure user registration and login using Lucia authentication library</s></p>
<ul>
<li><p><s>Integrate with database for user data storage</s></p>
</li>
<li><p><s>Implement password hashing and security measures</s></p>
</li>
</ul>
</li>
<li><p><s>Design and implement intuitive profile creation flow</s></p>
<ul>
<li><p><s>Multi-step wizard for gathering user information</s></p>
</li>
<li><p><s>Profile completeness indicator</s></p>
</li>
</ul>
</li>
<li><p><s>Develop basic settings page with account management options</s></p>
<ul>
<li><p><s>Password change functionality</s></p>
</li>
<li><p>Notification preferences</p>
</li>
<li><p>Privacy settings</p>
</li>
</ul>
</li>
<li><p>Implement optional email verification system</p>
<ul>
<li><p>Automated verification email sending</p>
</li>
<li><p>Secure verification link generation and processing</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>Character Creation</p>
<ul>
<li><p><s>Design and implement fantasy race selection system</s></p>
<ul>
<li><p><s>Create detailed descriptions and visual representations for each race</s></p>
</li>
<li><p><s>Implement unique stat bonuses and abilities for each race</s></p>
</li>
<li><p><s>Develop race-specific storylines and background options</s></p>
</li>
</ul>
</li>
<li><p>Develop custom avatar creation system</p>
<ul>
<li><p>Design modular character parts (hair, eyes, body types, etc.)</p>
</li>
<li><p>Implement color customization for various features</p>
</li>
<li><p>Create randomization option for quick character generation</p>
</li>
</ul>
</li>
<li><p><s>Design and implement initial class or path choice system</s></p>
<ul>
<li><p><s>Create detailed class descriptions and abilities</s></p>
</li>
<li><p><s>Implement class-specific starting equipment and skills</s></p>
</li>
<li><p><s>Develop class change or multi-class options for future progression</s></p>
</li>
</ul>
</li>
<li><p><s>Develop starting stats allocation system</s></p>
<ul>
<li><p><s>Create balanced stat distribution options</s></p>
</li>
<li><p><s>Implement stat impact explanations and tooltips</s></p>
</li>
<li><p>Develop reallocation or respec options for future use</p>
</li>
</ul>
</li>
<li><p>Implement character naming system</p>
<ul>
<li><p>Create name generation options (random, themed, custom)</p>
</li>
<li><p>Implement name uniqueness checks</p>
</li>
<li><p>Develop naming conventions and filters</p>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<h3 id="heading-phase-2-core-habit-system-6-8-weeks">Phase 2: Core Habit System (6-8 weeks)</h3>
<ol>
<li><p>Habit Management</p>
<ul>
<li><p><s>Implement CRUD operations for habits</s></p>
<ul>
<li><p><s>Design intuitive habit creation interface</s></p>
</li>
<li><p><s>Develop habit editing and updating functionality</s></p>
</li>
<li><p><s>Implement soft and hard deletion options</s></p>
</li>
<li><p><s>Add a visual when selecting a custom frequency so the user can see the days on the task</s></p>
</li>
</ul>
</li>
<li><p>Design and implement habit categories and types</p>
<ul>
<li><p>Create pre-defined categories (health, productivity, etc.)</p>
</li>
<li><p>Implement custom category creation</p>
</li>
<li><p>Develop habit type system (daily, weekly, one-time)</p>
</li>
</ul>
</li>
<li><p>Develop frequency settings for habits</p>
<ul>
<li><p><s>Implement daily, weekly, and custom recurrence options</s></p>
</li>
<li><p>Develop reminder system integrated with frequency settings</p>
</li>
</ul>
</li>
<li><p>Implement difficulty levels for habits</p>
<ul>
<li><p><strong><em>Create balanced difficulty scale (easy, medium, hard, etc.)</em></strong></p>
</li>
<li><p>Develop adaptive difficulty system based on user performance</p>
</li>
<li><p>Implement difficulty-based rewards and experience points</p>
</li>
</ul>
</li>
<li><p>Design and implement reward calculations</p>
<ul>
<li><p>Create algorithm for calculating rewards based on difficulty and consistency</p>
</li>
<li><p>Implement bonus rewards for streaks and milestones</p>
</li>
<li><p>Develop penalty system for missed habits</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>Daily System</p>
<ul>
<li><p>Design and implement daily quests and tasks view</p>
<ul>
<li><p>Create visually appealing and intuitive daily dashboard</p>
</li>
<li><p>Implement task prioritization and sorting options</p>
</li>
<li><p>Develop quick-add functionality for impromptu tasks</p>
</li>
</ul>
</li>
<li><p>Implement check-in system</p>
<ul>
<li><p>Design user-friendly check-in interface</p>
</li>
<li><p>Develop partial completion options for habits</p>
</li>
<li><p>Implement undo or correction functionality for accidental check-ins</p>
</li>
</ul>
</li>
<li><p>Develop streak tracking system</p>
<ul>
<li><p>Implement streak calculation algorithm</p>
</li>
<li><p>Create visual representations of streaks (flames, chains, etc.)</p>
</li>
<li><p>Develop streak recovery options (grace periods, streak freezes)</p>
</li>
</ul>
</li>
<li><p>Implement daily reset logic</p>
<ul>
<li><p>Develop time zone aware reset system</p>
</li>
<li><p>Implement rollover options for uncompleted tasks</p>
</li>
<li><p>Create daily summary and reflection prompts</p>
</li>
</ul>
</li>
<li><p>Design and implement progress indicators</p>
<ul>
<li><p>Create visual progress bars for habits and overall daily goals</p>
</li>
<li><p>Implement milestone celebrations and achievements</p>
</li>
<li><p>Develop long-term progress tracking and visualization</p>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<h3 id="heading-phase-3-rpg-elements-6-8-weeks">Phase 3: RPG Elements (6-8 weeks)</h3>
<h3 id="heading-phase-4-uiux-implementation-4-6-weeks">Phase 4: UI/UX Implementation (4-6 weeks)</h3>
<h3 id="heading-phase-5-enhanced-features-6-8-weeks">Phase 5: Enhanced Features (6-8 weeks)</h3>
<p>As you can see, I still have PLENTY to get done before this app is considered a working alpha. Anything crossed out has been finished so you can see my current state for this app. But I’m already very proud of the work I've put in thus far. This is the most consistent I’ve been with coding since my Lambda School days, and my current streak surpassed all streaks set when I was enrolled in 2019.</p>
<p>Some of the biggest challenges with what I’ve finished though would have to relate to the user system and character creation setup. I leaned <strong>HEAVILY</strong> on online resources and examples from other apps but got the job done. One thing that’s constantly tripping me up is the type system. Working with types in Typescript vs types in PHP throws me for a loop every time. They are pretty different in terms of how types are implemented and while Typescript is more strict and unforgiving, PHP is a bit looser, has type juggling, and is written differently for the most part (EX: Typescript uses type interfaces and PHP only uses them in certain contexts).</p>
<p>This brings me back to a hurdle I’ve mentioned in my past posts: the annoying action of context-switching. I’d love to focus on Typescript and Svelte for work and my personal projects, but the world isn’t my oyster, so I gotta do what I gotta do to pay the bills 💸.</p>
<p>For example, here is how I have the CreatureStats type interface set up in TS:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> CreatureStats {
    strength: <span class="hljs-built_in">number</span>;
    dexterity: <span class="hljs-built_in">number</span>;
    constitution: <span class="hljs-built_in">number</span>;
    intelligence: <span class="hljs-built_in">number</span>;
    wisdom: <span class="hljs-built_in">number</span>;
    charisma: <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> RegistrationData {
    email: <span class="hljs-built_in">string</span>;
    username: <span class="hljs-built_in">string</span>;
    password: <span class="hljs-built_in">string</span>;
    confirmPassword: <span class="hljs-built_in">string</span>;
    age: <span class="hljs-built_in">number</span>;
    creature: {
        name: <span class="hljs-built_in">string</span>;
        <span class="hljs-keyword">class</span>: CreatureClassType;
        race: CreatureRaceType;
        stats: CreatureStats;
        background?: <span class="hljs-built_in">string</span>;
    };
    general: <span class="hljs-built_in">string</span>;
}
</code></pre>
<p>You can see the stats being created in its own interface and being referenced in the RegistrationData interface. Below, it’s being set in my RegistrationWizard.svelte component with it’s default values:</p>
<pre><code class="lang-svelte"><span class="xml">    let formData = $state<span class="hljs-tag">&lt;<span class="hljs-name">RegistrationData</span>&gt;</span>(</span><span class="javascript">{
        <span class="hljs-attr">email</span>: <span class="hljs-string">''</span>,
        <span class="hljs-attr">username</span>: <span class="hljs-string">''</span>,
        <span class="hljs-attr">password</span>: <span class="hljs-string">''</span>,
        <span class="hljs-attr">confirmPassword</span>: <span class="hljs-string">''</span>,
        <span class="hljs-attr">age</span>: <span class="hljs-number">0</span>,
        <span class="hljs-attr">creature</span>: {
            <span class="hljs-attr">name</span>: <span class="hljs-string">''</span>,
            <span class="hljs-attr">class</span>: CreatureClass.WARRIOR,
            <span class="hljs-attr">race</span>: CreatureRace.HUMAN,
            <span class="hljs-attr">stats</span>: {
                <span class="hljs-attr">strength</span>: <span class="hljs-number">10</span>,
                <span class="hljs-attr">dexterity</span>: <span class="hljs-number">10</span>,
                <span class="hljs-attr">constitution</span>: <span class="hljs-number">10</span>,
                <span class="hljs-attr">intelligence</span>: <span class="hljs-number">10</span>,
                <span class="hljs-attr">wisdom</span>: <span class="hljs-number">10</span>,
                <span class="hljs-attr">charisma</span>: <span class="hljs-number">10</span>
            },
            <span class="hljs-attr">background</span>: <span class="hljs-literal">undefined</span>
        }</span><span class="xml">,
        general: ''
    });</span>
</code></pre>
<p>Whereas, this is how it would look in PHP:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">CreatureStats</span> </span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStrength</span>(<span class="hljs-params"></span>): <span class="hljs-title">int</span></span>;
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getDexterity</span>(<span class="hljs-params"></span>): <span class="hljs-title">int</span></span>;
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getConstitution</span>(<span class="hljs-params"></span>): <span class="hljs-title">int</span></span>;
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getIntelligence</span>(<span class="hljs-params"></span>): <span class="hljs-title">int</span></span>;
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getWisdom</span>(<span class="hljs-params"></span>): <span class="hljs-title">int</span></span>;
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCharisma</span>(<span class="hljs-params"></span>): <span class="hljs-title">int</span></span>;
}
</code></pre>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

$creatureStats = [
    <span class="hljs-string">'strength'</span> =&gt; <span class="hljs-number">10</span>,
    <span class="hljs-string">'dexterity'</span> =&gt; <span class="hljs-number">8</span>,
    <span class="hljs-string">'constitution'</span> =&gt; <span class="hljs-number">12</span>,
    <span class="hljs-string">'intelligence'</span> =&gt; <span class="hljs-number">14</span>,
    <span class="hljs-string">'wisdom'</span> =&gt; <span class="hljs-number">10</span>,
    <span class="hljs-string">'charisma'</span> =&gt; <span class="hljs-number">6</span>
];

$registrationData = <span class="hljs-keyword">new</span> RegistrationData();
$registrationData-&gt;email = <span class="hljs-string">"{enter email}"</span>;
$registrationData-&gt;creature = [
    <span class="hljs-string">'name'</span> =&gt; <span class="hljs-string">'{Creature Name}'</span>,
    <span class="hljs-string">'class'</span> =&gt; CreatureClass::ARCHER,
    <span class="hljs-string">'race'</span> =&gt; CreatureRace::ELF,
    <span class="hljs-string">'stats'</span> =&gt; $creatureStats
];
</code></pre>
<p>The key differences between TS and PHP when it comes to types:</p>
<ul>
<li><p><strong>Type Safety</strong>: TypeScript provides compile-time type checking, while PHP relies on runtime checks and optional type hints.</p>
</li>
<li><p><strong>Advanced Types</strong>: TypeScript supports generics, unions, intersections, and more, which PHP does not natively support.</p>
</li>
<li><p><strong>Interfaces</strong>: TypeScript interfaces are purely structural, while PHP interfaces are more rigid and require explicit implementation.</p>
</li>
</ul>
<p>However, the daily work and consistency are helping alleviate the struggle with context switching, so I just have to keep up the hustle.</p>
<h2 id="heading-oh-you-want-a-lil-preview">Oh, You Want a Lil Preview???</h2>
<p>Instead of showing every little thing I’ve done via code snippets, I implore you to check out the Github repo and clone/fork it to try it out locally. It’s nothing pretty as of yet but a lot of the main features are ready to use/test out. In the video below, I’ll go through creating a new user, character, and habit. This should show the persistence of the user log-in and state, basic CRUD, and a look at the user dashboard and creature dashboard.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.loom.com/share/09ed1fcb61d84c66bc77cbc789f8dde2">https://www.loom.com/share/09ed1fcb61d84c66bc77cbc789f8dde2</a></div>
<p> </p>
<p>I tend to ramble so I used all 5 min of the loom video limit for free accounts lol. But I do touch on everything I said I would. Some other features work like, “Change Password” and" “Edit Habit” but I was running out of time so they aren’t shown in the demo</p>
<h2 id="heading-eod">EOD</h2>
<p>✌🏾 that wraps up my check-in. Hopefully, some interested people out there want to check out the app locally and play around. I’m always open to feedback so if you have any suggestions or critiques, submit an issue or contribute and make a pull request! I’m planning on doing another check-in within the next month or 2 so be on the lookout for my next update!</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-work-or-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my work or want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://bsky.app/profile/digitaldopamine.dev"><strong>🦋 Bluesky</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Building Custom Authentication: A Session and Cookie Overview]]></title><description><![CDATA[Intro
What’s up folks, been a minute since my last post. As everyone and their mother knows the last couple of months have been pretty hectic in terms of things going on in the world. All the while, I’ve been head down on making moves within my compa...]]></description><link>https://blacknerd.dev/how-to-build-custom-authentication-in-sveltekit-a-session-and-cookie-overview</link><guid isPermaLink="true">https://blacknerd.dev/how-to-build-custom-authentication-in-sveltekit-a-session-and-cookie-overview</guid><category><![CDATA[authentication]]></category><category><![CDATA[software development]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[DrizzleORM]]></category><category><![CDATA[Svelte]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Mon, 09 Dec 2024 02:26:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733877585420/55602fd2-1648-4b6c-86d0-1a1a88501875.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-intro">Intro</h3>
<p>What’s up folks, been a minute since my last post. As everyone and their mother knows the last couple of months have been pretty hectic in terms of things going on in the world. All the while, I’ve been head down on making moves within my company and the efforts have paid off. My last project (<a target="_blank" href="https://leo-ledger.onrender.com/">https://leo-ledger.onrender.com/</a>) seems to still be living, due to Render changing its free tier limits (although, it still takes about 60 seconds for the server to boot back up 🫠). No additional work has been put into it and I’m now thinking that I will most likely leave the project as is. This is due to the fact that I don’t plan on using HTMX in many of my future projects. I enjoyed seeing its capability when it comes to working closely with Python, but 9/10, I’m opting for Svelte w/Typescript to handle my front end.</p>
<p>I’ve become comfortable with the pair and depending on the type of project I’m doing, I will either choose Django to handle my backend or stick with Typescript, an ORM, and DB tool. I recently started a new project with a scope bigger than I’ve handled before 😅, but I wanted to challenge myself with one main objective, to strengthen my understanding of the scope of different types of applications. I work for a company that builds and maintains websites for car dealerships and I’ve gotten a great sense of the workflow of a company this size and our client’s needs/expectations for their customers. As mentioned earlier, My last project was a Finance tracker and the scope of that project is much different than what I do for a paycheck. That leads me to my new project and the theme of this article.</p>
<h4 id="heading-drum-roll">🥁🥁🥁<em>Drum Roll</em>🥁🥁🥁</h4>
<p>My next project is a RPG Habit Tracker App! Nothing super original about this idea but the scope of this type of app is much different than my finance tracker and what I do at my job. This forces me to reach for new tools that will better suit the project’s functionality which for me, is what keeps me excited about building things. I’ve probably mentioned it in a previous article but my tendency to be a “Jack of All Languages” sometimes hinders me from really understanding a specific language if I don’t do enough work with it. It’s come to bite me in the ass a couple of times as well but I’m learning to manage my context switching a bit better. One thing I need to do is practice more on code wars and get OOP fundamentals down to a T as well as just thinking a bit more pragmatically. But enough rambling, we’re here to talk about some pretty dope things I learned when I started to work on authentication for my new app. My repo is pubic for the moment but i might move it back to private. Get a gander while you still can → <a target="_blank" href="https://github.com/kdleonard93/creatures-of-habit">https://github.com/kdleonard93/creatures-of-habit</a>.</p>
<h3 id="heading-so-long-lucia">So Long Lucia</h3>
<p>When deciding on the tech stack I was going to use for this project, the Sveltekit scaffolding process showed Lucia and I’ve heard of the tool in the past as a great choice for setting up Auth within a Sveltekit app. But when I got my project all set. I noticed there were no Lucia references in the auth.ts file. After some searching on the net, I discovered that the maintainer was no longer working or using Lucia himself and explained that it just wasn’t working in its current state. They made an <a target="_blank" href="https://github.com/lucia-auth/lucia/discussions/1714">announcement</a> stating that it would be deprecated in March 2025. But what they are doing to circumvent the hole it’s leaving in the community, is turning that repo into a learning source on implementing auth from scratch on your own. Now I'm sure that sounds cumbersome and I thought the same exact thing, but it really wasn’t that bad and I ended up learning a bit about general guidelines related to implementing auth on web applications.</p>
<h3 id="heading-okay-cool-you-learned-about-sessionswant-a-cookie">Okay Cool, You Learned About Sessions……Want a Cookie?!?!</h3>
<p>Hopefully, the section title wasn’t too corny 😏, but this is where I yap about how I appreciate what that maintainer did. If they never made documentation on how to convert from Lucia and build auth from scratch on my own, I might have switched up my stack a bit and used Drizzle with Supabase instead of Drizzle with Turso. The Lucia docs are straightforward and concise when it comes to examples of setting up <code>sessions</code> and <code>cookies</code>, providing documentation links for related references like, encoding schemes, the Crypto: randomUUID() method, and the SHA-256 hash function to name a few.</p>
<p>Without getting into much of the nitty gritty (much of which i’m still wrapping my head around), the main advantage with digging deep and writing these from scratch is I can apply this knowledge to other projects that don’t necessarily use the same stack. It also helps me understand the nuances of authentication under the hood so I’m better equipped to be a helping hand to folks that aren’t privy to building auth.</p>
<p>Let's dive into the nuts and bolts of implementing authentication from scratch. I'll break down my <code>auth.ts</code> file to show you how surprisingly manageable this process can be (even though some concepts are still a. bit fuzzy to me).</p>
<h4 id="heading-the-building-blocks">The Building Blocks</h4>
<p>First up, we have some core functionality for handling sessions:</p>
<ol>
<li><strong>Session Token Generation</strong>: We create a secure random token using <code>crypto.getRandomValues()</code> and encode it in <code>base32</code>. This gives us a unique identifier for each user session.</li>
</ol>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateSessionToken</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> bytes = crypto.getRandomValues(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Uint8Array</span>(<span class="hljs-number">20</span>));
    <span class="hljs-keyword">return</span> encodeBase32LowerCase(bytes);
}
</code></pre>
<ol start="2">
<li><p><strong>Session Creation</strong>: When a user logs in, we create a new session by:</p>
<ul>
<li><p>Hashing the token using SHA-256 (for secure storage)</p>
</li>
<li><p>Setting an expiration date (30 days from creation)</p>
</li>
<li><p>Storing it in our database with the user's ID</p>
</li>
</ul>
</li>
</ol>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createSession</span>(<span class="hljs-params">token: <span class="hljs-built_in">string</span>, userId: <span class="hljs-built_in">string</span></span>) </span>{
    <span class="hljs-keyword">const</span> sessionId = encodeHexLowerCase(sha256(<span class="hljs-keyword">new</span> TextEncoder().encode(token)));
    <span class="hljs-keyword">const</span> session: table.Session = {
        id: sessionId,
        userId,
        expiresAt: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.now() + DAY_IN_MS * <span class="hljs-number">30</span>)
    };
    <span class="hljs-keyword">await</span> db.insert(table.session).values(session);
    <span class="hljs-keyword">return</span> session;
}
</code></pre>
<p>The cool thing about this approach is that we never store the raw session token in our database, just the hashed version. This adds an extra layer of security in case our database ever gets compromised.</p>
<h4 id="heading-session-management-amp-validation">Session Management &amp; Validation</h4>
<p>The real magic happens in the <code>validateSessionToken</code> function. Here's what it does:</p>
<ul>
<li><p>Checks if the session exists</p>
</li>
<li><p>Verifies it hasn't expired</p>
</li>
<li><p>Automatically renews sessions that are close to expiring (15 days out)</p>
</li>
<li><p>Returns the user's data if everything checks out</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">validateSessionToken</span>(<span class="hljs-params">token: <span class="hljs-built_in">string</span></span>) </span>{
    <span class="hljs-keyword">const</span> sessionId = encodeHexLowerCase(sha256(<span class="hljs-keyword">new</span> TextEncoder().encode(token)));
    <span class="hljs-keyword">const</span> [result] = <span class="hljs-keyword">await</span> db
        .select({
            <span class="hljs-comment">// Adjust user table here to tweak returned data</span>
            user: { id: table.user.id, username: table.user.username },
            session: table.session
        })
        .from(table.session)
        .innerJoin(table.user, eq(table.session.userId, table.user.id))
        .where(eq(table.session.id, sessionId));

    <span class="hljs-keyword">if</span> (!result) {
        <span class="hljs-keyword">return</span> { session: <span class="hljs-literal">null</span>, user: <span class="hljs-literal">null</span> };
    }
    <span class="hljs-keyword">const</span> { session, user } = result;

    <span class="hljs-comment">// Check if session is expired</span>
    <span class="hljs-keyword">const</span> sessionExpired = <span class="hljs-built_in">Date</span>.now() &gt;= session.expiresAt.getTime();
    <span class="hljs-keyword">if</span> (sessionExpired) {
        <span class="hljs-keyword">await</span> db.delete(table.session).where(eq(table.session.id, session.id));
        <span class="hljs-keyword">return</span> { session: <span class="hljs-literal">null</span>, user: <span class="hljs-literal">null</span> };
    }
    <span class="hljs-comment">// Renew session if close to expiration</span>
    <span class="hljs-keyword">const</span> renewSession = <span class="hljs-built_in">Date</span>.now() &gt;= session.expiresAt.getTime() - DAY_IN_MS * <span class="hljs-number">15</span>;
    <span class="hljs-keyword">if</span> (renewSession) {
        session.expiresAt = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.now() + DAY_IN_MS * <span class="hljs-number">15</span>);
        <span class="hljs-keyword">await</span> db
            .update(table.session)
            .set({ expiresAt: session.expiresAt })
            .where(eq(table.session.id, session.id));
    }

    <span class="hljs-keyword">return</span> { session, user };
}
</code></pre>
<p>I also implemented automatic session renewal. If a user's session is within 15 days of expiring, it gets extended in the background. This creates a smoother user experience to prevent kicking users off the app when their current session ends 🤘🏾.</p>
<h4 id="heading-cookie-management">Cookie Management</h4>
<p>The final piece of the puzzle is cookie handling. We set two main functions:</p>
<pre><code class="lang-typescript">setSessionTokenCookie(event, token, expiresAt)
deleteSessionTokenCookie(event)
</code></pre>
<p>These handle the grits of cookie management with some important security settings:</p>
<ul>
<li><p><code>httpOnly: true</code> prevents JavaScript access to the cookie</p>
</li>
<li><p><code>sameSite: "lax"</code> provides a good balance between security and usability</p>
</li>
<li><p>Proper expiration handling ensures cookies clean up after themselves</p>
</li>
</ul>
<h4 id="heading-why-this-matters">Why This Matters</h4>
<p>Building this from scratch instead of using a library like Lucia taught me a ton about web security. It's not just about storing a user's login state – it's about:</p>
<ul>
<li><p>Secure token generation and storage</p>
</li>
<li><p>Protection against session hijacking</p>
</li>
<li><p>Proper cookie security settings</p>
</li>
<li><p>Graceful session expiration handling</p>
</li>
</ul>
<p>While it might seem daunting at first, breaking down authentication into these components makes it much more approachable. Plus, as I previously mentioned, I can now adapt this knowledge to any framework or project I work on in the future.</p>
<p>The full file is below for reference:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { RequestEvent } <span class="hljs-keyword">from</span> <span class="hljs-string">'@sveltejs/kit'</span>;
<span class="hljs-keyword">import</span> { eq } <span class="hljs-keyword">from</span> <span class="hljs-string">'drizzle-orm'</span>;
<span class="hljs-keyword">import</span> { sha256 } <span class="hljs-keyword">from</span> <span class="hljs-string">'@oslojs/crypto/sha2'</span>;
<span class="hljs-keyword">import</span> { encodeBase32LowerCase, encodeHexLowerCase } <span class="hljs-keyword">from</span> <span class="hljs-string">'@oslojs/encoding'</span>;
<span class="hljs-keyword">import</span> { db } <span class="hljs-keyword">from</span> <span class="hljs-string">'$lib/server/db'</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> table <span class="hljs-keyword">from</span> <span class="hljs-string">'$lib/server/db/schema'</span>;

<span class="hljs-keyword">const</span> DAY_IN_MS = <span class="hljs-number">1000</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">24</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> sessionCookieName = <span class="hljs-string">'auth-session'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateSessionToken</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> bytes = crypto.getRandomValues(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Uint8Array</span>(<span class="hljs-number">20</span>));
    <span class="hljs-keyword">const</span> token = encodeBase32LowerCase(bytes);
    <span class="hljs-keyword">return</span> token;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createSession</span>(<span class="hljs-params">token: <span class="hljs-built_in">string</span>, userId: <span class="hljs-built_in">string</span></span>) </span>{
    <span class="hljs-keyword">const</span> sessionId = encodeHexLowerCase(sha256(<span class="hljs-keyword">new</span> TextEncoder().encode(token)));
    <span class="hljs-keyword">const</span> session: table.Session = {
        id: sessionId,
        userId,
        expiresAt: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.now() + DAY_IN_MS * <span class="hljs-number">30</span>)
    };
    <span class="hljs-keyword">await</span> db.insert(table.session).values(session);
    <span class="hljs-keyword">return</span> session;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">validateSessionToken</span>(<span class="hljs-params">token: <span class="hljs-built_in">string</span></span>) </span>{
    <span class="hljs-keyword">const</span> sessionId = encodeHexLowerCase(sha256(<span class="hljs-keyword">new</span> TextEncoder().encode(token)));
    <span class="hljs-keyword">const</span> [result] = <span class="hljs-keyword">await</span> db
        .select({
            <span class="hljs-comment">// Adjust user table here to tweak returned data</span>
            user: { id: table.user.id, username: table.user.username },
            session: table.session
        })
        .from(table.session)
        .innerJoin(table.user, eq(table.session.userId, table.user.id))
        .where(eq(table.session.id, sessionId));

    <span class="hljs-keyword">if</span> (!result) {
        <span class="hljs-keyword">return</span> { session: <span class="hljs-literal">null</span>, user: <span class="hljs-literal">null</span> };
    }
    <span class="hljs-keyword">const</span> { session, user } = result;

    <span class="hljs-comment">// Check if session is expired</span>
    <span class="hljs-keyword">const</span> sessionExpired = <span class="hljs-built_in">Date</span>.now() &gt;= session.expiresAt.getTime();
    <span class="hljs-keyword">if</span> (sessionExpired) {
        <span class="hljs-keyword">await</span> db.delete(table.session).where(eq(table.session.id, session.id));
        <span class="hljs-keyword">return</span> { session: <span class="hljs-literal">null</span>, user: <span class="hljs-literal">null</span> };
    }
    <span class="hljs-comment">// Renew session if close to expiration</span>
    <span class="hljs-keyword">const</span> renewSession = <span class="hljs-built_in">Date</span>.now() &gt;= session.expiresAt.getTime() - DAY_IN_MS * <span class="hljs-number">15</span>;
    <span class="hljs-keyword">if</span> (renewSession) {
        session.expiresAt = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.now() - DAY_IN_MS * <span class="hljs-number">15</span>);
        <span class="hljs-keyword">await</span> db
            .update(table.session)
            .set({ expiresAt: session.expiresAt })
            .where(eq(table.session.id, session.id));
    }

    <span class="hljs-keyword">return</span> { session, user };
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> SessionValidationResult = Awaited&lt;ReturnType&lt;<span class="hljs-keyword">typeof</span> validateSessionToken&gt;&gt;;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">invalidateSession</span>(<span class="hljs-params">sessionId: <span class="hljs-built_in">string</span></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">void</span>&gt; </span>{
    <span class="hljs-keyword">await</span> db.delete(table.session).where(eq(table.session.id, sessionId));
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setSessionTokenCookie</span>(<span class="hljs-params">event: RequestEvent, token: <span class="hljs-built_in">string</span>, expiresAt: <span class="hljs-built_in">Date</span></span>): <span class="hljs-title">void</span> </span>{
    event.cookies.set(sessionCookieName, token, {
        httpOnly: <span class="hljs-literal">true</span>,
        sameSite: <span class="hljs-string">"lax"</span>,
        expires: expiresAt,
        path: <span class="hljs-string">'/'</span>
    });
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deleteSessionTokenCookie</span>(<span class="hljs-params">event: RequestEvent</span>): <span class="hljs-title">void</span> </span>{
    event.cookies.delete(sessionCookieName, {
        httpOnly: <span class="hljs-literal">true</span>,
        sameSite: <span class="hljs-string">"lax"</span>,
        maxAge: <span class="hljs-number">0</span>,
        path: <span class="hljs-string">'/'</span>
    });
}
</code></pre>
<p>If anyone is looking for a deeper dive into the docs, I’ve added a couple of references below:</p>
<ul>
<li><p><a target="_blank" href="https://lucia-auth.com/">https://lucia-auth.com/</a> (resources on implementing authentication with JavaScript and TypeScript)</p>
</li>
<li><p><a target="_blank" href="https://thecopenhagenbook.com/">https://thecopenhagenbook.com/</a> (a general guideline on implementing auth in web applications)</p>
</li>
</ul>
<h3 id="heading-creatures-of-habit-status">“Creatures of Habit” Status</h3>
<p>My newest project, Creatures of Habit, is underway and off to a good start. The reason behind waiting to create an RPG-specific habit-tracking app is:</p>
<ol>
<li><p>I love RPGs….plain and simple 😂.</p>
</li>
<li><p>There are a few habit trackers out there that gamifies the tasks but none of them seemed to work exactly how I pictured it would work.</p>
</li>
</ol>
<p>My stack for this project is Sveltekit w/Typescript, Shadcn Svelte + Tailwind CSS, Drizzle ORM w/ SQLite, and authentication based on <a target="_blank" href="https://lucia-next.pages.dev/">this guide</a> from the Lucia maintainer. My goal, while hefty, is to create the perfect habit-tracking app for me and me only…if other people like it then so be it lol. But in all seriousness, my MVP (Minimum Viable Product) for this project is to get a working web app that actually functions like a storyboard/tabletop RPG. None of the other apps focus on the hero (or creature in my app’s case) enough. There are always a few stats given but none really felt like an RPG, just these shell RPGs that feel like grind fests or they turned into more of a subscription model for extra features and I totally get the latter if it’s a small team and they want to actually make money from the work they did. Habitica is a great example of an app that I want to make an improved version of. It’s got most of the features I’d want in an RPG-style habit tracker but there are also a few things I think it’s missing. Here is a little screenshot of the Dailies tab:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733707561746/cd682bec-a241-4663-93e0-ffe6fb4922c0.png" alt class="image--center mx-auto" /></p>
<p>One main feature I’d like to change is the quest system. It seems more akin to chores than actual quests. The main reason is because there is no story behind each quest. My idea is to treat them as recurring quests that get harder and harder each run-through, kind of like a roguelike progression with dungeons. Each dungeon has a boss but before you get to that boss level, you have to complete the objectives on each preceding level. Once the dungeon is cleared, there is a new tier you can do that will offer more exp and loot. That’s just one thing I’d change amongst a few others but overall, this will be an interesting journey to completion (or at least a working alpha). I made a roadmap in Notion and you can find that here → <a target="_blank" href="https://www.notion.so/Creatures-of-Habit-Roadmap-135ab7433e8e80d6a175c7de10bdb631?pvs=4">https://www.notion.so/Creatures-of-Habit-Roadmap-135ab7433e8e80d6a175c7de10bdb631?pvs=4</a></p>
<h3 id="heading-day-30-33-of-100-days-of-python">Day 30-33 of “100 Days of Python”</h3>
<p>Moving like molasses with this course 😅, but still moving nonetheless. The 4 days that were completed covered:</p>
<ol>
<li><p>Errors, Exception, &amp; JSON data</p>
</li>
<li><p>Flash Card Project</p>
</li>
<li><p>Email Sending with the <code>smtplib</code> &amp; <code>datetime</code> modules.</p>
</li>
<li><p>Learning about API Endpoints w/ ISS Overhead Notifier project</p>
</li>
</ol>
<p>The only new(ish) things covered within these 4 days were Errors, Exceptions, and using API Endpoints + Parameters. I won’t go into detail on these areas since this article is focused on Auth and my new project but you can find the completed code for each day on my <a target="_blank" href="https://github.com/kdleonard93/100-Days-Of-Code_Python">100-Days-Of-Python repo</a>.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>So that’s a wrap on this joint. There are lots of intriguing milestones to hit over the next year and I’m starting to settle in with what works for me in terms of my productivity. Tons of things learned in 2024 and I’m excited to get 2025 started on the right foot. I plan to continue posting article updates for the project but will also start smaller Python-based projects as well to not tire myself out on building Creature of Habit. The Python-specific projects are going to revolve around scripting and making things to automate my daily life + scripts for autonomous investing, specifically using the various APIs in the blockchain space. Until next time, folks ✌🏾.</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-work-or-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my work or want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://bsky.app/profile/digitaldopamine.dev">🦋 Bluesky</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Day 28 & 29 - Pomodoro GUI  & Password Manager with Tkinter]]></title><description><![CDATA[Tomato Tomàto
I’m combining these 2 days (and will probably group these up moving forward) and the Day 28 project was interesting but pretty boring 🥱. The objective was to create a Pomodoro app, which is essentially a focus time/break stopwatch. The...]]></description><link>https://blacknerd.dev/day-28-29-pomodoro-gui-password-manager-with-tkinter</link><guid isPermaLink="true">https://blacknerd.dev/day-28-29-pomodoro-gui-password-manager-with-tkinter</guid><category><![CDATA[Python]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[backend]]></category><category><![CDATA[app development]]></category><category><![CDATA[#codenewbies]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Wed, 11 Sep 2024 13:35:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1726023349914/b951a6e1-afd9-4ab4-b434-a14bb7886c56.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-tomato-tomato">Tomato Tomàto</h3>
<p>I’m combining these 2 days (and will probably group these up moving forward) and the Day 28 project was interesting but pretty boring 🥱. The objective was to create a Pomodoro app, which is essentially a focus time/break stopwatch. The app itself is very useful but there are of course a ton of apps similar to this that would be more beneficial and are built completely differently, but nonetheless, it was good practice.<br />The code can be found here → <a target="_blank" href="https://github.com/kdleonard93/100-Days-Of-Code_Python/tree/main/day-28">https://github.com/kdleonard93/100-Days-Of-Code_Python/tree/main/day-28</a> and give the program a try yourself. There weren’t any new and noteworthy concepts covered on this day so I won’t bore you with rambling about it.</p>
<h3 id="heading-peak-tkinter">Peak Tkinter</h3>
<p>Day 29 however, was a very interesting project. The password manager we created was supposed to have 3 main functions:</p>
<ul>
<li><p>Clean UI and alined entry fields</p>
</li>
<li><p>The “Generate Password” button generated a strong random password and added it into the corresponding field + copied it to your clipboard.</p>
</li>
<li><p>The “Add” button adds it to a text file and starts a new line after.</p>
</li>
</ul>
<p>Pretty straightforward goals but I also wanted to add another button to clean up file when I didn’t want it anymore. So I decided to add 1 additional function to this project that isn't in the module:</p>
<ul>
<li>I added a “Delete” button that deletes the file.</li>
</ul>
<p>One thing she notes here is that people are more inclined to store passwords on their local machine since that is the safer option and for the most part, I agree. However, she just tosses the password into a textfile. Now, I’m sure there are better methods to store passwords locally, but the one thing I added to her implementation was to make text file hidden to at least give me the illusion of storing passwords safely on my machine 😂. I enjoyed the password manager project and picked up a few tips along the way.</p>
<h4 id="heading-some-things-i-learned">Some Things I Learned</h4>
<p>There were two new functions from Tkinter that I hadn’t used before and picked up here. The <code>tk.insert()</code> and <code>tk.delete()</code> functions were used for my “Generate Password” and “Delete Text File” buttons, respectively. Nothing super unique about how they work but here is a quick overview and let’s say our entry in question is <code>email</code>:</p>
<ul>
<li><p><code>email.insert(index, "Text")</code>: Takes 2 arguments, the first is the index of entry you choose and the second argument is the text you’d like to insert. Pretty damn simple</p>
</li>
<li><p><code>email.delete(start_index, end_index)</code>: Takes 2 arguments, being the start and the end indexes of what you want to delete. 9/10, you’d write something like: <code>email.delete(0, END)</code> to delete the entire entry field.</p>
</li>
</ul>
<p>The second main takeaway from this project was the use cases for the paperclip library. Now I didn’t actually use that library for this project since Tkinter has its own clipboard functions, but, I do see myself using this library on future projects that need some sort of auto copy to clipboard. Bookmarked 🤘🏾.</p>
<p>If you wanna give this little project a spin, you can clone/fork it here → <a target="_blank" href="https://github.com/kdleonard93/100-Days-Of-Code_Python/tree/main/day-29">https://github.com/kdleonard93/100-Days-Of-Code_Python/tree/main/day-29</a></p>
<p>Here’s a lil’ demo:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.loom.com/share/62f424523b1f41efa48e1749133e86ce?sid=9b767fde-e585-412e-9967-fe9e6f96bfea">https://www.loom.com/share/62f424523b1f41efa48e1749133e86ce?sid=9b767fde-e585-412e-9967-fe9e6f96bfea</a></div>
<p> </p>
<h3 id="heading-eod">EOD</h3>
<p>That pretty much sums up these 2 days. I mentioned this in a previous post but I’ve been trying to figure out the best and most affordable way to gain some Kubernetes skills. Was informed by my company that they “didn’t have the budget” for the certificate training. Which to me is pretty shocking as the certificate is only $400. I’d understand not being able to reimburse tuition or anything else at a similar scale but $400??</p>
<p>But enough of my ranting, I still plan to make moves in the DevOps field and gain a deeper knowledge of Kubernetes and Docker. I’m finding that I like DevOps more and more as time passes. Either that or I’m starting to hate designing pages more and more as the day goes on lol. I’m garbage at styling and would truly be happier if I never have to do it again.</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-writing-or-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my writing or want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://twitter.com/RingoMandingo93"><strong>𝕏 Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discor</strong></a><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>d</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Day 27 - Tkinter, *args, **kwargs & Creating GUIs]]></title><description><![CDATA[Where's day 26????
I skipped it.....fight me about it 😄. But for real, skipped writing up and pushing a PR for day 26 cause it wasn't anything new and felt like a waste of time. I was also still in a bad headspace (quick context at the end of this p...]]></description><link>https://blacknerd.dev/day-27-tkinter-args-kwargs-creating-guis</link><guid isPermaLink="true">https://blacknerd.dev/day-27-tkinter-args-kwargs-creating-guis</guid><category><![CDATA[Python]]></category><category><![CDATA[tkinter]]></category><category><![CDATA[Object Oriented Programming]]></category><category><![CDATA[GUI]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Sat, 27 Apr 2024 15:55:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1714230735970/9d2fe3f1-3d37-4a72-a53b-15c0303f6376.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-wheres-day-26">Where's day 26????</h2>
<p>I skipped it.....fight me about it 😄. But for real, skipped writing up and pushing a PR for day 26 cause it wasn't anything new and felt like a waste of time. I was also still in a bad headspace (quick context at the end of this post), so I kept it moving. That leads us to Day 27!</p>
<h3 id="heading-understanding-args-and-kwargs"><strong>Understanding</strong> <code>*args</code> and <code>**kwargs</code></h3>
<p>For day 27, the focus was on <code>*args</code> and <code>**kwargs</code>. <code>*args</code> and <code>**kwargs</code> are special operators that allow functions to accept an arbitrary number of arguments and keyword arguments respectively. Below is a quick definition of both and how they can be useful:</p>
<ul>
<li><p><code>*args</code> allows you to pass any number of positional arguments to a function. This is handy when you want to write functions that can handle varying amounts of input data.</p>
</li>
<li><p><code>**kwargs</code> lets you handle named arguments that you have not explicitly defined in advance. This is great for working with functions where you might have a variety of configuration options.</p>
</li>
</ul>
<p><strong>Pros:</strong></p>
<ul>
<li><p><strong>Flexibility:</strong> Both <code>*args</code> and <code>**kwargs</code> provide flexibility in function calls, allowing for more general and adaptable code.</p>
</li>
<li><p><strong>Convenience:</strong> They can make initializing objects and calling functions cleaner and more intuitive. Let's say, for example, when dealing with a complex set of parameters.</p>
</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li><p><strong>Readability:</strong> Functions using <code>*args</code> and <code>**kwargs</code> can be less explicit, which might lead to code that is harder to understand.</p>
</li>
<li><p><strong>Debugging:</strong> It can be more challenging to debug issues as the source of incorrect values can be obscured.</p>
</li>
</ul>
<h3 id="heading-a-few-code-examples-for-my-visual-folks"><strong>A few code examples for my visual folks</strong></h3>
<p>Here’s how you might use these in your own Python scripts:</p>
<h4 id="heading-basic-usage-of-args"><strong>Basic Usage of</strong> <code>*args</code></h4>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_function</span>(<span class="hljs-params">*args</span>):</span>
    <span class="hljs-keyword">for</span> arg <span class="hljs-keyword">in</span> args:
        print(arg)

my_function(<span class="hljs-string">'Hello'</span>, <span class="hljs-string">'World'</span>, <span class="hljs-number">123</span>, true)
</code></pre>
<p>This function will accept any number of arguments and print each one. So if i wanted to pass 4 arguments, I'd just need to call <code>my_function('Hello', 'World', 123, True)</code> . With the 4th argument being a boolean.</p>
<h4 id="heading-basic-usage-of-kwargs"><strong>Basic Usage of</strong> <code>**kwargs</code></h4>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_function</span>(<span class="hljs-params">**kwargs</span>):</span>
    <span class="hljs-keyword">for</span> key, value <span class="hljs-keyword">in</span> kwargs.items():
        print(<span class="hljs-string">f"<span class="hljs-subst">{key}</span> = <span class="hljs-subst">{value}</span>"</span>)

my_function(name=<span class="hljs-string">'Alice'</span>, age=<span class="hljs-number">25</span>)
</code></pre>
<p>This function prints out key-value pairs, accepting any number of named arguments. The same thing goes for **kwargs when it comes to adding boolean values. It gets a bit trickier though but hopefully the updated example helps:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_function</span>(<span class="hljs-params">**kwargs</span>):</span>
    <span class="hljs-comment"># Print all keyword arguments</span>
    <span class="hljs-keyword">for</span> key, value <span class="hljs-keyword">in</span> kwargs.items():
        print(<span class="hljs-string">f"<span class="hljs-subst">{key}</span> = <span class="hljs-subst">{value}</span>"</span>)

    <span class="hljs-comment"># Check if debug mode is enabled</span>
    <span class="hljs-keyword">if</span> kwargs.get(<span class="hljs-string">"debug"</span>, <span class="hljs-literal">False</span>):  <span class="hljs-comment"># Explicitly use default value for clarity</span>
        print(<span class="hljs-string">"Debug mode is enabled."</span>)
    <span class="hljs-keyword">else</span>:
        print(<span class="hljs-string">"Debug mode is not enabled."</span>)

    <span class="hljs-comment"># Check if logging is on or off</span>
    logging_status = <span class="hljs-string">'on'</span> <span class="hljs-keyword">if</span> kwargs.get(<span class="hljs-string">'logging'</span>, <span class="hljs-literal">False</span>) <span class="hljs-keyword">else</span> <span class="hljs-string">'off'</span>
    print(<span class="hljs-string">f"Logging is <span class="hljs-subst">{logging_status}</span>."</span>)

my_function(name=<span class="hljs-string">'Alice'</span>, age=<span class="hljs-number">25</span>, debug=<span class="hljs-literal">True</span>, logging=<span class="hljs-literal">False</span>)
</code></pre>
<p>The expected output of the above code is:</p>
<pre><code class="lang-python">name = Alice
age = <span class="hljs-number">25</span>
debug = <span class="hljs-literal">True</span>
logging = <span class="hljs-literal">False</span>
Debug mode <span class="hljs-keyword">is</span> enabled.
Logging <span class="hljs-keyword">is</span> off.
</code></pre>
<p><strong>How Tkinter heavily uses <em>args and</em> *kwargs</strong></p>
<p>Tkinter uses <code>*args</code> and <code>**kwargs</code> extensively to make it flexible when creating widgets with varying configurations.</p>
<h4 id="heading-widget-example"><strong>Widget Example</strong></h4>
<p>Here’s a Tkinter example from the day's project how <code>*args</code> and <code>**kwargs</code> are useful for creating UI elements with different configurations:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> tkinter <span class="hljs-keyword">import</span> *


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">button_clicked</span>():</span>
    print(<span class="hljs-string">"I got clicked"</span>)
    new_text = input.get()
    my_label.config(text=new_text)


window = Tk()
window.title(<span class="hljs-string">"My First GUI Program"</span>)
window.minsize(width=<span class="hljs-number">500</span>, height=<span class="hljs-number">300</span>)
window.config(padx=<span class="hljs-number">100</span>, pady=<span class="hljs-number">200</span>)

<span class="hljs-comment"># Label</span>
my_label = Label(text=<span class="hljs-string">"I Am a Label"</span>, font=(<span class="hljs-string">"Arial"</span>, <span class="hljs-number">24</span>, <span class="hljs-string">"bold"</span>))
my_label.config(text=<span class="hljs-string">"New Text"</span>)
my_label.grid(column=<span class="hljs-number">0</span>, row=<span class="hljs-number">0</span>)
my_label.config(padx=<span class="hljs-number">50</span>, pady=<span class="hljs-number">50</span>)

<span class="hljs-comment"># Button</span>
button = Button(text=<span class="hljs-string">"Click Me"</span>, command=button_clicked)
button.grid(column=<span class="hljs-number">1</span>, row=<span class="hljs-number">1</span>)

new_button = Button(text=<span class="hljs-string">"New Button"</span>)
new_button.grid(column=<span class="hljs-number">2</span>, row=<span class="hljs-number">0</span>)

<span class="hljs-comment"># Entry</span>
input = Entry(width=<span class="hljs-number">10</span>)
print(input.get())
input.grid(column=<span class="hljs-number">3</span>, row=<span class="hljs-number">2</span>)


window.mainloop()
</code></pre>
<p>I'll run through a quick explanation of each important section.</p>
<h4 id="heading-import-tkinter">Import Tkinter</h4>
<pre><code class="lang-python">pythonCopy codefrom tkinter <span class="hljs-keyword">import</span> *
</code></pre>
<p>This line imports all classes, functions, and variables from the Tkinter module. It allows the script to access Tkinter's GUI components without prefixing them with <code>tkinter.</code> .</p>
<h4 id="heading-define-button-click-function">Define Button Click Function</h4>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">button_clicked</span>():</span>
    print(<span class="hljs-string">"I got clicked"</span>)
    new_text = input.get()
    my_label.config(text=new_text)
</code></pre>
<p>This function <code>button_clicked</code> is called when the GUI's button is clicked. It performs a couple actions:</p>
<ol>
<li><p>Prints "I got clicked" to the console.</p>
</li>
<li><p>It retrieves text from an entry widget (<code>input</code>), and sets this text as the new label for <code>my_label</code> using <code>config()</code>.</p>
</li>
</ol>
<h4 id="heading-set-the-main-window">Set the Main Window</h4>
<pre><code class="lang-python">window = Tk()
window.title(<span class="hljs-string">"My First GUI Program"</span>)
window.minsize(width=<span class="hljs-number">500</span>, height=<span class="hljs-number">300</span>)
window.config(padx=<span class="hljs-number">100</span>, pady=<span class="hljs-number">200</span>)
</code></pre>
<p>These lines set up the main window of the application:</p>
<ul>
<li><p><code>Tk()</code> creates the main window.</p>
</li>
<li><p><code>title()</code> sets the window's title.</p>
</li>
<li><p><code>minsize()</code> specifies the minimum size of the window.</p>
</li>
<li><p><code>config()</code> adds padding around the inside of the window (100 pixels on the sides, 200 pixels on the top and bottom).</p>
</li>
</ul>
<h4 id="heading-create-label">Create Label</h4>
<pre><code class="lang-python">my_label = Label(text=<span class="hljs-string">"I Am a Label"</span>, font=(<span class="hljs-string">"Arial"</span>, <span class="hljs-number">24</span>, <span class="hljs-string">"bold"</span>))
my_label.config(text=<span class="hljs-string">"New Text"</span>)
my_label.grid(column=<span class="hljs-number">0</span>, row=<span class="hljs-number">0</span>)
my_label.config(padx=<span class="hljs-number">50</span>, pady=<span class="hljs-number">50</span>)
</code></pre>
<p>This section creates a label widget:</p>
<ul>
<li><p><code>Label()</code> creates a label with initial text "I Am a Label" and specifies the font.</p>
</li>
<li><p>The label text is immediately changed to "New Text" with <code>config()</code>.</p>
</li>
<li><p><code>grid()</code> places the label in the grid layout (at column 0, row 0).</p>
</li>
<li><p>Additional padding is added around the label.</p>
</li>
</ul>
<h4 id="heading-create-buttons">Create Buttons</h4>
<pre><code class="lang-python">button = Button(text=<span class="hljs-string">"Click Me"</span>, command=button_clicked)
button.grid(column=<span class="hljs-number">1</span>, row=<span class="hljs-number">1</span>)

new_button = Button(text=<span class="hljs-string">"New Button"</span>)
new_button.grid(column=<span class="hljs-number">2</span>, row=<span class="hljs-number">0</span>)
</code></pre>
<p>This code creates two buttons:</p>
<ul>
<li><p>The first button, when clicked, triggers <code>button_clicked()</code>.</p>
</li>
<li><p>The buttons are placed using <code>grid()</code>; the first button at column 1, row 1, and the second button at column 2, row 0.</p>
</li>
<li><p>The second button is just filler. There is no command attached to it.</p>
</li>
</ul>
<h4 id="heading-create-an-entry-widget">Create an Entry Widget</h4>
<pre><code class="lang-python">input = Entry(width=<span class="hljs-number">10</span>)
print(input.get())
input.grid(column=<span class="hljs-number">3</span>, row=<span class="hljs-number">2</span>)
</code></pre>
<ul>
<li><p><code>Entry()</code> creates an entry widget allowing users to input text.</p>
</li>
<li><p><code>input.get()</code> is used to retrieve the current text in the entry widget, which is empty initially and its result (<code>''</code>) is printed.</p>
</li>
<li><p><code>grid()</code> positions the entry widget in the grid.</p>
</li>
</ul>
<h3 id="heading-start-the-gui"><strong>Start the GUI</strong></h3>
<pre><code class="lang-python">window.mainloop()
</code></pre>
<p>This line starts the Tkinter event loop, which is necessary to keep the window displaying and responding to user inputs.</p>
<p>That's it, a pretty vanilla GUI created with just Tkinter 🙂.</p>
<h3 id="heading-conclusion"><strong>Conclusion</strong></h3>
<p>Using <code>*args</code> and <code>**kwargs</code> can significantly increase the flexibility and reusability of your Python code, making it easier to handle functions with a variable number of parameters. When paired with Tkinter, these operators allow you to create more dynamic and configurable GUIs without committing to a fixed number of parameters in your functions. While they offer great advantages, no need to go crazy and use them everywhere lol. Use it if it helps maintain code clarity and simplicity.</p>
<p>I still have plans to add authentication to my FanFlix project and have been making some adjustments behind the scenes in how I want to tackle my Finance tracker app 🙃. I can never make up my mind</p>
<h3 id="heading-hiatus-context-for-whoever-cares">Hiatus Context for whoever cares</h3>
<h4 id="heading-emerging-from-the-shadows-of-sadness">Emerging from the shadows of sadness</h4>
<p>My life is defined by shake-ups, I'm almost certain of it now. Needed to take yet another break away from leveling up and Python projects because of a few things (that I won't go in-depth over):<br />1. Long-term relationship was getting rocky and ultimately ended last month.<br />2. Had to make a quick move recently out of our shared home causing me to drop a ton of money in a very short time.<br />3. And worst of all, a couple of weeks ago, lost a relative that I was extremely close to.</p>
<p>It's hard to keep my focus on leveling up when my world and the general world is in chaos. My heart aches for myself and my family, but it also aches for the innocent lives in the DR Congo, Haiti, Sudan, and Gaza. It's really grim times out here and I've had to shift focus on family and friends. At the end of the day, time is the only thing we can't get back, so for the people I care about, I need to dedicate more time to just having fun and experiencing joy with them. Who knows, anything unexpected can happen just like it did for my fam.</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-writing-or-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my writing or want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://twitter.com/RingoMandingo93"><strong>𝕏 Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Whipping Up a Film Catalogue With the DUST Stack]]></title><description><![CDATA[Project Intro
Flashing back to my previous post, I mentioned I was working on a "Movie List" project using Django, Svelte, and TypeScript. Typescript was an addition to this project as I've been wanting to learn it for some time now to have better ty...]]></description><link>https://blacknerd.dev/whipping-up-a-film-catalogue-with-the-dust-stack</link><guid isPermaLink="true">https://blacknerd.dev/whipping-up-a-film-catalogue-with-the-dust-stack</guid><category><![CDATA[Django]]></category><category><![CDATA[Sveltekit]]></category><category><![CDATA[Svelte]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Movies]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[app development]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Mon, 22 Jan 2024 00:10:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1705881768686/c2bce49d-e3e4-459f-93d5-a2c387d0606e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-project-intro">Project Intro</h2>
<p>Flashing back to my previous post, I mentioned I was working on a "Movie List" project using Django, Svelte, and TypeScript. Typescript was an addition to this project as I've been wanting to learn it for some time now to have better type safety in my projects helping me catch errors early in my text editor instead of in the browser. I feel it also encourages better coding practices in general, so I wanted to embrace and use it moving forward instead of just using JS. I'd like to coin the <strong>DUST</strong> <strong>stack</strong> (<strong>D</strong>anjgo <strong>U</strong>I <strong>S</strong>velte <strong>T</strong>ypescript) for the project. I was also thinking of other stack names like CESN (pronounced season) which would be <strong>C</strong>ockroachDB, <strong>E</strong>xpress, <strong>S</strong>velte, and <strong>N</strong>ode but let's not go down a tech-stack acronym rabbit hole lol.</p>
<p>The project, at first glance, appears straightforward but is quite intricate under the hood. My primary learning curve was working with Django for the first time. Although I've done some setup work with Django and Flask before, this is my first complete project with a functioning database. Django's handling of the heavy lifting, like WSGI and ORM setup, allowed me to focus more on the documentation, styling, and additional functionalities.</p>
<h2 id="heading-frontend-finesse">Frontend Finesse</h2>
<p>Svelte is increasingly becoming my preferred frontend framework. It's much easier for me to grasp core concepts in Svelte compared to React. Having recently used Vue at work, I'd rank my framework preferences as Svelte, Vue, Qwik, and then React. I've never used Qwik, but that's a testament to my aversion to React (big feels 😤). But enough of my rambling, let us get into what I used in full and what I learned along the way.</p>
<h3 id="heading-skeletonui">SkeletonUI</h3>
<p>I initiated the project using <a target="_blank" href="https://kit.svelte.dev/">Sveltekit</a> and <a target="_blank" href="https://www.skeleton.dev/">Skeleton</a> as the UI library. Skeleton, being tightly integrated with Svelte, is like a component library for Tailwind CSS but specifically tailored for Svelte and Sveltekit. It includes components, stores, and actions. The theme generator on their site is a standout feature, allowing users to experiment with pre-designed themes or create their own. This is especially helpful for those, like myself, who aren't inherently skilled in design. I experimented with several themes before settling on a pre-designed one to adhere to best color practice standards.<br />Unlike my recent <a target="_blank" href="https://digitaldopamine.dev/">Dev Portfolio</a> update, which was just working with basic Svelte components, I dove deeper into Sveltekit's core concepts such as Filesystem-Based Routing, Loading Data, and Form Actions.</p>
<h3 id="heading-routing">Routing</h3>
<p>This routing system is CLUTCH for readability and file structure. Each route of the app, let's say for example: <code>src/routes/about</code>, is the actual page URL and in this instance, the URL would be <code>http://localhost:5173/about</code>. Now within the project directory, the <code>/about/</code> route is a folder that would contain the actual svelte files for the page.</p>
<h3 id="heading-page">+page</h3>
<p>For a quick definition: A <code>+page.svelte</code> component defines a page of your app. By default, pages are rendered both on the server (<a target="_blank" href="https://kit.svelte.dev/docs/glossary#ssr">SSR</a>) for the initial request and in the browser (<a target="_blank" href="https://kit.svelte.dev/docs/glossary#csr">CSR</a>) for subsequent navigation. According to the docs, often times a +page.js/ts will be added in the same folder to load actual data for the page to render.</p>
<h3 id="heading-loading-data">Loading Data</h3>
<p>The docs explained this very clearly so I'll just echo what they said for the most part. A <code>+page.svelte</code> file can have a sibling <code>+page.js</code> that exports a <code>load</code> function, the return value of which is available to the page via the <code>data</code> prop. For example in my case, below I've added what my load function looks is:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">load</span>(<span class="hljs-params">{ params }</span>) </span>{
  <span class="hljs-keyword">return</span> {
    id: params.id,
  };
}
</code></pre>
<p>Essentially, this function is designed to run when a specific film's route is accessed. The <code>{params}</code> object is passed to the function as an argument containing the route parameters. <code>params.id</code> refers to the unique identifier of a film and the purpose of the <code>load</code> function is to extract the <code>id</code> from the route parameters and return it. The <code>id</code> is then used to load or fetch the specific film data from your Django or whatever backend/data source you use. By doing this, the application can dynamically display the details of the film that corresponds to the unique <code>id</code> in the URL. This is pretty much the butter to the bread which we call <code>+page.svelte</code>.</p>
<h3 id="heading-typescript-torturi-mean-fun">TypeScript Tortur......I Mean <em>Fun</em> 🙃</h3>
<p>*MR. PBH voice* Oooooooweeeee, this one was a doozie! I haven't touched or even looked at the docs of Typescript before adding it to this project but sometime late last year, there was a podcast I was listening to talking about how it'll change the way you code and look at JS forever once you dive in and work with it in a couple of projects. I was never a rockstar at JS in the first place so at first, I was telling myself I needed to get better at JS before getting into TS but then I thought.....why wait 😂. Let me tell you the struggle was real but well worth it. There are a handful of things I would have missed or didn't realize would have caused issues during runtime. The things that I got the most errors for were related to naming my variables and how data was passed in functions. There was a need to explicitly provide a data type or give it a type of <code>any</code>, but you'd do good to try and avoid using <code>any</code> since that's a cop-out that can bite you in the ass down the road with a bigger project as it grows. For example, I needed to define variables related to the data keys in my model so that my movie cards populate and update properly. In JS you'd just create a <code>let</code> or <code>const</code> like so:</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">let</span> name;
  <span class="hljs-keyword">let</span> director;
  <span class="hljs-keyword">let</span> release_year;
  <span class="hljs-keyword">let</span> description;
  <span class="hljs-keyword">let</span> imageFile;
  <span class="hljs-keyword">let</span> film;
  <span class="hljs-keyword">let</span> id;
</code></pre>
<p>Seems pretty simple right? But a lot of things can go wrong with this data. In my case, with the Django model only accepting certain data types for each input field when receiving and retrieving the data, it's important to make sure that my logic is solid when it submits that data during runtime. The Typescript way of defining these variables is:</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">let</span> name: <span class="hljs-built_in">string</span> = <span class="hljs-string">""</span>;
  <span class="hljs-keyword">let</span> director: <span class="hljs-built_in">string</span> = <span class="hljs-string">""</span>;
  <span class="hljs-keyword">let</span> release_year: <span class="hljs-built_in">string</span> = <span class="hljs-string">""</span>;
  <span class="hljs-keyword">let</span> description: <span class="hljs-built_in">string</span> = <span class="hljs-string">""</span>;
  <span class="hljs-keyword">let</span> imageFile: File | <span class="hljs-literal">null</span> = <span class="hljs-literal">null</span>;
  <span class="hljs-keyword">let</span> film: Film | <span class="hljs-literal">null</span> = <span class="hljs-literal">null</span>;
  <span class="hljs-keyword">let</span> id: <span class="hljs-built_in">number</span>;
</code></pre>
<p>Here you can see the need to explicitly classify these variables as they are. There are a few caveats with each value, like strings needing to be set to an empty or default value. But when it comes to handling images, "Typescript got hands" is how I would describe how it kicked my ass trying to figure out how the hell I needed to work with the <code>imageFile</code> variable. But through some digging and questions, I was led to the docs for the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/File">File Interface</a>. I'm not going to go into specifics on that particular interface but I will elaborate on what I learned about Typescript interfaces in general.</p>
<h4 id="heading-interfaces">Interfaces</h4>
<p>TypeScript's main thing is that it checks types based on the shape of values. This approach is often referred to as “duck typing” or “structural subtyping”. In TypeScript, interfaces are key. They're used to name these types and are an effective tool for defining agreements within your code and also with code that's outside of your project.<br />I only needed to create one interface for Films and you can see how the <code>film-store.ts</code> file was structured with my Film interface here:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { writable } <span class="hljs-keyword">from</span> <span class="hljs-string">"svelte/store"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> Film {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
  director: <span class="hljs-built_in">string</span>;
  description: <span class="hljs-built_in">string</span>;
  release_year: <span class="hljs-built_in">number</span> | <span class="hljs-literal">null</span>;
  image: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> FilmStore = writable&lt;Film[]&gt;([]);
</code></pre>
<p>This code sets up a Svelte store to manage a list of films in my application. As you might see in my repo, I am now able to access/subscribe to <code>FilmStore</code> in other parts of the application, super useful and power-charged with TS. Other parts of the frontend aren't new concepts to me so we won't dive into every little thing I did before I started working on my backend branch.</p>
<h2 id="heading-build-a-backend">Build-A-Backend</h2>
<p>This is the first project in which I used Django. I decided to stick with learning and working with Django over Flask because I found their documentation to be much more organized and maintained. Django is also a better fit when it comes to working with Postgres or any SQL database. I can still do it with Flask, but there would have been much more setup needed and additional libraries, like <code>Gunicorn</code> for the WSGI Server, <code>Alembic</code> for database migrations, and <code>SQLAlchemy</code> for ORM just to name a few. There is a bunch more to add for a full-fledged app and not having to worry about that each time is a time saver. One downside though, is that some projects (including this one) don't use all that Django offers. That's just a small inconvenience though as I would have had a way larger inconvenience working with Flask down the line with my main project since it's not easy to use CockroachDB with Flask. The main reason is because of Django's built-in ORM, which can handle the nuances of the database like connection management, transactions, and data integrity. Cockroach Labs only has documentation for Django as well so that swayed me as well.<br />I'll do a project with Flask at some point this year but right now, I'm leaning on the tools with better learning resources. With that being said, let's get into the backend of my project</p>
<h3 id="heading-django-unchained">Django ⛓️ Unchained</h3>
<p>Working with Django started pretty slow. There's a lot to understand about how things work with the framework but after the basic setup was complete, building out the needed models and views was easy enough to code up. I'll admit that I will still need to go over docs the next few times when starting up my backend since I'm still trying to wrap my head around some of the concepts. The core concepts I'd note for Django that stuck with me would be MTV (Model-Template-View) Architecture, ORM (Object-Relational Mapper), Admin Interface, and Migrations.</p>
<p><strong>MTV (Model-Template-View) Architecture</strong></p>
<p><strong>Model:</strong> Represents the data structure and manages the business logic of the application. It is responsible for retrieving and storing data, as well as performing any necessary data processing.</p>
<p><strong>Template</strong>: Defines how the data is presented to the user. It is responsible for rendering the HTML and displaying the data from the Model.</p>
<p><strong>View:</strong> Acts as a bridge between the Model and Template. It receives user input from the Template, processes it, and updates the Model accordingly. The View also retrieves data from the Model and passes it to the Template for rendering.</p>
<p><strong>ORM (Object-Relational Mapper)</strong></p>
<p>I've spoken about the ORM before but just to hammer it in, Django’s ORM allows me to interact with the database using Python code instead of SQL. That's about the simplest definition I can provide.</p>
<p><strong>Admin Interface</strong></p>
<p>The admin interface was my favorite aspect of working with Django and i read into libraries that can accomplish this with Flask as well. But having an admin panel in general is a great tool for working with the DB and being able to create a SuperUser and account with just a few commands makes the admin setup as smooth as Prince-permed hair.</p>
<p><strong>Migrations</strong></p>
<p>I didn't work with migration other than migrating my initial model setup. Any changes to your models and views would require you to make another migration but again, very straightforward scripts that have already been set up in the framework made the updates easy.</p>
<p>I'd say I spent more time on the front end than I did on the backend setup so there isn't much to talk about other than creating the models.</p>
<h4 id="heading-film-model">Film Model</h4>
<p>Creating the Film model required fields like Name, Release Year, Description, Director, and Image. I used SQLite over CockroachDb for this one for its simplicity and compatibility with Django's default settings. The Film model in Django is intuitive and ensures clean data inputs:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models
<span class="hljs-keyword">from</span> django.core.validators <span class="hljs-keyword">import</span> MinValueValidator, MaxValueValidator
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Film</span>(<span class="hljs-params">models.Model</span>):</span>
    name = models.CharField(max_length=<span class="hljs-number">128</span>)
    release_year = models.IntegerField(validators=[
        MinValueValidator(<span class="hljs-number">1900</span>),
        MaxValueValidator(datetime.now().year)
    ],
        default=datetime.now().year
    )
    description = models.TextField(max_length=<span class="hljs-number">356</span>)
    director = models.CharField(max_length=<span class="hljs-number">128</span>)
    image = models.ImageField(upload_to=<span class="hljs-string">'images/'</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> self.name
</code></pre>
<p>As you can see here, I have my imports so I can utilize Django's model class. A new Film class is created and it inherits from <code>models.Model</code>, making it a Django model, so it's tied to a database table. Within this Film class, you see all of my model keys mentioned earlier with constraints tied to them for cleaner data inputs. I want to highlight my image key and note that for the images to be stored, I created a media and image folder (<code>/media/images/</code>), and directed the image uploads to be managed there. One thing I didn't get added was the removal of those images from the folder when a film is deleted. What happens now is, that the data is removed from the DB and the state + store is reset/removed from the front end, but the actual image file lingers in the repo so this can cause a major issue with a bigger app that takes in a bunch of create and delete requests a day. But I'll be my only enemy here so that's a feature I was going to try and add to this project for improvement.</p>
<h2 id="heading-complete-but-room-for-improvement">Complete But Room For Improvement</h2>
<p>With all that said, the project is complete and works. You can run both the front end and back end of the site to start. The homepage is just a title and this is 1 section that I plan to improve by just adding some basic designs and content about the project. To run the project, you would need to open up 2 terminal tabs, 1 for the front end and 1 for the back end. You can check out the <a target="_blank" href="https://github.com/kdleonard93/film-fan">README</a> on the git repo for a detailed doc on how to get started but below is a video of me booting up both the front-end and back-end + navigating through the app a bit:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.loom.com/share/06b92e88eda6474f8028743cd23d7623">https://www.loom.com/share/06b92e88eda6474f8028743cd23d7623</a></div>
<p> </p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>That wraps up my project! Folks should expect more features to come to this project like Tagging/Sorting, Authentication, and Deployment, but I knocked out the deliverable for my 1st Svelte + Django project and everyone is welcome to test it out and and tell me everything I did wrong or can do better 😄.<br />My next goal is to choose one of 2 tasks:</p>
<p><strong>A)</strong> Work on a project that involves CockroachDB. Most likely a workshop they have on their "University" site <a target="_blank" href="https://university.cockroachlabs.com/">https://university.cockroachlabs.com/</a>.</p>
<p><strong>B)</strong> Build a stock analysis app with Svletekit to get more practice with consuming third-party APIs and working with Svelte store and local storage.</p>
<p>I'm leaning towards <strong>B</strong> since I do need more work with APIs but I also want to get through a small project using Cockroach to get my feet wet there. I'll make my decision and provide an update in my next post, which will be "Day 26 of 100-Days-of-Python-Code". I think I'm pretty on pace with the phases I made for myself. My Goal for the full-fledged Personal Finance Tracker app was to have it complete within a year, which is double the total time of the phases (6 months) since I'm not doing this full time and like to enjoy the other things in life as well. But here is the Repo for the project -&gt; <a target="_blank" href="https://github.com/kdleonard93/film-fan">https://github.com/kdleonard93/film-fan</a> so check it out! After the next project for practice, I'll begin my work on the main app 🤘🏾.</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-writing-or-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my writing or want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://twitter.com/RingoMandingo93"><strong>𝕏 Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Day 25 - US States Game + Movie List]]></title><description><![CDATA[Let's start with the boring stuff
Today I completed Day-25 of my 100 Days of Python journey. The challenge? To deepen my understanding of Pandas through a project that, honestly, wasn't the most thrilling – a U.S. States guessing game. Sure, it sound...]]></description><link>https://blacknerd.dev/day-25-us-states-game-movie-list</link><guid isPermaLink="true">https://blacknerd.dev/day-25-us-states-game-movie-list</guid><category><![CDATA[100DaysOfCode]]></category><category><![CDATA[pandas]]></category><category><![CDATA[Python]]></category><category><![CDATA[DataManipulation]]></category><category><![CDATA[Svelte]]></category><category><![CDATA[Django]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Mon, 08 Jan 2024 04:17:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1704686246528/563f51da-2228-4eb2-a321-ed765311dd76.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-lets-start-with-the-boring-stuff">Let's start with the boring stuff</h2>
<p>Today I completed Day-25 of my 100 Days of Python journey. The challenge? To deepen my understanding of Pandas through a project that, honestly, wasn't the most thrilling – a U.S. States guessing game. Sure, it sounds mundane, but it's crucial stuff, especially since handling CSV files with Pandas is a fundamental skill in the world of finance apps. So, let's dive in and break down this project, tedious as it might have been!</p>
<h4 id="heading-the-toolbox-turtle-amp-pandas">🎨 The Toolbox: turtle &amp; pandas</h4>
<p>For this project, we only needed two libraries: <code>turtle</code> and <code>pandas</code>. Turtle is like the old reliable of Python teaching tools. It's great for learning Python basics through small, graphical projects.</p>
<p>And then there's <code>pandas</code> – the data wizard of Python. Think of it as your go-to tool for manipulating structured data like CSV files. In this project, <code>pandas</code> was the key to managing the game's data, from importing state info to exporting our "to-learn" list. 📊📝</p>
<h4 id="heading-setting-the-scene-the-game-screen">🕹️ Setting the Scene: The Game Screen</h4>
<p>The first order of business was setting up our game screen, creating a new window titled "U.S. States Game" and decking it out with a background image of a blank U.S. map. The image is a <code>.gif</code> file since the turtle library only works with those files for use within its methods. For example:</p>
<pre><code class="lang-python">pythonCopy code    image = <span class="hljs-string">"blank_states_img.gif"</span>  
    screen.addshape(image)
    turtle.shape(image)
</code></pre>
<p>With these lines, we're utilizing <code>turtle</code>'s <code>addshape()</code> and <code>shape()</code> functions to integrate our map into the game.</p>
<h4 id="heading-crunching-data-with-pandas">📚 Crunching Data with Pandas</h4>
<p>Next, we harnessed the power of <code>pandas</code> to read a CSV file named "50_states.csv". This file was essentially the game's backbone, storing the names and coordinates of all U.S. states. We transformed this data into a list called <code>all_states</code>, setting us up for the game's logic. 🗺️</p>
<h4 id="heading-the-gameplay-guessing-the-states">🎮 The Gameplay: Guessing the States</h4>
<p>This is where things got a tad more interesting. We started with an empty list, <code>guessed_states</code>, to track correct guesses. The game looped, prompting for state name guesses. Typing "Exit" would end the game and save a list of states still to be guessed into "states_to_learn.csv". A nice little feature to keep the challenge going! 🏁</p>
<h4 id="heading-marking-the-map-displaying-guessed-states">✍️ Marking the Map: Displaying Guessed States</h4>
<p>Every correct guess brought a new turtle onto the scene to mark the state's name on the map. Simple, yet effective. You can find the project here if you wanna check out the code -&gt; <a target="_blank" href="https://github.com/kdleonard93/100-Days-Of-Code_Python/pull/17">Github Repo</a>.</p>
<p>So, that wraps up Day-25. While the project didn't exactly set my world on fire, it did reinforce some key skills. But let's be real, my mind kept drifting to a more exciting project...</p>
<h3 id="heading-teasing-fanflix"><strong>Teasing FanFlix</strong></h3>
<p>The working title? FanFlix. It's not set in stone, but the project itself is something I'm pretty pumped about. It's a CRUD app blending Svelte's sleekness with Django's robustness. I'm still chipping away at it, and I'll share more in a dedicated post once it's up and running. Stay tuned for a real-world example of Svelte and Django in harmony – something that's not too common out there.</p>
<h4 id="heading-lets-connect">📢 Let's Connect!</h4>
<p>Got any thoughts on Python, game coding, my main project, or just tech in general? Hit me up and lets chat!</p>
<ul>
<li><p><a target="_blank" href="https://twitter.com/RingoMandingo93"><strong>𝕏 Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Personal Finance Tracker Project and 2024 Plans]]></title><description><![CDATA[Project Planning
Hey folks, after a bunch of quick trials with a bunch of different stacks, I finally settled on what I'm going to focus on with this project. The projection of a project of this size is about 6 months but knowing how I work and what ...]]></description><link>https://blacknerd.dev/personal-finance-tracker-project-and-2024-plans</link><guid isPermaLink="true">https://blacknerd.dev/personal-finance-tracker-project-and-2024-plans</guid><category><![CDATA[Svelte]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Python]]></category><category><![CDATA[Django]]></category><category><![CDATA[cockroachdb]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Docker]]></category><category><![CDATA[app development]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Wed, 03 Jan 2024 12:00:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1704262361339/2fb0dc0b-ac9d-4411-9f66-3903c75270cd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-project-planning">Project Planning</h2>
<p>Hey folks, after a bunch of quick trials with a bunch of different stacks, I finally settled on what I'm going to focus on with this project. The projection of a project of this size is about 6 months but knowing how I work and what I have going on in my personal life, I'm hoping to have a working app within a year. The new tech coming out these days has sent me down many rabbit holes, resulting in me wanting to use all of it in my day-to-day activity 😄. With that urge aside, I needed to make a decision on what I was going to use for the Personal Finance app mentioned in my <a target="_blank" href="https://blacknerd.dev/twitter-bot-roadblock">last post</a>. I have a hard time making decisions as is on tech stacks and the idea of needing to learn new technologies when I'm still perfecting languages I use in my salaried job is exhausting and pretty daunting at times. But I know the pain and constraints with tried and true technologies and at my point in my career, I feel these new technologies are going to become more relevant and needed in this evergrowing industry where exponential growth is imminent. My main goal is still a career working with Python but to also try and pinpoint the finance industry, whether it's big tech or freelancing, hence this project. The tech I've worked with for deciding my stack (New to Old) includes <a target="_blank" href="https://kit.svelte.dev/">SvelteKit</a>, <a target="_blank" href="https://www.python.org/">Python</a> (<a target="_blank" href="https://www.djangoproject.com/">Django</a> + <a target="_blank" href="https://flask.palletsprojects.com/en/3.0.x/">Flask</a>), <a target="_blank" href="https://bun.sh/">Bun</a> (for frontend package management), <a target="_blank" href="https://www.docker.com/">Docker</a>, <a target="_blank" href="https://www.cockroachlabs.com/">CockroachDB</a>, <a target="_blank" href="https://fauna.com/">FaunaDB</a>, Cloudflare, and <a target="_blank" href="https://render.com/">Redner.</a> While all of these tools had their use cases, I ended with SvelteKit + Typescript for my front-end, Python w/ Django for my back-end, and CockroachDB for my database. I plan to deploy on Render (That's still to be determined though.) Was fun and frustrating during this trial but my reasoning behind my choice is pretty sound in my opinion.</p>
<h2 id="heading-sticky-stacking">Sticky Stacking</h2>
<p>When I first started this project, I went in with the idea of 100% using a serverless database, whether it was Relational or Non-Relational. So out of the available options, I tried out CockroachDB and Fauna DB.</p>
<h4 id="heading-svelte-sorcerer">Svelte Sorcerer</h4>
<p>Out of all of the frontend frameworks out there these days, Svelte won me over for its easy learning curve and simplicity. I compared mainly Vue and Svelte, but Solid.js and Qwik were also considered. I didn't even consider Recact cause I have personal issues with that framework and is just a "me" problem 😂. The winner though, was Svelete for many reasons:</p>
<ol>
<li><p><strong>Performance</strong>: Svelte's compiler approach ensures that the final bundle size is smaller and the runtime performance is faster. For an expense-tracking app, which likely involves numerous interactive elements and frequent updates, Svelte’s efficient update mechanism can make the UX much smoother for the customers, aka myself and my lady.</p>
</li>
<li><p><strong>Ease of Development</strong>: Svelte's syntax is simple and intuitive, and has less boilerplate code (especially related to react). This should make the development process faster and more straightforward, especially since this project shouldn't call for highly complex functionalities.</p>
</li>
<li><p><strong>State Management</strong>: Ahh yes, state management. Svelte’s reactive state management is straightforward and built-in, eliminating the need for additional state management libraries. This is a godsend for me since managing state was always difficult (*cough* <strong><em>react</em></strong> *cough*). This simplifies handling user inputs, server responses, and UI updates, which are core aspects of an expense-tracking application.</p>
</li>
<li><p><strong>Learning Curve and Productivity</strong>: I've been reading into Svelete for some time now but just recently using it in a project (*<em>new portfolio below*</em>), and its learning curve isn't as steep as other frameworks. I was scaffolded and going pretty quickly with a skeleton project. The clear and concise codebase can also be easier to maintain and scale.</p>
</li>
<li><p><strong>SvelteKit for Full-Stack Development</strong>: If the expense tracking app requires both frontend and backend development, SvelteKit offers an all-in-one solution, streamlining the development process. It provides features like server-side rendering, static site generation, and file-based routing, which can be very beneficial.</p>
</li>
</ol>
<h4 id="heading-django-dogg">Django Dogg 🐕</h4>
<p>Regarding the backend framework, Django won the battle here against Flask. I experimented a bit with both and my decision was pretty back and forth, but all in all, Django fits this project's needs the most. Some of the key features for choosing Django:</p>
<ol>
<li><p><strong>Structured and Feature-Rich</strong>: Django's "batteries-included" approach provides a lot of built-in functionalities that I learned are beneficial for this app. The admin interface (best feature in my opinion), user authentication, and ORM are particularly useful. The admin interface can be quickly customized to manage expenses, categories, and user accounts, which would be a significant part of an expense-tracking app.</p>
</li>
<li><p><strong>ORM and Database Management</strong>: Django's ORM is powerful and simplifies interactions with the database. For an expense tracking app, where you'll be dealing with a lot of CRUD operations, Django's ORM can make these tasks more manageable and efficient.</p>
</li>
<li><p><strong>Security</strong>: Django totes its robust security and helps avoid common mistakes like SQL injection, cross-site scripting, cross-site request forgery, etc. For an app dealing with personal financial data, security is essential.</p>
</li>
<li><p><strong>Scalability</strong>: While Flask is also scalable, Django's structure and components make it easier to scale for larger applications. If the expense tracking app grows in complexity or user base, Django can handle this growth more readily.</p>
</li>
<li><p><strong>Community and Ecosystem</strong>: Django has a large community and extensive documentation, which makes finding solutions to problems easier. There are also a bunch of dope packages to extend Django, adding functionality and/or simplifying development.</p>
</li>
</ol>
<h4 id="heading-durable-like-a-cockroach">Durable like a Cockroach 🪳</h4>
<p>Choosing between these two was my biggest decision and ultimately came down to which would be easier to set up and what worked better with my backend choice. After playing with Fauna first, I chose CockroachDB because I was planning on using Django as my backend framework and I was familiar with SQL. On the other hand, I am very familiar with JSON and work with it a good amount at my company, so Fauna's JSON-like document model was pretty attractive. The issue I found with Fauna though, is that there's not a lot of support out there in terms of resources and community, making using this for a large project a difficult task. When trying out CockroachDB, the learning curve, to me, seems to be a bit steeper but they have <strong><em>much</em></strong> more learning material. They also have more organized and richer documentation with a load of resources in what they call "Cockroach University". These resources include quick projects, webinars, and free courses that offer certificates upon completion. This makes CockroachDB an easy winner for this particular project, but I still plan to build something with Fauna at some point this year. Here are the main reasons for CockraochDB summed up</p>
<ol>
<li><p><strong>Strong Consistency</strong>: For a financial application, data consistency is crucial. CockroachDB's strong consistency model ensures that all transactions are accurate and reliable, which is paramount in handling financial data where errors or inconsistencies can have serious consequences.</p>
</li>
<li><p><strong>Distributed SQL Database</strong>: CockroachDB's architecture is inherently distributed, making it highly resilient and suitable for applications that require high availability and fault tolerance. It's said to be especially beneficial if the app needs to scale or maintain high uptime. FaunaDB offers a distributed serverless platform as well so this was kind of a tie.</p>
</li>
<li><p><strong>Transactional Data Integrity</strong>: CockroachDB supports full ACID transactions, which is important for an application dealing with financial records. Ensuring the integrity of each transaction (like adding or modifying expense entries) is essential, and CockroachDB's focus on transactional integrity aligns well with this requirement.</p>
</li>
<li><p><strong>SQL Interface</strong>: The use of standard SQL in CockroachDB can be a significant advantage. SQL is an OG in the database game, making it easier to find developers familiar with it or to work within a team that already has SQL knowledge. This could lead to a smoother development process and easier maintenance. I know we use MySQL at my company so having access to public AND company resources are nice to have for learning and troubleshooting.</p>
</li>
<li><p><strong>Scalability and Geo-Distribution</strong>: CockroachDB's scalability and support for geo-distributed clusters can be advantageous if the app grows in user base or if there's a need to provide low-latency access to users in different geographical locations.</p>
</li>
</ol>
<h4 id="heading-diving-in-off-the-docker-no-bun-intended">Diving in off the Dock(er), no <code>Bun</code> intended...😏 🥁</h4>
<p>I started out wanting to containerize this application while using Bun to serve up my app. I was put in my place immediately 😅. The complexity of getting Bun set up with Docker is not well documented whatsoever and the few example projects I saw were a bit too basic for me to apply what they were showing to this project. So my idea is to just stick with using node and (possibly) implement bun after launching the project as an enhancement. I understand Docker enough to get around some troubleshooting for my work at Cars Commerce, but I'm no expert whatsoever. So I'm banking on this project to bring along some good lessons (and plenty of challenges).</p>
<h2 id="heading-project-concept"><strong>Project Concept</strong></h2>
<p><strong>Objective</strong>: My journey with the "Personal Finance Tracker" is not just about developing a project; it's about crafting a personal financial assistant. This tool, grounded in advanced analytics, aims to provide precise financial health forecasts. My goal is to empower not only myself but potentially others as well, enabling more informed decisions about finances.</p>
<p><strong>Project Significance</strong>: More than a mere technical endeavor, this project is a deeply personal endeavor aimed at demystifying financial management for a broader audience, ranging from lower-income communities to students, and working professionals. Through predictive analytics, my ambition is to boost financial literacy, guiding users to navigate through financial challenges and plan proactively for their future.</p>
<h3 id="heading-understanding-the-scope"><strong>Understanding the Scope</strong></h3>
<p><strong>Key Features – From Concepts to Reality</strong></p>
<ol>
<li><p><strong>Tracking Income and Expenses</strong>: A core feature where users can log and monitor their finances for a crystal-clear view of their financial status.</p>
</li>
<li><p><strong>Categorizing Financial Transactions</strong>: An automatic system to categorize transactions – making it simpler to understand spending habits and pinpoint areas for improvement.</p>
</li>
<li><p><strong>Predictive Analysis (<em>extra credit</em>)</strong>: Using historical data to forecast future expenses and savings, aiding in strategic budget planning and financial decision-making.</p>
</li>
</ol>
<p><strong>Intended Users</strong>: From budget-conscious individuals and students to long-term investment planning professionals – this tool is designed for anyone looking to upgrade their financial management skills.</p>
<p><strong>Use Cases</strong>: Whether it's regular budget tracking, setting financial goals, forecasting expenses, or identifying savings opportunities, this tool aims to cover it all.</p>
<h3 id="heading-technical-journey-and-choices"><strong>Technical Journey and Choices</strong></h3>
<p><strong>Technology Stack Decisions</strong></p>
<ol>
<li><p><strong>Frontend</strong>: After exploring various options, I've landed on SvelteKit with TypeScript and Vite. Their modern architecture and fast rendering capabilities promise a seamless user experience.</p>
</li>
<li><p><strong>Backend</strong>: I chose Python with Django over Flask for its flexibility and simplicity – crucial for future customizations.</p>
</li>
<li><p><strong>Database</strong>: CockroachDB won over Fauna for its strong consistency, transactional data integrity, and rich documentation for newcomers.</p>
</li>
</ol>
<p><strong>Extra Tools</strong>: I plan to Dockerize my app. Bun was considered, but it's currently on the "extra credit" list, possibly integrated at the project's end as an enhancement.</p>
<p><strong>Rationale Behind Choices</strong>: Each piece of the tech stack was selected for its strength and industry relevance. SvelteKit for frontend efficiency, Python and Flask for a flexible backend, and FaunaDB for robust serverless data management – a combination that I believe will address the needs of this project effectively.</p>
<h3 id="heading-project-timeline-and-milestones"><strong>Project Timeline and Milestones</strong></h3>
<p><strong>Rough Timeline</strong></p>
<ul>
<li><p><strong>Phase 1</strong>: Conceptualization and Planning (Month 1)</p>
</li>
<li><p><strong>Phase 2</strong>: Development of Core Features (Months 2-4)</p>
</li>
<li><p><strong>Phase 3</strong>: Integration and Testing (Month 5)</p>
</li>
<li><p><strong>Phase 4</strong>: Launch and Feedback Incorporation (Month 6)</p>
</li>
<li><p><strong>Note</strong>: Aiming for completion within a year, considering personal and professional commitments. Timeline adjustments are possible.</p>
</li>
</ul>
<p><strong>Key Milestones</strong></p>
<ul>
<li><p><strong>M1</strong>: Project Blueprint and Requirement Analysis (End of Month 1)</p>
</li>
<li><p><strong>M2</strong>: Alpha Version with Basic Features (End of Month 3)</p>
</li>
<li><p><strong>M3</strong>: Beta Version <em>+Predictive Analysis</em> (End of Month 5)</p>
</li>
<li><p><strong>M4</strong>: App Launch (End of Month 6)</p>
</li>
</ul>
<p><strong>New Dev Portfolio &amp; Anticipating the Journey Ahead</strong></p>
<p>I'm pumped again as I venture deeper into this project, eager to immerse myself in the technologies I've chosen and tackle their challenges. Over the last couple of weeks, I took a long and much-needed vacation. During that time I built a new dev portfolio and added it to a domain I picked up last year with a vision of what I'm trying to offer and contribute to the space of development: <a target="_blank" href="https://digitaldopamine.dev/">Digital Dopamine</a>.</p>
<p>This blog post marks the beginning of a new series dedicated to this project, where I'll be sharing detailed plans and technical insights as the project unfolds. Alongside, I'll continue my journey with the 100-days-of-python challenge. While it might seem monotonous at times, I told myself I was going to complete it and I'm committed to seeing it through (no matter how much interest I lost in it 😅).</p>
<p>For those interested in following the project's progress, feel free to ⭐️ and 👀 the repository here: <a target="_blank" href="https://github.com/kdleonard93/Leo_Ledger">Leo_Ledger on GitHub</a>. Let's get to work!</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-writing-or-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my writing or want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://twitter.com/RingoMandingo93"><strong>𝕏 Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Twitter....I Mean X Bot Roadblock]]></title><description><![CDATA[Getting this started
After the Email Sender project, I scouted again to see what would be an excellent project but a bit more challenging and more practical for day-to-day use (I don't plan to actually use the auto email sender....at least not yet 😁...]]></description><link>https://blacknerd.dev/twitter-bot-roadblock</link><guid isPermaLink="true">https://blacknerd.dev/twitter-bot-roadblock</guid><category><![CDATA[Python]]></category><category><![CDATA[#TwitterAPI ]]></category><category><![CDATA[100DaysOfCode]]></category><category><![CDATA[PythonBots]]></category><category><![CDATA[Tweepy]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Wed, 11 Oct 2023 02:52:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1696457499406/c3e7e219-0d27-47a2-9607-527084394c9b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-getting-this-started">Getting this started</h2>
<p>After the <code>Email Sender</code> project, I scouted again to see what would be an excellent project but a bit more challenging and more practical for day-to-day use (I don't plan to actually use the auto email sender....at least not yet 😁). So after discussing things I could do that would benefit both of us, we figured that a Twitter bot would help us both. She'd have a tool to automate her Twitter posts of her plants and I'd get some practice building out Python bots. It seemed easy enough to get all of the details:<br /><strong>- Set up a Twitter Developer Account</strong><br /><strong>- Code the Bot</strong><br /><strong>- Spice up the bot</strong><br /><strong>- Deploy the Bot</strong></p>
<p>Tweepy is the popular library of choice for working with Twitter's API and their documentation is understandable. For the sake of clarity, I'm going to reference X as Twitter (like the good lord intended 😂)</p>
<h2 id="heading-api-permissions-are-the-battle">API Permissions are the battle</h2>
<p>The main issue I'm having with this bot creation is with API Permissions. Tweepy's docs help a good amount but I'm not sure what I'm missing between their doc (<a target="_blank" href="https://docs.tweepy.org/en/stable/authentication.html#twitter-api-v2">https://docs.tweepy.org/en/stable/authentication.html#twitter-api-v2</a>) and Twitter's (<a target="_blank" href="https://developer.twitter.com/en/docs/twitter-api/tweets/manage-tweets/api-reference/post-tweets">https://developer.twitter.com/en/docs/twitter-api/tweets/manage-tweets/api-reference/post-tweets</a>). My permissions for the Twitter account are for API V2 and restricted V1 access. However, no matter what I did to change the code to get a post to work, I kept getting an error about API permissions.</p>
<p><img src="https://media2.giphy.com/media/ISOckXUybVfQ4/giphy.gif?cid=ecf05e47gdg0iqx3gkzh6lbkqed7wisr043ef8e4t0monzw8&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g" alt="SpongeBob gif. While it rains outside, SpongeBob sits alone at a booth in the Krusty Krab, staring blankly at a steaming mug." class="image--center mx-auto" /></p>
<p>I tried to see if I could access the few API v1 endpoints I had access to but that didn't work either. My working repo is here -&gt; <a target="_blank" href="https://github.com/kdleonard93/Twitter_bot">https://github.com/kdleonard93/Twitter_bot</a> and you can see how I currently have it set up (if anyone knows what I'm doing wrong let me know 🙏🏾). Most attempts were to the POST endpoint but I tried to GET a list of recent tweets as well as read my follower count but no luck there either. The closest I got was where I had a setup that prompted me to navigate to the URL provided to get a code to authenticate, but once I go to that URL (Which has my ID, redirect URL, and Username data in it) I get an error. <a target="_blank" href="https://loom.com/i/96fe0e1cb1b740ccbe5c4f9437c60cc5">https://loom.com/i/96fe0e1cb1b740ccbe5c4f9437c60cc5</a></p>
<p>After coming back to this a handful of times with a "fresh mind" but no progress with this issue, I've decided to pause it and see if any of my peers might know what I'm missing, instead of frustrating myself day in and day out. Considering that my lady still needs to create the content that I'd be posting (listing images, deals, etc.) I can take a breather and work on something else while I try to source some guidance. That led me to move on to Day 24 in my 100-Days-Of-Python course (yes....I'm still planning on finishing all 100 days 😄) as well as pick a new project that is more useful for now while I figure out the Twitter bot and wait for content to post. The new project is a "Personal Finance Tracker with Predictive Analysis". The predictive analysis portion of the project is extra credit since it will involve ML which I have never dove into. This might also sound more difficult than the last project, and I'm sure it is 😅, but it's something fresh that I need.</p>
<h2 id="heading-day-24-files-directories-and-paths">Day 24 - Files, Directories, and Paths</h2>
<p>This day came pretty easy. It's pretty much just an overview of directories and how to access the files within through relative and absolute paths. Reading and Writing files are easy peasy as well utilizing one main method, <code>open()</code>. <code>open()</code> returns a file object, and is most commonly used with two positional arguments and one keyword argument: <code>open(filename, mode, encoding=None).</code> The first argument is a string that will be the file name or the path to the file you're trying to read. The <code>mode</code> argument is to determine which way the file is to be used. You can read <code>mode="r"</code>, write <code>mode="w"</code>, and append <code>mode="a"</code> for most people's needs. There are a couple more, like <code>r+</code> for reading and writing and <code>b</code> for opening the file in binary mode.</p>
<p>A quick example of this would be:</p>
<pre><code class="lang-python">open(<span class="hljs-string">'letter.txt'</span>, <span class="hljs-string">'r'</span>)
<span class="hljs-keyword">or</span>
open(<span class="hljs-string">'letter.txt'</span>, mode=<span class="hljs-string">'r'</span>)
</code></pre>
<p>Personally, I prefer <code>open('letter.txt', mode='r')</code> so whoever reads the code, is provided more clarity on what that is doing, but to each their own. This leads us to a nice lil' trick for Python to ensure the file closes after the update is made. We do this by using <code>with</code> and <code>as</code> like so:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Example 1</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">'letter.txt'</span>, mode=<span class="hljs-string">'w'</span>) <span class="hljs-keyword">as</span> new_letter:
    <span class="hljs-string">"""The text below will update whatever text is on letter.txt"""</span>
    new_letter.write(<span class="hljs-string">"New letter, who dis?"</span>)

<span class="hljs-comment"># Example 2</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">'letter.txt'</span>, mode=<span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> new_letter:
    <span class="hljs-string">"""The call below will read letter.txt and store the data in `contents`."""</span>
    contents = read_letter.read()
    print(contents)

<span class="hljs-comment"># Example 3</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">'letter.txt'</span>, mode=<span class="hljs-string">'a'</span>) <span class="hljs-keyword">as</span> new_letter:
    <span class="hljs-string">"""The call below will append the added text to letter.txt at the end of the file."""</span>
    new_letter.write(<span class="hljs-string">"It's ya boi, that's who?"</span>)
</code></pre>
<p>There's more to learn about "Input and Output" though and I recommend taking a peek at the docs -&gt; <a target="_blank" href="https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files">https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files</a>.</p>
<h2 id="heading-the-project-mail-merger-project">The Project - Mail Merger Project</h2>
<p>The project for day 24 was not as hefty as some of the more recent days, which I appreciate very much ( ͡⏿ ͜ʖ ͡⏿). The goal was to take a list of names from a given doc and create email drafts for each person. The content was the same but the names needed to be swapped out for each new file and labeled as such in the file name and body copy. Outside of using a for loop, this project consisted of only what we learned on day 24 and the code is brief:</p>
<pre><code class="lang-python">PLACEHOLDER = <span class="hljs-string">"[name]"</span>

<span class="hljs-keyword">with</span> open(<span class="hljs-string">"/Users/kyleleonard/100-Days-Of-Code_Python/day-24/Input/Names/invited_names.txt"</span>) <span class="hljs-keyword">as</span> names_file:
   names = names_file.readlines()
   print(names)

<span class="hljs-keyword">with</span> open(<span class="hljs-string">"/Users/kyleleonard/100-Days-Of-Code_Python/day-24/Input/Letters/starting_letter.txt"</span>) <span class="hljs-keyword">as</span> letter_file:
    letter_content = letter_file.read()
    <span class="hljs-keyword">for</span> name <span class="hljs-keyword">in</span> names:
        stripped_name = name.strip()
        new_letter = letter_content.replace(PLACEHOLDER, stripped_name)
        <span class="hljs-keyword">with</span> open(<span class="hljs-string">f"/Users/kyleleonard/100-Days-Of-Code_Python/day-24/Output/ReadyToSend/letter_for_<span class="hljs-subst">{stripped_name}</span>"</span>, mode=<span class="hljs-string">"w"</span>) <span class="hljs-keyword">as</span> completed_letter:
            completed_letter.write(new_letter)
</code></pre>
<p>That's it! Now the files created from running this are listed in the repo and you can check it all out here -&gt; <a target="_blank" href="https://github.com/kdleonard93/100-Days-Of-Code_Python/tree/main/day-24">https://github.com/kdleonard93/100-Days-Of-Code_Python/tree/main/day-24</a>.</p>
<h2 id="heading-eod">EOD</h2>
<p>My main goal now is to get started on that other project I mentioned while also tackling days 25-30. I'm starting to wind down with all the weddings vacations, birthdays, and everything in between, and am VERY happy about it. I never realized how exhausting travel could be when you're doing it in succession for a few months. Nonetheless, there are a couple of new things out in the dev world that have been released over the last month:</p>
<ul>
<li><p><a target="_blank" href="https://bun.sh/blog/bun-v1.0.4">Bun - A JS runtime &amp; toolkit</a> (Stable version)</p>
</li>
<li><p><a target="_blank" href="https://www.modular.com/mojo">Mojo - A superset of Python built for AI</a> (No stable version yet but an SDK is available)</p>
</li>
</ul>
<p>These 2 intrigue me the most and I plan to use Bun as my runtime for my new project. I know this will inevitably cause me a headache with the impending wall of errors I plan to face. It should be good for me though, forcing me to get acquainted with Bun and maybe even trying to contribute to it or other open-source tools I use. Fingers crossed 🤞🏾 and until next time, ✌🏾.</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-writing-or-just-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my writing or just want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://twitter.com/RingoMandingo93">𝕏 Twitter</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Automated Email Sender - Finalized Project]]></title><description><![CDATA[It's all done!
What's up, folks! I wrapped up this project last night and early this AM and it's safe to say it was a pretty decent challenge along the way. When picking a "beginner" Python project to work on, I didn't expect it to last as long as th...]]></description><link>https://blacknerd.dev/automated-email-sender-finalized-project</link><guid isPermaLink="true">https://blacknerd.dev/automated-email-sender-finalized-project</guid><category><![CDATA[Python]]></category><category><![CDATA[Security]]></category><category><![CDATA[backend]]></category><category><![CDATA[Object Oriented Programming]]></category><category><![CDATA[automation]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Thu, 24 Aug 2023 16:03:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1692892620784/072363d6-e949-4e70-89b1-d4c6482d52be.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-its-all-done">It's all done!</h2>
<p>What's up, folks! I wrapped up this project last night and early this AM and it's safe to say it was a pretty decent challenge along the way. When picking a "beginner" Python project to work on, I didn't expect it to last as long as this Email Sender did, and I definitely didn't expect the amount of work that actually had to go into getting this project to work successfully. Throughout the course, I did my best to follow the best practices when it comes to commits and PRs (Like I've been doing my <code>100 Days of Python Code</code> challenge *still ongoing 😅*) but will admit some commits were pushed directly through the master. I also created a proper README for anyone who wants to pull the project for themselves and use it! It's fully functional and you can modify it accordingly as you please 😁. The full project repo can be found here -&gt; <a target="_blank" href="https://github.com/kdleonard93/automated_email_sender">https://github.com/kdleonard93/automated_email_sender</a>.</p>
<h2 id="heading-reflection">Reflection</h2>
<p>What a ride this project has been! 🚀 Remember Day 1? Me neither because it was a blur of setting up Python environments and sorting out initial roadblocks like version control (yeah, Git, I'm looking at you 😅). But once the basics were nailed, the ball started rolling.</p>
<p>One of the big wins was getting scheduling right on Day 6. Man, figuring out how to automatically send emails based on a CSV file was very helpful and a challenge in itself. But now, If you've got a list of people to email and specific times to do it, just fire and forget! Gotta love automation.</p>
<p>I also took the security aspect seriously, keeping passwords and other sensitive stuff out of the GitHub repo by using <code>.env</code> and <code>.gitignore</code>. Can't trust the internet, ya know 😂?</p>
<pre><code class="lang-python"><span class="hljs-comment"># .env example</span>
EMAIL_PASSWORD=YourEmailPassword
SENDER_EMAIL=YourSenderEmail
SMTP_SERVER=YourSMTPServer
SMTP_PORT=YourSMTPPort
</code></pre>
<pre><code class="lang-python"><span class="hljs-comment"># .gitignore example</span>
.env
email_data.csv
email_log.csv 
venv/
</code></pre>
<p>Now, you might be wondering about stress testing. To be honest, I haven't dived into it yet, but it's on my radar for sure. I plan to tweak and refine this project over time, and that’s when I'll throw the stress tests into the mix.</p>
<p>All in all, it's been a whirlwind of code, tea, and a smidge of destressing 😄. Can't wait to see where this project takes me next, and what new skills I'll add to the ol' toolkit next. 🛠️</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-writing-or-just-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my writing or just want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://twitter.com/RingoMandingo93">𝗫 Twitter</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Automated Email Sender Project Check-In]]></title><description><![CDATA[Automation Complication
I know, I know, I said "This Project should only take me a couple of weeks".....well, here I am 4 weeks later and only halfway through lol. Went to Canada for a bachelor party and have been finishing up house projects, so lots...]]></description><link>https://blacknerd.dev/automated-email-sender-project-check-in</link><guid isPermaLink="true">https://blacknerd.dev/automated-email-sender-project-check-in</guid><category><![CDATA[EmailSenderProject]]></category><category><![CDATA[Python]]></category><category><![CDATA[backend]]></category><category><![CDATA[automation]]></category><category><![CDATA[Blogging]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Thu, 17 Aug 2023 02:34:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1692235826052/ed724522-9ad0-4ed0-8d61-88501921d825.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-automation-complication">Automation Complication</h2>
<p>I know, I know, I said "This Project should only take me a couple of weeks".....well, here I am 4 weeks later and only halfway through lol. Went to Canada for a bachelor party and have been finishing up house projects, so lots of moving parts recently. Add the aspect of needing to learn more libraries ad you have a major delay in getting this completed 😅.</p>
<p>The meat of the project is done though! You can go over to my repo here -&gt; <a target="_blank" href="https://github.com/kdleonard93/automated_email_sender">https://github.com/kdleonard93/automated_email_sender</a> and check in on the commits made since I started this project. Feel free to pull it down and give it a try yourself. Please note though, if you wanna try it out, you'll need to make a few adjustments:</p>
<ol>
<li><p>Update the <code>sender_email</code> argument value in the <code>send_email</code> function to your own (you'll need to use Gmail). Change the recipient's email as well. (Dont you dare use mine lol)</p>
</li>
<li><p>Make sure you set your password in an environment variable since we are storing sensitive data (aka: password). You can do this in your terminal by running <code>export EMAIL_PASSWORD=your_password_here</code> .</p>
</li>
<li><p>Lastly, confirm all of the needed libraries are installed on your local.</p>
</li>
</ol>
<p>Once you have the following checks and this repo pulled, run <code>python main.py</code> and you should be set!<br />As I was writing this, I realized the amount of other sensitive info I had in the project (emails, env variables, etc.) So I made some code changes already to improve the project 😄.</p>
<p>For those who wanna see a quick snippet of the <code>email_sender.py</code> file, you can take a peek below:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> smtplib
<span class="hljs-keyword">import</span> ssl
<span class="hljs-keyword">import</span> csv
<span class="hljs-keyword">from</span> email.mime.text <span class="hljs-keyword">import</span> MIMEText
<span class="hljs-keyword">from</span> email.mime.multipart <span class="hljs-keyword">import</span> MIMEMultipart
<span class="hljs-keyword">import</span> schedule
<span class="hljs-keyword">import</span> time
<span class="hljs-keyword">from</span> os <span class="hljs-keyword">import</span> environ
<span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv

load_dotenv()  <span class="hljs-comment"># This will load environment variables from a .env file.</span>

PASSWORD = environ.get(<span class="hljs-string">"EMAIL_PASSWORD"</span>)
SENDER_EMAIL = environ.get(<span class="hljs-string">"SENDER_EMAIL"</span>)
SMTP_SERVER = environ.get(<span class="hljs-string">"SMTP_SERVER"</span>)  <span class="hljs-comment"># Set up the SMTP server and port</span>
SMTP_PORT = int(environ.get(<span class="hljs-string">"SMTP_PORT"</span>, <span class="hljs-number">465</span>))  <span class="hljs-comment"># For SSL</span>

<span class="hljs-comment"># Check if necessary environment variables are set</span>
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> (PASSWORD <span class="hljs-keyword">and</span> SENDER_EMAIL <span class="hljs-keyword">and</span> SMTP_SERVER):
    <span class="hljs-keyword">raise</span> EnvironmentError(<span class="hljs-string">"Required environment variables are missing!"</span>)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">read_email_data</span>(<span class="hljs-params">filename=<span class="hljs-string">"email_data.csv"</span></span>):</span>
    email_data = []
    <span class="hljs-keyword">with</span> open(filename, <span class="hljs-string">"r"</span>) <span class="hljs-keyword">as</span> file:
        reader = csv.DictReader(file)
        <span class="hljs-keyword">for</span> row <span class="hljs-keyword">in</span> reader:
            email_data.append(row)
    <span class="hljs-keyword">return</span> email_data


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">send_email</span>(<span class="hljs-params">email_data, sender_email=SENDER_EMAIL</span>):</span>
    <span class="hljs-comment"># Create email headers and body</span>
    message = MIMEMultipart()
    message[<span class="hljs-string">"From"</span>] = sender_email
    message[<span class="hljs-string">"To"</span>] = email_data[<span class="hljs-string">"recipient_email"</span>]
    message[<span class="hljs-string">"Subject"</span>] = email_data[<span class="hljs-string">"subject"</span>]
    message.attach(MIMEText(email_data[<span class="hljs-string">"body"</span>], <span class="hljs-string">"plain"</span>))

    <span class="hljs-comment"># Create a secure SSL context</span>
    context = ssl.create_default_context()

    <span class="hljs-comment"># Try to log in to the server and send the email</span>
    <span class="hljs-keyword">try</span>:
        server = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=context)
        server.login(sender_email, PASSWORD)
        server.sendmail(
            sender_email, email_data[<span class="hljs-string">"recipient_email"</span>], message.as_string())
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        print(<span class="hljs-string">f"An error occurred while sending the email: <span class="hljs-subst">{e}</span>"</span>)
    <span class="hljs-keyword">finally</span>:
        server.quit()


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">send_multi_emails</span>(<span class="hljs-params">filename=<span class="hljs-string">"email_data.csv"</span></span>):</span>
    email_data_list = read_email_data(filename)
    <span class="hljs-keyword">for</span> data <span class="hljs-keyword">in</span> email_data_list:
        send_email(email_data=data)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">job</span>():</span>
    <span class="hljs-string">"""Define the job to be executed at a scheduled time."""</span>
    send_multi_emails()


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">email_schedule</span>(<span class="hljs-params">interval</span>):</span>
    <span class="hljs-string">"""Schedules the email sending job."""</span>
    schedule.every(interval).minutes.do(job)
    <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
        schedule.run_pending()
        time.sleep(<span class="hljs-number">1</span>)
</code></pre>
<p>One thing I will admit to is that the automation for this had me stuck for a little. Learning a lot about security practices with this one as well and will need it for future projects so I've been appreciating the challenge.</p>
<h2 id="heading-eod">EOD</h2>
<p>Wish I was able to get a check-in written sooner but, you know.....life. The last few things I need to do is create a log for the jobs, do some final testing after that, and then reflect and write my final thoughts on this project! See yall then!</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-progress-or-just-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my progress or just want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://twitter.com/RingoMandingo93"><strong>🐦 Twitter</strong></a> <strong>(Not calling it X yet...doesn't roll off the tongue lol)</strong></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Practice??? We're Talking About Practice, Man.]]></title><description><![CDATA[Intro to the Switch Up
Hey folks, been a little over a couple of weeks since I posted but I had a big move out of the city that I finished up last week and we just got done unpacking and setting up most of our home. Lots of went into refurbishing, an...]]></description><link>https://blacknerd.dev/practice-were-talking-about-practice-man</link><guid isPermaLink="true">https://blacknerd.dev/practice-were-talking-about-practice-man</guid><category><![CDATA[pythonautomation]]></category><category><![CDATA[EmailSenderProject]]></category><category><![CDATA[BlackNerdsJourney]]></category><category><![CDATA[PythonCodeWarrior]]></category><category><![CDATA[TechDIYProjects]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Tue, 11 Jul 2023 14:35:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1689083385806/fb9f0be3-4762-41e9-af8a-58f4cf7aa413.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-intro-to-the-switch-up">Intro to the Switch Up</h2>
<p>Hey folks, been a little over a couple of weeks since I posted but I had a big move out of the city that I finished up last week and we just got done unpacking and setting up most of our home. Lots of went into refurbishing, and my girlfriend and I enjoyed tackling these DIY projects sprinkled around the house. My workspace is shaping up well, and I didn't anticipate how personalizing my office could enhance focus while working remotely, reducing feelings of isolation or the desire to escape to coffee shops for a change of scenery (previously I had old roommates and lacked a dedicated home office, making do with my bedroom and living room.)</p>
<p>With being said, I had the urge to switch up my next couple of weeks to continue practicing what I've learned so far in Python, as mentioned in my last post. Also, I was getting kinda sick of working with the turtle module for EVRY SINGLE PROJECT 😅. While it's excellent for kickstarting the learning process, particularly with creating some classic games, it soon became monotonously familiar. So rather than merely tackling a handful of CodeWars challenges a few days a week and bouncing to the next course day, I intend to intersperse some standalone projects to further solidify these concepts. I genuinely need to master the nuances of OOP to enhance my skill in refining and updating existing code, all to progress in my present job and take on tougher tickets. I'm banking on these projects to secure that knowledge. For me, repetition is key; I've been working on boosting my cognitive health for some time to help improve retention.</p>
<h2 id="heading-the-project">The Project</h2>
<p>The project that I'm going to work on over the next couple of weeks is an <strong><em>Automated Email Sender</em></strong>. I researched beginner-friendly projects that involve automation, in an attempt to gradually advance towards more complex projects like chatbots and trading bots, and then eventually, NLP. I actually made one crypto trading bot not too long ago but so far, it carries out my orders with a "taker's fee" and does so at the prevailing market price instead of the limit price I've set with the "maker fee" only (maker fees are generally less than taker fees).<br />I'll refrain from veering into a discourse about trading bots and the like, so back to the topic at hand 😄. After researching this project I noted some of the common libraries used as well as a game plan on how to tackle each day. Here's an outline of the project and resources required to hit the ground running:</p>
<p><strong>MVP (Minimum Viable Product)</strong>:</p>
<ol>
<li><p><strong>Send an Email</strong>: The program should be able to send an email to any address.</p>
</li>
<li><p><strong>Customize the Subject and Body</strong>: The user should be able to input the subject and body of the email.</p>
</li>
<li><p><strong>Send Multiple Emails</strong>: The program should be able to send multiple emails, either to the same address or to different addresses.</p>
</li>
<li><p><strong>Schedule Emails</strong>: The user should be able to schedule emails to be sent at specific times.</p>
</li>
<li><p><strong>Log Sent Emails</strong>: The program should keep a log of all sent emails, including the date and time, recipient, subject, and body.</p>
</li>
</ol>
<p><strong>Libraries Needed</strong>:</p>
<ul>
<li><p><strong>smtplib</strong>: This is the built-in Python library for sending emails using the Simple Mail Transfer Protocol (SMTP).</p>
</li>
<li><p><strong>ssl</strong>: Another built-in Python library. I'll use it to create a secure connection with my mail server.</p>
</li>
<li><p><strong>getpass</strong>: This built-in library is used for secure password handling.</p>
</li>
<li><p><strong>schedule</strong>: This is an external library that I can use to schedule emails to be sent at specific times.</p>
</li>
<li><p><strong>pandas</strong>: I will use this library to create and manage a DataFrame that will serve as an email log.</p>
</li>
</ul>
<p><strong>Optional Libraries</strong>:</p>
<ul>
<li><p><strong>os</strong>: If you want to make your log persistent across runs (i.e., save it to a file), you'll need this library to interact with your operating system's file system.</p>
</li>
<li><p><strong>datetime</strong>: For working with dates and times, useful for scheduling and logging.</p>
</li>
</ul>
<p><strong>Estimated Timeline</strong>:</p>
<ul>
<li><p><strong>Days 1-2</strong>: Research the needed libraries and setup the project. Draft the program by defining the main functions and how they'll interact.</p>
</li>
<li><p><strong>Days 3-4</strong>: Write the function to send a single email. Test this function to make sure it works correctly.</p>
</li>
<li><p><strong>Day 5</strong>: Write the function to send multiple emails. More tests.</p>
</li>
<li><p><strong>Day 6</strong>: Add scheduling capabilities to your program. You guessed it, +Testing.</p>
</li>
<li><p><strong>Day 7</strong>: Create the email log. "Testing testing, 1-2-3".</p>
</li>
<li><p><strong>Day 8:</strong> Do a final round of testing and debugging on the project as a whole to make sure all parts of the program work together correctly.</p>
</li>
<li><p><strong>Day 9</strong>: Polish project; Clean up code, add comments, write documentation, and anything else needed to wrap up the project.</p>
</li>
<li><p><strong>Day 10</strong>: Reflect on the project, and plan your next steps.</p>
</li>
</ul>
<p>The project timeline is predicated on full-time work, so since this is a part-time endeavor, it'll take me roughly 2 weeks, as previously mentioned. I've initiated the repo and you can follow it on GitHub here -&gt; -&gt; <a target="_blank" href="https://github.com/kdleonard93/automated_email_sender">https://github.com/kdleonard93/automated_email_sender</a></p>
<h2 id="heading-eod">EOD</h2>
<p>How I intend to document all of this is still under consideration. I might opt to write a midway and final report OR perhaps just a wrap-up post at the end. Likely the former, and maybe even a third if I stumble upon an exciting add-on to this project. But that brings today's post to a close, and I'm excited to get this one going!</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-progress-or-just-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my progress or just want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://twitter.com/RingoMandingo93"><strong>🐦 Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Day 23: Python Turtle Crossing Game - OOP and Creativity]]></title><description><![CDATA[Hey folks! We're on to Day 23 and it's time to delve into our 2nd capstone project. If you've been following along, you'll remember our work with Classes, Class Inheritance, and creating methods within those classes. Today, we're adding a new element...]]></description><link>https://blacknerd.dev/day-23-python-turtle-crossing-game-oop-and-creativity</link><guid isPermaLink="true">https://blacknerd.dev/day-23-python-turtle-crossing-game-oop-and-creativity</guid><category><![CDATA[100DaysOfCode]]></category><category><![CDATA[Python]]></category><category><![CDATA[BlackInTech]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[TurtleCrossingGame]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Thu, 22 Jun 2023 01:19:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1687203709265/84371033-331a-4f7d-812e-4e95eb893d61.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey folks! We're on to Day 23 and it's time to delve into our 2nd capstone project. If you've been following along, you'll remember our work with Classes, Class Inheritance, and creating methods within those classes. Today, we're adding a new element to our programming toolkit: the Python turtle game engine. This project will give us an opportunity to apply what we've learned in a practical and engaging way. In my experience, building these games has been a great method for reinforcing newly learned concepts. So, let's dive in and see if we can get these turtles across the street. Here's to another day of coding 🤘🏾😄!</p>
<h2 id="heading-breaking-down-the-problem">Breaking down the Problem</h2>
<p>So we need to figure out how to exactly get the game set up and below is how we broke down the project:</p>
<ul>
<li><p>Move the turtle up with a specified keypress</p>
</li>
<li><p>Create and move cars</p>
</li>
<li><p>Detect collisions with cars and display a "GAME OVER"</p>
</li>
<li><p>Detect completion of the game and start a new level</p>
</li>
<li><p>Create a scoreboard.</p>
</li>
</ul>
<h2 id="heading-the-project">The Project</h2>
<p>Since this is a capstone project, I'm going to go into more detail about what exactly we did in each file, but I'll still only post a snippet or two, leaving the whole repo to be viewed on GitHub -&gt; <a target="_blank" href="https://github.com/kdleonard93/100-Days-Of-Code_Python/tree/day-23/day-23">https://github.com/kdleonard93/100-Days-Of-Code_Python/tree/day-23/day-23</a>.</p>
<p>With the final lines of code in place, our Turtle Crossing Game has taken shape, showcasing the capabilities of Python's Turtle module and the OOP principles I've been practicing. Let's take a closer look at what we've accomplished.</p>
<p>We start off with our <a target="_blank" href="http://main.py"><code>main.py</code></a> file, which serves as the core of our game. Here, we set up our game screen, initialize our player, car manager, and scoreboard, and keep the game running as long as our turtle dodges traffic. Below is a snippet of the code from <code>main.py</code>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> time
<span class="hljs-keyword">from</span> turtle <span class="hljs-keyword">import</span> Screen
<span class="hljs-keyword">from</span> player <span class="hljs-keyword">import</span> Player
<span class="hljs-keyword">from</span> car_manager <span class="hljs-keyword">import</span> CarManager
<span class="hljs-keyword">from</span> scoreboard <span class="hljs-keyword">import</span> Scoreboard

screen = Screen()
screen.setup(width=<span class="hljs-number">600</span>, height=<span class="hljs-number">600</span>)
screen.tracer(<span class="hljs-number">0</span>)

player = Player()
car_manager = CarManager()
scoreboard = Scoreboard()

screen.listen()
screen.onkey(player.go_up, <span class="hljs-string">"Up"</span>)

game_is_on = <span class="hljs-literal">True</span>
<span class="hljs-keyword">while</span> game_is_on:
    time.sleep(<span class="hljs-number">0.1</span>)
    screen.update()

    car_manager.create_car()
    car_manager.move_cars()

    <span class="hljs-keyword">for</span> car <span class="hljs-keyword">in</span> car_manager.all_cars:
        <span class="hljs-keyword">if</span> car.distance(player) &lt; <span class="hljs-number">20</span>:
            game_is_on = <span class="hljs-literal">False</span>
            scoreboard.game_over()

    <span class="hljs-keyword">if</span> player.is_at_finish_line():
        player.go_to_start()
        car_manager.level_up()
        scoreboard.increase_level()

screen.exitonclick()
</code></pre>
<p>As you can see from the code above, I import the necessary modules and initialize the game screen, player, car manager, and scoreboard. The script then starts listening for an 'Up' key press to make the turtle move. The game loop begins, controlled by the <code>game_is_on</code> flag, where the screen gets updated, cars are created and moved, and collision between cars and the player is checked. If a collision occurs, the game ends. If the player reaches the finish line, the player is moved back to the start, and the level of difficulty is increased. The game loop runs until a collision happens, and the game can be exited anytime by clicking on the screen.</p>
<p>Next, we delve into <code>car_manager.py</code>. We create new car objects at random intervals, color them randomly for a little excitement, and handle the movement of the cars. As the player progresses, the cars get faster, increasing the difficulty with each level. This was a bit of a challenge and you can see a snippet of this code below:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> turtle <span class="hljs-keyword">import</span> Turtle
<span class="hljs-keyword">import</span> random

COLORS = [<span class="hljs-string">"red"</span>, <span class="hljs-string">"orange"</span>, <span class="hljs-string">"yellow"</span>, <span class="hljs-string">"green"</span>, <span class="hljs-string">"blue"</span>, <span class="hljs-string">"purple"</span>]
STARTING_MOVE_DISTANCE = <span class="hljs-number">5</span>
MOVE_INCREMENT = <span class="hljs-number">10</span>


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CarManager</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        self.all_cars = []
        self.car_speed = STARTING_MOVE_DISTANCE

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_car</span>(<span class="hljs-params">self</span>):</span>
        random_chance = random.randint(<span class="hljs-number">1</span>,<span class="hljs-number">6</span>)
        <span class="hljs-keyword">if</span> random_chance == <span class="hljs-number">1</span>:
            new_car = Turtle(<span class="hljs-string">"square"</span>)
            new_car.shapesize(stretch_wid=<span class="hljs-number">1</span>, stretch_len=<span class="hljs-number">2</span>)
            new_car.penup()
            new_car.color(random.choice(COLORS))
            random_y = random.randint(<span class="hljs-number">-280</span>, <span class="hljs-number">260</span>)
            new_car.goto(<span class="hljs-number">300</span>, random_y)
            self.all_cars.append(new_car)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">move_cars</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">for</span> car <span class="hljs-keyword">in</span> self.all_cars:
            car.backward(self.car_speed)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">level_up</span>(<span class="hljs-params">self</span>):</span>
        self.car_speed += MOVE_INCREMENT
</code></pre>
<p>Then, there's the <a target="_blank" href="http://player.py"><code>player.py</code></a> file or, the turtle itself. Our turtle begins at the starting position and moves towards the finish line when the "Up" key is pressed. Upon reaching the finish line, our turtle resets to the starting position, signaling a successful level completion and displaying the current level for the player.</p>
<p>Which leads to the <a target="_blank" href="http://scoreboard.py"><code>scoreboard.py</code></a> file, which keeps track of the player's progress. It shows the current level and if the player is smashed by a careless driver, <code>GAME OVER</code> will appear in the middle of the screen.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687395777239/41616fd3-739f-4c4f-8b7c-c42f3ac52aee.gif" alt class="image--center mx-auto" /></p>
<p>Creating this game not only puts into practice what we've learned about Classes, Class Inheritance, and creating methods within those classes, but also tests our understanding of the Python turtle game engine.</p>
<p>But the beauty of coding is in its limitless possibilities, right? Angela challenged the audience to remix this game to show off some creativity and skill. I'm admittedly not going to do this extra challenge but for anyone that finds the turtle game fun should give it a shot! The turtle module is a large playground for creativity, and I've come to find there's always something new to learn and explore.</p>
<h2 id="heading-eod">EOD</h2>
<p>With that, I conclude Day 23 of our 100-day Python journey. Stay tuned for more fun projects and keep those coding gears grinding. Happy coding! 🤘🏾😄!</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-progress-or-just-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow"><strong>If you want to keep up with my progress or just want to connect as peers, check out my social links below and give me a follow!</strong></h4>
<ul>
<li><p><a target="_blank" href="https://twitter.com/RingoMandingo93"><strong>🐦 Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Day 22 - Building Pong: The Famous Arcade Game]]></title><description><![CDATA[Agenda for the Day
Hey folks! My task for today was to conjure up the classic "Pong Arcade Game", utilizing all the knowledge and skills I've garnered thus far. Angela didn't introduce any fresh concepts and today was all concentrated on the game dev...]]></description><link>https://blacknerd.dev/day-22-building-pong-the-famous-arcade-game</link><guid isPermaLink="true">https://blacknerd.dev/day-22-building-pong-the-famous-arcade-game</guid><category><![CDATA[100DaysOfCode]]></category><category><![CDATA[Python]]></category><category><![CDATA[PythonGameDevelopment]]></category><category><![CDATA[PongArcadeGame]]></category><category><![CDATA[ProgrammingChallenges]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Wed, 14 Jun 2023 05:20:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1686715708767/a808f69d-68fa-4c25-a56a-a05c32323bec.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-agenda-for-the-day"><strong>Agenda for the Day</strong></h2>
<p>Hey folks! My task for today was to conjure up the classic "Pong Arcade Game", utilizing all the knowledge and skills I've garnered thus far. Angela didn't introduce any fresh concepts and today was all concentrated on the game development. So, I'll dive into some of the riveting and tricky bits of this venture, with examples and my thoughts on why it was a pain in the ass at certain points 😄.</p>
<h2 id="heading-dissecting-the-project"><strong>Dissecting the Project</strong></h2>
<p>Oddly enough, this might have been the least technical segment of the task, yet for some reason, my initial estimations on how to segment this project were off a bit, enough to make me double back to some of my previous notes and older lectures. The project was ultimately sectioned into the following stages:</p>
<ul>
<li><p>Create the game screen</p>
</li>
<li><p>Create the first paddle and add movement controls</p>
</li>
<li><p>Create another paddle</p>
</li>
<li><p>Create the ball and make it start moving left or right</p>
</li>
<li><p>Detect collision with the wall and bounce from that position</p>
</li>
<li><p>Detect collision with a paddle and bounce from that position</p>
</li>
<li><p>Detect when the paddle misses</p>
</li>
<li><p>Keep the score</p>
</li>
</ul>
<p>As you can see, this project is a bit more challenging than the snake game but once I finally completed it, I was happy with the end result and the skills I was able to practice!</p>
<h2 id="heading-it-either-made-sense-or-it-didnt"><strong>It Either Made Sense, Or It Didn't</strong></h2>
<p>Parts of this project came together quite effortlessly. We've been tinkering with the turtle module for a while now and recent projects have been a breeze to kickstart. Setting up the file structure, basic imports, variables, and event handlers was a piece of cake. But, when it came to detecting the collision with the top and bottom walls as well as collision with the paddles, it was quite the mental task. I found myself either over-analyzing or underthinking and needed a bit of a helping hand. For instance, devising the logic for when the ball strikes the paddle proved to be the most challenging segment of this project for me. I had to take into account the dimensions of both the ball and the paddle while devising the logic. Another obstacle was resetting the ball's position once a score was made, while simultaneously making it move in the opposite direction to give the other player a fair start on the next round. Here's a glimpse at how the ball and bounce logic was constructed:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> turtle <span class="hljs-keyword">import</span> Turtle


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Ball</span>(<span class="hljs-params">Turtle</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        super().__init__()
        self.shape(<span class="hljs-string">"circle"</span>)
        self.penup()
        self.color(<span class="hljs-string">"white"</span>)
        self.x_move = <span class="hljs-number">10</span>
        self.y_move = <span class="hljs-number">10</span>
        self.movement = <span class="hljs-number">0.1</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">move</span>(<span class="hljs-params">self</span>):</span>
        new_x = self.xcor() + self.x_move
        new_y = self.ycor() + self.y_move
        self.goto(new_x, new_y)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">bounce</span>(<span class="hljs-params">self</span>):</span>
        self.y_move *= <span class="hljs-number">-1</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">paddle_bounce</span>(<span class="hljs-params">self</span>):</span>
        self.x_move *= <span class="hljs-number">-1</span>
        self.movement *= <span class="hljs-number">0.7</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">reset_position</span>(<span class="hljs-params">self</span>):</span>
        self.goto(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>)
        self.movement = <span class="hljs-number">0.1</span>
        self.paddle_bounce()
</code></pre>
<p>Additionally, in the code snippet above, you can see that I have a movement attribute that sets the ball's initial speed, and, each time it hits a paddle, the speed is ramped up. The speed, along with the position, is reset when someone scores. There were a few more areas that required a fair amount of thought, but overall, this was an engaging project. You can check out the final product here -&gt; <a target="_blank" href="https://github.com/kdleonard93/100-Days-Of-Code_Python/tree/main/day-22"><strong>https://github.com/kdleonard93/100-Days-Of-Code_Python/tree/main/day-22</strong></a> and here's a short gif of me playing a round of pong solo.</p>
<p><img src="https://p21.f4.n0.cdn.getcloudapp.com/items/qGu6DKlx/080eaab1-18e5-41b2-8a71-2049e3a17185.gif?source=viewer&amp;v=f1f041bbdb24f12ac5c45c82bd6a93dd" alt="Screen Recording 2023-06-13 at 11.57.00 PM" /></p>
<p>Due to limitations of the free version of Zight, the gif only spans 15 seconds and I didn't want to use my work account for personal stuff so you only get to witness one point being scored lol. Here's hoping that Loom introduces gif-making capabilities soon.</p>
<h2 id="heading-eod"><strong>EOD</strong></h2>
<p>Cheers to another Python game accomplished! It was indeed tough, but it was a significant step in consolidating recent knowledge and realizing that there is still more to learn. I've been spending some time on <a target="_blank" href="http://codewars.com">codewars.com</a> to improve my skills and I'm contemplating devoting one or two of my weekly sessions to working on more practice problems rather than rushing into new concepts. This way, I'll be able to thoroughly grasp every aspect of this journey. Peace out! ✌🏾</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-progress-or-just-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow">If you want to keep up with my progress or just want to connect as peers, check out my social links below and give me a follow!</h4>
<ul>
<li><p><a target="_blank" href="https://twitter.com/RingoMandingo93"><strong>🐦 Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Day 21 - Building the Snake Game Pt 2]]></title><description><![CDATA[Today's Objective
As I mentioned yesterday today we learned about class inheritance and how we're going to use it to build out the other classes to keep score of the game and to detect collisions with the food, the wall, and the snake's tail.
Class I...]]></description><link>https://blacknerd.dev/day-21-building-the-snake-game-pt-2</link><guid isPermaLink="true">https://blacknerd.dev/day-21-building-the-snake-game-pt-2</guid><category><![CDATA[100DaysOfCode]]></category><category><![CDATA[Python]]></category><category><![CDATA[Blogging]]></category><category><![CDATA[Object Oriented Programming]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Fri, 09 Jun 2023 03:18:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1686272442303/a4acfcea-ed37-4f23-b385-6b6d4d5304e7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-todays-objective">Today's Objective</h2>
<p>As I mentioned yesterday today we learned about class inheritance and how we're going to use it to build out the other classes to keep score of the game and to detect collisions with the food, the wall, and the snake's tail.</p>
<h4 id="heading-class-inheritance">Class Inheritance</h4>
<p>To sum it up: Class Inheritance is the ability to inherit the properties of another class in a newly created one. Imagine you have a class called <code>Parent</code> that has special features and methods. Now, you can make another class, let's call it the <code>Child</code> class, that can utilize everything from the <code>Parent</code> class and can add its own unique methods. So, instead of starting from scratch every time, we can reuse and extend the features of existing classes to make our code more efficient and organized.</p>
<p>To get this initialized you would have to call <code>super().__init__()</code> within the 1 <code>__init__</code> function. I've added a code snippet to give a simple visual:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Parent</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, parent_attribute</span>):</span>
        self.parent_attribute = parent_attribute

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Child</span>(<span class="hljs-params">Parent</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, parent_attribute, child_attribute</span>):</span>
        super().__init__(parent_attribute)
        self.child_attribute = child_attribute

confession = Child(<span class="hljs-string">"I'm the Pappy!"</span>, <span class="hljs-string">"That's the truth!"</span>)


print(confession.parent_attribute)
print(confession.child_attribute)
</code></pre>
<p>The above code will print:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686278185393/b527df6e-df98-4f4a-b223-2470b16ff689.png" alt class="image--center mx-auto" /></p>
<p>This is very useful for minimizing the need to rinse and repeat for class and method creation.</p>
<h2 id="heading-the-project">The Project</h2>
<p>So today we need to finalize the game by completing 4 objectives:</p>
<ol>
<li><p>Detect food collision.</p>
</li>
<li><p>Create a scoreboard.</p>
</li>
<li><p>Detect collision with the wall.</p>
</li>
<li><p>Detect collision with the snake's tail.</p>
</li>
</ol>
<p>This called for the creation of some new files and classes that can be found in the <code>day-20</code>'s folder (since I'm not starting today in a new folder). Detecting the food and re-assigning it to a random spot inherited methods from the <code>Turtle()</code> class. We used that to create a new turtle and customized it to represent food. We created <code>refresh</code> method as well that moves the food to a random X &amp; Y coordinate once the head of the snake was <code>{number-value}</code> away from it depending on what you set. The same thing goes for the scoreboard in which we created a new <code>Turtle,</code> but, had to dive into the docs again to alter it to work as a scoreboard. Finally, added the work to detect wall and tail collision to wrap up!</p>
<p>You can find the final project here -&gt; <a target="_blank" href="https://github.com/kdleonard93/100-Days-Of-Code_Python/pull/12">https://github.com/kdleonard93/100-Days-Of-Code_Python/pull/12</a>. If you want, clone the code from Day 20 and give the game a try yourself to see what high score your can get!</p>
<h2 id="heading-eod"><strong>EOD</strong></h2>
<p>There's the game in all its glory! Gonna go ahead and find a way to throw this up on the App Store and start raking in secondary income 😂. But all jokes aside, creating this game was pretty fun, and was actually able to apply a bit of what I learned at work earlier today 😁.</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-progress-or-just-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow">If you want to keep up with my progress or just want to connect as peers, check out my social links below and give me a follow!</h4>
<ul>
<li><p><a target="_blank" href="https://twitter.com/RingoMandingo93"><strong>🐦 Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93"><strong>💻 Github</strong></a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570"><strong>👾 Discord</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/"><strong>👔 LinkedIn</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Day 20 - Building the Snake Game: Part 1]]></title><description><![CDATA[The Objective
For the following days (Day 20 and Day 21), the objective is the build the classic Snake game. Angela breaks down the goals for the and part one will consist of:

Creating the snake body by creating 3 squares on the screen last a given ...]]></description><link>https://blacknerd.dev/day-20-building-the-snake-game-part-1</link><guid isPermaLink="true">https://blacknerd.dev/day-20-building-the-snake-game-part-1</guid><category><![CDATA[100DaysOfCode]]></category><category><![CDATA[Python]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[backend]]></category><category><![CDATA[#codenewbies]]></category><dc:creator><![CDATA[Kyle L]]></dc:creator><pubDate>Wed, 07 Jun 2023 13:25:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1686141688240/52bd385b-80d9-46d2-ab21-448d1442df0b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-objective">The Objective</h2>
<p>For the following days (Day 20 and Day 21), the objective is the build the classic Snake game. Angela breaks down the goals for the and part one will consist of:</p>
<ol>
<li><p>Creating the snake body by creating 3 squares on the screen last a given starting point.</p>
</li>
<li><p>Moving the snake forward without breaking.</p>
</li>
<li><p>Finally, add the ability to turn the snake.</p>
</li>
</ol>
<p>There weren't any new concepts discussed in today's lesson, but, looking at Day 21, we touch base on <code>Class Inheritance</code>.</p>
<h2 id="heading-making-the-snake">Making the Snake</h2>
<p>This project has to be one of the hardest I've tried yet. More so on all the connected parts than the lack of understanding of certain concepts. We built the snake in our main.py file initially but then migrated the snake build into its own file and <code>Class</code>.</p>
<h4 id="heading-snake-class">Snake Class:</h4>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Snake</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        self.full_snake = []
        self.create_snake()
        self.head = self.full_snake[<span class="hljs-number">0</span>]

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_snake</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">for</span> position <span class="hljs-keyword">in</span> STARTING_POSITION:
            new_snake = Turtle(shape=<span class="hljs-string">"square"</span>)
            new_snake.penup()
            new_snake.goto(position)
            self.full_snake.append(new_snake)
</code></pre>
<p>After getting it created, we worked on the movement. This part is where I struggled a bit due to needing to work with <code>X</code> and <code>Y</code> coordinates. Trying to get the 3 squares to align like a snake and then move smoothly took some more diving into the <a target="_blank" href="https://docs.python.org/3/library/turtle.html">Python Turtle Graphics doc</a>. After the snake got moving, we worked on adding the ability to turn it, which was a little bit easier to grasp and code up. After a couple of hours of early AM work, finished part 1! The snake moves forward and can turn without turning on itself.  </p>
<p>Video: <a target="_blank" href="https://www.loom.com/share/5f624e84854649e7be0201261efec7f4">Snake Game Part 1 Example</a>  </p>
<p>Since I'm working on more than one file for this project, I won't be posting everything I wrote today but you can find it in the <code>Day-19</code> folder on GitHub -&gt; <a target="_blank" href="https://github.com/kdleonard93/100-Days-Of-Code_Python/tree/day-20/day-20">https://github.com/kdleonard93/100-Days-Of-Code_Python/tree/day-20/day-20</a></p>
<h2 id="heading-eod">EOD</h2>
<p>That wraps up Part 1 of the Snake Project. Since I worked on this before my normal work hours, there's a chance I'll be posting Part 2 (Day 21) later this evening. There I will make note of the new concept <code>Class Inheritance</code> and wrap up my first full Python game!</p>
<h4 id="heading-if-you-want-to-keep-up-with-my-progress-or-just-want-to-connect-as-peers-check-out-my-social-links-below-and-give-me-a-follow">If you want to keep up with my progress or just want to connect as peers, check out my social links below and give me a follow!</h4>
<ul>
<li><p><a target="_blank" href="https://twitter.com/RingoMandingo93">🐦 Twitter</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kdleonard93">💻 Github</a></p>
</li>
<li><p><a target="_blank" href="https://discord.com/users/407639833146818570">👾 Discord</a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/kyle-leonard93/">👔 LinkedIn</a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>