<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jason Purdy</title>
    <description>The latest articles on DEV Community by Jason Purdy (@purdy).</description>
    <link>https://dev.to/purdy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F689736%2Fe2367508-76b6-4ef3-acd2-7a2bbdb18e03.jpeg</url>
      <title>DEV Community: Jason Purdy</title>
      <link>https://dev.to/purdy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/purdy"/>
    <language>en</language>
    <item>
      <title>Cronjob Oversight</title>
      <dc:creator>Jason Purdy</dc:creator>
      <pubDate>Wed, 12 Nov 2025 02:29:20 +0000</pubDate>
      <link>https://dev.to/purdy/cronjob-oversight-2j8j</link>
      <guid>https://dev.to/purdy/cronjob-oversight-2j8j</guid>
      <description>&lt;p&gt;I just got back from &lt;a href="https://phpconference.com/munich/" rel="noopener noreferrer"&gt;the iPC/iJS conference in Munich&lt;/a&gt; and had a great time. I brought back 7 lessons I learned (more on that later) and inspiration for other things. One of them was to get a handle of managing all of our cronjobs. We have 2 servers and several Elastic Beanstalk applications, all of which have their own cronjob setups. I've been having them email a group email address and tucking them away and reviewing them every once in a while to see if there were any errors. This led me to the thought that there's got to be a better way. I started thinking about how I could append a ping to a centralized service after each cron job and that service could monitor the jobs and then ping me if anything goes missing. Then I thought, wait a second ... maybe someone else has already done this. I DDG'd (DuckDuckGo'd) it and came across &lt;a href="https://uptimerobot.com/blog/best-cronjob-monitoring-tools/" rel="noopener noreferrer"&gt;this writeup&lt;/a&gt; with several solutions. I checked out &lt;a href="https://healthchecks.io/" rel="noopener noreferrer"&gt;Healthchecks.io&lt;/a&gt; and upon digging &lt;a href="https://healthchecks.io/about/" rel="noopener noreferrer"&gt;deeper&lt;/a&gt;, saw that their software was available via &lt;a href="https://hub.docker.com/r/healthchecks/healthchecks" rel="noopener noreferrer"&gt;a Docker image&lt;/a&gt; that you could self-host.&lt;/p&gt;

&lt;p&gt;So I created a new Elastic Beanstalk Docker application with this Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; healthchecks/healthchecks:latest&lt;/span&gt;

&lt;span class="c"&gt;# Expose port 8000 (default for healthchecks)&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;

&lt;span class="c"&gt;# Environment variables for healthchecks configuration&lt;/span&gt;
&lt;span class="c"&gt;# You can customize these as needed&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; SECURE_PROXY_SSL_HEADER=HTTP_X_FORWARDED_PROTO,https \&lt;/span&gt;
    DEBUG=False \
    DEFAULT_FROM_EMAIL=healthchecks@example.com \
    SECRET_KEY=RANDOM_SSL_SECRET_KEY \
    ADMINS=admin@example.com \
    DB=mysql \
    DB_HOST={{DB_HOST}} \
    DB_NAME={{DB_NAME}} \
    DB_PASSWORD={{DB_PASSWORD}} \
    DB_USER={{DB_USER}} \
    EMAIL_HOST={{STMP_HOST}} \
    EMAIL_HOST_USER={{STMP_USER}} \
    EMAIL_HOST_PASSWORD={{STMP_PASSWORD}} \
    SITE_NAME="Example Healthchecks" \
    SITE_ROOT={{SITE_ROOT}} \
    RP_ID=hc.example.com

&lt;span class="c"&gt;# The healthchecks image already has the entrypoint configured&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I spun up a single spot instance environment for the application and it generated a domain and then I was able to sign up on the website and get going with setting up the various cronjobs. The process starts with creating a check and it will give you the URL and Usage Examples on how to integrate the ping with the cronjob. Most of my cronjobs were in /etc/crontab, so I just appended the &lt;code&gt;&amp;amp;&amp;amp; curl ...&lt;/code&gt; cmd to the end of each one with the specific cronjob.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcsdm47ds2867k2byaz5q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcsdm47ds2867k2byaz5q.png" alt="Usage Examples" width="800" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've been amazed how much thought and features are baked into the software. Yes, it can send you email, but it can also notify you via Slack, Google Chat, Slack, Microsoft Teams, etc. It has passkey authentication support, timezone support, badges, and great documentation.&lt;/p&gt;

&lt;p&gt;So now I have all of our various cronjobs plugged in. Come to find out, we have 55!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzuc9k69ugm9lpvoormd7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzuc9k69ugm9lpvoormd7.png" alt="Cronjob Healthcheck Dashboard" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And through the inventory process, I found that there was a cronjob that had not been running successfully for quite a while, so this was a great exercise.&lt;/p&gt;

&lt;p&gt;For extra measure, I set up a new subdomain and pointed the A record to an alias of the Elastic Beanstalk, which allowed me to have a dedicated hostname. I also wanted SSL/TLS, so that required some certbot setup.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover Image Credit: &lt;a href="https://www.pickpik.com/alarm-clock-museum-a-collection-of-an-antique-clock-gray-88737" rel="noopener noreferrer"&gt;https://www.pickpik.com/alarm-clock-museum-a-collection-of-an-antique-clock-gray-88737&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
    </item>
    <item>
      <title>Grok'ing Stripe</title>
      <dc:creator>Jason Purdy</dc:creator>
      <pubDate>Mon, 31 Jul 2023 23:05:00 +0000</pubDate>
      <link>https://dev.to/purdy/groking-stripe-5hdi</link>
      <guid>https://dev.to/purdy/groking-stripe-5hdi</guid>
      <description>&lt;p&gt;I think I'm going to do a series of posts here on Stripe, as we're in the middle of developing an e-commerce site from scratch. I know paths have been developed before me and I have found a few helpful ones, but I'm also hoping that it can be of help to others as well as attract any of those other path builders to weigh in with their own advice.&lt;/p&gt;

&lt;p&gt;Our use case is that we're developing an online marketplace for sellers to post their products and buyers to come buy them. Stripe has a lot of offerings and features for marketplaces that make things easy to handle. Additionally, they have their own tax product that helps calculate and record tax transactions to make it easier to file and remit sales tax.&lt;/p&gt;

&lt;p&gt;Apologies if this is starting to sound like a commercial for Stripe (it slices, it dices, and soo much more!). I am not a Stripe employee or paid by Stripe for posting this.&lt;/p&gt;

&lt;p&gt;So sellers come to our site and register, but before they can post items for sale, they need to connect their Stripe account to our site. There are different account types: Standard, Express, and Custom. We are currently using Standard accounts, which means the seller has a longer on-boarding process, with providing their own financial information before being able to accept payouts. Express accounts are more streamlined, but we would have to pay $2/month for each account. I didn't really dive further into Custom accounts because Standard accounts seem to work just fine for our use case. And if in the process of connecting their Stripe account, they already have a Stripe account, then they just need to log in. After going through the on-boarding process or logging in, they return to our site along with their Stripe account ID, which we can use to transfer money back and forth.&lt;/p&gt;

&lt;p&gt;On the flipside, buyers put their items in a cart, which breaks down into one or more orders based on the items' sellers. So if the buyer is buying 3 items from 2 sellers, then there are 2 orders. And then there are line items for each item. We also use the Stripe Tax API to calculate tax based on the shipping address and our own company's tax registrations (which are managed in our own Stripe account).&lt;/p&gt;

&lt;p&gt;For the purchasing process, with Stripe, I'm using the Address and Payment Elements on a custom checkout page along with a SetupIntent, which will record the payment method. The user places the order and sees an "Order Placed" screen that says they'll receive confirmation via email for their order. When we receive the SetupIntent via webhooks, we generate a PaymentIntent per order for the amount with a manual capture. If that was successful, we notify the seller and send a receipt to the buyer. If it wasn't successful, we will send an email to the buyer to send them back to the cart to remit payment again.&lt;/p&gt;

&lt;p&gt;When the seller receives their order notification, they can drop line items (i.e. the item is no longer available) or cancel orders altogether. They can also mark an order as shipped, which will then capture the authorized funds from the PaymentIntent. If they dropped any items from the order, we capture the partial amount, which "refunds" the difference. We will also record a tax transaction for future filing and remitting.&lt;/p&gt;

&lt;p&gt;So that's the high-level overview. In future posts, I will be breaking it down into more details.&lt;/p&gt;

</description>
      <category>stripe</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Have you heard about Glitch? Or colab?</title>
      <dc:creator>Jason Purdy</dc:creator>
      <pubDate>Tue, 31 May 2022 12:46:13 +0000</pubDate>
      <link>https://dev.to/purdy/have-you-heard-about-glitch-or-colab-5dph</link>
      <guid>https://dev.to/purdy/have-you-heard-about-glitch-or-colab-5dph</guid>
      <description>&lt;p&gt;I may be late to the party here, but I've been going through &lt;a href="https://www.edx.org/course/google-ai-for-javascript-developers-with-tensorflowjs"&gt;an awesome class on EdX.org on ML&lt;/a&gt; and for some of the exercises, we use &lt;a href="https://glitch.com/"&gt;glitch.com&lt;/a&gt;, which I had never heard of before. But it's a playground kind of site where you can put together a prototype of a JavaScript app (and perhaps other tech stacks are supported, too, but I was just using JS) and it provides a live, hosted, and sharable preview.&lt;/p&gt;

&lt;p&gt;Another tool I learned in the process was &lt;a href="https://colab.research.google.com/"&gt;colab&lt;/a&gt;, which is similar, but is a full-blown hosted environment for Python projects.&lt;/p&gt;

</description>
      <category>tooling</category>
    </item>
    <item>
      <title>One small step forward w/ Elastic Beanstalk</title>
      <dc:creator>Jason Purdy</dc:creator>
      <pubDate>Thu, 05 May 2022 14:25:38 +0000</pubDate>
      <link>https://dev.to/purdy/one-small-step-forward-w-elastic-beanstalk-223b</link>
      <guid>https://dev.to/purdy/one-small-step-forward-w-elastic-beanstalk-223b</guid>
      <description>&lt;p&gt;Finally got my PHP app running &amp;amp; setup (just the code bits .. still need to work on other configs + database), but wanted to share some of my findings in the case it may be helpful to others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup SSH
&lt;/h2&gt;

&lt;p&gt;This is key (pun intended ;)) because it can help you see what's going on, versus making a change, uploading it, see if it works, if not, request the logs, and go through them. There are a few steps to get SSH working:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create key pair: &lt;code&gt;ssh-keygen -b 2048 ~/.ssh/elastic-beanstalk&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create key file config in .ebextensions (I called mine &lt;code&gt;.ebextensions/02_setup_ssh.config&lt;/code&gt;) and it looks like #1 below.&lt;/li&gt;
&lt;li&gt;Open the SSH port. This is another config file (I called mine &lt;code&gt;.ebextensions/03_open_sshport.config&lt;/code&gt;) and it looks like #2 below.&lt;/li&gt;
&lt;li&gt;Find the IP address from your EC2 instances to connect to and then connect: &lt;code&gt;ssh -i "~/.ssh/elastic-beanstalk" root@[IP ADDRESS]&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Container commands
&lt;/h2&gt;

&lt;p&gt;One thing that was tripping me up was that my PHP app (which is a Codeigniter v4 framework app) has a different project layout, where the root folder has various config stuff and then the CodeIgniter app is installed in a &lt;code&gt;ci4&lt;/code&gt; subdirectory (which is where the &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;composer.json&lt;/code&gt; files were), and the public webroot is a subdirectory from that, too.&lt;/p&gt;

&lt;p&gt;So after the code is extracted, but before deployment, I need to do the &lt;code&gt;composer install&lt;/code&gt; and &lt;code&gt;npm install&lt;/code&gt;. So I had another .ebextensions config to run container commands to do those, but I was using the static path of &lt;code&gt;/var/app/current/ci4&lt;/code&gt;, which was affecting the current deployment and not the staged code. So then when EB deployed the staged code, it didn't have the expected &lt;code&gt;node_modules&lt;/code&gt; or &lt;code&gt;vendor&lt;/code&gt; directories. This took a &lt;em&gt;lot&lt;/em&gt; of back &amp;amp; forth, but eventually, I got it working and it looks like #3 below. Oh, and as a bonus with #3, it also gets nodejs (which includes npm) installed, which was something I was wrestling with earlier.&lt;/p&gt;

&lt;p&gt;#1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;files:
  "/root/.ssh/authorized_keys" :
    mode: "000600"
    owner: root
    group: root
    content: |
      [contents from your elastic-beanstalk.pub file]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;#2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Resources:
  sslSecurityGroupIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
      IpProtocol: tcp
      ToPort: 443
      FromPort: 443
      CidrIp: 0.0.0.0/0
  sshSecurityGroupIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
      IpProtocol: tcp
      ToPort: 22
      FromPort: 22
      CidrIp: 0.0.0.0/0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(I went ahead and added 443 for setting up HTTPS later)&lt;/p&gt;

&lt;p&gt;#3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;commands:
  00_install_curl:
    command: sudo yum -y install curl
  01_nodejs_setup:
    command: curl -sL https://rpm.nodesource.com/setup_14.x | sudo bash -
  02_install_nodejs:
    command: sudo yum -y install nodejs
  03_enable_epel:
    command: sudo amazon-linux-extras install epel

container_commands:
  01_npm_install:
    command: 'cd ci4 &amp;amp;&amp;amp; npm install'
    test: '[ ! -d ci4/node_modules ] &amp;amp;&amp;amp; echo "node_modules files not installed"'
  02_composer_install:
    command: 'cd ci4 &amp;amp;&amp;amp; /usr/bin/composer.phar install'
    test: '[ ! -d ci4/vendor ] &amp;amp;&amp;amp; echo "vendor files not installed"'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>aws</category>
      <category>elasticbeanstalk</category>
    </item>
    <item>
      <title>Amazon Elastic Beanstalk Woes</title>
      <dc:creator>Jason Purdy</dc:creator>
      <pubDate>Wed, 04 May 2022 21:35:30 +0000</pubDate>
      <link>https://dev.to/purdy/amazon-elastic-beanstalk-woes-29k3</link>
      <guid>https://dev.to/purdy/amazon-elastic-beanstalk-woes-29k3</guid>
      <description>&lt;p&gt;Lots of head-banging today, trying to get a PHP app up &amp;amp; running.&lt;/p&gt;

&lt;p&gt;First, my app has its webroot in a sub-directory. I could not get &lt;code&gt;.ebextensions/nginx/nginx.conf&lt;/code&gt; or &lt;code&gt;.ebextensions/nginx/conf.d/mysettings.conf&lt;/code&gt; to specify the &lt;code&gt;root&lt;/code&gt;. I ended up finding a configuration in Elastic Beanstalk itself that specified the webroot, so I was able to set it to &lt;code&gt;/var/app/current/[MYSUBDIR]&lt;/code&gt;. But it took lots of attempts before I got that right.&lt;/p&gt;

&lt;p&gt;Now I'm trying to get npm installed so I can install some files I have in my package.json, but npm depends on nodejs, which when I try to install this, it complains that it requires libuv &amp;gt;= 1:1.43.0. So now I gotta figure out how to either fast-forward the Amazon AMI to &lt;a href="https://docs.aws.amazon.com/linux/al2022/release-notes/all-packages-al2022-20220419.html"&gt;this version&lt;/a&gt; that has that version of libuv, or compile/install libuv directly, or install a downgraded version of nodejs. But I mean ... com'on ... I'm just trying to get npm installed. Why is this so difficult?&lt;/p&gt;

&lt;p&gt;Not a good day ... I'm thinking if I ever do get this all figured out, I'll publish some straight-forward docs.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>elasticbeanstalk</category>
    </item>
    <item>
      <title>Sublime Merge Keymap</title>
      <dc:creator>Jason Purdy</dc:creator>
      <pubDate>Thu, 28 Apr 2022 13:55:59 +0000</pubDate>
      <link>https://dev.to/purdy/sublime-merge-keymap-36l</link>
      <guid>https://dev.to/purdy/sublime-merge-keymap-36l</guid>
      <description>&lt;p&gt;Wanted to share a useful Sublime Merge config. You can create your own key mappings to perform various tasks. One of my frequently-used tasks is to &lt;code&gt;git fetch --all --prune&lt;/code&gt; to bring my local box up-to-date with the various work of others. So I created a key mapping so I can do a Shift + ⌘ + r (think force refresh from the browser) inside Sublime Merge to do just that.&lt;/p&gt;

&lt;p&gt;To do this, select the &lt;code&gt;Edit Key Bindings...&lt;/code&gt; menu option under the &lt;code&gt;Sublime Merge&lt;/code&gt; / &lt;code&gt;Preferences&lt;/code&gt; menu:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frk010k6opblyy135wyrg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frk010k6opblyy135wyrg.png" alt="Screenshot showing the menu option"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is what my keymap looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  {
    "keys": ["shift+command+r"],
    "command": "git",
    "args": {"argv": ["fetch", "--all", "--prune"]}
  }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is Mac-specific, but you could do the same with control vs. command for Windows/Linux.&lt;/p&gt;

</description>
      <category>sublimemerge</category>
    </item>
    <item>
      <title>Hello World</title>
      <dc:creator>Jason Purdy</dc:creator>
      <pubDate>Thu, 19 Aug 2021 16:48:17 +0000</pubDate>
      <link>https://dev.to/purdy/hello-world-i25</link>
      <guid>https://dev.to/purdy/hello-world-i25</guid>
      <description>&lt;p&gt;Just saying hi and seeing how this works. It's Markdown, so I'm familiar w/ that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Hello, World!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;&lt;a href="https://commons.wikimedia.org/wiki/File:TI_BASIC_HELLO_WORLD.png"&gt;Cover image&lt;/a&gt; is in the public domain.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
